Kai Ole Hartwig
21 Min. Lesezeit
Von

Linux-Kernel CVE-2026-31695: Ein virtuelles WLAN-Gerät zeigt auf ein Gerät, das es nicht mehr gibt

Der Treiber virt_wifi lässt eine gewöhnliche Netzwerkverbindung wie WLAN aussehen und setzt dazu auf eine vorhandene Netzwerkschnittstelle auf. Bis zum Fix trug er diese Schnittstelle zusätzlich als übergeordnetes Gerät im Gerätemodell des Kernels ein, ohne dass der Verweis sie am Leben hielt. Wird sie gelöscht, während ethtool gerade das virtuelle Gerät bearbeitet, greift der Kernel auf bereits freigegebenen Speicher zu. Alexander Popov hat am 2. November gezeigt, dass daraus auf einem Fedora-Server in Standardinstallation Root wird, und zwar für jeden lokalen Benutzer, der User Namespaces anlegen darf. Lehrreich ist der Fall weniger wegen des einen Treibers als wegen des Wegs dorthin. Ein Modul, das auf einem Server niemand braucht, lädt sich dort auf Anfrage selbst, und der Fix lag schon seit Mai bereit, als der Exploit die Lücke ins Gespräch brachte.

TL;DR — 90 Sekunden

Ein Verweis, der sein Ziel nicht festhält

Im Kernel ist jedes Netzwerkgerät zweimal vertreten. Als struct net_device gehört es zum Netzwerkstack, und eingebettet darin steckt ein struct device, mit dem es im allgemeinen Gerätemodell hängt, also in derselben Hierarchie wie PCI-Karten oder USB-Geräte. Das Feld parent dieser Struktur sagt, an welchem übergeordneten Gerät ein Gerät hängt. Netzwerktreiber setzen es mit dem Makro SET_NETDEV_DEV(), bei einer echten Netzwerkkarte zeigt es auf das PCI-Gerät. Das Energiemanagement nutzt diesen Verweis, um vor einem Zugriff das übergeordnete Gerät aufzuwecken.

virt_wifi ist ein Hilfstreiber, der laut Kconfig Ethernet-Verbindungen über ein spezielles rtnetlink-Gerät als WLAN-Verbindungen erscheinen lässt. Man legt dazu einen Link vom Typ virt_wifi auf einem bestehenden Gerät an, dem sogenannten Lower Device. Der Treiber registriert sich als Empfänger der Pakete dieses Geräts und verknüpft sich als Upper Device mit ihm. Bis zum Fix setzte er in virt_wifi_newlink() außerdem SET_NETDEV_DEV(dev, &priv->lowerdev->dev) und trug das Lower Device damit auch als übergeordnetes Gerät im Gerätemodell ein.

Genau hier lag der Fehler. Der Verweis in dev.parent hielt das Lower Device nicht am Leben. Wird es gelöscht, baut netdev_run_todo() im selben Zug das daran hängende virt_wifi-Gerät ab und gibt das Lower Device frei. Läuft in diesem Moment ein ethtool-Befehl auf dem virt_wifi-Gerät, etwa eine Änderung der Features über die Netlink-Schnittstelle, ruft ethnl_ops_begin() als Erstes pm_runtime_get_sync(dev->dev.parent) auf. Erst danach prüft die Funktion, ob das Gerät gerade abgemeldet wird. Der Kernel arbeitet also mit einem struct device, dessen Speicher schon freigegeben ist und neu vergeben sein kann. Popov fand die Stelle mit einem angepassten syzkaller, KASAN meldete einen slab-use-after-free in __pm_runtime_resume().

