Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

FluxContactConfidentialité© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-31413-BPF-Container-Escape — CVE-2026-31413 : bug de solidité du vérificateur BPF - évasion de conteneur | Kitploit
Outils/GitHubGitHub/rat5ak/cve-2026-31413-bpf-container-escape
Escalade de PrivilègesAnalyse des VulnérabilitésExploitationPost-ExploitationArticles et RechercheApprentissage et ÉducationÉvasion de ConteneurExploitation de Binaires

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
GitHub
rat5ak/cve-2026-31413-bpf-container-escape

CVE-2026-31413-BPF-Container-Escape

CVE-2026-31413 : bug de solidité du vérificateur BPF - évasion de conteneur

Voir le dépôt
1116il y a 5 moisPas encore vérifié

CVE-2026-31413 : un octet dans le vérificateur BPF pour une évasion de conteneur

J'ai trouvé un bug de sûreté dans le vérificateur BPF de Linux - un + 1 dans un appel à push_stack() qui fait que le vérificateur saute une instruction ALU sur un chemin dupliqué. Pour BPF_OR, cela signifie que le vérificateur suit dst = 0 alors que le CPU calcule 0 | K = K. J'ai écrit une évasion de conteneur complète : lecture/écriture hors limites depuis une map BPF, détournement de vtable, écrasement de modprobe_path, root sur l'hôte. Ensuite, j'ai rédigé une série de deux correctifs - une correction d'un caractère dans le vérificateur et 90 lignes de selftests - et je l'ai fait fusionner dans mainline.

📹 Vidéo de démonstration de l'évasion de conteneur

CVECVE-2026-31413
Classe de bugSûreté du vérificateur - divergence de valeur de registre
Cause racinepush_stack(env, env->insn_idx + 1, ...) saute l'instruction ALU sur le chemin dupliqué
Introduitbffacdb80b93 - Linux 7.0-rc1 (14 janv. 2026)
Corrigéc845894ebd6f - Linux 7.0-rc5 (22 mars 2026)
Affecté6.12.75+ (backport stable dea9989a3f) jusqu'à 7.0-rc4
ImpactR/W arbitraire du noyau → évasion de conteneur → root hôte
RequisCAP_BPF + CAP_PERFMON + CAP_NET_ADMIN
CorrectifUn caractère : insn_idx + 1 → insn_idx

En bref

maybe_fork_scalars() duplique l'état du vérificateur lorsqu'elle voit ARSH + AND/OR avec une constante. Le chemin dupliqué reçoit dst = 0 et saute l'instruction ALU. Pour AND, c'est bon : 0 & K = 0. Pour OR, c'est faux : 0 | K = K, pas 0.

Le vérificateur pense que le registre est zéro. Le CPU a K. Je m'en suis servi pour construire une lecture/écriture hors limites arbitraire à partir d'une valeur de map BPF, divulguer l'adresse noyau de la map, construire une fausse vtable bpf_map_ops, rediriger map_push_elem via array_map_get_next_key pour une écriture arbitraire, et écraser modprobe_path. Déclenchez un format binaire inconnu, le noyau exécute mon script en root. Dans un conteneur, évasion complète de l'hôte.

Série de deux correctifs : une correction d'un caractère dans le vérificateur plus 90 lignes de selftests BPF couvrant la duplication OR vs AND. Fusionnée par Alexei Starovoitov le 22 mars. CVE-2026-31413 assignée par Greg Kroah-Hartman le 12 avril.


Contexte : le vérificateur BPF

eBPF vous permet de charger de petits programmes dans le noyau - filtres de paquets, hooks de traçage, politiques de sécurité - sans compiler de module noyau. Le problème, c'est que vous injectez du code dans l'anneau 0. Si ce code contient un bug, c'est un bug du noyau.

Ainsi, avant qu'un programme BPF ne s'exécute, le vérificateur du noyau simule chaque chemin d'exécution possible. Il suit ce que contient chaque registre (un pointeur ? un scalaire ? quelle plage ?), vérifie chaque accès mémoire par rapport aux limites de la map, et rejette tout ce qui pourrait lire ou écrire hors limites. Si le vérificateur dit qu'un programme est sûr, le JIT le compile en code machine natif et l'exécute avec tous les privilèges du noyau. Il n'y a plus aucun contrôle de bornes à l'exécution après ce point. Le vérificateur est la frontière de sécurité.

C'est pourquoi les bugs de sûreté du vérificateur diffèrent des corruptions mémoire classiques. Avec un débordement de tas ou une UAF, vous obtenez une primitive de corruption et devez travailler à partir de là - pulvériser le tas, préparer les objets, remporter une course. Avec un bug du vérificateur, vous amenez le noyau à croire un mensonge sur la valeur d'un registre. Chaque contrôle de bornes qui dépend de ce registre passe. Le noyau a approuvé votre accès hors limites. Il l'exécute sans poser de question. Si vous alignez correctement l'état des registres, vous en tirez une primitive propre et fiable.

