[BUG] bpf: module BTF accepts and publishes a non-VAR DATASEC member

From: Puranam, Ananta Srikar

Date: Sat Nov 07 2026 - 03:35:07 EST


Hi,

I'm Ananta Srikar Puranam, a PhD student at Washington State University.
I'm reporting this bug on behalf of my team: Dr. Swarn Priya and
Dr. Basavesh Ammanaghatta Shivakumar (both Cc'd).

btf_parse_module() accepts module BTF in which a DATASEC member
references an INT directly instead of a VAR, and the kernel then
publishes that BTF in /sys/kernel/btf/<module>. The full type
resolver rejects the same relationship ("Not a VAR kind member"), but
btf_parse_module() never runs it.

Trigger
=======

A matched pair built from one minimal external module with a single
per-cpu int, so that pahole emits, in the module's distilled split BTF:

control.ko INT (distilled base) -> VAR -> DATASEC '.data..percpu'
invalid.ko identical, except the DATASEC member's type is the base
INT instead of the VAR (one 4-byte field changed; the VAR
itself is still present)

Observed (current mainline, x86_64)
===================================

Both modules load (insmod exit 0) and both publish
/sys/kernel/btf/btf_datasec_repro (199 bytes). Decoding the published
BTF in the guest with bpftool from the same tree:

control:
[152041] DATASEC '.data..percpu' size=4 vlen=1
type_id=152044 offset=0 size=4 (VAR 'btf_datasec_target')
invalid:
[152041] DATASEC '.data..percpu' size=4 vlen=1
type_id=107629 offset=0 size=4 (INT 'int')

type_id 107629 is vmlinux's 'int', i.e. the distilled base reference
after relocation. The invalid module's BTF also appears in
"bpftool btf list". There is no oops, warning, or BTF error message.

Tested
======

- Mainline a91337fb5e8e ("Merge tag 'configfs-7.4-rc2' ..."),
7.4.0-rc1-00590-ga91337fb5e8e, x86_64 under KVM:
non-VAR twin accepted and its BTF published in 4/4 loads;
control accepted and published in 4/4
- Earlier validation on the same commit (separate build and guest):
the same result for a matched distilled split-BTF pair.

Cause
=====

btf_parse_module() parses the distilled base and the split module BTF,
relocates base references with btf_relocate(), and returns the BTF
after metadata validation. It does not call btf_check_all_types(), so
btf_datasec_resolve() never runs on module BTF, and its check that each
DATASEC member resolves to a VAR:

if (!var_type || !btf_type_is_var(var_type)) {
btf_verifier_log_vsi(env, v->t, vsi,
"Not a VAR kind member");
return -EINVAL;
}

is skipped. The invalid relationship therefore remains in the
published module BTF.

This has been the case since module BTF loading was added in:

36e68442d1af ("bpf: Load and verify kernel module BTFs")

which is in v5.11-rc1 and later.

Impact
======

Triggering requires CONFIG_DEBUG_INFO_BTF_MODULES=y and permission to
load a module (CAP_SYS_MODULE). I observed inconsistent validation and
publication of malformed type metadata, not a crash, memory corruption,
privilege escalation, or an unprivileged boundary crossing. Per
Documentation/process/threat-model.rst I am treating this as a BTF
validation bug, not a vulnerability. I have not checked what BPF
programs or tools do when they consume this BTF.

Setup
=====

Host: Ubuntu 26.04.1 LTS, x86_64
Toolchain: Ubuntu GCC 15.2.0 / GNU ld 2.46, pahole v1.31
(built with O=)
Config: x86_64_defconfig plus kernel/configs/kvm_guest.config
plus
CONFIG_BPF_SYSCALL=y
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DEBUG_INFO_BTF_MODULES=y
(resolved with olddefconfig; full .config available)
Module: out-of-tree, built with M= against the same build tree,
so kbuild emits .BTF plus a distilled .BTF.base; the
invalid twin is the built .ko with only the DATASEC
member's btf_var_secinfo.type rewritten
QEMU: qemu-system-x86_64 10.2.1 with KVM, -smp 16 -m 8G
Guest: Ubuntu 26.04 amd64 rootfs (debootstrap); modules loaded
with insmod over SSH; BTF decoded with tools/bpf/bpftool
built from the same tree (v7.9.0, libbpf 1.9)

The source reproducer (module and BTF-rewriting script) is available
privately on request. Per Documentation/process/security-bugs.rst I
have not posted it to the list.

Fix
===

A proposed series runs the existing btf_check_all_types() in
btf_parse_module() after distilled base references have been
relocated to their final vmlinux BTF IDs, and adds matched valid and
invalid module fixtures to the BPF selftests. On a91337fb5e8e with
the fix applied (x86_64 KVM), the control still loaded and the non-VAR
twin was rejected with:

BPF: Not a VAR kind member
failed to validate module [btf_datasec_repro] BTF: -22

The focused selftest fixtures built and passed in a direct KVM run.
The full test_progs build could not be completed on that machine
because its Clang 18 lacks address-space support needed by unrelated
current arena tests. I will send the series separately.

Tool transparency
=================

The root-cause analysis, original reproducer and proposed series were
prepared with the help of an AI coding assistant (OpenAI Codex). The
QEMU/KVM reproduction environment and the reproducer used for the
results above were set up with Anthropic's Claude Code. I re-ran the
reproduction myself and have checked the statements in this report.

Thanks,
Srikar