POSIX-совместимый способ узнать, перезагружена ли система?

Я пишу очень портативный код безопасности. Я пытаюсь избежать недостатков безопасности в служебной программе, например этот, найденный в некоторых версиях sudo:

... можно стать суперпользователем, запустив sudo -k, а затем сбросив системные часы до 01-01-1970.

Это происходит потому, что sudo полагается на абсолютное время (aka calendar), чтобы определить, был ли время ожидания доступа.

Моя идея - использовать CLOCK_MONOTONIC, определенный в time.h.

Из стандарта POSIX,

[CLOCK_MONOTONIC] определяется как часы, значение которых не может быть установлено через clock_settime() и которые не могут иметь обратные тактовые импульсы. Максимально возможный такт будет определяться реализацией.

Проблема во многих (наиболее?) системах, CLOCK_MONOTONIC сбрасывается при перезагрузке. Есть ли какой-либо гарантированный POSIX-способ для определения того, перезагрузилась ли система с момента запуска последней программы?

Один (плохой) способ - проверить, больше или нет сохраненное значение часов больше текущего значения часов, однако это просто сдвигает проблему. В системах, где CLOCK_MONOTONIC сбрасывается при перезагрузке, может быть короткое окно длины TIMEOUT, где разрешен доступ.

Что мне не хватает, чтобы избежать этой проблемы?

Ответ 1

Мне кажется, что это просто сделать с помощью объекта POSIX shared memory:

Объекты разделяемой памяти POSIX имеют стойкость ядра: разделяемая память объект будет существовать до тех пор, пока система не будет выключена, или пока все процессы отменили объект, и он был удален с помощью shm_unlink

Всякий раз, когда ваша программа запускает его, можно shm_open создать новый объект с некоторым согласованным именем и установить для владельца значение root. Объект не должен содержать никакого конкретного значения. POSIX требует, чтобы все объекты общей памяти сохранялись до перезагрузки, если они не были уничтожены вручную (что может сделать только его владелец или создатель), который в этом случае является пользователем root.

Всякий раз, когда ваша программа запускает его, он сначала проверяет, существует ли такой объект общей памяти с правами root. Поскольку только root может создать такой объект, и только root или перезагрузка могут его уничтожить, вы можете точно знать, была ли запущена ваша программа с момента последней перезагрузки, сохранить единственное возможное обходное обращение, которое пользователь root вызывает shm_unlink on объект вручную.

Я написал тестовую функцию ниже того, что должен делать именно то, что вам нужно. И он работает, за исключением настроек/обнаружения владельца: по какой-то неизвестной причине оба вызова shmctl не работают в моей системе, говоря "недопустимый аргумент". На странице man для shmctl говорится, что ошибка EINVAL указывает либо неверный идентификатор объекта памяти, либо недопустимую команду. Но команды IPC_SET и IPC_STAT, безусловно, действительны, и вы можете посмотреть выход программы, чтобы увидеть действительный идентификатор объекта, который создается и/или открыт каждый раз.

#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;
}

Если у кого есть какие-то подсказки, я в тупике. Некоторая информация и примеры для общей памяти POSIX здесь.

Ответ 2

В этой библиотеке python они ищут последнюю запись BOOT_TIME в utmp. Технически, как в POSIX, это utmpx (формат файла) и функции libc для доступа к нему. Я думаю, что это так хорошо, как вы можете оставаться в POSIX.