Fragen? Antworten! Siehe auch: Alternativlos
Unter Linux gibt es neben fork() auch noch clone(), das ein paar Flags und optional zusätzliche Argument kriegt. Damit kann man dann den neuen Prozess zu einem Thread machen, und sagen, ob der Speicher geshared werden soll und so weiter. Und mit den User Namespaces kann man jetzt eben auch sagen: Der neue Prozess läuft in einem eigenen Namespace für PIDs, für Dateisystem-Mountpunkte, für den Hostname, das Netzwerk, etc. Der Kniff bei den User Namespaces ist jetzt, dass man innerhalb der neuen Namespaces root-Capabilities hat, also so Dinge darf wie chroot aufrufen oder sein virtuelles Netzwerkinterface konfigurieren. Diese Änderungen gelten dann nur relativ zu dem neuen Namespace.
Durch diesen Mechanismus hat man jetzt also plötzlich auch als Nicht-Root-Prozess die Möglichkeit, sich per chroot wegzusperren, und kann sich per PID-Namespace die Möglichkeit nehmen, per ptrace() oder kill() andere Prozesse zu manipulieren.
Wer auch mal damit herumspielen möchte: Hier ist ein bisschen Code zum Herumspielen:
#define _GNU_SOURCEDa gehört jetzt natürlich noch ordentlich Fehlerbehandlung und so rein, klar. Ein bisschen fummelig ist, dass man dann natürlich seine Capabilities ablegen sollte, bevor man irgendwas tut, und das API könnte schöner sein. Das ist das capset hier in dem Spielcode. Da könnte man auch libcap für nehmen, aber ich habe bei sowas gerne so wenig unnötige Abhängigkeiten wie möglich.
#include <sys/mman.h>
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/capability.h>int clonecallback(void* x) {
struct __user_cap_header_struct ch = { .version=_LINUX_CAPABILITY_VERSION_3, .pid=0 };
struct __user_cap_data_struct cd[2] = { 0 };
chroot(".");
capset(&ch,cd);
printf("uid %u, euid %u, pid %u, parent pid %u\n",getuid(),geteuid(),getpid(),getppid());
exit(0);
}int main() {
char* stack=mmap(NULL, 4096+8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN|MAP_STACK,-1,0);
if (stack!=MAP_FAILED) {
mprotect(stack,4096,PROT_NONE); /* guard page */
if (clone(clonecallback, stack+4096+8192, CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUSER|CLONE_NEWUTS, /*arg*/ NULL)==-1) {
perror("clone failed");
return 0;
}
sleep(1);
} else
perror("mmap failed");
}
Viel Spaß beim Ausprobieren!
Update: Das mit den Capabilities ist komplex und fühlt sich für mich sehr unintuitiv an. Hier gibt es einen weiterhelfenden Blogartikel dazu.