Флаг Unix O_CREAT без указания режима - PullRequest
7 голосов
/ 25 февраля 2009

Определение функции UNIX open () при использовании с флагом O_CREAT заключается в том, что для установки привилегий файлов требуется третий аргумент с именем mode .

Что если этот режим не указан?

int file;
static const char filename[] = "test.test";

if ((file = open(filename, O_RDWR | O_CREAT | O_TRUNC)) == 1)
{
    perror("Error opening file.");
    exit(EXIT_FAILURE);
}

close(file);

Что происходит с файлом, созданным с использованием этих флагов? В моей системе я получаю:

-r--r-s---  1 hyperboreean hyperboreean     0 2009-02-25 01:40 test.test

Теория заключается в том, что функция open просматривает стек и проверяет параметр mode и в конечном итоге использует случайное целое число, которое находит.

Что стандарт говорит об этом?

Ответы [ 6 ]

8 голосов
/ 25 февраля 2009

Прототипы стандарта POSIX (IEEE 1003.1: 2008) open() как:

int open(const char *path, int oflag, ...);

В разделе, описывающем поведение O_CREAT, не говорится, что произойдет, если вы опустите необходимый третий аргумент, что означает, что поведение не определено - все возможно.

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

Стандарт POSIX 2008 имеет несколько интересных новых (и полезных) флагов для open(), в том числе:

  • O_FDCLOEXEC для указания закрытия при запуске при открытии.
  • O_DIRECTORY, чтобы указать, что файл должен быть каталогом.
  • O_NOFOLLOW, чтобы указать, что не следует использовать символические ссылки.
2 голосов
/ 25 февраля 2009

Хороший вопрос. Значение mode будет изменено umask процесса. Поэтому, если вы не передаете mode явно open в операции O_CREAT, и если это приводит к использованию случайных битов для режима, эти случайные биты будут изменены umask.

Хотел бы я быть более точным и точным, но я согласен с cdonner, что используются "случайные" значения вместе с umask.

Редактировать: одну вещь, которую вы можете попробовать, это использовать dtruss или truss или какое-то другое средство для отслеживания системных вызовов и посмотреть значение mode во время выполнения, чтобы увидеть, используется ли что-то разумное или просто случайные биты, модифицированные umask, например.

1 голос
/ 07 марта 2014

Для справки: на большинстве систем libc вы, вероятно, будете иметь в руках va_arg , что указано на его странице руководства :

   If there is no next argument, or if type is not compatible with the
   type of the actual next argument (as promoted according to the
   default argument promotions), **random errors will occur**.
int
__libc_open64 (const char *file, int oflag, ...)
{
    int mode = 0;

    if (oflag & O_CREAT)
    {
        va_list arg;
        va_start (arg, oflag);
        mode = va_arg (arg, int);
        va_end (arg);
    }

    if (SINGLE_THREAD_P)
        return INLINE_SYSCALL (open, 3, file, oflag | O_LARGEFILE, mode);

    int oldtype = LIBC_CANCEL_ASYNC ();

    int result = INLINE_SYSCALL (open, 3, file, oflag | O_LARGEFILE, mode);

    LIBC_CANCEL_RESET (oldtype);

    return result;
}
1 голос
/ 25 февраля 2009

гиперборея, ваше подозрение может быть не так уж далеко от цели. Я надеялся найти ответ в Kernighan Ritchie . К сожалению, я этого не сделал. Я думаю, что параметр permissions требуется с флагом O_CREAT, и если вы не предоставите его, open () извлечет случайное значение из стека, что, конечно, остается незамеченным в C.

Редактировать: под "случайным" я подразумеваю непредсказуемый. Вероятно, он выбирает часть адреса возврата, который находится поверх параметров в стеке.

0 голосов
/ 18 мая 2019

Мы можем использовать макросы C для решения этой проблемы.

#undef open
#define open(a, b, c) open(a, b, c)

Теперь вы не можете вызвать open без трех аргументов.

Это похоже на написание макросов для struct инициализаторов, чтобы убедиться, что пользователи не пренебрегают инициализацией некоторых членов:

#define foo_initializer(a, b, c) { .x = (a), .y = (b), .z = (c) }

Если позже мы добавим новый элемент w, мы можем расширить foo_initializer новым аргументом. Когда мы перекомпилируем кодовую базу, компилятор найдет все места, где ему дается только три аргумента. Принимая во внимание, что «голые» инициализаторы, которые игнорируют инициализацию w, продолжат чистую компиляцию.

0 голосов
/ 18 мая 2019
   int open(const char *pathname, int flags);
   int open(const char *pathname, int flags, mode_t mode);

... open () ... O_CREAT flag ...
Что если этот mode не указан?

В дополнение к ответам других людей, если вы хотите противостоять этому будущему, наберите get ting a ошибка компилятора при вы забыли указать флаг mode в тех случаях, когда это необходимо (т. е. O_CREAT или O_TMPFILE , согласно man 2 open), у вас будет использовать GNU project C и C ++ (например, команду gcc) с аргументом arg. -D_FORTIFY_SOURCE=1 (или 2) и флаг оптимизации, например. -O1 или обычный -O2 (потому что _FORTIFY_SOURCE requires compiling with optimization (-O)).

Например:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
int file;
static const char filename[] = "test.test";

if ((file = open(filename, O_RDWR | O_CREAT | O_TRUNC)) == 1)
{
    perror("Error opening file.");
    exit(EXIT_FAILURE);
}

