File-Descriptors

我應該使用“O_PATH”做什麼,如何使用?

  • September 22, 2017

我使用基於 Linux 4.x 的發行版,最近我注意到核心的open()系統呼叫支持O_PATH打開標誌。

雖然它的man頁面確實有一個理論上可以使用的系統呼叫列表,但我不太明白這個想法是什麼。我open(O_PATH)只有目錄,而不是文件嗎?如果我這樣做了,為什麼我要使用文件描述符而不是目錄的路徑?此外,那裡列出的大多數係統呼叫似乎都不是特定於目錄的。那麼,我是否也打開正常文件O_PATH以某種方式將它們的目錄作為文件描述符?或者為他們獲取文件描述符但功能有限?

O_PATH有人可以對我們應該使用它的內容、方式和目的給出令人信服的解釋嗎?

筆記:

  • 除非必要,否則無需描述其演變的歷史(相關手冊頁提到了 Linux 2.6.x、3.5 和 3.6 中的變化)——我只關心現在的情況。
  • 請不要告訴我只使用 libc 或其他更高級別的工具,我知道。

手冊頁中的描述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)。所以,有一點很重要:如果這是我們的目的,那麼打開 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().

為了使這個更具體……假設我們有一個程序想要在其目前工作目錄以外的目錄中執行多個操作,這意味著我們必須指定一些目錄前綴作為我們使用的文件名的一部分。例如,假設路徑名是/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的**穩定引用。**如果符號連結的目標隨後發生變化,這不會影響所指的內容。現在,我們可以使用與上面的and呼叫等效的“*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 */
}

在這些步驟中,對路徑名中符號連結的任何操作/dir/dir2都不會產生影響:檢查 ( fstatat()) 和操作 ( openat()) 保證在同一目錄中進行。

使用“*at()”呼叫還有另一個目的,它與多執行緒程序中的“每執行緒目前工作目錄”的概念有關(我們可以再次使用打開目錄O_PATH),但我認為這種用法可能是與您的問題不太相關,open(2)如果您想了解更多資訊,我會讓您閱讀手冊頁。

與正常文件的文件描述符一起使用

with 正常文件的一種用法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)系統呼叫,在提供該系統呼叫的核心上。)

引用自:https://unix.stackexchange.com/questions/392546