Aus Linux-Magazin 03/2005

Zusätzliche Sicherheitskonzepte im Linux-Kernel

Projekte wie Pax oder Exec-Shield verankern tief im Kern eines Linux-Systems zusätzliche Sicherheitsmechanismen, die deutlich über die herkömmliche Zugriffskontrolle hinausgehen. Sie vermindern die Gefahr durch Exploits, Race Conditions und Ressourcen-Verschwendung.

Trotz aller Vorsicht beim Programmieren ist auch Linux-Software immer wieder durch Buffer Overflows, Race Conditions und andere Sicherheitslücken bedroht. Sobald ein Fehler bekannt ist, können ihn die Entwickler korrigieren und Anwender können ihre installierte Software aktualisieren. Die große Gefahr sind unbekannte Fehler. Oft gibt es schwarze Schafe, die Sicherheitslücken zwar finden, aber nicht melden, sondern lieber selbst ausnutzen.

Die Gefährdung durch bislang unbekannte Bugs verringern – dieser Aufgabe haben sich mehrere Projekte verschrieben. Die gängigen, im Kernel ansetzenden Schutzmethoden implementieren zusätzliche Access-Control-Funktionen. Beispiele dafür sind LIDS[4], SE Linux[5], GR Security[6] oder RSBAC[7]. Sie begrenzen vor allem die Rechte von Benutzern und Prozessen auf das nötige Minimum, um die Folgen eines erfolgreichen Angriffs zu begrenzen.

Tiefer ansetzende Schutzkonzepte betreffen den Speicher der Prozesse. Zu finden sind diese Mechanismen in bekannten Patches wie Pax[1], Exec-Shield[2] und Openwall[3]. Diese Projekte haben den Exploits den Kampf angesagt. Ihr Ziel: Auch wenn eine Lücke im System steckt, soll kein Angreifer durch sie hindurchschlüpfen und das System knacken. Die Abwehrmaßnahmen dieser Projekte funktionieren zwar ähnlich, sind jedoch teilweise sehr unterschiedlich implementiert. Sie nutzen verschiedene (oft Hardware-spezifische) Merkmale, um zusätzliche und striktere Kontrollen zu ermöglichen.

Ausfuhrverbot

Viele Exploit versuchen eine auf dem Stack liegende Rücksprungadresse zu überschreiben. Nach einem Return-Aufruf soll das attackierte Programm den Code des Angreifers ausführen. Dieser Code liegt ebenfalls auf dem Stack – dort hat ihn der Saboteur per Buffer Overflow eingeschleust[8]. Als Gegenwehr verbieten es einige Patches, Code innerhalb des Stack auszuführen. Konsequent weitergedacht bedeutet dies, einzelne Speicherbereiche explizit als entweder beschreibbar oder ausführbar zu markieren. Dann ist es weder möglich, ausführbaren Code zu verändern noch Code in Datenbereichen auszuführen.

Bit-Schererei

Diese gute Idee ist in der Praxis nicht so leicht umzusetzen. Eines der Probleme ist das Flag »PROT_READ_OR_EXEC« der x86-Hardware-Architektur. Es verwendet für die Einträge in der Page Table leider nur ein gemeinsames Bit, um zu markieren, ob ein Bereich lesbar (»PROT_READ«) oder ausführbar ist (»PROT_EXEC«). Dadurch sind alle Daten in einem lesbaren Bereich automatisch auch ausführbar, das ist aber oft nicht erwünscht. Ein Speicherbereich der beschreibbar ist, muss meist auch lesbar sein, was ihm automatisch das Ausführungsrecht verschafft.

Das unter Linux übliche Binärformat ELF[9] markiert aus Kompatibilitätsgründen den Stack eines Prozesses automatisch als ausführbar, und zwar selbst dann, wenn die Prozessorarchitektur über ein eigenes No-Execute-Bit verfügt. ELF verzichtet immer darauf, zwischen einem lesbaren und einem ausführbaren Stack zu unterscheiden.