Warum ein unprivilegierter Benutzer überhaupt bis zu dieser Stelle kommt, erklären zwei Mechanismen, die jeder für sich gewollt sind. Einen Link anzulegen erfordert CAP_NET_ADMIN, doch diese Capability prüft der Kernel gegenüber dem jeweiligen Netzwerk-Namespace. Wer einen eigenen User Namespace erzeugt, ist darin Root und darf in einem zugehörigen Netzwerk-Namespace Geräte anlegen, ohne auf dem Host irgendein Recht zu besitzen. Laut Popov genügt genau das, um den Fehler auszulösen. Der zweite Mechanismus ist das automatische Laden von Modulen. Findet rtnetlink keinen Treiber für den angeforderten Link-Typ, ruft der Kernel request_module("rtnl-link-%s") auf, und virt_wifi meldet sich über MODULE_ALIAS_RTNL_LINK("virt_wifi") genau für diesen Namen. Eine weitere Rechteprüfung steht in diesem Pfad nicht, das habe ich im aktuellen Quelltext von net/core/rtnetlink.c nachgesehen. Ein Treiber, den auf einem Server niemand braucht, wird so geladen, sobald jemand im eigenen Namespace danach fragt.

Im Kernel steckt der Fehler seit Commit d43c65b05b84 mit dem Titel „ethtool: runtime-resume netdev parent in ethnl_ops_begin“, laut OSV betrifft er alle Versionen ab 5.15. Mit diesem Commit kam das Aufwecken des übergeordneten Geräts in ethnl_ops_begin() hinzu, und erst dadurch wurde der ungesicherte Verweis von virt_wifi gefährlich. Die Lücke entstand also aus dem Zusammenspiel zweier Änderungen, von denen jede für sich vernünftig aussieht.

Änderungen im Fix-Commit

Der Fix-Commit 789b06f9f39c von Popov heißt „wifi: virt_wifi: remove SET_NETDEV_DEV to avoid use-after-free“ und ist so kurz, wie der Titel verspricht. Er entfernt in drivers/net/wireless/virtual/virt_wifi.c den Aufruf von SET_NETDEV_DEV() aus virt_wifi_newlink(). Ein virt_wifi-Gerät hat danach kein übergeordnetes Gerät mehr, ethnl_ops_begin() findet in dev.parent nichts und überspringt das Aufwecken. Die Verbindung zum Lower Device bleibt über die Upper-Device-Verknüpfung und den Paket-Handler bestehen, die der Treiber beim Abbau ausdrücklich auflöst.

Popov hatte zuerst einen anderen Weg vorgeschlagen. Sein erster Patch zog in ethnl_ops_begin() die Prüfung auf ein abgemeldetes Gerät vor den Aufruf von pm_runtime_get_sync(). Die Maintainer wollten den gemeinsam genutzten ethtool-Pfad nicht anfassen und entschieden sich für die Änderung im Treiber. Im aktuellen Mainline-Quelltext steht die Reihenfolge in ethnl_ops_begin() weiterhin wie vorher, zuerst das Aufwecken, dann die Prüfung. Daraus leite ich ab, dass der Fix die Ursache nur für diesen einen Treiber beseitigt. Ein anderer Treiber, der ein Gerät als übergeordnet einträgt, das vor ihm verschwinden kann, würde dieselbe Klasse von Fehler wieder öffnen. Popov selbst hält die geänderte Reihenfolge in ethtool weiterhin für eine sinnvolle Verteidigung.

Vom eigenen Namespace zu Root auf dem Host

Ein Angriff braucht drei Dinge: einen Kernel ohne Fix, virt_wifi als ladbares Modul und die Erlaubnis für unprivilegierte Benutzer, User Namespaces anzulegen. Popov nennt für seinen Aufbau CONFIG_USER_NS=y, CONFIG_VETH=m und CONFIG_VIRT_WIFI=m, als Ziel diente Fedora 44 Server mit Kernel 6.19.10 in der Standardinstallation. Ab Werk betroffen ist also, wessen Distribution den Treiber als Modul mitliefert und User Namespaces nicht einschränkt. Für Amazon Linux führt ALAS unter Amazon Linux 2023 den Standard-Kernel kernel und kernel6.18 als betroffen. kernel6.12 unter Amazon Linux 2023 und alle Kernel von Amazon Linux 2 gelten dort als nicht betroffen, eine Begründung nennt ALAS nicht.

