rwx - sonst nix, Unix Rechte und wie sie funktionieren

8
rwx - sonst nix? Experiment 1: UID und GID an Dateien Experiment 2: UID und GID von Prozessen Experiment 3: x-Recht an Verzeichnissen Experiment 4: Shellscripte mit SUID-Rechten? aus "Linux Magazin", Ausgabe 3/96. Von Kristian Köhntopp rwx - sonst nix? In jedem Buch über UNIX wird das UNIX-Rechtesystem ungefähr in Kapitel 2 vollständig erläutert und es bleiben keine Frage mehr offen. Wieso also ein Artikel über Zugriffsrechte? Nun, dieser Artikel erklärt Zugriffsrechte für Leute, die es ganz genau wissen wollen: Welche Rechte werden für den Zugriff auf eine Datei benötigt? Wer prüft die Rechte und wie passiert das? Wie oft hört man Redewendungen wie "Ich öffne jetzt einmal diese Datei..." oder "Ich habe keine Zugriffsrechte, um auf dieses Verzeichnis zuzugreifen.". Solche Formulierungen sind zwar üblich und auch jedermann verständlich, weil sie so schön bildhaft sind. Aber wenn man über Zugriffsrechte reden möchte, sind sie eher schädlich als hilfreich. Natürlich öffnet ein User niemals eine Datei oder greift auf ein Verzeichnis zu. Prozesse tätigen Systemaufrufe und der Kernel öffnet Dateien oder liest Verzeichnisse im Auftrag und mit den Rechten dieses Prozesses. Solche Unterscheidungen sind auf den ersten Blick Haarspaltereien. Aber wenn man sich den Spaß macht und die Sache einmal zu Ende verfolgt, wird man feststellen, daß sich auf diese Weise einige Dinge besser verstehen und leichter erklären lassen. Der Autor will sich also in diesem Artikel einer besonders genauen Sprache befleißigen. Woher kommen Zugriffsrechte? Zugriffsrechte und Eigentümer sind etwas, das einem zunächst an Dateien ins Auge fällt. Eine Datei besteht in Linux aus einem Haufen Blöcken auf der Festplatte und einer Verwaltungsstruktur, der I-Node. Während die Blöcke die eigentliche Information in der Datei speichern, findet man in der I-Node fast alle Information über die Datei - also Metainformation. Was die Rechte betrifft, findet man in der I-Node eine User-ID (UID), eine Group-ID (GID) und zwölf Rechtebits: Drei Bits mit den Bezeichnungen sst und dann jeweils drei Dreiergruppen rwx. sst sind gewissermaßen Allzweckbits, die je nach Typ der Datei und Systemaufruf unterschiedliche Bedeutung haben können. Die Auswertung der übrigen neun Bits erfolgt dagegen relativ zentral in einer Funktion permission(), auf die wir weiter unten eingehen werden. Was dem UNIX-Neuling noch auffallen wird, ist die Tatsache, daß sich in der I-Node der Datei nicht der Username und der Gruppenname findet, sondern nur jeweils eine ID. Diese ID-Nummern werden vom ls-Kommando in Namen umgewandelt, falls man es ihm nicht verbietet. Man kann das ls-Kommando zwingen, die Angaben numerisch zu machen, indem man die Option -n verwendet oder indem man dem Benutzer, der ls aufruft, den Lesezugriff auf /etc/passwd und /etc/group verweigert (Experiment 1 ). Experiment 1 root@white:~# ls -l /etc/passwd /etc/group -rw-r--r-- 1 root root 281 Jul 15 14:39 /etc/group -rw-r--r-- 1 root root 1163 Nov 28 15:30 /etc/passwd root@white:~# chmod 000 /etc/passwd /etc/group root@white:~# su - kris kris@white:~$ ls -l /tmp -rw-r--r-- 1 0 0 5 Nov 23 13:36 lall -rw-rw-rw- 1 60000 20 11 Nov 28 21:32 lpq.00002b98 drwxr-xr-x 2 0 0 1024 Nov 17 07:31 root kris@white:~$ exit root@white:~# chmod 644 /etc/passwd /etc/group root@white:~# ls -l /tmp -rw-r--r-- 1 root root 5 Nov 23 13:36 lall -rw-rw-rw- 1 smbguest users 11 Nov 28 21:32 lpq.00002b98 drwxr-xr-x 2 root root 1024 Nov 17 07:31 root

Transcript of rwx - sonst nix, Unix Rechte und wie sie funktionieren

Page 1: rwx - sonst nix, Unix Rechte und wie sie funktionieren

rwx - sonst nix?Experiment 1: UID und GID an DateienExperiment 2: UID und GID von ProzessenExperiment 3: x-Recht an VerzeichnissenExperiment 4: Shellscripte mit SUID-Rechten?

aus "Linux Magazin", Ausgabe 3/96. Von Kristian Köhntopp

