Unix O_CREAT flag without mode

The definition of the UNIX open () function when used with the O_CREAT flag is that a third mode named is required to set file privileges.

What if this mode is not specified?

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

What happens to a file created using these flags? On my system, I get:

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

The theory is that an open function looks up the stack and checks the mode parameter and ends up using a random integer that it finds.

What does the standard say about this?

+7
c unix
source share
6 answers

Prototypes of the POSIX standard (IEEE 1003.1: 2008) open() as:

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

The section describing the behavior of O_CREAT does not say what happens if you omit the necessary third argument, which means that the behavior is undefined - anything is possible.

In practice, the use of the part of the stack, which should have been a frame of the stack or a return address or something similar, is quite probable - in a reasonable approximation, which can be considered a random integer.

The POSIX 2008 standard has interesting (and useful) flags for open() , including:

  • O_FDCLOEXEC to indicate close-on-exec when opened.
  • O_DIRECTORY to indicate that the file should be a directory.
  • O_NOFOLLOW to indicate so as not to chase symbolic links.
+8
source share
Good question. The mode value will be changed using the umask process. Therefore, if you do not pass mode explicitly to open in the O_CREAT operation, and if this leads to the random bits used for the mode, these random bits will be changed using umask .

I wish that I was more precise and precise, but I agree with cdonner that "random" values ​​are used, as well as umask .

Edit: you can try to use dtruss or a farm or some other object to track system calls and look at the mode value at runtime to see if something sensible is being used, or if it's just random bits changed, like umask .

+2
source share

Hyperborea, your suspicion may not be so far. I was hoping to find an answer at Kernighan Ritchie . Unfortunately, I did not. I think that the permission parameter is required with the O_CREAT flag, and if you do not provide it, open () will pull a random value from the stack, which, of course, will go unnoticed in C.

Edit: By "random" I mean unpredictable. It probably collects part of the return address, which is on top of the parameters on the stack.

+1
source share

For writing on most libc systems, you will probably be in the hands of va_arg , which lists the 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; } 
+1
source share
  int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); 

... open () ... O_CREAT flag ...
What if this mode is not specified?

In addition to other people's answers, if you want proof against this in the future by getting a Ting compilation error when you forgot to specify the mode flag in cases where it is necessary (for example, O_CREAT or O_TMPFILE, according to man 2 open , man 2 open ) you will have to use the GNU C project and the C ++ compiler (for example, the gcc command) with the arg argument. -D_FORTIFY_SOURCE=1 (or 2 ) and an optimization flag, for example. -O1 or regular -O2 (because _FORTIFY_SOURCE requires compiling with optimization (-O) ).

For example:

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

(save this as file: ac )

 $ gcc -D_FORTIFY_SOURCE=2 -O1 ac In file included from /usr/include/fcntl.h:328, from ac:3: In function 'open, inlined from 'main at ac: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 (); ^~~~~~~~~~~~~~~~~~~~~~ 

So you get: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments

Pedantics should :
Note: -O0 or not -O arg. will not work (i.e. it won’t tell you that you forgot to add mode because it is like _FORTIFY_SOURCE not _FORTIFY_SOURCE or just ignored):

 $ gcc -D_FORTIFY_SOURCE=2 -O0 ac In file included from /usr/include/bits/libc-header-start.h:33, from /usr/include/stdio.h:27, from ac: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) ^~~~~~~ 

Using _FORTIFY_SOURCE will protect you in the same way for other cases , and for open() there is one more case: open can be called either with 2 or 3 arguments, not more , as seen from the file /usr/include/bits/fcntl2.h as:

 __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 ()); } 

The reason why you need a GNU C compiler (e.g. gcc ) is at least because of the following code from the /usr/include/sys/cdefs.h file:

 #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 

this suggests that gcc version 4.3 is the minimum required for this to work. (For your information: my current version of gcc (GCC) is 8.3.0)

Therefore, if you try clang version 8.0.0 (tags / RELEASE_800 / final) Target: x86_64-pc-linux-gnu, you will not get a compilation error:

 $ clang -D_FORTIFY_SOURCE=2 -O1 ac 

(there is no output even with -, the compilation was successful: a.out was created), since this version of clang defines __GNUC__ equal to 4 and __GNUC_MINOR__ equal to 2 in this way, 4.2 is simply not __GNUC_MINOR__ 4.3 necessary for its operation; and coercion, for example. 8.3 will not work:

 $ clang -D_FORTIFY_SOURCE=1 -D__GNUC__=8 -D__GNUC_MINOR__=8 -O1 ac 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. 

The source code above was obtained from the glibc 2.29.9000.r269.g1f50f2ad85-1 package on Arch Linux. i.e.

 /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: without _FORTIFY_SOURCE you can get random modes every time the program starts, as I did :

 $ ./go -rx--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 
0
source share

We can use C macros to solve this problem.

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

Now you cannot call open without three arguments.

This is similar to writing macros for struct initializers to make sure that users remember to initialize some members:

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

If we add a new member w later, we can extend foo_initializer new argument. When we recompile the code base, the compiler will find all the places where it is given only three arguments. While bare initializers that neglect the initialization of w will continue to compile cleanly.

0
source share

All Articles