12

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?

Ben Burns
  • 14,978
  • 4
  • 35
  • 56
  • 1
    For others who do not need guaranteed POSIX compatibility: `/proc/sys/kernel/random/boot_id` is probably what you are looking for. @BenBurns: You could create a file in some "guaranteed temporary" location if there is one, if the file isn't present, the system was rebooted. Otherwise, if you have a permanently running process, assume the system was not rebooted while your process was running, and assume the system might have been rebooted if your process has freshly started. – Jan Schejbal Dec 07 '13 at 23:43
  • Couldn't you use a combination of system time and `CLOCK_MONOTONIC` to detect clock skew and/or system reboot and force re-authentication in either of those circumstances? – mpontillo Dec 09 '13 at 21:16
  • Another idea might be to fork a long-lived process (similar to the way ssh-agent works) and do IPC to it. – mpontillo Dec 09 '13 at 21:18
  • Or use [authenticated NTP](http://www.nist.gov/pml/div688/grp40/auth-ntp.cfm)! (ok, stretching the limits of POSIX here) – mpontillo Dec 09 '13 at 21:25
  • I'd love a POSIX analog to `/proc/sys/kernel/random/boot_id`. I take it from the lack of answers that none exists. @Mike keep in mind that the goal here is to only execute the utility program when it is needed. Solutions which require a long-running process, etc, aren't useful. Something which can be accessed statelessly and doesn't live in userspace would be ideal (like linux's boot id). – Ben Burns Dec 10 '13 at 18:24
  • utmp is the POSIX way of finding out when the system booted but if you don't trust root (i.e. someone who can change the time) then who do you trust? You need something like SELinux if you want to curb the ambitions of UID 0. – abasterfield Dec 11 '13 at 21:49
  • Just a different approach: have you considered just instantiating a thread with it's own relative timer to determine when a particular session should time out? Thereby eliminating the need to look at any clock at all? The thread would obviously disappear upon reboot and it wouldn't matter if they reset the clock as it's counting it's own ticks. Just a thought. – NotMe Dec 12 '13 at 17:31
  • @Chris Lively, the goal is to allow a short-lived process to reliably detect a system reboot between runs. Using a long-running process to handle this is not ideal, and it effectively changes the question. Bootid would be perfect, but barring that jrodatus' strategy does exactly what I need. – Ben Burns Dec 13 '13 at 17:23

2 Answers2

10

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.

  • Pardon me, I just realized that you may not be trusting root, in which case my entire idea is useless to you. Sorry. –  Dec 12 '13 at 05:22
  • 4
    Not trusting root would make any attempt at security on that system pretty pointless, since root can, by definition, do anything. You provided an excellent answer! – Jan Schejbal Dec 13 '13 at 14:33
  • @jrodatus, it has been a long time, but I just noticed your comment. This would obviously only help complete the `CLOCK_MONOTONIC` strategy to avoid the hole of short-lived processes storing and checking system time for timeouts where a *non-root* user is able to change system time. – Ben Burns Apr 09 '16 at 21:16
1

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.

abasterfield
  • 2,214
  • 12
  • 17
  • 1
    You sure about that? I'm no POSIX expert, but according to the Wikipedia entry on utmp, "The utmp, wtmp and btmp files were never a part of any official Unix standard, such as Single UNIX Specification" –  Dec 12 '13 at 00:22
  • utmpx is the POSIX file format and the filename varies between platforms There are also POSIX libc functions for accessing the file which abstract away it's actual filename. – abasterfield Dec 12 '13 at 00:32