Superkleber (Linux-Magazin, Juni 2015)

Sommerzeit, Jobwechselzeit! Wer den Sprung ins kalte Wasser wagen und einen neuen lukrativen Job an Land ziehen möchte, muss im Einstellungsgespräch wissen, was es mit dem Sticky-Bit unter Unix auf sich hat.

Eine beliebte Frage bei Einstellungsgesprächen ist die nach dem sogenannten Sticky-Bit. Ich persönlich stelle diese Frage nie, wenn ich einen Kandidaten durchleuchte, denn ich denke, dass wer die Antwort weiß, sich höchstens dadurch auszeichnet, dass er sich auf das Gespräch vorbereitet hat. Ein Beweis für Fachkompetenz lässt sich daraus nicht ableiten, aber viele große Firmen im Silicon-Valley haben die Frage im Katalog, und als Spieler in der A-Klasse der Softwareindustrie muss man selbstverständlich auch auf unnütze Fragen eine Antwort wissen.

Deshalb nehmen wir uns diesmal im Perl-Snapshot dem Thema an: Wozu ist das Sticky-Bit da? Wie immer wenn es um knifflige Unix-Fragen geht, hilft das Buch von Michael Kerrisk ([2]) weiter. Dieser Wälzer der Superlative mit mehr als 1500 Seiten ist als Papierbuch nur von durchtrainierten Bodybuildern lesbar, aber im elektronischen Kindle-Format leicht überall mitzuführen. Und wer denkt, er wüsste schon alles über die Feinheiten der Programmierung unter Unix, dem haut es bei der Lektüre dieses Jahrhundertwerks regelmäßig den Schalter raus: Selbst graubärtige Unix-Gurus fördern aus den erstaunlichen Tiefen des Schinkens immer wieder verblüffende Tatsachen hervor.

Löschen ohne Rechte

Wer hätte zum Beispiel gewusst, dass ein User auf einem Unix-System eine Datei aus einem Verzeichnis löschen kann, obwohl er keine Schreibrechte an der Datei hat? Richtig, erforderlich sind nur Schreib- und Ausführungsrechte am Verzeichnis, in dem die Datei liegt. Listing 1 legt zu Demonstrationszwecken ein neues Verzeichnis test im aktuellen Verzeichnis an und gibt ihm mit dem oktalen Wert 0333 die Rechte -wx-wx-wx.

Der anschließend mit blurt() aus dem CPAN-Modul Sysadm::Install erzeugten Datei abc weist die chmod-Anweisung in Zeile 9 mit 0444 die Rechte -r--r--r-- zu. Alle User dürfen sie also lesen, aber nicht beschreiben. Dennoch läuft die unlink-Anweisung in Zeile 14 ohne Meckern durch, und auch ein rm -f test/abc auf der Kommandozeile löscht die Datei ohne Rückfrage oder Fehler. Des Rätsels Lösung: Schreibrechte an einer Datei unter Unix erlauben das Modifizieren des Inhalts einer Datei, Löschrechte hingegen regelt das Verzeichnis, in dem die Datei liegt, nicht die Datei selber.

Listing 1: file-rm

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use Sysadm::Install qw( blurt );
    04 use File::Path qw( rmtree );
    05 
    06 mkdir "test";
    07 blurt "data", "test/abc";
    08 chmod 0333, "test";
    09 chmod 0444, "test/abc";
    10 
    11 # d-wx-wx-wx test
    12 # -r--r--r-- test/abc
    13 
    14 unlink "test/abc" 
    15     or die "unlink failed: $!";
    16 
    17 rmtree "test";

Als weitere interessante Feinheit der Unix-Programmierung sticht bei Listing 1 ins Auge, dass das Verzeichnis test allen Usern mit -wx-wx-wx zwar Schreib- und Ausführungsrechte gewährt, aber keine Leserechte. Gemäß diesen Vorschriften kann das Skript das Verzeichnis also durchqueren und schreibend darauf zugreifen, die in ihm enthaltenen Dateien aber nicht auflisten. Nachdem das Skript aber weiß, dass die Datei test/abc darin liegt, darf es diese auch löschen, denn Schreibrechte liegen vor. Gehört die Datei einem anderen User, ändert sich nichts daran, dass Unix sie rücksichtslos löscht, wenn das Verzeichnis, in dem sie liegt, seine Zustimmung gegeben hat. Das führt naturgemäß zu Problemen, für die die Unix-Väter in grauer Vorzeit eine recht zusammengeschusterte Lösung fanden, aber davon später.

Wegerecht

Was bedeutet das x, wenn ein Verzeichnis die Rechte -wx-wx-wx anzeigt? Es regelt das Wegerecht zum Durchsteigen des Verzeichnisses. Egal ob ein Kommando auf den Inhalt einer Datei sub/file schreiben oder lesend zugreift oder mit sub/sub1/... in ein im Verzeichnis sub liegendes Unterverzeichnis sub1 hinabsteigt, benötigt es Ausführungsrechte am Verzeichnis sub.

Wie Listing 2 zeigt, heißt das aber keineswegs, dass ein Kommando, das auf das Verzeichnis top/sub/sub1 zugreift, Ausführungsrechte an allen Pfadkomponenten top, sub und sub1 benötigt. Hat ein Skript zum Beispiel sein aktuelles Verzeichnis vor dem Einrichten der Zugangssperre mit chdir() auf top/sub/sub2 gesetzt, kann es mit der relativen Pfadangabe ../sub1 sehr wohl auch dann auf sub1 zugreifen, auch wenn dann später top die x-Rechte wie in Zeile 21 in Listing 2 entzogen werden (Abbildung 1).

Abbildung 1: Gewährt das Verzeichnis top kein Wegerecht, schlägt der Zugriff auf sub1 über den absoluten Pfad zwar fehl, doch der relative Pfad von sub2 nach sub1 ist passierbar.

Listing 2 zeigt eine mit Test::More implementierte Testsuite zum Verifizieren dieser Tatsache. Die Funktion throws_ok() in Zeile 23 stammt aus dem CPAN-Modul Test::Exception, fängt auftretende Fehler ab und erlaubt es, diese mittels regulären Ausdrücken mit in einer Testsuite erwarteten zu vergleichen. Erwartungsgemäß tritt beim Zugriff über den absoluten Pfad mit "Permission denied" ein Fehler auf, während eine relative Pfadangabe wie in Zeile 27 zum Erfolg führt:

    $ ./dir-traverse 
    ok 1 - abs path fails
    ok 2 - rel path ok
    1..2

Wer übrigens denkt, dass der Trick mit dem relativen Pfad auch dann funktioniert, falls nicht top sondern top/sub das Wegerecht storniert, irrt sich: Wenn ein Skript sich in top/sub/sub2 befindet und mittels ../sub1 auf top/sub/sub1 zugreift, durchschreitet es top/sub und benötigt deshalb dort das Wegerecht.

Listing 2: dir-traverse

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use Test::More;
    04 use Test::Exception;
    05 use File::Temp qw( tempdir );
    06 use Sysadm::Install qw( blurt slurp 
    07   cd cdback );
    08 
    09 my $ROOT_DIR = tempdir( CLEANUP => 1 );
    10 
    11 # Read via a relative path while the abso-
    12 # lute path doesn't have traversal rights.
    13 dirmk( "top", 0700 );
    14 dirmk( "top/sub", 0700 );
    15 dirmk( "top/sub/sub1", 0700 );
    16 dirmk( "top/sub/sub2", 0700 );
    17 
    18 blurt "content", 
    19   "$ROOT_DIR/top/sub/sub1/file";
    20 cd "$ROOT_DIR/top/sub/sub2";
    21 chmod 0600, "$ROOT_DIR/top";
    22 
    23 throws_ok 
    24  { slurp( "$ROOT_DIR/top/sub/sub1/file" ) } 
    25  qr/Permission denied/, "abs path fails";
    26 
    27 is slurp( "../sub1/file" ), 
    28   "content", "rel path ok";
    29 
    30 cdback;
    31 done_testing();
    32 
    33 ###########################################
    34 sub dirmk {
    35 ###########################################
    36   my( $dir, $perm ) = @_;
    37 
    38   my $path = "$ROOT_DIR/$dir";
    39   mkdir $path or die "$!";
    40   chmod $perm, $path or die "$!";
    41 }

