89d92e4fc5
[ Upstream commit fd381ce60a2d79cc967506208085336d3d268ae0 ]
When there are concurrent uref release and bpf timer init operations,
the following sequence diagram is possible. It will break the guarantee
provided by bpf_timer: bpf_timer will still be alive after userspace
application releases or unpins the map. It also will lead to kmemleak
for old kernel version which doesn't release bpf_timer when map is
released.
bpf program X:
bpf_timer_init()
lock timer->lock
read timer->timer as NULL
read map->usercnt != 0
process Y:
close(map_fd)
// put last uref
bpf_map_put_uref()
atomic_dec_and_test(map->usercnt)
array_map_free_timers()
bpf_timer_cancel_and_free()
// just return
read timer->timer is NULL
t = bpf_map_kmalloc_node()
timer->timer = t
unlock timer->lock
Fix the problem by checking map->usercnt after timer->timer is assigned,
so when there are concurrent uref release and bpf timer init, either
bpf_timer_cancel_and_free() from uref release reads a no-NULL timer
or the newly-added atomic64_read() returns a zero usercnt.
Because atomic_dec_and_test(map->usercnt) and READ_ONCE(timer->timer)
in bpf_timer_cancel_and_free() are not protected by a lock, so add
a memory barrier to guarantee the order between map->usercnt and
timer->timer. Also use WRITE_ONCE(timer->timer, x) to match the lockless
read of timer->timer in bpf_timer_cancel_and_free().
Reported-by: Hsin-Wei Hung <hsinweih@uci.edu>
Closes: https://lore.kernel.org/bpf/CABcoxUaT2k9hWsS1tNgXyoU3E-=PuOgMn737qK984fbFmfYixQ@mail.gmail.com
Fixes:
|
||
---|---|---|
.. | ||
preload | ||
arraymap.c | ||
bloom_filter.c | ||
bpf_inode_storage.c | ||
bpf_iter.c | ||
bpf_local_storage.c | ||
bpf_lru_list.c | ||
bpf_lru_list.h | ||
bpf_lsm.c | ||
bpf_struct_ops_types.h | ||
bpf_struct_ops.c | ||
bpf_task_storage.c | ||
btf.c | ||
cgroup_iter.c | ||
cgroup.c | ||
core.c | ||
cpumap.c | ||
devmap.c | ||
disasm.c | ||
disasm.h | ||
dispatcher.c | ||
hashtab.c | ||
helpers.c | ||
inode.c | ||
Kconfig | ||
link_iter.c | ||
local_storage.c | ||
lpm_trie.c | ||
Makefile | ||
map_in_map.c | ||
map_in_map.h | ||
map_iter.c | ||
memalloc.c | ||
mmap_unlock_work.h | ||
net_namespace.c | ||
offload.c | ||
percpu_freelist.c | ||
percpu_freelist.h | ||
prog_iter.c | ||
queue_stack_maps.c | ||
reuseport_array.c | ||
ringbuf.c | ||
stackmap.c | ||
syscall.c | ||
sysfs_btf.c | ||
task_iter.c | ||
tnum.c | ||
trampoline.c | ||
verifier.c |