cloud's Blog

Security blog

View on GitHub
3 July 2026

CVE-2026-43456 : Type Confusion 19 ans dans le bonding Linux — LPE en 1 seconde

by cloud

CVE-2026-43456 est une vulnérabilité de type confusion dans le sous-système net/bonding du noyau Linux, découverte par Koike (CTO) et Toda (stagiaire) de GMO Cybersecurity by Ierae. Le code vulnérable a été introduit en 2007 (commit 1284cd3a2b74) et est resté inexploité pendant 19 ans. L’exploit atteint >99% de succès en moins d’une seconde pour une élévation de privilèges locale complète, ce qui a valu aux chercheurs 80 000 $+ de récompense via le programme kernelCTF de Google.

Caractéristiques principales :


TL;DR

Champ Valeur
CVE CVE-2026-43456
Impact Critique — LPE noyau, type confusion → exécution code arbitraire
Composant net/bondingbond_setup_by_slave()
Type CWE-843 — Type Confusion
Auteurs Koike (CTO) + Toda (stagiaire) — GMO Cybersecurity by Ierae (Japon)
Récompense >80 000 $ (kernelCTF)
Versions affectées Linux 2.6.24 → 6.12.77
Commit d’introduction 1284cd3a2b74 (2007)
Commit de correction 950803f72547 (mars 2026)
Prérequis CAP_NET_ADMIN (user namespace non privilégié)

1. La racine : une seule ligne fatale

1.1 La fonction bond_setup_by_slave()

Quand on attache un périphérique esclave (slave) à un bond device (agrégation de liens réseau), le noyau copie certains attributs du slave vers le bond pour assurer la transparence. Une ligne en particulier est problématique :

static void bond_setup_by_slave(struct net_device *bond_dev,
                                struct net_device *slave_dev)
{
    bool was_up = !!(bond_dev->flags & IFF_UP);

    dev_close(bond_dev);

    bond_dev->header_ops        = slave_dev->header_ops;   // ★ ← LA LIGNE

    bond_dev->type              = slave_dev->type;
    bond_dev->hard_header_len   = slave_dev->hard_header_len;
    bond_dev->needed_headroom   = slave_dev->needed_headroom;
    bond_dev->addr_len          = slave_dev->addr_len;
    ...
}

header_ops est un pointeur vers une table de fonctions qui manipulent les en-têtes de paquets. L’intention est louable : “le bond traite les en-têtes exactement comme le ferait le slave”. Le problème, c’est que ces fonctions accèdent à la zone de données privée (dev->priv) du périphérique — et le bond n’alloue pas la même structure que le slave.

1.2 Type confusion : struct bonding vs struct ip_tunnel

Le bond device alloue sizeof(struct bonding) pour son dev->priv :

struct rtnl_link_ops bond_link_ops __read_mostly = {
    .kind           = "bond",
    .priv_size      = sizeof(struct bonding),   // ← structure bonding
    .setup          = bond_setup,
    ...
};

Un tunnel GRE, utilisé comme slave, s’attend à trouver sizeof(struct ip_tunnel) :

struct ip_tunnel *t = netdev_priv(dev);  // ← lit un bonding comme si c'était ip_tunnel

Les deux structures n’ont rien à voir :

struct bonding {                       // offset  | taille
    struct   net_device *dev;          // 0x0000  | 0x0008
    struct   slave __rcu *curr_active_slave;
    struct   slave __rcu *current_arp_slave;
    struct   slave __rcu *primary_slave;
    struct   bond_up_slave __rcu *usable_slaves;
    struct   bond_up_slave __rcu *all_slaves;
    ...
    int      (*recv_probe)(...);       // 0x0038  | 0x0008  ← FONCTION POINTEUR
    ...
};

struct ip_tunnel {                     // offset  | taille
    struct ip_tunnel __rcu  *next;     // 0x0000  | 0x0008
    struct hlist_node hash_node;
    struct net_device   *dev;
    ...
    struct in6_addr laddr;             // 0x0038  | 0x0010  ← ADRESSE IPv6
    ...
};

