Linux Trace Kernel
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Haoli Zhang" <26031212468@stu.xidian.edu.cn>
Cc: linux-trace-kernel@vger.kernel.org
Subject: Re: [PATCH v2] tracing: ring_buffer: serialize shrink with NMI readers
Date: Tue, 10 Nov 2026 12:45:56 +0000	[thread overview]
Message-ID: <sashiko-outbox-207040@kernel.org> (raw)
In-Reply-To: <20261110122610.655756-1-26031212468@stu.xidian.edu.cn>

Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [High] tracing: ring_buffer: infinite NMI loop when empty checks
  return false on skip
- [Medium] tracing: ring_buffer: stale event pointer in
  ring_buffer_consume() on skip
- [Low] tracing: ring_buffer: missing Fixes trailer on bug-fix commit
--

commit baa028f41be3b1534303fc3f33ecfebadca9debf
Author: Haoli Zhang <26031212468@stu.xidian.edu.cn>

> tracing: ring_buffer: serialize shrink with NMI readers
>
> The syzbot report shows a use-after-free in rb_is_reader_page(), reached
> from rb_move_tail(), with the freed page released by rb_remove_pages().
> Source inspection suggests that a detached reader page can retain a link
> into the range removed during a shrink. This patch addresses that possible
> stale-link path and coordinates NMI readers with page removal.
>
> When an NMI reader cannot acquire reader_lock, rb_reader_lock() falls back
> to reading the ring buffer without the lock. Gate lockless NMI readers
> while rb_remove_pages() removes pages. Track readers that entered before
> the gate and drain them before changing the page list; new NMI readers that
> fail reader_lock while the gate is closed skip that CPU. The gate and
> reader-count protocol uses ordering barriers and release/acquire accesses.
> Readers that successfully acquire reader_lock remain serialized by it.
>
> The NMI fallback increment of record_disabled is intentionally retained.
> The pre-existing fallback comment says an unlocked NMI read may corrupt the
> buffer and that recording must remain disabled afterward; the existing
> unlock path does not undo that disable. The new nmi_readers counter is
> separate: it tracks only the temporary lifetime of the lockless NMI read
> and is decremented when that read exits.
>
> During shrink, redirect reader_page->list.prev if it points to a page being
> removed. Detect a stale reader_page->list.next as well, and after reconnecting
> the ring redirect it to the surviving successor link, including the
> transferred head flag. Treat skipped empty-buffer queries as non-empty, and
> clear the timestamp output when peek or consume skips the read.
>
> Reported-by: syzbot+37e1793bdb0061ea3a47@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspotmail.com/bug?extid=37e1793bdb0061ea3a47
> Co-developed-by: Jinning Zhang <25031212117@stu.xidian.edu.cn>
> Signed-off-by: Jinning Zhang <25031212117@stu.xidian.edu.cn>
> Co-developed-by: Bin Yu <byu@xidian.edu.cn>
> Signed-off-by: Bin Yu <byu@xidian.edu.cn>
> Signed-off-by: Haoli Zhang <26031212468@stu.xidian.edu.cn>

[Severity: Low]
Since this fixes live page unlinking in rb_remove_pages(), should this
include:

  Fixes: 83f40318dab0 ("ring-buffer: Make removal of ring buffer pages atomic")

> diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
> index e760d2fe386fb..e947ac629e67f 100644
> --- a/kernel/trace/ring_buffer.c
> +++ b/kernel/trace/ring_buffer.c

[ ... ]

