Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
StackRot — CVE-2023-3269: vulnerabilità di escalation dei privilegi nel kernel Linux | Kitploit
Strumenti/GitHubGitHub/lrh2000/stackrot
Escalation di PrivilegiAnalisi delle VulnerabilitàExploitCTFBinary Exploitation
GitHublrh2000/stackrot

StackRot

CVE-2023-3269: vulnerabilità di escalation dei privilegi nel kernel Linux

Vedi Repository
499375 mesi faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

StackRot (CVE-2023-3269): Vulnerabilità di escalation dei privilegi nel kernel Linux

GitHub CI (Exploit verificato tramite GitHub-CI)

Demo

È stato riscontrato un difetto nella gestione dell'espansione dello stack nel kernel Linux dalla versione 6.1 alla 6.4, noto come "Stack Rot". L'albero degli aceri (maple tree), responsabile della gestione delle aree di memoria virtuale, può subire la sostituzione di nodi senza acquisire correttamente il lock MM in scrittura, portando a problemi di use-after-free. Un utente locale senza privilegi potrebbe sfruttare questo difetto per compromettere il kernel ed elevare i propri privilegi.

Poiché StackRot è una vulnerabilità del kernel Linux individuata nel sottosistema di gestione della memoria, colpisce quasi tutte le configurazioni del kernel e richiede capacità minime per essere innescata. Va tuttavia notato che i nodi dell'albero degli aceri vengono liberati tramite callback RCU, ritardando la effettiva deallocazione della memoria fino al termine del periodo di grazia RCU. Di conseguenza, lo sfruttamento di questa vulnerabilità è considerato complesso.

Al meglio delle mie conoscenze, attualmente non esistono exploit pubblicamente disponibili che prendano di mira bug di use-after-free-by-RCU (UAFBR). Questo rappresenta il primo caso in cui bug UAFBR si sono dimostrati sfruttabili, anche in assenza delle impostazioni CONFIG_PREEMPT o CONFIG_SLAB_MERGE_DEFAULT. In particolare, questo exploit è stato dimostrato con successo nell'ambiente fornito da Google kCTF VRP (bzImage_upstream_6.1.25, config).

La vulnerabilità StackRot è presente nel kernel Linux dalla versione 6.1, quando la struttura dell'albero VMA è stata modificata da alberi rosso-neri ad alberi degli aceri.

Contesto

Ogni volta che la chiamata di sistema mmap() viene utilizzata per stabilire un mapping di memoria, il kernel crea una struttura chiamata vm_area_struct per rappresentare la corrispondente area di memoria virtuale (VMA). Questa struttura memorizza varie informazioni tra cui flag, proprietà e altri dettagli pertinenti relativi al mapping.```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 */

root@kitploit:~
    /* size: 136, cachelines: 3, members: 14 */
    /* forced alignments: 1 */
    /* last cacheline: 8 bytes */

} attribute((aligned(8)));

root@kitploit:~
Successivamente, quando il kernel incontra page fault o altre chiamate di sistema
legate alla memoria, necessita di una ricerca rapida del VMA basata esclusivamente sull'indirizzo.
In precedenza, i VMA erano gestiti tramite alberi rosso-neri. Tuttavia, a partire
dalla versione 6.1 del kernel Linux, è avvenuta la migrazione agli alberi maple. [Maple
trees][mt] sono strutture dati ad albero B RCU-safe ottimizzate per memorizzare
intervalli non sovrapposti. Tuttavia, la loro natura intricata aggiunge complessità
al codebase e introduce la vulnerabilità StackRot.

 [mt]: https://docs.kernel.org/6.4/core-api/maple_tree.html

Alla base, un albero maple è composto da nodi maple. Sebbene la struttura dell'albero
possa essere complessa, è importante notare che questa complessità non ha nulla a che fare
con il bug StackRot. Pertanto, in questo articolo, si assume che
l'albero maple sia costituito da un solo nodo, cioè il nodo radice.

Questo nodo radice può contenere fino a 16 intervalli. Questi intervalli possono
rappresentare un gap o puntare a un VMA. Poiché anche i gap contano come intervalli, tutti
gli intervalli sono collegati sequenzialmente, risultando nella necessità di soli 15
punti finali, noti anche come pivot, all'interno della struttura del nodo. Si noti che
il punto finale più a sinistra e il punto finale più a destra sono omessi, poiché possono essere
recuperati dal nodo padre.```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 */
};

