syzbot


ID Workflow Result Correct Bug Created Started Finished Revision Error
2ddf3085-b7ff-435f-9ecb-56468bfb8d65 assessment-kcsan 💥 KCSAN: data-race in data_push_tail / number (5) 2026/01/15 08:29 2026/01/15 08:29 2026/01/15 08:32 a9d6a79219801d2130df3b1a792c57f0e5428e9f unexpected reply fields ({Content:0xc0058eb4a0 CitationMetadata:0xc0094009a8 FinishMessage: TokenCount:0 FinishReason:STOP AvgLogprobs:0 GroundingMetadata:<nil> Index:0 LogprobsResult:<nil> SafetyRatings:[] URLContextMetadata:<nil>})

Crash report:
Bluetooth: hci0: Opcode 0x1003 failed: -110
==================================================================
BUG: KCSAN: data-race in data_push_tail / number

write to 0xffffffff88eb4338 of 1 bytes by task 3641 on cpu 1:
 number+0x8fd/0xab0 lib/vsprintf.c:572
 vsnprintf+0x64e/0x860 lib/vsprintf.c:2911
 va_format lib/vsprintf.c:1722 [inline]
 pointer+0x821/0xcb0 lib/vsprintf.c:2568
 vsnprintf+0x491/0x860 lib/vsprintf.c:2951
 vscnprintf+0x41/0x90 lib/vsprintf.c:3012
 printk_sprint+0x30/0x2d0 kernel/printk/printk.c:2192
 vprintk_store+0x568/0x830 kernel/printk/printk.c:2312
 vprintk_emit+0x15a/0x5c0 kernel/printk/printk.c:2402
 vprintk_default+0x26/0x30 kernel/printk/printk.c:2441
 vprintk+0x1d/0x30 kernel/printk/printk_safe.c:82
 _printk+0x79/0xa0 kernel/printk/printk.c:2451
 bt_err+0x9d/0xd0 net/bluetooth/lib.c:296
 hci_cmd_timeout+0x97/0x140 net/bluetooth/hci_core.c:1469
 process_one_work kernel/workqueue.c:3257 [inline]
 process_scheduled_works+0x4ce/0x9d0 kernel/workqueue.c:3340
 worker_thread+0x582/0x770 kernel/workqueue.c:3421
 kthread+0x489/0x510 kernel/kthread.c:463
 ret_from_fork+0x149/0x290 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246

read to 0xffffffff88eb4338 of 8 bytes by task 44 on cpu 0:
 data_make_reusable kernel/printk/printk_ringbuffer.c:608 [inline]
 data_push_tail+0x100/0x470 kernel/printk/printk_ringbuffer.c:693
 data_alloc+0x11b/0x390 kernel/printk/printk_ringbuffer.c:1089
 prb_reserve+0x8d7/0xad0 kernel/printk/printk_ringbuffer.c:1724
 vprintk_store+0x53c/0x830 kernel/printk/printk.c:2302
 vprintk_emit+0x15a/0x5c0 kernel/printk/printk.c:2402
 vprintk_default+0x26/0x30 kernel/printk/printk.c:2441
 vprintk+0x1d/0x30 kernel/printk/printk_safe.c:82
 _printk+0x79/0xa0 kernel/printk/printk.c:2451
 bt_err+0x9d/0xd0 net/bluetooth/lib.c:296
 __hci_cmd_sync_status_sk net/bluetooth/hci_sync.c:271 [inline]
 __hci_cmd_sync_status net/bluetooth/hci_sync.c:287 [inline]
 hci_read_local_features_sync net/bluetooth/hci_sync.c:3708 [inline]
 hci_init_stage_sync net/bluetooth/hci_sync.c:3623 [inline]
 hci_init1_sync net/bluetooth/hci_sync.c:3755 [inline]
 hci_init_sync net/bluetooth/hci_sync.c:4885 [inline]
 hci_dev_init_sync net/bluetooth/hci_sync.c:5077 [inline]
 hci_dev_open_sync+0xfa4/0x2290 net/bluetooth/hci_sync.c:5155
 hci_dev_do_open net/bluetooth/hci_core.c:430 [inline]
 hci_power_on+0xef/0x390 net/bluetooth/hci_core.c:959
 process_one_work kernel/workqueue.c:3257 [inline]
 process_scheduled_works+0x4ce/0x9d0 kernel/workqueue.c:3340
 worker_thread+0x582/0x770 kernel/workqueue.c:3421
 kthread+0x489/0x510 kernel/kthread.c:463
 ret_from_fork+0x149/0x290 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246

value changed: 0x00000000ffffe607 -> 0x6f656d6974207874

Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 44 Comm: kworker/u9:0 Not tainted syzkaller #0 PREEMPT(voluntary) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025
Workqueue: hci0 hci_power_on
==================================================================

Trajectory:
Seq Timestamp Type Name Duration
0/0 2026/01/15 08:29 flow assessment-kcsan
2m Model:
unknown

Error:
unexpected reply fields ({Content:0xc0058eb4a0 CitationMetadata:0xc0094009a8 FinishMessage: TokenCount:0 FinishReason:STOP AvgLogprobs:0 GroundingMetadata:<nil> Index:0 LogprobsResult:<nil> SafetyRatings:[] URLContextMetadata:<nil>})

1/1 2026/01/15 08:29 action kernel-checkouter
0m Model:
unknown

Results:
map[KernelSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/src/d68e4f68e008af0820b7b740e9d90673b3a857b4]

2/1 2026/01/15 08:29 action kernel-builder
0m Model:
unknown

Results:
map[KernelObj:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/build/6f7a8d5365858494174e0806f9abcef148d7be5b]

3/1 2026/01/15 08:29 action codesearch-prepare
0m Model:
unknown

Results:
map[Index:codesearch-index]

4/1 2026/01/15 08:29 agent expert
2m Model:
unknown

Error:
unexpected reply fields ({Content:0xc0058eb4a0 CitationMetadata:0xc0094009a8 FinishMessage: TokenCount:0 FinishReason:STOP AvgLogprobs:0 GroundingMetadata:<nil> Index:0 LogprobsResult:<nil> SafetyRatings:[] URLContextMetadata:<nil>})

Instruction:
You are an experienced Linux kernel developer tasked with determining if the given kernel
data race is benign or not. The data race report is from KCSAN tool.
It contains 2 stack traces of the memory accesses that constitute a data race.

A "benign" data races are on a simple int/bool variable or similar field,
and the accesses are not supposed to be protected by any mutual exclusion primitives.
Common examples of such "benign" data races are accesses to various flags fields,
statistics counters, and similar. A "benign" data race does not lead to memory corruption/crash
with a conservative compiler that compiles memory accesses to primitive types
effectively as atomic.

A non-benign (or "harmful" data race) can lead to corruption/crash even with
a conservative compiler that compiles memory accesses to primitive types
effectively as atomic. A common example of a "harmful" data races is race on
a complex container (list/hashmap/etc), where accesses are supposed to be protected
by a mutual exclusion primitive.

In the final reply explain why you think the given data race is benign or is harmful.

Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
In particular, don't make assumptions about the kernel source code,
use codesearch tools to read the actual source code.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
The data race report is:

Bluetooth: hci0: Opcode 0x1003 failed: -110
==================================================================
BUG: KCSAN: data-race in data_push_tail / number

write to 0xffffffff88eb4338 of 1 bytes by task 3641 on cpu 1:
 number+0x8fd/0xab0 lib/vsprintf.c:572
 vsnprintf+0x64e/0x860 lib/vsprintf.c:2911
 va_format lib/vsprintf.c:1722 [inline]
 pointer+0x821/0xcb0 lib/vsprintf.c:2568
 vsnprintf+0x491/0x860 lib/vsprintf.c:2951
 vscnprintf+0x41/0x90 lib/vsprintf.c:3012
 printk_sprint+0x30/0x2d0 kernel/printk/printk.c:2192
 vprintk_store+0x568/0x830 kernel/printk/printk.c:2312
 vprintk_emit+0x15a/0x5c0 kernel/printk/printk.c:2402
 vprintk_default+0x26/0x30 kernel/printk/printk.c:2441
 vprintk+0x1d/0x30 kernel/printk/printk_safe.c:82
 _printk+0x79/0xa0 kernel/printk/printk.c:2451
 bt_err+0x9d/0xd0 net/bluetooth/lib.c:296
 hci_cmd_timeout+0x97/0x140 net/bluetooth/hci_core.c:1469
 process_one_work kernel/workqueue.c:3257 [inline]
 process_scheduled_works+0x4ce/0x9d0 kernel/workqueue.c:3340
 worker_thread+0x582/0x770 kernel/workqueue.c:3421
 kthread+0x489/0x510 kernel/kthread.c:463
 ret_from_fork+0x149/0x290 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246

