Skip to content

Commit fd381ce

Browse files
Hou TaoAlexei Starovoitov
authored andcommitted
bpf: Check map->usercnt after timer->timer is assigned
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: b00628b ("bpf: Introduce bpf timers.") Signed-off-by: Hou Tao <houtao1@huawei.com> Link: https://lore.kernel.org/r/20231030063616.1653024-1-houtao@huaweicloud.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
1 parent 15fb6f2 commit fd381ce

File tree

1 file changed

+16
-9
lines changed

1 file changed

+16
-9
lines changed

kernel/bpf/helpers.c

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,13 +1177,6 @@ BPF_CALL_3(bpf_timer_init, struct bpf_timer_kern *, timer, struct bpf_map *, map
11771177
ret = -EBUSY;
11781178
goto out;
11791179
}
1180-
if (!atomic64_read(&map->usercnt)) {
1181-
/* maps with timers must be either held by user space
1182-
* or pinned in bpffs.
1183-
*/
1184-
ret = -EPERM;
1185-
goto out;
1186-
}
11871180
/* allocate hrtimer via map_kmalloc to use memcg accounting */
11881181
t = bpf_map_kmalloc_node(map, sizeof(*t), GFP_ATOMIC, map->numa_node);
11891182
if (!t) {
@@ -1196,7 +1189,21 @@ BPF_CALL_3(bpf_timer_init, struct bpf_timer_kern *, timer, struct bpf_map *, map
11961189
rcu_assign_pointer(t->callback_fn, NULL);
11971190
hrtimer_init(&t->timer, clockid, HRTIMER_MODE_REL_SOFT);
11981191
t->timer.function = bpf_timer_cb;
1199-
timer->timer = t;
1192+
WRITE_ONCE(timer->timer, t);
1193+
/* Guarantee the order between timer->timer and map->usercnt. So
1194+
* when there are concurrent uref release and bpf timer init, either
1195+
* bpf_timer_cancel_and_free() called by uref release reads a no-NULL
1196+
* timer or atomic64_read() below returns a zero usercnt.
1197+
*/
1198+
smp_mb();
1199+
if (!atomic64_read(&map->usercnt)) {
1200+
/* maps with timers must be either held by user space
1201+
* or pinned in bpffs.
1202+
*/
1203+
WRITE_ONCE(timer->timer, NULL);
1204+
kfree(t);
1205+
ret = -EPERM;
1206+
}
12001207
out:
12011208
__bpf_spin_unlock_irqrestore(&timer->lock);
12021209
return ret;
@@ -1374,7 +1381,7 @@ void bpf_timer_cancel_and_free(void *val)
13741381
/* The subsequent bpf_timer_start/cancel() helpers won't be able to use
13751382
* this timer, since it won't be initialized.
13761383
*/
1377-
timer->timer = NULL;
1384+
WRITE_ONCE(timer->timer, NULL);
13781385
out:
13791386
__bpf_spin_unlock_irqrestore(&timer->lock);
13801387
if (!t)

0 commit comments

Comments
 (0)