没有指定模式的Unix O_CREAT标志

时间:2009-02-25 00:02:04

标签: c unix

与O_CREAT标志一起使用时,UNIX open()函数的定义是,它需要第三个名为 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 个答案:

答案 0 :(得分:8)

POSIX标准(IEEE 1003.1:2008)原型open()为:

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

描述O_CREAT行为的部分没有说明如果省略必要的第三个参数将会发生什么,这意味着行为未定义 - 一切皆有可能。

实际上,使用堆栈框架或返回地址或类似内容的部分很可能 - 合理的近似值,可以认为是随机整数。

POSIX 2008标准为open()提供了一些有趣的新(且有用)标志,包括:

  • O_FDCLOEXEC指定open-on-exec at open。
  • O_DIRECTORY指定文件必须是目录。
  • O_NOFOLLOW指定不追逐符号链接。

答案 1 :(得分:2)

好问题。 mode值将由流程的umask修改。因此,如果您未在mode操作中明确地将open传递给O_CREAT,并且如果这会导致模式使用随机位,那么这些随机位将被umask

希望我能更加确定和准确,但我同意cdonner正在使用“随机”值以及umask

编辑:您可以尝试的一件事是使用dtruss或truss或其他工具来跟踪系统调用,并在运行时查看mode的值以查看是否使用了合理的内容,或者是否它只是由umask修改的随机位,例如。

答案 2 :(得分:1)

hyperboreean,你的怀疑可能不是那么遥远。 我希望在Kernighan Ritchie中找到答案。不幸的是,我没有。 我认为权限参数是O_CREAT标志所必需的,如果你不提供它,open()将从堆栈中提取一个随机值,这当然在C中没有被注意到。

编辑:“随机”我的意思是不可预测。它可能正在拾取返回地址的一部分,该地址位于堆栈上的参数之上。

答案 3 :(得分:1)

为了记录,在大多数libc系统上,您可能会接触到va_arg, which states in it's man page

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

答案 4 :(得分:0)

   int open(const char *pathname, int flags);
   int open(const char *pathname, int flags, mode_t mode);
  

... open()... O_CREAT标志...
  如果未指定该 mode 怎么办?

除了其他人的答案,如果您想通过获取设置 a 编译器错误 < / strong>,如果您在需要的情况下忘记指定 mode 标志(即 O_CREAT O_TMPFILE ),则根据{{ 1}}),您必须使用 GNU项目C和C ++编译器(例如 man 2 open 命令)与arg。 -D_FORTIFY_SOURCE=1(或gcc)和一个优化标志,例如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 arg。将不起作用(即,它不会告诉您您忘记添加-O,因为好像mode未被指定或只是被忽略了):

_FORTIFY_SOURCE

使用_FORTIFY_SOURCE也会像other cases一样保护您,$ 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) ^~~~~~~ 还有另外一种情况:open(),在文件open can be called either with 2 or 3 arguments, not more中显示为:

/usr/include/bits/fcntl2.h

之所以需要GNU C编译器(例如__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 ()); } ),至少是由于文件gcc中的以下代码:

/usr/include/sys/cdefs.h

表示gcc版本4.3是此功能最低要求。 (仅供参考:我当前的版本是gcc(GCC)8.3.0)

因此,如果您尝试使用#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 版本8.0.0(标签/ RELEASE_800 / final)目标:x86_64-pc-linux-gnu ,则不会出现编译错误:< / p>

clang

(即使-也没有输出,编译成功: a.out 已创建) 因为此clang版本将$ clang -D_FORTIFY_SOURCE=2 -O1 a.c 定义为__GNUC__,将4定义为__GNUC_MINOR__,所以4.2不能满足其工作所需的4.3;和强迫例如。 8.3不起作用:

2

以上源代码来自Arch Linux上的$ 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. 2.29.9000.r269.g1f50f2ad85-1软件包。即。

glibc

PS:如果没有/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 ,您可以在每次运行程序时获得随机模式,例如I did

_FORTIFY_SOURCE

答案 5 :(得分:0)

我们可以使用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的“裸”初始化程序将继续干净地编译。