
CVE-2026-72711 — Bug di correttezza del kernel di Lean 4: avvelenamento della cache di inferenza
Dimostrazione di 0 = 1 senza assiomi tramite avvelenamento della cache FVar di dichiarazioni opache.
| Campo | Valore |
|---|---|
| Bug report | leanprover/lean4#14484 |
| Fix | leanprover/lean4#14498 |
| Affected | Lean 4 ≤ v4.31.0 / tutte le versioni precedenti |
| Fixed in | nightly 2026-07-23+, backportato a v4.32.1 e v4.33.0 |
| CVSS 3.1 | AV:L/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N — 7.1 |
| CWE | CWE-488 (Esposizione di un elemento dati alla sessione errata), CWE-20 (Validazione impropria dell'input) |
| CVE | CVE-2026-72711 |
Il percorso environment::add_opaque del kernel di Lean 4 non chiama
check_no_metavar_no_fvar sul corpo della dichiarazione — a differenza delle definizioni e
dei teoremi, che eseguono questo controllo di chiusura prima del type-checking del valore.
Un metaprogramma malevolo sfrutta questa omissione tramite l'avvelenamento della cache di inferenza:
Priming — Un'espressione di tipo costruita ad hoc (cachePrimingType) è una
beta-redex chiusa definizionalmente uguale a False. Durante il suo type-checking, il
kernel crea una dichiarazione locale temporanea _kernel_fresh.2 : False e
memorizza nella cache il tipo inferito nell'inference cache dell'istanza type_checker.
Invalidazione del contesto — Il contesto locale viene ripristinato (la variabile
temporanea viene rimossa), ma la cache di inferenza persiste sulla stessa
istanza type_checker. La voce della cache è ora obsoleta — associa un
identificatore che non fa più riferimento a nulla nello scope.
Sfruttamento — Il valore della dichiarazione opaca è un Expr.fvar
che punta all'identificatore obsoleto. La ricerca nella cache scatta prima che
il ramo FVar verifichi l'appartenenza al contesto locale, restituendo False come
tipo senza mai rilevare la variabile non associata.
Il risultato è una costante opaca di tipo False accettata dal kernel, da cui
0 = 1 segue banalmente.
L'exploit:
addDecl verificato del kernel--trust=0 (verifica massima)#print axiomssorry, unsafeCast, debug.skipKernelTC, FFI o manomissioni di .oleanleanchecker --freshdocker build -t lean-cve-poc-14484 .
docker run --rm lean-cve-poc-14484
Output atteso su una versione vulnerabile di Lean:
[*] Running CachePoisonFalse.lean with --trust=0 ...
kernel accepted bad : False
'bad' does not depend on any axioms
'assumptionFreeFalse' does not depend on any axioms
'zero_eq_one' does not depend on any axioms
[!] VULNERABILITY CONFIRMED
zero_eq_one : 0 = 1
Depends on: no axioms
Bug class: Inference cache poisoning (CWE-488)
Root cause: missing check_no_metavar_no_fvar in add_opaque
Bug report: https://github.com/leanprover/lean4/issues/14484
Fix: https://github.com/leanprover/lean4/pull/14498
In una versione di Lean corretta, il kernel rifiuta la dichiarazione opaca mal tipizzata e lo script segnala che la versione non è vulnerabile.
Qualsiasi sistema che considera le prove verificate dal kernel di Lean come verità di riferimento è interessato. Questo include:
leanchecker --fresh accetta l'exploit. Il formato di esportazione comparator non può esprimere un FVar, quindi quel percorso di validazione non è interessato.In src/kernel/environment.cpp, la funzione add_opaque controlla il tipo del valore opaco ma non esegue il controllo di chiusura:
environment environment::add_opaque(declaration const & d, bool check) const {
scoped_diagnostics diag(*this, check);
opaque_val const & v = d.to_opaque_val();
if (check) {
type_checker checker(*this, diag.get());
check_constant_val(*this, v.to_constant_val(), checker);
// ← MISSING: check_no_metavar_no_fvar(*this, v.get_name(), v.get_value());
expr val_type = checker.check(v.get_value(), v.get_lparams());
// ...
}
}
Sia add_definition che add_theorem chiamano check_no_metavar_no_fvar sui
loro valori prima di verificarne il tipo. L'omissione in add_opaque è il
punto di ingresso.
La cache di inferenza in type_checker mappa le espressioni ai loro tipi inferiti.
In infer_type_core, la ricerca nella cache:
auto it = m_infer_type_cache[binfo_idx(bi)].find(e);
if (it != m_infer_type_cache[binfo_idx(bi)].end())
return it->second;
attiva prima che il ramo FVar validi l'appartenenza al contesto locale:
case expr_kind::FVar: {
optional<local_decl> d = m_lctx.find_local_decl(e);
if (!d) throw_kernel_exception(...);
return d->get_type();
}
L'attaccante controlla il popolamento della cache tramite l'espressione di tipo, rendendo il nome della voce obsoleta (_kernel_fresh.2) prevedibile e attaccabile.
| Aspetto | #14484 (questo bug) | #14576 |
|---|---|---|
| Classe di bug | Avvelenamento della cache | Type confusion tramite validazione della proiezione |
| Punto di ingresso | add_opaque manca il controllo della closure | Parametri phantom induttivi annidati |
| Cache coinvolta | Sì — cache di inferenza | Sì — confronto hash delle espressioni |
| Influisce su leanchecker | Sì | Sì |
| Influisce su comparator | No (FVar non esprimibile) | Sì |