Die Sicherheitspatches verwenden je nach Hardware unterschiedliche Ansätze, um die Noexec-Eigenschaft (NX) zu implementieren. Am einfachsten gelingt das, wenn die Hardware-Architektur bereits über passende Paging-Eigenschaften verfügt. Auf vielen Non-x86-Architekturen ist das der Fall. Deren MMU (Memory Management Unit) unterscheidet zwischen nur lesbaren und ausführbaren Speicherbereichen. Die Page Table kennzeichnet dann nicht ausführbare Bereiche mit einem eigenen Bit.

Emulation des
NX-Flag
Zurzeit sind unter Linux zwei Methoden im Einsatz, um ein dem Prozessor fehlendes No-Execution-Flag zu emulieren: die Pageexec- und die Segmexec-Methode.

Pageexec: Diese Methode nutzt die Aufteilung des TLB (Translation Lookaside Buffer) bei Prozessoren ab der Pentium-Serie (Intel) und dem K5 (AMD). Diese verwalten Code und Daten im TLB nicht mehr gemeinsam, sondern getrennt im Instruction-TLB für Code und im Data-TLB für Daten. Die TLBs wirken als sehr schnelle Caches für die Abbildung von virtuellen auf physikalische Adressen.

Will der Prozessor auf eine virtuelle Speicheradresse zugreifen, sucht er den Eintrag zuerst im TLB. Wird er dort nicht fündig, lädt er die Adresse aus der Page Table und speichert sie im TLB für spätere Zugriffe. Um das Cacheing der Einträge im TLB kümmern sich die x86-Prozessoren normalerweise automatisch. Sie erlauben es aber auch der Software, den kompletten TLB zu leeren oder einzelne Einträge für virtuelle Adressen zu löschen. Dies nutzt die Pageexec-Technik, um Non-Executable-Pages zu implementieren.

Pageexec entfernt Speicherseiten, die nicht ausführbar sein sollen, aus dem TLB oder gewährt den Zugriff nur im Supervisor-Level. Bei jedem TLB-Zugriff, der auf die geschützten Bereiche zielt, generiert der Prozessor einen Page Fault. Der Kernel erhält dann die Kontrolle und darf entscheiden, ob es sich um einen legalen Datenzugriff handelt oder um das verbotene Laden von Instruktionen. Allerdings senken die gezielt verursachten Page Faults die Performance und die Effektivität des TLB.

Segmexec: Die zweite No-Execute-Emulation basiert auf so genannten Segmentation based non-executable pages (Segmexec). Wie in Abbildung 1 zu sehen ist, unterteilt sie den Speicher in unterschiedliche Segmente. Auf x86-Prozessoren läuft Linux im Protected Mode und verwendet Paging. Das Paging übersetzt bei jedem Datenzugriff die aus Segment und Offset bestehende logische Adresse in eine virtuelle (lineare) Adresse. Diese Technik bildet den virtuellen auf den physikalischen Speicher ab.

Im Normalfall umfasst der virtuelle Userspace eines Prozesses 3 GByte Speicher. Segmexec unterteilt diesen Bereich in zwei Hälften. Die erste enthält alle Mappings für Lese- und Schreibzugriffe, die zweite Hälfte ist für ausführbaren Code zuständig. Unterhalb der 1,5-GByte-Grenze liegen also die Daten, darüber der Code. Die ausführbare Hälfte darf auch Mappings auf Daten nutzen, umgekehrt ist das nicht gestattet.

Dank des Tricks mit der Speicherhalbierung sind Code- und Datenbereich unterscheidbar. Das Laden von Instruktionen aus dem ausschließlich für nicht ausführbare Daten bestimmten Bereich führt zu einem Page Fault, der Kernel kann diesen Zugriff somit kontrolliert unterbinden.

Kombination: Da sie architekturspezifische Eigenschaft verwenden, sind beide Ansätze allerdings an die x86-Architektur gebunden. Openwall[3] und andere Projekte implementieren auf eine der beiden Arten einen nicht ausführbaren Stack.