Klebriges Zeugs

Das eingangs erwähnte Kuriosum, dass ein User eine Datei löschen darf, auch wenn die Datei selbst ihm keine Schreibrechte gewährt, führt zu Problemen, falls sich mehrere User ein Verzeichnis teilen. Legen zum Beispiel die User A und B die Dateien file-a und file-b in /tmp ab, sollte es A verwehrt bleiben, file-b zu löschen. Doch wie soll das gehen, falls nicht die Datei selbst, sondern das enthaltende Verzeichnis den Löschzugriff regelt? Damit jeder User auf /tmp zugreifen darf, sind dessen Rechte auf rwxrwxrwx eingestellt. Dies eröffnet allerdings auch beliebigen Usern Löschrechte an allen dort eingestellten Dateien, und deshalb behalfen sich die Unix-Väter nach der Geburt des Dinosauriers mit einem Trick: Ein spezielles 'sticky' Bit, das mit

    chmod +t /tmp

gesetzt wird und daran zu erkennen ist, dass das ls-Kommando die Verzeichnisrechte folgendermaßen anzeigt:

    $ ls -ld /tmp
    drwxrwxrwt 7 root root 4096 Apr 13 00:17 /tmp

Statt einem abschließenden "rwx" steht nun ein "rwt" in der Zugriffslatte, und Unix stellt deswegen die Zugriffsregeln in diesem Verzeichnis auf den Kopf. Auf einmal darf nun nicht mehr jeder x-beliebige User Einträge löschen, sondern nur noch der Eigentümer des jeweiligen Eintrags. Und genau deswegen ist auf jedem Unix-System im Verzeichnis /tmp das Sticky-Bit gesetzt. Es verhindert, dass User sich gegenseitig ihre temporären Datein weglöschen. Schreib- und Leserechte am Verzeichnis sind nach wie vor für Alle vorhanden, also sollten temporäre Dateien ihre Zugriffsrechte entsprechend beschränken.

Bonuspunkte

Bonuspunkte erhält der Kandidat beim Einstellungsgespräch übrigens noch, falls er erklären kann, wann Unix das Sticky-Bit eines Verzeichnisses mit t und wann mit T anzeigt. Mit dem abschließenden t in rwt verdeckt der Hack nämlich mit dem Sticky-Bit die Ausführungsrechte der Allgemeinheit am Verzeichnis. Es lässt sich nicht mehr sagen, ob vor dem Setzen des Sticky-Bits rw- oder rwx vorlag. Deshalb zeigt Unix rwt an, falls rwx vorliegt und rwT, falls die Ausführungsrechte fehlen, also vorher rw- dort stand.

Und wer auch noch weiß, dass nicht nur Verzeichnisse, sondern auch Dateien ein Sticky-Bit führen können, dieses aber etwas völlig anderes regelt, beweist, dass er die Vorbereitung ernst genommen und [2] studiert hat. Der Kandidat hat 100 Punkte und kommt in die nächste Runde!

Infos

[1]

Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2015/06/Perl

[2]

Michael Kerrisk, "The Linux Programming Interface: A Linux and UNIX System Programming Handbook", No Starch Press, 2010

Michael Schilli

arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen der Skriptsprache Perl. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.