> @@ -6416,7 +6475,7 @@ ring_buffer_consume(struct trace_buffer *buffer, int cpu, u64 *ts,
>  	struct ring_buffer_per_cpu *cpu_buffer;
>  	struct ring_buffer_event *event = NULL;
>  	unsigned long flags;
> -	bool dolock;
> +	enum rb_reader_lock_state state;
>  
>   again:
>  	/* might be called in atomic */
> @@ -6427,15 +6486,18 @@ ring_buffer_consume(struct trace_buffer *buffer, int cpu, u64 *ts,
>  
>  	cpu_buffer = buffer->buffers[cpu];
>  	local_irq_save(flags);
> -	dolock = rb_reader_lock(cpu_buffer);
> +	state = rb_reader_lock(cpu_buffer);
>  
> -	event = rb_buffer_peek(cpu_buffer, ts, lost_events);
> +	if (state != RB_READER_SKIP)
> +		event = rb_buffer_peek(cpu_buffer, ts, lost_events);
> +	else if (ts)
> +		*ts = 0;
>  	if (event) {
>  		cpu_buffer->lost_events = 0;
>  		rb_advance_reader(cpu_buffer);
>  	}
>  
> -	rb_reader_unlock(cpu_buffer, dolock);
> +	rb_reader_unlock(cpu_buffer, state);
>  	local_irq_restore(flags);
>  
>   out:

[Severity: Medium]
Can event retain a stale non-NULL pointer when ring_buffer_consume()
retries at again: and then hits RB_READER_SKIP?

event is initialized to NULL only before the again: label.  If the first
pass peeks a RINGBUF_TYPE_PADDING event and jumps back to again: at the
end of ring_buffer_consume(), and the retry sees state == RB_READER_SKIP,
event is not cleared to NULL (unlike in ring_buffer_peek()).  if (event)
then calls rb_advance_reader(cpu_buffer) without reader_lock or an
nmi_readers reference while rb_remove_pages() is unlinking and freeing
pages, and the trailing RINGBUF_TYPE_PADDING check loops back to again:.

[ ... ]

> @@ -6848,7 +6910,7 @@ bool ring_buffer_empty(struct trace_buffer *buffer)
>  {
>  	struct ring_buffer_per_cpu *cpu_buffer;
>  	unsigned long flags;
> -	bool dolock;
> +	enum rb_reader_lock_state state;
>  	bool ret;
>  	int cpu;
>  
> @@ -6856,9 +6918,10 @@ bool ring_buffer_empty(struct trace_buffer *buffer)
>  	for_each_buffer_cpu(buffer, cpu) {
>  		cpu_buffer = buffer->buffers[cpu];
>  		local_irq_save(flags);
> -		dolock = rb_reader_lock(cpu_buffer);
> -		ret = rb_per_cpu_empty(cpu_buffer);
> -		rb_reader_unlock(cpu_buffer, dolock);
> +		state = rb_reader_lock(cpu_buffer);
> +		ret = state == RB_READER_SKIP ? false :
> +			rb_per_cpu_empty(cpu_buffer);
> +		rb_reader_unlock(cpu_buffer, state);
>  		local_irq_restore(flags);
>  
>  		if (!ret)
> @@ -6878,7 +6941,7 @@ bool ring_buffer_empty_cpu(struct trace_buffer *buffer, int cpu)
>  {
>  	struct ring_buffer_per_cpu *cpu_buffer;
>  	unsigned long flags;
> -	bool dolock;
> +	enum rb_reader_lock_state state;
>  	bool ret;
>  
>  	if (!cpumask_test_cpu(cpu, buffer->cpumask))
> @@ -6886,9 +6949,10 @@ bool ring_buffer_empty_cpu(struct trace_buffer *buffer, int cpu)
>  
>  	cpu_buffer = buffer->buffers[cpu];
>  	local_irq_save(flags);
> -	dolock = rb_reader_lock(cpu_buffer);
> -	ret = rb_per_cpu_empty(cpu_buffer);
> -	rb_reader_unlock(cpu_buffer, dolock);
> +	state = rb_reader_lock(cpu_buffer);
> +	ret = state == RB_READER_SKIP ? false :
> +		rb_per_cpu_empty(cpu_buffer);
> +	rb_reader_unlock(cpu_buffer, state);
>  	local_irq_restore(flags);
>  
>  	return ret;

[Severity: High]
Does returning false from ring_buffer_empty() and ring_buffer_empty_cpu()
on RB_READER_SKIP cause ftrace_dump_one() to spin forever in NMI context?

rb_remove_pages()
  atomic_set_release(&cpu_buffer->resize_in_progress, 1)
  raw_spin_lock_irq(&cpu_buffer->reader_lock)
  <NMI>
    ftrace_dump_one()
      while (!trace_empty(&iter))
        ring_buffer_empty_cpu()  --> false (RB_READER_SKIP)
        trace_find_next_entry_inc(&iter)
          ...
            ring_buffer_peek()   --> NULL (RB_READER_SKIP)
        touch_nmi_watchdog()     <-- spins forever

rb_reader_lock() only returns RB_READER_SKIP in NMI context when
reader_lock is held and resize_in_progress is set.  If ftrace_dump_one()
interrupts rb_remove_pages() (or runs on another CPU during a panic after
stopping the resizing CPU), rb_remove_pages() cannot resume to release
reader_lock or clear resize_in_progress until the NMI dump finishes.
Because ring_buffer_empty_cpu() reports the skipped CPU buffer as
non-empty while ring_buffer_peek() returns NULL, trace_empty(&iter) stays
false and the while (!trace_empty(&iter)) loop in ftrace_dump_one() never
terminates.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261110122610.655756-1-26031212468@stu.xidian.edu.cn?part=1

      reply	other threads:[~2026-11-10 12:45 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 12:26 [PATCH v2] tracing: ring_buffer: serialize shrink with NMI readers Haoli Zhang
2026-11-10 12:45 ` sashiko-bot [this message]

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=sashiko-outbox-207040@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=26031212468@stu.xidian.edu.cn \
    --cc=linux-trace-kernel@vger.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox