stat(), fstat(), lstat() и fopen(); Как написать защищенный от TOCTOU системный код

Я уже несколько недель имею дело с проблемой обновления 20-летнего кода, который должен быть независимым от системы (работает как в Linux, так и в Windows). Он включает в себя вопросы времени проверки, времени использования (TOCTOU). Я сделал здесь тему, но она не зашла слишком далеко, и после некоторого размышления над ней и более глубокого изучения проблемы, я думаю, что понимаю свой вопрос немного лучше. Может быть, я могу спросить это немного лучше тоже...

Из того, что я прочитал, код должен проверить, существует ли файл, доступен ли он, открыть файл, выполнить некоторые операции и, наконец, закрыть файл. Кажется, лучший способ сделать это - звонок lstat() вызов fopen() вызов fstat() (чтобы исключить TOCTOU), а затем операции и закрытие файла.

Тем не менее, я был убежден, что lstat() а также fstat() определены POSIX, а не C Standard, исключая их использование для системно-независимой программы, во многом таким же образом open() не должен использоваться для перекрестной совместимости. Как бы вы это реализовали?

Если вы посмотрите на мой первый пост, вы увидите, что разработчик 20 лет назад использовал препроцессор C, чтобы разрезать код на кросс-совместимые части, но даже если бы я это сделал, я бы не знал, что заменить lstat() или же fstat() с (их окна сверстники).

Редактировать: Добавлен сокращенный код в этот пост; если что-то неясно, пожалуйста, перейдите к исходному сообщению

#ifdef WIN32
    struct _stat buf;
#else
    struct stat buf;
#endif //WIN32

    FILE *fp;
    char data[2560];

    // Make sure file exists and is readable
#ifdef WIN32
    if (_access(file.c_str(), R_OK) == -1) {
#else
    if (access(file.c_str(), R_OK) == -1) {
#endif //WIN32

        char message[2560];
        sprintf(message, "File '%s' Not Found or Not Readable", file.c_str());
        throw message;
        }

    // Get the file status information
#ifdef WIN32
    if (_stat(file.c_str(), &buf) != 0) {
#else
    if (stat(file.c_str(), &buf) != 0) {
#endif //WIN32

        char message[2560];
        sprintf(message, "File '%s' No Status Available", file.c_str());
        throw message;
        }

    // Open the file for reading
    fp = fopen(file.c_str(), "r");
    if (fp == NULL) {
        char message[2560];
        sprintf(message, "File '%s' Cound Not be Opened", file.c_str());
        throw message;
    }

    // Read the file
    MvString s, ss;
    while (fgets(data, sizeof(data), fp) != (char *)0) {
        s = data;
        s.trimBoth();
        if (s.compare( 0, 5, "GROUP" ) == 0) {
            //size_t t = s.find_last_of( ":" );
            size_t t = s.find( ":" );
            if (t != string::npos) {
                ss = s.substr( t+1 ).c_str();
                ss.trimBoth();
                ss = ss.substr( 1, ss.length() - 3 ).c_str();
                group_list.push_back( ss );
            }
        }
    }
    // Close the file
    fclose(fp);
}

1 ответ

Решение

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

access() функция формально задает вопрос, отличный от того, что вы думаете; она спрашивает "может ли реальный идентификатор пользователя или реальный идентификатор группы получить доступ к файлу", но программа будет использовать эффективный идентификатор пользователя или эффективный идентификатор группы для доступа к файлу. Если ваша программа не работает с SUID или SGID и не была запущена из программы с SUID или SGID - и это нормальный случай - тогда нет никакой разницы. Но вопрос в другом.

Использование stat() или же lstat() не кажется полезным Особенно, lstat() только говорит вам, начинаете ли вы с символической ссылки, но код не заботится об этом.

Оба access() и stat() звонки предоставляют вам окна уязвимости TOCTOU; файл может быть удален после того, как они сообщили, что он присутствует, или создан после того, как они сообщили, что он отсутствует.

Вы должны просто позвонить fopen() и посмотреть, работает ли это; код будет более простым и более устойчивым к проблемам TOCTOU. Возможно, вам придется подумать, стоит ли использовать open() со всеми дополнительными элементами управления (O_EXCLи т. д.), а затем преобразовать дескриптор файла в указатель файла (fdopen()).

Все это относится к Unix-стороне.

Детали будут отличаться, но на стороне Windows вам все равно лучше всего попытаться открыть файл и соответственно отреагировать на неудачу.

В обеих системах убедитесь, что параметры, предоставляемые функции open, являются подходящими.

Другие вопросы по тегам