Unverified Commit edcc8a38 authored by Qi Xi's avatar Qi Xi Committed by Arnd Bergmann
Browse files

once: fix race by moving DO_ONCE to separate section



The commit c2c60ea3 ("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: c2c60ea3 ("once: use __section(".data.once")")
Reported-by: default avatarHulk Robot <hulkci@huawei.com>
Signed-off-by: default avatarQi Xi <xiqi2@huawei.com>
Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
parent 8327bd4f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -361,6 +361,7 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
	__start_once = .;						\
	*(.data..once)							\
	__end_once = .;							\
	*(.data..do_once)						\
	STRUCT_ALIGN();							\
	*(__tracepoints)						\
	/* implement dynamic printk debug */				\
+2 −2
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ void __do_once_sleepable_done(bool *done, struct static_key_true *once_key,
#define DO_ONCE(func, ...)						     \
	({								     \
		bool ___ret = false;					     \
		static bool __section(".data..once") ___done = false;	     \
		static bool __section(".data..do_once") ___done = false;     \
		static DEFINE_STATIC_KEY_TRUE(___once_key);		     \
		if (static_branch_unlikely(&___once_key)) {		     \
			unsigned long ___flags;				     \
@@ -64,7 +64,7 @@ void __do_once_sleepable_done(bool *done, struct static_key_true *once_key,
#define DO_ONCE_SLEEPABLE(func, ...)						\
	({									\
		bool ___ret = false;						\
		static bool __section(".data..once") ___done = false;		\
		static bool __section(".data..do_once") ___done = false;	\
		static DEFINE_STATIC_KEY_TRUE(___once_key);			\
		if (static_branch_unlikely(&___once_key)) {			\
			___ret = __do_once_sleepable_start(&___done);		\