read to 0xffffffff88eb4338 of 8 bytes by task 44 on cpu 0:
 data_make_reusable kernel/printk/printk_ringbuffer.c:608 [inline]
 data_push_tail+0x100/0x470 kernel/printk/printk_ringbuffer.c:693
 data_alloc+0x11b/0x390 kernel/printk/printk_ringbuffer.c:1089
 prb_reserve+0x8d7/0xad0 kernel/printk/printk_ringbuffer.c:1724
 vprintk_store+0x53c/0x830 kernel/printk/printk.c:2302
 vprintk_emit+0x15a/0x5c0 kernel/printk/printk.c:2402
 vprintk_default+0x26/0x30 kernel/printk/printk.c:2441
 vprintk+0x1d/0x30 kernel/printk/printk_safe.c:82
 _printk+0x79/0xa0 kernel/printk/printk.c:2451
 bt_err+0x9d/0xd0 net/bluetooth/lib.c:296
 __hci_cmd_sync_status_sk net/bluetooth/hci_sync.c:271 [inline]
 __hci_cmd_sync_status net/bluetooth/hci_sync.c:287 [inline]
 hci_read_local_features_sync net/bluetooth/hci_sync.c:3708 [inline]
 hci_init_stage_sync net/bluetooth/hci_sync.c:3623 [inline]
 hci_init1_sync net/bluetooth/hci_sync.c:3755 [inline]
 hci_init_sync net/bluetooth/hci_sync.c:4885 [inline]
 hci_dev_init_sync net/bluetooth/hci_sync.c:5077 [inline]
 hci_dev_open_sync+0xfa4/0x2290 net/bluetooth/hci_sync.c:5155
 hci_dev_do_open net/bluetooth/hci_core.c:430 [inline]
 hci_power_on+0xef/0x390 net/bluetooth/hci_core.c:959
 process_one_work kernel/workqueue.c:3257 [inline]
 process_scheduled_works+0x4ce/0x9d0 kernel/workqueue.c:3340
 worker_thread+0x582/0x770 kernel/workqueue.c:3421
 kthread+0x489/0x510 kernel/kthread.c:463
 ret_from_fork+0x149/0x290 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246

value changed: 0x00000000ffffe607 -> 0x6f656d6974207874

Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 44 Comm: kworker/u9:0 Not tainted syzkaller #0 PREEMPT(voluntary) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025
Workqueue: hci0 hci_power_on
==================================================================


5/2 2026/01/15 08:29 llm expert
0m Model:
unknown

Thoughts:
**Analyzing a Data Race in the Kernel's `printk` Subsystem**

Okay, so I'm looking at this report, and it's flagged a data race. Specifically, it's between a write operation in `lib/vsprintf.c` and a read operation in `kernel/printk/printk_ringbuffer.c`. The addresses are the same, `0xffffffff88eb4338`, which, given the starting address, strongly suggests this is in the kernel's text or data section. Confirmed; it's within the kernel image. That's a red flag.

The write side, `lib/vsprintf.c:572`, is writing a single byte to that location. The read side, `kernel/printk/printk_ringbuffer.c:608`, is reading 8 bytes. I need to dig into this.

First, I need to see *what* is being written and read. The value changed from `0x00000000ffffe607` to `0x6f656d6974207874`. Decoding this, remembering that these systems are little endian, it's "tx timeo". `vsprintf.c` is writing "tx timeo".

Now let's look at the call stacks to understand the context. The write side shows `printk` is definitely involved, tracing through `_printk` all the way down to a function called `number`. This call stack shows that `printk` is formatting some sort of string using the `vsprintf` family of functions. It looks like it's a format string expansion that is getting pushed to the printk ring buffer.

The read side's stack is also firmly rooted in `printk`, traversing from `_printk` down through `data_make_reusable`. This suggests it is reserving space in the printk ringbuffer, and specifically is trying to push the tail to reclaim old data. This seems to be the part of the code that grabs and reads the formatted string.

The crux of the matter is *why* the formatted string in `vsprintf.c` is being written to the *exact* same location as the data being read by `printk_ringbuffer.c`. That is the critical question. This suggests shared memory, but it's *printk*. This isn't where I'd expect that kind of shared data structure. I need to examine the source code of both functions more closely to see the nature of their interaction. Something is clearly being shared between them, and the report flags a potential race condition which needs further investigation.



6/2 2026/01/15 08:29 tool codesearch-file-index
0m Model:
unknown

Args:
map[SourceFile:lib/vsprintf.c]

Results:
map[Entities:[map[Kind:function Name:__ptr_to_hashval] map[Kind:function Name:address_val] map[Kind:function Name:bdev_name] map[Kind:function Name:bitmap_list_string] map[Kind:function Name:bitmap_string] map[Kind:function Name:bstr_printf] map[Kind:function Name:bstr_printf] map[Kind:function Name:check_pointer] map[Kind:function Name:check_pointer_msg] map[Kind:function Name:clock] map[Kind:function Name:convert_num_spec] map[Kind:function Name:date_str] map[Kind:function Name:debug_boot_weak_hash_enable] map[Kind:function Name:default_pointer] map[Kind:function Name:dentry_name] map[Kind:function Name:device_node_string] map[Kind:function Name:err_ptr] map[Kind:function Name:error_string] map[Kind:function Name:escaped_string] map[Kind:function Name:file_dentry_name] map[Kind:function Name:fill_ptr_key] map[Kind:function Name:flags_string] map[Kind:function Name:format_decode] map[Kind:function Name:format_flags] map[Kind:function Name:format_page_flags] map[Kind:function Name:fourcc_string] map[Kind:function Name:fwnode_full_name_string] map[Kind:function Name:fwnode_string] map[Kind:function Name:hash_pointers_finalize] map[Kind:function Name:hash_pointers_mode_parse] map[Kind:function Name:hex_range] map[Kind:function Name:hex_string] map[Kind:function Name:ip4_addr_string] map[Kind:function Name:ip4_addr_string_sa] map[Kind:function Name:ip4_string] map[Kind:function Name:ip6_addr_string] map[Kind:function Name:ip6_addr_string_sa] map[Kind:function Name:ip6_compressed_string] map[Kind:function Name:ip6_string] map[Kind:function Name:ip_addr_string] map[Kind:function Name:mac_address_string] map[Kind:function Name:move_right] map[Kind:function Name:netdev_bits] map[Kind:function Name:no_hash_pointers_enable] map[Kind:function Name:num_to_str] map[Kind:function Name:number] map[Kind:function Name:pointer] map[Kind:function Name:pointer_string] map[Kind:function Name:ptr_to_hashval] map[Kind:function Name:ptr_to_id] map[Kind:function Name:put_dec] map[Kind:function Name:put_dec_full8] map[Kind:function Name:put_dec_trunc8] map[Kind:function Name:range_string] map[Kind:function Name:resource_or_range] map[Kind:function Name:resource_string] map[Kind:function Name:restricted_pointer] map[Kind:function Name:rtc_str] map[Kind:function Name:scnprintf] map[Kind:function Name:scnprintf] map[Kind:function Name:set_field_width] map[Kind:function Name:set_precision] map[Kind:function Name:simple_strntoll] map[Kind:function Name:simple_strntoul] map[Kind:function Name:simple_strntoul] map[Kind:function Name:simple_strntoull] map[Kind:function Name:simple_strtol] map[Kind:function Name:simple_strtol] map[Kind:function Name:simple_strtoll] map[Kind:function Name:simple_strtoll] map[Kind:function Name:simple_strtoul] map[Kind:function Name:simple_strtoul] map[Kind:function Name:simple_strtoull] map[Kind:function Name:simple_strtoull] map[Kind:function Name:skip_atoi] map[Kind:function Name:snprintf] map[Kind:function Name:snprintf] map[Kind:function Name:spec_flag] map[Kind:function Name:special_hex_number] map[Kind:function Name:sprintf] map[Kind:function Name:sprintf] map[Kind:function Name:sscanf] map[Kind:function Name:sscanf] map[Kind:function Name:string] map[Kind:function Name:string_nocheck] map[Kind:function Name:symbol_string] map[Kind:function Name:time64_str] map[Kind:function Name:time_and_date] map[Kind:function Name:time_str] map[Kind:function Name:timespec64_str] map[Kind:function Name:uuid_string] map[Kind:function Name:va_format] map[Kind:function Name:vbin_printf] map[Kind:function Name:vbin_printf] map[Kind:function Name:vscnprintf] map[Kind:function Name:vscnprintf] map[Kind:function Name:vsnprintf] map[Kind:function Name:vsnprintf] map[Kind:function Name:vsprintf] map[Kind:function Name:vsprintf] map[Kind:function Name:vsprintf_init_hashval] map[Kind:function Name:vsscanf] map[Kind:function Name:vsscanf] map[Kind:function Name:widen_string]] Missing:false]

7/2 2026/01/15 08:29 tool codesearch-file-index
0m Model:
unknown

Args:
map[SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Entities:[map[Kind:function Name:_prb_commit] map[Kind:function Name:_prb_read_valid] map[Kind:function Name:_prb_read_valid] map[Kind:function Name:copy_data] map[Kind:function Name:count_lines] map[Kind:function Name:data_alloc] map[Kind:function Name:data_check_size] map[Kind:function Name:data_make_reusable] map[Kind:function Name:data_push_tail] map[Kind:function Name:data_realloc] map[Kind:function Name:desc_last_finalized_seq] map[Kind:function Name:desc_make_final] map[Kind:function Name:desc_make_reusable] map[Kind:function Name:desc_push_tail] map[Kind:function Name:desc_read] map[Kind:function Name:desc_read_finalized_seq] map[Kind:function Name:desc_reopen_last] map[Kind:function Name:desc_reserve] map[Kind:function Name:desc_update_last_finalized] map[Kind:function Name:get_data] map[Kind:function Name:get_desc_state] map[Kind:function Name:get_next_lpos] map[Kind:function Name:is_blk_wrapped] map[Kind:function Name:need_more_space] map[Kind:function Name:prb_commit] map[Kind:function Name:prb_final_commit] map[Kind:function Name:prb_first_seq] map[Kind:function Name:prb_first_valid_seq] map[Kind:function Name:prb_init] map[Kind:function Name:prb_next_reserve_seq] map[Kind:function Name:prb_next_seq] map[Kind:function Name:prb_read] map[Kind:function Name:prb_read_valid] map[Kind:function Name:prb_read_valid_info] map[Kind:function Name:prb_record_text_space] map[Kind:function Name:prb_reserve] map[Kind:function Name:prb_reserve_in_last] map[Kind:function Name:space_used] map[Kind:function Name:to_blk_size] map[Kind:function Name:to_block] map[Kind:function Name:to_desc] map[Kind:function Name:to_info]] Missing:false]

8/2 2026/01/15 08:29 llm expert
0m Model:
unknown

