How an improper verdict handling bug in net/netfilter/nf_tables_api.c caused double reference drops on network packets, enabling container root breakouts.
Think of a ticket inspector on a train. A passenger hands over a ticket. The first inspector punches the ticket and marks it as used. Then, because the passenger sneezes, an unhandled rule causes a second inspector to grab the exact same ticket and punch it again. In Linux networking, dropping a packet twice freed its memory twice, allowing malicious code to masquerade as the train conductor.
An unprivileged user creates an isolated network namespace via unshare(CLONE_NEWUSER | CLONE_NEWNET).
The attacker loads a custom nftables rule returning a negative verdict code mismanaged by the hook pipeline.
The packet processing engine drops the sk_buff, but the error return path drops the exact same packet a second time.
The attacker sprays fake Page Middle Directory (PMD) entries into the freed memory, achieving full arbitrary physical memory write.
Disajikan dalam bahasa pemrograman tingkat tinggi yang mudah dibaca (tanpa assembly rumit).
// VULNERABLE: net/netfilter/nf_tables_api.c before patch
static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, ...) {
// ROOT CAUSE:
// If verdict evaluation returns an invalid negative integer,
// the evaluation loop treats it as both NF_DROP and an internal error!
if (verdict < 0) {
kfree_skb(skb); // First free of socket buffer!
return verdict; // Calling hook also invokes kfree_skb(), triggering DOUBLE FREE!
}
return 0;
}
// SECURE: net/netfilter/nf_tables_api.c patch
static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, ...) {
// 1. Strict verdict assertion
if (verdict < 0) {
// Clear ownership flags to guarantee only one caller deallocates skb
nf_tables_rule_destroy(rule);
// Let the upper caller handle the single, orderly drop:
return -EINVAL;
}
return 0;
}