Blog
Linux Kernel LPEContainer EscapePrivilege EscalationCVE-2026-46331

pedit COW e DirtyClone: due LPE Linux sulla page cache e perché 'locale' significa root nel 2026

Due bug del kernel Linux sulla page cache — pedit COW (CVE-2026-46331) e DirtyClone (CVE-2026-43503) — trasformano un foothold in container in root. Perché la privilege escalation locale è il breakout del 2026.

Zero Hunt Research··10 min di lettura

In una sola finestra di giugno, due team del kernel Linux scollegati tra loro hanno corretto due bug scollegati tra loro che fanno la stessa cosa: corrompere la page cache condivisa da codice kernel e portare un utente non privilegiato fino a root. CVE-2026-46331, soprannominato pedit COW, ha ricevuto un CVE al momento del merge il 16 giugno 2026 e un exploit pubblico funzionante il giorno dopo. CVE-2026-43503, DirtyClone, è stato corretto in sordina il 21 maggio e ha avuto un walkthrough di exploit pubblico da JFrog Security Research il 25 giugno. Nessuno dei due è una remote code execution. Nessuno dei due finirà nei titoli dei breach come un CitrixBleed o una RCE pre-auth su Splunk. Eppure contano più della maggior parte delle CVE su appliance che invece ci sono finite, per via di dove Linux gira davvero nel 2026.

La page cache è diventata l'autostrada della privilege escalation su Linux

Per quasi tutto l'ultimo decennio, la "local privilege escalation" è rimasta in fondo a ogni coda di triage. Il ragionamento: l'attaccante ha già bisogno di esecuzione di codice sulla macchina, quindi una LPE non fa che "promuovere" un foothold esistente — la si corregge con comodo. Quel ragionamento è morto con il container.

Il parco Linux moderno è multi-tenant per costruzione. Un nodo Kubernetes esegue carichi di una dozzina di team. Un runner CI/CD esegue il codice delle pull request di chiunque abbia un fork. Un build host condiviso, una flotta di database gestiti, un backend serverless — tutti danno per design una shell locale a codice parzialmente fidato. Su questi sistemi non c'è uno scarto reale tra "ho un foothold" e "ho esecuzione arbitraria come utente normale". L'unica barriera rimasta è quella che il kernel impone tra quell'utente e root. Una LPE affidabile abbatte l'ultimo muro dell'edificio.

Le LPE affidabili del 2026 attaccano tutte la stessa struttura: la page cache — la copia in memoria del contenuto dei file che il kernel condivide tra ogni processo che apre lo stesso file. Corrompi una pagina che fa da backing a un binario di root o a una configurazione privilegiata e possiedi il sistema senza mai toccare i byte su disco. È la genealogia che la community sta nominando bug dopo bug:

  • Dirty COW (CVE-2016-5195) — la race copy-on-write originale, 2016.
  • Dirty Pipe (CVE-2022-0847) — scrittura sulla page cache via pipe buffer, 2022.
  • Copy Fail (aprile 2026), DirtyFrag (maggio 2026), poi DirtyClone e pedit COW nello stesso giugno.

Cinque voci nella stessa famiglia nel solo 2026 non sono una coincidenza. È una classe che si sta industrializzando, e i due bug di giugno mostrano come si raggiunga lo stesso root da estremità opposte del kernel.

pedit COW: un bug di packet-editing in tc che riscrive la page cache

pedit COW vive in net/sched, il sottosistema di traffic control. L'azione act_pedit è ciò che tc usa per riscrivere i byte di header sui pacchetti in transito — il tipo di cosa che fa di routine la configurazione di un firewall o di un load balancer. Dentro tcf_pedit_act() il kernel calcola un intervallo copy-on-write una volta sola, prima del loop per-chiave, usando un valore chiamato tcfp_off_max_hint. Il problema: quell'hint non tiene conto dell'offset di header a runtime aggiunto dalle chiavi tipizzate. Alcune scritture finiscono fuori dalla regione copiata privatamente e cadono su pagine condivise della page cache. La patch sposta la chiamata skb_ensure_writable() dentro il loop per-chiave, dove l'offset reale è noto, e aggiunge controllo di overflow sull'aritmetica degli offset (analisi TuxCare).

I prerequisiti dell'exploit sono la parte che conta sul piano operativo. Secondo il write-up TuxCare e l'advisory SentinelOne, all'attaccante servono tutti:

  1. un kernel che porti il code path act_pedit vulnerabile;
  2. il modulo act_pedit caricato, o auto-caricabile;
  3. user namespace non privilegiati abilitati, che consegnano all'attaccante un CAP_NET_ADMIN locale al namespace;
  4. la possibilità di eseguire codice come utente locale non fidato o parzialmente fidato.

