java-topology/docs/tickets/linux-0006-btf-module-scan-hash.md
russell@unturf.com 998e2b7b0f linux: fix ticket doc numbering; add missing docs for 0001/0005/0006/0007/0008
- 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)
2026-04-04 11:41:36 -04:00

95 lines
3.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```c
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_CREATE` with a value type containing kptr fields (BPF_TYPE_KPTR,
BPF_TYPE_KPTR_REF): one `bpf_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.
```c
/* 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_CREATE` with 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`