rwx - sonst nix?In jedem Buch über UNIX wird das UNIX-Rechtesystem ungefähr in Kapitel 2 vollständig erläutert und es bleibenkeine Frage mehr offen. Wieso also ein Artikel über Zugriffsrechte? Nun, dieser Artikel erklärt Zugriffsrechte fürLeute, die es ganz genau wissen wollen: Welche Rechte werden für den Zugriff auf eine Datei benötigt? Wer prüft dieRechte und wie passiert das?

Wie oft hört man Redewendungen wie "Ich öffne jetzt einmal diese Datei..." oder "Ich habe keine Zugriffsrechte, umauf dieses Verzeichnis zuzugreifen.". Solche Formulierungen sind zwar üblich und auch jedermann verständlich, weilsie so schön bildhaft sind. Aber wenn man über Zugriffsrechte reden möchte, sind sie eher schädlich als hilfreich.

Natürlich öffnet ein User niemals eine Datei oder greift auf ein Verzeichnis zu. Prozesse tätigen Systemaufrufe undder Kernel öffnet Dateien oder liest Verzeichnisse im Auftrag und mit den Rechten dieses Prozesses. SolcheUnterscheidungen sind auf den ersten Blick Haarspaltereien. Aber wenn man sich den Spaß macht und die Sacheeinmal zu Ende verfolgt, wird man feststellen, daß sich auf diese Weise einige Dinge besser verstehen und leichtererklären lassen. Der Autor will sich also in diesem Artikel einer besonders genauen Sprache befleißigen.

Woher kommen Zugriffsrechte?Zugriffsrechte und Eigentümer sind etwas, das einem zunächst an Dateien ins Auge fällt. Eine Datei besteht in Linuxaus einem Haufen Blöcken auf der Festplatte und einer Verwaltungsstruktur, der I-Node. Während die Blöcke dieeigentliche Information in der Datei speichern, findet man in der I-Node fast alle Information über die Datei - alsoMetainformation. Was die Rechte betrifft, findet man in der I-Node eine User-ID (UID), eine Group-ID (GID) undzwölf Rechtebits: Drei Bits mit den Bezeichnungen sst und dann jeweils drei Dreiergruppen rwx. sst sindgewissermaßen Allzweckbits, die je nach Typ der Datei und Systemaufruf unterschiedliche Bedeutung haben können.Die Auswertung der übrigen neun Bits erfolgt dagegen relativ zentral in einer Funktion permission(), auf die wirweiter unten eingehen werden.

Was dem UNIX-Neuling noch auffallen wird, ist die Tatsache, daß sich in der I-Node der Datei nicht der Usernameund der Gruppenname findet, sondern nur jeweils eine ID. Diese ID-Nummern werden vom ls-Kommando in Namenumgewandelt, falls man es ihm nicht verbietet. Man kann das ls-Kommando zwingen, die Angaben numerisch zumachen, indem man die Option -n verwendet oder indem man dem Benutzer, der ls aufruft, den Lesezugriff auf/etc/passwd und /etc/group verweigert (Experiment 1).

Experiment 1

root@white:~# ls -l /etc/passwd /etc/group -rw-r--r-- 1 root root 281 Jul 15 14:39 /etc/group -rw-r--r-- 1 root root 1163 Nov 28 15:30 /etc/passwd root@white:~# chmod 000 /etc/passwd /etc/group root@white:~# su - kris kris@white:~$ ls -l /tmp -rw-r--r-- 1 0 0 5 Nov 23 13:36 lall -rw-rw-rw- 1 60000 20 11 Nov 28 21:32 lpq.00002b98 drwxr-xr-x 2 0 0 1024 Nov 17 07:31 root kris@white:~$ exit root@white:~# chmod 644 /etc/passwd /etc/group root@white:~# ls -l /tmp -rw-r--r-- 1 root root 5 Nov 23 13:36 lall -rw-rw-rw- 1 smbguest users 11 Nov 28 21:32 lpq.00002b98 drwxr-xr-x 2 root root 1024 Nov 17 07:31 root

Page 2: rwx - sonst nix, Unix Rechte und wie sie funktionieren

Indem man kris die Leserechte auf die Dateien /etc/passwd und /etc/group entzieht, kann man demonstrieren, daß

in einer I-Node nur numerische Information über Eigentümer und Gruppe einer Datei gespeichert wird. ls kann diese

Zahlen durch Zugriff auf die Paßwortdatenbank in Namen übersetzen. Dazu existiert eine Familie von

Bibliotheksfunktionen, die in getpwent(3), getpwuid(3), getgrent(3) und getgrgid(3) dokumentiert ist.

Zugriffe auf Dateien erfolgen durch den Kernel im Auftrag eines Prozesses. Der Kernel entscheidet anhand der

Rechtebits in der Inode der Datei einer Datei, ob er den Zugriff auf die Datei ausführen wird oder nicht. Entscheidend

für die Interpretation dieser Rechtebits ist noch, mit welcher UID und GID der Prozeß abläuft, der auf die Datei

zugreifen möchte. Wie man in /usr/include/linux/sched.h nachlesen kann, hat ein struct task_struct in

Linux die Felder

#define NGROUPS 32

unsigned short uid,euid,suid,fsuid;

unsigned short gid,egid,sgid,fsgid;

int groups[NGROUPS];

