Re: [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers

Steven Rostedt <[email protected]>
Newsgroups org.kernel.vger.linux-trace-kernel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
On Fri, 14 Aug 2026 01:16:13 +0900
"Masami Hiramatsu (Google)" <[email protected]> wrote:

> From: Masami Hiramatsu (Google) <[email protected]>
> 
> 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) <[email protected]>
> ---


Please rebase on top of ring-buffer/for-next, as I added Vincent's patches to that.

>  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;

This is still needed.

>  	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) {

Swap the order please. It has to check both to continue and if one fails it
will not continue. Checking for cpu_buffer->free_page to be NULL first is
the quicker check. And also the more likely one to fail.

>  		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);
> +	}

Honestly, this should be a separate patch. The first part of this patch is
data races with adding and freeing, but this is about changes to the size.

> +
>  	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);

This is already fixed by Vincent (and I would have asked this to be a
separate patch too if it hadn't).

-- Steve

>  
>  		rb_head_page_activate(cpu_buffer);
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.