History

bpf: Register dtor for freeing special fields

There is a race window where BPF hash map elements can leak special fields if the program with access to the map value recreates these special fields between the check_and_free_fields done on the map value and its eventual return to the memory allocator. Several ways were explored prior to this patch, most notably [0] tried to use a poison value to reject attempts to recreate special fields for map values that have been logically deleted but still accessible to BPF programs (either while sitting in the free list or when reused). While this approach works well for task work, timers, wq, etc., it is harder to apply the idea to kptrs, which have a similar race and failure mode. Instead, we change bpf_mem_alloc to allow registering destructor for allocated elements, such that when they are returned to the allocator, any special fields created while they were accessible to programs in the mean time will be freed. If these values get reused, we do not free the fields again before handing the element back. The special fields thus may remain initialized while the map value sits in a free list. When bpf_mem_alloc is retired in the future, a similar concept can be introduced to kmalloc_nolock-backed kmem_cache, paired with the existing idea of a constructor. Note that the destructor registration happens in map_check_btf, after the BTF record is populated and (at that point) avaiable for inspection and duplication. Duplication is necessary since the freeing of embedded bpf_mem_alloc can be decoupled from actual map lifetime due to logic introduced to reduce the cost of rcu_barrier()s in mem alloc free path in 9f2c6e96c65e ("bpf: Optimize rcu_barrier usage between hash map and bpf_mem_alloc."). As such, once all callbacks are done, we must also free the duplicated record. To remove dependency on the bpf_map itself, also stash the key size of the map to obtain value from htab_elem long after the map is gone. [0]: https://lore.kernel.org/bpf/20260216131341.1285427-1-mykyta.yatsenko5@gmail.com

Kumar Kartikeya Dwivedi committed 8 months ago

Commit 1df97a71 parent 6881af23 files · +140 −11Browse files

Unified · Split

3 files
  1. include/linux/bpf_mem_alloc.h +6 −0
  2. kernel/bpf/hashtab.c +87 −0
  3. kernel/bpf/memalloc.c +47 −11