Linux unterscheidet sich hier von einem normalen UNIX: Für Zugriffe auf Dateien sind die Felder fsuid, fsgid und

groups[] relevant, während ein normales UNIX euid und egid verwendet. Natürlich unterscheidet sich Linux nicht

so sehr von einem normalen UNIX, daß es inkompatibel wäre: Normalerweise ist die fsuid/fsgid mit der

euid/egid des Prozesses identisch. Die Zugriffe auf Dateien erfolgen also mit der sogenannten effektiven User-ID

und Group-ID eines Prozesses. Kasten 2 zeigt, wie man feststellen kann, unter welchen IDs Zugriffe erfolgen.

Experiment 2

kris@white:~$ id

uid=100(kris) gid=20(users) groups=20(users),11(floppy)

kris@white:~$ su -

Password:

root@white:~# id

uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy)

root@white:~# exit

kris@white:~$ id marit

uid=101(marit) gid=20(users) groups=11(floppy)

kris@white:~$ ls -l /tmp/bash

-rwsr-xr-x 1 root root 295940 Nov 29 12:07 /tmp/bash

kris@white:~$ /tmp/bash

kris@white:~# id

uid=100(kris) gid=20(users) euid=0(root) groups=20(users),11(floppy)

Jemand, der als kris eingeloggt ist, greift normalerweise mit der UID 100, der GID 20 und den Einträgen 20 und 11

im Feld groups[] auf Dateien zu. Ein Systemverwalter dagegen hat die UID und die GID 0 und noch weitere

Einträge im Feld groups[]. Mit einem Usernamen als Parameter kann man auch die IDs anderer Benutzer abfragen.

Effektive und reale UID und GID müssen nicht immer übereinstimmen: Werden Programme mit gesetztem Set UserID-Bit ausgeführt, kann sich die effektive UID eines Prozesses ändern - was set user id genau ist und was das Bit

bewirkt, werden wir später noch klären.

Wir halten fest: Dateien haben genau einen Eigentümer und gehören zu genau einer Gruppe, während ein Prozeß

einen Eigemtümer hat, aber zu bis zu 32 Gruppen gehören kann. Zugriffsrechte werden an Dateien in 12 Bit

vermerkt. Die Interpretation dieser 12 Bit hängt ab vom Typ der Datei und von der Identität des zugreifenden

Prozesses.

Wo werden die Rechte geprüft?Linux prüft Zugriffsrechte mit Hilfe der Funktion permission(). Sie ist in /usr/src/linux/fs/namei.c in den

Zeilen 91 bis 114 definiert. Ein Parameter von permission() ist ein Zeiger auf die I-Node der Datei, auf die

zugegriffen wird. Außerdem muß der Funktion noch gesagt werden, welche Art von Zugriff auf die Datei gewünscht

wird. /usr/include/linux/fs.h definiert dazu in den Zeilen 35 bis 37 die Werte MAY_EXEC, MAY_WRITE und

MAY_READ, die zu einer mask zusammengebaut werden können. Der Kasten zeigt den Quelltext von

permission() und die Definitionen aus fs.h.

Page 3: rwx - sonst nix, Unix Rechte und wie sie funktionieren

Die Kernelfunktion permission()

/usr/src/linux/fs/namei.c: 91 /* 92 * permission() 93 * 94 * is used to check for read/write/execute permissions on a file. 95 * We use "fsuid" for this, letting us set arbitrary permissions 96 * for filesystem access without changing the "normal" uids which 97 * are used for other things.. 98 */ 99 int permission(struct inode * inode,int mask) 100 { 101 int mode = inode->i_mode; 102 103 if (inode->i_op && inode->i_op->permission) 104 return inode->i_op->permission(inode, mask); 105 else if ((mask & S_IWOTH) && IS_IMMUTABLE(inode)) 106 return -EACCES; /* Nobody gets write access to an immutable file */ 107 else if (current->fsuid == inode->i_uid) 108 mode >>= 6; 109 else if (in_group_p(inode->i_gid)) 110 mode >>= 3; 111 if (((mode & mask & 0007) == mask) || fsuser()) 112 return 0; 113 return -EACCES; 114 }

/usr/include/linux/kernel.h: 68 #define fsuser() (current->fsuid == 0)

/usr/include/linux/fs.h: 35 #define MAY_EXEC 1 36 #define MAY_WRITE 2 37 #define MAY_READ 4

Die Funktion permission() ist der zentrale Kontrollpunkt für Zugriffsrechte. An ihr kann man sehen, nach welchemVerfahren Linux die Zugriffsrechte prüft

Der Algorithmus, nach dem permission() die Zugriffsrechte prüft, ist sehr einfach: Zunächst einmal (Zeile 103-104)wird festgestellt, ob für die zu prüfende I-Node im Feld i_op->permission() der I-Node Struktur einedateisystemspezifische permission()-Routine definiert ist. Auf diese Weise können Dateisysteme, die erweiterteoder von UNIX verschiedene Zugriffsrechte implementieren, ihre eigene Routine zum Testen des Zugriffsbereitstellen. Derzeit (Kernel 1.2.13) tut dies nur das ext2-Dateisystem mit der Funktion ext2_permission(), die in/usr/src/linux/fs/ext2/acl.c definiert ist (und die in Kernel 1.2.13 nichts anderes tut als Zeilen 107-113 dernormalen permission()-Funktion, also noch vollkommen sinnlos ist).

Die Zeilen 105-106 prüfen dann, ob die Datei als immutable file (unveränderliche Datei) gekennzeichnet ist. Wenndies der Fall ist und in mask MAY_WRITE gesetzt ist, wird der Zugriff auf die Datei verweigert und der Fehlerpermission denied (EACCES) zurückgegeben. Der Code verwendet in Zeile 105 seltsamerweise die DefinitionS_IWOTH (das Schreibrecht für other aus der Inlcudedatei linux/stat.h) anstatt korrekterweise die DefinitionMAY_WRITE zu verwenden. Da aber beide Defintionen den Wert 2 haben, kommt es nicht zu einem Fehler. Da dasext2-Dateisystem eine eigene permission()-Routine bereitstellt, die den Test auf immutable files nicht enthält, kannman dort unveränderliche Dateien beschreiben. Das ist ein Fehler, denn an anderen Stellen kennt das ext2-Dateisystemen IS_IMMUTABLE() und nimmt Rücksicht darauf.

Der entscheidende Code befindet sich dann in den Zeilen 107-113: current bezeichnet den struct task desaktuellen Prozesses, also desjenigen Prozesses, der soeben versucht hat, auf eine Datei zuzugreifen und dessenZugriffsrechte jetzt geprüft werden müssen. Falls die fsuid dieses Prozesses mit der i_uid der Dateiübereinstimmen, werden die Zugriffsrechte des linken rwx-Tripels der Datei für die Rechteprüfung verwendet (Zeilen107 und 108). Andernfalls prüft Linux mit der Funktion in_group_p(), ob irgendeine GID des aktuellen Prozessesmit der i_gid der Datei übereinstimmt. Ist dies der Fall, werden die Zugriffsrechte des mittleren rwx-Tripelsverwendet (Zeilen 109-110). Ist weder eine Übereinstimmung der User- noch der Group-IDs zu finden, werden dieDefaultzugriffsrechte aus dem rechten Tripel verwendet.

Die Funktion in_group_p() ist in /usr/src/linux/kernel/sys.c in den Zeilen 585-599 definiert. Sie testet, ob ihrArgument gleich der fsgid des aktuellen Prozesses ist oder ob das Argument gleich einer der Gruppen aus dem

Page 4: rwx - sonst nix, Unix Rechte und wie sie funktionieren

groups[]-Array des Prozesses ist. Der Name der Funktion erklärt sich aus der "p-convention" (siehe Jargon-File,ursprünglich eine Sitte aus dem Bereich der LISP-Programmierung): Es handelt sich um ein Prädikat, also eineFunktion, die entweder true oder false liefert, je nachdem ob die zu prüfende Aussage wahr oder falsch ist.

Die Bedingung in Zeile 111 entscheidet jetzt, ob der Zugriff auf eine I-Node gewährt wird oder nicht. Wennmindestens die in mask gewünschten Rechtebits in den durch mode schon in Zeile 101 vorab ermittelten Rechtenenthalten sind oder der Zugriff mit Superuser-Privilegien erfolgt (fsuser() ist wahr), wird der Zugriff auf die Dateigewährt. In allen anderen Fällen wird ein Fehler permission denied (EACCES) geliefert.

Wir halten fest: Einzelne Dateisysteme können besondere Rechteroutinen haben. Im Normalfall gelten aber dieUserrechte für den Eigentümer einer Datei, die Gruppenrechte für jeden, der Mitglied in der Gruppe der Datei ist unddie Other-Rechte für alle anderen. Die Prüfung dieser Rechte erledigt Linux an einer zentralen, leicht wartbaren Stelleim System.

Pfade und Rechte entlang des PfadesAlle UNIX-Kernelfunktionen, die mit Dateien hantieren, nehmen Pfadnamen als Parameter an. Intern verwendetLinux jedoch wie alle Unices ausschließlich I-Nodes, um mit Dateien zu arbeiten. Die Umwandlung einesPfadnamens in eine I-Node übernimmt in Linux eine von mehreren leicht unterschiedlichen namei()-Funktionen, diein /usr/src/linux/fs/namei.c definiert sind.

namei() bekommt, vereinfacht dargestellt, einen Pfadnamen und die I-Node eines Verzeichnisses als Startpunktübergeben. In diesem Verzeichnis wird mittels der Funktion lookup() die I-Node der ersten Komponente desPfadnamens gesucht. Diese I-Node wird jetzt als Startpunkt genommen, um die nächste Komponente des Pfadnamesdurch lookup() suchen zu lassen und so weiter bis zum Ende des Pfades. Am Ende des Pfades ist das Ergebnis dieserOperation genau die I-Node des Verzeichnisses, das durch den Pfadnamen bezeichnet wird.