Das Exec-Shield-Patch[2] (und somit Pax[1]) geht noch einen Schritt weiter und wendet das Konzept zusätzlich auf den Heap an. Es schützt damit auch jene Datenbereiche, die ein Prozess mittels »mmap()« in den Speicher abbildet, sowie den Heap-Speicher, den es per »malloc()« anfordert. Pax verwendet sogar – je nach eingesetzter Hardware – Pageexec oder Segmexec und bedient damit beinahe jede Hardware-Architektur.

Abbildung 1: Speicherverwaltung ohne und mit Segmexec. Durch die Teilung des virtuellen Speichers in Code- und Datenbereich ist auch auf der x86-Architektur ein NX-Flag möglich.

Abbildung 1: Speicherverwaltung ohne und mit Segmexec. Durch die Teilung des virtuellen Speichers in Code- und Datenbereich ist auch auf der x86-Architektur ein NX-Flag möglich.

Abbildung 2: Alle Speichermappings landen hier außerhalb des geschützten Ascii-Armor-Bereichs. Das Exec-Limit liegt sogar bei 0xbfffffff (3 GByte), es umfasst also den gesamten Userspace.

Abbildung 2: Alle Speichermappings landen hier außerhalb des geschützten Ascii-Armor-Bereichs. Das Exec-Limit liegt sogar bei 0xbfffffff (3 GByte), es umfasst also den gesamten Userspace.

Dank Hardware-Unterstützung arbeitet diese Lösung ohne Geschwindigkeitsverlust. Der Kernel muss nur beim Laden des Binary trotz ELF-Format die richtigen Flags setzen. Unter Pax setzt das Tool »chpax« diese Flags im ELF-Header einer Datei. Sie sind Teil des Dateiobjekts und damit unabhängig vom Dateisystem. Sie bleiben auch beim Komprimieren, Kopieren, Verschlüsseln oder Verschieben erhalten.

Auf Architekturen ohne NX-Unterstützung in der Hardware muss der Kernel das fehlende Flag emulieren. Dabei kommen zwei Techniken zum Einsatz, der Kasten “Emulation des NX-Flag” beschreibt beide. Die 64-Bit-Prozessoren von Intel und AMD besitzen ein No-Execution-Flag und auch VIA wird es in kommenden Prozessoren einsetzen. Die Prozessoren von Transmeta können dank des Software-Kerns per Update ein NX-Flag erhalten.

Strenge Funktionen

Um das NX-Bit richtig zu verwenden, sind Änderungen an den Funktionen nötig, die Rechtevergaben für Speicherbereiche regeln. Ausführbarer Code kann auf zwei Arten in den Adressbereich eines Prozesses gelangen: Der Prozess verändert einen vorhandenen Speicherbereich, der bereits beschreibbar und ausführbar ist, oder er erstellt einen neuen, ausführbaren Speicherbereich.

Die bisher betrachteten Techniken helfen gegen die erste Art des Einschleusens von schädlichem Code. Für die zweite sind herkömmliche Zugriffskontrollmechanismen (Access Control) sowie jene Schnittstelle zuständig, die Zugriffsrechte für Speicherbereiche regelt.

Abbildung 3: Mit Ascii-Armor-Area geschützt liegt hier die höchste ausführbare Adresse bei 0x01003fff. Alle ausführbaren Bereiche liegen in der sicheren Zone.

Abbildung 3: Mit Ascii-Armor-Area geschützt liegt hier die höchste ausführbare Adresse bei 0x01003fff. Alle ausführbaren Bereiche liegen in der sicheren Zone.

Linux Security Modules

Die Zugriffskontrollen von SE Linux[5] oder RSBAC[7] hat das Linux-Magazin bereits in früheren Artikeln eingehend betrachtet. Während SE Linux die LSM-Architektur (Linux Security Modules[10]) nutzt, genügt diese Schicht vielen Projekten nicht. LSM regelt nur Access Control, Exec-Shield oder Pax greifen tiefer auf die Strukturen des Kernels zu. Sie sind folglich als eigenständige Patches ohne LSM implementiert.

