* [PATCH v8 0/3] ring-buffer: Fixes for subbuf resizing and persistent buffers
@ 2026-08-26 9:45 Vincent Donnefort
2026-08-26 9:45 ` [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers Vincent Donnefort
` (2 more replies)
0 siblings, 3 replies; 15+ messages in thread
From: Vincent Donnefort @ 2026-08-26 9:45 UTC (permalink / raw)
To: rostedt, mhiramat, linux-trace-kernel
Cc: mathieu.desnoyers, kernel-team, linux-kernel, Vincent Donnefort
This series addresses multiple issues discovered with the dynamic ring
buffer resizing.
I have managed to reproduce a ring_buffer_read_page() race with
$ while true; do for i in 8 16 32; do echo $i > /sys/kernel/tracing/buffer_subbuf_size_kb; sleep 0.1; done; done
Changelog:
v8:
- ring_buffer_read_page() handles gracefully read_page/reader_page
order mismatch (Steven)
v7 (https://lore.kernel.org/linux-trace-kernel/20260817101533.1558223-1-vdonnefort@google.com/):
- Match the "static" rb limit with bpage::id bitwidth
- Cover another 32-bit truncation in rb_range_buffer (Sashiko)
- Fix uninitialized spare_size (Sashiko)
v6 (https://lore.kernel.org/all/20260814154823.755406-1-vdonnefort@google.com/):
- New prototype for ring_buffer_alloc_read_page() (Steven)
- ring_buffer_read_page() to return -EAGAIN (Steven)
- Keep nr_pages "unsigned long" (Steven)
- Repase on ring-buffer/next (Drop most of the patches)
v5 (https://lore.kernel.org/all/20260813131152.3589632-1-vdonnefort@google.com/):
- Reset info->spare_read only when data is in the ring-buffer (Sashiko)
- Use `unsigned long` for subbuf_size declaration to avoid 32-bit
truncation. (Sashiko)
- Make cpu_buffer::free_page a buffer_read_data_page
- Update kerneldoc for ring_buffer_alloc_read_page()
v4 (https://lore.kernel.org/all/20260812153311.2328812-1-vdonnefort@google.com/):
- Add rb_subbuf_start() helper (Steven)
- kerneldoc additions
- Fix races in trace_pipe_raw readers
- Use rb_subbuf_capacity() in ring_buffer_subbuf_order_set()
- use rb_page_capacity() in ring_buffer_map_get_reader (Sashiko)
- Fix 32-bit overflow in ring_buffer_subbuf_order_set() (Sashiko)
- Hold cpu_buffer::lock when modifying cpu_buffer->free_page in
ring_buffer_subbuf_order_set (Sashiko)
v3 (https://lore.kernel.org/all/20260810125633.3344684-1-vdonnefort@google.com/):
- Drop first 3 patches (Rebased on 7.2-rc7)
- Add a patch to align "nr_pages" to unsigned int
- Add a patch to remove useless trace_buffer::cpus
- Add unsigned long cast for rb_subbuf_size()
- subbuf_order fix for rb_free_cpu_buffer() (Sashiko)
- Use __always_inline just like the other accessors for the hot-path.
v2 (https://lore.kernel.org/all/20260806211306.3704194-1-vdonnefort@google.com/):
- Prevent resizing of the persistent ring buffer
- Add missing bpage::order init
- Rework subbuf_size/subbuf_order (Sashiko)
- Remove ring_buffer_per_cpu::mapped
- Dynamically calculate trace_buffer::max_data_size
v1 (https://lore.kernel.org/all/20260805153225.2096152-1-vdonnefort@google.com/)
Vincent Donnefort (3):
tracing: Fix subbuf resize races with trace_pipe_raw readers
ring-buffer: Cap static ring buffer nr_pages
ring-buffer: Prevent truncation of nr_pages / nr_subbufs
include/linux/ring_buffer.h | 5 +-
kernel/trace/ring_buffer.c | 198 ++++++++++++++++++---------
kernel/trace/ring_buffer_benchmark.c | 6 +-
kernel/trace/trace.c | 94 ++++++-------
kernel/trace/trace.h | 9 +-
5 files changed, 185 insertions(+), 127 deletions(-)
base-commit: 66498c75b4f8017f62d720d9b59675bdf3abce91
--
2.55.0.860.g4b6b3295ed-goog
^ permalink raw reply [flat|nested] 15+ messages in thread* [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-26 9:45 [PATCH v8 0/3] ring-buffer: Fixes for subbuf resizing and persistent buffers Vincent Donnefort @ 2026-08-26 9:45 ` Vincent Donnefort 2026-08-26 9:59 ` sashiko-bot 2026-08-26 9:45 ` [PATCH v8 2/3] ring-buffer: Cap static ring buffer nr_pages Vincent Donnefort 2026-08-26 9:45 ` [PATCH v8 3/3] ring-buffer: Prevent truncation of nr_pages / nr_subbufs Vincent Donnefort 2 siblings, 1 reply; 15+ messages in thread From: Vincent Donnefort @ 2026-08-26 9:45 UTC (permalink / raw) To: rostedt, mhiramat, linux-trace-kernel Cc: mathieu.desnoyers, kernel-team, linux-kernel, Vincent Donnefort Concurrent subbuffer resizes may crash trace_pipe_raw readers or leak uninitialized memory to userspace due to stale size values. Modify ring_buffer_alloc_read_page() to handle the resizing of an existing buffer_data_read_page if necessary and add a new ring_buffer_read_page_size(). This new function enables ring-buffer buffer_data_read_page users to not call the racy ring_buffer_subbuf_size_get(). This makes the spare_size member of ftrace_buffer_info redundant. Finally, handle buffer_data_read_page/reader_page order discrepancy in ring_buffer_read_page(). On a mismatch simply copy manually the data to the buffer_data_read_page. Fixes: bce761d75745 ("ring-buffer: Read and write to ring buffers with custom sub buffer size") Signed-off-by: Vincent Donnefort <vdonnefort@google.com> diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h index 0670742b2d60..afc7daa6ee7d 100644 --- a/include/linux/ring_buffer.h +++ b/include/linux/ring_buffer.h @@ -218,14 +218,15 @@ bool ring_buffer_time_stamp_abs(struct trace_buffer *buffer); size_t ring_buffer_nr_dirty_pages(struct trace_buffer *buffer, int cpu); struct buffer_data_read_page; -struct buffer_data_read_page * -ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu); +int ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu, + struct buffer_data_read_page **rpage); void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, struct buffer_data_read_page *page); int ring_buffer_read_page(struct trace_buffer *buffer, struct buffer_data_read_page *data_page, size_t len, int cpu, int full); void *ring_buffer_read_page_data(struct buffer_data_read_page *page); +unsigned int ring_buffer_read_page_size(struct buffer_data_read_page *rpage); struct trace_seq; diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 3c3ed639923d..b8e6bd309707 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -330,6 +330,11 @@ struct buffer_data_read_page { struct buffer_data_page *data; /* actual data, stored in this page */ }; +static __always_inline unsigned int rb_read_page_capacity(struct buffer_data_read_page *rpage) +{ + return (PAGE_SIZE << rpage->order) - BUF_PAGE_HDR_SIZE; +} + /* * Note, the buffer_page list must be first. The buffer pages * are allocated in cache lines, which means that each buffer @@ -6990,56 +6995,78 @@ EXPORT_SYMBOL_GPL(ring_buffer_swap_cpu); * ring_buffer_alloc_read_page - allocate a page to read from buffer * @buffer: the buffer to allocate for. * @cpu: the cpu buffer to allocate. + * @rpage: pointer to pass in an already allocated page (can be NULL) + * and returns the allocated page. * - * This function is used in conjunction with ring_buffer_read_page. + * This function is used in conjunction with ring_buffer_read_page(). * When reading a full page from the ring buffer, these functions * can be used to speed up the process. The calling function should * allocate a few pages first with this function. Then when it * needs to get pages from the ring buffer, it passes the result - * of this function into ring_buffer_read_page, which will swap + * of this function into ring_buffer_read_page(), which will swap * the page that was allocated, with the read page of the buffer. * + * If @rpage is provided, and it has a different order than the current + * subbuffer order, its payload will be freed and re-allocated. If it + * already matches the order, it is simply returned. + * * Returns: - * The page allocated, or ERR_PTR + * 0 on success, < 0 on error */ -struct buffer_data_read_page * -ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu) +int ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu, + struct buffer_data_read_page **rpage) { struct ring_buffer_per_cpu *cpu_buffer; - struct buffer_data_read_page *bpage = NULL; unsigned long flags; + unsigned int order; if (!cpumask_test_cpu(cpu, buffer->cpumask)) - return ERR_PTR(-ENODEV); + return -ENODEV; - bpage = kzalloc_obj(*bpage); - if (!bpage) - return ERR_PTR(-ENOMEM); + if (!rpage) + return -EINVAL; - bpage->order = buffer->subbuf_order; + order = buffer->subbuf_order; + + if (*rpage) { + if ((*rpage)->order == order) + return 0; + + /* We can reuse rpage, but we discard the payload */ + free_pages((unsigned long)(*rpage)->data, (*rpage)->order); + (*rpage)->data = NULL; + } else { + *rpage = kzalloc_obj(**rpage); + if (!*rpage) + return -ENOMEM; + } + + (*rpage)->order = order; cpu_buffer = buffer->buffers[cpu]; + local_irq_save(flags); arch_spin_lock(&cpu_buffer->lock); if (cpu_buffer->free_page.data) { - *bpage = cpu_buffer->free_page; + **rpage = cpu_buffer->free_page; cpu_buffer->free_page.data = NULL; } arch_spin_unlock(&cpu_buffer->lock); local_irq_restore(flags); - if (bpage->data) { - rb_init_data_page(bpage->data); + if ((*rpage)->data) { + rb_init_data_page((*rpage)->data); } else { - bpage->data = alloc_cpu_data(cpu, bpage->order); - if (!bpage->data) { - kfree(bpage); - return ERR_PTR(-ENOMEM); + (*rpage)->data = alloc_cpu_data(cpu, (*rpage)->order); + if (!(*rpage)->data) { + kfree(*rpage); + *rpage = NULL; + return -ENOMEM; } } - return bpage; + return 0; } EXPORT_SYMBOL_GPL(ring_buffer_alloc_read_page); @@ -7047,21 +7074,30 @@ EXPORT_SYMBOL_GPL(ring_buffer_alloc_read_page); * ring_buffer_free_read_page - free an allocated read page * @buffer: the buffer the page was allocate for * @cpu: the cpu buffer the page came from - * @data_page: the page to free + * @rpage: the buffer_dat_read_page to free * * Free a page allocated from ring_buffer_alloc_read_page. */ void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, - struct buffer_data_read_page *data_page) + struct buffer_data_read_page *rpage) { struct ring_buffer_per_cpu *cpu_buffer; - struct buffer_data_page *dpage = data_page->data; - struct page *page = virt_to_page(dpage); + struct buffer_data_page *dpage; unsigned long flags; + struct page *page; if (!buffer || !buffer->buffers || !buffer->buffers[cpu]) return; + if (!rpage) + return; + + dpage = rpage->data; + if (!dpage) + goto out; + + page = virt_to_page(dpage); + cpu_buffer = buffer->buffers[cpu]; /* @@ -7069,14 +7105,14 @@ 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 || rpage->order != buffer->subbuf_order) goto out; local_irq_save(flags); arch_spin_lock(&cpu_buffer->lock); if (!cpu_buffer->free_page.data) { - cpu_buffer->free_page = *data_page; + cpu_buffer->free_page = *rpage; dpage = NULL; } @@ -7084,8 +7120,8 @@ void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, local_irq_restore(flags); out: - free_pages((unsigned long)dpage, data_page->order); - kfree(data_page); + free_pages((unsigned long)dpage, rpage->order); + kfree(rpage); } EXPORT_SYMBOL_GPL(ring_buffer_free_read_page); @@ -7156,10 +7192,9 @@ int ring_buffer_read_page(struct trace_buffer *buffer, if (!dpage) return -1; - guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock); + len = min_t(size_t, len, rb_read_page_capacity(data_page)); - if (data_page->order != cpu_buffer->reader_page->order) - return -1; + guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock); reader = rb_get_reader_page(cpu_buffer); if (!reader) @@ -7183,7 +7218,8 @@ int ring_buffer_read_page(struct trace_buffer *buffer, */ if (read || (len < (size - read)) || cpu_buffer->reader_page == cpu_buffer->commit_page || - rb_is_static(cpu_buffer)) { + rb_is_static(cpu_buffer) || + data_page->order != reader->order) { struct buffer_data_page *rpage = cpu_buffer->reader_page->page; unsigned int rpos = read; unsigned int pos = 0; @@ -7284,7 +7320,7 @@ int ring_buffer_read_page(struct trace_buffer *buffer, * missed events, then record it there. */ if (missed_events > 0 && - rb_page_capacity(reader) - size >= sizeof(missed_events)) { + rb_read_page_capacity(data_page) - size >= sizeof(missed_events)) { memcpy(&dpage->data[size], &missed_events, sizeof(missed_events)); local_add(RB_MISSED_STORED, &dpage->commit); @@ -7304,8 +7340,8 @@ int ring_buffer_read_page(struct trace_buffer *buffer, /* * This page may be off to user land. Zero it out here. */ - if (size < rb_page_capacity(reader)) - memset(&dpage->data[size], 0, rb_page_capacity(reader) - size); + if (size < rb_read_page_capacity(data_page)) + memset(&dpage->data[size], 0, rb_read_page_capacity(data_page) - size); return read; } @@ -7323,6 +7359,18 @@ void *ring_buffer_read_page_data(struct buffer_data_read_page *page) } EXPORT_SYMBOL_GPL(ring_buffer_read_page_data); +/** + * ring_buffer_read_page_size - get size of the read page. + * @page: the page to get the size from + * + * Returns size of the page in bytes. + */ +unsigned int ring_buffer_read_page_size(struct buffer_data_read_page *rpage) +{ + return PAGE_SIZE << rpage->order; +} +EXPORT_SYMBOL_GPL(ring_buffer_read_page_size); + /** * ring_buffer_subbuf_size_get - get size of the sub buffer. * @buffer: the buffer to get the sub buffer size from diff --git a/kernel/trace/ring_buffer_benchmark.c b/kernel/trace/ring_buffer_benchmark.c index 593e3b59e42e..c3d34c0e64e2 100644 --- a/kernel/trace/ring_buffer_benchmark.c +++ b/kernel/trace/ring_buffer_benchmark.c @@ -104,7 +104,7 @@ static enum event_status read_event(int cpu) static enum event_status read_page(int cpu) { - struct buffer_data_read_page *bpage; + struct buffer_data_read_page *bpage = NULL; struct ring_buffer_event *event; struct rb_page *rpage; unsigned long commit; @@ -114,8 +114,8 @@ static enum event_status read_page(int cpu) int inc; int i; - bpage = ring_buffer_alloc_read_page(buffer, cpu); - if (IS_ERR(bpage)) + ret = ring_buffer_alloc_read_page(buffer, cpu, &bpage); + if (ret < 0) return EVENT_DROPPED; page_size = ring_buffer_subbuf_size_get(buffer); diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 3e0907aef172..a9cf76a0a3d8 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -7082,8 +7082,8 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf, { struct ftrace_buffer_info *info = filp->private_data; struct trace_iterator *iter = &info->iter; + unsigned int spare_size; void *trace_data; - int page_size; ssize_t ret = 0; ssize_t size; @@ -7093,36 +7093,24 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf, if (iter->snapshot && tracer_uses_snapshot(iter->tr->current_trace)) return -EBUSY; - page_size = ring_buffer_subbuf_size_get(iter->array_buffer->buffer); - - /* Make sure the spare matches the current sub buffer size */ +again: if (info->spare) { - if (page_size != info->spare_size) { - ring_buffer_free_read_page(iter->array_buffer->buffer, - info->spare_cpu, info->spare); - info->spare = NULL; - } + spare_size = ring_buffer_read_page_size(info->spare); + /* Do we have previous read data to read? */ + if (info->read < spare_size) + goto read; } - if (!info->spare) { - info->spare = ring_buffer_alloc_read_page(iter->array_buffer->buffer, - iter->cpu_file); - if (IS_ERR(info->spare)) { - ret = PTR_ERR(info->spare); - info->spare = NULL; - } else { - info->spare_cpu = iter->cpu_file; - info->spare_size = page_size; - } - } - if (!info->spare) + /* Make sure the read page order is aligned with the current subbuf order */ + ret = ring_buffer_alloc_read_page(iter->array_buffer->buffer, iter->cpu_file, + &info->spare); + if (ret) return ret; - /* Do we have previous read data to read? */ - if (info->read < page_size) - goto read; + spare_size = ring_buffer_read_page_size(info->spare); + info->read = spare_size; + info->spare_cpu = iter->cpu_file; - again: trace_access_lock(iter->cpu_file); ret = ring_buffer_read_page(iter->array_buffer->buffer, info->spare, @@ -7148,8 +7136,9 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf, } info->read = 0; + read: - size = page_size - info->read; + size = spare_size - info->read; if (size > count) size = count; trace_data = ring_buffer_read_page_data(info->spare); @@ -7199,17 +7188,17 @@ int tracing_buffers_release(struct inode *inode, struct file *file) } struct buffer_ref { - struct trace_buffer *buffer; - void *page; - int cpu; - refcount_t refcount; + struct trace_buffer *buffer; + struct buffer_data_read_page *rpage; + int cpu; + refcount_t refcount; }; static void buffer_ref_release(struct buffer_ref *ref) { if (!refcount_dec_and_test(&ref->refcount)) return; - ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->page); + ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->rpage); kfree(ref); } @@ -7270,23 +7259,12 @@ ssize_t tracing_buffers_splice_read(struct file *file, loff_t *ppos, }; struct buffer_ref *ref; bool woken = false; - int page_size; int entries, i; ssize_t ret = 0; if (iter->snapshot && tracer_uses_snapshot(iter->tr->current_trace)) return -EBUSY; - page_size = ring_buffer_subbuf_size_get(iter->array_buffer->buffer); - if (*ppos & (page_size - 1)) - return -EINVAL; - - if (len & (page_size - 1)) { - if (len < page_size) - return -EINVAL; - len &= (~(page_size - 1)); - } - if (splice_grow_spd(pipe, &spd)) return -ENOMEM; @@ -7294,7 +7272,8 @@ ssize_t tracing_buffers_splice_read(struct file *file, loff_t *ppos, trace_access_lock(iter->cpu_file); entries = ring_buffer_entries_cpu(iter->array_buffer->buffer, iter->cpu_file); - for (i = 0; i < spd.nr_pages_max && len && entries; i++, len -= page_size) { + for (i = 0; i < spd.nr_pages_max && len && entries; i++) { + unsigned int page_size; struct page *page; int r; @@ -7306,25 +7285,35 @@ ssize_t tracing_buffers_splice_read(struct file *file, loff_t *ppos, refcount_set(&ref->refcount, 1); ref->buffer = iter->array_buffer->buffer; - ref->page = ring_buffer_alloc_read_page(ref->buffer, iter->cpu_file); - if (IS_ERR(ref->page)) { - ret = PTR_ERR(ref->page); - ref->page = NULL; + + ret = ring_buffer_alloc_read_page(ref->buffer, iter->cpu_file, &ref->rpage); + if (ret) { kfree(ref); break; } ref->cpu = iter->cpu_file; - r = ring_buffer_read_page(ref->buffer, ref->page, - len, iter->cpu_file, 1); + page_size = ring_buffer_read_page_size(ref->rpage); + + r = -EINVAL; + if (IS_ALIGNED(*ppos, page_size) && len >= page_size) { + r = ring_buffer_read_page(ref->buffer, ref->rpage, len, iter->cpu_file, 1); + } else if (!i) { + /* + * If the first iteration fails this is an invalid userspace input. + * Otherwise, this is because the subbuf order has been modified. Do not + * report an error and finish the read. + */ + ret = -EINVAL; + } + if (r < 0) { - ring_buffer_free_read_page(ref->buffer, ref->cpu, - ref->page); + ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->rpage); kfree(ref); break; } - page = virt_to_page(ring_buffer_read_page_data(ref->page)); + page = virt_to_page(ring_buffer_read_page_data(ref->rpage)); spd.pages[i] = page; spd.partial[i].len = page_size; @@ -7332,6 +7321,7 @@ ssize_t tracing_buffers_splice_read(struct file *file, loff_t *ppos, spd.partial[i].private = (unsigned long)ref; spd.nr_pages++; *ppos += page_size; + len -= page_size; entries = ring_buffer_entries_cpu(iter->array_buffer->buffer, iter->cpu_file); } diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 74a7a50d1e78..203d098ee14e 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -745,11 +745,10 @@ static inline int tracing_get_cpu(struct inode *inode) void tracing_reset_cpu(struct array_buffer *buf, int cpu); struct ftrace_buffer_info { - struct trace_iterator iter; - void *spare; - unsigned int spare_cpu; - unsigned int spare_size; - unsigned int read; + struct trace_iterator iter; + struct buffer_data_read_page *spare; + unsigned int spare_cpu; + unsigned int read; }; /** -- 2.55.0.860.g4b6b3295ed-goog ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-26 9:45 ` [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers Vincent Donnefort @ 2026-08-26 9:59 ` sashiko-bot 2026-08-26 14:37 ` Steven Rostedt 0 siblings, 1 reply; 15+ messages in thread From: sashiko-bot @ 2026-08-26 9:59 UTC (permalink / raw) To: Vincent Donnefort; +Cc: linux-trace-kernel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] ring_buffer_read_page() incorrectly returns -1 for splice_read requests on subbuf order mismatch -- commit c4d6ee6f55f23c8ec6da6f812ec3b722b2b7f6b6 Author: Vincent Donnefort <vdonnefort@google.com> tracing: Fix subbuf resize races with trace_pipe_raw readers This commit modifies ring_buffer_alloc_read_page() and ring_buffer_read_page() to handle dynamic resizing of trace ring buffers, allowing readers to avoid stale size values. It introduces a manual data copy fallback in ring_buffer_read_page() when the subbuffer orders do not match. > diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c > --- a/kernel/trace/ring_buffer.c > +++ b/kernel/trace/ring_buffer.c [ ... ] > @@ -7183,7 +7218,8 @@ int ring_buffer_read_page(struct trace_buffer *buffer, > */ > if (read || (len < (size - read)) || > cpu_buffer->reader_page == cpu_buffer->commit_page || > - rb_is_static(cpu_buffer)) { > + rb_is_static(cpu_buffer) || > + data_page->order != reader->order) { > struct buffer_data_page *rpage = cpu_buffer->reader_page->page; > unsigned int rpos = read; > unsigned int pos = 0; [Severity: High] Does this new condition break splice reads when the buffer order changes? When tracing_buffers_splice_read() allocates a read page with the new order and calls ring_buffer_read_page() with full=1, it will now enter this manual copy block because the subbuffer orders no longer match. However, just a few lines down inside this block in ring_buffer_read_page(), there is the following check: if (full && (!read || (len < (size - read)) || cpu_buffer->reader_page == cpu_buffer->commit_page)) return -1; Since the reader page typically hasn't been partially read yet (read == 0), and full == 1 from splice, this check evaluates to true and immediately returns -1 instead of copying the data. Because tracing_buffers_splice_read() loops and ultimately returns -EAGAIN on failure, will this cause an infinite loop of -EAGAIN for userspace tools trying to read across buffer resizes? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260826094528.3738023-1-vdonnefort@google.com?part=1 ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-26 9:59 ` sashiko-bot @ 2026-08-26 14:37 ` Steven Rostedt 2026-08-26 16:24 ` Vincent Donnefort 0 siblings, 1 reply; 15+ messages in thread From: Steven Rostedt @ 2026-08-26 14:37 UTC (permalink / raw) To: sashiko-bot; +Cc: sashiko-reviews, Vincent Donnefort, linux-trace-kernel On Wed, 26 Aug 2026 09:59:46 +0000 sashiko-bot@kernel.org wrote: > > diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c > > --- a/kernel/trace/ring_buffer.c > > +++ b/kernel/trace/ring_buffer.c > [ ... ] > > @@ -7183,7 +7218,8 @@ int ring_buffer_read_page(struct trace_buffer *buffer, > > */ > > if (read || (len < (size - read)) || > > cpu_buffer->reader_page == cpu_buffer->commit_page || > > - rb_is_static(cpu_buffer)) { > > + rb_is_static(cpu_buffer) || > > + data_page->order != reader->order) { > > struct buffer_data_page *rpage = cpu_buffer->reader_page->page; > > unsigned int rpos = read; > > unsigned int pos = 0; > > [Severity: High] > Does this new condition break splice reads when the buffer order changes? > > When tracing_buffers_splice_read() allocates a read page with the new order > and calls ring_buffer_read_page() with full=1, it will now enter this manual > copy block because the subbuffer orders no longer match. > > However, just a few lines down inside this block in ring_buffer_read_page(), > there is the following check: > > if (full && > (!read || (len < (size - read)) || > cpu_buffer->reader_page == cpu_buffer->commit_page)) > return -1; > > Since the reader page typically hasn't been partially read yet (read == 0), > and full == 1 from splice, this check evaluates to true and immediately > returns -1 instead of copying the data. > > Because tracing_buffers_splice_read() loops and ultimately returns -EAGAIN > on failure, will this cause an infinite loop of -EAGAIN for userspace tools > trying to read across buffer resizes? > This is a legitimate concern. But with an easy solution. Perhaps just add: if (full && (!read || (len < (size - read)) || - cpu_buffer->reader_page == cpu_buffer->commit_page)) + cpu_buffer->reader_page == cpu_buffer->commit_page) && + data_page->order == buffer->subbuf_order) return -1; If the user is changing the buffer size at the same time as reading raw pages, they get what they deserve! I just don't want to let the kernel go into an infinite loop. -- Steve ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-26 14:37 ` Steven Rostedt @ 2026-08-26 16:24 ` Vincent Donnefort 2026-08-26 18:31 ` Steven Rostedt 0 siblings, 1 reply; 15+ messages in thread From: Vincent Donnefort @ 2026-08-26 16:24 UTC (permalink / raw) To: Steven Rostedt; +Cc: sashiko-bot, sashiko-reviews, linux-trace-kernel On Wed, Aug 26, 2026 at 10:37:36AM -0400, Steven Rostedt wrote: > On Wed, 26 Aug 2026 09:59:46 +0000 > sashiko-bot@kernel.org wrote: > > > > diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c > > > --- a/kernel/trace/ring_buffer.c > > > +++ b/kernel/trace/ring_buffer.c > > [ ... ] > > > @@ -7183,7 +7218,8 @@ int ring_buffer_read_page(struct trace_buffer *buffer, > > > */ > > > if (read || (len < (size - read)) || > > > cpu_buffer->reader_page == cpu_buffer->commit_page || > > > - rb_is_static(cpu_buffer)) { > > > + rb_is_static(cpu_buffer) || > > > + data_page->order != reader->order) { > > > struct buffer_data_page *rpage = cpu_buffer->reader_page->page; > > > unsigned int rpos = read; > > > unsigned int pos = 0; > > > > [Severity: High] > > Does this new condition break splice reads when the buffer order changes? > > > > When tracing_buffers_splice_read() allocates a read page with the new order > > and calls ring_buffer_read_page() with full=1, it will now enter this manual > > copy block because the subbuffer orders no longer match. > > > > However, just a few lines down inside this block in ring_buffer_read_page(), > > there is the following check: > > > > if (full && > > (!read || (len < (size - read)) || > > cpu_buffer->reader_page == cpu_buffer->commit_page)) > > return -1; > > > > Since the reader page typically hasn't been partially read yet (read == 0), > > and full == 1 from splice, this check evaluates to true and immediately > > returns -1 instead of copying the data. > > > > Because tracing_buffers_splice_read() loops and ultimately returns -EAGAIN > > on failure, will this cause an infinite loop of -EAGAIN for userspace tools > > trying to read across buffer resizes? > > > > > This is a legitimate concern. But with an easy solution. Perhaps just add: > > if (full && > (!read || (len < (size - read)) || > - cpu_buffer->reader_page == cpu_buffer->commit_page)) > + cpu_buffer->reader_page == cpu_buffer->commit_page) && > + data_page->order == buffer->subbuf_order) > return -1; > > If the user is changing the buffer size at the same time as reading raw > pages, they get what they deserve! I just don't want to let the kernel go > into an infinite loop. > > -- Steve > Sounds good. Do you prefer to get a v9 or you fold this into the existing commit? -- Vincent ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-26 16:24 ` Vincent Donnefort @ 2026-08-26 18:31 ` Steven Rostedt 2026-08-27 6:31 ` Vincent Donnefort 0 siblings, 1 reply; 15+ messages in thread From: Steven Rostedt @ 2026-08-26 18:31 UTC (permalink / raw) To: Vincent Donnefort; +Cc: sashiko-bot, sashiko-reviews, linux-trace-kernel On Wed, 26 Aug 2026 17:24:35 +0100 Vincent Donnefort <vdonnefort@google.com> wrote: > > This is a legitimate concern. But with an easy solution. Perhaps just add: > > > > if (full && > > (!read || (len < (size - read)) || > > - cpu_buffer->reader_page == cpu_buffer->commit_page)) > > + cpu_buffer->reader_page == cpu_buffer->commit_page) && > > + data_page->order == buffer->subbuf_order) > > return -1; > > > > If the user is changing the buffer size at the same time as reading raw > > pages, they get what they deserve! I just don't want to let the kernel go > > into an infinite loop. > > > > -- Steve > > > > Sounds good. Do you prefer to get a v9 or you fold this into the existing commit? > Please send a v9. It makes the patchwork flow much better. If I modify a patch, it makes patchwork not be able to automate the status updates as it moves around git trees. Thanks, -- Steve ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-26 18:31 ` Steven Rostedt @ 2026-08-27 6:31 ` Vincent Donnefort 2026-08-27 13:15 ` Steven Rostedt 0 siblings, 1 reply; 15+ messages in thread From: Vincent Donnefort @ 2026-08-27 6:31 UTC (permalink / raw) To: Steven Rostedt; +Cc: sashiko-bot, sashiko-reviews, linux-trace-kernel On Wed, Aug 26, 2026 at 02:31:23PM -0400, Steven Rostedt wrote: > On Wed, 26 Aug 2026 17:24:35 +0100 > Vincent Donnefort <vdonnefort@google.com> wrote: > > > > This is a legitimate concern. But with an easy solution. Perhaps just add: > > > > > > if (full && > > > (!read || (len < (size - read)) || > > > - cpu_buffer->reader_page == cpu_buffer->commit_page)) > > > + cpu_buffer->reader_page == cpu_buffer->commit_page) && > > > + data_page->order == buffer->subbuf_order) > > > return -1; > > > > > > If the user is changing the buffer size at the same time as reading raw > > > pages, they get what they deserve! I just don't want to let the kernel go > > > into an infinite loop. > > > > > > -- Steve > > > > > > > Sounds good. Do you prefer to get a v9 or you fold this into the existing commit? > > > > Please send a v9. It makes the patchwork flow much better. If I modify a > patch, it makes patchwork not be able to automate the status updates as it > moves around git trees. > > Thanks, > > -- Steve > Looking at the function again, shouldn't we add !rb_is_static(cpu_buffer) as well to the full page test? -- Vincent ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-27 6:31 ` Vincent Donnefort @ 2026-08-27 13:15 ` Steven Rostedt 2026-08-27 16:21 ` Vincent Donnefort 0 siblings, 1 reply; 15+ messages in thread From: Steven Rostedt @ 2026-08-27 13:15 UTC (permalink / raw) To: Vincent Donnefort; +Cc: sashiko-bot, sashiko-reviews, linux-trace-kernel On Thu, 27 Aug 2026 07:31:23 +0100 Vincent Donnefort <vdonnefort@google.com> wrote: > Looking at the function again, shouldn't we add !rb_is_static(cpu_buffer) as > well to the full page test? Why? -- Steve ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-27 13:15 ` Steven Rostedt @ 2026-08-27 16:21 ` Vincent Donnefort 2026-08-27 19:33 ` Steven Rostedt 0 siblings, 1 reply; 15+ messages in thread From: Vincent Donnefort @ 2026-08-27 16:21 UTC (permalink / raw) To: Steven Rostedt; +Cc: sashiko-bot, sashiko-reviews, linux-trace-kernel On Thu, Aug 27, 2026 at 09:15:41AM -0400, Steven Rostedt wrote: > On Thu, 27 Aug 2026 07:31:23 +0100 > Vincent Donnefort <vdonnefort@google.com> wrote: > > > Looking at the function again, shouldn't we add !rb_is_static(cpu_buffer) as > > well to the full page test? > > Why? > > -- Steve So that the splice can make progress when the ring-buffer is presistent or user-mapped. Or do we say that's expected behaviour? -- Vincent ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-27 16:21 ` Vincent Donnefort @ 2026-08-27 19:33 ` Steven Rostedt 2026-08-28 8:24 ` Vincent Donnefort 0 siblings, 1 reply; 15+ messages in thread From: Steven Rostedt @ 2026-08-27 19:33 UTC (permalink / raw) To: Vincent Donnefort; +Cc: sashiko-bot, sashiko-reviews, linux-trace-kernel On Thu, 27 Aug 2026 17:21:39 +0100 Vincent Donnefort <vdonnefort@google.com> wrote: > On Thu, Aug 27, 2026 at 09:15:41AM -0400, Steven Rostedt wrote: > > On Thu, 27 Aug 2026 07:31:23 +0100 > > Vincent Donnefort <vdonnefort@google.com> wrote: > > > > > Looking at the function again, shouldn't we add !rb_is_static(cpu_buffer) as > > > well to the full page test? > > > > Why? > > > > -- Steve > > So that the splice can make progress when the ring-buffer is presistent or > user-mapped. Or do we say that's expected behaviour? > Ah yeah, I see. Hmm, looking at the code to see how this would work, I think I found another bug. Let me look deeper into it. -- Steve ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-27 19:33 ` Steven Rostedt @ 2026-08-28 8:24 ` Vincent Donnefort 2026-08-28 8:36 ` Steven Rostedt 0 siblings, 1 reply; 15+ messages in thread From: Vincent Donnefort @ 2026-08-28 8:24 UTC (permalink / raw) To: Steven Rostedt; +Cc: sashiko-bot, sashiko-reviews, linux-trace-kernel On Thu, Aug 27, 2026 at 03:33:14PM -0400, Steven Rostedt wrote: > On Thu, 27 Aug 2026 17:21:39 +0100 > Vincent Donnefort <vdonnefort@google.com> wrote: > > > On Thu, Aug 27, 2026 at 09:15:41AM -0400, Steven Rostedt wrote: > > > On Thu, 27 Aug 2026 07:31:23 +0100 > > > Vincent Donnefort <vdonnefort@google.com> wrote: > > > > > > > Looking at the function again, shouldn't we add !rb_is_static(cpu_buffer) as > > > > well to the full page test? > > > > > > Why? > > > > > > -- Steve > > > > So that the splice can make progress when the ring-buffer is presistent or > > user-mapped. Or do we say that's expected behaviour? > > > > Ah yeah, I see. Hmm, looking at the code to see how this would work, I > think I found another bug. Let me look deeper into it. > > -- Steve A bug if we allow rb_is_static()? If not, shall I add it to my series? -- Vincent ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-28 8:24 ` Vincent Donnefort @ 2026-08-28 8:36 ` Steven Rostedt 0 siblings, 0 replies; 15+ messages in thread From: Steven Rostedt @ 2026-08-28 8:36 UTC (permalink / raw) To: Vincent Donnefort; +Cc: sashiko-bot, sashiko-reviews, linux-trace-kernel On August 28, 2026 4:24:36 AM EDT, Vincent Donnefort <vdonnefort@google.com> wrote: >On Thu, Aug 27, 2026 at 03:33:14PM -0400, Steven Rostedt wrote: >> On Thu, 27 Aug 2026 17:21:39 +0100 >> Vincent Donnefort <vdonnefort@google.com> wrote: >> >> > On Thu, Aug 27, 2026 at 09:15:41AM -0400, Steven Rostedt wrote: >> > > On Thu, 27 Aug 2026 07:31:23 +0100 >> > > Vincent Donnefort <vdonnefort@google.com> wrote: >> > > >> > > > Looking at the function again, shouldn't we add !rb_is_static(cpu_buffer) as >> > > > well to the full page test? >> > > >> > > Why? >> > > >> > > -- Steve >> > >> > So that the splice can make progress when the ring-buffer is presistent or >> > user-mapped. Or do we say that's expected behaviour? >> > >> >> Ah yeah, I see. Hmm, looking at the code to see how this would work, I >> think I found another bug. Let me look deeper into it. >> >> -- Steve > >A bug if we allow rb_is_static()? No, something else. > >If not, shall I add it to my series? Yeah, go ahead and update it. Thanks, -- Steve ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v8 2/3] ring-buffer: Cap static ring buffer nr_pages 2026-08-26 9:45 [PATCH v8 0/3] ring-buffer: Fixes for subbuf resizing and persistent buffers Vincent Donnefort 2026-08-26 9:45 ` [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers Vincent Donnefort @ 2026-08-26 9:45 ` Vincent Donnefort 2026-08-26 9:45 ` [PATCH v8 3/3] ring-buffer: Prevent truncation of nr_pages / nr_subbufs Vincent Donnefort 2 siblings, 0 replies; 15+ messages in thread From: Vincent Donnefort @ 2026-08-26 9:45 UTC (permalink / raw) To: rostedt, mhiramat, linux-trace-kernel Cc: mathieu.desnoyers, kernel-team, linux-kernel, Vincent Donnefort Static ring buffers (i.e. persistent, user-mapped and remote) rely on the bpage::id field. The number of pages for those ring buffers must fit into that variable. Enforce this limit on ring buffer creation or user-mapping. While at it, make buffer_page::id 31 bits. This does not change the struct buffer_page size. Fixes: be68d63a139b ("ring-buffer: Add ring_buffer_alloc_range()") Signed-off-by: Vincent Donnefort <vdonnefort@google.com> diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index b8e6bd309707..6089fcc67e2b 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -350,7 +350,7 @@ struct buffer_page { local_t entries; /* entries on this page */ unsigned long real_end; /* real end of data */ unsigned order; /* order of the page */ - u32 id:30; /* ID for external mapping */ + u32 id:31; /* ID for external mapping */ u32 range:1; /* Mapped via a range */ struct buffer_data_page *page; /* Actual data page */ }; @@ -657,6 +657,15 @@ static bool rb_is_static(struct ring_buffer_per_cpu *cpu_buffer) return cpu_buffer->user_mapped || cpu_buffer->remote || cpu_buffer->ring_meta; } +static unsigned long rb_static_max_pages(void) +{ + /* + * Static ring buffers are using bpage::id and must account for the + * reader page. + */ + return (1UL << 31) - 1; +} + struct ring_buffer_iter { struct ring_buffer_per_cpu *cpu_buffer; unsigned long head; @@ -2842,6 +2851,10 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags, */ nr_pages = (size - sizeof(struct ring_buffer_cpu_meta)) / (subbuf_size + sizeof(int)); + + if (nr_pages > rb_static_max_pages()) + goto fail_free_buffers; + /* Need at least two pages plus the reader page */ if (nr_pages < 3) goto fail_free_buffers; @@ -2874,6 +2887,10 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags, /* The writer is remote. This ring-buffer is read-only */ atomic_inc(&buffer->record_disabled); nr_pages = desc->nr_page_va - 1; + + if (nr_pages > rb_static_max_pages()) + goto fail_free_buffers; + if (nr_pages < 2) goto fail_free_buffers; } else { @@ -7839,6 +7856,9 @@ int ring_buffer_map(struct trace_buffer *buffer, int cpu, /* prevent another thread from changing buffer/sub-buffer sizes */ guard(mutex)(&buffer->mutex); + if (cpu_buffer->nr_pages > rb_static_max_pages()) + return -E2BIG; + err = rb_alloc_meta_page(cpu_buffer); if (err) return err; -- 2.55.0.860.g4b6b3295ed-goog ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v8 3/3] ring-buffer: Prevent truncation of nr_pages / nr_subbufs 2026-08-26 9:45 [PATCH v8 0/3] ring-buffer: Fixes for subbuf resizing and persistent buffers Vincent Donnefort 2026-08-26 9:45 ` [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers Vincent Donnefort 2026-08-26 9:45 ` [PATCH v8 2/3] ring-buffer: Cap static ring buffer nr_pages Vincent Donnefort @ 2026-08-26 9:45 ` Vincent Donnefort 2026-08-26 10:02 ` sashiko-bot 2 siblings, 1 reply; 15+ messages in thread From: Vincent Donnefort @ 2026-08-26 9:45 UTC (permalink / raw) To: rostedt, mhiramat, linux-trace-kernel Cc: mathieu.desnoyers, kernel-team, linux-kernel, Vincent Donnefort Although ring_buffer_per_cpu::nr_pages is defined as unsigned long, it is capped to 32-bits in a few places, limiting the operations possible on a very large buffer. Use `unsigned long` where appropriate and prevent truncation of values using nr_pages (or nr_subbufs). While at it, subbuf_size must be at least `unsigned int`. Note that persistent, remote and user-mapped ring buffers are capping the number of pages to 31 bits already, making "int" safe in many places. Signed-off-by: Vincent Donnefort <vdonnefort@google.com> diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 6089fcc67e2b..1fcf55e2ca81 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -1683,7 +1683,7 @@ static void rb_check_pages(struct ring_buffer_per_cpu *cpu_buffer) * This is used to help find the next per cpu subbuffer within a mapped range. */ static unsigned long -rb_range_align_subbuf(unsigned long addr, int subbuf_size, int nr_subbufs) +rb_range_align_subbuf(unsigned long addr, unsigned int subbuf_size, unsigned long nr_subbufs) { addr += sizeof(struct ring_buffer_cpu_meta) + sizeof(int) * nr_subbufs; @@ -1693,13 +1693,12 @@ rb_range_align_subbuf(unsigned long addr, int subbuf_size, int nr_subbufs) /* * Return the ring_buffer_meta for a given @cpu. */ -static void *rb_range_meta(struct trace_buffer *buffer, int nr_pages, int cpu) +static void *rb_range_meta(struct trace_buffer *buffer, unsigned long nr_pages, int cpu) { - int subbuf_size = rb_subbuf_size(buffer); + unsigned int subbuf_size = rb_subbuf_size(buffer); struct ring_buffer_cpu_meta *meta; struct ring_buffer_meta *bmeta; - unsigned long ptr; - int nr_subbufs; + unsigned long ptr, nr_subbufs; bmeta = buffer->meta; if (!bmeta) @@ -1745,7 +1744,7 @@ static void *rb_range_meta(struct trace_buffer *buffer, int nr_pages, int cpu) /* Return the start of subbufs given the meta pointer */ static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta) { - int subbuf_size = meta->subbuf_size; + unsigned int subbuf_size = meta->subbuf_size; unsigned long ptr; ptr = (unsigned long)meta; @@ -1757,11 +1756,11 @@ static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta) /* * Return a specific sub-buffer for a given @cpu defined by @idx. */ -static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, int idx) +static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, unsigned int idx) { struct ring_buffer_cpu_meta *meta; + unsigned int subbuf_size; unsigned long ptr; - int subbuf_size; meta = rb_range_meta(cpu_buffer->buffer, 0, cpu_buffer->cpu); if (!meta) @@ -1777,7 +1776,7 @@ static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, int idx) ptr = (unsigned long)rb_subbufs_from_meta(meta); - ptr += subbuf_size * idx; + ptr += (unsigned long)subbuf_size * idx; if (ptr + subbuf_size > cpu_buffer->buffer->range_addr_end) return NULL; @@ -1854,13 +1853,13 @@ static bool rb_meta_init(struct trace_buffer *buffer, int scratch_size) * must be the same. */ static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu, - struct trace_buffer *buffer, int nr_pages, + struct trace_buffer *buffer, unsigned long nr_pages, unsigned long *subbuf_mask) { - int subbuf_size = PAGE_SIZE; + unsigned long subbuf_size = PAGE_SIZE; unsigned long buffers_start; unsigned long buffers_end; - int i; + unsigned long i; if (!subbuf_mask) return false; @@ -2109,8 +2108,8 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) struct buffer_page *head_page, *orig_head, *orig_reader; struct rb_validation_state state = { 0 }; bool skip = false; + unsigned long i; int ret; - int i; if (!meta || !meta->head_buffer) return; @@ -2161,7 +2160,7 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) rb_validate_buffer(head_page, cpu_buffer, meta, &state, 0, state.ts); } if (i) - pr_info("Ring buffer [%d] rewound %d pages\n", cpu_buffer->cpu, i); + pr_info("Ring buffer [%d] rewound %lu pages\n", cpu_buffer->cpu, i); /* The last rewound page must be skipped. */ if (head_page != orig_head) @@ -2245,7 +2244,8 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) } } -static void rb_range_meta_init(struct trace_buffer *buffer, int nr_pages, int scratch_size) +static void rb_range_meta_init(struct trace_buffer *buffer, + unsigned long nr_pages, int scratch_size) { struct ring_buffer_cpu_meta *meta; unsigned long *subbuf_mask; @@ -2345,8 +2345,8 @@ static int rbm_show(struct seq_file *m, void *v) rb_meta_subbuf_idx(meta, (void *)meta->head_buffer)); seq_printf(m, "commit_buffer: %d\n", rb_meta_subbuf_idx(meta, (void *)meta->commit_buffer)); - seq_printf(m, "subbuf_size: %d\n", meta->subbuf_size); - seq_printf(m, "nr_subbufs: %d\n", meta->nr_subbufs); + seq_printf(m, "subbuf_size: %u\n", meta->subbuf_size); + seq_printf(m, "nr_subbufs: %u\n", meta->nr_subbufs); return 0; } @@ -2431,7 +2431,7 @@ static void *ring_buffer_desc_page(struct ring_buffer_desc *desc, unsigned int p } static int __rb_allocate_pages(struct ring_buffer_per_cpu *cpu_buffer, - long nr_pages, struct list_head *pages) + unsigned long nr_pages, struct list_head *pages) { struct trace_buffer *buffer = cpu_buffer->buffer; struct ring_buffer_cpu_meta *meta = NULL; @@ -2559,7 +2559,7 @@ static int rb_allocate_pages(struct ring_buffer_per_cpu *cpu_buffer, } static struct ring_buffer_per_cpu * -rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu) +rb_allocate_cpu_buffer(struct trace_buffer *buffer, unsigned long nr_pages, int cpu) { struct ring_buffer_per_cpu *cpu_buffer __free(kfree) = alloc_cpu_buffer(cpu); @@ -2716,8 +2716,8 @@ static void rb_test_inject_invalid_pages(struct trace_buffer *buffer) struct ring_buffer_cpu_meta *meta; struct buffer_data_page *dpage; unsigned long entry_bytes = 0; + unsigned int subbuf_size; unsigned long ptr; - int subbuf_size; int invalid = 0; int cpu; int i; @@ -2787,8 +2787,8 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags, struct ring_buffer_remote *remote) { struct trace_buffer *buffer __free(kfree) = NULL; - long nr_pages; - int subbuf_size; + unsigned int subbuf_size; + unsigned long nr_pages; int bsize; int cpu; int ret; @@ -5872,12 +5872,12 @@ __rb_get_reader_page_from_remote(struct ring_buffer_per_cpu *cpu_buffer) static struct buffer_page * __rb_get_reader_page(struct ring_buffer_per_cpu *cpu_buffer) { - int max_loops = cpu_buffer->ring_meta ? cpu_buffer->nr_pages : 3; + unsigned long max_loops = cpu_buffer->ring_meta ? cpu_buffer->nr_pages : 3; struct buffer_page *reader = NULL; + unsigned long nr_loops = 0; unsigned long overwrite; unsigned long flags; int missed_events = 0; - int nr_loops = 0; bool ret; local_irq_save(flags); @@ -6195,8 +6195,8 @@ rb_iter_peek(struct ring_buffer_iter *iter, u64 *ts) struct trace_buffer *buffer; struct ring_buffer_per_cpu *cpu_buffer; struct ring_buffer_event *event; - int nr_loops = 0; - int max_loops; + unsigned long nr_loops = 0; + unsigned long max_loops; if (ts) *ts = 0; @@ -7442,8 +7442,8 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order) struct ring_buffer_per_cpu *cpu_buffer; struct buffer_page *bpage, *tmp; unsigned int old_capacity; + unsigned long nr_pages; int old_order; - int nr_pages; int psize; int err; int cpu; @@ -7625,10 +7625,10 @@ static void rb_setup_ids_meta_page(struct ring_buffer_per_cpu *cpu_buffer, struct buffer_page **subbuf_ids) { struct trace_buffer_meta *meta = cpu_buffer->meta_page; - unsigned int nr_subbufs = cpu_buffer->nr_pages + 1; + unsigned long nr_subbufs = cpu_buffer->nr_pages + 1; struct buffer_page *first_subbuf, *subbuf; - int cnt = 0; - int id = 0; + unsigned int cnt = 0; + unsigned int id = 0; id = rb_page_id(cpu_buffer, cpu_buffer->reader_page, id); subbuf_ids[id++] = cpu_buffer->reader_page; -- 2.55.0.860.g4b6b3295ed-goog ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v8 3/3] ring-buffer: Prevent truncation of nr_pages / nr_subbufs 2026-08-26 9:45 ` [PATCH v8 3/3] ring-buffer: Prevent truncation of nr_pages / nr_subbufs Vincent Donnefort @ 2026-08-26 10:02 ` sashiko-bot 0 siblings, 0 replies; 15+ messages in thread From: sashiko-bot @ 2026-08-26 10:02 UTC (permalink / raw) To: Vincent Donnefort; +Cc: linux-trace-kernel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] The kernel fails to validate the untrusted `meta->nr_subbufs` value from persistent memory against the expected `nr_pages + 1`, leading to arbitrary heap out-of-bounds writes and potential arbitrary kernel memory corruption. -- commit 5ed0ab549782b2ff87e1bd2394abb78b10c4b602 Author: Vincent Donnefort <vdonnefort@google.com> ring-buffer: Prevent truncation of nr_pages / nr_subbufs Although ring_buffer_per_cpu::nr_pages is defined as unsigned long, it is capped to 32-bits in a few places, limiting the operations possible on a very large buffer. Use unsigned long where appropriate and prevent truncation of values using nr_pages (or nr_subbufs). While at it, subbuf_size must be at least unsigned int. Note that persistent, remote and user-mapped ring buffers are capping the number of pages to 31 bits already, making "int" safe in many places. > diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c > index 6089fcc67e2b6..1fcf55e2ca81f 100644 > --- a/kernel/trace/ring_buffer.c > +++ b/kernel/trace/ring_buffer.c > @@ -1745,7 +1744,7 @@ static void *rb_range_meta(struct trace_buffer *buffer, int nr_pages, int cpu) > /* Return the start of subbufs given the meta pointer */ > static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta) > { > - int subbuf_size = meta->subbuf_size; > + unsigned int subbuf_size = meta->subbuf_size; > unsigned long ptr; > > ptr = (unsigned long)meta; [Severity: High] This isn't a bug introduced by this patch, but does the next line in this function perform pointer arithmetic using an unvalidated meta->nr_subbufs? ptr = rb_range_align_subbuf(ptr, subbuf_size, meta->nr_subbufs); Could this yield a corrupted or attacker-controlled subbuf base pointer if meta->nr_subbufs is read from persistent memory without being checked against expected limits? [ ... ] > @@ -1854,13 +1853,13 @@ static bool rb_meta_init(struct trace_buffer *buffer, int scratch_size) > * must be the same. > */ > static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu, > - struct trace_buffer *buffer, int nr_pages, > + struct trace_buffer *buffer, unsigned long nr_pages, > unsigned long *subbuf_mask) > { > - int subbuf_size = PAGE_SIZE; > + unsigned long subbuf_size = PAGE_SIZE; > unsigned long buffers_start; > unsigned long buffers_end; > - int i; > + unsigned long i; > > if (!subbuf_mask) > return false; [Severity: High] This isn't a bug introduced by this patch, but is there a risk of an out-of-bounds heap write further down in this function? The subbuf_mask is allocated with nr_pages + 1 bits in rb_range_meta_init(), but later in rb_cpu_meta_valid() operations use the unvalidated meta->nr_subbufs value read from persistent memory: bitmap_clear(subbuf_mask, 0, meta->nr_subbufs); /* * Ensure the meta::buffers array has correct data. The data in each subbufs * are checked later in rb_meta_validate_events(). */ for (i = 0; i < meta->nr_subbufs; i++) { if (meta->buffers[i] < 0 || meta->buffers[i] >= meta->nr_subbufs) { pr_info("Ring buffer boot meta [%d] array out of range\n", cpu); return false; } if (test_bit(meta->buffers[i], subbuf_mask)) { pr_info("Ring buffer boot meta [%d] array has duplicates\n", cpu); return false; } set_bit(meta->buffers[i], subbuf_mask); } If meta->nr_subbufs is larger than nr_pages + 1, could this write out of bounds and corrupt kernel memory? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260826094528.3738023-1-vdonnefort@google.com?part=3 ^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-08-28 8:36 UTC | newest] Thread overview: 15+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-26 9:45 [PATCH v8 0/3] ring-buffer: Fixes for subbuf resizing and persistent buffers Vincent Donnefort 2026-08-26 9:45 ` [PATCH v8 1/3] tracing: Fix subbuf resize races with trace_pipe_raw readers Vincent Donnefort 2026-08-26 9:59 ` sashiko-bot 2026-08-26 14:37 ` Steven Rostedt 2026-08-26 16:24 ` Vincent Donnefort 2026-08-26 18:31 ` Steven Rostedt 2026-08-27 6:31 ` Vincent Donnefort 2026-08-27 13:15 ` Steven Rostedt 2026-08-27 16:21 ` Vincent Donnefort 2026-08-27 19:33 ` Steven Rostedt 2026-08-28 8:24 ` Vincent Donnefort 2026-08-28 8:36 ` Steven Rostedt 2026-08-26 9:45 ` [PATCH v8 2/3] ring-buffer: Cap static ring buffer nr_pages Vincent Donnefort 2026-08-26 9:45 ` [PATCH v8 3/3] ring-buffer: Prevent truncation of nr_pages / nr_subbufs Vincent Donnefort 2026-08-26 10:02 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox