From: Yuan Chen bpf_ma_set_dtor() duplicates the map's btf_record for the bpf_mem_alloc destructor. For kptr fields backed by the program BTF (MEM_ALLOC kptrs, e.g. objects allocated with bpf_obj_new()/bpf_percpu_obj_new()), btf_record_dup() only borrows the reference, matching what btf_parse_fields() did for the map's own record. The duplicated record, however, is released later from the deferred bpf_mem_alloc destructor workqueue (free_mem_alloc_deferred), by which time the program BTF may already have been freed: bpf_map_free() drops the map's own reference, and the RCU callback can run before the workqueue. Reading field->kptr.btf in btf_record_free() (via btf_is_kernel()) is then a use-after-free, detected by KASAN as "slab-use-after-free in btf_is_kernel" when a map with a MEM_ALLOC kptr field is destroyed. Hold a reference on program BTF for the lifetime of the duplicated record and drop it right before the record is freed. The last btf_put() only schedules the object for RCU destruction, so btf_record_free() can still safely read the field descriptors. The rhtab kptr selftests exercise this path on every map teardown and triggered the bug under KASAN; with this fix they pass cleanly. Fixes: 1df97a7453ee ("bpf: Register dtor for freeing special fields") Signed-off-by: Yuan Chen --- kernel/bpf/hashtab.c | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 0df8db27cd8c..b8df2bc9a9a0 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -493,10 +493,54 @@ static void htab_pcpu_mem_dtor(void *obj, void *ctx) bpf_obj_free_fields(hrec->record, per_cpu_ptr(pptr, cpu)); } +/* + * bpf_ma_set_dtor() duplicates the map's btf_record. For kptr fields whose + * btf is the program BTF (MEM_ALLOC kptrs, e.g. objects allocated with + * bpf_obj_new()/bpf_percpu_obj_new()) btf_record_dup() only borrows the + * reference, like btf_parse_fields() did for the map's own record. The + * duplicated record is released later from the deferred bpf_mem_alloc + * destructor workqueue, by which time the program BTF may already have been + * freed (the map dropped its own reference in bpf_map_free()), so reading + * field->kptr.btf there would be a use-after-free. + * + * Hold a reference on non-kernel (program) BTF for the lifetime of the + * duplicated record and release it before the record is freed. After the + * last btf_put() the object is only destroyed after an RCU grace period, so + * btf_record_free() can still safely read the field descriptors. + */ +static void htab_record_prog_btf_ref(struct btf_record *rec, bool get) +{ + int i; + + if (IS_ERR_OR_NULL(rec)) + return; + + for (i = 0; i < rec->cnt; i++) { + const struct btf_field *field = &rec->fields[i]; + + switch (field->type) { + case BPF_KPTR_UNREF: + case BPF_KPTR_REF: + case BPF_KPTR_PERCPU: + case BPF_UPTR: + if (field->kptr.btf && !btf_is_kernel(field->kptr.btf)) { + if (get) + btf_get(field->kptr.btf); + else + btf_put(field->kptr.btf); + } + break; + default: + break; + } + } +} + static void htab_dtor_ctx_free(void *ctx) { struct htab_btf_record *hrec = ctx; + htab_record_prog_btf_ref(hrec->record, false); btf_record_free(hrec->record); kfree(ctx); } @@ -521,6 +565,7 @@ static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma, kfree(hrec); return err; } + htab_record_prog_btf_ref(hrec->record, true); bpf_mem_alloc_set_dtor(ma, dtor, htab_dtor_ctx_free, hrec); return 0; } -- 2.54.0