
Exploit di escalation dei privilegi locale per CVE-2023-0386 che mira a overlayfs del kernel Linux. Include analisi dettagliata della vulnerabilità, codice PoC e guida allo sfruttamento passo-passo utilizzando FUSE e user namespaces.
gcc -Wall exp.c `pkg-config fuse --cflags --libs` -o exp
./exp /tmp

La conoscenza teorica in questo articolo (namespace, overlay filesystem, fuse filesystem, ecc.) proviene da chatGPT.
ID vulnerabilità: CVE-2023-0386
Prodotto vulnerabile: kernel Linux - overlay filesystem
Interessato: 5.11 ~ 5.19
Prerequisiti: capacità di eseguire unshare o creare un overlay filesystem
Effetto: escalation dei privilegi locali
Compilare il kernel manualmente:
Preparare una versione all'interno dell'intervallo vulnerabile, esclusa la 5.15 (pare che la 5.15 abbia dei problemi), abilitare i due fs overlay e fuse:
CONFIG_SLUB_DEBUGOVERLAY_FS
CONFIG_FUSE_FS
Ubuntu 21.10 con kernel 5.13.0-16-generic testato funzionante:

Prima dell'analisi della vulnerabilità, facciamo impersonare a chatGPT un esperto del kernel Linux:
(Chiedere a chatGPT: ora interpreterai un esperto del kernel Linux per aiutarmi a rispondere ad alcune domande)
Le informazioni pubbliche sulla vulnerabilità sono scarse; la più diretta è la patch. Link alla patch:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f11ada10d0a

Si può vedere che è stato aggiunto un controllo nella funzione ovl_copy_up_one. Chiediamo prima a chatGPT cosa fa questa funzione:

Quindi questa funzione si verifica durante l'operazione di copia dal layer inferiore al layer superiore nell'overlay filesystem. Ora analizziamo il nuovo controllo della patch nel contesto:
static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
int flags)
{
int err;
DEFINE_DELAYED_CALL(done);
struct path parentpath;
struct ovl_copy_up_ctx ctx = {
.parent = parent,
.dentry = dentry,
.workdir = ovl_workdir(dentry),
};
if (WARN_ON(!ctx.workdir))
return -EROFS;
ovl_path_lower(dentry, &ctx.lowerpath);
err = vfs_getattr(&ctx.lowerpath, &ctx.stat, //[1] Ottiene le stat del filesystem sottostante
STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT);
if (err)
return err;
//[2] La patch aggiunge il controllo se user id e group id nelle stat del file sono mappati nel namespace corrente
if (!kuid_has_mapping(current_user_ns(), ctx.stat.uid) ||
!kgid_has_mapping(current_user_ns(), ctx.stat.gid))
return -EOVERFLOW;
[1] Innanzitutto, tramite la funzione vfs_getattr recupera gli attributi del file di destinazione nel filesystem sottostante. vfs_getattr ottiene la struttura struct stat corrispondente a un file passando una struttura struct path.
[1.1] ctx.lowerpath è il percorso di un file nel filesystem inferiore dell'overlay filesystem. L'overlay filesystem verrà introdotto successivamente.
[1.2] La struttura struct stat contiene informazioni sui metadati del file, inclusi il proprietario e il gruppo. Le informazioni sul proprietario del file così ottenute vengono verificate nel controllo aggiunto dalla patch.
[2] Quindi chiama la funzione kuid_has_mapping per verificare le informazioni sul proprietario e sul gruppo del file appena ottenute. Controlla se il proprietario e il gruppo del file di destinazione sono mappati nel namespace utente corrente.
[2.1] La funzione kuid_has_mapping riceve due parametri: una struttura struct user_namespace (namespace utente) e una struttura struct kuid (utente kernel). Questa funzione verifica se le informazioni sull'utente specificato sono mappate nel namespace utente specificato. La mappatura degli utenti nei namespace verrà spiegata in dettaglio più avanti.
Quindi sappiamo che, durante l'esecuzione dell'operazione della funzione vulnerabile (ovl_copy_up_one), se il proprietario o il gruppo del file di destinazione nel layer inferiore non è mappato nel namespace corrente, l'operazione fallirà.
Il principio della patch è quindi chiaro. Tuttavia, per riprodurre la vulnerabilità, dobbiamo risolvere i seguenti problemi:
ovl_copy_up_one, ovvero la copia dal layer inferiore al layer superiore nell'overlay filesystem?lowerpath, il file di cui si verifica la mappatura del proprietario, nella catena logica?Prima di rispondere a queste domande, dobbiamo comprendere alcune conoscenze di base:
(Chiedere a chatGPT: per favore introduci i namespace nel kernel Linux)
In Linux, i namespace sono una funzionalità del kernel utilizzata per implementare l'isolamento delle risorse. Attraverso i namespace, un insieme di processi può apparire come se operasse in ambienti di sistema indipendenti, migliorando la sicurezza e la gestibilità del sistema. I namespace giocano un ruolo chiave nelle tecnologie dei container (come Docker), consentendo ai container di funzionare in ambienti isolati senza influenzare altri container o il sistema host.
Il kernel Linux supporta 7 tipi di namespace (mount, pid, net, ipc, user, time, cgroup), ognuno dei quali isola una specifica categoria di risorse di sistema. I namespace vengono creati, modificati e gestiti tramite una serie di system call (come clone, unshare e setns). I runtime dei container (come Docker) e altri strumenti di virtualizzazione sfruttano queste funzionalità per fornire ambienti di esecuzione indipendenti e isolati per i container.
La funzione di controllo aggiunta dalla patch, kuid_has_mapping, riguarda lo user namespace tra i 7 namespace sopra menzionati.
(Chiedere a chatGPT: per favore introduci lo user namespace)
Lo user namespace viene utilizzato per isolare gli ID utente (UID) e gli ID gruppo (GID). Attraverso lo user namespace, è possibile utilizzare insiemi indipendenti di ID utente e gruppo in diversi namespace. Ciò significa che utenti e gruppi in uno user namespace possono avere ID o permessi diversi in un altro namespace. Lo user namespace migliora la sicurezza e la gestibilità del sistema, specialmente negli ambienti containerizzati.
La caratteristica chiave dello user namespace è la mappatura degli ID: lo user namespace consente di mappare UID e GID da un namespace a un altro. Ciò significa che, in diversi user namespace, gli stessi UID e GID possono rappresentare utenti e gruppi diversi. Ad esempio, un utente root (UID 0) in un container può essere mappato come utente non privilegiato nel sistema host.
Dobbiamo solo ricordare i seguenti punti:
Ad esempio, se l'utente breeze crea un nuovo user namespace e poi in quel namespace guarda un file di proprietà di root, il gruppo visualizzato sarà nobody: