- Renamed linux-0001-audit-filter-inodes → linux-0002 (matches patch file reality) - Renamed linux-0002-dev-alloc-name → linux-0003 - Renamed linux-0003-neigh-parms → linux-0004 - Added linux-0001-headerdep-hash.md (scripts/headerdep.pl detect_cycles CWE-407) - Added linux-0005-component-find-quadratic.md (drivers/base/component.c) - Added linux-0006-btf-module-scan-hash.md (kernel/bpf/btf.c bpf_find_btf_id) - Added linux-0007-pktgen-thread-dev-xarray.md (net/core/pktgen.c) - Added linux-0008-taskstats-listener-hashset.md (kernel/taskstats.c)
3.4 KiB
linux-0006: bpf_find_btf_id — O(F×M) idr_for_each_entry scan per BPF map create
File: kernel/bpf/btf.c
Function: bpf_find_btf_id()
Severity: HIGH — triggered on every BPF map creation involving kptr fields
CWE: CWE-407 (Inefficient Algorithmic Complexity)
Code
s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p)
{
/* ... check vmlinux BTF first (fast) ... */
/* If name is not found in vmlinux's BTF then search in module's BTFs */
spin_lock_bh(&btf_idr_lock);
idr_for_each_entry(&btf_idr, btf, id) { // O(M) — all loaded modules
/* ... btf_find_by_name_kind(btf, name, kind) ... */
}
spin_unlock_bh(&btf_idr_lock);
}
The kernel comment at the call site explicitly acknowledges: "linear search could be slow".
Every kptr field in a BPF map struct requires one call to bpf_find_btf_id().
A struct with F kptr fields costs O(F × M) idr iterations per BPF_MAP_CREATE syscall.
Complexity
| Variable | Meaning |
|---|---|
| F | Number of kptr fields in the BPF map value type |
| M | Number of kernel modules loaded with BTF |
On a production Kubernetes node running eBPF observability tools:
- M=200 modules loaded (driver modules, filesystem modules, network modules)
- F=10 kptr fields per BPF map type
- Every
BPF_MAP_CREATE: 200 × 10 = 2,000 idr iterations
eBPF programs compiled with complex struct types (e.g., Cilium's connection
tracking maps) issue multiple BPF_MAP_CREATE calls during program load.
When Triggered
BPF_MAP_CREATEwith a value type containing kptr fields (BPF_TYPE_KPTR, BPF_TYPE_KPTR_REF): onebpf_find_btf_id()call per field.bpftool map create, Cilium agent startup, BCC tools with kptr maps.- Kernel module load/unload (btf_idr changes) does NOT invalidate the slow path — it just makes future misses equally slow.
Root Cause
btf_idr is an IDR (integer to pointer map) optimised for sequential integer
allocation, not name-based lookup. idr_for_each_entry() visits every BTF
object in registration order. There is no secondary name index.
The vmlinux BTF check (done first) is efficient — it calls btf_find_by_name_kind
directly on the vmlinux BTF object. Only the module BTF path is slow.
Fix
Add a secondary DECLARE_HASHTABLE(btf_name_ht, 10) (1024 buckets) in btf.c.
Cache entries store: name_hash ^ kind, btf_id, btf *, and a name copy.
bpf_find_btf_id() checks the cache before the idr walk. On miss, the idr walk
runs as before, then the result is inserted into the cache. On module unload,
btf_free_id() evicts the corresponding cache entry.
/* Fast path — O(1) on cache hit */
u32 key = btf_name_hash(name, kind);
hash_for_each_possible(btf_name_ht, ce, node, key) {
if (ce->key == key && ce->kind == kind &&
strncmp(ce->name, name, sizeof(ce->name)) == 0) {
*btf_p = ce->btf;
btf_get(*btf_p);
return ce->btf_id; /* no re-scan */
}
}
/* Slow fallback — O(M) idr walk on cache miss */
idr_for_each_entry(&btf_idr, btf, id) { ... }
/* Populate cache on positive hit */
Impact
- Each
BPF_MAP_CREATEwith kptr fields pays O(F × M) in the uncached case. - Cilium on a loaded node: F=8 fields × M=200 modules = 1600 idr iterations per map create, versus 8 hash probes on warm cache.
- eBPF program hot-reload (common in CI/CD pipelines): O(F × M) per reload event.
Patch
See defects/linux/patch/linux-0006-btf-module-scan-hash.patch