与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参数,最后使用它找到的随机整数。
标准对此有何看法?
答案 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
的“裸”初始化程序将继续干净地编译。