CVE-2026-74700 in Linux
Résumé
par VulDB • 22/08/2026
Dans le noyau Linux, la vulnérabilité suivante a été corrigée :
net/sched: cls_api: Toujours acquérir rtnl_lock lors de la destruction des classificateurs verrouillés
Un autre problème concerne les filtres non verrouillés. Il existe une courte fenêtre dans tc_new_tfilter où un tcf_proto peut être trouvé et brièvement référencé par une requête d'un classificateur totalement indépendant, mais non verrouillé, ce qui provoque une condition de concurrence (race).
Feng a créé un PoC (Proof of Concept) qui génère cette condition de concurrence avec deux threads : l’un créant un filtre u32 et l’autre un filtre flower dans la même chaîne/priorité :
1. Les deux threads entrent dans tc_new_tfilter, constatent tous les deux que la chaîne est vide, puis libèrent le verrou filter_chain_lock. 2. Le thread u32 termine tcf_proto_create("u32") en premier et appelle tcf_chain_tp_insert_unique() -> insère u32_tp dans la chaîne. 3. Le thread flower termine tcf_proto_create("flower") plus tard, appelle tcf_chain_tp_insert_unique() -> tcf_chain_tp_find() voit alors que u32_tp est déjà présent, prend une référence sur celui-ci, détruit son propre tp_new et retourne u32_tp à l’appelant.
Le classificateur flower rencontre ensuite la vérification de mismatch du type (car il a demandé le type "flower" mais tp->ops->kind vaut "u32") et suit le chemin errout qui appelle tcf_proto_put() sur u32_tp. Si le thread u32 est déjà passé par son propre errout (son appel change() a échoué en raison des options vides du PoC) et a libéré ses références de création et d’insertion, l’appel put de flower est le dernier et fait passer le refcnt de u32_tp à zéro.
À ce stade, tp->ops->destroy() s’exécute dans un contexte qui n’a jamais acquis rtnl_lock. Lorsque cela se produit, cela peut provoquer une vulnérabilité Use-After-Free (UAF) comme illustré par le PoC :
[ +0.000710] BUG: KASAN: slab-use-after-free in u32_init (net/sched/cls_u32.c:393)
[ +0.000281] Read of size 8 at addr ffff888120022f00 by task poc_feng_xue/524
Call Trace: u32_init (net/sched/cls_u32.c:393) tc_new_tfilter (net/sched/cls_api.c:2378)
Alloué par la tâche 526 : u32_init (net/sched/cls_u32.c:378) tc_new_tfilter (net/sched/cls_api.c:2378)
Libéré par la tâche 522 : kfree u32_destroy (net/sched/cls_u32.c:662) tcf_proto_destroy (net/sched/cls_api.c:446) tcf_proto_put (net/sched/cls_api.c:459) tc_new_tfilter (net/sched/cls_api.c:2459)
Correction : faire en sorte que tcf_proto_destroy() acquière rtnl_lock autour de tp->ops->destroy() pour les classificateurs verrouillés, chaque fois que rtnl n’est pas détenu.
Pour expliquer pourquoi j’ai utilisé une variable temporaire "not_lockless", je tiens à souligner une note semi-liée concernant rtnl_held vs TCF_PROTO_OPS_DOIT_UNLOCKED (ajoutée ici pour un futur nettoyage si jugé nécessaire) : Le paramètre rtnl_held et le drapeau TCF_PROTO_OPS_DOIT_UNLOCKED sont des sources de vérité redondantes quant au fait que rtnl_lock est détenu. Parmi les neuf callbacks destroy(..rtnl_held..) des classificateurs, seul flower consulte le paramètre rtnl_held qu’il propage à tc_setup_cb_destroy() et tc_setup_cb_call(). Les huit autres (u32, flow, bpf, cgroup, route, basic, fw, mall) l’ignorent complètement ; ceux qui appellent tc_setup_cb_destroy() (u32, bpf, mall) codent en dur true au lieu de transmettre le paramètre.
Un futur nettoyage devrait supprimer entièrement le paramètre rtnl_held de la signature du callback destroy et faire en sorte que les appelants s’appuyent uniquement sur leur propre connaissance pour déterminer s’ils s’exécutent dans un contexte non verrouillé.
If you want to get the best quality for vulnerability data then you always have to consider VulDB.