Konzeptionell läuft der Angriff so ab. Der Angreifer legt einen eigenen User- und Netzwerk-Namespace an, erzeugt darin ein Paar virtueller Ethernet-Geräte und setzt ein virt_wifi-Gerät auf eines davon. Dann löscht er das Lower Device und schickt im selben Moment einen ethtool-Befehl an das virt_wifi-Gerät. Damit der Wettlauf zuverlässig ausgeht, erhöhte Popov die Konkurrenz um die Sperre, auf die der ethtool-Pfad wartet, und belegte den frei gewordenen Speicher in der Zwischenzeit mit eigenen Daten. Folgt der Kernel dem alten Zeiger, liest er ein gefälschtes struct device. Den Rest erledigte eine Kette, die Kernel-ASLR über einen Seitenkanal aushebelt, SMEP und SMAP umgeht und am Ende den Pfad umbiegt, über den der Kernel Hilfsprogramme zum Laden von Modulen startet. SELinux hielt den naiven letzten Schritt auf Fedora zunächst auf, Popov schaltete es dann innerhalb der Kette ab. Die Slab-Härtungen CONFIG_RANDOM_KMALLOC_CACHES und CONFIG_SLAB_BUCKETS halfen hier nicht, weil Popov den Speicher über Netzwerkpuffer belegte, die von diesen Härtungen nicht erfasst werden.

Der realistische Schaden ist Root auf dem Host, und auf einem Kubernetes-Node heißt das Ausbruch aus dem Container. Wer Code in einem Pod ausführt, etwa über eine Lücke in einer Webanwendung, bekommt damit alles, was der Node sieht: die Secrets aller Pods auf diesem Node, die Anmeldedaten des Kubelets und unter AWS die Rolle der Instanz. Voraussetzung bleibt, dass der Angreifer schon Code auf dem System ausführt. Wie schnell sich eine solche Lücke an eine Remote-Lücke anhängen lässt, hat gerade PHP2Shell gezeigt, das Angreifer mit der Kernel-Lücke Fragnesia zu Root auf Firewalls verkettet haben.

Wirklich betroffen bist du, wenn auf einem Host mit betroffenem Kernel fremder oder aus dem Netz erreichbarer Code läuft und alle drei Voraussetzungen erfüllt sind. Das gilt für Kubernetes-Nodes mit Pods ohne seccomp, für Build-Runner, die fremde Pipelines ausführen, und für Systeme mit vielen Benutzern. Entwarnung geben kannst du dagegen für Hosts, deren Kernel zwar in der betroffenen Spanne liegt, auf denen aber kein Prozess User Namespaces anlegen kann. Dazu zählt auch jeder Pod im Pod Security Standard „restricted“. Er läuft mit dem Standardprofil von seccomp, kann damit weder unshare noch clone mit neuen Namespaces aufrufen und erreicht den Fehler deshalb nicht. Ebenfalls sicher ist, wessen Kernel virt_wifi gar nicht als Modul enthält. Das Container-Image spielt keine Rolle, denn der Fehler sitzt im Kernel des Hosts.

CVEBetroffenFixVoraussetzungCVSS
CVE-2026-31695Linux 5.15 bis vor 5.15.203, 5.16 bis vor 6.1.168, 6.2 bis vor 6.6.134, 6.7 bis vor 6.12.81, 6.13 bis vor 6.18.22, 6.19 bis vor 6.19.12; Amazon Linux 2023 kernel und kernel6.18Commit 789b06f9f39c; 5.15.203 / 6.1.168 / 6.6.134 / 6.12.81 / 6.18.22 / 6.19.12; ALAS2023-2026-1681 und -1746Lokale Codeausführung, unprivilegierte User Namespaces, CONFIG_VIRT_WIFI=m7.8 (hoch)

