Re: [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers
From: sashiko-bot@kernel.org
Date: 2026-08-13 16:30:11
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: Pre-existing issues: - [Critical] Structural TOCTOU race: global order and size are updated before replacing per-CPU buffer pages, leading to Heap Buffer Overflow. - [High] Incomplete TOCTOU fix: alloc_cpu_data() evaluates buffer->subbuf_order locklessly, causing bpage->order mismatch. - [High] Race window between pre-flush loop and buffer->subbuf_order update allows caching undersized pages. - [High] Post-update flush loop unconditionally frees newly cached pages using old_order. - [High] Error path fails to flush cached pages when rolling back to old_order. --
--- Patch [1]: [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers ---commit d1c3f71c852aab912bda60e5894b681e43da7c7b Author: Masami Hiramatsu (Google) [off-list ref] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers This commit attempts to fix a TOCTOU race condition in the ring buffer code by ensuring that the per-CPU free_page cache is flushed and protected by the cpu_buffer lock during order changes. It also updates reader functions to validate the page order under the same lock to prevent using mismatched page sizes.
quoted hunk ↗ jump to hunk
diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index c2c57fbf6f448..9a11c663cff5b 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c@@ -6957,11 +6957,11 @@ ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu) if (!bpage) return ERR_PTR(-ENOMEM); - bpage->order = buffer->subbuf_order; cpu_buffer = buffer->buffers[cpu]; local_irq_save(flags); arch_spin_lock(&cpu_buffer->lock); + bpage->order = buffer->subbuf_order; if (cpu_buffer->free_page) { bpage->data = cpu_buffer->free_page; cpu_buffer->free_page = NULL; }
[Severity: High]
This isn't a bug introduced by this patch, but does this locklessly evaluate
buffer->subbuf_order after dropping the lock?
Looking at the code just below this hunk in ring_buffer_alloc_read_page():
arch_spin_unlock(&cpu_buffer->lock);
local_irq_restore(flags);
if (bpage->data) {
rb_init_data_page(bpage->data);
} else {
bpage->data = alloc_cpu_data(cpu, cpu_buffer->buffer->subbuf_order);
...
If a concurrent writer changes the global order after the lock is dropped but
before alloc_cpu_data() evaluates the argument, the allocated page size will
mismatch the stored bpage->order.
When ring_buffer_free_read_page() later frees this page using bpage->order, it
could free a mismatched page size and corrupt the buddy allocator.
[ ... ]quoted hunk ↗ jump to hunk
@@ -7350,6 +7350,27 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order) /* Make sure all commits have finished */ synchronize_rcu(); + /* Flush any cached free_page allocated with old_order */ + for_each_buffer_cpu(buffer, cpu) { + struct buffer_data_page *old_free; + unsigned long flags; + + if (!cpumask_test_cpu(cpu, buffer->cpumask)) + continue; + + cpu_buffer = buffer->buffers[cpu]; + + local_irq_save(flags); + arch_spin_lock(&cpu_buffer->lock); + old_free = cpu_buffer->free_page; + cpu_buffer->free_page = NULL; + arch_spin_unlock(&cpu_buffer->lock); + local_irq_restore(flags); + + if (old_free) + free_pages((unsigned long)old_free, old_order); + } + buffer->subbuf_order = order; buffer->subbuf_size = psize - BUF_PAGE_HDR_SIZE;
[Severity: High] This isn't a bug introduced by this patch, but does updating the global order after the flush loop leave a race window? During the unprotected window between the flush loop completing and updating buffer->subbuf_order, the global order is still old_order. A concurrent reader could call ring_buffer_free_read_page(), see that data_page->order matches buffer->subbuf_order (both old), and cache an old_order page back into cpu_buffer->free_page. Once the order is updated below, subsequent readers might extract this cached old_order page, tag it with the new order, and eventually free it with the new order, causing buddy allocator corruption. [Severity: Critical] This isn't a bug introduced by this patch, but does updating the global order and size here, before replacing per-CPU buffer pages, create a structural race? If a concurrent reader calls ring_buffer_read_page(), it validates its new-order data_page against the new global order and swaps it into the ring buffer. It extracts the old, unreplaced reader->page into dpage. If the buffer is expanding, dpage is an undersized old-order page. However, the reader executes memset() using the prematurely updated global buffer->subbuf_size: ring_buffer_read_page(): if (size < buffer->subbuf_size) memset(&dpage->data[size], 0, buffer->subbuf_size - size); This would blindly write zeroes far beyond the bounds of the extracted old-order dpage, resulting in a heap buffer overflow reachable via unprivileged tracefs reads. [ ... ]
quoted hunk ↗ jump to hunk
@@ -7431,8 +7452,10 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order) cpu_buffer->nr_pages = cpu_buffer->nr_pages_to_update; cpu_buffer->nr_pages_to_update = 0; + arch_spin_lock(&cpu_buffer->lock); old_free_data_page = cpu_buffer->free_page; cpu_buffer->free_page = NULL; + arch_spin_unlock(&cpu_buffer->lock); rb_head_page_activate(cpu_buffer);
[Severity: High] This isn't a bug introduced by this patch, but does this second flush loop unconditionally free potentially new-order pages with old_order? After the global order is set to new_order, this function performs a sleepable allocation loop (__rb_allocate_pages). During this time, concurrent readers can allocate and free new_order pages, successfully caching them into cpu_buffer->free_page. When this code extracts cpu_buffer->free_page and subsequently frees it down below: free_pages((unsigned long)old_free_data_page, old_order); It blindly forces what could be a new_order page to be freed with old_order, corrupting the buddy allocator. [Severity: High] This isn't a bug introduced by this patch, but does the error path fail to flush cached pages when rolling back? If __rb_allocate_pages() fails, the code jumps to the error label and restores buffer->subbuf_order = old_order: ring_buffer_subbuf_order_set(): error: buffer->subbuf_order = old_order; buffer->subbuf_size = old_size; During the window where it was new_order, concurrent readers may have cached new_order pages into cpu_buffer->free_page. The error path returns without flushing cpu_buffer->free_page. Subsequent readers would extract these new_order pages, tag them as old_order, and free them with old_order, causing a memory leak (if expanding) or allocator corruption (if shrinking). -- Sashiko AI review · https://sashiko.dev/#/patchset/178663777320.475864.4716637934003507750.stgit@devnote2?part=1