Für den Linux-Kernel kann man auf jedem System gewisse Schalter aktivieren, um die Sicherheit des Systems zu erhöhen. Auf Serversystemen lassen sich meist strengere Regeln umsetzen, weil diese wohldefinierte und einfache Aufgaben verrichten als auf Desktopsystemen.

Die im Folgenden aufgeführten Einstellungen für sysctl habe ich bei mir in /etc/sysctl.d/local.conf abgelegt. Da sysctl von Systemd verwaltet wird, kann man alle Änderungen mit systemctl restart systemd-sysctl laden oder einzelne Einstellungen mit /lib/systemd/systemd-sysctl --prefix ….

Keine zusätzlichen Kernel-Module laden

Für einen Server ist in der Regel klar, welche Programme gestartet werden und welche Ressourcen er dafür benötigt. Daher kann man über sysctl das Laden weiterer Module sperren:

# Freeze loaded modules
kernel.modules_disabled = 1

Diese Sperre wird somit beim Start gesetzt, sodass im Betrieb keine weiteren Module geladen werden können und der Kernel in seinem Funktionsumfang klar definiert ist. Einige Kommandozeilenprogramme wie ss würden jedoch zu einem späteren Zeitpunkt das Laden eines Moduls auslösen. Da dies nicht möglich ist, müssen solche Module bereits beim Start mithilfe der Konfigurationsdatei /etc/modules-load.d/local.conf geladen werden:

# used by `ss`
netlink_diag
tcp_diag
udp_diag
unix_diag

# used by acpi
button
evdev

virtio_console
aesni_intel

wireguard

Bei der Einrichtung eines System starte ich dieses ohne die Sperre und sichere die Liste der geladenen Module (lsmod). Später zeigen sich fehlende Module meist dadurch, dass Programme melden, dass bestimmte Funktionen nicht unterstützt werden: socket: Protocol not supported.

PTrace einschränken

PTrace ist eine Systemfunktion, über die Prozesse in den Speicherbereich anderer Prozesse greifen und auch das Verhalten der anderen Prozesse steuern können. Notwendig ist dies, weil grundlegend unter Unix jeder Prozess in seiner eigenen virtuellen (Speicher-)Welt lebt und nicht auf fremden Speicher zugreifen kann. Manchmal ist es aber notwendig, diese Grenze zu überschreiten, wenn man zum Beispiel wissen möchte, was der andere Prozess treibt; bekannte Beispiele dafür sind strace oder ein Debugger, aber auch Firefox nutzt diese Funktion, um bei einem Absturz Informationen zu sichern.

Daher will man PTrace meist nicht haben, aber manchmal doch. Genau das kann man mit über sysctl mit kernel.yama.ptrace_scope einstellen:

Da sich leider schwer vorhersehen lässt, wann und unter welchen Umständen PTrace genutzt wird, habe ich zum Tests erst einmal auf einem Serversystem den Wert 2 und auf einem Desktopsystem den Wert 1 gesetzt. Auf einem Embedded-System würde ich gar den Wert 3 setzen, weil dort niemand PTrace nutzen muss.

Darüber hinaus lässt sich PTrace auch mit AppArmor feingranularer eingrenzen, aber als Grundeinstellung ist diese Steuerung über Yama ausreichend.

Read-only Root

Read-only-Root ist keine direkte Kernel-Einstellung, sondern eine Funktion des Dateisystems, aber dennoch will ich sie an dieser Stelle erwähnen. Schon sehr lange habe ich meine Dateisysteme so aufgeteilt, dass zwei Dateisysteme nutze: eines für die schreibbaren Daten (/home, /srv, /var1) und eines für den Rest. Somit ist möglich, das Dateisystem mit /bin, /etc, /sbin, /usr als schreibgeschützt einzubinden.

  1. Dieses Dateisystem binde ich unter /var ein und nutze mount --bind um Verzeichnisse von /var/local/… an die entsprechende Stelle zu bringen. 

Ganz am Anfang war der Anlass, die Dauer der regelmäßigen Dateisystemprüfungen nach 20 Mal mounten zu reduzieren, zumal das System neu starten musste, wenn sich etwas am Hauptsystem geändert hatte, aber auch heute bietet der Umstand, dass die Programme und Konfigurationdateien nicht einfach bearbeitet werden können, einen gewissen Schutz. Auf Embedded-Systemen bietet diese Funktion auch einen Schutz dagegen, dass das System aufgrund eines zerstörten Dateisystems nicht mehr startet. Aber selbst auf normalen Desktopsystemen, die großteils nur genutzt und nicht administriert werden, lohnt sich das schreibgeschützte Dateisystem, damit im Extremfall wenigstens das Kernsystem startet.

Setzen lässt sich diese Einstellung des Dateisystems einfach in /etc/fstab als Option des Hauptdateisystems:

/dev/disk/by-label/Linux_Root  /  ext4  ro,errors=remount-ro,noatime  0  1

Für die Paketverwaltung apt lässt sich in /etc/apt/apt.conf.d/zzlocal einstellen, dass vor und nach der Paketinstallation das Dateisystem schreibbar und im Anschluss wieder zurück geschaltet wird. Der Punkt zz ist dabei notwendig, weil needrestart als 99 läuft und erst damit alle Prozesse mit veränderten Bibliotheken neu gestartet und die alten Dateien freigegeben wurden.

DPkg {
    // Auto re-mounting of a readonly /
    Pre-Invoke { "mount -o remount,rw /"; };
    Post-Invoke { "test ${NO_APT_REMOUNT:-no} = yes && exit 0; mount -o remount,ro /; true"; };
};