Bei der Ausnutzung ist die Lage übersichtlich. Popov hat seinen Exploit am 2. November ausführlich beschrieben, laut seinem Artikel erreicht er Root auf Fedora 44 Server. Angriffe in freier Wildbahn sind nicht berichtet, und im CISA-KEV-Katalog stand die Lücke am 4. November nicht. Der EPSS-Wert lag am selben Tag bei 0,00126, also rund 0,13 Prozent, und beruht auf der Zeit vor dem öffentlichen Exploit. Red Hat stuft die Lücke laut Presseberichten als niedrig ein, Amazon wie kernel.org mit 7.8 als hoch. Für deinen Host zählt am Ende weniger die Einstufung als die Frage, ob Modul und User Namespaces dort verfügbar sind.

Kernel-Fix und Pakete der Distributionen

Die einzige vollständige Gegenmaßnahme ist ein Kernel mit Commit 789b06f9f39c. Upstream steckt er laut OSV in 5.15.203, 6.1.168, 6.6.134, 6.12.81, 6.18.22 und 6.19.12. Erst ein Neustart aktiviert den neuen Kernel, bis dahin läuft auf dem Host der alte Code weiter. Einen Livepatch für diese CVE habe ich für Amazon Linux nicht gefunden.

Unter Amazon Linux 2023 hat Amazon den Standard-Kernel am 9. Mai mit ALAS2023-2026-1681 korrigiert, die Version für x86_64 ist kernel-6.1.168-202.320.amzn2023. Das Advisory nennt dafür dnf update --advisory ALAS2023-2026-1681 --releasever 2023.11.20260509. Für kernel6.18 folgte am 26. Mai ALAS2023-2026-1746 mit kernel6.18-6.18.25-52.107.amzn2023, eingespielt mit dnf update --advisory ALAS2023-2026-1746 --releasever 2023.11.20260526. Der Parameter --releasever ist dabei kein Detail. Amazon Linux 2023 bindet einen Host an einen festen Stand des Repositorys, und ein Host, der auf einem älteren Stand steht, findet die neue Version erst mit dem angegebenen Stand. Auf EKS-Nodes ersetzt du statt eines Updates per dnf das AMI durch eines mit gepatchtem Kernel und rollst die Nodes durch. Für Amazon Linux 2 meldet ALAS alle Kernel als nicht betroffen.

In Wolfi und anderen Base-Images für Container gibt es nichts zu tun, weil sie keinen Kernel mitbringen. Ein neu gebautes Image schließt die Lücke nicht, und ein Scanner, der nur Images prüft, sieht sie gar nicht. Die Prüfung muss deshalb am Host ansetzen. Verhaltensänderungen bringt der Fix kaum mit. Ein virt_wifi-Gerät hat danach kein übergeordnetes Gerät mehr, was aus meiner Sicht nur für Werkzeuge zählt, die unter /sys die Gerätehierarchie auswerten. Im normalen Serverbetrieb merkst du davon nichts.

Härtung über den Patch hinaus

Gegen eine Lücke im Kernel wirkt keine Maßnahme im Container-Image, wohl aber alles, was den Weg zum fehlerhaften Code abschneidet oder die Ausnutzung erschwert. Ich ordne die Maßnahmen deshalb in drei Klassen. Die erste nimmt dem Angreifer den Treiber, die zweite die Rechte, mit denen er den Treiber in einem eigenen Namespace erreicht, und die dritte verteuert die Techniken, mit denen aus dem Speicherfehler Root wird. Die ersten beiden schließen diesen konkreten Weg vollständig, die dritte wirkt breiter, aber schwächer.

Das Modul virt_wifi sperren

Am günstigsten ist es, den Treiber gar nicht erst laden zu lassen. Laut der Handbuchseite modprobe.d(5) sorgt blacklist virt_wifi dafür, dass modprobe die internen Aliase des Moduls ignoriert, und genau über einen solchen Alias, rtnl-link-virt_wifi, läuft das automatische Laden. Gründlicher ist install virt_wifi /bin/false, weil modprobe dann statt des Ladens den angegebenen Befehl ausführt, auch beim direkten Aufruf mit dem Modulnamen. Beides gehört in eine Datei unter /etc/modprobe.d/, auf Kubernetes-Nodes in die Bootstrap-Konfiguration oder in ein eigenes AMI. Der Aufwand ist gering, denn auf einem gewöhnlichen Server hat dieser Treiber keine Aufgabe. Die Grenze ist ebenso klar. Ist das Modul bereits geladen oder fest in den Kernel gebaut, hilft der Eintrag nicht, und er schützt nur vor genau diesem einen Treiber.