Der Code von lookup() ist nicht ganz so einfach wie diese Erklärung, weil er so unschöne Dinge wie Mountpoints,".."-Komponenten im Pfadnamen und ungültige Pfadnamen berücksichtigen muß, aber er ist auch nicht sonderlichkompliziert. Für unsere Zwecke sind jedoch nur wenige Zeilen von lookup() wesentlich:

/usr/src/linux/fs/namei.c: 163 perm = permission(dir,MAY_EXEC);

181 if (perm != 0) { 182 iput(dir); 183 return perm; 184 }

Um in einem Verzeichnis einen Namen nachschlagen zu können, muß der aufrufende Prozeß das x-Recht an diesemVerzeichnis haben. Hat er dieses x-Recht nicht, schlägt der Aufruf fehl - ganz gleich, welche Kernelfunktionaufgerufen wurde. Die Folgen können ziemlich fatal sein, wie Experiment 3 demonstriert.

Experiment 3

Warnung: Dieses Experiment darf nur durchgeführt werden, wenn in einem zweiten Fenster noch eine weitereRootshell aktiv ist. Andernfalls kann man sich so die Systeminstallation ruinieren.

kris@white:~$ ls -ld / drwxr-xr-x 20 root root 1024 Nov 29 22:29 / kris@white:~$ su - root@white:~# chmod go-x / root@white:~# ls -ld / drwxr--r-- 20 root root 1024 Nov 29 22:29 / root@white:~# exit kris@white:~$ ls csh: /bin/ls: Permission denied kris@white:~$ su csh: /bin/su: Permission denied

In einem anderen Fenster mit einer aktiven Rootshell:

root@white:~# chmod 755 /

Das Hauptverzeichnis / hat normalerweise die Rechte 755. Jeder Benutzer hat also x-Recht am Verzeichnis / undkann damit absolute Pfadnamen auflösen. Nimmt man dem Root-Verzeichnis die x-Rechte, kann kein normaler

Page 5: rwx - sonst nix, Unix Rechte und wie sie funktionieren

Benutzer mehr absolute Pfadnamen auflösen und jeder Aufruf eines Systemkommandos schlägt fehl. Insbesondere istauch kein su mehr möglich, um die Operation rückgängig zu machen. Daher muß schon vor dem Beginn desExperimentes eine zweite, aktive Rootshell bereitstehen, um die Zugriffsrechte wieder zu reparieren.

Wir halten fest: Jede richtige Antwort auf die Frage "Welche Rechte braucht man, um mit einer Datei eine bestimmteOperation (Öffnen, Löschen, Umbenennen und so weiter) durchführen zu können?" muß mit den Worten "Manbraucht die x-Rechte an den Verzeichnissen entlang des Pfades und ..." beginnen. Ohne diese x-Rechte kann derPfadname von Linux nicht aufgelöst werden und was immer sich an seinem Ende befindet, ist nicht zugreifbar. Weilwirklich jede Antwort mit diesem Halbsatz beginnen muß, wird er oft weggelassen - vergessen darf man ihn trotzdemnicht! Die einzelne Operation (open(2), unlink(2), rename(2) und so weiter) kann dann noch zusätzlich weitereZugriffsrechte verlangen.

Lese- und Schreibrechte an DateienWelche Zugriffsrechte werden nun wirklich benötigt, um eine Datei zu öffnen? Auch dies können wir im Linux-Kernel nachlesen: In /usr/src/linux/fs/open.c befindet sich der Code des Systemaufrufes open(2). Die Funktionsys_open(), die diese Kernelfunktion realisiert, ist nur sehr kurz. Sie tut kaum mehr, als do_open() in derselbenDatei mit den richtigen Parametern aufzurufen. Der Code von do_open() beschäftigt sich hauptsächlich mit derVerwaltung der Dateitabelle des Kernels und ist für die Klärung unserer Frage nicht interessant. Die eigentlicheArbeit des Öffnens der Datei überläßt er der Funktion open_namei(). die wie bereits erklärt, den filename in eine I-Node umwandelt. Der Aufruf findet sich in Zeile 442 in open.c:

442 error = open_namei(filename,flag,mode,&inode,NULL);

Dabei ist filename der Name der zu öffnenden Datei, flag der Modus, in dem die Datei geöffnet werden soll(O_RDONLY, O_CREAT und so weiter) und mode ist nur interessant, wenn die Datei angelegt werden soll: Dannsoll sie mit den in mode angegebenen Wunschrechten angelegt werden. In inode liefert die Funktion die I-Node dergeöffneten Datei zurück.

Der Code von open_namei() steht dann in /usr/src/linux/fs/namei.c, von dem wir uns ja weiter oben schonTeile angesehen haben. open_namei() ist eine lange Funktion, sie erstreckt sich von Zeile 333 bis Zeile 439. DieUmwandlung des filename-Parameters in die inode erfolgt in zwei Schritten. In einem ersten Schritt wird mit Hilfevon dir_namei() erst einmal die I-Node des Verzeichnisses lokalisiert, in dem sich die zu öffnende Datei befindet.Der zweite Schritt ist dann leicht unterschiedlich, je nachdem, ob die die öffnende Datei bereits existiert oder ob siewegen eines gesetzten O_CREAT erst noch erzeugt werden muß.

