Sicherheitsfragen

Heutzutage ist Sicherheit ein zunehmend wichtiger Aspekt. Wir besprechen Sicherheitsfragen in diesem Buch, wenn sie auftreten. Es gibt aber auch einige allgemeine Konzepte, auf die wir bereits hier eingehen wollen.

Es gibt grundsätzlich zwei Arten möglicher Gefährdungen, die man absichtlich und unbeabsichtigt nennen kann. Ein Sicherheitsproblem ist der Schaden, den ein Benutzer durch den Mißbrauch existierender Programme oder die zufällige Ausnutzung von Fehlern erreichen kann. Der andere Aspekt beschäftigt sich damit, was für eine (Fehl-)Funktionalität ein Programmierer implementieren kann. Offensichtlich hat der Programmierer weitaus mehr Möglichkeiten als der reine Benutzer. Mit anderen Worten: Es ist ebenso gefährlich, als Superuser ein Programm zu starten, das Sie von einem Freund bekommen haben, wie ihm oder ihr zu diesem Zeitpunkt direkt eine Superuser-Shell zur Verfügung zu stellen. Obwohl der Zugang zu einem Compiler per se noch keine Sicherheitslücke ist, kann ein Loch auftreten, wenn der kompilierte Code tatsächlich ausgeführt wird. Seien Sie besonders vorsichtig mit Modulen, weil ein Kernel-Modul alles machen kann. Ein Modul ist genauso mächtig wie eine Superuser-Shell.

Jede Sicherheitskontrolle im System geht vom Kernel aus. Wenn der Kernel Sicherheitslücken hat, dann hat das ganze System Sicherheitslücken. In der offiziellen Kernel-Distribution kann nur ein dazu autorisierter Benutzer Module laden; der Systemaufruf create_module überprüft, ob der aufrufende Prozeß das Recht hat, ein Modul in den Kernel zu laden. Bei einem offiziellen Kernel kann also nur der Superuser[1] oder ein Eindringling, dem es gelungen ist, Superuser-Rechte zu erhalten, die Machtfülle des privilegierten Codes ausnutzen.

Wo immer möglich sollten Treiber-Programmierer es vermeiden, eine Sicherheits-Policy in ihren Code einzubinden. Sicherheit ist eine strategische Frage, die am besten auf höheren Ebenen im Kernel unter der Kontrolle des Systemverwalters abgehandelt wird. Es gibt aber natürlich immer Ausnahmen. Als Programmierer von Gerätetreibern sollten Sie sich über solche Situationen im klaren sein, in denen manche Gerätezugriffe das System als Ganzes negativ beeinflussen könnten, und entsprechende Kontrollen vorsehen. Beispielsweise sollten Geräteoperationen, die globale Ressourcen betreffen (wie das Einstellen einer Interrupt-Leitung) oder die Auswirkungen auf andere Benutzer haben (wie das Einstellen der Default-Blockgröße eines Bandlaufwerks), normalerweise nur hinreichend privilegierten Benutzern zur Verfügung stehen. Eine solche Überprüfung muß vom Treiber selbst vorgenommen werden.

Treiber-Autoren müssen natürlich auch darauf achten, keine Sicherheitsfehler einzubauen. Die Programmiersprache C macht es recht einfach, bestimmte Typen von Fehlern zu machen. Viele derzeitige Sicherheitsprobleme entstehen beispielsweise durch sogenannte buffer overrun-Fehler, bei denen der Programmierer vergessen hat, zu überprüfen, wie viele Daten in einen Puffer geschrieben werden, wodurch die Daten über das Puffer-Ende hinaus geschrieben werden und damit andere Daten überschreiben können. Solche Fehler können das gesamte System beeinträchtigen und müssen auf jeden Fall vermieden werden. Glücklicherweise ist es im Zusammenhang mit Gerätetreibern relativ einfach, diese Fehler zu vermeiden, da die Benutzerschnittstelle sehr genau definiert ist und unter ständiger Beobachtung steht.

Auch einige andere generelle Ideen aus dem Bereich der Sicherheit sollten Sie im Kopf behalten. Alle Eingaben, die aus Benutzerprozessen stammen, sollten mit großem Mißtrauen betrachtet werden; trauen Sie den Daten nicht, sofern Sie sie nicht überprüfen können. Achten Sie auch auf uninitialisierten Speicher; jeder Speicher, den Sie vom Kernel bekommen, sollte mit Nullen gefüllt oder anderweitig initialisiert werden, bevor er einem Benutzer-Prozeß oder einem Gerät zur Verfügung gestellt wird, ansonsten können vertrauliche Informationen nach außen gelangen. Wenn Ihr Gerät gesendete Daten interpretiert, sollten Sie sicherstellen, daß der Benutzer nichts schicken kann, was das System beschädigen könnte. Denken Sie schließlich auch über die möglichen Auswirkungen von Geräteoperationen nach; wenn bestimmte Operationen (etwa das Neuladen der Firmware auf einer Adapter-Karte oder das Formatieren einer Festplatte) das ganze System beeinträchtigen könnten, sollten diese Operationen wahrscheinlich nur privilegierten Benutzern zur Verfügung stehen.

Seien Sie auch vorsichtig, wenn Sie Software von Dritten bekommen, insbesondere wenn es dabei um den Kernel geht: Weil jeder Zugriff auf den Quellcode hat, kann auch jeder Sachen kaputtmachen und neu kompilieren. Obwohl Sie den vorkompilierten Kerneln aus Ihrer Distribution vertrauen können, sollten Sie keine Kernel verwenden, die von einem Bekannten kompiliert worden sind, dem Sie nicht voll vertrauen — wenn Sie kein vorkompiliertes Binärprogramm als Superuser starten wollen, dann sollten Sie genausowenig einen vorkompilierten Kernel benutzen. Beispielsweise könnte ein böswillig modifizierter Kernel es jedem erlauben, ein Modul zu laden, und damit eine unerwartete Hintertür via create_module schaffen.

Beachten Sie auch, daß man einen Linux-Kernel ohne jede Modulunterstützung kompilieren und so alle diesbezüglichen Sicherheitslöcher schließen kann. Natürlich müssen in diesem Fall alle benötigten Treiber direkt in den Kernel eingebaut werden. Ab der Version 2.2 ist es auch möglich, das Laden von Kernel-Modulen über den Capability-Mechanismus nach dem Booten abzuschalten.

Fußnoten

[1]

In der Version 2.0 des Kernels darf nur der Superuser privilegierten Code ausführen, während in der Version 2.2 anspruchsvollere Überprüfungen möglich sind. Wir besprechen das in Kapitel 5.