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

once: fix race by moving DO_ONCE to separate section

The commit c2c60ea37e5b ("once: use __section(".data.once")") moved
DO_ONCE's ___done variable to .data.once section, which conflicts with
DO_ONCE_LITE() that also uses the same section.

This creates a race condition when clear_warn_once is used:

Thread 1 (DO_ONCE) Thread 2 (DO_ONCE)
__do_once_start
read ___done (false)
acquire once_lock
execute func
__do_once_done
write ___done (true) __do_once_start
release once_lock // Thread 3 clear_warn_once reset ___done
read ___done (false)
acquire once_lock
execute func
schedule once_work __do_once_done
once_deferred: OK write ___done (true)
static_branch_disable release once_lock
schedule once_work
once_deferred:
BUG_ON(!static_key_enabled)

DO_ONCE_LITE() in once_lite.h is used by WARN_ON_ONCE() and other warning
macros. Keep its ___done flag in the .data..once section and allow resetting
by clear_warn_once, as originally intended.

In contrast, DO_ONCE() is used for functions like get_random_once() and
relies on its ___done flag for internal synchronization. We should not reset
DO_ONCE() by clear_warn_once.

Fix it by isolating DO_ONCE's ___done into a separate .data..do_once section,
shielding it from clear_warn_once.

Fixes: c2c60ea37e5b ("once: use __section(".data.once")")
Reported-by: Hulk Robot <hulkci@huawei.com>
Signed-off-by: Qi Xi <xiqi2@huawei.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>

authored by

Qi Xi and committed by
Arnd Bergmann
edcc8a38 8327bd4f

+3 -2
+1
include/asm-generic/vmlinux.lds.h
··· 361 361 __start_once = .; \ 362 362 *(.data..once) \ 363 363 __end_once = .; \ 364 + *(.data..do_once) \ 364 365 STRUCT_ALIGN(); \ 365 366 *(__tracepoints) \ 366 367 /* implement dynamic printk debug */ \
+2 -2
include/linux/once.h
··· 46 46 #define DO_ONCE(func, ...) \ 47 47 ({ \ 48 48 bool ___ret = false; \ 49 - static bool __section(".data..once") ___done = false; \ 49 + static bool __section(".data..do_once") ___done = false; \ 50 50 static DEFINE_STATIC_KEY_TRUE(___once_key); \ 51 51 if (static_branch_unlikely(&___once_key)) { \ 52 52 unsigned long ___flags; \ ··· 64 64 #define DO_ONCE_SLEEPABLE(func, ...) \ 65 65 ({ \ 66 66 bool ___ret = false; \ 67 - static bool __section(".data..once") ___done = false; \ 67 + static bool __section(".data..do_once") ___done = false; \ 68 68 static DEFINE_STATIC_KEY_TRUE(___once_key); \ 69 69 if (static_branch_unlikely(&___once_key)) { \ 70 70 ___ret = __do_once_sleepable_start(&___done); \