Comment je l'ai trouvé

J'auditais maybe_fork_scalars() - du nouveau code, ajouté en janvier 2026 dans bffacdb80b93. La duplication d'état est toujours intéressante car c'est là que le vérificateur se divise en chemins d'exploration parallèles, et si un chemin suit une valeur incorrecte, tout ce qui se trouve en aval de ce chemin est non fiable.

La fonction duplique l'état lorsqu'elle voit ARSH + AND/OR avec une source constante. Le chemin dupliqué reçoit dst = 0 et saute l'instruction ALU. Je lisais la ligne push_stack(env, env->insn_idx + 1, ...) et tout s'est éclairé immédiatement - le + 1 signifie que le chemin dupliqué n'exécute jamais l'opération ALU. Pour AND, 0 & K = 0, donc ignorer l'instruction est correct. Pour OR, 0 | K = K. Le chemin dupliqué pense que le résultat est 0 alors qu'il est en réalité K.

J'ai écrit un programme BPF ce soir-là. ARSH 63 pour obtenir {0, -1}, OR avec une constante, branche conditionnelle pour séparer les chemins du vérificateur, puis ajout du registre « zéro » à un pointeur de map. Le vérificateur a approuvé map_value + 0. Le CPU a accédé à map_value + K. KASAN a confirmé l'accès hors limites lors des tests.

Lecture/écriture hors limites dès le lendemain matin. Évasion de conteneur dès le lendemain soir. J'ai utilisé Claude (Opus 4.5) tout au long - pour analyser la logique de duplication d'état du vérificateur, réfléchir aux primitives d'exploitation, et transformer l'accès hors limites en une chaîne d'évasion complète. L'approche du détournement de vtable est née d'un échange où Claude a passé en revue les pointeurs de fonction de bpf_map_ops à la recherche de gadgets appelables.

Le commit qui a introduit le bug

Le commit bffacdb80b93 (« bpf: Recognize special arithmetic shift in the verifier ») a été intégré le 14 janvier 2026 dans 7.0-rc1. Alexei Starovoitov, co-développé par Puranjay Mohan. Il a ajouté maybe_fork_scalars() pour gérer un motif DAGCombiner LLVM :``` w2 s>>= 31 // arithmetic shift right: w2 becomes 0 or -1 w2 &= -134 // AND with constant K

LLVM abaisse `select_cc setlt X, 0, A, 0` en `sra + and`. Après le décalage arithmétique à droite, le registre vaut soit `0` (entrée non négative), soit `-1` (tous à 1). Le ET avec une constante donne `0` ou `K`.

Le vérificateur ne peut pas suivre `{0, K}` dans un seul `bpf_reg_state` — sa plage signée `[0, K]` est une sur-approximation, ce qui le poussait à rejeter des programmes Cilium valides. La correction : dupliquer l’état du vérificateur. Un chemin explore `dst = 0`, l’autre `dst = -1`, chacun suivant la valeur précise.

La mise en œuvre :```c
static int maybe_fork_scalars(struct bpf_verifier_env *env,
                              struct bpf_insn *insn,
                              struct bpf_reg_state *dst_reg)
{
    // ... condition check: dst range is [-1, 0], src is constant ...

    branch = push_stack(env, env->insn_idx + 1, env->insn_idx, false);
    //                             ^^^^^^^^^^^^
    //                    pushed path resumes AFTER the ALU insn
    if (IS_ERR(branch))
        return PTR_ERR(branch);

    regs = branch->frame[branch->curframe]->regs;
    __mark_reg_known(&regs[insn->dst_reg], 0);   // pushed: dst = 0
    __mark_reg_known(dst_reg, -1ull);             // current: dst = -1
    return 0;
}

Deux choses se produisent sur le chemin poussé :

  1. Le registre de destination est mis à 0
  2. L'exécution reprend à insn_idx + 1 - l'instruction après l'opération ALU

Pour BPF_AND : dst = 0, on saute le AND. À l'exécution : 0 & K = 0. Correspondance. Sûr.

Pour BPF_OR : dst = 0, on saute le OR. À l'exécution : 0 | K = K. Incohérence. Le vérificateur voit 0. Le CPU a K. Non sûr.

La fonction ne vérifie pas l'opcode. Elle a été écrite pour AND - où sauter l'instruction équivaut à l'exécuter avec dst = 0 - et a été appliquée à OR aussi. Pour OR, cette équivalence ne tient pas.

Déclencher la divergence

Télécharger l’outil