Interessant ist jene Kernelschnittstelle, die sich um die Zugriffsrechte der Speicherbereiche eines Prozesses kümmert. Es handelt sich um die Funktionen »mmap()« und »mprotect()«. Sie sollten das Einschleusen von neuem Code in den Adressraum einer Task erschweren oder verhindern. Es gilt: Ausführbare Speicherbereiche dürfen nicht beschreibbar und beschreibbare Speicherbereiche nicht ausführbar sein. Um dies zu erreichen, muss ein Patch folgende Aktionen unterbinden:

  • Erstellen von ausführbaren, anonymen Mappings
  • Erstellen von Mappings auf gleichzeitig ausführbare und
    beschreibbare Bereiche in einer Task
  • Ändern eines ausführbaren Speicherbereichs als
    zusätzlich beschreibbar
  • Ändern eines nicht ausführbaren, jedoch
    beschreibbaren Mappings als ausführbar

Eine weitere Technik zum Schutz vor Exploits sorgt dafür, dass alle Mappings auf »PROT_EXEC«-Segmente in der so genannten Ascii-Armor-Area liegen. Auf 32-Bit-Prozessoren sind das alle Speicheradressen zwischen 0 und 16 MByte. In diesen Bereich kann Code nicht springen, wenn er aus einem Buffer-Overflow-Exploit mit überlangen Ascii-Folgen stammt. In C kennzeichnet ein Null-Byte das Ende eines Strings, ein String kann daher keine Null-Bytes enthalten. Der Angreifer muss seinen Exploit-Code als String übergeben. Da jede Adresse in der Ascii-Armor-Area aber mindestens ein Null-Byte enthält, kann der Code dorthin nicht springen.

Sinnvoll ist es, die Standardadressen von Shared Libraries in diesen geschützten Bereich zu legen. Normalerweise mappt »mmap()« die Bibliotheken in den Adressraum. Wenn sie dank nicht ausführbarem Stack keinen eigenen Code mitbringen können, springen Exploits gerne Funktionen in Shared Libraries an, besonders »system()« in der Libc. Abbildung 2 zeigt das Speicher-Mapping des »cat«-Befehls ohne Ascii-Armor-Area. Die ausführbaren Speicherbereiche liegen gänzlich außerhalb dieses geschützten Bereichs und sind daher anfällig. Unter Exec-Shield mit Armor-Area liegt alles innerhalb des geschützten Bereichs (Abbildung 3).

Zufallstreffer

Address Space Layout Randomization ist eine weitere Technik, die Exploits erschwert. Sie variiert die Positionen der Speicherbereiche einer Task zufällig. Das verhindert gezielte Manipulationen von Daten, da der Angreifer die Positionen im Speicher nicht kennt. Er müsste mittels Brute Force alle Speicheradressen ausprobieren. Da Anwendungen oft bei einem Angriffsversuch abstürzen, hat der Admin eine gute Chance, die Attacke rechtzeitig zu bemerken. Fürs Belegen mit zufälligen Werten eignen sich:

  • Adressen von Segmenten des ELF-Binary (Codesegment,
    Datensegment, BSS)
  • Alle mit »mmap()« verwalteten Speicherbereiche
    (Libraries, Heaps Stacks von Threads, Shared Memory)
  • Stack der Task im Userspace
  • Kernelstack der Task

Die Grundidee dieses Konzepts ist auch für weitere Bereiche gut. Besonders Pax setzt stark auf den Schutz des Zufalls. Abbildung 4 zeigt zwei Prozesse eines Programms, deren Speichermapping sich unterscheidet, obwohl die Funktionalität gleich bleibt.

Ein Patch des Openwall-Projekts verbietet es regulären Benutzern, Hardlinks auf Dateien zu erstellen, für die sie weder Lese- noch Schreibrechte besitzen (zum Beispiel durch Gruppenzugehörigkeit). Normalerweise ist diese Aktion erlaubt. Viele Exploits nutzen sie aber für Links, die auf fremde Ressourcen verweisen. Höher privilegierte Benutzer und Prozesse verändern dann unbeabsichtigt die verlinkten Files, zum Beispiel bei Race Conditions.

Beschränkung und Zensur