In Zeile 342 von namei.c wird zunächst die I-Node des Verzeichnisses lokalisiert, in dem eine Datei geöffnet werdensoll. Das ist der erste Schritt zum Öffnen der Datei.

342 error = dir_namei(pathname,&namelen,&basename,base,&dir);

Dabei wird von dir_namei() die Variable basename mit dem Namen der Datei im Verzeichnis belegt, namelen mitder Länge dieses Namens. dir ist ein Zeiger auf die I-Node des Verzeichnisses, indem die Datei geöffnet werden soll,das eigentliche Ergebnis dieses Aufrufes (und wird in Zeile 361 weiterverwendet). Wenige Zeilen später wirdfallweise unterschieden, ob eine Datei angelegt werden soll oder nicht (Zeile 359). Soll eine Datei angelegt werden,muß überprüft werden, ob eine Datei dieses Namens schon existiert (Zeile 361). Wenn dies der Fall ist und außerdemO_EXCL als Parameter beim open() angegeben war, muß das open() fehlschlagen (Zeile 362-366). Existiert dieDatei im Verzeichnis noch nicht, kann versucht werden, sie zu erzeugen. Dazu müssen w- und x-Recht amVerzeichnis vorhanden sein, daß die Datei einmal enthalten soll (Zeile 367). Außerdem muß eine create-Funktion inden I-Node-Operations für das Verzeichnis definiert sein (Zeile 369-370). Ist dies nicht der Fall, wird ebenfallspermission denied gemeldet. Auf Dateisystemen, die read-only angemeldet sind, dürfen selbstverständlichebenfalls keine Dateien angelegt werden (Zeile 371-372). Erst wenn alle diese Vorbedingungen erfüllt sind, darf dieDatei wirklich angelegt werden und ist damit geöffnet.

Wir halten fest: Um eine Datei in einem Verzeichnis neu anlegen zu können, wird also das x-Recht an allenVerzeichnissen entlang des Pfades dieser Datei benötigt und das w- und das x-Recht an dem Verzeichnis, in dem dieDatei neu anzulegen ist.

