Ich verwende eine Linux 4.x-basierte Distribution und habe kürzlich festgestellt, dass der open()
Systemaufruf des Kernels ein O_PATH
offenes Flag unterstützt.
Während die man
Seite dafür eine Liste von Systemaufrufen enthält, mit denen sie theoretisch verwendet werden könnte, verstehe ich die Idee nicht ganz. Mache ich open(O_PATH)
nur Verzeichnisse anstatt Dateien? Und wenn ja, warum möchte ich anstelle des Verzeichnispfads einen Dateideskriptor verwenden? Außerdem scheinen die meisten der dort aufgeführten Systemaufrufe nicht spezifisch für Verzeichnisse zu sein. öffne ich also auch reguläre Dateien mit O_PATH
, um deren Verzeichnis irgendwie als Dateideskriptor zu erhalten? Oder um einen Dateideskriptor für sie zu bekommen, aber mit eingeschränkter Funktionalität?
Kann jemand eine überzeugende Erklärung geben, worum es O_PATH
geht und wie und wofür wir es verwenden sollen?
Anmerkungen:
- Es ist nicht erforderlich, den Verlauf der Entwicklung zu beschreiben (in den entsprechenden Manpages werden Änderungen in Linux 2.6.x, 3.5 und 3.6 erwähnt), es sei denn, dies ist erforderlich. Ich kümmere mich nur darum, wie die Dinge jetzt sind.
- Bitte sag mir nicht, ich soll nur libc oder andere übergeordnete Einrichtungen verwenden, das weiß ich.
quelle
Antworten:
Die Beschreibung in der
open(2)
Manpage gibt zunächst einige Hinweise: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 habenO_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 öffnenO_RDONLY
. Für die Verwendung von istO_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, wieopenat()
,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 deropen(2)
Manpage nachsehen , finden wir diesen Text (unter einer Unterüberschrift mit der Begründung für die Systemaufrufe "* at"):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:/dir1/dir2/file
(z. B. wem die Datei gehört oder wann sie zuletzt geändert wurde)./dir1/dir2/file.new
.Nehmen wir zunächst an, wir haben alles mit herkömmlichen Systemaufrufen auf der Basis von Pfadnamen durchgeführt:
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 Linkdir2
, 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:
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 desopen()
Aufrufs verwiesen hat . Wenn das Ziel der symbolischen Verknüpfungdir2
anschließend geändert wird, hat dies keine Auswirkungen auf das, woraufdirfd
Bezug genommen wird. Jetzt können wir unsere check + -Operation mit den "* at" -Aufrufen ausführen, die den obigen Aufrufenstat()
und entsprechenopen()
: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, dieopen(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önnenO_RDONLY
). Dieser Dateideskriptor kann dann übergeben werdenfexecve(3)
, um das Programm auszuführen. Alles,fexecve(fd, argv, envp)
was mit seinerfd
Argumentation zu tun hat , ist im Wesentlichen:(Ab Glibc 2.27 verwendet die Implementierung stattdessen den
execveat(2)
Systemaufruf auf Kerneln, die diesen Systemaufruf bereitstellen.)quelle
The problem is that between the existence check and the file creation step, path or to ... could be modified
- kann diesen Satz nicht analysieren. Aber ich verstehe das Wesentliche, denke ich. Es dient also als eine Art Sperrmechanismus für ein Verzeichnis. Aber warum sollte man dasopen()
Ergebnis anstelle einer tatsächlichen Sperre verwenden?