Die Beschreibung in der open(2)
Manpage gibt zunächst einige Hinweise:
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.
Manchmal möchten wir keine Datei oder ein Verzeichnis öffnen. Stattdessen möchten wir nur einen Verweis auf dieses Dateisystemobjekt, um bestimmte Vorgänge auszuführen (z. B. fchdir()
auf ein Verzeichnis, auf das durch einen Dateideskriptor verwiesen wird, den wir mit geöffnet haben O_PATH
). Ein trivialer Punkt: Wenn dies unser Ziel ist, O_PATH
sollte das Öffnen mit etwas billiger sein, da die Datei selbst nicht geöffnet wird.
Und ein weniger trivialer Punkt: Vor der Existenz von O_PATH
bestand die Möglichkeit, einen solchen Verweis auf ein Dateisystemobjekt zu erhalten, darin, das Objekt mit zu öffnen O_RDONLY
. Für die Verwendung von ist O_RDONLY
jedoch eine Leseberechtigung für das Objekt erforderlich. Es gibt jedoch verschiedene Anwendungsfälle, in denen das Objekt nicht tatsächlich gelesen werden muss: Zum Beispiel das Ausführen einer Binärdatei oder der Zugriff auf ein Verzeichnis ( fchdir()
) oder das Greifen über ein Verzeichnis, um ein Objekt im Verzeichnis zu berühren.
Verwendung mit Systemaufrufen "* at ()"
Die gemeinsame, aber nicht die einzigen, die Verwendung von O_PATH
ist ein Verzeichnis zu öffnen, um einen Verweis auf das Verzeichnis für die Verwendung mit der „* bei“ Systemaufrufen zu haben, wie openat()
, fstatat()
, fchownat()
, und so weiter. Diese Familie von Systemaufrufen, die wir als den modernen Nachfolger der älteren Systemaufrufe mit ähnlichen Namen grob denken kann ( open()
, fstat()
, fchown()
, usw.), dienen ein paar Zwecke, von denen die erste auf Sie berühren , wenn Sie fragen " Warum möchte ich einen Dateideskriptor anstelle des Verzeichnispfads verwenden? ". Wenn wir weiter unten in der open(2)
Manpage nachsehen , finden wir diesen Text (unter einer Unterüberschrift mit der Begründung für die Systemaufrufe "* 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().
Um dies konkreter zu machen ... Angenommen, wir haben ein Programm, das mehrere Operationen in einem anderen Verzeichnis als dem aktuellen Arbeitsverzeichnis ausführen möchte, was bedeutet, dass wir als Teil der von uns verwendeten Dateinamen ein Verzeichnispräfix angeben müssen. Angenommen, der Pfadname lautet /dir1/dir2/file
und wir möchten zwei Operationen ausführen:
- Führen Sie eine Überprüfung durch
/dir1/dir2/file
(z. B. wem die Datei gehört oder wann sie zuletzt geändert wurde).
- Wenn wir mit dem Ergebnis dieser Prüfung zufrieden sind, möchten wir möglicherweise eine andere Dateisystemoperation im selben Verzeichnis ausführen, z. B. eine Datei mit dem Namen
/dir1/dir2/file.new
.
Nehmen wir zunächst an, wir haben alles mit herkömmlichen Systemaufrufen auf der Basis von Pfadnamen durchgeführt:
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 */
}
Angenommen, im Verzeichnispräfix war /dir1/dir2
eine der Komponenten (z. B. dir2
) tatsächlich eine symbolische Verknüpfung (die sich auf ein Verzeichnis bezieht), und zwischen dem Aufrufstat()
open()
einer böswilligen Person und dem Aufruf einer böswilligen Person konnte das Ziel der geändert werden symbolischer Link dir2
, um auf ein anderes Verzeichnis zu verweisen. Dies ist eine klassische Race-of-Check-Time-of-Use-Rennbedingung. Unser Programm überprüfte eine Datei in einem Verzeichnis, wurde dann aber dazu verleitet, eine Datei in einem anderen Verzeichnis zu erstellen - möglicherweise in einem sicherheitsrelevanten Verzeichnis. Der entscheidende Punkt hier ist, dass der Pfadname gleich /dir/dir2
aussah, aber was er bezieht, hat sich komplett geändert.
Wir können diese Art von Problemen mit den "* at" -Aufrufen vermeiden. Zunächst erhalten wir ein Handle, das auf das Verzeichnis verweist, in dem wir unsere Arbeit erledigen werden:
dirfd = open("/dir/dir2", O_PATH);
Der kritische Punkt hierbei ist, dass dirfd
es sich um eine stabile Referenz auf das Verzeichnis handelt, auf das der Pfad /dir1/dir2
zum Zeitpunkt des open()
Aufrufs verwiesen hat . Wenn das Ziel der symbolischen Verknüpfung dir2
anschließend geändert wird, hat dies keine Auswirkungen auf das, worauf dirfd
Bezug genommen wird. Jetzt können wir unsere check + -Operation mit den "* at" -Aufrufen ausführen, die den obigen Aufrufen stat()
und entsprechen 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 */
}
Während dieser Schritte hat jede Manipulation symbolischer Links im Pfadnamen /dir/dir2
keine Auswirkungen: Die Prüfung check ( fstatat()
) und die Operation ( openat()
) finden garantiert im selben Verzeichnis statt.
Die Verwendung der Aufrufe "* at ()" hat einen anderen Zweck, der sich auf die Idee von "aktuellen Arbeitsverzeichnissen pro Thread" in Multithread-Programmen bezieht (und wir könnten die Verzeichnisse wieder mit öffnen O_PATH
), aber ich denke, diese Verwendung ist wahrscheinlich weniger relevant für Ihre Frage, und ich überlasse es Ihnen, die open(2)
Manpage zu lesen, wenn Sie mehr wissen möchten.
Verwendung mit Dateideskriptoren für reguläre Dateien
Eine Verwendung von O_PATH
regulären Dateien besteht darin, eine Binärdatei zu öffnen, für die wir die Ausführungsberechtigung haben (aber nicht unbedingt die Leseberechtigung, damit wir die Datei nicht mit öffnen können O_RDONLY
). Dieser Dateideskriptor kann dann übergeben werden fexecve(3)
, um das Programm auszuführen. Alles, fexecve(fd, argv, envp)
was mit seiner fd
Argumentation zu tun hat , ist im Wesentlichen:
snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);
(Ab Glibc 2.27 verwendet die Implementierung stattdessen den execveat(2)
Systemaufruf auf Kerneln, die diesen Systemaufruf bereitstellen.)