* [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 a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox