Das Geheimnis von su

Während in Windows ein ganzes Gestrüpp an Funktionen herangewachsen ist um Prozesse mit anderen Rechten zu starten, hat POSIX bzw. Linux gleich ganz auf eine API verzichtet. Wer root ist, darf mit setuid() und seinen Freunden machen was er will und sonst hat niemand (kein nicht-privilegierter Account) was zu melden.

Wie bekommt also ein normaler User eine Funktion mit “erhöhten Rechten” ausgeführt?

Na man ruft das Programm su oder sudo auf … und die machen das schon irgendwie.

Aber wie?

Es beginnt bei einer aus meiner Sicht ganz seltsamen Eigenart der Unix-Dateisysteme. Kurz gesagt, wenn eine Datei das sogenannte “SUID-Bit” gesetzt hat, ist es egal, welcher User das Programm startet, das Programm selbst läuft automatisch mit den Rechten des Eigentümers der Datei.

Also brauchen die Dateien su und sudo einfach nur dem Benutzer root gehören, dann laufen sie auch immer als root und können wie oben beschrieben die setuid() APIs nutzen um sich selbst auf jeden anderen erdenklichen Account umzusetzen und dann per exec*() einfach das Zielprogramm starten, und das Zielprogramm erbt dann den gewünschten Account.

So weit so gut … für tippende Administratoren. Aber wie bringen wir dieses Feature in eine Programmfunktion, mit der wir per C Code Programme mit anderen Usern starten können?

Natürlich können wir auf mit fork() und exec() aufrufen, aber dann müssen wir ja als erstes das Passwort des Zielaccounts “eintippen”.

Wer glaubt, wir können uns einfach STDIN krallen und es dort hineinschreiben, der wird enttäuscht. So unsicher ist Linux (sind Unices) nicht. su liest das Passwort vom Terminal direkt und nicht über STDIN.

Wir können aber ein neues virtuelles Terminal erzeugen, und zwar mit forkpty() und hier erhalten wir eine Deskriptor, den wir beschreiben können und so empfängt su dann unser Passwort.

Wenn alles gut geht, haben wir somit eine Möglichkeit geschaffen, von einem nicht-privilegiertem Prozess aus einen anderen mit mehr Rechten zu starten, den wir teilweise per Ein- und Ausgabe fernsteuern können.


In der Praxis ist es dann schon noch ein bisschen komplizierter. Wir sollten vor dem Aufruf von su schon wissen, ob Account und Passwort korrekt sind, weil wir sonst nicht unterscheiden können, ob su mit einem Fehler terminiert hat oder das Zielprogramm.
Die libpam ist da recht hilfreich.
Und die eine oder andere Adaption der Environment-Variablen nach forkpty() ist manchmal auch notwendig.

Nachtrag: sudo habe ich noch nie für solche Zwecke eingesetzt. Bei der Schaffung einer API analog zu CreateProcessWithLogon unter Windows, ist ein Passwort immer erforderlich.

Vielleicht bietet sudo gepaart mit anderen Bibliotheken heutzutage bessere Optionen. Mein Wissen zu forkpty() und Co stammt jedenfalls aus dem Quellcode zu kdesudo und hat bisher gut funktioniert.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!