La struttura maple_range_64, come mostrato sopra, rappresenta un nodo maple. In aggiunta ai pivot, gli slot vengono utilizzati per fare riferimento alla struttura VMA quando il nodo funge da nodo foglia, o ad altri nodi maple quando il nodo funge da nodo interno. Se un intervallo corrisponde a un gap, lo slot conterrà semplicemente un valore NULL. La disposizione dei punti pivot e degli slot può essere visualizzata come illustrato di seguito:``` 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

root@kitploit:~
Per quanto riguarda la modifica concorrente, l'albero maple impone una restrizione specifica, ovvero che un blocco esclusivo deve essere detenuto dagli scrittori (*Rule W*). Nel caso dell'albero VMA, il blocco esclusivo corrisponde al blocco di scrittura MM. Per quanto riguarda i lettori, sono disponibili due opzioni. La prima opzione prevede di mantenere il blocco di lettura MM (*Rule A1*), il che comporta il blocco dello scrittore da parte del blocco di lettura-scrittura MM. In alternativa, la seconda opzione è entrare nella sezione critica RCU (*Rule A2*). In questo modo, lo scrittore non viene bloccato e i lettori possono continuare le loro operazioni poiché l'albero maple è RCU-safe. Mentre la maggior parte degli accessi VMA esistenti sceglie la prima opzione (cioè Rule A1), la Rule A2 viene impiegata in alcuni scenari critici per le prestazioni, come i page fault senza blocco.

Tuttavia, c'è un aspetto aggiuntivo che richiede particolare attenzione, relativo all'espansione dello stack. Lo stack rappresenta un'area di memoria mappata con il flag MAP_GROWSDOWN, che indica l'espansione automatica quando si accede a un indirizzo al di sotto della regione. In tali casi, l'indirizzo di inizio del corrispondente VMA viene aggiustato, così come l'intervallo associato all'interno dell'albero maple. È importante notare che questi aggiustamenti vengono effettuati senza mantenere il blocco di scrittura MM.```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))) {
		// ...
	}

	// ...
}

Tipicamente, esiste un gap tra la VMA dello stack e la VMA vicina, poiché il kernel impone una stack guard. In questo scenario, quando si espande lo stack, solo il valore pivot nel maple node necessita di essere aggiornato, un processo che può essere eseguito atomicamente. Tuttavia, se la VMA vicina possiede anch'essa il flag MAP_GROWSDOWN, non viene imposta alcuna stack guard.```c int expand_downwards(struct vm_area_struct *vma, unsigned long address) { // ...

root@kitploit:~
if (prev) {
	if (!(prev->vm_flags & VM_GROWSDOWN) &&
	    vma_is_accessible(prev) &&
	    (address - prev->vm_end < stack_guard_gap))
		return -ENOMEM;
}

// ...

}

root@kitploit:~
Di conseguenza, l'espansione dello stack può eliminare il divario. In tali situazioni, l'intervallo di gap all'interno del nodo maple deve essere rimosso. Poiché l'albero maple è RCU-safe, non è possibile sovrascrivere il nodo in-place. Invece, viene creato un nuovo nodo, che innesca la sostituzione del nodo, e il vecchio nodo viene successivamente distrutto utilizzando un callback RCU.```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;

	// ...
}

Il callback RCU viene invocato solo dopo che tutte le sezioni critiche RCU preesistenti sono state concluse. Tuttavia, il problema si presenta quando si accede alle VMA, poiché viene mantenuto solo il lock di lettura MM, e non si entra nella sezione critica RCU (secondo la Regola A1). Di conseguenza, in teoria, il callback potrebbe essere invocato in qualsiasi momento, portando alla liberazione del vecchio nodo maple. Tuttavia, i puntatori al vecchio nodo potrebbero essere già stati recuperati, causando un bug use-after-free quando si tenta di accedervi successivamente.

Di seguito è mostrato il backtrace dove si verifica il bug use-after-free (UAF):```

  • CPU 0 - - CPU 1 -

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()

root@kitploit:~
## Correzione

Ho segnalato questa vulnerabilità al team di sicurezza del kernel Linux il 15 giugno.
Successivamente, il processo di risoluzione di questo bug è stato guidato da Linus Torvalds.
Data la sua complessità, ci sono volute quasi due settimane per sviluppare una serie di patch che
hanno ricevuto il consenso.

Il 28 giugno, durante la finestra di unione per il kernel Linux 6.5, la correzione è stata integrata
nell'albero di Linus. Linus ha fornito un [messaggio di merge completo][fix] per
chiarire la serie di patch da un punto di vista tecnico.

 [fix]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9471f1f2f50282b9e8f59198ec6bb738b4ccc009