Durch die Umgebungsvariable NO_APT_REMOUNT kann man verhindern, dass ständig das Dateisystem wieder gesperrt wird, wenn man damit arbeiten will. Hierfür kann man sich in der Shell diese Variable zu beginn setzen mount -o remount,rw / && export NO_APT_REMOUNT=yes und auch wieder löschen mount -o remount,ro / && unset NO_APT_REMOUNT.

Wenn sich das Dateisystem einmal nicht schreibgeschützt setzen lässt, muss man mit lsof auf Suche gehen.

USB unterbinden

Siehe: Automatische Einbindung von USB-Geräten unterbinden

Lockdown-Modus

Der Lockdown-Modus ist unter sehr kontroversen Diskussionen in den Kernel gekommen, da er stark die Möglichkeiten des Administrators root einschränkt: Kernel-Module lassen sich nur laden, wenn sie signiert sind, der Direktzugriff auf Hardware wird weitestgehend unterbunden und Performance-Counter und BPF sind deaktiviert.

Wenn man die Kontrolle über das System hat, ist das genau das, was man möchte, um einem Angreifer das Leben zu erschweren. Im Zusammenspiel mit Secure-Boot kann man auf diese Weise sicherstellen, dass auf dem System nur die Programme laufen, die man möchte.

Wenn man aber nicht die Kontrolle über das System hat, hat man es mit dem Lockdown-Modus schwer, das System anzupassen. Und genau daran entzündet sich die große Kritik dieses Modus', denn im Falle eines Embedded-Systems, bei dem man ein Gerät (z. B. Handy, (Sonos-)Sound-System) teuer kauft, unter Umständen sogar den Root-Zugriff hat, aber am Ende auf das Wohlwollen des Herstellers angewiesen ist. Sollte der – im schlimmsten Fall – die Unterstützung für das Gerät abkündigen, hat man einen funktionsfähigen Haufen Schrott und kann weder Anpassungen für externe API (z. B. YouTube) vornehmen, noch Sicherheitsprobleme beheben.

Des einen Freud, des andren Leid – es kommt dabei sehr auf den Standpunkt an. Jedoch ist dies am Ende ein soziales Problem, das mit Aufklärung und Selbstermächtigung der Kunden oder auch Gesetzen gelöst werden muss, aber nicht mit technischem Maßnahmen. Daher ist aus Sicht des Administrators der Lockdown-Modus schon sinnvoll, wenn er denn auch passt.

Aktivieren lässt er sich entweder zur Laufzeit, indem man in die Datei /sys/kernel/security/lockdown einen der Werte integrity oder confidentiality schreibt oder sofort ab dem Systemstart mit dem Kernel-Parameter lockdown=…. Diesen kann man in /etc/default/grub eintragen, danach update-grub aufrufen und das System neu starten.

Mit dem Wert integrity werden alle Möglichkeiten unterbunden, den Kernel zu verändern, – ein Schreibschutz so zu sagen – und mit confidentiality werden zusätzlich die Möglichkeiten, heikle Informationen über den Kernel auszulesen – der Vollschutz.

Ich habe meine virtuelle Maschine mit Qemu einmal mit lockdown=confidentiality gestartet und es kamen einige Meldungen1, die mir nicht ganz so zusagen:

  1. Im Übrigen lassen sich die Unterschiede recht angenehm mit diff -u2 --color <(journalctl -b-1 -o cat) <(journalctl -b -o cat) finden. 

--- ohne lockdown
+++ mit lockdown=confidentiality
+Kernel is locked down from command line; see man kernel_lockdown.7
 ftrace: allocated 147 pages with 4 groups
+Lockdown: swapper: use of tracefs is restricted; see man kernel_lockdown.7

+Tracing disabled due to lockdown
+Lockdown: swapper/0: use of tracefs is restricted; see man kernel_lockdown.7

+Can not register tracer function_graph due to lockdown
+Can not register tracer mmiotrace due to lockdown
+Can not register tracer blk due to lockdown
+blktrace: Warning: could not register the block tracer
+Lockdown: swapper/0: hibernation is restricted; see man kernel_lockdown.7
+Lockdown: resume: hibernation is restricted; see man kernel_lockdown.7

+Lockdown: atop: unsafe use of perf is restricted; see man kernel_lockdown.7

Wenn ich das System mit lockdown=integrity starte, kommen nur die folgenden Meldungen, die man mit dem Kernel-Parameter hibernate=no beseitigen kann. Wer allerdings die Wiederherstellung des Systems nutzen will, muss mit einem verschlüsselten Swap-System arbeiten; Garrett: Making hibernation work under Linux Lockdown .

Lockdown: swapper/0: hibernation is restricted; see man kernel_lockdown.7
Lockdown: resume: hibernation is restricted; see man kernel_lockdown.7

Ich werde jetzt das System in diesem Zustand betreiben und über die Zeit hin beobachten (hierfür ist die Überwachung des Journals nützlich) wo noch Probleme auftauchen. Im Übrigen steht in den Meldungen hinter Lockdown: der Name des Prozesses. Mithilfe des Journals und dem Prozess-Accounting von atop kann man so erraten, welcher Prozess den Verstoß verursacht hat. Hoffentlich bzw. leider lässt sich dann nicht mit SystemTap der Stacktrace zum Aufruf ermitteln.

Zum Schluss möchte ich noch dem Tippgeber für den Hinweis (per Stift oben rechts) auf den Lockdown-Modus und den Artikel »Wie der Lockdown-Modus den Linux-Kernel sicherer macht« danken.