Re: KCSAN: data-race in data_push_tail / symbol_string
From: Petr Mladek
Date: Thu Mar 19 2026 - 12:40:13 EST
Hi Jianzhou,
first, thanks a lot for the report.
On Wed 2026-03-11 15:36:47, Jianzhou Zhao wrote:
>
>
> Subject: [BUG] printk: KCSAN: data-race in data_push_tail / symbol_string
>
> Dear Maintainers,
>
> We are writing to report a KCSAN-detected data-race vulnerability in the Linux kernel. This bug was found by our custom fuzzing tool, RacePilot. The bug occurs during ringbuffer tail advancement where a reader speculatively reads the `blk->id` from a physical address that has concurrently been overwritten by a writer formatting a string. We observed this on the Linux kernel version 6.18.0-08691-g2061f18ad76e-dirty.
>
> Call Trace & Context
> ==================================================================
> BUG: KCSAN: data-race in data_push_tail.part.0 / symbol_string
>
> write to 0xffffffff88f194a8 of 1 bytes by task 38579 on cpu 0:
> string_nocheck lib/vsprintf.c:658 [inline]
> symbol_string+0x129/0x2c0 lib/vsprintf.c:1020
> pointer+0x24c/0x920 lib/vsprintf.c:2565
> vsnprintf+0x5d0/0xb80 lib/vsprintf.c:2982
> vscnprintf+0x41/0x90 lib/vsprintf.c:3042
> printk_sprint+0x31/0x1c0 kernel/printk/printk.c:2199
> vprintk_store+0x3f6/0x980 kernel/printk/printk.c:2321
> vprintk_emit+0xfd/0x540 kernel/printk/printk.c:2412
> vprintk_default+0x26/0x30 kernel/printk/printk.c:2451
> vprintk+0x1d/0x30 kernel/printk/printk_safe.c:82
> _printk+0x63/0x90 kernel/printk/printk.c:2461
> printk_stack_address arch/x86/kernel/dumpstack.c:70 [inline]
>
> read to 0xffffffff88f194a8 of 8 bytes by task 38521 on cpu 1:
> data_make_reusable kernel/printk/printk_ringbuffer.c:606 [inline]
> data_push_tail.part.0+0xe6/0x350 kernel/printk/printk_ringbuffer.c:692
> data_push_tail kernel/printk/printk_ringbuffer.c:656 [inline]
> data_alloc+0x157/0x330 kernel/printk/printk_ringbuffer.c:1096
> prb_reserve+0x44d/0x7d0 kernel/printk/printk_ringbuffer.c:1742
> vprintk_store+0x3b4/0x980 kernel/printk/printk.c:2311
> vprintk_emit+0xfd/0x540 kernel/printk/printk.c:2412
> vprintk_default+0x26/0x30 kernel/printk/printk.c:2451
> vprintk+0x1d/0x30 kernel/printk/printk_safe.c:82
> _printk+0x63/0x90 kernel/printk/printk.c:2461
>
> value changed: 0x00000000fffff47a -> 0x302f303978302b6c
>
> Reported by Kernel Concurrency Sanitizer on:
> CPU: 1 UID: 0 PID: 38521 Comm: syz.7.1998 Not tainted 6.18.0-08691-g2061f18ad76e-dirty #42 PREEMPT(voluntary)
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
> ==================================================================
>
> Execution Flow & Code Context
> On CPU 0, a printing task formats an output string containing a symbol address through `vsprintf.c` which recursively formats data and writes to a buffer natively byte-by-byte:
> ```c
> // lib/vsprintf.c
> static char *string_nocheck(char *buf, char *end, const char *s,
> struct printf_spec spec)
> {
> ...
> while (lim--) {
> char c = *s++;
> ...
> if (buf < end)
> *buf = c; // <-- Plain Write
> ++buf;
> ...
> }
> return widen_string(buf, len, end, spec);
> }
> ```
>
> This destination buffer represents the text block inside the physical `printk_ringbuffer` array, historically mapped out by `data_alloc()`. Concurrently, CPU 1 calls `prb_reserve()` advancing `data_make_reusable()` along the same space to check if it's safe to clear descriptors. The reader uses `blk->id` unannotated to see if a particular logical block was recycled:
> ```c
> // kernel/printk/printk_ringbuffer.c
> static bool data_make_reusable(struct printk_ringbuffer *rb, ...)
> {
> ...
> while (need_more_space(data_ring, lpos_begin, lpos_end)) {
> blk = to_block(data_ring, lpos_begin);
>
> /*
> * Load the block ID from the data block. This is a data race
> * against a writer that may have newly reserved this data
> * area. If the loaded value matches a valid descriptor ID,
> ...
> */
> id = blk->id; /* LMM(data_make_reusable:A) */ // <-- Plain Lockless Read
> ...
> ```
>
> Root Cause Analysis
> A data race occurs because the reader speculatively accesses `blk->id` using a plain memory access (`id = blk->id`). However, because another concurrent task (`CPU 0`) running `vsprintf` has already pushed the logical boundaries on this data array and is linearly formatting strings onto this exact overlapping physical memory region block, `CPU 1` reads data undergoing character writes. This is an intentional heuristic documented by the comment: "This is a data race against a writer that may have newly reserved this data area". Reading garbage here is gracefully handled out-of-band by mapping the `sys_desc` ring ID and concluding it mismatching. However, it still trips compiler sanitizer checks.
> Unfortunately, we were unable to generate a reproducer for this bug.
>
> Potential Impact
> This data race is functionally benign. If `data_make_reusable` reads formatted text characters rather than a proper `unsigned long id`, it safely skips it and verifies limits via `blk_lpos` logic. However, tripping the KCSAN sanitizer adds unnecessary debugging noise and may hide actual vulnerabilities under prolonged workloads.
>
> Proposed Fix
> To silence the compiler sanitizer and explicitly annotate to the memory model that this deliberate racing behavior is expected, `data_race()` macro should wrap the read on `blk->id`.
>
> ```diff
> --- a/kernel/printk/printk_ringbuffer.c
> +++ b/kernel/printk/printk_ringbuffer.c
> @@ -616,7 +616,7 @@ static bool data_make_reusable(struct printk_ringbuffer *rb,
> * sure it points back to this data block. If the check fails,
> * the data area has been recycled by another writer.
> */
> - id = blk->id; /* LMM(data_make_reusable:A) */
> + id = data_race(blk->id); /* LMM(data_make_reusable:A) */
>
> d_state = desc_read(desc_ring, id, &desc, NULL,
> NULL); /* LMM(data_make_reusable:B) */
> ```
>
> We would be highly honored if this could be of any help.
The proposed change makes perfect sense. Would you like to send it as
a proper patch? Or should I prepare it myself (giving you credits)?
The proper patch might be similar to this report. I would just do:
+ keep only printk-related part of the backtraces
+ format the other text to keep <= 75 characters long lines
+ add the tags (Reported-by:, Closes:, Fixes:, Signed-off-by:).
Best Regards,
Petr