
CVE-2023-3269: Linux-Kernel Privilegieneskalations-Schwachstelle
(GitHub-CI-verifizierter Exploit)
Es wurde ein Fehler in der Handhabung der Stapelerweiterung im Linux-Kernel 6.1 bis 6.4 gefunden, auch bekannt als „Stack Rot“. Der Maple-Baum, der für die Verwaltung virtueller Speicherbereiche zuständig ist, kann Knotenersetzungen durchführen, ohne den MM-Schreib-Lock ordnungsgemäß zu erlangen, was zu Use-After-Free-Problemen führt. Ein nicht privilegierter lokaler Benutzer könnte diesen Fehler ausnutzen, um den Kernel zu kompromittieren und seine Rechte zu erweitern.
Da StackRot eine Schwachstelle im Linux-Kernel ist, die im Speicherverwaltungs-Subsystem gefunden wurde, betrifft sie fast alle Kernelkonfigurationen und erfordert nur minimale Fähigkeiten, um sie auszulösen. Es sei jedoch darauf hingewiesen, dass Maple-Knoten mittels RCU-Callbacks freigegeben werden, wodurch die tatsächliche Speicherfreigabe bis nach der RCU-Schonfrist verzögert wird. Folglich gilt die Ausnutzung dieser Schwachstelle als herausfordernd.
Nach meinem besten Wissen gibt es derzeit keine öffentlich verfügbaren Exploits, die auf Use-After-Free-by-RCU (UAFBR)-Fehler abzielen. Dies ist der erste Fall, bei dem nachgewiesen wurde, dass UAFBR-Fehler ausnutzbar sind, selbst ohne das Vorhandensein der Einstellungen CONFIG_PREEMPT oder CONFIG_SLAB_MERGE_DEFAULT. Bemerkenswerterweise wurde dieser Exploit erfolgreich in der Umgebung demonstriert, die von Google kCTF VRP (bzImage_upstream_6.1.25, config) bereitgestellt wurde.
Die StackRot-Schwachstelle ist im Linux-Kernel seit Version 6.1 vorhanden, als die VMA-Baumstruktur von Rot-Schwarz-Bäumen auf Maple-Bäume geändert wurde.
Immer wenn der Systemaufruf mmap() verwendet wird, um eine
Speicherabbildung einzurichten, erzeugt der Kernel eine Struktur namens
vm_area_struct, um den entsprechenden virtuellen Speicherbereich (VMA)
darzustellen. Diese Struktur speichert verschiedene Informationen, darunter
Flags, Eigenschaften und andere relevante Details zur Abbildung.```c
struct vm_area_struct {
long unsigned int vm_start; /* 0 8 /
long unsigned int vm_end; / 8 8 /
struct mm_struct * vm_mm; / 16 8 /
pgprot_t vm_page_prot; / 24 8 /
long unsigned int vm_flags; / 32 8 /
union {
struct {
struct rb_node rb attribute((aligned(8))); / 40 24 /
/ --- cacheline 1 boundary (64 bytes) --- /
long unsigned int rb_subtree_last; / 64 8 /
} attribute((aligned(8))) shared attribute((aligned(8))); / 40 32 /
struct anon_vma_name * anon_name; / 40 8 /
} attribute((aligned(8))); / 40 32 /
/ --- cacheline 1 boundary (64 bytes) was 8 bytes ago --- /
struct list_head anon_vma_chain; / 72 16 /
struct anon_vma * anon_vma; / 88 8 /
const struct vm_operations_struct * vm_ops; / 96 8 /
long unsigned int vm_pgoff; / 104 8 /
struct file * vm_file; / 112 8 /
void * vm_private_data; / 120 8 /
/ --- cacheline 2 boundary (128 bytes) --- /
atomic_long_t swap_readahead_info; / 128 8 /
struct vm_userfaultfd_ctx vm_userfaultfd_ctx; / 136 0 */
/* size: 136, cachelines: 3, members: 14 */
/* forced alignments: 1 */
/* last cacheline: 8 bytes */
} attribute((aligned(8)));
Anschließend benötigt der Kernel bei Seitenfehlern oder anderen speicherbezogenen Systemaufrufen eine schnelle Suche der VMA allein anhand der Adresse. Zuvor wurden die VMAs mithilfe von Rot-Schwarz-Bäumen verwaltet. Ab Linux-Kernel Version 6.1 fand jedoch die Migration zu Maple Trees statt. [Maple Trees][mt] sind RCU-sichere B-Baum-Datenstrukturen, die für die Speicherung nicht überlappender Bereiche optimiert sind. Dennoch erhöht ihre komplexe Natur die Komplexität des Codebestands und führt die StackRot-Sicherheitslücke ein.
[mt]: https://docs.kernel.org/6.4/core-api/maple_tree.html
Im Kern besteht ein Maple Tree aus Maple Nodes. Obwohl die Struktur des Baums komplex sein kann, ist es wichtig zu beachten, dass diese Komplexität nichts mit dem StackRot-Fehler zu tun hat. Daher wird im gesamten Artikel angenommen, dass der Maple Tree nur aus einem einzigen Node besteht, nämlich dem Wurzelknoten.
Dieser Wurzelknoten kann bis zu 16 Intervalle enthalten. Diese Intervalle können entweder eine Lücke darstellen oder auf eine VMA verweisen. Da auch Lücken als Intervalle zählen, sind alle Intervalle sequentiell verbunden, sodass innerhalb der Knotenstruktur nur 15 Endpunkte, auch Pivots genannt, benötigt werden. Beachten Sie, dass der linkste Endpunkt und der rechtste Endpunkt weggelassen werden, da sie vom übergeordneten Knoten abgerufen werden können.```c
struct maple_range_64 {
struct maple_pnode * parent; /* 0 8 */
long unsigned int pivot[15]; /* 8 120 */
/* --- cacheline 2 boundary (128 bytes) --- */
union {
void * slot[16]; /* 128 128 */
struct {
void * pad[15]; /* 128 120 */
/* --- cacheline 3 boundary (192 bytes) was 56 bytes ago --- */
struct maple_metadata meta; /* 248 2 */
}; /* 128 128 */
}; /* 128 128 */
/* size: 256, cachelines: 4, members: 3 */
};
Die maple_range_64-Struktur, wie oben gezeigt, stellt einen Maple-Knoten dar. Zusätzlich
zu den Drehpunkten werden die Slots verwendet, um auf die VMA-Struktur zu verweisen, wenn
der Knoten als Blattknoten fungiert, oder auf andere Maple-Knoten, wenn der Knoten
als innerer Knoten fungiert. Wenn ein Intervall einer Lücke entspricht, enthält der Slot
einfach einen NULL-Wert. Die Anordnung der Drehpunkte und Slots kann
wie unten dargestellt visualisiert werden:```
Slots -> | 0 | 1 | 2 | ... | 12 | 13 | 14 | 15 |
┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │ │ │ └─ Implied maximum
│ │ │ │ │ │ │ └─ Pivot 14
│ │ │ │ │ │ └─ Pivot 13
│ │ │ │ │ └─ Pivot 12
│ │ │ │ └─ Pivot 11
│ │ │ └─ Pivot 2
│ │ └─ Pivot 1
│ └─ Pivot 0
└─ Implied minimum
Bezüglich gleichzeitiger Modifikationen auferlegt der Maple Tree eine spezifische Einschränkung: Ein exklusiver Lock muss von Schreibenden gehalten werden (*Regel W*). Im Fall des VMA-Baums entspricht der exklusive Lock dem MM-Schreib-Lock. Für Lesende stehen zwei Optionen zur Verfügung. Die erste Option besteht darin, den MM-Lese-Lock zu halten (*Regel A1*), was dazu führt, dass der Schreibende durch den MM-Lese-Schreib-Lock blockiert wird. Alternativ besteht die zweite Option darin, den RCU-kritischen Abschnitt zu betreten (*Regel A2*). Dadurch wird der Schreibende nicht blockiert, und Lesende können ihre Operationen fortsetzen, da der Maple Tree RCU-sicher ist. Während die meisten bestehenden VMA-Zugriffe die erste Option (d.h. Regel A1) wählen, wird Regel A2 in einigen leistungskritischen Szenarien verwendet, wie z.B. bei locklosen Seitenfehlern.
Es gibt jedoch einen zusätzlichen Aspekt, der besondere Aufmerksamkeit erfordert und die Stapelerweiterung betrifft. Der Stack repräsentiert einen Speicherbereich, der mit dem Flag MAP_GROWSDOWN gemappt wird, was eine automatische Erweiterung anzeigt, wenn eine Adresse unterhalb der Region zugegriffen wird. In solchen Fällen werden die Startadresse der entsprechenden VMA sowie das zugehörige Intervall im Maple Tree angepasst. Bemerkenswerterweise werden diese Anpassungen vorgenommen, ohne den MM-Schreib-Lock zu halten.```c
static inline
void do_user_addr_fault(struct pt_regs *regs,
unsigned long error_code,
unsigned long address)
{
// ...
if (unlikely(!mmap_read_trylock(mm))) {
// ...
}
// ...
if (unlikely(expand_stack(vma, address))) {
// ...
}
// ...
}
Typischerweise besteht eine Lücke zwischen dem Stack-VMA und seinem benachbarten VMA, da der Kernel einen Stack Guard erzwingt. In diesem Szenario muss beim Erweitern des Stacks nur der Pivot-Wert im maple node aktualisiert werden, ein Vorgang, der atomar durchgeführt werden kann. Wenn das benachbarte VMA jedoch ebenfalls das Flag MAP_GROWSDOWN besitzt, wird kein Stack Guard erzwungen.```c int expand_downwards(struct vm_area_struct *vma, unsigned long address) { // ...
if (prev) {
if (!(prev->vm_flags & VM_GROWSDOWN) &&
vma_is_accessible(prev) &&
(address - prev->vm_end < stack_guard_gap))
return -ENOMEM;
}
// ...
}
Als Ergebnis kann die Stapelerweiterung die Lücke beseitigen. In solchen Situationen muss das Lückenintervall innerhalb des Maple-Knotens entfernt werden. Da der Maple-Baum RCU-sicher ist, ist ein direktes Überschreiben des Knotens nicht möglich. Stattdessen wird ein neuer Knoten erstellt, was einen Knotenersatz auslöst, und der alte Knoten wird anschließend mittels eines RCU-Callbacks zerstört.```c
static inline void mas_wr_modify(struct ma_wr_state *wr_mas)
{
// ...
if ((wr_mas->offset_end - mas->offset <= 1) &&
mas_wr_slot_store(wr_mas)) // <-- in-place update
return;
else if (mas_wr_node_store(wr_mas)) // <-- node replacement
return;
// ...
}
Der RCU-Callback wird erst aufgerufen, nachdem alle bereits bestehenden RCU-kritischen Abschnitte abgeschlossen sind. Das Problem tritt jedoch beim Zugriff auf VMAs auf, da nur die MM-Lesesperre gehalten wird und sie nicht in den RCU-kritischen Abschnitt eintritt (gemäß Regel A1). Infolgedessen könnte der Callback theoretisch jederzeit aufgerufen werden, was zur Freigabe des alten Maple-Knotens führt. Allerdings könnten Zeiger auf den alten Knoten bereits abgerufen worden sein, was zu einem Use-After-Free-Fehler führt, wenn später versucht wird, darauf zuzugreifen.
Der Backtrace, bei dem der Use-After-Free (UAF) auftritt, wird unten gezeigt:```
mm_read_lock() mm_read_lock() expand_stack() find_vma_prev() expand_downwards() mas_walk() mas_store_prealloc() mas_state_walk() mas_wr_story_entry() mas_start() mas_wr_modify() mas_root() mas_wr_node_store() node = rcu_dereference_check() mas_replace() [ The node pointer is recorded ] mas_free() ma_free_rcu() call_rcu(&mt_free_rcu) [ The node is dead ] mm_read_unlock()
[ Wait for the next RCU grace period.. ] rcu_do_batch() mas_prev() mt_free_rcu() mas_prev_entry() kmem_cache_free() mas_prev_nentry() [ The node is freed ] mas_slot() mt_slot() rcu_dereference_check(node->..) [ UAF occurs here ] mm_read_unlock()
## Fix
I reported this vulnerability to the Linux kernel security team on June 15th.
Following that, the process of addressing this bug was led by Linus Torvalds.
Given its complexity, it took nearly two weeks to develop a set of patches that
received consensus.
On June 28th, during the merge window for Linux kernel 6.5, the fix was merged
into Linus' tree. Linus provided a [comprehensive merge message][fix] to
elucidate the patch series from a technical perspective.
[fix]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9471f1f2f50282b9e8f59198ec6bb738b4ccc009
These patches were subsequently backported to stable kernels ([6.1.37][6.1],
[6.3.11][6.3], and [6.4.1][6.4]), effectively resolving the "Stack Rot" bug on
July 1st.
[6.1]: https://lore.kernel.org/stable/2023070133-create-stainless-9a8c@gregkh/T/
[6.3]: https://lore.kernel.org/stable/2023070146-endearing-bounding-d21a@gregkh/T/
[6.4]: https://lore.kernel.org/stable/2023070140-eldercare-landlord-133c@gregkh/T/
## Exploit
The exploit primarily focuses on the Google kCTF challenge, specifically when
neither CONFIG_PREEMPT nor CONFIG_SLAB_MERGE_DEFAULT is set. To exploit
StackRot, the most important task is to locate a VMA iteration that fulfills
the following criteria:
1. The iteration's timing can be controlled. This control allows us to ensure
that the RCU grace period concludes during the VMA iteration.
2. The iteration retrieves specific information from the VMA structure, and
returns the information to the userspace. This feature enables us to
exploit the UAF vulnerability of the maple node to leak some kernel
addresses.
3. The iteration invokes certain function pointers in the VMA structure. This
particular capability allows us to exploit the UAF of the maple node to
control the kernel-mode program counter (PC).
The chosen VMA iteration is the iteration responsible for generating the
contents of `/proc/[pid]/maps`. The following sections will show how this
iteration satisfies the above criteria.
### Step 0: From UAFBR to UAF
During any VMA iteration, the reference to the root node of the VMA tree is
obtained, and the iteration proceeds through its slots. Thus, by triggering
stack expansion in another thread on a separate CPU during the VMA iteration,
the node replacement can be concurrently initiated. At this point, accessing
the old node is considered a use-after-free-by-RCU (UAFBR) situation. However,
actual issues arise only when the old node is truly freed, which occurs in the
RCU callback.
This presents two challenges: (i) determining when the old node is freed and
(ii) ensuring that the VMA iteration does not complete before the old node is
freed.
The first question is relatively straightforward. In the kernel, the
`synchronize_rcu()` function can be employed to wait until the RCU grace period
concludes, ensuring that all pre-existing RCU callbacks have been invoked. In
userspace, system calls that ultimately call `synchronize_rcu()` can be
utilized for the same purpose. Thus, when such system calls terminate, it is
known that the old node has been freed. Notably, there is a system call,
`membarrier(MEMBARRIER_CMD_GLOBAL, 0, -1)`, that solely invokes
`synchronize_rcu()`.```c
SYSCALL_DEFINE3(membarrier, int, cmd, unsigned int, flags, int, cpu_id)
{
// ...
switch (cmd) {
// ...
case MEMBARRIER_CMD_GLOBAL:
/* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
if (tick_nohz_full_enabled())
return -EINVAL;
if (num_online_cpus() > 1)
synchronize_rcu();
return 0;
// ...
}
}
Die zweite Frage erfordert weitere Überlegung. Mehrere potenzielle Lösungen sind wie folgt:
jiffies_till_first_fqs (standardmäßig mehrere Jiffies) überschreitet, wird ein
Inter-Processor-Interrupt (IPI) an die Opfer-CPU gesendet und
eine freiwillige Verdrängung ausgelöst. Im Falle einer VMA-Iteration kann die
freiwillige Verdrängung die RCU-Schonfrist enden lassen und den Maple-Knoten freigeben,
wodurch UAFBR effektiv in ein echtes Use-after-Free (UAF)-Szenario umgewandelt wird.Eine wesentliche Beobachtung ist, dass während der VMA-Iteration für
/proc/[pid]/maps der vollständige Dateipfad für dateiabgebildete Speicherbereiche
generiert wird. Obwohl der Verzeichnisname normalerweise auf maximal
255 Zeichen beschränkt ist, gibt es keine Begrenzung für die Verzeichnistiefe. Das bedeutet,
dass durch das Erstellen einer Datei mit einer extrem großen Verzeichnistiefe und dem Einrichten einer
Speicherabbildung für diese Datei der Zugriff auf /proc/[pid]/maps während der
VMA-Iteration eine beträchtliche Zeit in Anspruch nehmen kann. Folglich ermöglicht diese
verlängerte Dauer die Möglichkeit, die RCU-Schonfrist zu beenden
und die UAF-Primitive zu erhalten.```c
static void
show_map_vma(struct seq_file *m, struct vm_area_struct *vma)
{
// ...
/*
* Print the dentry name for named mappings, and a
* special [heap] marker for the heap:
*/
if (file) {
seq_pad(m, ' ');
/*
* If user named this anon shared memory via
* prctl(PR_SET_VMA ..., use the provided name.
*/
if (anon_name)
seq_printf(m, "[anon_shmem:%s]", anon_name->name);
else
seq_file_path(m, file, "\n");
goto done;
}
// ...
}
Dieser Schritt wird in der folgenden Abbildung veranschaulicht:

### Schritt 1: Von Slab-UAF zu Page-UAF
Nun, da UAF innerhalb eines Slabs funktioniert. Wenn CONFIG_SLAB_MERGE_DEFAULT aktiviert ist und der Slab der Maple-Knoten mit kmalloc-256 zusammengeführt wird, können die Inhalte des alten Knotens kontrolliert werden, indem eine neue Struktur aus kmalloc-256 zugewiesen und mit Benutzerdaten gefüllt wird. Wenn CONFIG_SLAB_MERGE_DEFAULT jedoch nicht gesetzt ist, ist ein alternativer Ansatz erforderlich. In diesem Fall muss die Seite des freigegebenen Knotens an den Seitenzuweiser zurückgegeben werden, sodass der alte Knoten kontrolliert werden kann, indem eine neue Seite zugewiesen und entsprechend gefüllt wird.
Denken Sie daran, dass der VMA-Baum nur einen Knoten enthält. Daher werden durch Nutzung von `fork()`/`clone()` mehrere VMA-Bäume und eine gleiche Anzahl von Maple-Knoten erzeugt. Angenommen, ein Slab umfasst M Maple-Knoten, und ein Knoten pro M Knoten wird beibehalten, während alle anderen Knoten über `exit()` freigegeben werden, werden die verbleibenden Knoten zu den einzigen Knoten in ihren jeweiligen Slabs. Zunächst befinden sich diese Slabs in der Teilliste der CPU. Wenn die Teilliste ihre Kapazität erreicht, werden die Slabs zurück in die Teilliste des entsprechenden NUMA-Knotens gespült.
Wenn der letzte Maple-Knoten innerhalb eines Slabs freigegeben wird, wird der Slab leer. Wenn sich dieser Slab in der Teilliste eines NUMA-Knotens befindet und die Teilliste dieses bestimmten NUMA-Knotens bereits ihre maximale Kapazität erreicht hat, wird die Seite sofort an den Seitenzuweiser zurückgegeben. Folglich verwandelt sich der Slab-UAF in ein Page-UAF-Szenario. Die Inhalte der freigegebenen Seite können manipuliert werden, indem einige Daten über `msgsnd()` gesendet werden, das elastische Objekte zuweist und diese direkt mit den bereitgestellten Benutzerdaten füllt.```c
static void __slab_free(struct kmem_cache *s, struct slab *slab,
void *head, void *tail, int cnt,
unsigned long addr)
{
// ...
if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))
goto slab_empty;
// ...
return;
slab_empty:
// ...
discard_slab(s, slab);
}
Die Anzahl der Maple-Knoten pro Slab, M, hängt von der Anzahl der CPUs ab. Die Exploit-Implementierung betrachtet eine Situation mit zwei CPUs und nimmt daher 16 als Wert von M an, wie in der folgenden Abbildung dargestellt:

Nach Erlangen der Kontrolle über den Maple-Knoten wird es möglich, die Adressen nachfolgender VMAs zu manipulieren, die später iteriert werden. Da die gezielte Iteration darauf abzielt, /proc/self/maps zu generieren, werden bestimmte VMA-Informationen, wie die Start- und Endadressen, die sich innerhalb der VMA-Struktur befinden, an den Benutzerraum zurückgegeben.
Es ergibt sich jedoch eine Herausforderung: Die Adresse einer VMA-Struktur im Maple-Knoten kann nur dann korrekt gesetzt werden, wenn einige Adressen bereits bekannt sind. Glücklicherweise dient CVE-2023-0597 direkt diesem Zweck. Laut CVE-2023-0597 ist die Adresse von cpu_entry_area nicht randomisiert. Obwohl diese Schwachstelle in Linux 6.2 gepatcht wurde, wurde sie zum Zeitpunkt des Schreibens nicht auf ältere stabile Kernel zurückportiert. Folglich wird durch Überschreiben der Adresse der VMA-Struktur mit der des letzten IDT-Eintrags der Eintrag, der die Adresse von asm_sysvec_spurious_apic_interrupt enthält, direkt geleakt, wodurch die Basisadressen des Kernel-Codes und der Kernel-Daten preisgegeben werden.

Die zuvor besprochene Methode kann wiederholt verwendet werden, um inkrementell weitere Adressen aus dem Kernel-Datenbereich preiszugeben. Beispielsweise zeigt der init_task.tasks.prev-Zeiger innerhalb des Datenbereichs auf die task_struct-Struktur der zuletzt erstellten Aufgabe, die ohne Frage auf dem Heap allokiert ist.

Wenn alle neu erstellten Aufgaben beendet werden, werden ihre task_struct-Strukturen anschließend freigegeben. Wenn die Anzahl dieser Aufgaben groß genug ist, können die entsprechenden Seiten an den Page-Allokator zurückgegeben werden. Dies ermöglicht die Möglichkeit, diese Seiten neu zu allozieren und mit Benutzerdaten zu füllen. Beachten Sie jedoch, dass die freigegebenen Seiten im Allgemeinen zur per-CPU-Seitenliste (PCP) gehören. Für Seiten in der PCP-Liste können sie ausschließlich in derselben Seitenordnung neu alloziiert werden. Folglich erfüllt das bloße Zuordnen neuer Seiten in den Benutzerraum, das nur Ordnung-0-Seiten vom Page-Allokator erfordert, nicht die Ziele.
Dennoch fordert der msgsnd-Systemaufruf Speicherblöcke über kmalloc an und füllt diese Blöcke mit benutzerdefinierten Daten. Wenn der kmalloc-Cache erschöpft ist, wird er Seiten vom Page-Allokator in einer bestimmten Ordnung anfordern. Wenn die Nachrichtengröße genau angepasst ist, wird die gewünschte Ordnung erreicht. Somit wird die Seite, deren Adresse zuvor geleakt wurde, neu alloziiert. Dadurch wird es möglich, eine Seite mit bekannter Adresse und vom Benutzer manipulierten Daten zu erhalten.
Es ist nun möglich, die VMA-Struktur in der adressbekannten Seite zu fälschen und den Funktionszeiger vma->vm_ops->name zu kontrollieren. Der nächste Schritt besteht darin, geeignete Gadgets zu finden, um Container zu verlassen und Root-Rechte zu erlangen.```c
static void
show_map_vma(struct seq_file *m, struct vm_area_struct *vma)
{
// ...
if (vma->vm_ops && vma->vm_ops->name) {
name = vma->vm_ops->name(vma);
if (name)
goto done;
}
// ...
}

Die Gadget-Konstruktionen sind wie folgt:
1. Stack-Pivot: `movq %rbx, %rsi; movq %rbp, %rdi; call
__x86_indirect_thunk_r13` -> `pushq %rsi; jmp 46(%rsi)` -> `popq %rsp; ret`
-> `popq %rsp; ret`, wobei %rdi, %rbx und %r13 _anfänglich_ auf
benutzerkontrollierte Daten zeigen.
2. Root-Rechte erlangen: `popq %rdi; ret` -> `prepare_kernel_cred` -> `popq
%rdi; ret` -> `movq %rax, (%rdi); ret`, wobei %rdi _nun_ auf die
Stapelspitze zeigt; `popq %rdi; ret` -> `commit_creds`, was effektiv
`commit_creds(prepare_kernel_cred(&init_task))` ausführt.
3. Container-Escape: `popq %rdi; ret` -> `find_task_by_vpid` -> `popq %rdi;
ret` -> `movq %rax, (%rdi); ret`, wobei %rdi _nun_ auf die Stapelspitze
zeigt; `popq %rdi; ret` -> `popq %rsi; ret` -> `switch_task_namespaces`,
was effektiv `switch_task_namespaces(find_task_by_vpid(1),
&init_nsproxy)` ausführt.
4. mm entsperren: `popq %rax; ret` -> `movq %rbp, %rdi; call
__x86_indirect_thunk_rax`, wobei %rbp auf die ursprüngliche seq_file
zeigt; `popq %rax; ret` -> `m_stop`, was effektiv `m_stop(seq_file, ..)`
ausführt.
5. Rückkehr in den Userspace: verwende
`swapgs_restore_regs_and_return_to_usermode` und rufe `execve()` auf, um
die Shell zu erhalten.
Schließlich verwende `nsenter --mount=/proc/1/ns/mnt`, um den Mount-Namespace
wiederherzustellen und erhalte die Flagge mittels `cat /flag/flag`.
### Quellcode
Der vollständige Exploit-Quellcode ist [hier](https://github.com/lrh2000/stackrot/blob/HEAD/exp) verfügbar. Weitere Details entnehmen Sie bitte der README-Datei.