[PATCH] once: fix race by moving DO_ONCE to separate section

Qi Xi posted 1 patch 2 days, 2 hours ago
There is a newer version of this series
include/asm-generic/vmlinux.lds.h | 1 +
include/linux/once.h              | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
[PATCH] once: fix race by moving DO_ONCE to separate section
Posted by Qi Xi 2 days, 2 hours ago
The commit c2c60ea37e5b ("once: use __section(".data.once")") moved
DO_ONCE's ___done variable to .data.once section, which conflicts with
WARN_ONCE series macros that also use 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)

Fix by moving DO_ONCE to separate .data..do_once section to avoid
race conditions.

Fixes: c2c60ea37e5b ("once: use __section(".data.once")")
Reported-by: Hulk Robot <hulkci@huawei.com>
Signed-off-by: Qi Xi <xiqi2@huawei.com>
---
 include/asm-generic/vmlinux.lds.h | 1 +
 include/linux/once.h              | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 883dbac79da9..94850b52e5cc 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -384,6 +384,7 @@
 	__start_once = .;						\
 	*(.data..once)							\
 	__end_once = .;							\
+	*(.data..do_once)						\
 	STRUCT_ALIGN();							\
 	*(__tracepoints)						\
 	/* implement dynamic printk debug */				\
diff --git a/include/linux/once.h b/include/linux/once.h
index 30346fcdc799..261a7a4977dd 100644
--- a/include/linux/once.h
+++ b/include/linux/once.h
@@ -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;				     \
-- 
2.33.0
Re: [PATCH] once: fix race by moving DO_ONCE to separate section
Posted by Eric Dumazet 2 days, 1 hour ago
On Sat, Sep 6, 2025 at 6:58 AM Qi Xi <xiqi2@huawei.com> wrote:
>
> The commit c2c60ea37e5b ("once: use __section(".data.once")") moved
> DO_ONCE's ___done variable to .data.once section, which conflicts with
> WARN_ONCE series macros that also use 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)

Should we  use this section as well in include/linux/once_lite.h ?

Or add a comment there explaining that there is a difference
between the two variants, I am not sure this was explicitly mentioned
in the past.
Re: [PATCH] once: fix race by moving DO_ONCE to separate section
Posted by Qi Xi 14 hours ago
Hello Eric,

DO_ONCE_LITE_IF() 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 and move the flag to the new .data..do_once 
section.

I'll send the v2 patch with the comment.

Qi

On 06/09/2025 22:34, Eric Dumazet wrote:
> On Sat, Sep 6, 2025 at 6:58 AM Qi Xi <xiqi2@huawei.com> wrote:
>> The commit c2c60ea37e5b ("once: use __section(".data.once")") moved
>> DO_ONCE's ___done variable to .data.once section, which conflicts with
>> WARN_ONCE series macros that also use 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)
> Should we  use this section as well in include/linux/once_lite.h ?
>
> Or add a comment there explaining that there is a difference
> between the two variants, I am not sure this was explicitly mentioned
> in the past.