Wenn man unter Unix ein Programm ausführen will. dann benutzt man am Ende execve(). execve nimmt den Namen des Programms als String-Argument.
Das ist normalerweise in Ordnung. Aber eine Sache hat mich immer gestört. Ich kann nicht einen leeren Container aufsetzen und dann in dem Container ein Programm ausführen. Das Programm (die Datei) muss selbst in dem Container liegen.
Das Programm kann den Container selber aufsetzen, nachdem man es gestartet hat, und sich dann in dem Container einschließen. Das geht.
Aber jetzt geht auch ein Programm in einem Container starten. Linux hat jetzt einen Syscall namens execveat (schon seit Jahren, seit Version 3.14 sagt die Man Page). Das nimmt wie openat einen "dirfd", wo man den Deskriptor eines Verzeichnisses reingeben kann, und relativ zu dem werden relative Dateinamen dann aufgelöst. Aber es gibt auch ein Flags-Argument, und da gibt es das Flag AT_EMPTY_PATH. Wenn man das übergibt, dann ist dirfd nicht ein Verzeichnis sondern das Binary, das man ausführen will. Damit wird jetzt ein Szenario möglich, in dem man ein Binary öffnet, dann ein chroot oder Namespace-basiertes Jail aufsetzt, in dem das Binary nicht liegt, und dann in dem Jail das Binary ausführt.
Finde ich super. Wollte ich schon länger haben. Hilft natürlich erstmal nur mir, denn dynamisch gelinkte Programme brauchen immer noch ihre shared libraries in dem Jail, und Skripte brauchen immer noch ihre Interpreter in dem Jail. Das will man ja eigentlich auch nicht haben. Aber ich linke ja statisch, für mich reicht das.
Es hat noch eine interessante Auswirkung. Man kann unter Linux mit memfd_create einen Deskriptor auf eine Datei erzeugen, die nie die Festplatte berührt. Da kann man dann eine Malware reinschreiben und so ausführen. "Endpoint Security", die Dateien auf der Platte scannt, würde das gar nicht sehen.
Man konnte sich das bisher zusammentricksen, indem man execve auf /proc/self/fd/15 macht, aber dafür muss in dem Jail /proc gemountet sein.