Thoughts:
Here's my take on those thoughts:

**Analyzing the Printk Ringbuffer**

Okay, so we're talking about a write operation in `vsprintf.c` and a corresponding read within `data_make_reusable` in `printk_ringbuffer.c`. My immediate thought is, "Right, so we've got a classic producer-consumer scenario here, dealing with the kernel's logging output." I need to understand how these two functions are working together within the ring buffer. Let's start by digging into `data_make_reusable` in `kernel/printk/printk_ringbuffer.c`. I'm going to start by looking for synchronization primitives, memory barriers, and how the buffer indices are being managed. Understanding how that data is being marked as reusable is key to figuring out the whole picture.



9/2 2026/01/15 08:29 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:data_make_reusable SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Missing:false SourceCode: 572:	/*
 573:	 * Given the text data ring, put the associated descriptor of each
 574:	 * data block from @lpos_begin until @lpos_end into the reusable state.
 575:	 *
 576:	 * If there is any problem making the associated descriptor reusable, either
 577:	 * the descriptor has not yet been finalized or another writer context has
 578:	 * already pushed the tail lpos past the problematic data block. Regardless,
 579:	 * on error the caller can re-load the tail lpos to determine the situation.
 580:	 */
 581:	static bool data_make_reusable(struct printk_ringbuffer *rb,
 582:				       unsigned long lpos_begin,
 583:				       unsigned long lpos_end,
 584:				       unsigned long *lpos_out)
 585:	{
 586:	
 587:		struct prb_data_ring *data_ring = &rb->text_data_ring;
 588:		struct prb_desc_ring *desc_ring = &rb->desc_ring;
 589:		struct prb_data_block *blk;
 590:		enum desc_state d_state;
 591:		struct prb_desc desc;
 592:		struct prb_data_blk_lpos *blk_lpos = &desc.text_blk_lpos;
 593:		unsigned long id;
 594:	
 595:		/* Loop until @lpos_begin has advanced to or beyond @lpos_end. */
 596:		while (need_more_space(data_ring, lpos_begin, lpos_end)) {
 597:			blk = to_block(data_ring, lpos_begin);
 598:	
 599:			/*
 600:			 * Load the block ID from the data block. This is a data race
 601:			 * against a writer that may have newly reserved this data
 602:			 * area. If the loaded value matches a valid descriptor ID,
 603:			 * the blk_lpos of that descriptor will be checked to make
 604:			 * sure it points back to this data block. If the check fails,
 605:			 * the data area has been recycled by another writer.
 606:			 */
 607:			id = blk->id; /* LMM(data_make_reusable:A) */
 608:	
 609:			d_state = desc_read(desc_ring, id, &desc,
 610:					    NULL, NULL); /* LMM(data_make_reusable:B) */
 611:	
 612:			switch (d_state) {
 613:			case desc_miss:
 614:			case desc_reserved:
 615:			case desc_committed:
 616:				return false;
 617:			case desc_finalized:
 618:				/*
 619:				 * This data block is invalid if the descriptor
 620:				 * does not point back to it.
 621:				 */
 622:				if (blk_lpos->begin != lpos_begin)
 623:					return false;
 624:				desc_make_reusable(desc_ring, id);
 625:				break;
 626:			case desc_reusable:
 627:				/*
 628:				 * This data block is invalid if the descriptor
 629:				 * does not point back to it.
 630:				 */
 631:				if (blk_lpos->begin != lpos_begin)
 632:					return false;
 633:				break;
 634:			}
 635:	
 636:			/* Advance @lpos_begin to the next data block. */
 637:			lpos_begin = blk_lpos->next;
 638:		}
 639:	
 640:		*lpos_out = lpos_begin;
 641:		return true;
 642:	}
 SourceFile:kernel/printk/printk_ringbuffer.c]

10/2 2026/01/15 08:29 llm expert
0m Model:
unknown

Thoughts:
**Data Race Conundrum: A Deep Dive**

Okay, so I'm looking at this report and something's not sitting right. Line 607 (or maybe 608, details, details!) in the code grabs the `id` from `blk->id` and there's a comment flagging this as a data race, which piques my interest immediately. I see a write to `0xffffffff88eb4338` of 1 byte, originating from task 3641 via `vsprintf.c`, and then a read from *that same address* of 8 bytes by task 44 in `printk_ringbuffer.c` at line 608.

The address, `0xffffffff88eb4338`, is the key. Since `data_make_reusable` is involved and that function is responsible for preparing data from the printk ringbuffer, that address must correspond to the ringbuffer's data area, meaning `blk->id` lives there.

But here's the kicker:  Why is `number` writing to this address? `number` is linked with `vprintk_store` and `printk_sprint`. From what I know, `printk_sprint` formats the message into a temporary buffer for `printk`. So, if `printk` is used, it *usually* formats the message into a buffer before storing it in the ringbuffer. 

Time to dive into `printk_sprint` and figure out what exactly it's doing with that buffer.



11/2 2026/01/15 08:30 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:printk_sprint SourceFile:kernel/printk/printk.c]

Results:
map[Missing:false SourceCode:2184:	__printf(5, 0)
2185:	static u16 printk_sprint(char *text, u16 size, int facility,
2186:				 enum printk_info_flags *flags, const char *fmt,
2187:				 va_list args)
2188:	{
2189:		u16 text_len;
2190:	
2191:		text_len = vscnprintf(text, size, fmt, args);
2192:	
2193:		/* Mark and strip a trailing newline. */
2194:		if (text_len && text[text_len - 1] == '\n') {
2195:			text_len--;
2196:			*flags |= LOG_NEWLINE;
2197:		}
2198:	
2199:		/* Strip log level and control flags. */
2200:		if (facility == 0) {
2201:			u16 prefix_len;
2202:	
2203:			prefix_len = printk_parse_prefix(text, NULL, NULL);
2204:			if (prefix_len) {
2205:				text_len -= prefix_len;
2206:				memmove(text, text + prefix_len, text_len);
2207:			}
2208:		}
2209:	
2210:		trace_console(text, text_len);
2211:	
2212:		return text_len;
2213:	}
 SourceFile:kernel/printk/printk.c]

12/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
Okay, here's my take on those thoughts, as if I were the one thinking them:

**Dissecting the Kernel's Printing Mechanism**

Alright, so I'm digging into how these printk messages are handled, and it looks like `printk_sprint` is the key function. It's using `vscnprintf` to actually *format* the message and write it into a buffer, specifically the `text` buffer. Now, that `text` buffer... where does *that* come from? Ah, right, `vprintk_store` is the one feeding it. Time to shift focus and see exactly what `vprintk_store` is doing. I need to understand the flow from start to finish.



13/2 2026/01/15 08:30 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:vprintk_store SourceFile:kernel/printk/printk.c]

Results:
map[Missing:false SourceCode:2215:	__printf(4, 0)
2216:	int vprintk_store(int facility, int level,
2217:			  const struct dev_printk_info *dev_info,
2218:			  const char *fmt, va_list args)
2219:	{
2220:		struct prb_reserved_entry e;
2221:		enum printk_info_flags flags = 0;
2222:		struct printk_record r;
2223:		unsigned long irqflags;
2224:		u16 trunc_msg_len = 0;
2225:		char prefix_buf[8];
2226:		u8 *recursion_ptr;
2227:		u16 reserve_size;
2228:		va_list args2;
2229:		u32 caller_id;
2230:		u16 text_len;
2231:		int ret = 0;
2232:		u64 ts_nsec;
2233:	
2234:		if (!printk_enter_irqsave(recursion_ptr, irqflags))
2235:			return 0;
2236:	
2237:		/*
2238:		 * Since the duration of printk() can vary depending on the message
2239:		 * and state of the ringbuffer, grab the timestamp now so that it is
2240:		 * close to the call of printk(). This provides a more deterministic
2241:		 * timestamp with respect to the caller.
2242:		 */
2243:		ts_nsec = local_clock();
2244:	
2245:		caller_id = printk_caller_id();
2246:	
2247:		/*
2248:		 * The sprintf needs to come first since the syslog prefix might be
2249:		 * passed in as a parameter. An extra byte must be reserved so that
2250:		 * later the vscnprintf() into the reserved buffer has room for the
2251:		 * terminating '\0', which is not counted by vsnprintf().
2252:		 */
2253:		va_copy(args2, args);
2254:		reserve_size = vsnprintf(&prefix_buf[0], sizeof(prefix_buf), fmt, args2) + 1;
2255:		va_end(args2);
2256:	
2257:		if (reserve_size > PRINTKRB_RECORD_MAX)
2258:			reserve_size = PRINTKRB_RECORD_MAX;
2259:	
2260:		/* Extract log level or control flags. */
2261:		if (facility == 0)
2262:			printk_parse_prefix(&prefix_buf[0], &level, &flags);
2263:	
2264:		if (level == LOGLEVEL_DEFAULT)
2265:			level = default_message_loglevel;
2266:	
2267:		if (dev_info)
2268:			flags |= LOG_NEWLINE;
2269:	
2270:		if (is_printk_force_console())
2271:			flags |= LOG_FORCE_CON;
2272:	
2273:		if (flags & LOG_CONT) {
2274:			prb_rec_init_wr(&r, reserve_size);
2275:			if (prb_reserve_in_last(&e, prb, &r, caller_id, PRINTKRB_RECORD_MAX)) {
2276:				text_len = printk_sprint(&r.text_buf[r.info->text_len], reserve_size,
2277:							 facility, &flags, fmt, args);
2278:				r.info->text_len += text_len;
2279:	
2280:				if (flags & LOG_FORCE_CON)
2281:					r.info->flags |= LOG_FORCE_CON;
2282:	
2283:				if (flags & LOG_NEWLINE) {
2284:					r.info->flags |= LOG_NEWLINE;
2285:					prb_final_commit(&e);
2286:				} else {
2287:					prb_commit(&e);
2288:				}
2289:	
2290:				ret = text_len;
2291:				goto out;
2292:			}
2293:		}
2294:	
2295:		/*
2296:		 * Explicitly initialize the record before every prb_reserve() call.
2297:		 * prb_reserve_in_last() and prb_reserve() purposely invalidate the
2298:		 * structure when they fail.
2299:		 */
2300:		prb_rec_init_wr(&r, reserve_size);
2301:		if (!prb_reserve(&e, prb, &r)) {
2302:			/* truncate the message if it is too long for empty buffer */
2303:			truncate_msg(&reserve_size, &trunc_msg_len);
2304:	
2305:			prb_rec_init_wr(&r, reserve_size + trunc_msg_len);
2306:			if (!prb_reserve(&e, prb, &r))
2307:				goto out;
2308:		}
2309:	
2310:		/* fill message */
2311:		text_len = printk_sprint(&r.text_buf[0], reserve_size, facility, &flags, fmt, args);
2312:		if (trunc_msg_len)
2313:			memcpy(&r.text_buf[text_len], trunc_msg, trunc_msg_len);
2314:		r.info->text_len = text_len + trunc_msg_len;
2315:		r.info->facility = facility;
2316:		r.info->level = level & 7;
2317:		r.info->flags = flags & 0x1f;
2318:		r.info->ts_nsec = ts_nsec;
2319:		r.info->caller_id = caller_id;
2320:		if (dev_info)
2321:			memcpy(&r.info->dev_info, dev_info, sizeof(r.info->dev_info));
2322:	
2323:		/* A message without a trailing newline can be continued. */
2324:		if (!(flags & LOG_NEWLINE))
2325:			prb_commit(&e);
2326:		else
2327:			prb_final_commit(&e);
2328:	
2329:		ret = text_len + trunc_msg_len;
2330:	out:
2331:		printk_exit_irqrestore(recursion_ptr, irqflags);
2332:		return ret;
2333:	}
 SourceFile:kernel/printk/printk.c]

