我应该使用“O_PATH”做什么,以及如何使用?

我应该使用“O_PATH”做什么,以及如何使用?

我使用基于 Linux 4.x 的发行版,最近我注意到内核的open()系统调用支持O_PATHopen 标志。

虽然man它的页面确实有一个理论上可以使用的系统调用列表,但我不太明白这个想法是什么。我open(O_PATH)只使用目录,而不使用文件吗?如果这样做,为什么我要使用文件描述符而不是目录路径?此外,其中列出的大多数系统调用似乎并不特定于目录;那么,我是否也打开常规文件以O_PATH某种方式将其目录作为文件描述符?或者获取它们的文件描述符但功能有限?

有人能给出一个令人信服的解释,说明O_PATH我们应该如何使用它,以及如何使用它?

笔记:

  • 除非有必要,否则不需要描述它是如何演变的历史(相关手册页提到了 Linux 2.6.x、3.5 和 3.6 中的变化)——我只关心现在的情况。
  • 请不要告诉我只使用 libc 或其他更高级别的设施,我知道这一点。

答案1

中的描述open(2)手册页提供了一些开始的线索:

   O_PATH (since Linux 2.6.39)
          Obtain a file descriptor that can be used for two purposes:
          to  indicate  a location in the filesystem tree and to per‐
          form operations that act  purely  at  the  file  descriptor
          level.  The file itself is not opened, and other file oper‐
          ations  (e.g.,  read(2),  write(2),  fchmod(2),  fchown(2),
          fgetxattr(2), ioctl(2), mmap(2)) fail with the error EBADF.

有时,我们不想打开文件或目录。相反,我们只需要对该文件系统对象的引用,以便执行某些操作(例如,fchdir()我们使用打开的文件描述符引用的目录O_PATH)。所以,有一点很重要:如果这是我们的目的,那么用 来打开O_PATH应该会便宜一些,因为文件本身实际上并没有打开。

还有一个不那么重要的一点:在 存在之前O_PATH,获取对文件系统对象的引用的方法是使用 打开该对象O_RDONLY。但使用O_RDONLY需要我们拥有该对象的读取权限。但是,在许多用例中,我们不需要实际读取对象:例如,执行二进制文件或访问目录 ( fchdir()) 或通过目录访问目录内的对象。

与“*at()”系统调用一起使用

的常见(但不是唯一)用途O_PATH是打开一个目录,以便引用该目录以与“*at”系统调用一起使用,例如openat()fstatat()fchownat()等。这个系统调用系列,我们可以粗略地将其视为具有相似名称( open()fstat()fchown()等)的旧系统调用的现代继承者,它们有几个目的,当您询问“时,您会提到第一个目的”为什么我想使用文件描述符而不是目录路径?”。如果我们进一步查看手册页open(2),我们会找到以下文本(在带有“*at”系统调用基本原理的小标题下):

   First,  openat()  allows  an  application to avoid race conditions
   that could occur when using open() to open  files  in  directories
   other  than  the current working directory.  These race conditions
   result from the fact that some component of the  directory  prefix
   given  to  open()  could  be  changed in parallel with the call to
   open().  Suppose, for example, that we wish  to  create  the  file
   path/to/xxx.dep  if  the  file path/to/xxx exists.  The problem is
   that between the existence check and the file creation step,  path
   or  to  (which might be symbolic links) could be modified to point
   to a different location.  Such races can be avoided by  opening  a
   file descriptor for the target directory, and then specifying that
   file descriptor as the dirfd argument of (say) fstatat(2) and ope‐
   nat().

为了使这一点更具体......假设我们有一个程序想要在当前工作目录之外的目录中执行多个操作,这意味着我们必须指定一些目录前缀作为我们使用的文件名的一部分。例如,假设路径名是/dir1/dir2/file,我们要执行两个操作:

  1. 执行一些检查/dir1/dir2/file(例如,谁拥有该文件,或者上次修改该文件的时间)。
  2. 如果我们对该检查的结果感到满意,也许我们想在同一目录中执行一些其他文件系统操作,例如,创建一个名为/dir1/dir2/file.new.

现在,首先假设我们使用传统的基于路径名的系统调用完成了所有操作:

struct stat stabuf;
stat("/dir1/dir2/file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
    fd = open("/dir1/dir2/file.new", O_CREAT | O_RDWR, 0600);
    /* And then populate file referred to by fd */
}

现在,进一步假设在目录前缀中,/dir1/dir2其中一个组件(例如dir2)实际上是一个符号链接(指的是一个目录),并且在 呼叫stat()和 呼叫之间open()恶意者能够更改符号链接的目标dir2以指向不同的目录。这是一个经典的检查时间-使用时间竞争条件。我们的程序检查了一个目录中的一个文件,但随后被欺骗在另一个目录(可能是一个安全敏感目录)中创建了一个文件。这里的关键点是路径名/dir/dir2看起来相同,但它所指的内容完全改变了。

我们可以使用“*at”调用来避免此类问题。首先,我们获得一个指向我们将在其中执行工作的目录的句柄:

dirfd = open("/dir/dir2", O_PATH);

这里的关键点dirfd稳定的/dir1/dir2对调用时路径所引用的目录的引用open()。如果符号链接的目标dir2随后发生更改,这不会影响dirfd所指的内容。现在,我们可以使用与上面的stat()and调用等效的“*at”调用来执行 check + 操作open()

fstatat(dirfd, ""file", &statbuf)
struct stat stabuf;
fstatat(dirfd, "file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
    fd = openat(dirfd, "file.new", O_CREAT | O_RDWR, 0600);
    /* And then populate file referred to by fd */
}

在这些步骤中,对路径名中的符号链接的任何操作/dir/dir2都不会产生影响:检查 ( fstatat()) 和操作 ( openat()) 保证在同一目录中进行。

使用“*at()”调用还有另一个目的,它与多线程程序中“每线程当前工作目录”的想法有关(我们可以再次使用打开目录O_PATH),但我认为这种用途可能是与您的问题不太相关,open(2)如果您想了解更多信息,我让您阅读手册页。

与常规文件的文件描述符一起使用

常规文件的一种用法O_PATH是打开我们具有执行权限的二进制文件(但不一定具有读取权限,因此我们无法使用 来打开该文件O_RDONLY)。然后可以将该文件描述符传递给fexecve(3)执行程序。fexecve(fd, argv, envp)其论证所做的一切fd本质上是:

snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);

(尽管从 glibc 2.27 开始,实现将改为使用execveat(2)系统调用,在提供该系统调用的内核上。)

相关内容