Même offset 0x38, interprétation totalement différente : une adresse de fonction noyau (recv_probe) devient une adresse IPv6 source (laddr). C’est le cœur de la type confusion.


2. L’exploit en deux étapes

L’exploitation complète contourne KASLR puis exécute du code arbitraire, le tout en moins d’une seconde.

2.1 Step 1 : Fuite KASLR via IP6GRE

En attachant un tunnel IP6GRE comme slave du bond :

// Ce que le bonding a VRAIMENT à offset 0x38 :
bond->recv_probe = bond_rcv_validate;   // adresse fonction noyau

// Ce que IP6GRE LIT à offset 0x38 :
struct in6_addr laddr;                  // → fuit l'adresse noyau comme une IPv6 !

2.2 Step 2 : Exécution de code arbitraire via GRE

2.2.1 Buffer overflow dans skb_push()

En utilisant GRE (IPv4) cette fois comme slave :

static int ipgre_header(struct sk_buff *skb, struct net_device *dev, ...)
{
    struct ip_tunnel *t = netdev_priv(dev);  // ← type confusion : t = bonding
    ...
    iph = skb_push(skb, t->hlen + sizeof(*iph));
    greh = (struct gre_base_hdr *)(iph + 1);
    greh->flags = gre_tnl_flags_to_gre_flags(t->parms.o_flags);
}

t->hlen dans struct ip_tunnel est normalement ≥ 4. Mais comme t pointe vers struct bonding où l’offset correspondant vaut 0, skb_push() ne recule pas assez. greh se retrouve alors à une position qui dépasse la zone de données normale du skb.

2.2.2 Condition spatiale : aligner skb->data sur skb_shared_info

Sous certaines conditions, skb->data (après le push tronqué) pointe vers le début de struct skb_shared_info, qui se trouve toujours à la fin du buffer du skb (aligné sur une page). skb_shared_info::flags est à l’offset 0 — exactement comme gre_base_hdr::flags.

struct gre_base_hdr {     // offset | taille
    __be16 flags;         // 0x00   | 0x02
    ...
};

struct skb_shared_info {  // offset | taille
    __u8 flags;           // 0x00   | 0x01
    ...
};

Quand greh->flags est écrit, la valeur gre_tnl_flags_to_gre_flags(t->parms.o_flags) = 0x07ff active le bit SKBFL_ZEROCOPY_ENABLE (bit 0) dans skb_shared_info::flags.

2.2.3 Détournement de callback

Une fois SKBFL_ZEROCOPY_ENABLE activé, le noyau croit que ce skb a un ubuf_info (utilisé pour la notification zerocopy) :

static inline void skb_zcopy_clear(struct sk_buff *skb, bool success)
{
    struct ubuf_info *uarg = skb_zcopy(skb);
    if (uarg)                          // ← devient non-NULL grâce au flag
        uarg->callback(skb, uarg, success);  // ← IP contrôlé !
}

Le champ uarg->callback est lu à une position que l’attaquant peut contrôler dans le skb → Instruction Pointer détourné → exécution de code arbitraire → élévation de privilèges.

2.3 Le tour de force : 329 tunnels GRE chaînés

Pour que la condition spatiale fonctionne (aligner skb->data pile sur skb_shared_info), les chercheurs ont dû fabriquer un bond device dont LL_RESERVED_SPACE = 0x3ec0 exactement.

Ils ont chaîné 329 tunnels GRE successifs :

if0(GRE+FOU) → if1(GRE+FOU) → ... → if7(GRE+FOU)
    → if8(GRE) → if9(GRE) → ... → if328(GRE)
    → enslavé au bond device

Chaque tunnel ajoute son tunnel->hlen + sizeof(struct iphdr) plus le hard_header_len + needed_headroom du device inférieur :

FOU GRE    : tunnel->hlen = 0xc, t_hlen = 0x20, hard_header_len = 0x20
plain GRE  : tunnel->hlen = 0x4, t_hlen = 0x18, hard_header_len = 0x18

