Hi Christian,
erstmal: Wow, Wahnsinn was du mal wieder für einen langen Beitrag geschrieben hast, Herzlichen Dank dafür schon mal vorab :-)
Wenn Du per setuid() die Benutzerkennung wechselst, dann werden reale und effektive Benutzer-ID auf eben die neue Benutzerkennung angepasst. Die Saved-Set-User-ID wird dann auf die alte Benutzerkennung gesetzt - damit weiß der Kernel, dass bei einem eventuellen, neuen Zurückwechseln-Wollen der Prozess berechtigt ist, zur *alten* Benutzerkennung zurückzukehren.
Ok, die Idee dahinter habe ich verstanden, das Prinzip glaube ich auch ;-) Letztendlich ist die Saved-Set nur ein "Speicherplatz" für eine User-ID, zu der das Script (zurück-) wechseln darf und kann vom Script nicht geändert werden (deshalb ja Saved-Set).
Dein ursprüngliches Problem ist, dass der Kernel das setuid-Bit nicht auf Scripte anwendet.
Das scheint übrigens eine Debian Security-Policy zu sein oder so etwas in der Art, zumindest nach dem was meine bisherigen Recherchen ergeben haben. Offensichtlich wird das aber von den meisten anderen Distributionen genauso gehandhabt.
- "/bin/ls"
- { "ls", "-l", NULL }
- environ
Woher kommt das eigentlich, dass man dem Programm als ersten Parameter immer noch mal seinen eigenen Namen (ls) bzw. den Pfad zum eigenen Namen (/bin/ls) übergibt? Macht man das einfach, weil das so üblich ist und jeder das so macht, oder steckt da ein tieferer Sinn dahinter?
Ok, stell Dir folgendes vor: Du hast den Code mit system ("/bin/bash /pfad/zum/script"); bei Dir drin. Jemand ruft dann Dein setuid-Programm auf, setzt aber vorher *irgendwelche* Umgebungsvariablen, die eine besondere Bedeutung haben, auf bösartige Werte. Dann werden die Umgebungsvariablen zum Script durchgereicht. Nehmen wir (das einfachste Beispiel) PATH: Wenn der Aufrufer bei sich PATH auf /boeser/pfad:/bin:/usr/bin:... setzt und bei sich in /boeser/pfad eine ausführbare Datei namens 'ls' existiert und Dein Script selbst ruft 'ls' auf - dann würde Dein Script plötzlich eben diese bösartige Datei ausführen. Du hattest diesmal sogar Glück, dass Du im system()-Aufruf "/bin/bash ..." stehen hattest und nicht "bash ..." - dann wäre nämlich schon in der Phase ein Angriff möglich. Und PATH ist nicht die einzige Umgebungsvariable, mit der man Sachen anstellen kann, es ist nur die einfachste und anschaulichste Lösung. Weitere böse Umgebungsvariablen beinhalten beispielsweise LD_LIBRARY_PATH und LD_PRELOAD.
Danke für diese Sicherheits-Aufklärungen - zwar hab ich davon schon mal gehört und auch derweil hier im Forum drüber gelesen, aber dass dies bei so kleinen Programmen wie diesem hier schon so relevant wird, da denke ich als !C-Programmierer natürlich nicht dran ;-) Letztendlich läuft es aber doch immer darauf hinaus, dass Programme die man in seinem Script (oder Programm, je nach dem) verwendet weitere Programme, Libraries oder Scripte über Pfade aus Umgebungsvariablen nachladen wollen - oder übersehe ich da was und pauschalisiere das deshalb fälschlicherweise?
Zu dem Code hätte ich noch zwei Fragen:
int main (int argc, char **argv) {
Was ist Unterschied von **argv zu *argv[]?
char *const p_env[] = { [...] };
char *const p_argv[] = { [...] };
const char *p_prog = "/bin/bash";
Warum heißt es hier in den ersten beiden Fällen char const und im letzten Fall const char, also anders herum?
hier kann dann entweder die UID fest kodiert stehen wie in
meinem einfachen Beispiel oder eben eine Logik, die den
Pfad des ausgeführten Programms herausfindet und die UID
über stat() ausliest (wird relativ kompliziert, wenn man es
richtig machen will)
Im Prinzip ist es doch keine Gefahr, die UID hart zu kodieren, weil wenn es nicht die UID des Scriptes ist, scheitert der User-Wechsel ja sowieso, korrekt so weit?
Damit hast Du dann ein kleines C-Programm, das nichts anderes tut, als Dein Shell-Script mit sauberen (!) Umgebungsvariablen unter einem anderen User aufzurufen.
Danke, ich habe das Programm zweimal kompiliert (einmal für pre-update.sh und einmal für post-update.sh), set-UID Bit gesetzt und siehe da, es funktioniert alles einwandfrei :-)
Vielleicht noch kurz ein Wort zum Anwendungszweck: Auf meinem Server sind mehrere SVN-Repositores, die (wie unter Debian üblich) vom User svn verwaltet werden. Nun will ich dort in einem Repository einen Pre-Hook und einen Post-Hook einbauen, das sind Scripte die von svn aufgerufen werden. Da ich dabei dem User svn keinen Lesezugriff auf sämtliche Verzeichnisse von vps geben will, kann mit obigem Script nun der User vps sich selber die neuesten Updates aus dem SVN-Repository holen und in die richtigen Zielverzeichinsse packen. Schönerweise sind dann nämlich auch Owner und Gruppe direkt richtig gesetzt und ich brauch nicht mit root noch mal den Owner von svn auf vps ändern.
Viele Grüße,
~ Dennis.