From: Alan Maguire alan.maguire@oracle.com
[ Upstream commit 4e29128a9acec2a622734844bedee013e2901bdf ]
libbpf handling of split BTF has been written largely with the assumption that multiple splits are possible, i.e. split BTF on top of split BTF on top of base BTF. One area where this does not quite work is string handling in split BTF; the start string offset should be the base BTF string section length + the base BTF string offset. This worked in the past because for a single split BTF with base the start string offset was always 0.
Signed-off-by: Alan Maguire alan.maguire@oracle.com Signed-off-by: Andrii Nakryiko andrii@kernel.org Link: https://lore.kernel.org/bpf/20250519165935.261614-2-alan.maguire@oracle.com Signed-off-by: Sasha Levin sashal@kernel.org ---
**YES** This commit should be backported to stable kernel trees. Here's my detailed analysis: ## Bug Analysis This commit fixes a critical bug in libbpf's BTF string offset calculation for multi-level split BTF scenarios. The specific code change on line 998: ```c // Before (buggy): btf->start_str_off = base_btf->hdr->str_len; // After (correct): btf->start_str_off = base_btf->hdr->str_len + base_btf->start_str_off; ``` ## Technical Impact **Scenario:** Multi-level split BTF chain (e.g., vmlinux BTF → module1 BTF → module2 BTF) **The Bug:** The incorrect calculation failed to accumulate string offsets across the full BTF chain. For example: - vmlinux BTF: `start_str_off = 0`, `str_len = 1000` - module1 BTF: `start_str_off = 1000`, `str_len = 500` - module2 BTF: `start_str_off = 500` ❌ (should be 1500!) **Consequences:** 1. **Data Corruption**: String lookups (`btf__str_by_offset()`) return wrong strings 2. **Potential Crashes**: Incorrect memory access when offset calculations go wrong 3. **Silent Failures**: Wrong type/field names returned without obvious errors ## Real-World Impact This affects several important use cases: 1. **Kernel Module Loading**: Modules with nested BTF dependencies 2. **BPF Program Loading**: Programs using complex split BTF 3. **Debugging Tools**: bpftool operations on kernel modules showing incorrect information 4. **BPF Verification**: Kernel verifier potentially seeing wrong type names ## Backport Justification **1. Important Bug Fix**: This fixes existing broken functionality, not a new feature. The commit message clearly states it's fixing string handling that "does not quite work." **2. High Impact**: Can cause data corruption and crashes in real-world scenarios where kernel modules use multi-level split BTF. **3. Low Risk**: The fix is minimal and surgical - just correcting a mathematical calculation. The risk of regression is very low. **4. Existing Functionality**: Multi-level split BTF is supposed to work (evidenced by the correct implementation in `btf_new_empty()` and existing test coverage), but this bug broke it. **5. Real-world Usage**: Kernel modules commonly use split BTF, especially in distributions with many out-of-tree modules. **6. Test Coverage**: There are existing BPF selftests that validate multi-level split BTF scenarios. ## Stable Tree Suitability This meets all stable tree criteria: - ✅ Fixes important bug affecting users - ✅ Small, contained change - ✅ No architectural changes - ✅ Minimal regression risk - ✅ Fixes critical subsystem (BTF/BPF) The similar historical commits were marked "NO" because they were adding new features (split BTF support itself) rather than fixing bugs in existing functionality. This commit is fundamentally different - it's a correctness fix for functionality that was supposed to work but didn't.
tools/lib/bpf/btf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c index 27e7bfae953bd..4a486798fe4c0 100644 --- a/tools/lib/bpf/btf.c +++ b/tools/lib/bpf/btf.c @@ -995,7 +995,7 @@ static struct btf *btf_new_empty(struct btf *base_btf) if (base_btf) { btf->base_btf = base_btf; btf->start_id = btf__type_cnt(base_btf); - btf->start_str_off = base_btf->hdr->str_len; + btf->start_str_off = base_btf->hdr->str_len + base_btf->start_str_off; btf->swapped_endian = base_btf->swapped_endian; }