【问题标题】:Unix O_CREAT flag without mode specified未指定模式的 Unix O_CREAT 标志
【发布时间】:2010-10-09 17:04:39
【问题描述】:

UNIX open() 函数与 O_CREAT 标志一起使用时的定义是,它需要名为 mode 的第三个参数来设置文件的权限。

如果没有指定 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 函数在堆栈上查找并检查模式参数,并最终使用它找到的随机整数。

标准对此有何规定?

【问题讨论】:

  • 这就是发明函数原型的原因。
  • 你的评论没有意义。
  • 确实没有。也没有投反对票。
  • 据我所知,“重载”一词在 C++ 出现之前并不存在。在 C 中,一个函数只能有一个实现。
  • @David:open() 的函数参数的数量和类型可能会有所不同——这可能不是这里的情况,但我认为我们不能谈论 C 中的重载。

标签: c unix


【解决方案1】:

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

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

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

在实践中,很有可能使用旨在作为堆栈帧或返回地址或类似内容的堆栈部分 - 以合理的近似值,可以将其视为随机整数。

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

  • O_FDCLOEXEC 在打开时指定 close-on-exec。
  • O_DIRECTORY 指定文件必须是目录。
  • O_NOFOLLOW 指定不追逐符号链接。

【讨论】:

    【解决方案2】:

    好问题。 mode 值将被进程的umask 修改。因此,如果您没有在O_CREAT 操作中将mode 显式传递给open,并且如果这导致该模式使用随机位,则这些随机位将被umask 修改。

    希望我可以更加明确和精确,但我同意 cdonner 的观点,即使用“随机”值以及 umask

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

    【讨论】:

    • 在我使用 ptrace() 的测试中,这只是看似随机的东西,但在我的情况下,它始终是一个以 2777 开头的 11 位(前导 0 表示八进制的 12 位)数字。
    • 02777 的值很有趣,因为它暗示世界可通过 set-gid 位写入。好吧,这只是在没有模式的情况下创建文件很危险的另一个原因。
    【解决方案3】:

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

    编辑:“随机”是指不可预测。它可能正在获取位于堆栈参数顶部的返回地址的一部分。

    【讨论】:

      【解决方案4】:

      郑重声明,在大多数 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;
      }
      

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:
             int open(const char *pathname, int flags);
             int open(const char *pathname, int flags, mode_t mode);
          

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

          除了其他人的答案之外,如果您想通过 getting a 编译器错误 当你忘记在需要它的情况下指定 mode 标志时(即 O_CREATO_TMPFILE,根据@987654326 @),您必须使用 GNU 项目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 也会像other cases 一样保护您,而对于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 编译器(例如 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 version 8.0.0 (tags/RELEASE_800/final) Target: 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.
          

          以上源码来自 Arch Linux 上的glibc 2.29.9000.r269.g1f50f2ad85-1 包。即。

          /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,你可以在每个程序运行时获得随机模式,比如I did

          $ ./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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-12-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-10-27
            • 1970-01-01
            • 1970-01-01
            • 2022-12-04
            相关资源
            最近更新 更多