Après 8 FOU + 320 plain GRE :

needed_headroom = 0x3e98
LL_RESERVED_SPACE = align_down(0x3eb0, 0x10) + 0x10 = 0x3ec0  ✓

3. Pourquoi 19 ans sans être détectée ?

Cette question est centrale et la réponse est fascinante.

La condition spatiale (LL_RESERVED_SPACE = 0x3ec0) est extrêmement spécifique. Sans elle, le buffer overflow écrit dans du padding inutilisé entre la fin des données et le skb_shared_info — un espace non utilisé, aligné sur une page.

Conséquences :

C’est en tombant par hasard sur un crash syzkaller que les chercheurs ont pu remonter la piste jusqu’à la racine. Sans cet accident, la vulnérabilité serait probablement encore inconnue aujourd’hui.

Le vrai génie de cette découverte, c’est d’avoir relié un crash obscur (callback nul appelé) à une ligne de code vieille de 19 ans dans un sous-système complètement différent (net/bonding).


4. Correction et mitigation

4.1 Correctif upstream

Le correctif (commit 950803f72547, mergé dans mainline en mars 2026) empêche la copie des header_ops du slave vers le bond :

- bond_dev->header_ops = slave_dev->header_ops;
+ /* Ne pas copier header_ops — incompatible avec dev->priv du bond */

Les distributions majeures ont backporté le correctif.

4.2 Mitigations immédiates

Si vous ne pouvez pas appliquer le correctif :

# Option 1 : bloquer les user namespaces non privilégiés
echo 0 > /proc/sys/kernel/unprivileged_userns_clone
# (désactive aussi Rootless Docker, Podman rootless, etc.)

# Option 2 : désactiver le module bonding
echo "install bonding /bin/false" > /etc/modprobe.d/disable-bonding.conf
rmmod bonding 2>/dev/null

Le bonding est généralement fourni comme module et n’est pas chargé par défaut sur la plupart des distributions.

4.3 Détection

Si le bonding n’est pas utilisé sur votre système, vous êtes probablement déjà protégé. Vérifiez :

lsmod | grep bonding
ip link show type bond

5. Leçons

5.1 Une leçon d’architecture noyau

Cette vulnérabilité illustre un pattern récurrent de bugs de sécurité : l’hypothèse implicite que copier des pointeurs de fonctions entre structures de types différents est sûr parce que “l’interface est la même”. Dans un noyau monolithique où chaque sous-système a ses propres structures de données privées, ce genre de shortcut peut cacher une type confusion pendant presque deux décennies.

5.2 Le rôle de syzkaller et de l’IA

Les chercheurs notent que sans syzkaller, ils n’auraient jamais trouvé ce bug. Et sans l’aide de modèles de langage (en 2025 !), le Root Cause Analysis — remonter du crash callback jusqu’à la ligne bond_setup_by_slave() — aurait été extrêmement difficile.

5.3 L’exploitabilité cachée

Un bug peut exister pendant 19 ans sans être exploitable en pratique. Mais il suffit d’une chaîne de conditions (329 tunnels, LL_RESERVED_SPACE précis, type de protocole, version du noyau…) pour qu’il devienne une arme fiable à >99%.


6. Timeline

Date Événement
2007 Commit 1284cd3a2b74 introduit le bug dans bond_setup_by_slave()
~2025 Crash syzkaller repéré par l’équipe GMO
2025 H1 Root Cause Analysis assistée par IA
2026-03 Correctif mergé dans mainline Linux
2026-07-03 Publication du write-up public

Références

Source URL
Article original GMO Cybersecurity https://gmo-cybersecurity.com/blog/19-year-old-linux-kernel-zero-day/
kernelCTF (Google) https://google.github.io/security-research/kernelctf/rules.html
Commit d’introduction 1284cd3a2b74
Commit de correction 950803f72547

Have fun.

tags: security - linux - cve - exploit - lpe - kernel - bonding - typeconfusion - gre