Insights Blog Bug Hunting im Linux-Kernel mit KASAN

Bug Hunting im Linux-Kernel mit KASAN

Verfasst von Viacheslav Moskvin
Agenda

Dieser Beitrag wurde aus dem Englischen übersetzt. Zum Originalbeitrag.

Linux1

Linux1

Manchmal kann man dem Drang einfach nicht widerstehen, sich in den Code von Linux-Kernel-Modulen zu vertiefen. Ich entschied mich, mit einem Third-Party-Modul zu beginnen, das ich selbst nutze, dessen Verbreitung jedoch begrenzt ist – ein ideales Ziel.

Schon nach wenigen Stunden entdeckte ich etwas, das wie eine Schwachstelle aussah. Eine genauere Analyse bestätigte den Verdacht: Laut Code war ein Kernel Memory Overread möglich. Allerdings war das zunächst nur eine Theorie. Um sie zu verifizieren, musste ich einen Proof of Concept (PoC) erstellen.

Die Schwachstelle war nichts besonders Spektakuläres, „nur“ ein Double-Fetch-Problem, wie in der Abbildung unten dargestellt. Allerdings wusste ich nicht, ob es ausreichen würde, einfach zwei Threads zu starten, um die Race Condition auszunutzen – oder ob komplexere Techniken nötig wären. Es gab nur einen Weg, das herauszufinden.

Linux2


Linux2

Arbeiten an einem PoC

Ich begann mit einem einfachen PoC, der über grundlegende Befehle mit dem Kernel-Modul kommunizieren konnte. Nachdem ich erfolgreich eine Antwort erhalten hatte, entwickelte ich einen PoC speziell für die Schwachstelle.
Meine Erwartung war klar: Das Auslösen des Bugs sollte das System abstürzen lassen. Doch beim Start des PoC passierte – nichts. Ich überprüfte mehrfach, ob die Kommunikation mit dem Modul funktionierte und der richtige Codepfad im IOCTL-Handler erreicht wurde. Trotzdem kein Absturz.


Also musste es daran liegen, dass der Double-Fetch so schnell erfolgt, dass sich die Race Condition ohne zusätzliche Tricks nicht ausnutzen lässt. Ich investierte mehrere Tage in die Recherche entsprechender Techniken. Und ja – es gibt durchaus sehr kreative Ansätze. Allerdings waren die meisten bereits gepatcht, und der Rest funktionierte in meinem Setup nicht. Letztlich legte ich das Thema zunächst auf Eis.

Neustart

Einige Monate später griff ich das Thema mit frischer Motivation wieder auf:

  • Ich könnte ein eigenes Kernel-Modul entwickeln, das dem Zielmodul ähnelt, umfangreiche Debug-Ausgaben integrieren und versuchen, den Double Fetch gezielt zu triggern.

  • Wenn das funktioniert, ließe sich derselbe Ansatz auf das Originalmodul übertragen.

  • Alternativ könnte ich den Kernel Address Sanitizer (KASAN) einsetzen – ein im Kernel integriertes Tool zur Erkennung von Speicherfehlern, auch solcher, die schwer zu reproduzieren sind. [docs.kernel.org]

Da ich zuvor noch nicht mit KASAN gearbeitet hatte, war ich unsicher, wie komplex die Nutzung ist. Daher entschied ich mich zunächst für die ersten beiden Optionen.


linux3

Linux3

Code, Code, Code – das Trainingsmodul war schnell erstellt.
Noch mehr Code – und auch ein einfacher zweithreadiger PoC war fertig.
Ein Blick auf ./poc && dmesg bestätigte die Vermutung: Die Debug-Ausgaben zeigten, dass sich der Bug tatsächlich relativ zuverlässig auslösen ließ.

Linux4

Linux4

Eine weitere Iteration mit zusätzlichen Debug-Ausgaben im Zielmodul, Anpassung des PoC – und schließlich wurde klar, was passierte: printk() gab Kernel-Heap-Metadaten aus.

Was war also beim ersten Versuch schiefgelaufen?

Ganz simpel: Ich hatte einen entscheidenden Codeabschnitt übersehen, der die maximale Lesemenge begrenzte. Zwar konnte weiterhin über das Buffer-Ende hinaus gelesen werden, jedoch nicht in einem Umfang, der einen Kernel-Crash auslöste.

 

EIN BESSERER ANSATZ

Linux5

Warum also KASAN im Titel?

Ganz einfach: Nach all dem Aufwand entschied ich mich doch, KASAN auszuprobieren – und stellte fest, dass es mir enorm viel Zeit hätte sparen können. Statt mehrerer Tage war das Setup in ein bis zwei Stunden erledigt. Zusätzlich liefert KASAN eine klare, binäre Aussage: Ist das Verhalten eine Schwachstelle – ja oder nein?

KASAN arbeitet mit sogenanntem Shadow Memory, um Speicherzugriffe zu überwachen. Dabei repräsentiert ein Byte im Shadow Memory jeweils acht Byte echten Speichers. Werte zwischen 0x00 und 0x07 markieren gültigen Zugriff, während negative Werte spezielle Zustände kennzeichnen, z. B.:

  • 0xff → bereits freigegebener Speicher

  • 0xfc → Redzone zwischen Allokationen

Bei jedem Speicherzugriff prüft KASAN, ob sich der Zugriff innerhalb gültiger Speicherbereiche bewegt. [besthub.dev]

Linux5

KASAN einrichten

Um KASAN zu nutzen, muss der Kernel neu kompiliert werden. Stellen Sie sicher, dass etwa 40 GB freier Speicherplatz verfügbar sind. Im folgenden Beispiel erfolgt der Build für ARM64 – bei Bedarf kann ARCH=arm64 weggelassen werden.

Abhängigkeiten installieren

$ sudo apt update
$ sudo apt install build-essential libncurses-dev bison flex libssl

Kernel-Quellcode herunterladen

$ apt source linux-image-$(uname -r)
$ cd linux-*

KASAN aktivieren

$ make ARCH=arm64 menuconfig

Navigation:
Kernel hacking → Memory Debugging → KASAN: runtime memory debugger

Wählen Sie den passenden KASAN-Modus (z.B. Generic KASAN).

Kernel kompilieren

$ make ARCH=arm64 -j $(nproc)
$ make ARCH=arm64 modules
$ make ARCH=arm64 dtbs

Beim Build trat in meinem Fall folgender Fehler auf:

LD [M]  net/hsr/hsr.ko
  LD [M]  net/qrtr/qrtr.ko
  LD [M]  net/qrtr/qrtr-smd.ko
  LD [M]  net/qrtr/qrtr-mhi.ko
make: *** [Makefile:246: __sub-make] Error 2

Also habe ich den Build fortgesetzt, wobei ich nur eine Aufgabe hatte: eine bessere Fehlerausgabe zu erzielen.

$ make ARCH=arm64 -j 1
  SYNC    include/generated/autoconf.h
  CALL    scripts/checksyscalls.sh
  DESCEND bpf/resolve_btfids
  INSTALL libsubcmd_headers
  UPD     include/generated/utsversion.h
  CC      init/version-timestamp.o
BTF: .tmp_vmlinux.btf: pahole (pahole) is not available
Failed to generate BTF for vmlinux
Try to disable CONFIG_DEBUG_INFO_BTF
make[2]: *** [scripts/Makefile.vmlinux:37: vmlinux] Error 1
make[1]: *** [/media/sl4v/a6417fe8-1e5e-4957-86d7-0413e50d4dc6/kasan/linux-6.6.15/Makefile:1176: vmlinux] Error 2
make: *** [Makefile:246: __sub-make] Error 2

Glücklicherweise gab mir die Fehlermeldung ganz klar vor, was zu tun war: Ich sollte in die Datei .config gehen und CONFIG_DEBUG_INFO_BTF auf „n“ setzen – und es hat funktioniert. Ich habe den Kompilierungsprozess fortgesetzt, und der Rest verlief reibungslos.

Es folgt die Installation der Module und des Kernels selbst:

$ sudo make ARCH=arm64 modules_install
$ sudo make ARCH=arm64 install

Zum Schluss: Aktualisieren Sie die Bootloader-Konfiguration und starten das System neu:

$ sudo update-grub
$ sudo reboot

Nach dem Boot können Sie das Zielmodul laden und den PoC ausführen. KASAN liefert anschließend detaillierte Diagnosedaten inklusive Shadow-Memory-Dump.

Im untersuchten Fall zeigte der Dump, dass der fehlerhafte Zugriff auf eine Adresse mit Shadow-Wert 0xfc erfolgte – einer Redzone zwischen Puffern. Damit war klar: Das Modul liest über die Grenze des allokierten Speichers hinaus.

Linux6

KASAN liefert nicht nur eine klare Aussage darüber, ob eine Schwachstelle vorliegt, sondern unterstützt auch effektiv bei der Root-Cause-Analyse – insbesondere in Kombination mit Fuzzing oder komplexen Race Conditions.

Für tiefergehende Einblicke in unsere Ansätze zur Entwicklung robuster Sicherheitsarchitekturen empfiehlt sich ein Blick auf unsere Cybersecurity-Services.