include/linux/bpf_mem_alloc.h+6−0
@@ -14,6 +14,8 @@ struct bpf_mem_alloc { · 13 unmodified lines
1414 struct obj_cgroup *objcg;
1515 bool percpu;
1616 struct work_struct work;
17+ void (*dtor_ctx_free)(void *ctx);
18+ void *dtor_ctx;
1719 };
1820
1921 /* 'size != 0' is for bpf_mem_alloc which manages fixed-size objects.
@@ -32,6 +34,10 @@ int bpf_mem_alloc_percpu_init(struct bpf_mem_alloc *ma, struct obj_cgroup *objcg · 12 unmodified lines
3234 /* The percpu allocation with a specific unit size. */
3335 int bpf_mem_alloc_percpu_unit_init(struct bpf_mem_alloc *ma, int size);
3436 void bpf_mem_alloc_destroy(struct bpf_mem_alloc *ma);
37+void bpf_mem_alloc_set_dtor(struct bpf_mem_alloc *ma,
38+ void (*dtor)(void *obj, void *ctx),
39+ void (*dtor_ctx_free)(void *ctx),
40+ void *ctx);
3541
3642 /* Check the allocation size for kmalloc equivalent allocator */
3743 int bpf_mem_alloc_check_size(bool percpu, size_t size);
kernel/bpf/hashtab.c+87−0
@@ -125,6 +125,11 @@ struct htab_elem { · 124 unmodified lines
125125 char key[] __aligned(8);
126126 };
127127
128+struct htab_btf_record {
129+ struct btf_record *record;
130+ u32 key_size;
131+};
132+
128133 static inline bool htab_is_prealloc(const struct bpf_htab *htab)
129134 {
130135 return !(htab->map.map_flags & BPF_F_NO_PREALLOC);
@@ -457,6 +462,84 @@ static int htab_map_alloc_check(union bpf_attr *attr) · 326 unmodified lines
457462 return 0;
458463 }
459464
465+static void htab_mem_dtor(void *obj, void *ctx)
466+{
467+ struct htab_btf_record *hrec = ctx;
468+ struct htab_elem *elem = obj;
469+ void *map_value;
470+
471+ if (IS_ERR_OR_NULL(hrec->record))
472+ return;
473+
474+ map_value = htab_elem_value(elem, hrec->key_size);
475+ bpf_obj_free_fields(hrec->record, map_value);
476+}
477+
478+static void htab_pcpu_mem_dtor(void *obj, void *ctx)
479+{
480+ void __percpu *pptr = *(void __percpu **)obj;
481+ struct htab_btf_record *hrec = ctx;
482+ int cpu;
483+
484+ if (IS_ERR_OR_NULL(hrec->record))
485+ return;
486+
487+ for_each_possible_cpu(cpu)
488+ bpf_obj_free_fields(hrec->record, per_cpu_ptr(pptr, cpu));
489+}
490+
491+static void htab_dtor_ctx_free(void *ctx)
492+{
493+ struct htab_btf_record *hrec = ctx;
494+
495+ btf_record_free(hrec->record);
496+ kfree(ctx);
497+}
498+
499+static int htab_set_dtor(const struct bpf_htab *htab, void (*dtor)(void *, void *))
500+{
501+ u32 key_size = htab->map.key_size;
502+ const struct bpf_mem_alloc *ma;
503+ struct htab_btf_record *hrec;
504+ int err;
505+
506+ /* No need for dtors. */
507+ if (IS_ERR_OR_NULL(htab->map.record))
508+ return 0;
509+
510+ hrec = kzalloc(sizeof(*hrec), GFP_KERNEL);
511+ if (!hrec)
512+ return -ENOMEM;
513+ hrec->key_size = key_size;
514+ hrec->record = btf_record_dup(htab->map.record);
515+ if (IS_ERR(hrec->record)) {
516+ err = PTR_ERR(hrec->record);
517+ kfree(hrec);
518+ return err;
519+ }
520+ ma = htab_is_percpu(htab) ? &htab->pcpu_ma : &htab->ma;
521+ /* Kinda sad, but cast away const-ness since we change ma->dtor. */
522+ bpf_mem_alloc_set_dtor((struct bpf_mem_alloc *)ma, dtor, htab_dtor_ctx_free, hrec);
523+ return 0;
524+}
525+
526+static int htab_map_check_btf(const struct bpf_map *map, const struct btf *btf,
527+ const struct btf_type *key_type, const struct btf_type *value_type)
528+{
529+ struct bpf_htab *htab = container_of(map, struct bpf_htab, map);
530+
531+ if (htab_is_prealloc(htab))
532+ return 0;
533+ /*
534+ * We must set the dtor using this callback, as map's BTF record is not
535+ * populated in htab_map_alloc(), so it will always appear as NULL.
536+ */
537+ if (htab_is_percpu(htab))
538+ return htab_set_dtor(htab, htab_pcpu_mem_dtor);
539+ else
540+ return htab_set_dtor(htab, htab_mem_dtor);
541+}
542+
460543 static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
461544 {
462545 bool percpu = (attr->map_type == BPF_MAP_TYPE_PERCPU_HASH ||
@@ -2281,6 +2364,7 @@ const struct bpf_map_ops htab_map_ops = { · 1818 unmodified lines
22812364 .map_seq_show_elem = htab_map_seq_show_elem,
22822365 .map_set_for_each_callback_args = map_set_for_each_callback_args,
22832366 .map_for_each_callback = bpf_for_each_hash_elem,
2367+ .map_check_btf = htab_map_check_btf,
22842368 .map_mem_usage = htab_map_mem_usage,
22852369 BATCH_OPS(htab),
22862370 .map_btf_id = &htab_map_btf_ids[0],
@@ -2303,6 +2387,7 @@ const struct bpf_map_ops htab_lru_map_ops = { · 16 unmodified lines
23032387 .map_seq_show_elem = htab_map_seq_show_elem,
23042388 .map_set_for_each_callback_args = map_set_for_each_callback_args,
23052389 .map_for_each_callback = bpf_for_each_hash_elem,
2390+ .map_check_btf = htab_map_check_btf,
23062391 .map_mem_usage = htab_map_mem_usage,
23072392 BATCH_OPS(htab_lru),
23082393 .map_btf_id = &htab_map_btf_ids[0],
@@ -2482,6 +2567,7 @@ const struct bpf_map_ops htab_percpu_map_ops = { · 173 unmodified lines
24822567 .map_seq_show_elem = htab_percpu_map_seq_show_elem,
24832568 .map_set_for_each_callback_args = map_set_for_each_callback_args,
24842569 .map_for_each_callback = bpf_for_each_hash_elem,
2570+ .map_check_btf = htab_map_check_btf,
24852571 .map_mem_usage = htab_map_mem_usage,
24862572 BATCH_OPS(htab_percpu),
24872573 .map_btf_id = &htab_map_btf_ids[0],
@@ -2502,6 +2588,7 @@ const struct bpf_map_ops htab_lru_percpu_map_ops = { · 14 unmodified lines
25022588 .map_seq_show_elem = htab_percpu_map_seq_show_elem,
25032589 .map_set_for_each_callback_args = map_set_for_each_callback_args,
25042590 .map_for_each_callback = bpf_for_each_hash_elem,
2591+ .map_check_btf = htab_map_check_btf,
25052592 .map_mem_usage = htab_map_mem_usage,
25062593 BATCH_OPS(htab_lru_percpu),
25072594 .map_btf_id = &htab_map_btf_ids[0],
kernel/bpf/memalloc.c+47−11
@@ -102,6 +102,8 @@ struct bpf_mem_cache { · 101 unmodified lines
102102 int percpu_size;
103103 bool draining;
104104 struct bpf_mem_cache *tgt;
105+ void (*dtor)(void *obj, void *ctx);
106+ void *dtor_ctx;
105107
106108 /* list of objects to be freed after RCU GP */
107109 struct llist_head free_by_rcu;
@@ -260,12 +262,14 @@ static void free_one(void *obj, bool percpu) · 152 unmodified lines
260262 kfree(obj);
261263 }
262264
263−static int free_all(struct llist_node *llnode, bool percpu)
265+static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool percpu)
264266 {
265267 struct llist_node *pos, *t;
266268 int cnt = 0;
267269
268270 llist_for_each_safe(pos, t, llnode) {
271+ if (c->dtor)
272+ c->dtor((void *)pos + LLIST_NODE_SZ, c->dtor_ctx);
269273 free_one(pos, percpu);
270274 cnt++;
271275 }
@@ -276,7 +280,7 @@ static void __free_rcu(struct rcu_head *head) · 4 unmodified lines
276280 {
277281 struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu_ttrace);
278282
279− free_all(llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size);
283+ free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size);
280284 atomic_set(&c->call_rcu_ttrace_in_progress, 0);
281285 }
282286
@@ -308,7 +312,7 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c) · 25 unmodified lines
308312 if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) {
309313 if (unlikely(READ_ONCE(c->draining))) {
310314 llnode = llist_del_all(&c->free_by_rcu_ttrace);
311− free_all(llnode, !!c->percpu_size);
315+ free_all(c, llnode, !!c->percpu_size);
312316 }
313317 return;
314318 }
@@ -417,7 +421,7 @@ static void check_free_by_rcu(struct bpf_mem_cache *c) · 102 unmodified lines
417421 dec_active(c, &flags);
418422
419423 if (unlikely(READ_ONCE(c->draining))) {
420− free_all(llist_del_all(&c->waiting_for_gp), !!c->percpu_size);
424+ free_all(c, llist_del_all(&c->waiting_for_gp), !!c->percpu_size);
421425 atomic_set(&c->call_rcu_in_progress, 0);
422426 } else {
423427 call_rcu_hurry(&c->rcu, __free_by_rcu);
@@ -635,13 +639,13 @@ static void drain_mem_cache(struct bpf_mem_cache *c) · 211 unmodified lines
635639 * Except for waiting_for_gp_ttrace list, there are no concurrent operations
636640 * on these lists, so it is safe to use __llist_del_all().
637641 */
638− free_all(llist_del_all(&c->free_by_rcu_ttrace), percpu);
639− free_all(llist_del_all(&c->waiting_for_gp_ttrace), percpu);
640− free_all(__llist_del_all(&c->free_llist), percpu);
641− free_all(__llist_del_all(&c->free_llist_extra), percpu);
642− free_all(__llist_del_all(&c->free_by_rcu), percpu);
643− free_all(__llist_del_all(&c->free_llist_extra_rcu), percpu);
644− free_all(llist_del_all(&c->waiting_for_gp), percpu);
642+ free_all(c, llist_del_all(&c->free_by_rcu_ttrace), percpu);
643+ free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), percpu);
644+ free_all(c, __llist_del_all(&c->free_llist), percpu);
645+ free_all(c, __llist_del_all(&c->free_llist_extra), percpu);
646+ free_all(c, __llist_del_all(&c->free_by_rcu), percpu);
647+ free_all(c, __llist_del_all(&c->free_llist_extra_rcu), percpu);
648+ free_all(c, llist_del_all(&c->waiting_for_gp), percpu);
645649 }
646650
647651 static void check_mem_cache(struct bpf_mem_cache *c)
@@ -680,6 +684,9 @@ static void check_leaked_objs(struct bpf_mem_alloc *ma) · 32 unmodified lines
680684
681685 static void free_mem_alloc_no_barrier(struct bpf_mem_alloc *ma)
682686 {
687+ /* We can free dtor ctx only once all callbacks are done using it. */
688+ if (ma->dtor_ctx_free)
689+ ma->dtor_ctx_free(ma->dtor_ctx);
683690 check_leaked_objs(ma);
684691 free_percpu(ma->cache);
685692 free_percpu(ma->caches);
@@ -1014,3 +1021,32 @@ int bpf_mem_alloc_check_size(bool percpu, size_t size) · 328 unmodified lines
10141021
10151022 return 0;
10161023 }
1024+
1025+void bpf_mem_alloc_set_dtor(struct bpf_mem_alloc *ma, void (*dtor)(void *obj, void *ctx),
1026+ void (*dtor_ctx_free)(void *ctx), void *ctx)
1027+{
1028+ struct bpf_mem_caches *cc;
1029+ struct bpf_mem_cache *c;
1030+ int cpu, i;
1031+
1032+ ma->dtor_ctx_free = dtor_ctx_free;
1033+ ma->dtor_ctx = ctx;
1034+
1035+ if (ma->cache) {
1036+ for_each_possible_cpu(cpu) {
1037+ c = per_cpu_ptr(ma->cache, cpu);
1038+ c->dtor = dtor;
1039+ c->dtor_ctx = ctx;
1040+ }
1041+ }
1042+ if (ma->caches) {
1043+ for_each_possible_cpu(cpu) {
1044+ cc = per_cpu_ptr(ma->caches, cpu);
1045+ for (i = 0; i < NUM_CACHES; i++) {
1046+ c = &cc->cache[i];
1047+ c->dtor = dtor;
1048+ c->dtor_ctx = ctx;
1049+ }
1050+ }
1051+ }
1052+}