14/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
**Analyzing a Potential Data Race in the `printk` Ringbuffer**

Okay, let's break this down. I'm seeing a possible race condition in the `printk` ringbuffer. It seems to happen when two CPUs are concurrently writing to the ringbuffer. Specifically: one CPU is deep within `vprintk_store` and has hit `printk_sprint`, while another is in `vprintk_store` as well, but in `prb_reserve`.

The key here is that `printk_sprint` is writing directly to `r.text_buf`, which is this buffer space reserved for the new message. This `r.text_buf` is initialized and populated earlier in the write process via `prb_rec_init_wr` and `prb_reserve`, respectively. This buffer space *lives* within the ringbuffer's data area, which is important.

Now, on the read side, `vprintk_store` -> `prb_reserve` -> `data_alloc` -> `data_push_tail` -> `data_make_reusable`. `data_make_reusable` is the interesting function. It's pulling `blk->id` from a data block (`blk`) within the ringbuffer. The aim is to reclaim old space to make way for new messages.

The potential race kicks in if `r.text_buf` (the write area for the *new* message) overlaps with `blk` (the *old* block being made reusable). That's not right. Hang on, I think I'm getting it. `r.text_buf` is newly reserved, and `data_make_reusable` is looking at the tail of the ringbuffer to make space, because the ringbuffer is circular. If the ringbuffer is full, `prb_reserve` calls `data_alloc` which eventually advances the tail via `data_push_tail` by making old blocks reusable via `data_make_reusable`.

`data_make_reusable` is using `blk->id` to identify which descriptor owns the data block it is looking at. `blk` is a `struct prb_data_block`. Okay, I need to look at what's in that struct. This seems like a textbook race – write overlap with the read, specifically related to the ringbuffer's tail and the space reserved for new writes. The timing of `data_make_reusable` relative to `printk_sprint`'s write is crucial here. Let's see how `struct prb_data_block` is defined.



15/2 2026/01/15 08:30 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:prb_data_block SourceFile:kernel/printk/printk_ringbuffer.h]

Results:
map[Missing:true SourceCode: SourceFile:]

16/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
Okay, here's the summary, tailored for an expert audience, in the first person:

**Locating the Definition**

Right, so I need to track down the definition. Given the context, my initial hunch is that the core structure or macro related to this will be nestled within the `kernel/printk/printk_ringbuffer.c` file, or perhaps the corresponding header, `printk_ringbuffer.h`. That's where I'd expect the fundamental data structures and likely the relevant macros dealing with the ring buffer implementation of the kernel's printk system to reside. Time to crack open those files and see what we have.



17/2 2026/01/15 08:30 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:prb_data_block SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Missing:true SourceCode: SourceFile:]

18/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
Here's my thought process, as it went:

**Digging into the `printk` Ring Buffer**

Okay, hold on a second. Before I go too deep, there's a nagging feeling that I glanced over something. It's possible the data I'm looking for *is* in that same `printk` file, but I just didn't spot it. I should probably be more thorough. Let me quickly list the available structures within `kernel/printk/printk_ringbuffer.c` to refresh my memory and make sure I didn't miss something fundamental. It's always best to start with a complete picture before assuming anything is elsewhere.



19/2 2026/01/15 08:30 tool codesearch-file-index
0m Model:
unknown

Args:
map[SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Entities:[map[Kind:function Name:_prb_commit] map[Kind:function Name:_prb_read_valid] map[Kind:function Name:_prb_read_valid] map[Kind:function Name:copy_data] map[Kind:function Name:count_lines] map[Kind:function Name:data_alloc] map[Kind:function Name:data_check_size] map[Kind:function Name:data_make_reusable] map[Kind:function Name:data_push_tail] map[Kind:function Name:data_realloc] map[Kind:function Name:desc_last_finalized_seq] map[Kind:function Name:desc_make_final] map[Kind:function Name:desc_make_reusable] map[Kind:function Name:desc_push_tail] map[Kind:function Name:desc_read] map[Kind:function Name:desc_read_finalized_seq] map[Kind:function Name:desc_reopen_last] map[Kind:function Name:desc_reserve] map[Kind:function Name:desc_update_last_finalized] map[Kind:function Name:get_data] map[Kind:function Name:get_desc_state] map[Kind:function Name:get_next_lpos] map[Kind:function Name:is_blk_wrapped] map[Kind:function Name:need_more_space] map[Kind:function Name:prb_commit] map[Kind:function Name:prb_final_commit] map[Kind:function Name:prb_first_seq] map[Kind:function Name:prb_first_valid_seq] map[Kind:function Name:prb_init] map[Kind:function Name:prb_next_reserve_seq] map[Kind:function Name:prb_next_seq] map[Kind:function Name:prb_read] map[Kind:function Name:prb_read_valid] map[Kind:function Name:prb_read_valid_info] map[Kind:function Name:prb_record_text_space] map[Kind:function Name:prb_reserve] map[Kind:function Name:prb_reserve_in_last] map[Kind:function Name:space_used] map[Kind:function Name:to_blk_size] map[Kind:function Name:to_block] map[Kind:function Name:to_desc] map[Kind:function Name:to_info]] Missing:false]

20/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Missing Structure Definition Investigation**

Alright, so I'm trying to work with something, but I can't quite see the structure definition I need. That's always frustrating. It’s pretty typical for this level of kernel code to be broken out into various header files. Based on the context, I'm going to take a peek at `kernel/printk/printk_ringbuffer.h`. That's the most logical place I can think of off the top of my head for something related to ring buffers and printk functionality. That's probably where the data structure I need lives. Let's see if that pans out.



21/2 2026/01/15 08:30 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:prb_data_block SourceFile:kernel/printk/printk.c]

Results:
map[Missing:true SourceCode: SourceFile:]

22/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing the Data Structure Definition**

Okay, hold on. Before I dive too deep into this problem, let's take a step back and make sure I've got a firm grasp on the data structures involved. Specifically, I need to pinpoint the exact definition of the `struct` in question. The specific implementation details will greatly influence the best approach to take for this particular operation, whatever that may be. I'm going to quickly search the codebase (or documentation, depending on the context) for the exact `struct` declaration. This isn't just about syntax; it's about understanding the underlying memory layout, any padding considerations, and the types and order of the members. Without that, I'm essentially working blind and risk making some serious assumptions that could lead to unexpected behavior or even subtle bugs down the line. A quick review of the struct's definition is an absolute necessity before I continue.



23/2 2026/01/15 08:30 tool codesearch-definition-comment
0m Model:
unknown

Args:
map[Name:prb_data_block SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Comment: Kind: Missing:true]

24/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
Here's a summary of my thought process:

**Diving into the Data Block Structure**

Alright, I need to understand how this `prb_data_block` is constructed. My initial impulse is to use a text search for the structure definition across all files, but that might be a bit inefficient. Wait a second, the most direct approach is to just trace the `to_block` function's implementation, I'll see exactly what it's returning and how this `prb_data_block` is populated. That should quickly reveal the relevant information. Let's start there.



25/2 2026/01/15 08:30 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:to_block SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Missing:false SourceCode: 373:	static struct prb_data_block *to_block(struct prb_data_ring *data_ring,
 374:					       unsigned long begin_lpos)
 375:	{
 376:		return (void *)&data_ring->data[DATA_INDEX(data_ring, begin_lpos)];
 377:	}
 SourceFile:kernel/printk/printk_ringbuffer.c]

26/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
Okay, here's what's going through my mind:

**Finding the Definition of `prb_data_block`**

Alright, so I'm diving into this code and I see a reference to `struct prb_data_block`. Naturally, the first thing I need to do is locate where this struct is actually *defined*. I can't work effectively without understanding its structure and the fields it holds. My initial instinct, given the context, is to check the likely candidate: `kernel/printk/printk_ringbuffer.h`. That seems like the most logical place to begin my search given the mention of a ring buffer and printk in the context. Hopefully it's in there. Let's see...