close(file);
}

(сохранить как файл: a.c)

$ gcc -D_FORTIFY_SOURCE=2 -O1 a.c
In file included from /usr/include/fcntl.h:328,
                 from a.c:3:
In function ‘open’,
    inlined from ‘main’ at a.c:10:13:
/usr/include/bits/fcntl2.h:50:4: error: call to ‘__open_missing_mode’ declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments
    __open_missing_mode ();
    ^~~~~~~~~~~~~~~~~~~~~~

Итак, вы получите: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments

Педантику следует :
Обратите внимание: -O0 или нет -O arg. не будет работать (т.е. он не скажет вам, что вы забыли добавить mode, потому что это как если бы _FORTIFY_SOURCE не было указано или просто проигнорировано):

$ gcc -D_FORTIFY_SOURCE=2 -O0 a.c
In file included from /usr/include/bits/libc-header-start.h:33,
                 from /usr/include/stdio.h:27,
                 from a.c:1:
/usr/include/features.h:382:4: warning: #warning _FORTIFY_SOURCE requires compiling with optimization (-O) [-Wcpp]
 #  warning _FORTIFY_SOURCE requires compiling with optimization (-O)
    ^~~~~~~

Использование _FORTIFY_SOURCE защитит вас таким же образом и для других случаев тоже, а для open() есть еще один случай: open can be called either with 2 or 3 arguments, not more, видимый в файле /usr/include/bits/fcntl2.h как:

__errordecl (__open_too_many_args,
         "open can be called either with 2 or 3 arguments, not more");
__errordecl (__open_missing_mode,
         "open with O_CREAT or O_TMPFILE in second argument needs 3 arguments");

__fortify_function int
open (const char *__path, int __oflag, ...)
{
  if (__va_arg_pack_len () > 1)
    __open_too_many_args ();

  if (__builtin_constant_p (__oflag))
    {
      if (__OPEN_NEEDS_MODE (__oflag) && __va_arg_pack_len () < 1)
    {
      __open_missing_mode ();
      return __open_2 (__path, __oflag);
    }
      return __open_alias (__path, __oflag, __va_arg_pack ());
    }

  if (__va_arg_pack_len () < 1)
    return __open_2 (__path, __oflag);

  return __open_alias (__path, __oflag, __va_arg_pack ());
}

Причина, по которой необходим GNU C Compiler (например, gcc), по крайней мере, из-за следующего кода из файла /usr/include/sys/cdefs.h:

#if __GNUC_PREREQ (4,3)
# define __warndecl(name, msg) \
  extern void name (void) __attribute__((__warning__ (msg)))
# define __warnattr(msg) __attribute__((__warning__ (msg)))
# define __errordecl(name, msg) \                                                                                               
  extern void name (void) __attribute__((__error__ (msg)))
#else
# define __warndecl(name, msg) extern void name (void)
# define __warnattr(msg)
# define __errordecl(name, msg) extern void name (void)
#endif       

, который говорит, что gcc версии 4.3 - это минимум, необходимый для этой работы. (К вашему сведению: моя текущая версия gcc (GCC) 8.3.0)

Так что если вы попробуете clang версии 8.0.0 (tags / RELEASE_800 / final) Цель: x86_64-pc-linux-gnu , вы не получите ошибку компиляции:

$ clang -D_FORTIFY_SOURCE=2 -O1 a.c

(здесь нет выходных данных даже с -, компиляция прошла успешно: a.out создан) потому что эта версия clang определяет __GNUC__ как 4 и __GNUC_MINOR__ как 2, поэтому 4.2 просто стесняется 4.3, необходимой для его работы; и принуждение, например. 8.3 не будет работать:

$ clang -D_FORTIFY_SOURCE=1 -D__GNUC__=8 -D__GNUC_MINOR__=8 -O1 a.c
In file included from <built-in>:355:
<command line>:2:9: warning: '__GNUC__' macro redefined [-Wmacro-redefined]
#define __GNUC__ 8
        ^
<built-in>:9:9: note: previous definition is here
#define __GNUC__ 4
        ^
In file included from <built-in>:355:
<command line>:3:9: warning: '__GNUC_MINOR__' macro redefined [-Wmacro-redefined]
#define __GNUC_MINOR__ 8
        ^
<built-in>:7:9: note: previous definition is here
#define __GNUC_MINOR__ 2
        ^
2 warnings generated.

Приведенные выше исходные коды получены из пакета glibc 2.29.9000.r269.g1f50f2ad85-1 в Arch Linux. то есть.

/usr/include/sys/cdefs.h is owned by glibc 2.29.9000.r269.g1f50f2ad85-1
/usr/include/bits/fcntl2.h is owned by glibc 2.29.9000.r269.g1f50f2ad85-1

PS: без _FORTIFY_SOURCE, вы можете получить случайные режимы при каждом запуске программы, как Я сделал :

$ ./go
-r-x--s--T 1 user user 0 May 17 17:22 /tmp/broken_perms.log
$ ./go
---sr-s--- 1 user user 0 May 17 17:23 /tmp/broken_perms.log
$ ./go
-rws--x--- 1 user user 0 May 17 17:23 /tmp/broken_perms.log
$ ./go
--wsr-x--T 1 user user 0 May 17 17:23 /tmp/broken_perms.log
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...