Queste patch sono state successivamente backportate ai kernel stabili ([6.1.37][6.1],
[6.3.11][6.3] e [6.4.1][6.4]), risolvendo efficacemente il bug "Stack Rot" il
1° luglio.

 [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

L'exploit si concentra principalmente sulla sfida Google kCTF, in particolare quando
né CONFIG_PREEMPT né CONFIG_SLAB_MERGE_DEFAULT sono impostati. Per sfruttare
StackRot, il compito più importante è individuare un'iterazione VMA che soddisfi
i seguenti criteri:
 1. I tempi dell'iterazione possono essere controllati. Questo controllo ci permette di assicurare
    che il periodo di grazia RCU termini durante l'iterazione VMA.
 2. L'iterazione recupera informazioni specifiche dalla struttura VMA e
    restituisce tali informazioni allo spazio utente. Questa caratteristica ci permette di
    sfruttare la vulnerabilità UAF del nodo maple per perdere alcuni indirizzi del kernel.
 3. L'iterazione invoca alcuni puntatori a funzione nella struttura VMA. Questa
    capacità particolare ci permette di sfruttare la UAF del nodo maple per controllare il
    contatore di programma (PC) in modalità kernel.

L'iterazione VMA scelta è l'iterazione responsabile della generazione del
contenuto di `/proc/[pid]/maps`. Le sezioni seguenti mostreranno come questa
iterazione soddisfi i criteri sopra.

### Passo 0: Da UAFBR a UAF

Durante qualsiasi iterazione VMA, si ottiene il riferimento al nodo radice dell'albero VMA e
l'iterazione procede attraverso i suoi slot. Quindi, attivando l'espansione dello stack in un altro thread su una CPU separata durante l'iterazione VMA,
la sostituzione del nodo può essere avviata concorrentemente. A questo punto, l'accesso
al vecchio nodo è considerato una situazione di use-after-free-by-RCU (UAFBR). Tuttavia,
i problemi veri e propri si presentano solo quando il vecchio nodo viene effettivamente liberato,
cosa che avviene nella callback RCU.

Ciò presenta due sfide: (i) determinare quando il vecchio nodo viene liberato e
(ii) assicurarsi che l'iterazione VMA non termini prima che il vecchio nodo sia liberato.

La prima domanda è relativamente semplice. Nel kernel, la funzione
`synchronize_rcu()` può essere utilizzata per attendere fino alla conclusione del periodo di grazia
RCU, assicurando che tutte le callback RCU preesistenti siano state invocate. Nello
spazio utente, le chiamate di sistema che alla fine chiamano `synchronize_rcu()` possono essere
utilizzate allo stesso scopo. Pertanto, quando tali chiamate di sistema terminano, si sa
che il vecchio nodo è stato liberato. In particolare, esiste una chiamata di sistema,
`membarrier(MEMBARRIER_CMD_GLOBAL, 0, -1)`, che invoca solo `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;
	// ...
	}
}

La seconda questione richiede ulteriore considerazione. Diverse potenziali soluzioni sono le seguenti:

  1. Il task di iterazione viene interrotto, il periodo di grazia RCU termina e l'iterazione riprende l'esecuzione. Tuttavia, questo approccio è inefficace se CONFIG_PREEMPT non è impostato.
  2. Il task di iterazione entra in uno stato di sospensione (ad es., in attesa di I/O), il periodo di grazia RCU termina e l'iterazione continua. Attualmente, non sono a conoscenza di alcuna iterazione VMA che soddisfi questo requisito e possa essere sfruttata per far trapelare indirizzi del kernel e controllare il program counter (PC). Potrebbe esistere, ma è necessaria un'indagine approfondita.
  3. Il task di iterazione subisce un'interruzione (ad es., interrupt del timer), durante la quale il periodo di grazia RCU si conclude. È possibile utilizzare timerfd per creare più timer hardware che, allo scadere durante l'iterazione VMA, possono attivare un interrupt di lunga durata. Tuttavia, questo approccio non è praticabile perché il gestore dell'interrupt opera con gli interrupt disabilitati e se una CPU non può gestire gli interrupt inter-processore (IPI), il periodo di grazia RCU non terminerà.
  4. Il task di iterazione viene deliberatamente prolungato, permettendo al periodo di grazia RCU di scadere. Questa è la soluzione scelta. Se l'attuale periodo di grazia RCU supera jiffies_till_first_fqs (impostato predefinitamente a diversi jiffies), un interrupt inter-processore (IPI) verrà inviato alla CPU vittima e innescherà una prelazione volontaria. Nel caso dell'iterazione VMA, la prelazione volontaria può far concludere il periodo di grazia RCU e liberare il nodo maple, convertendo efficacemente UAFBR in un autentico scenario use-after-free (UAF).

Un'osservazione significativa è che durante l'iterazione VMA per /proc/[pid]/maps, viene generato l'intero percorso del file per le regioni di memoria mappate da file. Sebbene il nome della directory sia tipicamente limitato a un massimo di 255 caratteri, non esiste alcuna limitazione sulla profondità della directory. Ciò significa che creando un file con una profondità di directory estremamente grande e stabilendo un mapping di memoria per questo file, l'accesso a /proc/[pid]/maps può richiedere una quantità di tempo considerevole durante l'iterazione VMA. Di conseguenza, questa durata estesa consente la possibilità di concludere il periodo di grazia RCU e acquisire il primitivo UAF.```c static void show_map_vma(struct seq_file *m, struct vm_area_struct *vma) { // ...

root@kitploit:~
/*
 * 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;
}

// ...

}

root@kitploit:~
Questo passaggio è illustrato nella figura seguente:

![Step 0: From UAFBR to UAF](https://assets.kitploit.com/production/public/readmes/28620/6791d2c105f1fc504247b668317c7d23c3141c92456dab2fb2e54f7f70e16e15.png)

### Passaggio 1: Da slab UAF a page UAF

Ora che UAF funziona all'interno di uno slab. Se CONFIG_SLAB_MERGE_DEFAULT è abilitato e lo slab dei nodi maple si unisce con kmalloc-256, i contenuti all'interno del vecchio nodo possono essere controllati allocando una nuova struttura da kmalloc-256 e riempiendola con dati dallo spazio utente. Tuttavia, se CONFIG_SLAB_MERGE_DEFAULT non è impostato, è necessario un approccio alternativo. In questo caso, è necessario restituire la pagina del nodo liberato all'allocatore di pagine, permettendo di controllare il vecchio nodo allocando una nuova pagina e riempiendola di conseguenza.

Ricorda che l'albero VMA conterrà un solo nodo. Quindi, utilizzando `fork()`/`clone()`, vengono generati multipli alberi VMA e un uguale numero di nodi maple. Supponendo che uno slab comprenda M nodi maple, e che un nodo per M nodi venga mantenuto mentre tutti gli altri nodi vengono liberati tramite `exit()`, i nodi rimanenti diventano gli unici nodi all'interno dei rispettivi slab. Inizialmente, questi slab risiedono nella lista parziale della CPU. Quando la lista parziale raggiunge la sua capacità, gli slab vengono riscaricati nella lista parziale del corrispondente nodo NUMA.

Se l'ultimo nodo maple all'interno di uno slab viene liberato, lo slab diventa vuoto. Se questo slab risiede nella lista parziale di un nodo NUMA, e la lista parziale di quel particolare nodo NUMA è già alla massima capacità, la pagina viene immediatamente restituita all'allocatore di pagine. Di conseguenza, lo slab UAF si trasforma in uno scenario di page UAF. I contenuti all'interno della pagina liberata possono essere manipolati inviando alcuni dati tramite `msgsnd()`, che alloca oggetti elastici e li popola direttamente con i dati utente forniti.```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);
}

Il numero di nodi maple per slab, M, dipende dal numero di CPU. L'implementazione dell'exploit considera una situazione con due CPU e quindi assume 16 come valore di M, come illustrato nella figura seguente:

Passo 1: Da slab UAF a page UAF

Passo 2: Da UAF a perdita di indirizzi

Una volta ottenuto il controllo del nodo maple, diventa possibile manipolare gli indirizzi dei successivi VMA che verranno successivamente iterati. Poiché l'iterazione mirata è volta a generare /proc/self/maps, alcune informazioni VMA, come gli indirizzi di inizio e fine, che risiedono all'interno della struttura VMA, vengono restituite allo spazio utente.

Tuttavia, sorge una sfida: l'indirizzo di una struttura VMA nel nodo maple può essere impostato correttamente solo se alcuni indirizzi sono già noti. Fortunatamente, CVE-2023-0597 serve direttamente a questo scopo. Secondo CVE-2023-0597, l'indirizzo di cpu_entry_area non è casuale. Sebbene questa vulnerabilità sia stata corretta in Linux 6.2, non è stata backportata ai kernel stabili precedenti al momento della scrittura. Di conseguenza, sovrascrivendo l'indirizzo della struttura VMA con quello dell'ultimo ingresso IDT, l'ingresso che contiene l'indirizzo di asm_sysvec_spurious_apic_interrupt viene direttamente divulgato, rivelando così gli indirizzi base del codice kernel e dei dati kernel.

Passo 2: Da UAF a perdita di indirizzi (1)

Il metodo discusso in precedenza può essere utilizzato ricorrentemente per esporre incrementalmente più indirizzi dalla sezione dati del kernel. Ad esempio, il puntatore init_task.tasks.prev all'interno della sezione dati punta alla struttura task_struct dell'ultimo task creato, che è senza dubbio allocato nell'heap.

Passo 2: Da UAF a perdita di indirizzi (2)

Quando tutti i task appena creati vengono terminati, le loro strutture task_struct verranno successivamente deallocate. Se la quantità di questi task è sufficientemente grande, le pagine corrispondenti possono essere restituite all'allocatore di pagine. Ciò offre la possibilità di riallocare queste pagine e riempirle con dati utente. Tuttavia, tieni presente che le pagine rilasciate generalmente appartengono alla lista per-CPU (PCP). Per le pagine presenti nella lista PCP, possono essere riallocate esclusivamente nello stesso ordine di pagina. Di conseguenza, mappare solo nuove pagine nello spazio utente, che richiede solo pagine di ordine 0 dall'allocatore di pagine, non soddisfa gli obiettivi.

Tuttavia, la chiamata di sistema msgsnd richiederà blocchi di memoria tramite kmalloc e popolerà questi blocchi con dati definiti dall'utente. Quando la cache kmalloc è esaurita, richiederà pagine dall'allocatore di pagine a un ordine specifico. Se la dimensione del messaggio è regolata con precisione, si otterrà l'ordine desiderato. Pertanto, la pagina il cui indirizzo è stato precedentemente divulgato verrà riallocata. Di conseguenza, diventa possibile ottenere una pagina con un indirizzo noto e dati manipolabili dall'utente.

Passo 3: Da UAF a privilegi di root

Ora è possibile falsificare la struttura VMA nella pagina con indirizzo noto e controllare il puntatore a funzione vma->vm_ops->name. Il passo successivo consiste nel trovare gadget adatti per uscire dai container e acquisire privilegi di root.```c static void show_map_vma(struct seq_file *m, struct vm_area_struct *vma) { // ...

root@kitploit:~
if (vma->vm_ops && vma->vm_ops->name) {
	name = vma->vm_ops->name(vma);
	if (name)
		goto done;
}

// ...

}

root@kitploit:~
![Step 3: Da UAF ai privilegi di root](https://assets.kitploit.com/production/public/readmes/28620/d9c03475803bb799f2b594935d118d73f2547ea4dcf5b40c54e1454324575a0f.png)

Le costruzioni dei gadget sono le seguenti:
 1. Pivot dello stack: `movq %rbx, %rsi; movq %rbp, %rdi; call
    __x86_indirect_thunk_r13` -> `pushq %rsi; jmp 46(%rsi)` -> `popq %rsp; ret`
    -> `popq %rsp; ret`, dove %rdi, %rbx e %r13 puntano _inizialmente_ a dati
    controllabili dall'utente.
 2. Ottenere privilegi di root: `popq %rdi; ret` -> `prepare_kernel_cred` -> `popq
    %rdi; ret` -> `movq %rax, (%rdi); ret`, dove %rdi punta _ora_ alla cima dello
    stack; `popq %rdi; ret` -> `commit_creds`, eseguendo effettivamente
    `commit_creds(prepare_kernel_cred(&init_task))`.
 3. Evadere dai container: `popq %rdi; ret` -> `find_task_by_vpid` -> `popq %rdi;
    ret` -> `movq %rax, (%rdi); ret`, dove %rdi punta _ora_ alla cima dello stack;
    `popq %rdi; ret` -> `popq %rsi; ret` -> `switch_task_namespaces`,
    eseguendo effettivamente `switch_task_namespaces(find_task_by_vpid(1),
    &init_nsproxy)`.
 4. Sbloccare mm: `popq %rax; ret` -> `movq %rbp, %rdi; call
    __x86_indirect_thunk_rax`, dove %rbp punta al seq_file originale;
    `popq %rax; ret` -> `m_stop`, eseguendo effettivamente `m_stop(seq_file, ..)`.
 5. Ritorno allo spazio utente: usare `swapgs_restore_regs_and_return_to_usermode` e
    chiamare `execve()` per ottenere la shell.

Infine, usando `nsenter --mount=/proc/1/ns/mnt` per ripristinare il namespace mount
e ottenere la flag tramite `cat /flag/flag`.

### Source code

Il codice sorgente completo dell'exploit è disponibile [qui](https://github.com/lrh2000/stackrot/blob/HEAD/exp). Per maggiori dettagli, consulta il suo file README.
Scarica lo strumento