Kein Nachladen von Modulen nach dem Boot

Popov selbst empfiehlt die breitere Variante. Mit kernel.modules_disabled=1 lassen sich laut Kernel-Dokumentation Module weder laden noch entladen, und der Schalter lässt sich bis zum nächsten Neustart nicht zurücksetzen. Popov schlägt dafür eine systemd-Unit vom Typ oneshot vor, die nach multi-user.target den Wert setzt, sobald alle benötigten Module geladen sind. Damit fällt die ganze Klasse von Lücken in selten genutzten Treibern weg, die erst durch automatisches Laden erreichbar werden, virt_wifi eingeschlossen. Laut Popov verhindert dieselbe Sperre auch die Technik, mit der sein Exploit am Ende Root holt.

Auf Kubernetes-Nodes ist der Preis höher als auf einem einzelnen Webserver. CNI-Plugins, kube-proxy und Speichertreiber laden Netfilter-, IPVS- oder Dateisystemmodule oft erst bei Bedarf, und nach der Sperre schlägt das fehl. Du musst also vorher ermitteln, welche Module ein Node im Betrieb braucht, sie beim Boot laden und neue Funktionen künftig mit einem Neustart einführen. Das leite ich aus der Funktionsweise des Schalters ab, eine fertige Modulliste für EKS-Nodes kenne ich nicht. Die schwächste Variante, kernel.modprobe auf einen leeren Wert zu setzen, hält Popov für unzureichend, weil sich der alte Pfad wiederherstellen lässt.

User Namespaces für unprivilegierte Benutzer

Der Angriff braucht CAP_NET_ADMIN in einem eigenen Netzwerk-Namespace, und den bekommt ein unprivilegierter Benutzer nur über einen eigenen User Namespace. Der Sysctl user.max_user_namespaces legt laut Kernel-Dokumentation fest, wie viele User Namespaces jeder Benutzer im aktuellen Namespace anlegen darf. Mit dem Wert 0 entfällt diese Möglichkeit auf dem ganzen Host, und mit ihr der Einstieg in diesen Exploit. Die Maßnahme reicht weit über diese CVE hinaus, weil User Namespaces Netzwerk-, Netfilter- und Dateisystem-Code für unprivilegierte Benutzer erreichbar machen, der sonst nur Root offensteht.

Der Betriebsaufwand liegt bei allem, was User Namespaces legitim nutzt. Dazu gehören rootless Podman und rootless BuildKit auf Build-Runnern, die Sandbox von Chromium-basierten Browsern in Testjobs und Pods mit hostUsers: false, die Kubernetes in eigenen User Namespaces startet. Auf einem Kubernetes-Node, der Container ausschließlich über containerd als Root startet, fällt die Einschränkung meist nicht auf, auf einem GitLab Runner mit rootless Builds dagegen sofort. Das leite ich aus der Funktionsweise dieser Werkzeuge ab, deshalb würde ich die Einstellung zuerst auf einem Test-Node ausrollen.

seccomp im Profil RuntimeDefault für jeden Pod

Auf Kubernetes-Nodes lässt sich der Weg auch gezielter abschneiden, nämlich für Container statt für den ganzen Host. Das Standardprofil von containerd erlaubt unshare, setns, clone3 und mount nur Prozessen mit CAP_SYS_ADMIN und clone nur ohne die Flags für neue Namespaces, darunter CLONE_NEWUSER und CLONE_NEWNET. Ein Prozess im Container kann so weder einen User Namespace noch einen Netzwerk-Namespace anlegen und kommt an virt_wifi_newlink() gar nicht heran. Das habe ich in contrib/seccomp/seccomp_default.go im Quelltext von containerd nachgelesen, das Standardprofil von Docker verhält sich gleich.