27/2 2026/01/15 08:30 tool codesearch-file-index
0m Model:
unknown

Args:
map[SourceFile:kernel/printk/printk_ringbuffer.h]

Results:
map[Entities:[map[Kind:function Name:prb_commit] map[Kind:function Name:prb_final_commit] map[Kind:function Name:prb_first_seq] map[Kind:function Name:prb_first_valid_seq] map[Kind:function Name:prb_init] map[Kind:function Name:prb_next_reserve_seq] map[Kind:function Name:prb_next_seq] map[Kind:function Name:prb_read_valid] map[Kind:function Name:prb_read_valid_info] map[Kind:function Name:prb_rec_init_rd] map[Kind:function Name:prb_rec_init_wr] map[Kind:function Name:prb_record_text_space] map[Kind:function Name:prb_reserve] map[Kind:function Name:prb_reserve_in_last]] Missing:false]

28/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
**Decoding a Printk Race Condition**

Okay, so I'm trying to figure out this race condition. I'm missing the struct definition for this `prb_data_block`, which is annoying, but let's see, I don't have grep, so I'll try looking around in `kernel/printk/internal.h` or somewhere similar. I could also just double check `data_make_reusable` again.

Wait a second. Going back to `data_make_reusable`, I see that `blk` has an `id` field. The race report pinpoints the read and write: a one-byte write to `0xffffffff88eb4338` by `number`, and an eight-byte read from the same address by `data_make_reusable`. Clearly `blk->id` must be an `unsigned long` if it's eight bytes, meaning this write to one byte of it by `number` is definitely a race with the read!

But why is `number` writing to `blk->id`? Ah, right, `number` is writing formatted digits to the buffer. That leads me to `printk_sprint`, which calls `vscnprintf`. The `text` argument for `vscnprintf` is `r.text_buf`, and `r.text_buf` is part of the `prb_data_block`.

Right, now it's coming back to me. When a new data block is reserved in `prb_reserve`, the process is: find space in the `data_ring`, set `blk->id` to a new descriptor ID, and then return a pointer to the *data area* of the block – the area after `blk->id` – to the caller via `r.text_buf`.

So, does `r.text_buf` point *after* `blk->id`? That's the critical question here. I need to go back and check the code for `prb_reserve` to understand how `r` is set up to verify this and fully understand this race.



29/2 2026/01/15 08:30 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:prb_reserve SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Missing:false SourceCode:1628:	/**
1629:	 * prb_reserve() - Reserve space in the ringbuffer.
1630:	 *
1631:	 * @e:  The entry structure to setup.
1632:	 * @rb: The ringbuffer to reserve data in.
1633:	 * @r:  The record structure to allocate buffers for.
1634:	 *
1635:	 * This is the public function available to writers to reserve data.
1636:	 *
1637:	 * The writer specifies the text size to reserve by setting the
1638:	 * @text_buf_size field of @r. To ensure proper initialization of @r,
1639:	 * prb_rec_init_wr() should be used.
1640:	 *
1641:	 * Context: Any context. Disables local interrupts on success.
1642:	 * Return: true if at least text data could be allocated, otherwise false.
1643:	 *
1644:	 * On success, the fields @info and @text_buf of @r will be set by this
1645:	 * function and should be filled in by the writer before committing. Also
1646:	 * on success, prb_record_text_space() can be used on @e to query the actual
1647:	 * space used for the text data block.
1648:	 *
1649:	 * Important: @info->text_len needs to be set correctly by the writer in
1650:	 *            order for data to be readable and/or extended. Its value
1651:	 *            is initialized to 0.
1652:	 */
1653:	bool prb_reserve(struct prb_reserved_entry *e, struct printk_ringbuffer *rb,
1654:			 struct printk_record *r)
1655:	{
1656:		struct prb_desc_ring *desc_ring = &rb->desc_ring;
1657:		struct printk_info *info;
1658:		struct prb_desc *d;
1659:		unsigned long id;
1660:		u64 seq;
1661:	
1662:		if (!data_check_size(&rb->text_data_ring, r->text_buf_size))
1663:			goto fail;
1664:	
1665:		/*
1666:		 * Descriptors in the reserved state act as blockers to all further
1667:		 * reservations once the desc_ring has fully wrapped. Disable
1668:		 * interrupts during the reserve/commit window in order to minimize
1669:		 * the likelihood of this happening.
1670:		 */
1671:		local_irq_save(e->irqflags);
1672:	
1673:		if (!desc_reserve(rb, &id)) {
1674:			/* Descriptor reservation failures are tracked. */
1675:			atomic_long_inc(&rb->fail);
1676:			local_irq_restore(e->irqflags);
1677:			goto fail;
1678:		}
1679:	
1680:		d = to_desc(desc_ring, id);
1681:		info = to_info(desc_ring, id);
1682:	
1683:		/*
1684:		 * All @info fields (except @seq) are cleared and must be filled in
1685:		 * by the writer. Save @seq before clearing because it is used to
1686:		 * determine the new sequence number.
1687:		 */
1688:		seq = info->seq;
1689:		memset(info, 0, sizeof(*info));
1690:	
1691:		/*
1692:		 * Set the @e fields here so that prb_commit() can be used if
1693:		 * text data allocation fails.
1694:		 */
1695:		e->rb = rb;
1696:		e->id = id;
1697:	
1698:		/*
1699:		 * Initialize the sequence number if it has "never been set".
1700:		 * Otherwise just increment it by a full wrap.
1701:		 *
1702:		 * @seq is considered "never been set" if it has a value of 0,
1703:		 * _except_ for @infos[0], which was specially setup by the ringbuffer
1704:		 * initializer and therefore is always considered as set.
1705:		 *
1706:		 * See the "Bootstrap" comment block in printk_ringbuffer.h for
1707:		 * details about how the initializer bootstraps the descriptors.
1708:		 */
1709:		if (seq == 0 && DESC_INDEX(desc_ring, id) != 0)
1710:			info->seq = DESC_INDEX(desc_ring, id);
1711:		else
1712:			info->seq = seq + DESCS_COUNT(desc_ring);
1713:	
1714:		/*
1715:		 * New data is about to be reserved. Once that happens, previous
1716:		 * descriptors are no longer able to be extended. Finalize the
1717:		 * previous descriptor now so that it can be made available to
1718:		 * readers. (For seq==0 there is no previous descriptor.)
1719:		 */
1720:		if (info->seq > 0)
1721:			desc_make_final(rb, DESC_ID(id - 1));
1722:	
1723:		r->text_buf = data_alloc(rb, r->text_buf_size, &d->text_blk_lpos, id);
1724:		/* If text data allocation fails, a data-less record is committed. */
1725:		if (r->text_buf_size && !r->text_buf) {
1726:			prb_commit(e);
1727:			/* prb_commit() re-enabled interrupts. */
1728:			goto fail;
1729:		}
1730:	
1731:		r->info = info;
1732:	
1733:		/* Record full text space used by record. */
1734:		e->text_space = space_used(&rb->text_data_ring, &d->text_blk_lpos);
1735:	
1736:		return true;
1737:	fail:
1738:		/* Make it clear to the caller that the reserve failed. */
1739:		memset(r, 0, sizeof(*r));
1740:		return false;
1741:	}
 SourceFile:kernel/printk/printk_ringbuffer.c]

30/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
Here's the summary, framed as your thought process:

**Diving into the Memory Allocation**

Okay, so `prb_reserve` kicks things off with a call to `data_alloc`. Specifically, I see it's setting `r->text_buf` to the result of `data_alloc`, and the allocation size is based on `r->text_buf_size`. We're tracking the starting logical position with `&d->text_blk_lpos`, and we have the identifier, `id`.  That `&` means we're passing the address, so this looks like the function might be updating the position directly.

Let's dissect `data_alloc` to understand how the memory is being handled here. I need to figure out the exact allocation strategy, the impact on that logical position and how this relates to our larger block-level approach.



