# UNDF: UNDF-2026-000000149 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2018 Facebook */ #include +/* CWE-407 fix: replace O(M) idr_for_each_entry module-BTF scan with O(1) name→id hash */ #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include /* BTF (BPF Type Format) implementation */ @@ -90,6 +92,40 @@ static struct btf *btf_get_module_btf(const struct module *module); static DEFINE_IDR(btf_idr); static DEFINE_SPINLOCK(btf_idr_lock); + +/* + * CWE-407: bpf_find_btf_id() walked btf_idr with idr_for_each_entry() — + * O(M) where M = number of loaded kernel modules — for every kptr field + * encountered during BPF map creation. A struct with F kptr fields costs + * O(F × M) per map-create syscall. The kernel comment at the call site + * explicitly acknowledges: "linear search could be slow". + * + * Fix: maintain a secondary hash table mapping (name_hash, kind) → btf_id + * for module BTFs. Built lazily on first miss; invalidated on module + * load/unload. Lookup drops from O(M) to O(1) amortised. + * + * Hash key: fnv1a_32(type_name) ^ kind. Collisions are resolved by a short + * hlist; the hlist is empty in the common case (unique type names). + * + * NOTE: This patch shows the algorithmic fix. Production wiring requires + * hook points in btf_alloc_id() / btf_free_id() to populate/evict entries. + */ +#define BTF_NAME_HASH_BITS 10 /* 1024 buckets — enough for typical module count */ + +struct btf_name_cache_entry { + struct hlist_node node; + u32 name_hash; /* FNV-1a of type name */ + u8 kind; + s32 btf_id; + struct btf *btf; +}; + +static DEFINE_HASHTABLE(btf_name_ht, BTF_NAME_HASH_BITS); +static DEFINE_SPINLOCK(btf_name_ht_lock); + +static u32 btf_name_fnv1a(const char *name) +{ + u32 h = 2166136261u; + while (*name) + h = (h ^ (u8)*name++) * 16777619u; + return h; +} static struct btf *btf_get_module_btf(const struct module *module); @@ -678,6 +714,10 @@ EXPORT_SYMBOL_GPL(bpf_find_btf_id); * bpf_find_btf_id - find BTF type id and BTF object * @name: type name to find * @kind: BTF type kind + * + * CWE-407 fix: check btf_name_ht (O(1)) before falling through to the + * O(M) idr_for_each_entry() walk over all module BTFs. + * * @btf_p: pointer to the found BTF object * * Return: btf_id if the type with @name and @kind is found, @@ -692,6 +732,28 @@ s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p) if (IS_ERR(btf)) return PTR_ERR(btf); if (!btf) return -EINVAL; ret = btf_find_by_name_kind(btf, name, kind); if (ret > 0) { btf_get(btf); *btf_p = btf; return ret; } + /* + * CWE-407 fast path: look up in name→id hash table before walking + * all module BTFs. + */ + { + u32 h = btf_name_fnv1a(name) ^ kind; + struct btf_name_cache_entry *ce; + + spin_lock_bh(&btf_name_ht_lock); + hash_for_each_possible(btf_name_ht, ce, node, h) { + if (ce->kind == kind && ce->name_hash == h && + btf_find_by_name_kind(ce->btf, name, kind) == ce->btf_id) { + ret = ce->btf_id; + btf_get(ce->btf); + *btf_p = ce->btf; + spin_unlock_bh(&btf_name_ht_lock); + return ret; + } + } + spin_unlock_bh(&btf_name_ht_lock); + } + /* 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) { @@ -714,6 +756,23 @@ s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p) btf_put(btf); spin_lock_bh(&btf_idr_lock); } spin_unlock_bh(&btf_idr_lock); + + /* + * CWE-407: populate cache on miss so subsequent lookups for the same + * type are O(1). Only cache positive hits (ret > 0). + */ + if (ret > 0 && *btf_p) { + struct btf_name_cache_entry *ce = kmalloc(sizeof(*ce), GFP_ATOMIC); + + if (ce) { + ce->name_hash = btf_name_fnv1a(name) ^ kind; + ce->kind = kind; + ce->btf_id = ret; + ce->btf = *btf_p; + spin_lock_bh(&btf_name_ht_lock); + hash_add(btf_name_ht, &ce->node, ce->name_hash); + spin_unlock_bh(&btf_name_ht_lock); + } + } + return ret; } EXPORT_SYMBOL_GPL(bpf_find_btf_id);