359 if (flag & O_CREAT) {

361 error = lookup(dir,basename,namelen,&inode);

362 if (!error) {

363 if (flag & O_EXCL) {

364 iput(inode);

365 error = -EEXIST;

Page 6: rwx - sonst nix, Unix Rechte und wie sie funktionieren

366 }

367 } else if ((error = permission(dir,

MAY_WRITE | MAY_EXEC)

368 ; /* error is already set! */

369 else if (!dir->i_op || !dir->i_op->create)

370 error = -EACCES;

371 else if (IS_RDONLY(dir))

372 error = -EROFS;

373 else {

375 error =

dir->i_op->create(dir,basename,namelen,mode,res_inode);

379 }

381 } else

Soll dagegen eine bereits existierende Datei geöffnet werden, muß zunächst ihr Name im Verzeichnis gesucht

werden, um eine I-Node für diese Datei zu bekommen (Zeile 382). Es kann sein, daß die so gefundene I-Node nicht

die I-Node der wirklich zu öffnenden Datei ist, sondern daß es sich um ein Symlink handelt. Ein Aufruf von

follow_link() regelt dies (Zeile 387). Der folgende Code stellt dann sicher, daß ein Verzeichnis niemals zum

Schreiben geöffnet werden darf (Zeile 390-393). Wäre dies nicht so, könnte man in Verzeichnissen beliebige I-Node-

Nummern eintragen und so die Zugriffsrechte entlang des Pfades unterlaufen. Der eigentliche Rechtetest erfolgt dann

in den Zeilen 394-397, in denen die gewünschten Zugriffsrechte in flag mit den vorhandenen Rechten in der inode

verglichen werden. Nach diesen Zeilen folgt weiterer Code, der Gerätedateien in mit nodev gemounteten

Dateisystemen, read-only Dateisysteme, append-only Files und die Option O_TRUNC beim Öffnen behandelt, uns

hier aber bei unserer Frage nichts neues bringt.

382 error = lookup(dir,basename,namelen,&inode);

387 error = follow_link(dir,inode,flag,mode,&inode);

390 if (S_ISDIR(inode->i_mode) && (flag & 2)) {

391 iput(inode);

392 return -EISDIR;

393 }

394 if ((error = permission(inode,ACC_MODE(flag))) != 0) {

395 iput(inode);

396 return error;

397 }

Wir halten fest: Beim Öffnen einer vorhandenen Datei wird das x-Recht an allen Verzeichnisses entlang des Pfade

dieser Datei benötigt und das passende r- und/oder w-Recht an der Datei selbst.

Offenbar ist das Öffnen einer Datei und die Kontrolle der Zugriffsrechte für ein Betriebssystem eine recht

anstrengende und langsame Angelegenheit. Neben der zeitraubenden Umwandlung von Pfadnamen in I-Nodes

müssen auch noch zahlreiche Rechteflags an allen Verzeichnissen und an der Datei selbst kontrolliert werden und

zahlreiche Sonderfälle berücksichtigt werden. Zum Glück ist dieser ganze Aufwand auf das Öffnen einer Datei

beschränkt. Ist die Datei erst einmal geöffnet, findet keine weitere Rechtekontrolle mehr statt. read() und write()

können sich also auf ihre eigentliche Aufgabe, das Datenschaufeln, konzentrieren und müssen sich nicht mit der

Kontrolle von Zugriffsrechten herumschlagen.

Ausführungsrechte an DateienUm das x-Recht an Dateien zu verstehen, müssen wir uns den Systemaufruf execve(2) ansehen. Dieser Systemaufruf

ist die einzige Methode, um in UNIX ein neues Programm zu laden. In Linux, das auf mehr als einer

Prozessorplattform ablaufen kann, ist ein Teil des Aufrufes systemspezifisch. Für Intel-Prozessoren befindet sich in

/usr/src/linux/arch/i386/kernel/process.c der Code von sys_execve(), der die Parameter des

Systemaufrufes beschafft und dann den plattformunabhängigen Code in /usr/src/linux/fs/exec.c aufruft. Der

Funktion do_execve() werden außer dem Namen der zu startenden Binärdatei auch noch ein Feld von

Parameterstrings und ein Feld von Environmentvariablen sowie ein Satz initiale Prozessorregister mitgegeben.

do_execve() beschafft sich durch einen Aufruf von open_namei() zunächst einmal die I-Node der auszuführenden

Datei. Weiter oben ist schon gezeigt worden, daß dies nur gelingen kann, wenn auf allen Verzeichnissen entlang des

Pfades zu dieser Datei x-Rechte vorhanden sind. Der folgende Code stellt sicher, daß die ausführbare Datei ein

regular file ist. Wenn das Dateisystem mit der Option noexec gemountet ist, schlägt der Aufruf von execve() in

jedem Fall fehl, ebenso wenn der Superblock zugehörigen Dateisystems nicht erreichbar ist. Nach diesen elementaren

Page 7: rwx - sonst nix, Unix Rechte und wie sie funktionieren

Überprüfungen folgt der Code, der set user id/set group id handhabt und das x-Recht testet.

Ausführungsrecht und SUID/SGID beim execve() Systemaufruf

/usr/src/linux/fs/exec.c:

586 i = bprm.inode->i_mode;

587 if (IS_NOSUID(bprm.inode) && (((i & S_ISUID)

&& bprm.inode->i_uid != current->

588 euid) || ((i & S_ISGID)

&& !in_group_p(bprm.inode->i_gid))) && !suser()) {

589 retval = -EPERM;

590 goto exec_error2;

591 }

592 /* make sure we don't let suid, sgid files be ptraced. */

593 if (current->flags & PF_PTRACED) {

594 bprm.e_uid = current->euid;

595 bprm.e_gid = current->egid;

596 } else {

597 bprm.e_uid =

(i & S_ISUID) ? bprm.inode->i_uid : current->euid;

598 bprm.e_gid =

(i & S_ISGID) ? bprm.inode->i_gid : current->egid;

599 }

600 if ((retval = permission(bprm.inode, MAY_EXEC)) != 0)

601 goto exec_error2;

602 if (!(bprm.inode->i_mode & 0111) && fsuser()) {

603 retval = -EACCES;

604 goto exec_error2;

605 }

Im Rahmen der Funktion do_execve() werden das SUID- und SGID-Bit der auszuführenden Datei gebprüft undLinux bestimmt, unter welcher euid und egid das Programm zur Ausführung kommt. Danach wird getestet, obausreichende Rechte vorhanden sind, um das Programm starten zu dürfen.

Zunächst bestimmt der Kernel die Rechtebits der auszuführenden Datei (Zeile 586). Falls das Dateisystem, auf demsich diese Datei befindet, mit der Option nosuid angemeldet ist und die Ausführung der Datei einen Wechsel dereffektiven User-ID oder Group-ID bewirken würde, darf nur der Superuser die Datei ausführen (Zeilen 587-591).Danach wird bestimmt, unter welcher UID und GID das neue Programm zur Ausführung kommen wird. Falls dasProgramm zur Zeit mittels ptrace() in einem Debugger bearbeitet wird, werden sich seine Rechte nicht ändern(Zeilen 592-595). Auf diese Weise wird verhindert, daß man durch tracen eines SUID-Programmes undnachträgliches Verändern seines Codes zusätzliche Rechte gewinnen könnte. Bei normalen Programmen (Zeilen 596-599) wird die effektive UID aus der I-Node der auszuführenden Datei übernommen, wenn das SUID-Bit an der Dateigesetzt ist, andernfalls wird die UID des Vorläufers ererbt. Analog wird mit der GID verfahren. Der Kernel wird dasProgramm jedoch nur ausführen, wenn die permission()-Funktion feststellt, das MAY_EXEC-Recht vorhanden ist(Zeilen 600-601). Auch ein Superuser kann eine Datei nicht als Programm starten, wenn nicht wenigstens irgendeinx-Recht an der Datei vorhanden ist (Zeilen 602-603).

Der folgende, sehr umfangreiche Code beschäftigt sich dann mit dem Handling von Shellscripten, die durch einen #!-Kommentar eingeleitet werden. Erst ab Zeile 701 kann der Kernel versuchen, tatsächlich ein Programm mit dengegebenen Eigenschaften auszuführen.

Wir halten fest: Um ein Programm starten zu dürfen, muß außer den x-Rechten an den Verzeichnissen entlang desPfades auch das x-Recht an der Datei vorhanden sein. Ein Superuser kann eine Datei immerhin dann als Programmstarten, wenn wenigsten irgendein x-Recht an der Datei vorhanden ist. Wenn das SUID-Bit an der zu startenden Dateigesetzt ist, wird die Datei unter der euid des Dateieigentümers ausgeführt, sonst unter der euid des aufrufendenProzesses - Prozesse mit einer bestehenden Verbindung zu einem Debugger werden jedoch aus Sicherheitsgründenimmer nur unter der euid des aufrufenden Prozesses ausgeführt. Analog wird auch mit dem SGID-Bit verfahren.

Experiment 4

root@white:/tmp# cat >> x

#! /bin/sh --

id

root@white:/tmp# chmod 6555 x

Page 8: rwx - sonst nix, Unix Rechte und wie sie funktionieren

root@white:/tmp# ls -l x -r-sr-sr-x 1 root root 18 Dec 10 21:14 x root@white:/tmp# exit kris@white:~$ /tmp/x uid=100(kris) gid=20(users) groups=20(users),11(floppy)

Shellscripte mit gesetztem SUID- und SGID-Bit werden trotzdem mit normalen euid- und egid-Werten aufgerufen.

Experiment 4 zeigt, wie der Kernel mit Shellscripten umgeht, die mit SUID- oder SGID-Rechten versehen sind. Dasliegt daran, daß bei der Interpretation von #!-Zeichen der Name des auszuführenden Programmes verändert wird unddanach der execve()-Aufruf komplett neu gestartet wird. Aus dem Aufruf von /tmp/x wird so durch dieInterpretation der #!-Zeile der Aufruf /bin/sh -- /tmp/x, der vom Kernel komplett neu bewertet wird. An /bin/shist aber weder das SUID- noch das SGID-Bit gesetzt, also wird die Shell mit normalen Rechten gestartet. Daß es sichbei einem Parameter des Aufrufes um den Namen einer Datei handelt, an der SUID und SGID gesetzt sind, darf denKernel nicht interessieren und die Shell wertet es nicht aus - sie könnte es auch nicht, wenn sie wollte. Dieser Effektist durchaus beabsichtigt: Ein nichttriviales Shellscript zu schreiben, daß sicher und ohne Lücken für eventuelleHacker unter einer fremden User-ID laufen kann, ist so gut wie unmöglich.

RückblickWie wir gesehen haben, findet die Kontrolle der Zugriffsrechte an Dateien in Linux so gut es geht zentral statt.Wenige Funkionen wie permission() und die Funktionen der namei()-Familie spielen dabei eine sehr wichtigeRolle. Trotzdem muß jeder Systemaufruf noch für sich selbst die Rechte des aufrufenden Prozesses an der Dateitesten, die durch den Systemaufruf manipuliert werden soll. Dabei müssen viele Sonderfälle wie immutable files,append only directories und andere exotische Dinge beachtet werden. Wollte man eine vollständige und genaueAufstellung von Zugriffsrechten in Linux machen, müßte man eine Liste von Systemaufrufen machen und für jedenSystemaufruf eine vollständige Liste von Vorbedingungen aufschreiben. Ein Eintrag würde sich dann in etwa solesen:

unlinkunlink() löscht eine Datei mit einem gegebenen Pfadnamen, wenn alle Verzeichnisse entlang des Pfades x-Recht haben, der Name der Datei im letzten Verzeichnis nicht leer ("") ist, das Dateisystem nicht read only ist,am enthaltenden Verzeichnis w- und x-Recht bestehen, das enthaltende Verzeichnis nicht append only ist undfür die I-Node der Datei eine unlink()-Operation definiert ist. Diese unlink()-Operation kann jedoch nochweitere Bedingungen an die Entfernung einer Datei stellen (das ext2-Dateisystem macht dies zum Beispiel,wenn das t-Bit am enthaltenden Verzeichnis gesetzt ist).

Letztendlich wird eine solche Tabelle jedoch sehr unübersichtlich und lang. Man sieht, daß Zugriffsrechte in UNIXim allgemeinen und Linux im besonderen nur auf den ersten Blick einfach und harmlos aussehen. Wenn man über daseinfache rwx hinausgeht, wird die ganze Angelegenheit ziemlich kompliziert und unübersichtlich, da vieleSonderfälle betrachtet werden müssen. Zum Glück kommen solche Sonderfälle in einer normalen Installation nur sehrselten vor, denn sonst würde das eingangs erwähnte Kapitel 2 eines UNIX-Handbuches sehr lang werden ...