31/2 2026/01/15 08:30 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:data_alloc SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Missing:false SourceCode:1047:	/*
1048:	 * Allocate a new data block, invalidating the oldest data block(s)
1049:	 * if necessary. This function also associates the data block with
1050:	 * a specified descriptor.
1051:	 */
1052:	static char *data_alloc(struct printk_ringbuffer *rb, unsigned int size,
1053:				struct prb_data_blk_lpos *blk_lpos, unsigned long id)
1054:	{
1055:		struct prb_data_ring *data_ring = &rb->text_data_ring;
1056:		struct prb_data_block *blk;
1057:		unsigned long begin_lpos;
1058:		unsigned long next_lpos;
1059:	
1060:		if (size == 0) {
1061:			/*
1062:			 * Data blocks are not created for empty lines. Instead, the
1063:			 * reader will recognize these special lpos values and handle
1064:			 * it appropriately.
1065:			 */
1066:			blk_lpos->begin = EMPTY_LINE_LPOS;
1067:			blk_lpos->next = EMPTY_LINE_LPOS;
1068:			return NULL;
1069:		}
1070:	
1071:		size = to_blk_size(size);
1072:	
1073:		begin_lpos = atomic_long_read(&data_ring->head_lpos);
1074:	
1075:		do {
1076:			next_lpos = get_next_lpos(data_ring, begin_lpos, size);
1077:	
1078:			/*
1079:			 * data_check_size() prevents data block allocation that could
1080:			 * cause illegal ringbuffer states. But double check that the
1081:			 * used space will not be bigger than the ring buffer. Wrapped
1082:			 * messages need to reserve more space, see get_next_lpos().
1083:			 *
1084:			 * Specify a data-less block when the check or the allocation
1085:			 * fails.
1086:			 */
1087:			if (WARN_ON_ONCE(next_lpos - begin_lpos > DATA_SIZE(data_ring)) ||
1088:			    !data_push_tail(rb, next_lpos - DATA_SIZE(data_ring))) {
1089:				blk_lpos->begin = FAILED_LPOS;
1090:				blk_lpos->next = FAILED_LPOS;
1091:				return NULL;
1092:			}
1093:	
1094:			/*
1095:			 * 1. Guarantee any descriptor states that have transitioned
1096:			 *    to reusable are stored before modifying the newly
1097:			 *    allocated data area. A full memory barrier is needed
1098:			 *    since other CPUs may have made the descriptor states
1099:			 *    reusable. See data_push_tail:A about why the reusable
1100:			 *    states are visible. This pairs with desc_read:D.
1101:			 *
1102:			 * 2. Guarantee any updated tail lpos is stored before
1103:			 *    modifying the newly allocated data area. Another CPU may
1104:			 *    be in data_make_reusable() and is reading a block ID
1105:			 *    from this area. data_make_reusable() can handle reading
1106:			 *    a garbage block ID value, but then it must be able to
1107:			 *    load a new tail lpos. A full memory barrier is needed
1108:			 *    since other CPUs may have updated the tail lpos. This
1109:			 *    pairs with data_push_tail:B.
1110:			 */
1111:		} while (!atomic_long_try_cmpxchg(&data_ring->head_lpos, &begin_lpos,
1112:						  next_lpos)); /* LMM(data_alloc:A) */
1113:	
1114:		blk = to_block(data_ring, begin_lpos);
1115:		blk->id = id; /* LMM(data_alloc:B) */
1116:	
1117:		if (is_blk_wrapped(data_ring, begin_lpos, next_lpos)) {
1118:			/* Wrapping data blocks store their data at the beginning. */
1119:			blk = to_block(data_ring, 0);
1120:	
1121:			/*
1122:			 * Store the ID on the wrapped block for consistency.
1123:			 * The printk_ringbuffer does not actually use it.
1124:			 */
1125:			blk->id = id;
1126:		}
1127:	
1128:		blk_lpos->begin = begin_lpos;
1129:		blk_lpos->next = next_lpos;
1130:	
1131:		return &blk->data[0];
1132:	}
 SourceFile:kernel/printk/printk_ringbuffer.c]

32/2 2026/01/15 08:30 llm expert
0m Model:
unknown

Thoughts:
**The Ringbuffer Conundrum**

Okay, so I'm deep in this ringbuffer code, and it's getting weird. I'm focusing on a reported memory access issue, a race condition, where one process is writing a single byte, and another is reading eight bytes from the same address, which KCSAN flagged. The debugger's pointing me at this `data_make_reusable` function, and the `number` function. It seems like the crux of the issue boils down to the interaction between reclaiming old data and allocating new space in this circular buffer.

My initial thought was that the address overlap was due to an out-of-bounds write or some miscalculation. But it's more subtle than that. `data_alloc` hands out pointers to the data section of a block. `data_make_reusable`, on the other hand, *reads* the ID field at the beginning of the block, assuming it's part of an older, finalized block. Now, here's the problem: The ID and the data are contiguous in memory.

The critical insight from the comments in the source is that `data_make_reusable` explicitly states it's dealing with a potential data race, where a writer may be actively writing to the location the reader is trying to read the ID from. The write happens when a new block is allocated, and the old tail is being pushed, so the ringbuffer is circular and the head is trying to advance.

So, here's the scenario: the tail's advancing, the head has caught up (or nearly caught up), and a writer is allocating a new block, writing data. In this specific case, the reader, `data_make_reusable`, is trying to read the old block's ID, which now contains a portion of the *new* block's data. That's the data race. The reader sees a garbage value and tries to treat it as a valid ID, though in most cases the code will detect this issue.

The problem, therefore, isn't about *where* the write/read is happening (though it has to be a specific location within the buffer, with a circular logic). The problem is *when*. Because `data_alloc` has to perform certain operations to update the tail, *before* actually allocating new memory space, but other writers may still be accessing the ringbuffer space. The `data_make_reusable` call attempts to push the tail to make space, but at the same time, this space can be taken already.

This race is a classic "head chasing tail" scenario in a circular buffer. The `DATA_INDEX` and the circular nature of the buffer allow this overlap to occur. So the `data_make_reusable` is the root of the issue, because it is accessing the previous, "old", block's data while it's in the process of being overwritten by the new data from the "head".



33/2 2026/01/15 08:31 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:data_push_tail SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Missing:false SourceCode: 644:	/*
 645:	 * Advance the data ring tail to at least @lpos. This function puts
 646:	 * descriptors into the reusable state if the tail is pushed beyond
 647:	 * their associated data block.
 648:	 */
 649:	static bool data_push_tail(struct printk_ringbuffer *rb, unsigned long lpos)
 650:	{
 651:		struct prb_data_ring *data_ring = &rb->text_data_ring;
 652:		unsigned long tail_lpos_new;
 653:		unsigned long tail_lpos;
 654:		unsigned long next_lpos;
 655:	
 656:		/* If @lpos is from a data-less block, there is nothing to do. */
 657:		if (LPOS_DATALESS(lpos))
 658:			return true;
 659:	
 660:		/*
 661:		 * Any descriptor states that have transitioned to reusable due to the
 662:		 * data tail being pushed to this loaded value will be visible to this
 663:		 * CPU. This pairs with data_push_tail:D.
 664:		 *
 665:		 * Memory barrier involvement:
 666:		 *
 667:		 * If data_push_tail:A reads from data_push_tail:D, then this CPU can
 668:		 * see desc_make_reusable:A.
 669:		 *
 670:		 * Relies on:
 671:		 *
 672:		 * MB from desc_make_reusable:A to data_push_tail:D
 673:		 *    matches
 674:		 * READFROM from data_push_tail:D to data_push_tail:A
 675:		 *    thus
 676:		 * READFROM from desc_make_reusable:A to this CPU
 677:		 */
 678:		tail_lpos = atomic_long_read(&data_ring->tail_lpos); /* LMM(data_push_tail:A) */
 679:	
 680:		/*
 681:		 * Loop until the tail lpos is at or beyond @lpos. This condition
 682:		 * may already be satisfied, resulting in no full memory barrier
 683:		 * from data_push_tail:D being performed. However, since this CPU
 684:		 * sees the new tail lpos, any descriptor states that transitioned to
 685:		 * the reusable state must already be visible.
 686:		 */
 687:		while (need_more_space(data_ring, tail_lpos, lpos)) {
 688:			/*
 689:			 * Make all descriptors reusable that are associated with
 690:			 * data blocks before @lpos.
 691:			 */
 692:			if (!data_make_reusable(rb, tail_lpos, lpos, &next_lpos)) {
 693:				/*
 694:				 * 1. Guarantee the block ID loaded in
 695:				 *    data_make_reusable() is performed before
 696:				 *    reloading the tail lpos. The failed
 697:				 *    data_make_reusable() may be due to a newly
 698:				 *    recycled data area causing the tail lpos to
 699:				 *    have been previously pushed. This pairs with
 700:				 *    data_alloc:A and data_realloc:A.
 701:				 *
 702:				 *    Memory barrier involvement:
 703:				 *
 704:				 *    If data_make_reusable:A reads from data_alloc:B,
 705:				 *    then data_push_tail:C reads from
 706:				 *    data_push_tail:D.
 707:				 *
 708:				 *    Relies on:
 709:				 *
 710:				 *    MB from data_push_tail:D to data_alloc:B
 711:				 *       matching
 712:				 *    RMB from data_make_reusable:A to
 713:				 *    data_push_tail:C
 714:				 *
 715:				 *    Note: data_push_tail:D and data_alloc:B can be
 716:				 *          different CPUs. However, the data_alloc:B
 717:				 *          CPU (which performs the full memory
 718:				 *          barrier) must have previously seen
 719:				 *          data_push_tail:D.
 720:				 *
 721:				 * 2. Guarantee the descriptor state loaded in
 722:				 *    data_make_reusable() is performed before
 723:				 *    reloading the tail lpos. The failed
 724:				 *    data_make_reusable() may be due to a newly
 725:				 *    recycled descriptor causing the tail lpos to
 726:				 *    have been previously pushed. This pairs with
 727:				 *    desc_reserve:D.
 728:				 *
 729:				 *    Memory barrier involvement:
 730:				 *
 731:				 *    If data_make_reusable:B reads from
 732:				 *    desc_reserve:F, then data_push_tail:C reads
 733:				 *    from data_push_tail:D.
 734:				 *
 735:				 *    Relies on:
 736:				 *
 737:				 *    MB from data_push_tail:D to desc_reserve:F
 738:				 *       matching
 739:				 *    RMB from data_make_reusable:B to
 740:				 *    data_push_tail:C
 741:				 *
 742:				 *    Note: data_push_tail:D and desc_reserve:F can
 743:				 *          be different CPUs. However, the
 744:				 *          desc_reserve:F CPU (which performs the
 745:				 *          full memory barrier) must have previously
 746:				 *          seen data_push_tail:D.
 747:				 */
 748:				smp_rmb(); /* LMM(data_push_tail:B) */
 749:	
 750:				tail_lpos_new = atomic_long_read(&data_ring->tail_lpos
 751:								); /* LMM(data_push_tail:C) */
 752:				if (tail_lpos_new == tail_lpos)
 753:					return false;
 754:	
 755:				/* Another CPU pushed the tail. Try again. */
 756:				tail_lpos = tail_lpos_new;
 757:				continue;
 758:			}
 759:	
 760:			/*
 761:			 * Guarantee any descriptor states that have transitioned to
 762:			 * reusable are stored before pushing the tail lpos. A full
 763:			 * memory barrier is needed since other CPUs may have made
 764:			 * the descriptor states reusable. This pairs with
 765:			 * data_push_tail:A.
 766:			 */
 767:			if (atomic_long_try_cmpxchg(&data_ring->tail_lpos, &tail_lpos,
 768:						    next_lpos)) { /* LMM(data_push_tail:D) */
 769:				break;
 770:			}
 771:		}
 772:	
 773:		return true;
 774:	}
 SourceFile:kernel/printk/printk_ringbuffer.c]