Die Falle liegt im Standard von Kubernetes, denn ohne Angabe laufen Pods dort mit Unconfined, also ganz ohne seccomp. Abhilfe schaffen zwei Stellen. In der Kubelet-Konfiguration setzt seccompDefault: true das Profil RuntimeDefault für alle Pods, die selbst keins angeben. Der Pod Security Standard „restricted“, durchgesetzt über das Label pod-security.kubernetes.io/enforce: restricted am Namespace, verlangt zusätzlich seccompProfile.type mit RuntimeDefault oder Localhost, entzieht alle Capabilities bis auf NET_BIND_SERVICE und fordert allowPrivilegeEscalation: false sowie runAsNonRoot: true. Wo Pods CAP_SYS_ADMIN brauchen oder privilegiert laufen, etwa CSI-Treiber oder CNI-Agenten, greift der Schutz nicht, und diese Pods gehören auf eine kurze, begründete Ausnahmeliste. Eine Admission-Policy mit Kyverno oder als ValidatingAdmissionPolicy hält diese Liste kurz. Wie sich Pod Security, Namespaces und Ausnahmen für TYPO3 sauber aufteilen, beschreibt die Serie TYPO3 auf Kubernetes unter IT-Grundschutz.

Den Exploit selbst verteuern

Die dritte Klasse richtet sich gegen die Technik statt gegen den Zugang. Popov nennt den Boot-Parameter init_on_free=1, der freigegebenen Speicher sofort mit Nullen füllt. In seinen Tests machte das aus dem stillen Use-after-free eine Kernel-Warnung, die Rechteausweitung gelang trotzdem. Die Page Table Isolation erschwert laut Popov das Auslesen der Kernel-Adressen, auch wenn es andere Wege dafür gibt, und eine starke Control-Flow-Integrity würde die Wiederverwendung von Code blockieren, auf die seine Kette angewiesen ist. Welche dieser Optionen dein Kernel mitbringt, prüft sein Werkzeug kernel-hardening-checker anhand von Kernel-Konfiguration, Boot-Parametern und Sysctls. Auf einem Distributionskernel kannst du nur Boot-Parameter und Sysctls selbst setzen, CFI und Slab-Härtung hängen an der Konfiguration, mit der die Distribution baut. Ich sehe diese Klasse deshalb als Ergänzung, die den Preis für den nächsten Exploit erhöht, aber keine der ersten beiden ersetzt.

Was hier nicht hilft

Ein neues Container-Image, ein aktuelles Wolfi-Paket oder ein Scan der Images ändert nichts, weil der fehlerhafte Code im Kernel des Hosts läuft. Auch runAsNonRoot und ein schreibgeschütztes Root-Dateisystem schützen allein nicht, denn der Angriff ist von Anfang an für einen unprivilegierten Benutzer gebaut, und ein beschreibbares Verzeichnis wie /tmp findet er in fast jedem Pod. Eine NetworkPolicy greift nicht, weil der Angriff lokal läuft und nur Netlink im eigenen Namespace nutzt. SELinux hat Popovs ersten Versuch auf Fedora aufgehalten, die fertige Kette schaltet es aber vorher ab. Die Slab-Härtungen CONFIG_RANDOM_KMALLOC_CACHES und CONFIG_SLAB_BUCKETS haben laut Popov in diesem Fall nichts verhindert. Und das Modul veth zu sperren, das Popov als Lower Device nutzte, kommt auf Kubernetes-Nodes nicht in Frage, weil das CNI es für die Netzwerke der Pods braucht.

Erkennung und Prüfung

