All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Masami Hiramatsu (Google)" <mhiramat@kernel.org>
Cc: linux-trace-kernel@vger.kernel.org
Subject: Re: [PATCH] ring-buffer: Fix race between ring_buffer_subbuf_order_set() and readers
Date: Thu, 13 Aug 2026 16:30:10 +0000	[thread overview]
Message-ID: <20260813163011.16F161F000E9@smtp.kernel.org> (raw)
In-Reply-To: <178663777320.475864.4716637934003507750.stgit@devnote2>

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

  reply	other threads:[~2026-08-13 16:30 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
2026-08-13 17:25 ` [PATCH 0/1] ring-buffer: Fix sub-buffer order updates and free_page leaks Vincent Donnefort

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260813163011.16F161F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=mhiramat@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.