Das auskunftsfreudige Proc-Filesystem ist auch für Angreifer eine willkommene Informationsquelle. Ein Patch schränkt die Rechte in »/proc« so ein, dass nur noch Root und eine Gruppe namens »gid« Zugriff auf die Eigenschaften von aktiven Netzwerkverbindungen oder fremden Prozessen erhalten. Normale User sehen lediglich ihre eigenen Prozesse. Auch Programme wie »top«, »who« und »ps« zeigen nur die eigenen Daten des Benutzers an.

Um die Gefahr von Daten-Spoofing zu verringern, begrenzt ein weiteres Patch den Schreibzugriff auf Untrusted Fifos (Named Pipes). Ähnlich wie Restricted Links verhindert er, dass ein Benutzer ohne passende Rechte in fremde Fifos schreibt.

Prozesslimits

Ein Linux-Admin kann mittels »setrlimit()«-Aufruf und »RLIMIT_NPROC« die Anzahl an verfügbaren Prozessen für einen Benutzer begrenzen. Allerdings prüft das System dieses Limit nur bei »fork()«-Aufrufen. Wechselt ein Prozess seine User-ID, bleibt das Limit für verfügbare Prozesse gleich, obwohl ihm unter der neuen UID eventuell weniger Prozesse zur Verfügung stehen sollten. Systeme ohne Prozesslimit sind schon für einfache Fork-Bomben anfällig:

:(){ : | :& };:

Dieser Shell-Aufruf startet sehr schnell sehr viele Prozesse. Gegen komplexere Angriffe mittels Fork-Bomben, die ständig ihre UID wechseln, ist selbst manch gut gesichertes System trotz Prozessbeschränkungen kaum geschützt. Der Einsatz von Exec-Shield oder Pax kann sich hier als sehr nützlich erweisen.

Abbildung 4: Die "Address Space Layout Randomization"-Technik vergibt die Speicheradressen bei jedem Ausführen eines Programms zufällig - unter anderem für Stack, Shared Memory und die dynamisch gelinkten Bibliotheken. Damit haben es Angreifer wesentlich schwerer, die gewünschten Funktionen zu finden.

Abbildung 4: Die “Address Space Layout Randomization”-Technik vergibt die Speicheradressen bei jedem Ausführen eines Programms zufällig – unter anderem für Stack, Shared Memory und die dynamisch gelinkten Bibliotheken. Damit haben es Angreifer wesentlich schwerer, die gewünschten Funktionen zu finden.

Müllbeseitigung im gemeinsamen Speicher

Per »setrlimit()«-Aufruf kann man zwar die Speichergröße eines Prozesses begrenzen. Es ist jedoch möglich, dass Shared-Memory-Speicherbereiche existieren, obwohl kein Prozess sie mehr verwendet. Sie fallen dann auch unter keine Speicherlimitierung. Openwall löst dieses Problem, indem es Shared-Memory-Bereiche automatisch zerstört (Garbage-Collector-Prinzip), wenn sie kein Prozess mehr benutzt. Das gilt auch für Segmente, die nie verwendet wurden, deren Prozess aber schon beendet ist. Die Option ist jedoch nur wirksam, wenn Ressourcenlimits definiert sind: Erforderlich sind »RLIMIT_AS« sowie »RLIMIT_NPROC«.

Der Garbage Collector verletzt allerdings wichtige Standards, was sich auf manche Anwendungen negativ auswirkt. Vor allem kommerzielle Datenbanken sind davon betroffen, Apache und PostgreSQL sollten laut Entwickler jedoch fehlerfrei funktionieren.

Im laufenden Betrieb beanspruchen die beschriebenen Sicherheitskonzepte etwas System-Performance, die automatische Freigabe nicht genutzter Speicherbereiche per Garbage Collection etwas Rechenzeit. Zudem ist die Emulation des Non-Executable-Flag aufwändig, da sie tief in die Speicherverwaltung eingreift. Auf x86-Systemen läuft der Segmexec-Ansatz schneller als Pageexec. Die meisten anderen Patches bremsen das System nicht, da sie nur Freiheiten enger definieren. Sie führen aber gelegentlich zu Kompatibilitätsproblemen.