Ob ein Host betroffen ist, klärst du in drei Schritten. Zuerst vergleichst du den laufenden Kernel aus uname -r mit den Fix-Versionen, unter Amazon Linux 2023 zusätzlich die installierten Pakete aus rpm -q kernel kernel6.18 mit den Versionen aus den ALAS-Advisories. Ein installiertes, aber noch nicht gebootetes Paket schützt nicht. Dann prüfst du, ob der Treiber überhaupt vorliegt: modinfo virt_wifi zeigt das Modul oder meldet, dass es fehlt, und lsmod zeigt, ob es geladen ist. Zuletzt zeigt sysctl user.max_user_namespaces, ob User Namespaces erlaubt sind, und /proc/sys/kernel/modules_disabled, ob noch Module nachgeladen werden dürfen. Auf Kubernetes kommt die Frage hinzu, welche Pods ohne seccomp laufen, also jene ohne seccompProfile im securityContext, solange seccompDefault im Kubelet nicht aktiv ist.

Belegte Indikatoren aus echten Angriffen gibt es nicht. Drei Spuren leite ich aus dem Mechanismus ab. Ein geladenes Modul virt_wifi auf einem Server ohne WLAN-Tests ist für sich schon auffällig, denn außer einem Angriff oder einem Test lädt es dort niemand. Mit einer Audit-Regel auf die Systemaufrufe init_module und finit_module protokollierst du jedes Laden von Modulen, auch das automatische. Kernel-Meldungen mit __pm_runtime_resume oder ethnl_ops_begin im Call Trace passen zu einem fehlgeschlagenen Versuch, und mit init_on_free beobachtete Popov eine Warnung aus kernel/workqueue.c. Auf Kubernetes-Nodes fallen zudem Prozesse in Containern auf, die unerwartet Namespaces anlegen, was Laufzeitwerkzeuge wie Falco oder Tetragon melden können.

Bei einem Verdacht nimmst du den Node aus dem Betrieb, mit kubectl cordon und kubectl drain, sicherst die Logs und ersetzt ihn durch einen neuen Node mit gepatchtem Kernel, statt ihn zu reparieren. Danach rotierst du alles, was der Node sehen konnte: die Secrets der Pods, die dort liefen, die Zertifikate des Kubelets und die Zugangsdaten der Instanzrolle.

Betreiberempfehlung

Akut handeln solltest du, wenn auf einem Host mit betroffenem Kernel fremder oder aus dem Netz erreichbarer Code läuft und unprivilegierte User Namespaces erlaubt sind. Das betrifft vor allem Kubernetes-Nodes unter Amazon Linux 2023 mit kernel oder kernel6.18 vor den genannten Versionen, auf denen Pods ohne seccomp laufen, und Build-Runner für fremde Pipelines. Dort spielst du den Kernel heute ein und startest neu oder rollst die Nodes mit neuem AMI durch. Bis dahin sperrst du virt_wifi über modprobe.d, was praktisch nichts kostet.

Beobachten genügt, wenn der Host unter Amazon Linux 2 oder mit kernel6.12 unter Amazon Linux 2023 läuft, das Modul fehlt oder User Namespaces bereits abgeschaltet sind. Auch dann gehört der Kernel ins nächste Wartungsfenster, weil sich Konfigurationen ändern und der öffentliche Exploit anderen als Vorlage dient.

Dass dieser Fix seit Mai bereitliegt und trotzdem erst jetzt Aufmerksamkeit bekommt, zeigt, wo die eigentliche Lücke im Betrieb sitzt. Kernel-Updates gehören automatisiert wie jedes andere Update. Renovate oder Dependabot schlagen neue AMI-Versionen, Node-Images und die Helm-Charts deiner Node-Verwaltung als Merge Request vor, die Pipeline rollt sie zuerst auf einen Staging-Cluster aus, und Karpenter oder Kured tauschen beziehungsweise starten die Nodes danach geordnet neu. Der Kernel aus dem Mai-Advisory wäre so nach Stunden im Cluster gewesen und nicht erst, als der Exploit im November erschien. Eine Karenzzeit für Routine-Updates schützt vor kompromittierten Releases, Sicherheitsupdates nimmst du davon aus. Bricht ein neuer Kernel etwas, etwa weil nach dem Sperren des Nachladens ein Modul fehlt, fällt das auf Staging auf und nicht auf dem Produktions-Node. Wer Nodes von Hand aktualisiert, schiebt den Neustart gern auf, in der Urlaubszeit auch um Wochen, und holt später einen großen Sprung über mehrere Kernel-Versionen nach, der deutlich riskanter ist als viele kleine.

