139 lines
4.3 KiB
Diff
139 lines
4.3 KiB
Diff
--- 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 <linux/kernel.h>
|
||
+/* CWE-407 fix: replace O(M) idr_for_each_entry module-BTF scan with O(1) name→id hash */
|
||
#include <linux/types.h>
|
||
#include <linux/bpf.h>
|
||
#include <uapi/linux/bpf.h>
|
||
@@ -18,6 +19,7 @@
|
||
#include <linux/btf_ids.h>
|
||
#include <linux/vmalloc.h>
|
||
#include <linux/moduleparam.h>
|
||
+#include <linux/hashtable.h>
|
||
|
||
/* 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);
|