La condizione (3) è quella silenziosa. Gli user namespace non privilegiati sono aperti per default su RHEL e Debian; su Ubuntu sono protetti da kernel.apparmor_restrict_unprivileged_userns. Sono anche il meccanismo su cui poggiano la maggior parte dei container runtime. La stessa feature che rende possibili i container rootless è ciò che consegna all'exploit la sua capability. Red Hat ha classificato il bug come Important anziché Critical perché il vettore è locale — ma "locale" sta facendo molto lavoro in quella frase.

DirtyClone: lo stesso root, dall'altro estremo del kernel

DirtyClone (CVE-2026-43503, CVSS 8.8) non tocca mai tc. Sfrutta una mancata propagazione di flag in __pskb_copy_fclone() quando un socket buffer viene clonato attraverso il target TEE di netfilter. Il clone perde il marker di sicurezza SKBFL_SHARED_FRAG, e il buffer ora non marcato attraversa la decifratura in-place di IPsec senza protezione — corrompendo le stesse pagine condivise della page cache, raggiungendo lo stesso root. Un bug entra dal path di packet-editing; l'altro dalla clonazione dei pacchetti e da IPsec. Si incontrano sulla page cache.

È proprio questa convergenza il punto. Chi ragiona in termini di "correggo la CVE di tc" sta combattendo il sintomo. La debolezza strutturale — code path del kernel che assumono un buffer privato quando è condiviso — è ciò che continua a produrre questi bug. Entrambi sono stati corretti nei rispettivi sottosistemi; la classe no.

"Abbiamo patchato l'appliance la stessa settimana. Eravamo puliti."

L'appliance era patchata. I nodi Kubernetes su cui i suoi carichi vengono schedulati giravano un kernel precedente a entrambe le fix, con user namespace non privilegiati abilitati per default. La RCE pre-auth ha dato a qualcuno un pod. pedit COW gli ha dato il nodo.

Perché "locale" è il container escape del 2026

Ecco la catena che le ultime due settimane di titoli continuano a regalare agli attaccanti. Una RCE pre-auth — scegliete una qualsiasi tra Splunk, UniFi, Cisco Unified CM o PTC Windchill, tutte finite nel CISA KEV questo mese — fa cadere l'attaccante in un processo o in un container come service account. È un foothold, non una conquista. A trasformarlo in conquista è la risposta a una domanda: questo foothold può raggiungere root, evadere dal container e fare pivot sul nodo? Una LPE sulla page cache è il sì più pulito disponibile.

pedit COW DirtyClone
CVE CVE-2026-46331 CVE-2026-43503
Sottosistema net/sched act_pedit (tc) netfilter TEE__pskb_copy_fclone()
Causa intervallo COW calcolato prima di risolvere gli offset per-chiave lo skb clonato perde il marker SKBFL_SHARED_FRAG
Severità Red Hat Important CVSS 8.8
CVE / patch assegnato 16 giu 2026 patch in merge 21 mag 2026
Exploit pubblico 17 giu 2026 25 giu 2026 (JFrog)
Prerequisito chiave user namespace non privilegiati path netfilter TEE + IPsec
Risultato corruzione page cache → root corruzione page cache → root

Due proprietà rendono questa classe difficile da intercettare a posteriori. Primo: gli ambienti a più alto rischio — server multi-tenant, runner CI/CD, container host, cluster Kubernetes dove utenti non fidati possono creare namespace — sono esattamente quelli che si aspettano codice locale non fidato, quindi un exploit locale di per sé non genera alcuna anomalia (analisi CyberSecGuru). Secondo: gli exploit operano interamente in RAM. Corrompono pagine in cache, non file. Il file-integrity monitoring non vede nulla, perché nulla cambia su disco. Un agente EDR in attesa di binari rilasciati non ha nulla da rilevare.

E il divario degli scanner è strutturale, non un problema di tuning. Uno scanner di vulnerabilità legge la banner di versione del kernel e segnala "CVE Important presente". Non può dirvi se act_pedit è davvero caricabile sulla vostra build, se gli user namespace non privilegiati sono abilitati, o se un foothold su questo host specifico raggiungerebbe davvero root. Che la CVE sia "presente" e che l'host sia sfruttabile sono fatti diversi, e solo il secondo conta quando alle 2 di notte dovete decidere cosa correggere.

Remediation

Un runbook completo per entrambi i bug. Patchate per prima cosa; i controlli compensativi qui sotto sono per le flotte che non possono riavviare i kernel su richiesta.

1. Sono interessato?

# Versione kernel vs la build corretta della vostra distro
uname -r

# act_pedit è caricato o auto-caricabile? (pedit COW)
lsmod | grep -q act_pedit && echo "act_pedit CARICATO"
modprobe -n -v act_pedit            # dry-run: si risolve?

# Gli user namespace non privilegiati sono abilitati? (fonte della capability di pedit COW)
sysctl kernel.unprivileged_userns_clone 2>/dev/null   # Debian/Ubuntu
sysctl user.max_user_namespaces                        # valore > 0 = abilitato
cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns 2>/dev/null  # gate Ubuntu

Se gestite nodi multi-tenant, runner CI o qualsiasi host dove utenti parzialmente fidati ottengono una shell, considerate la risposta "interessato finché non dimostrato patchato".

2. Patch — build corrette esatte. Prendete il kernel stabile corrente della vostra distro; gli errata rilevanti per pedit COW sono:

  • RHEL 10 — RHSA-2026:27288
  • RHEL 9 — RHSA-2026:27789
  • RHEL 8 / AlmaLinux 8 — RHSA-2026:27353 (ALSA-2026:27353)
  • Debian 13 (trixie) — DSA-6355-1 (emesso 21 giu 2026)
  • Mainline e stable: backport in merge.

La fix di DirtyClone è entrata upstream il 21 maggio 2026; ogni kernel di distro compilato dopo gli errata di fine maggio/giugno del vostro vendor la include. Verificate il kernel in esecuzione dopo il reboot — un pacchetto patchato su disco ma non avviato non è un sistema patchato.

3. Non potete patchare ora? Controlli compensativi. Le patch del kernel richiedono un reboot, e le flotte rimandano i reboot per settimane. Nel frattempo:

  • Disabilitate gli user namespace non privilegiati dove non vi servono. Rimuove di netto la fonte della capability di pedit COW:
    sysctl -w user.max_user_namespaces=0            # famiglia RHEL/EL
    sysctl -w kernel.unprivileged_userns_clone=0    # Debian
    # Ubuntu: tenete kernel.apparmor_restrict_unprivileged_userns=1
    
  • Mettete in blacklist act_pedit se non usate regole tc pedit:
    echo 'install act_pedit /bin/true' | tee /etc/modprobe.d/disable-act_pedit.conf
    
  • Isolate i carichi non fidati con una sandbox che non esponga i path di rete del kernel host — gVisor o Kata Containers per runner CI e pod multi-tenant.

4. Cacciate la compromissione. Sono exploit solo-in-RAM, quindi la caccia su disco fallisce per costruzione. Guardate il comportamento:

  • auto-caricamento inatteso di act_pedit su un host che non usa mai il packet editing di tc;
  • utenti non privilegiati che creano user/network namespace su nodi di produzione (audit su clone/unshare con CLONE_NEWUSER);
  • un processo che acquisisce uid 0 senza un precedente path setuid/sudo nell'audit trail;
  • creazione anomala di tc filter legata a un carico non di rete.

Mappate le detection su MITRE ATT&CK T1068 (Exploitation for Privilege Escalation) e T1611 (Escape to Host). Su Kubernetes, allertate sui pod che acquisiscono capability a livello di nodo mai concesse.

5. Eradicate e verificate. Patch, reboot (o live-patch del vendor dove disponibile), poi ruotate ogni segreto raggiungibile dall'host o dal nodo compromesso — token di service account, credenziali kubelet, chiavi cloud montate, segreti CI. Un foothold root su un nodo li legge tutti. Confermate la versione del kernel avviato, ri-verificate la postura sugli userns, e solo allora dichiarate l'host pulito.

Dove si inserisce Zero Hunt

Tutto quanto sopra si riduce a una domanda operativa a cui uno scanner di vulnerabilità non sa rispondere e che un pentest trimestrale non mette alla prova: se un attaccante ottiene un foothold qui, diventa root? È una domanda assume-breach, e rispondervi su ogni host, in continuo, è ciò per cui è costruito lo swarm a 10 agenti di Zero Hunt.

Gli agenti Post-Exploit e Pivot fanno esattamente la catena che questi bug abilitano — prendere un foothold, tentare la privilege escalation locale, poi il movimento laterale — e generano il tentativo per ogni target. Invece di segnalare "CVE kernel Important presente", il motore ricostruisce se la vostra build del kernel, il vostro stato di act_pedit e la vostra postura sugli user namespace si combinino davvero in una primitiva di root funzionante. Uno scanner afferma che la CVE esiste; lo swarm dimostra che l'host è, o non è, sfruttabile — che è l'unica versione di quel fatto su cui valga la pena agire. Le skill di privilege escalation che usa sono backtestate nell'AI Gym contro il corpus Vulhub prima che una qualsiasi tocchi un target di produzione, così una nuova tecnica sulla page cache è validata in palestra, non improvvisata sulla vostra flotta.

La protezione è la stessa storia letta al contrario. Zero Hunt esegue ogni exploit generato dentro un container Docker effimero con hardening gVisor opzionale, e un host indurito che l'attacco non raggiunge mai. Quel design esiste proprio perché le LPE del kernel come pedit COW sono costruite per evadere confini deboli dei container — il kernel in user-space di gVisor non espone i path net/sched o skb dell'host, quindi una scrittura in stile pedit-COW non ha nulla dell'host da corrompere. Il motore che caccia la classe è isolato dalla stessa classe. E poiché le campagne sono change-triggered, un nuovo container host o un nodo appena reimmaginato sul perimetro viene ri-testato entro l'ora — che è la finestra che conta quando un PoC armato arriva il giorno dopo la CVE.