Mô tả trong open(2)
trang man cung cấp một số manh mối để bắt đầu:
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.
Đôi khi, chúng tôi không muốn mở một tập tin hoặc một thư mục. Thay vào đó, chúng tôi chỉ muốn một tham chiếu đến đối tượng hệ thống tập tin đó để thực hiện các hoạt động nhất định (ví dụ: fchdir()
đến một thư mục được gọi bởi một bộ mô tả tệp mà chúng tôi đã mở bằng cách sử dụng O_PATH
). Vì vậy, một điểm tầm thường: nếu đây là mục đích của chúng tôi, thì việc mở bằng O_PATH
sẽ rẻ hơn một chút, vì bản thân tệp không thực sự được mở.
Và một điểm ít tầm thường hơn: trước khi tồn tại O_PATH
, cách để có được một tham chiếu như vậy đến một đối tượng hệ thống tập tin là mở đối tượng với O_RDONLY
. Nhưng việc sử dụng O_RDONLY
đòi hỏi chúng ta phải đọc quyền trên đối tượng. Tuy nhiên, có nhiều trường hợp sử dụng khác nhau mà chúng ta không thực sự cần phải đọc đối tượng: ví dụ: thực thi nhị phân hoặc truy cập một thư mục ( fchdir()
) hoặc truy cập qua một thư mục để chạm vào một đối tượng bên trong thư mục.
Sử dụng với các cuộc gọi hệ thống "* at ()"
Phổ biến, nhưng không phải là duy nhất, sử dụng O_PATH
là để mở một thư mục, để có một tham chiếu đến thư mục đó để sử dụng với "* tại" cuộc gọi hệ thống, chẳng hạn như openat()
, fstatat()
, fchownat()
, và vân vân. This family các cuộc gọi hệ thống, mà chúng tôi đại khái có thể nghĩ đến là các kế hiện đại với các cuộc gọi hệ thống cũ với tên tương tự ( open()
, fstat()
, fchown()
, và vân vân), phục vụ cho một vài mục đích, là người đầu tiên trong số đó bạn chạm vào khi bạn hỏi " tại sao tôi muốn sử dụng một bộ mô tả tập tin thay vì đường dẫn của thư mục? ". Nếu chúng ta nhìn sâu hơn vào open(2)
trang man, chúng ta sẽ tìm thấy văn bản này (dưới tiêu đề phụ với lý do cho các cuộc gọi hệ thống "* tại"):
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().
Để làm cho điều này cụ thể hơn ... Giả sử chúng ta có một chương trình muốn thực hiện nhiều thao tác trong một thư mục khác với thư mục làm việc hiện tại của nó, nghĩa là chúng ta phải chỉ định một số tiền tố thư mục là một phần của tên tệp mà chúng ta sử dụng. Ví dụ, giả sử rằng tên đường dẫn là /dir1/dir2/file
và chúng tôi muốn thực hiện hai thao tác:
- Thực hiện một số kiểm tra
/dir1/dir2/file
(ví dụ: ai sở hữu tệp hoặc thời gian sửa đổi lần cuối).
- Nếu chúng tôi hài lòng với kết quả của kiểm tra đó, có lẽ sau đó chúng tôi muốn thực hiện một số thao tác hệ thống tệp khác trong cùng thư mục, ví dụ: tạo một tệp có tên
/dir1/dir2/file.new
.
Bây giờ, trước tiên, giả sử chúng tôi đã làm mọi thứ bằng cách sử dụng các cuộc gọi hệ thống dựa trên tên đường truyền thống:
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 */
}
Bây giờ, hơn nữa giả sử rằng trong tiền tố thư mục, /dir1/dir2
một trong các thành phần (giả sử dir2
) thực sự là một liên kết tượng trưng (liên quan đến một thư mục) và giữa cuộc gọi đến stat()
và cuộc gọi đếnopen()
người độc hại có thể thay đổi mục tiêu của liên kết tượng trưng dir2
để trỏ đến một thư mục khác. Đây là một điều kiện đua thời gian cổ điển kiểm tra thời gian sử dụng. Chương trình của chúng tôi đã kiểm tra một tệp trong một thư mục nhưng sau đó bị lừa tạo một tệp trong một thư mục khác - có lẽ là thư mục nhạy cảm với bảo mật. Điểm mấu chốt ở đây là tên đường dẫn /dir/dir2
trông giống nhau, nhưng những gì nó đề cập đã thay đổi hoàn toàn.
Chúng ta có thể tránh các loại vấn đề này bằng cách sử dụng các cuộc gọi "* tại". Trước hết, chúng tôi có được một điều khiển tham chiếu đến thư mục nơi chúng tôi sẽ thực hiện công việc của mình:
dirfd = open("/dir/dir2", O_PATH);
Điểm quan trọng ở đây là dirfd
một tham chiếu ổn định đến thư mục được tham chiếu bởi đường dẫn /dir1/dir2
tại thời điểm open()
cuộc gọi. Nếu mục tiêu của liên kết tượng trưng dir2
sau đó được thay đổi, điều này sẽ không ảnh hưởng đến những gì dirfd
đề cập đến. Bây giờ, chúng ta có thể thực hiện thao tác kiểm tra + bằng cách sử dụng các cuộc gọi "* tại" tương đương với các cuộc gọi stat()
và open()
cuộc gọi ở trên:
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 */
}
Trong các bước này, mọi thao tác liên kết tượng trưng trong tên đường dẫn /dir/dir2
sẽ không có tác động: kiểm tra ( fstatat()
) và thao tác ( openat()
) được đảm bảo diễn ra trong cùng một thư mục.
Có một mục đích khác để sử dụng các cuộc gọi "* at ()", liên quan đến ý tưởng "thư mục làm việc hiện tại trên mỗi luồng" trong các chương trình đa luồng (và một lần nữa chúng ta có thể mở các thư mục bằng cách sử dụng O_PATH
), nhưng tôi nghĩ rằng việc sử dụng này có lẽ là ít liên quan đến câu hỏi của bạn và tôi để bạn đọc open(2)
trang người đàn ông nếu bạn muốn biết thêm.
Sử dụng với mô tả tệp cho các tệp thông thường
Một cách sử dụng O_PATH
với các tệp thông thường là mở tệp nhị phân mà chúng tôi có quyền thực thi (nhưng không nhất thiết phải đọc quyền, do đó chúng tôi không thể mở tệp với O_RDONLY
). Mô tả tập tin đó sau đó có thể được thông qua fexecve(3)
để thực hiện chương trình. Tất cả những fexecve(fd, argv, envp)
gì đang làm với fd
đối số của nó về cơ bản là:
snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);
(Mặc dù, bắt đầu với glibc 2.27, việc triển khai thay vào đó sẽ sử dụng execveat(2)
lệnh gọi hệ thống, trên các hạt nhân cung cấp lệnh gọi hệ thống đó.)