* [PATCH 0/1] ring-buffer: Fix sub-buffer order updates and free_page leaks
@ 2026-08-13 16:16 Masami Hiramatsu (Google)
2026-08-13 16:16 ` [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers Masami Hiramatsu (Google)
2026-08-13 17:25 ` [PATCH 0/1] ring-buffer: Fix sub-buffer order updates and free_page leaks Vincent Donnefort
0 siblings, 2 replies; 4+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-08-13 16:16 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Vincent Donnefort
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, kernel-team
Hi Vincent,
Here is my patch to fix the race and free_page problem.
Let's see what Sashiko says for this.
Thank you,
---
base-commit: 3d6d817622b0a9721e3cc404df3469171582be13
Masami Hiramatsu (Google) (1):
ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers
kernel/trace/ring_buffer.c | 35 +++++++++++++++++++++++++++++------
1 file changed, 29 insertions(+), 6 deletions(-)
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers 2026-08-13 16:16 [PATCH 0/1] ring-buffer: Fix sub-buffer order updates and free_page leaks Masami Hiramatsu (Google) @ 2026-08-13 16:16 ` Masami Hiramatsu (Google) 2026-08-13 16:30 ` sashiko-bot 2026-08-13 17:25 ` [PATCH 0/1] ring-buffer: Fix sub-buffer order updates and free_page leaks Vincent Donnefort 1 sibling, 1 reply; 4+ messages in thread From: Masami Hiramatsu (Google) @ 2026-08-13 16:16 UTC (permalink / raw) To: Steven Rostedt, Masami Hiramatsu, Vincent Donnefort Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, kernel-team From: Masami Hiramatsu (Google) <mhiramat@kernel.org> When ring_buffer_subbuf_order_set() updates buffer->subbuf_order, it previously modified buffer->subbuf_order before clearing the cached per-CPU free_page entries. Furthermore, clearing cpu_buffer->free_page was done under cpu_buffer->reader_lock, whereas ring_buffer_alloc_read_page() protects cpu_buffer->free_page using arch_spin_lock(&cpu_buffer->lock). Because ring_buffer_alloc_read_page(), ring_buffer_free_read_page(), and ring_buffer_read_page() checked buffer->subbuf_order locklessly before accessing reader resources, a TOCTOU race allowed a concurrent reader to obtain, cache, or swap a page allocated under an outdated order while tagging bpage->order with the new order. This allowed undersized pages to be swapped into the ring buffer, leading to heap buffer overflows, or caused free_pages() to be called with an invalid order. Fix this by: 1. Flushing and freeing all per-CPU cached free_page entries under arch_spin_lock(&cpu_buffer->lock) using old_order before modifying buffer->subbuf_order. 2. Protecting bpage->order assignment under arch_spin_lock(&cpu_buffer->lock) in ring_buffer_alloc_read_page(). 3. Moving the buffer->subbuf_order validation inside arch_spin_lock(&cpu_buffer->lock) in ring_buffer_free_read_page(). 4. Re-validating buffer->subbuf_order inside reader_lock in ring_buffer_read_page(). 5. Protecting cpu_buffer->free_page extraction with arch_spin_lock(&cpu_buffer->lock) in ring_buffer_subbuf_order_set(). Fixes: 2808e31ec12e ("ring-buffer: Add interface for configuring trace sub buffer size") Assisted-by: Antigravity:gemini-3.6-flash Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> --- kernel/trace/ring_buffer.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 2667992f0aa2..6d180689ad59 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; @@ -7010,13 +7010,13 @@ void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, * is different from the subbuffer order of the buffer - * we can't reuse it */ - if (page_ref_count(page) > 1 || data_page->order != buffer->subbuf_order) + if (page_ref_count(page) > 1) goto out; local_irq_save(flags); arch_spin_lock(&cpu_buffer->lock); - if (!cpu_buffer->free_page) { + if (data_page->order == buffer->subbuf_order && !cpu_buffer->free_page) { cpu_buffer->free_page = dpage; dpage = NULL; } @@ -7094,15 +7094,15 @@ int ring_buffer_read_page(struct trace_buffer *buffer, if (!data_page || !data_page->data) return -1; - if (data_page->order != buffer->subbuf_order) - return -1; - dpage = data_page->data; if (!dpage) return -1; guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock); + if (data_page->order != buffer->subbuf_order) + return -1; + reader = rb_get_reader_page(cpu_buffer); if (!reader) return -1; @@ -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; @@ -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); ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers 2026-08-13 16:16 ` [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers Masami Hiramatsu (Google) @ 2026-08-13 16:30 ` sashiko-bot 0 siblings, 0 replies; 4+ messages in thread From: sashiko-bot @ 2026-08-13 16:30 UTC (permalink / raw) To: Masami Hiramatsu (Google); +Cc: linux-trace-kernel 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) <mhiramat@kernel.org> 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. > 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. [ ... ] > @@ -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. [ ... ] > @@ -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 ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 0/1] ring-buffer: Fix sub-buffer order updates and free_page leaks 2026-08-13 16:16 [PATCH 0/1] ring-buffer: Fix sub-buffer order updates and free_page leaks Masami Hiramatsu (Google) 2026-08-13 16:16 ` [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers Masami Hiramatsu (Google) @ 2026-08-13 17:25 ` Vincent Donnefort 1 sibling, 0 replies; 4+ messages in thread From: Vincent Donnefort @ 2026-08-13 17:25 UTC (permalink / raw) To: Masami Hiramatsu (Google) Cc: Steven Rostedt, Mathieu Desnoyers, linux-kernel, linux-trace-kernel, kernel-team On Fri, Aug 14, 2026 at 01:16:03AM +0900, Masami Hiramatsu (Google) wrote: > Hi Vincent, > > Here is my patch to fix the race and free_page problem. > Let's see what Sashiko says for this. > > Thank you, > > --- > base-commit: 3d6d817622b0a9721e3cc404df3469171582be13 > > Masami Hiramatsu (Google) (1): > ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers > > > kernel/trace/ring_buffer.c | 35 +++++++++++++++++++++++++++++------ > 1 file changed, 29 insertions(+), 6 deletions(-) > > -- > Masami Hiramatsu (Google) <mhiramat@kernel.org> Sorry I am not sure to understand what is wrong with that series I made here? https://lore.kernel.org/all/20260813131152.3589632-1-vdonnefort@google.com/ -- Vincent ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-13 17:25 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-13 16:16 [PATCH 0/1] ring-buffer: Fix sub-buffer order updates and free_page leaks Masami Hiramatsu (Google) 2026-08-13 16:16 ` [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers Masami Hiramatsu (Google) 2026-08-13 16:30 ` sashiko-bot 2026-08-13 17:25 ` [PATCH 0/1] ring-buffer: Fix sub-buffer order updates and free_page leaks Vincent Donnefort
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.