Pax und andere Patches sollen das Ausnutzen von Buffer Overflows und Race Conditions erschweren oder gar verhindern. Die Nachteile für Performance und Kompatibilität bleiben meist klein im Vergleich zum Sicherheitsgewinn. Die Projekte sorgen meist dafür, dass alle wichtigen Anwendungen bis auf wenige Ausnahmen problemlos laufen. Wenn es dennoch klemmt, kann der Admin die Beschränkungen komplett oder für einzelne Befehle im laufenden Betrieb deaktivieren.

Pax erkennt zudem automatisch so genannte GCC-Trampolines. Diese kleinen Codestücke generieren manche Prozesse zur Laufzeit und legen sie auf den Stack. Da dank Pax der Stack sein Ausführungsrecht verliert, führt das Trampoline zu einer Exception. Der Kernel fängt sie ab, erkennt das Trampoline und emuliert seine Funktion. Allerdings kann es Angreifern gelingen, ihren Exploit als Trampoline zu tarnen und den Kernel zu täuschen. Wenn möglich sollte der Admin daher auf die Emulation verzichten.

Lohn der Mühe

Pax, Openwall und ähnliche Projekte lohnen bereits bei Systemen mit durchschnittlichen Sicherheitsanforderungen. Sie gewähren zusätzlichen Schutz, ohne dass dem Administrator dabei viel Aufwand entsteht. Nur bei besonderen Anwendungen könnte die geringere Performance den Einsatz verhindern.

Programme, die unter den strikten Bedingungen bereits im Testbetrieb nicht einwandfrei funktionieren, sind im laufendem Betrieb ein deutliches Risiko. Gewissermaßen als Nebeneffekt fördern alle diese Konzepte folglich eine saubere (und sichere) Programmierung, da einige Sicherheitslöcher bereits während der Entwicklungs- oder Testphase einer Applikation auffallen.

Pax verhindert viele Exploits und sorgt außerdem dafür, dass sich mögliche Gefahrenquellen früh bemerkbar machen. Bereits integriert ist es zum Beispiel in dem Debian-Abkömmling Adamantix[11]. Das darf allerdings keinen Admin dazu verleiten, keine Sicherheits-Updates mehr einzuspielen. Sie sind noch immer die wichtigste Basis für ein sicheres System. (fjl)

Infos
[1] Pax-Projekt: [http://pax.grsecurity.net]

[2] Exec-Shield: [http://people.redhat.com/mingo/exec-shield/]

[3] Openwall: [http://www.openwall.com]

[4] LIDS: [http://www.lids.org]

[5] Achim Leitner, Carsten Grohmann und Konstantin Agouros, “SE Linux im Einsatz”: Linux-Magazin 01/03, S. 38, sowie Carsten Grohmann, “Zugriffs-Policy für SE Linux”: Linux-Magazin 03/03, S. 62, SE Linux: [http://www.nsa.gov/selinux/]

[6] GR Security: [http://www.grsecurity.net]

[7] Amon Ott, “Die Architektur des Linux-Sicherheitssystems Rule Set Based Access Control (RSBAC)”: Linux-Magazin 01/03, S. 48, sowie Linux-Magazin 04/03, S. 61, RSBAC-Homepage: [http://www.rsbac.org]

[8] Achim Leitner, “Nicht ganz dicht – Sicherheitslücken in Programmen vermeiden”: Linux-Magazin 06/01, S. 30

[9] Frank Peters, “ELF – das Executable and Linkable Format im Detail”: Linux-Magazin 05/04, S. 9, sowie [http://www.cs.princeton.edu/courses/archive/fall04/cos217/reading/elf.pdf]

[10] LSM: [http://lsm.immunix.org]

[11] Adamantix: [http://www.adamantix.org]

Der Autor
Florian Seitner studiert Informatik und beschäftigt sich mit Systemsicherheit und Mustererkennung. Seit 1997 ist er ambitionierter Pinguinliebhaber. In seiner Freizeit geht er gerne Segeln oder reist in der Weltgeschichte herum.
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben