Dieser Beitrag wurde am 14.Dezember 2014 auf die Linux-Kernel-Version 3.14.26 aktualisiert. |
Einen eigenen Linux Kernel aus den Quellen zu kompilieren, ist Thema dieses IT Security Blog Beitrags. In einigen Tagen (Update: Beitrag erschienen) werde ich einen weiteren Beitrag veröffentlichen, bei denen ich Möglichkeiten nennen werde, einen "gehärteten" bzw. "gepatchten" Linux Kernel zu kompilieren. Bevor wir uns jedoch mit Themen wie PAX, grSecurtity & Co. auseinander setzen, müssen einige Grundlagen vorhanden sein. Die Quellcodebeispiele werde ich auf Basis eines Debian GNU/Linux Systems (Ubuntu 14.04 LTS) demonstrieren.
Vor- und Nachteile eines eigenen Kernels
Um es vorwegzunehmen - notwendig ist ein eigener Kernel in den meisten Fällen nicht. Mit Hilfe eines eigenen Kernels ist es beispielsweise möglich, diesen exakt an das System anzupassen, daher nur die Module bzw. Treiber zu kompilieren, die auch wirklich für das System benötigt werden. Ein Debian Paket des aktuellen Kernels lässt sich somit auf eine Größe von etwa 6-7 MB reduzieren. Notwendig ist ein selbst kompilierter Kernel beispielsweise auch dann, wenn dieser für mehr Sicherheit, zur Systemhärtung, gepatcht und angepasst werden soll.
Die Konfiguration erfordert etwas Erfahrung, der größte "Nachteil" ist die etwas schwerere Wartbarkeit. Ein Update des selbst kompilierten Kernels aus den Paketquellen der Distribution ist dann nicht mehr ohne weiteres möglich.
Eine Wiederherstellung bzw. Rückkehr zum originalen Kernel der Distribution ist zu jedem Zeitpunkt ohne Probleme möglich.
Erster Schritt: Quellcode des Vanilla Kernels downloaden
Der Vanilla Kernel ist der originale Kernel, also der Kernel, der nicht durch die Maintainer der Distributionen (z.B. Ubuntu) gepatched oder verändert wurde.
Der aktuelle Kernel kann von der Webseite https://www.kernel.org/ heruntergeladen werden.
Im Screenshot sind verschiedene Versionen sichtbar. Der letzte "stable" Kernel im Bild ist der 3.18. Für eine leichtere Wartbarkeit, insbesondere dann, wenn dieser gepatcht werden soll, ist ein Longterm Kernel sinnvoll. Ich werde die folgenden Schritte am Beispiel des Kernels 3.14.26 demonstrieren, da es für diesen ein stabilen grSecurity Patch gibt, was in einem weiteren BLOG Beitrag von mir relevant werden wird.
Heruntergeladen wird hierbei zum einen der Kernel im tar.xz gepackten Format, zum anderen die pgp Signatur. Die pgp Signatur verifiziert nicht die tar.xz, sondern nur die tar Datei, die mit xz komprimiert wurde.
Das Kompilieren aus dem Quellcode heraus bietet nebenbei das Sicherheitsplus, dass der Quellcode mit der kompilierte Version übereinstimmt. Der Download eines Open Source Programms bedeutet noch lange nicht, dass die kompilierte Version ohne Modifikationen aus dem öffentlichen Quellcode erzeugt worden ist!
Am Ende dieses Schrittes sollten auf dem System zwei Dateien vorhanden sein:
- linux-3.14.26.tar.xz (Der Quellcodes des Kernels)
- linux-3.14.26.tar.sign (Die Signatur der linux-3.14.26.tar)
Download validieren
Die Unversehrtheit und Integrität des Downloads kann mit folgendem Befehl überprüft werden:
# Download entpacken unxz '/pfad/zur/datei/linux-3.14.26.tar.xz' # Download verifizieren gpg --verify '/pfad/zur/datei/linux-3.14.26.tar.sign'
Das Ergebnis sollte eine Ausgabe ähnlich dieser sein:
gpg: Unterschrift vom Fr 02 Aug 2013 23:07:57 CEST mittels RSA-Schlüssel ID 6092693E gpg: Korrekte Unterschrift von »Greg Kroah-Hartman (Linux kernel stable release signing key) <greg[at]kroah.com>« gpg: WARNUNG: Dieser Schlüssel trägt keine vertrauenswürdige Signatur! gpg: Es gibt keinen Hinweis, daß die Signatur wirklich dem vorgeblichen Besitzer gehört. Haupt-Fingerabdruck = 647F 2865 4894 E3BD 4571 99BE 38DB BDC8 6092 693E
Für eine Verifizierung sollte der Schlüssel des Inhabers importiert werden, weitere Informationen zu diesem Thema finden sich auf der Webseite Linux kernel releases PGP signatures.
System für die Kompilierung des Kernels vorbereiten
Ich verwende im Folgenden gcc in der Version 4.9, welches bei Ubuntu 14.04 standardmäßig nicht mitgeliefert wird. Die Version findet sich für Ubuntu im folgendem Respository: PPA for Ubuntu Toolchain Uploads (restricted)
Mit dem Befehl
sudo apt-get -y install patch bin86 kernel-package bc build-essential libncurses5-dev gcc-4.9-plugin-dev g++-4.9
werden alle notwendigen Pakete zum Erzeugen eines Kernels auf dem System installiert.
Download entpacken
Um uns im folgenden das ein oder andere "sudo" und "chown" sparen zu können, wechseln wir in der Konsole in eine Rootshell, bei der alle folgenden Befehle mit Rootrechten ausgeführt werden, bis diese mit "exit" verlassen wird:
sudo -i
Das Vorzeichen in der Kommandozeile wechselt hierbei von "$" auf "#".
Weitere Schritte:
# Arbeitsverzeichnis anlegen mkdir /usr/src/linux/ # Ins Arbeitsverzeichnis wechseln cd /usr/src/linux/ # tar Datei kopieren cp /pfad/zur/datei/linux-3.14.26.tar ./linux-3.14.26.tar # tar Datei entpacken tar -xvf linux-3.14.26.tar
An dieser Stelle möchte ich einige Anmerkungen zum Format "tar.xz" schreiben, da ich häufig nach den Unterschieden gefragt werden. Tar bietet die Möglichkeit, Dateien sequenziell, also hintereinander, in eine einzige Datei zu schreiben bzw. Dateien aus dem TAR-Archiv wieder herzustellen. Im Falle von tar.gz bedeutet dies beispielsweise, dass die Dateien erst mit tar gepackt worden sind, anschließend wurde das tar Archiv mit gz komprimiert:
Linux Kernel anpassen
Eine gute Ausgangsbasis für den eigenen Kernel ist die aktuelle Kernelkonfiguration, diese befindet sich im Ordner /boot und hat meist einen Namen ähnlich der "config-x.yy.zz-generic", also zum Beispiel "config-3.2.0-51-generic". Diese wird in den aktuellen Ordner kopiert:
# Aktuelle Konfiguration übernehmen cp /boot/config-$(uname -r) ./.config
Ist die Kernelversion der kopierten Konfiguration niedriger oder wird der Kernel aktualisiert, ist jetzt folgendes Kommando notwendig:
make oldconfig
An dieser Stelle kann der Kernel mit folgendem Befehl konfiguriert und angepasst werden:
make menuconfig
Es erscheint hierbei folgendes Konfigurationsmenü:
Einige wichtige Tastaturbefehle:
# Die Beschreibung oder Tipps zum aktuell ausgewählten Punkt anzeigen ? # Durch die verschiedenen Punkte navigieren Left/Right (⬌) Up/Down (⬍) PgUp/PgDn # Menükonfiguration beenden oder das Kommando abbrechen (2x die Esc-Taste drücken) Esc Esc # Kommando auswählen / aktivieren oder Untermenü anzeigen Enter (⏎) # Kernelfeature in den Kernel kompilieren y # Kernelfeature als Modul kompilieren m # Kernelfeature nicht kompilieren n
Um ausschließlich Module zu kompilieren, die aktuell (!) vom System benutzt werden, kann folgender Befehl genutzt werden:
make localmodconfig
Dieser Befehl ist auch die Ausgangsbasis, für einen schlanken, an das System angepassten Kernel. Wichtig ist hierbei, dass alle USB-Geräte, etc. zu dem Zeitpunkt eingesteckt sind, da sonst die Module für diese nicht mitkompiliert werden. Insbesondere bei Servern ein interessanter Befehl. Eine Liste aller aktuell geladenen Module kann mit dem Befehl
lsmod
aufgerufen werden. Um die Module nicht als Kernel-Modul, sondern direkt in den Kernel zu kompilieren, kann folgender Befehl genutzt werden:
make localyesconfig
Vorteile dieser Möglichkeit: Das dynamische Nachladen von Kernelmodulen kann in der menuconfig deaktivieren werden, was einen guten Grundschutz gegen Rootkits bietet. Etwa 90% der bekannten Rootkits versuchen, dynamisch Kernelmodule nachzuladen!
[ ] Enable loadable module support --->
Für weitere Optimierungen können wir nun noch einen Blick in die Make-Datei werfen:
nano Makefile
In der Datei gibt es folgenden Abschnitt
# Add user supplied CPPFLAGS, AFLAGS and CFLAGS as the last assignments # But warn user when we do so warn-assign = \ $(warning "WARNING: Appending $(1)") ifneq ($(KCPPFLAGS),) $(call warn-assign,CPPFLAGS) KBUILD_CPPFLAGS += $(KCPPFLAGS) endif ifneq ($(KAFLAGS),) $(call warn-assign,AFLAGS) KBUILD_AFLAGS += $(KAFLAGS)
Weitere Optimierungen sind hierbei in der Zeile KBUILD_CPPFLAGS möglich:
# Add user supplied CPPFLAGS, AFLAGS and CFLAGS as the last assignments # But warn user when we do so warn-assign = \ $(warning "WARNING: Appending $(1)") ifneq ($(KCPPFLAGS),) $(call warn-assign,CPPFLAGS) KBUILD_CPPFLAGS += $(KCPPFLAGS) -march=native -pipe -mtune=native endif ifneq ($(KAFLAGS),) $(call warn-assign,AFLAGS) KBUILD_AFLAGS += $(KAFLAGS)
Ergänzt habe ich 3 Parameter:
- -pipe: Verringert die Zeit, die zum Kompilieren benötigt wird, auf Systemen mit viel Arbeitsspeichert signifikant
- -march=native: Optimiert den Kernel auf die aktuelle CPU
- -mtune=native: Passt den Kernel an das System an, ist nur dann sinnvoll, wenn der Kernel ausschließlich auf dem gleichen System genutzt wird.
Den Kernel kompilieren
Im letzten Schritt wird der Kernel kompiliert:
make-kpkg clean # Mit mehreren Threads gleichzeitig kompilieren (Anzahl der CPU Kerne) export CONCURRENCY_LEVEL=4 # Kernel im "Debian Style" übersetzen make-kpkg --initrd --append-to-version "Bezeichnung_eurer_Wahl" kernel_image
Nach dem Abschluss findet sich im übergeordneten Verzeichnis ein deb-Paket, was mit dem Befehl
sudo dpkg -i Name_der_datei.deb
installiert werden kann.
Kompilierungsfehler bei deaktiviertem "Loadable Module Support"
Bei deaktiviertem "Loadable Module Support" bricht die Kompilierung unter Ubuntu 14.04 LTS mit folgendem Fehler ab:
The present kernel configuration has modules disabled. Type 'make config' and enable loadable module support. Then build a kernel with module support enabled. make[1]: *** [modules] Fehler 1 make[1]: Verlasse Verzeichnis '/usr/src/linux/linux-3.14.26' make: *** [debian/stamp/build/kernel] Fehler 2
Zur Lösung des Problems mit dem Befehl
grep -R "*CONFIG_MODULES $(CONFIG_FILE)"
die Kernel-Rulefiles durchsuchen und prüfen, wo dieser Ausdruck verwendet wird.
Anschließend in den Fundstellen die folgenden Ausdrücke ersetzen:
Den Text
grep -E ^[^\#]*CONFIG_MODULES $(CONFIG_FILE)
ersetzen mit
grep -E ^[^\#]*CONFIG_MODULES[^_] $(CONFIG_FILE)
Anschließend sollte sich der Kernel fehlerfrei kompilieren lassen.
Bildnachweis:
- Beitragsbild: © peshkova - Fotolia.com