Pergunta

I'm writing some highly portable security code. I'm trying to avoid security flaw in a utility program such as this one found in some versions of sudo:

... it is possible to become the super user by running sudo -k and then resetting the system clock to 01-01-1970.

This happens because sudo relies on absolute (aka calendar) time to determine whether or not access has timed out.

My idea is to use CLOCK_MONOTONIC defined in time.h.

From the POSIX standard,

[CLOCK_MONOTONIC is] defined as a clock whose value cannot be set via clock_settime() and which cannot have backward clock jumps. The maximum possible clock jump shall be implementation-defined.

Problem is, on many (most?) systems, CLOCK_MONOTONIC resets on reboot. Is there any guaranteed POSIX-compliant way to determine whether or not the system has rebooted since a program last ran?

One (bad) way is to check whether or not the stored clock value is greater than the current clock value, however this just shifts the problem. On systems where CLOCK_MONOTONIC resets on reboot, there could be a short window of length TIMEOUT where access would be permitted.

What am I missing that would avoid this problem?

Foi útil?

Solução

It seems to me this is straightforward to do using a POSIX shared memory object:

POSIX shared memory objects have kernel persistence: a shared memory object will exist until the system is shut down, or until all processes have unmapped the object and it has been deleted with shm_unlink

Whenever your program launches it can shm_open a new object with some consistent name and set the owner to root. The object needn't contain any particular value. POSIX requires that all shared memory objects persist until reboot unless manually destroyed (which only its owner or creator can do...which in this case is the root user).

Whenever your program launches it first checks if such a shared memory object already exists having root as the owner. Since only root could create such an object, and only root or a reboot could destroy it, you can thus know for certain whether your program has been launched since the last reboot, save the only possible circumvention being the root user invoking shm_unlink on the object manually.

I wrote a test-and-set function below that should do exactly what you need. And it works except for the ownership setting/detection: for some unknown reason both calls to shmctl are failing on my system, saying "invalid argument". The man page for shmctl says the EINVAL error indicates either an invalid memory object identifier or an invalid command. But the IPC_SET and IPC_STAT commands are certainly valid, and you can watch the program's output to see the valid object identifier that is being created and/or opened each time.

#include <sys/shm.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>

int rebooted_test_and_set() {
    int err;
    int rebooted;
    struct shmid_ds shmst;
    // create object if nonexistent, returning failure if already exists
    int shmid = shm_open("/bootcheck", O_CREAT | O_EXCL);
    if (shmid != -1) {
        fprintf(stderr, "bootcheck object did not exist, so created: %d\n", shmid);
        // object did not exist, so system has been rebooted
        rebooted = 1;
        // set owner to root, and no permissions for anyone
        shmst.shm_perm.uid = 0;
        shmst.shm_perm.gid = 0;
        shmst.shm_perm.mode = 0;
        if ((err = shmctl(shmid, IPC_SET, &shmst)) == -1) {
            perror("shmctl: shmctl failed to set owner and permissions for bootcheck object");
            exit(1);
        }
    } else {
        // object already exists, so reopen with read access and verify that the owner is root
        shmid = shm_open("/bootcheck", O_RDONLY);
        if (shmid == -1) {
            perror("shm_open: failed, perhaps due to insufficient privileges");
            exit(1);
        }
        fprintf(stderr, "bootcheck object (%d) exists, so checking ownership\n", shmid);
        if ((err = shmctl(shmid, IPC_STAT, &shmst)) == -1) {
            perror("shmctl: shmctl failed");
            exit(1);
        }
        if (shmst.shm_perm.uid == 0) {
            // yes, the bootcheck owner is root,
            // so we are confident the system has NOT been rebooted since last launch
            rebooted = 0;
        } else {
            // uh oh, looks like someone created the object illegitimately.
            // since that is only possible if the root-owned object did not exist, 
            // therefore we know that it never did exist since the last reboot
            rebooted = 1;
        }
    }
    return rebooted;
}

// for debugging purposes ONLY, so I don't have to keep rebooting to clear the object:
void rebooted_clear() {
    if (shm_unlink("/bootcheck") == -1) {
        perror("shm_unlink: failed, probably due to insufficient privileges or object nonexistent");
        exit(1);
    }
}

int main() {
    int rebooted = rebooted_test_and_set();
    printf("rebooted since last launch: %d\n", rebooted);
    return 0;
}

If anyone has any clues, I'm stumped. Some information and examples for POSIX shared memory here.

Outras dicas

In this python library they look for the last BOOT_TIME entry in utmp. Technically as what is in POSIX is utmpx (the file format) and the libc functions for accessing it. I think this is as good as you can get staying within POSIX.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top