ein*_*lum 11 system-calls file-descriptors
我使用基于 Linux 4.x 的发行版,最近我注意到内核的open()
系统调用支持O_PATH
open 标志。
虽然man
它的页面确实有一个理论上可以使用的系统调用列表,但我不太明白这个想法是什么。我open(O_PATH)
只是目录,而不是文件吗?如果我这样做了,为什么我要使用文件描述符而不是目录的路径?此外,那里列出的大多数系统调用似乎都不是特定于目录的。那么,我是否也打开常规文件O_PATH
以某种方式将它们的目录作为文件描述符?或者为它们获取文件描述符但功能有限?
有人可以O_PATH
对我们应该使用它的内容、方式和用途给出一个有说服力的解释吗?
笔记:
mtk*_*mtk 13
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.
Run Code Online (Sandbox Code Playgroud)
有时,我们不想打开文件或目录。相反,我们只需要对该文件系统对象的引用,以便执行某些操作(例如,指向fchdir()
我们使用 打开的文件描述符所引用的目录O_PATH
)。所以,一个微不足道的点:如果这是我们的目的,那么打开 withO_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().
Run Code Online (Sandbox Code Playgroud)
为了使这更具体......假设我们有一个程序想要在当前工作目录以外的目录中执行多个操作,这意味着我们必须指定一些目录前缀作为我们使用的文件名的一部分。例如,假设路径名是/dir1/dir2/file
并且我们想要执行两个操作:
/dir1/dir2/file
(例如,谁拥有文件,或上次修改的时间)。/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 */
}
Run Code Online (Sandbox Code Playgroud)
现在,进一步假设在目录前缀中的/dir1/dir2
一个组件(比如dir2
)实际上是一个符号链接(指的是一个目录),并且在调用stat()
和调用open()
恶意人员之间能够改变目标dir2
指向不同目录的符号链接。这是一个经典的检查时间-使用时间竞争条件。我们的程序检查了一个目录中的一个文件,但随后被诱骗在另一个目录中创建了一个文件——可能是一个安全敏感目录。这里的关键点是路径名/dir/dir2
看起来相同,但它所指的内容完全改变了。
我们可以使用“*at”调用来避免这类问题。首先,我们获得一个指向我们将要进行工作的目录的句柄:
dirfd = open("/dir/dir2", O_PATH);
Run Code Online (Sandbox Code Playgroud)
这里的关键点是对调用时路径所引用的目录dirfd
的稳定引用。如果符号链接的目标随后更改,这不会影响引用的内容。现在,我们可以使用与上面的和调用等效的“*at”调用来执行检查 + 操作:/dir1/dir2
open()
dir2
dirfd
stat()
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 */
}
Run Code Online (Sandbox Code Playgroud)
在这些步骤中,对路径名中符号链接的任何操作/dir/dir2
都不会产生影响:检查 ( fstatat()
) 和操作 ( openat()
) 保证发生在同一目录中。
使用“*at()”调用还有另一个目的,它与多线程程序中“每线程当前工作目录”的想法有关(我们可以再次使用打开目录O_PATH
),但我认为这种用法可能是与您的问题不太相关,open(2)
如果您想了解更多信息,我会让您阅读手册页。
与常规文件的文件描述符一起使用
O_PATH
with 常规文件的一种用法是打开一个我们有执行权限的二进制文件(但不一定是读取权限,所以我们不能用 来打开文件O_RDONLY
)。然后可以传递该文件描述符fexecve(3)
以执行程序。所有这一切fexecve(fd, argv, envp)
与它做的fd
说法基本上是:
snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);
Run Code Online (Sandbox Code Playgroud)
(尽管从 glibc 2.27 开始,实现将改为execveat(2)
在提供该系统调用的内核上使用系统调用。)