34/2 2026/01/15 08:31 llm expert
0m Model:
unknown

Thoughts:
**Thinking Through a Potential Data Race**

Okay, so I'm looking at this `data_push_tail` function, and it's using `cmpxchg` to update `tail_lpos`. That immediately flags a potential concurrency issue in my mind. If multiple writers are trying to update `tail_lpos` simultaneously, things could get interesting.

Let's walk through a scenario: Writer A and Writer B both read `tail_lpos` as, say, zero. Writer A starts by calling `data_make_reusable` for its intended `lpos`. Simultaneously, Writer B does the same. Now, Writer A finishes `data_make_reusable` and successfully updates `tail_lpos` using the atomic compare-and-swap. Meanwhile, Writer B is still inside `data_make_reusable` and reading `blk->id` at some location, `lpos_begin`.

Here's the crux: if Writer A has already advanced `head_lpos` and is now writing data, it means `data_push_tail` is complete. If Writer B now calls `data_push_tail`, it *might* see an outdated `tail_lpos` and start writing from that point. But then, inside Writer B's `data_push_tail`, it's going to read the *latest* `tail_lpos` using an atomic read (at line 678).

Hang on, what if the atomic read at line 678 returns an old tail value? Is this a memory ordering issue? Actually, the `cmpxchg` in `data_alloc` should provide some ordering guarantees, so probably not.

Aha! This comment in `data_alloc` clarifies things: "Another CPU may be in `data_make_reusable()` and is reading a block ID from this area." It explicitly states that a data race is possible, and furthermore that `data_make_reusable()` is designed to handle garbage values for the block ID.

So, it's an *intentional* data race, and handled in the code. I'd call it "benign" in that it won't crash things or cause corruption, but I bet KCSAN (Kernel Concurrency Sanitizer) will still flag it unless it's explicitly silenced with something like `data_race()`.

Wait a minute... I don't see any `data_race()` call for this `blk->id` read. I see the comment line in the code, but no `data_race()`.

Okay, back to the details of vsprintf. What data is it actually writing to the buffer? It's just a byte. I need to check the code in lib/vsprintf.c to figure out how the data is being pushed into memory.



35/2 2026/01/15 08:31 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:number SourceFile:lib/vsprintf.c]

Results:
map[Missing:false SourceCode: 463:	static noinline_for_stack
 464:	char *number(char *buf, char *end, unsigned long long num,
 465:		     struct printf_spec spec)
 466:	{
 467:		/* put_dec requires 2-byte alignment of the buffer. */
 468:		char tmp[3 * sizeof(num)] __aligned(2);
 469:		char sign;
 470:		char locase;
 471:		int need_pfx = ((spec.flags & SPECIAL) && spec.base != 10);
 472:		int i;
 473:		bool is_zero = num == 0LL;
 474:		int field_width = spec.field_width;
 475:		int precision = spec.precision;
 476:	
 477:		/* locase = 0 or 0x20. ORing digits or letters with 'locase'
 478:		 * produces same digits or (maybe lowercased) letters */
 479:		locase = (spec.flags & SMALL);
 480:		if (spec.flags & LEFT)
 481:			spec.flags &= ~ZEROPAD;
 482:		sign = 0;
 483:		if (spec.flags & SIGN) {
 484:			if ((signed long long)num < 0) {
 485:				sign = '-';
 486:				num = -(signed long long)num;
 487:				field_width--;
 488:			} else if (spec.flags & PLUS) {
 489:				sign = '+';
 490:				field_width--;
 491:			} else if (spec.flags & SPACE) {
 492:				sign = ' ';
 493:				field_width--;
 494:			}
 495:		}
 496:		if (need_pfx) {
 497:			if (spec.base == 16)
 498:				field_width -= 2;
 499:			else if (!is_zero)
 500:				field_width--;
 501:		}
 502:	
 503:		/* generate full string in tmp[], in reverse order */
 504:		i = 0;
 505:		if (num < spec.base)
 506:			tmp[i++] = hex_asc_upper[num] | locase;
 507:		else if (spec.base != 10) { /* 8 or 16 */
 508:			int mask = spec.base - 1;
 509:			int shift = 3;
 510:	
 511:			if (spec.base == 16)
 512:				shift = 4;
 513:			do {
 514:				tmp[i++] = (hex_asc_upper[((unsigned char)num) & mask] | locase);
 515:				num >>= shift;
 516:			} while (num);
 517:		} else { /* base 10 */
 518:			i = put_dec(tmp, num) - tmp;
 519:		}
 520:	
 521:		/* printing 100 using %2d gives "100", not "00" */
 522:		if (i > precision)
 523:			precision = i;
 524:		/* leading space padding */
 525:		field_width -= precision;
 526:		if (!(spec.flags & (ZEROPAD | LEFT))) {
 527:			while (--field_width >= 0) {
 528:				if (buf < end)
 529:					*buf = ' ';
 530:				++buf;
 531:			}
 532:		}
 533:		/* sign */
 534:		if (sign) {
 535:			if (buf < end)
 536:				*buf = sign;
 537:			++buf;
 538:		}
 539:		/* "0x" / "0" prefix */
 540:		if (need_pfx) {
 541:			if (spec.base == 16 || !is_zero) {
 542:				if (buf < end)
 543:					*buf = '0';
 544:				++buf;
 545:			}
 546:			if (spec.base == 16) {
 547:				if (buf < end)
 548:					*buf = ('X' | locase);
 549:				++buf;
 550:			}
 551:		}
 552:		/* zero or space padding */
 553:		if (!(spec.flags & LEFT)) {
 554:			char c = ' ' + (spec.flags & ZEROPAD);
 555:	
 556:			while (--field_width >= 0) {
 557:				if (buf < end)
 558:					*buf = c;
 559:				++buf;
 560:			}
 561:		}
 562:		/* hmm even more zero padding? */
 563:		while (i <= --precision) {
 564:			if (buf < end)
 565:				*buf = '0';
 566:			++buf;
 567:		}
 568:		/* actual digits of result */
 569:		while (--i >= 0) {
 570:			if (buf < end)
 571:				*buf = tmp[i];
 572:			++buf;
 573:		}
 574:		/* trailing space padding */
 575:		while (--field_width >= 0) {
 576:			if (buf < end)
 577:				*buf = ' ';
 578:			++buf;
 579:		}
 580:	
 581:		return buf;
 582:	}
 SourceFile:lib/vsprintf.c]

36/2 2026/01/15 08:31 llm expert
0m Model:
unknown

Thoughts:
**Diving Deep into a KCSAN Report: A Benign Data Race in the Printk Ringbuffer**

Okay, so I'm looking at this KCSAN report, specifically lines 571 and 572 in `lib/vsprintf.c` from the looks of it. Line 572, which is `++buf;`, is what the report points to, but, in my source view, the actual write happens on line 571: `*buf = tmp[i];`. KCSAN's offsets aren't always perfect, but that's fine. It's a write of a character to the buffer, that much is clear.

The report suggests a data race. My initial instinct is correct; the suspicion is that `data_make_reusable` is reading a block ID from memory that's being used as a text buffer for a new printk message. But wait, the comment in `data_make_reusable` and `data_alloc` confirms this race is known and handled, which means it should be benign.

But why? I need to understand the safeguards. The code doesn't blindly trust the `id` it reads. `data_make_reusable` calls `desc_read(desc_ring, id, &desc, NULL, NULL)`. `desc_read` will return `desc_miss` if the `id` is invalid. Even if it returns a state, `data_make_reusable` checks if the descriptor actually points back to the data block it's looking at, specifically at line 622: `if (blk_lpos->begin != lpos_begin)` it returns false.

`lpos_begin` is the logical position of the data block we are trying to reclaim. So, if the read `id` was garbage, the descriptor for that garbage `id` is highly unlikely to match `lpos_begin`. If it doesn't match, `data_make_reusable` returns `false`. `data_push_tail` will then reload the tail at line 748, which effectively retries with an updated view of the world. If another CPU pushed the tail, `tail_lpos_new` will be different from `tail_lpos`, and it will `continue` the loop.

So the code *is* designed to handle this race. It's an optimization for performance, to avoid locking on every data block access in the ringbuffer.

Is there any way for the race to still cause harm? The only way I see is if the garbage `id` *happens* to match a descriptor, and *that* descriptor happens to have `blk_lpos->begin == lpos_begin`. But if `blk_lpos->begin == lpos_begin`, it means the descriptor owns that data block. And if that descriptor is in `desc_finalized` state, it's safe to make it reusable.

But... wait! If the memory was repurposed for text, the original block at that `lpos` was already reclaimed. If that's the case, the owning descriptor would have been updated, so the `blk_lpos->begin` wouldn't be `lpos_begin` anymore.

Then another thought arises, can a descriptor's `id` be reused? Yes, descriptor IDs also wrap around.

This all points to a classic "optimistic read" pattern. It's reading without synchronization, then checking the validity with other synchronized state.

The fact that the source code documents it as a data race, tells me it's very likely to be benign.

Let me just check if there are other reports of this race. Searching for "KCSAN data-race in data_push_tail / number" or "data_make_reusable data race."

Actually, I should verify if the read should be marked as `data_race()` to suppress the KCSAN warning. Many similar cases in the kernel are marked with `data_race()` or `READ_ONCE()`.