Häufige Fragen zu CVE-2026-31695

Bin ich mit Amazon Linux 2 betroffen?+

Laut Amazon Linux Security Center nicht, alle Kernel von Amazon Linux 2 gelten als nicht betroffen. Unter Amazon Linux 2023 sind kernel und kernel6.18 betroffen und mit ALAS2023-2026-1681 und -1746 korrigiert, kernel6.12 gilt als nicht betroffen.

Schützt mich ein aktuelles Container-Image?+

Nein. Der Fehler sitzt im Kernel des Hosts, und Container-Images wie die von Wolfi bringen keinen Kernel mit. Du musst den Kernel auf dem Node aktualisieren und neu starten oder den Node mit einem neuen AMI ersetzen.

Reicht es, virt_wifi zu sperren?+

Für genau diese Lücke ja, sofern das Modul noch nicht geladen und nicht fest in den Kernel gebaut ist. Den Kernel-Fix ersetzt das trotzdem nicht, und gegen die nächste Lücke in einem anderen Treiber hilft nur das breitere Abschalten des Nachladens oder der User Namespaces.

Warum ist eine Lücke vom Mai jetzt wichtig?+

Weil Alexander Popov am 2. November gezeigt hat, dass sie zuverlässig zu Root führt, und seinen Weg ausführlich beschrieben hat. Der niedrige EPSS-Wert stammt aus der Zeit davor. Wer den Kernel aus dem Mai noch nicht eingespielt hat, sollte es jetzt tun.

Bricht user.max_user_namespaces=0 meinen Cluster?+

Auf einem Node, der Container nur über containerd als Root startet, in der Regel nicht. Rootless Builds mit Podman oder BuildKit, Browser-Sandboxes in Testjobs und Pods mit hostUsers: false brauchen User Namespaces aber, deshalb solltest du die Einstellung erst auf einem Test-Node ausprobieren.

Fazit

CVE-2026-31695 ist ein kleiner Fehler mit einer großen Lehre. Ein Verweis zwischen zwei Objekten, der die Lebensdauer seines Ziels nicht sichert, wird zum Use-after-free, sobald ein zweiter Pfad ihn im ungünstigen Moment liest, und dieser Pfad kam hier durch eine für sich harmlose Änderung in ethtool hinzu. Für den Betrieb wiegt die zweite Lehre schwerer. Erreichbar wurde der Fehler erst, weil ein Server einen Treiber nachlädt, den er nie braucht, und zwar für einen Benutzer, der in seinem eigenen Namespace Root spielen darf. Lücken dieser Art schließt du dauerhaft, indem du die Angriffsfläche des Kernels klein hältst: Module nur, wenn sie gebraucht werden, User Namespaces nur, wo sie gewollt sind, und seccomp für jeden Container. Und weil der Fix ein halbes Jahr vor dem Exploit kam, war jeder Betreiber mit automatisierten Kernel-Updates längst geschützt, als die Schlagzeilen erschienen.

Quellen

Ich prüfe deine Kubernetes-Nodes auf verwundbare Kernel, sperre ungenutzte Module und User Namespaces und setze seccomp für jeden Pod durch.

Kernel- und Node-Audits für EKS und k3s, Pod Security „restricted“ mit Admission-Policies, automatisierte AMI- und Kernel-Updates mit Renovate und geordnetem Node-Rollout.

Plattform-Betrieb statt Beratung auf Papier: Ich prüfe, patche und härte deine Infrastruktur laufend.

Über den Autor

Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freiberuflicher DevSecOps-Berater · OnlyOle Consulting

Programmiert seit 2002 – autodidaktisch gelernt, 2012 mit KO-Web selbständig gemacht. Über 100 Projekte, Fokus auf Security, Performance, Automatisierung und Qualität. Heute freiberuflich: DevSecOps-Beratung, Schulungen und Softwareentwicklung.