Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

bpf: Release module BTF IDR before module unload

Gregory reported in [0] that the global_map_resize test when run in
repeatedly ends up failing during program load. This stems from the fact
that BTF reference has not dropped to zero after the previous run's
module is unloaded, and the older module's BTF is still discoverable and
visible. Later, in libbpf, load_module_btfs() will find the ID for this
stale BTF, open its fd, and then it will be used during program load
where later steps taking module reference using btf_try_get_module()
fail since the underlying module for the BTF is gone.

Logically, once a module is unloaded, it's associated BTF artifacts
should become hidden. The BTF object inside the kernel may still remain
alive as long its reference counts are alive, but it should no longer be
discoverable.

To fix this, let us call btf_free_id() from the MODULE_STATE_GOING case
for the module unload to free the BTF associated IDR entry, and disable
its discovery once module unload returns to user space. If a race
happens during unload, the outcome is non-deterministic anyway. However,
user space should be able to rely on the guarantee that once it has
synchronously established a successful module unload, no more stale
artifacts associated with this module can be obtained subsequently.

Note that we must be careful to not invoke btf_free_id() in btf_put()
when btf_is_module() is true now. There could be a window where the
module unload drops a non-terminal reference, frees the IDR, but the
same ID gets reused and the second unconditional btf_free_id() ends up
releasing an unrelated entry.

To avoid a special case for btf_is_module() case, set btf->id to zero to
make btf_free_id() idempotent, such that we can unconditionally invoke it
from btf_put(), and also from the MODULE_STATE_GOING case. Since zero is
an invalid IDR, the idr_remove() should be a noop.

Note that we can be sure that by the time we reach final btf_put() for
btf_is_module() case, the btf_free_id() is already done, since the
module itself holds the BTF reference, and it will call this function
for the BTF before dropping its own reference.

[0]: https://lore.kernel.org/bpf/cover.1773170190.git.grbell@redhat.com

Fixes: 36e68442d1af ("bpf: Load and verify kernel module BTFs")
Acked-by: Martin KaFai Lau <martin.lau@kernel.org>
Suggested-by: Martin KaFai Lau <martin.lau@kernel.org>
Reported-by: Gregory Bell <grbell@redhat.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20260312205307.1346991-1-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Kumar Kartikeya Dwivedi and committed by
Alexei Starovoitov
146bd2a8 e06e6b80

+20 -4
+20 -4
kernel/bpf/btf.c
··· 1787 1787 * of the _bh() version. 1788 1788 */ 1789 1789 spin_lock_irqsave(&btf_idr_lock, flags); 1790 - idr_remove(&btf_idr, btf->id); 1790 + if (btf->id) { 1791 + idr_remove(&btf_idr, btf->id); 1792 + /* 1793 + * Clear the id here to make this function idempotent, since it will get 1794 + * called a couple of times for module BTFs: on module unload, and then 1795 + * the final btf_put(). btf_alloc_id() starts IDs with 1, so we can use 1796 + * 0 as sentinel value. 1797 + */ 1798 + WRITE_ONCE(btf->id, 0); 1799 + } 1791 1800 spin_unlock_irqrestore(&btf_idr_lock, flags); 1792 1801 } 1793 1802 ··· 8124 8115 { 8125 8116 const struct btf *btf = filp->private_data; 8126 8117 8127 - seq_printf(m, "btf_id:\t%u\n", btf->id); 8118 + seq_printf(m, "btf_id:\t%u\n", READ_ONCE(btf->id)); 8128 8119 } 8129 8120 #endif 8130 8121 ··· 8206 8197 if (copy_from_user(&info, uinfo, info_copy)) 8207 8198 return -EFAULT; 8208 8199 8209 - info.id = btf->id; 8200 + info.id = READ_ONCE(btf->id); 8210 8201 ubtf = u64_to_user_ptr(info.btf); 8211 8202 btf_copy = min_t(u32, btf->data_size, info.btf_size); 8212 8203 if (copy_to_user(ubtf, btf->data, btf_copy)) ··· 8269 8260 8270 8261 u32 btf_obj_id(const struct btf *btf) 8271 8262 { 8272 - return btf->id; 8263 + return READ_ONCE(btf->id); 8273 8264 } 8274 8265 8275 8266 bool btf_is_kernel(const struct btf *btf) ··· 8391 8382 if (btf_mod->module != module) 8392 8383 continue; 8393 8384 8385 + /* 8386 + * For modules, we do the freeing of BTF IDR as soon as 8387 + * module goes away to disable BTF discovery, since the 8388 + * btf_try_get_module() on such BTFs will fail. This may 8389 + * be called again on btf_put(), but it's ok to do so. 8390 + */ 8391 + btf_free_id(btf_mod->btf); 8394 8392 list_del(&btf_mod->list); 8395 8393 if (btf_mod->sysfs_attr) 8396 8394 sysfs_remove_bin_file(btf_kobj, btf_mod->sysfs_attr);