Line 607 in `kernel/printk/printk_ringbuffer.c` shows that it's just a raw read: `id = blk->id; /* LMM(data_make_reusable:A) */`. I'm thinking, `READ_ONCE` might be good here. However, even without it, on most architectures, an 8-byte aligned read of an `unsigned long` is atomic.

The write is also a raw write `*buf = tmp[i];`. This is a 1-byte write.

Since the code specifically handles the garbage `id` case, and this is an intentional design of the printk ringbuffer (which is lockless), I'll conclude it'


37/2 2026/01/15 08:31 tool codesearch-definition-source
0m Model:
unknown

Args:
map[IncludeLines:true Name:desc_read SourceFile:kernel/printk/printk_ringbuffer.c]

Results:
map[Missing:false SourceCode: 440:	/*
 441:	 * Get a copy of a specified descriptor and return its queried state. If the
 442:	 * descriptor is in an inconsistent state (miss or reserved), the caller can
 443:	 * only expect the descriptor's @state_var field to be valid.
 444:	 *
 445:	 * The sequence number and caller_id can be optionally retrieved. Like all
 446:	 * non-state_var data, they are only valid if the descriptor is in a
 447:	 * consistent state.
 448:	 */
 449:	static enum desc_state desc_read(struct prb_desc_ring *desc_ring,
 450:					 unsigned long id, struct prb_desc *desc_out,
 451:					 u64 *seq_out, u32 *caller_id_out)
 452:	{
 453:		struct printk_info *info = to_info(desc_ring, id);
 454:		struct prb_desc *desc = to_desc(desc_ring, id);
 455:		atomic_long_t *state_var = &desc->state_var;
 456:		enum desc_state d_state;
 457:		unsigned long state_val;
 458:	
 459:		/* Check the descriptor state. */
 460:		state_val = atomic_long_read(state_var); /* LMM(desc_read:A) */
 461:		d_state = get_desc_state(id, state_val);
 462:		if (d_state == desc_miss || d_state == desc_reserved) {
 463:			/*
 464:			 * The descriptor is in an inconsistent state. Set at least
 465:			 * @state_var so that the caller can see the details of
 466:			 * the inconsistent state.
 467:			 */
 468:			goto out;
 469:		}
 470:	
 471:		/*
 472:		 * Guarantee the state is loaded before copying the descriptor
 473:		 * content. This avoids copying obsolete descriptor content that might
 474:		 * not apply to the descriptor state. This pairs with _prb_commit:B.
 475:		 *
 476:		 * Memory barrier involvement:
 477:		 *
 478:		 * If desc_read:A reads from _prb_commit:B, then desc_read:C reads
 479:		 * from _prb_commit:A.
 480:		 *
 481:		 * Relies on:
 482:		 *
 483:		 * WMB from _prb_commit:A to _prb_commit:B
 484:		 *    matching
 485:		 * RMB from desc_read:A to desc_read:C
 486:		 */
 487:		smp_rmb(); /* LMM(desc_read:B) */
 488:	
 489:		/*
 490:		 * Copy the descriptor data. The data is not valid until the
 491:		 * state has been re-checked. A memcpy() for all of @desc
 492:		 * cannot be used because of the atomic_t @state_var field.
 493:		 */
 494:		if (desc_out) {
 495:			memcpy(&desc_out->text_blk_lpos, &desc->text_blk_lpos,
 496:			       sizeof(desc_out->text_blk_lpos)); /* LMM(desc_read:C) */
 497:		}
 498:		if (seq_out)
 499:			*seq_out = info->seq; /* also part of desc_read:C */
 500:		if (caller_id_out)
 501:			*caller_id_out = info->caller_id; /* also part of desc_read:C */
 502:	
 503:		/*
 504:		 * 1. Guarantee the descriptor content is loaded before re-checking
 505:		 *    the state. This avoids reading an obsolete descriptor state
 506:		 *    that may not apply to the copied content. This pairs with
 507:		 *    desc_reserve:F.
 508:		 *
 509:		 *    Memory barrier involvement:
 510:		 *
 511:		 *    If desc_read:C reads from desc_reserve:G, then desc_read:E
 512:		 *    reads from desc_reserve:F.
 513:		 *
 514:		 *    Relies on:
 515:		 *
 516:		 *    WMB from desc_reserve:F to desc_reserve:G
 517:		 *       matching
 518:		 *    RMB from desc_read:C to desc_read:E
 519:		 *
 520:		 * 2. Guarantee the record data is loaded before re-checking the
 521:		 *    state. This avoids reading an obsolete descriptor state that may
 522:		 *    not apply to the copied data. This pairs with data_alloc:A and
 523:		 *    data_realloc:A.
 524:		 *
 525:		 *    Memory barrier involvement:
 526:		 *
 527:		 *    If copy_data:A reads from data_alloc:B, then desc_read:E
 528:		 *    reads from desc_make_reusable:A.
 529:		 *
 530:		 *    Relies on:
 531:		 *
 532:		 *    MB from desc_make_reusable:A to data_alloc:B
 533:		 *       matching
 534:		 *    RMB from desc_read:C to desc_read:E
 535:		 *
 536:		 *    Note: desc_make_reusable:A and data_alloc:B can be different
 537:		 *          CPUs. However, the data_alloc:B CPU (which performs the
 538:		 *          full memory barrier) must have previously seen
 539:		 *          desc_make_reusable:A.
 540:		 */
 541:		smp_rmb(); /* LMM(desc_read:D) */
 542:	
 543:		/*
 544:		 * The data has been copied. Return the current descriptor state,
 545:		 * which may have changed since the load above.
 546:		 */
 547:		state_val = atomic_long_read(state_var); /* LMM(desc_read:E) */
 548:		d_state = get_desc_state(id, state_val);
 549:	out:
 550:		if (desc_out)
 551:			atomic_long_set(&desc_out->state_var, state_val);
 552:		return d_state;
 553:	}
 SourceFile:kernel/printk/printk_ringbuffer.c]

38/2 2026/01/15 08:31 llm expert
0m Model:
unknown

Thoughts:
**Analysis of Printk Ringbuffer Data Race**

Okay, let's break this down. `desc_read` uses `to_info` and `to_desc`, and I see how they're implemented. These functions are critical because they're taking an `id` and indexing into an array. If `id` is garbage from a "tx timeo" scenario, the `DESC_INDEX` macro is key. It masks the `id` to keep the index within bounds, ensuring no out-of-bounds access. The code is carefully bounded by the array size due to the masking. The result of `desc_read` is then validated by `data_make_reusable`, making sure the `id` is just used as an index into the bounded array. Looks safe so far.

Wait, `to_info` also does the exact same thing; everything is bounded. The data race *is* intentional, and the code appears robust. The system is designed to handle garbage values.

Now the write. It's a normal write. Seems like a simple write of a digit to a buffer within `number`. It's not the problem; the race is handled by the reader. So, the data race is benign.

Then I checked the KCSAN report. This is important to understand what the race actually looks like. It shows `0x00000000ffffe607` changing to `0x6f656d6974207874`. That first value seems like a plausible ID; the second is "tx timeo", confirming what I suspected.

I know the `printk_ringbuffer` is known for being careful about races. I remember seeing comments like `LMM(...)` referencing its memory model documentation. If the developers say it's a data race and explain it's acceptable, then it most likely is.

Okay, let's verify if `LMM(data_make_reusable:A)` is mentioned in the kernel documentation. Well, I can't check the documentation, but I can see the comment in the code, line 600-606: The comment confirms what I'm seeing: the data race is intentional. The comment's explanation supports that this is a benign, expected behavior.

Ah, but KCSAN is reporting it because it wasn't marked. In recent kernels, these accesses are often wrapped in `data_race()`. Should I be concerned that it wasn't? Maybe it was just missed.

Is it harmful in any other way? Could the compiler screw things up, maybe by assuming the value of `id` doesn't change between two reads? But, there is only one read of `blk->id` in the loop. The `id` is passed as value to `desc_read`. If `id` changes *during* `desc_read`? But no, `id` is passed by value to `desc_read`, so it's a local copy.

Let's look at `data_make_reusable` again... The `id` is read once into a local variable. The race occurs here. If the `number` write happens at the same time, `id` might get partial values. But even then, the code is already built to handle it. So, yes, it's benign.

Should I check if it's already fixed in later kernels? No, my job is to analyze this report as it is.

Verdict: Benign.

One final thought: is `blk->id` naturally aligned? Let's check the code: `to_block` returns an address, which is the offset of the data. And the array's size is large, and `DATA_INDEX` is just an offset. The data ring's `data` array is large, and `DATA_INDEX` is just an offset. If `begin_lpos` is always aligned to a certain power of 2, then `blk` would be aligned. The alignment is forced by `to_blk_size`. So, `id` (which is `unsigned long`) should be naturally aligned. The code is safe from crashing due to the checks and bounding.

So even if there's a race, it's on a naturally aligned `unsigned long`, and the code gracefully handles the garbage.

Okay, I'm confident.

**Summary**:

The data race is in the `printk` ringbuffer's `data_make_reusable` when reading the block ID from the data ring. This can race with a writer because the head might wrap around, and the tail is still pushing. The ringbuffer is lockless, which is why this is occurring. The ringbuffer specifically handles this race by checking the read ID against the descriptor state and back-


39/2 2026/01/15 08:32 tool set-results
0m Model:
unknown

Args:
map[Benign:true Confident:true]

Results:
map[Benign:true Confident:true]

40/2 2026/01/15 08:32 llm expert
0m Model:
unknown

Error:
unexpected reply fields ({Content:0xc0058eb4a0 CitationMetadata:0xc0094009a8 FinishMessage: TokenCount:0 FinishReason:STOP AvgLogprobs:0 GroundingMetadata:<nil> Index:0 LogprobsResult:<nil> SafetyRatings:[] URLContextMetadata:<nil>})

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)