All of lore.kernel.org
 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 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.