* [PATCH v6 0/2] ring-buffer: Fixes for subbuf resizing and persistent buffers
@ 2026-08-14 15:48 Vincent Donnefort
2026-08-14 15:48 ` [PATCH v6 1/2] tracing: Fix subbuf resize races with trace_pipe_raw readers Vincent Donnefort
2026-08-14 15:48 ` [PATCH v6 2/2] ring-buffer: Improve nr_pages type Vincent Donnefort
0 siblings, 2 replies; 8+ messages in thread
From: Vincent Donnefort @ 2026-08-14 15:48 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.
Changelog:
v6
- 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 (2):
tracing: Fix subbuf resize races with trace_pipe_raw readers
ring-buffer: Improve nr_pages type
include/linux/ring_buffer.h | 5 +-
kernel/trace/ring_buffer.c | 146 ++++++++++++++++++---------
kernel/trace/ring_buffer_benchmark.c | 6 +-
kernel/trace/trace.c | 104 +++++++++----------
kernel/trace/trace.h | 9 +-
5 files changed, 159 insertions(+), 111 deletions(-)
base-commit: 5f0775c9f72fd284028d955abb7098ecf212a604
--
2.55.0.691.gc56d675ccc-goog
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v6 1/2] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-14 15:48 [PATCH v6 0/2] ring-buffer: Fixes for subbuf resizing and persistent buffers Vincent Donnefort @ 2026-08-14 15:48 ` Vincent Donnefort 2026-08-14 16:01 ` sashiko-bot 2026-08-14 15:48 ` [PATCH v6 2/2] ring-buffer: Improve nr_pages type Vincent Donnefort 1 sibling, 1 reply; 8+ messages in thread From: Vincent Donnefort @ 2026-08-14 15:48 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 let it handle the resizing of a previous buffer_data_read_page if necessary and add a new ring_buffer_read_page_size() which enable ring-buffer users to not use the racy ring_buffer_subbuf_size_get(). This makes the spare_size member of ftrace_buffer_info redundant. Use those functions in trace_pipe_raw readers and handle in both the case where the subbuf order is modified in the middle of the read. Fixes: bce761d75745 ("ring-buffer: Read and write to ring buffers with custom sub buffer size") Signed-off-by: Vincent Donnefort <vdonnefort@google.com> --- include/linux/ring_buffer.h | 5 +- kernel/trace/ring_buffer.c | 100 ++++++++++++++++++-------- kernel/trace/ring_buffer_benchmark.c | 6 +- kernel/trace/trace.c | 104 +++++++++++++-------------- kernel/trace/trace.h | 9 ++- 5 files changed, 130 insertions(+), 94 deletions(-) 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 5fc009edc1ec..ec13779922ff 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -6990,56 +6990,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 +7069,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 +7100,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 +7115,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); @@ -7122,6 +7153,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_free_read_page); * Returns: * >=0 if data has been transferred, returns the offset of consumed data. * <0 if no data has been transferred. + * -EAGAIN if the subbuf size has changed and @data_page must be reallocated. */ int ring_buffer_read_page(struct trace_buffer *buffer, struct buffer_data_read_page *data_page, @@ -7159,7 +7191,7 @@ int ring_buffer_read_page(struct trace_buffer *buffer, guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock); if (data_page->order != cpu_buffer->reader_page->order) - return -1; + return -EAGAIN; reader = rb_get_reader_page(cpu_buffer); if (!reader) @@ -7323,6 +7355,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 395238b2b715..355278083b3f 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -7080,8 +7080,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; @@ -7091,36 +7091,21 @@ 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 */ - 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; - } - } - - 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) - return ret; - +again: /* Do we have previous read data to read? */ - if (info->read < page_size) + if (info->spare && (info->read < ring_buffer_read_page_size(info->spare))) goto read; - again: + /* Make sure the read page order is aligned with the current buffer subbuf order */ + ret = ring_buffer_alloc_read_page(iter->array_buffer->buffer, iter->cpu_file, + &info->spare); + if (ret < 0) + return ret; + + spare_size = ring_buffer_read_page_size(info->spare); + info->read = spare_size; + info->spare_cpu = iter->cpu_file; + trace_access_lock(iter->cpu_file); ret = ring_buffer_read_page(iter->array_buffer->buffer, info->spare, @@ -7128,7 +7113,9 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf, iter->cpu_file, 0); trace_access_unlock(iter->cpu_file); - if (ret < 0) { + if (ret == -EAGAIN) { + goto again; + } else if (ret < 0) { if (trace_empty(iter) && !iter->closed) { if (update_last_data_if_empty(iter->tr)) return 0; @@ -7146,8 +7133,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); @@ -7197,17 +7185,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); } @@ -7268,23 +7256,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; @@ -7292,7 +7269,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; @@ -7304,25 +7282,38 @@ 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; + +new_read_page: + ret = ring_buffer_alloc_read_page(ref->buffer, iter->cpu_file, &ref->rpage); + if (ret < 0) { 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); + if (r == -EAGAIN) + goto new_read_page; + } 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; @@ -7330,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 bf77331f56a4..cbf54eded447 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.691.gc56d675ccc-goog ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v6 1/2] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-14 15:48 ` [PATCH v6 1/2] tracing: Fix subbuf resize races with trace_pipe_raw readers Vincent Donnefort @ 2026-08-14 16:01 ` sashiko-bot 2026-08-17 8:59 ` Vincent Donnefort 0 siblings, 1 reply; 8+ messages in thread From: sashiko-bot @ 2026-08-14 16:01 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] The local variable `spare_size` is left uninitialized via `goto read;` in `tracing_buffers_read()`, resulting in a kernel memory leak to userspace. -- --- Patch [1]: [PATCH v6 1/2] tracing: Fix subbuf resize races with trace_pipe_raw readers --- commit bdc760fe7b1aa6eb8c8a8b8c38e9fd02d25051b6 Author: Vincent Donnefort <vdonnefort@google.com> tracing: Fix subbuf resize races with trace_pipe_raw readers Concurrent subbuffer resizes may crash trace_pipe_raw readers or leak uninitialized memory to userspace due to stale size values. This commit modifies ring_buffer_alloc_read_page() to handle resizing and introduces ring_buffer_read_page_size(). > diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c > index 395238b2b715c..355278083b3fb 100644 > --- a/kernel/trace/trace.c > +++ b/kernel/trace/trace.c [ ... ] > @@ -7091,36 +7091,21 @@ 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 */ > - 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; > - } > - } > +again: > + /* Do we have previous read data to read? */ > + if (info->spare && (info->read < ring_buffer_read_page_size(info->spare))) > + 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 buffer subbuf order */ > + ret = ring_buffer_alloc_read_page(iter->array_buffer->buffer, iter->cpu_file, > + &info->spare); > + if (ret < 0) > 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, [ ... ] > @@ -7146,8 +7133,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; [Severity: High] Can this regression leak uninitialized stack memory to userspace? If a userspace process does a partial read (consuming fewer bytes than a full subbuffer page), the next read syscall will take the goto read; branch at the top of tracing_buffers_read(). When that branch is taken, spare_size is completely uninitialized stack memory. Could size end up being a large garbage value (or underflow if spare_size < info->read), causing copy_to_user() below to read beyond the bounds of info->spare and leak arbitrary kernel memory? > if (size > count) > size = count; > trace_data = ring_buffer_read_page_data(info->spare); > ret = copy_to_user(ubuf, trace_data + info->read, size); -- Sashiko AI review · https://sashiko.dev/#/patchset/20260814154823.755406-1-vdonnefort@google.com?part=1 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v6 1/2] tracing: Fix subbuf resize races with trace_pipe_raw readers 2026-08-14 16:01 ` sashiko-bot @ 2026-08-17 8:59 ` Vincent Donnefort 0 siblings, 0 replies; 8+ messages in thread From: Vincent Donnefort @ 2026-08-17 8:59 UTC (permalink / raw) To: sashiko-reviews; +Cc: linux-trace-kernel On Fri, Aug 14, 2026 at 04:01:30PM +0000, sashiko-bot@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > - [High] The local variable `spare_size` is left uninitialized via `goto read;` in `tracing_buffers_read()`, resulting in a kernel memory leak to userspace. > -- > > --- Patch [1]: [PATCH v6 1/2] tracing: Fix subbuf resize races with trace_pipe_raw readers --- > commit bdc760fe7b1aa6eb8c8a8b8c38e9fd02d25051b6 > Author: Vincent Donnefort <vdonnefort@google.com> > > tracing: Fix subbuf resize races with trace_pipe_raw readers > > Concurrent subbuffer resizes may crash trace_pipe_raw readers or leak > uninitialized memory to userspace due to stale size values. This commit > modifies ring_buffer_alloc_read_page() to handle resizing and introduces > ring_buffer_read_page_size(). > > > diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c > > index 395238b2b715c..355278083b3fb 100644 > > --- a/kernel/trace/trace.c > > +++ b/kernel/trace/trace.c > [ ... ] > > @@ -7091,36 +7091,21 @@ 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 */ > > - 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; > > - } > > - } > > +again: > > + /* Do we have previous read data to read? */ > > + if (info->spare && (info->read < ring_buffer_read_page_size(info->spare))) > > + 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 buffer subbuf order */ > > + ret = ring_buffer_alloc_read_page(iter->array_buffer->buffer, iter->cpu_file, > > + &info->spare); > > + if (ret < 0) > > 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, > [ ... ] > > @@ -7146,8 +7133,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; > > [Severity: High] > Can this regression leak uninitialized stack memory to userspace? > > If a userspace process does a partial read (consuming fewer bytes than a full > subbuffer page), the next read syscall will take the goto read; branch at the > top of tracing_buffers_read(). > > When that branch is taken, spare_size is completely uninitialized stack memory. > > Could size end up being a large garbage value (or underflow if spare_size < > info->read), causing copy_to_user() below to read beyond the bounds of > info->spare and leak arbitrary kernel memory? sigh... I hope I will eventually get this right! again: - /* Do we have previous read data to read? */ - if (info->spare && (info->read < ring_buffer_read_page_size(info->spare))) - goto read; + if (info->spare) { + spare_size = ring_buffer_read_page_size(info->spare); + /* Do we have previous read data to read? */ + if (info->read < spare_size) + goto read; + } This should do. > > > if (size > count) > > size = count; > > trace_data = ring_buffer_read_page_data(info->spare); > > ret = copy_to_user(ubuf, trace_data + info->read, size); > > -- > Sashiko AI review · https://sashiko.dev/#/patchset/20260814154823.755406-1-vdonnefort@google.com?part=1 -- Vincent ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v6 2/2] ring-buffer: Improve nr_pages type 2026-08-14 15:48 [PATCH v6 0/2] ring-buffer: Fixes for subbuf resizing and persistent buffers Vincent Donnefort 2026-08-14 15:48 ` [PATCH v6 1/2] tracing: Fix subbuf resize races with trace_pipe_raw readers Vincent Donnefort @ 2026-08-14 15:48 ` Vincent Donnefort 2026-08-14 15:59 ` sashiko-bot 2026-08-17 10:27 ` Vincent Donnefort 1 sibling, 2 replies; 8+ messages in thread From: Vincent Donnefort @ 2026-08-14 15:48 UTC (permalink / raw) To: rostedt, mhiramat, linux-trace-kernel Cc: mathieu.desnoyers, kernel-team, linux-kernel, Vincent Donnefort If 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. Make sure nr_pages is never capped to 32-bits (that includes nr_subbufs) and reject a value over 32-bits for the user-mapped, persistent buffer and remote buffer cases where the limiting factor is the shared meta-data member for the number of pages/subbufs. While at it, make sure subbuf_size is 'unsigned int'. Signed-off-by: Vincent Donnefort <vdonnefort@google.com> --- kernel/trace/ring_buffer.c | 46 ++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index ec13779922ff..ec127e2ad052 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -1669,7 +1669,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; @@ -1679,13 +1679,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) @@ -1731,7 +1730,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; @@ -1746,8 +1745,8 @@ static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta) static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, 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) @@ -1840,10 +1839,10 @@ 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 int subbuf_size = PAGE_SIZE; unsigned long buffers_start; unsigned long buffers_end; int i; @@ -2231,7 +2230,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; @@ -2417,7 +2417,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; @@ -2545,7 +2545,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); @@ -2702,8 +2702,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; @@ -2773,8 +2773,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; @@ -2837,6 +2837,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)); + + /* limited by ring_buffer_cpu_meta::nr_subbufs */ + if (nr_pages > U32_MAX - 1) + goto fail_free_buffers; /* Need at least two pages plus the reader page */ if (nr_pages < 3) goto fail_free_buffers; @@ -2869,6 +2873,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; + + /* limited by ring_buffer_desc::nr_page_va */ + if (nr_pages > U32_MAX - 1) + goto fail_free_buffers; if (nr_pages < 2) goto fail_free_buffers; } else { @@ -7421,8 +7429,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; @@ -7604,7 +7612,7 @@ 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; @@ -7834,6 +7842,10 @@ int ring_buffer_map(struct trace_buffer *buffer, int cpu, /* prevent another thread from changing buffer/sub-buffer sizes */ guard(mutex)(&buffer->mutex); + /* limited by trace_buffer_meta::nr_subbufs */ + if (cpu_buffer->nr_pages > U32_MAX - 1) + return -E2BIG; + err = rb_alloc_meta_page(cpu_buffer); if (err) return err; -- 2.55.0.691.gc56d675ccc-goog ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v6 2/2] ring-buffer: Improve nr_pages type 2026-08-14 15:48 ` [PATCH v6 2/2] ring-buffer: Improve nr_pages type Vincent Donnefort @ 2026-08-14 15:59 ` sashiko-bot 2026-08-17 10:14 ` Vincent Donnefort 2026-08-17 10:27 ` Vincent Donnefort 1 sibling, 1 reply; 8+ messages in thread From: sashiko-bot @ 2026-08-14 15:59 UTC (permalink / raw) To: Vincent Donnefort; +Cc: linux-trace-kernel Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: New issues: - [High] When `nr_pages` is exactly `U32_MAX - 1`, the expression `meta->nr_subbufs + 1` overflows to 0, silently skipping persistent ring buffer validation and wiping data. Pre-existing issues: - [Critical] Loop counters iterating over `meta->nr_subbufs` are typed as `signed int`, which wrap to negative values for extremely large buffers, leading to out-of-bounds memory accesses. - [Critical] The `meta->nr_subbufs` field is read from potentially corrupted persistent memory and used without validation against `nr_pages`, causing a heap out-of-bounds write in `bitmap_clear()`. - [High] Multiplication of `subbuf_size` and `idx` overflows 32 bits for ring buffers > 4GB, causing severe memory aliasing. -- --- Patch [2]: [PATCH v6 2/2] ring-buffer: Improve nr_pages type --- commit 030ec6a7ceec6e71123077bd1fc64e0cf5c27cc8 Author: Vincent Donnefort <vdonnefort@google.com> ring-buffer: Improve nr_pages type If 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. [ ... ] > diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c > index ec13779922ffb..ec127e2ad0527 100644 > --- a/kernel/trace/ring_buffer.c > +++ b/kernel/trace/ring_buffer.c [ ... ] > @@ -1746,8 +1745,8 @@ static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta) > static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, 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) [Severity: High] This is a pre-existing issue, but does the multiplication of subbuf_size and idx overflow 32 bits for ring buffers larger than 4GB? In rb_range_buffer(), we have: subbuf_size = meta->subbuf_size; idx = meta->buffers[idx]; ptr = (unsigned long)rb_subbufs_from_meta(meta); ptr += subbuf_size * idx; Since subbuf_size is an unsigned int and idx is an int, the multiplication evaluates as a 32-bit unsigned integer. For ring buffers over 4GB, this can overflow and wrap around before being zero-extended and added to the 64-bit ptr, causing memory aliasing by overwriting earlier subbuffers. [ ... ] > @@ -1840,10 +1839,10 @@ 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 int subbuf_size = PAGE_SIZE; > unsigned long buffers_start; > unsigned long buffers_end; > int i; [Severity: Critical] This is a pre-existing issue, but will loop counters iterating over meta->nr_subbufs wrap to negative values for extremely large buffers? In rb_cpu_meta_valid(): for (i = 0; i < meta->nr_subbufs; i++) { if (meta->buffers[i] < 0 || ... And in rb_range_meta_init(): for (i = 0; i < meta->nr_subbufs; i++) { meta->buffers[i] = i; The loop counter i is a signed int. Since nr_pages can now go up to U32_MAX - 1, if the buffer size pushes meta->nr_subbufs past INT_MAX, i will wrap to negative. i < meta->nr_subbufs will promote i to unsigned, evaluating to true, and the negative index will be sign-extended on 64-bit architectures, causing out-of-bounds reads and writes. [Severity: Critical] This is a pre-existing issue, but could meta->nr_subbufs cause a heap out-of-bounds write if it doesn't match nr_pages? In rb_cpu_meta_valid(): bitmap_clear(subbuf_mask, 0, meta->nr_subbufs); for (i = 0; i < meta->nr_subbufs; i++) { ... set_bit(meta->buffers[i], subbuf_mask); meta->nr_subbufs is read directly from persistent memory and used without being validated against the dynamically allocated subbuf_mask size, which is based on nr_pages + 1. If meta->nr_subbufs is maliciously or accidentally set larger than nr_pages + 1, bitmap_clear() and set_bit() will write zeroes and ones past the end of the bitmap allocation. [ ... ] > @@ -2837,6 +2837,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)); > + > + /* limited by ring_buffer_cpu_meta::nr_subbufs */ > + if (nr_pages > U32_MAX - 1) > + goto fail_free_buffers; > /* Need at least two pages plus the reader page */ > if (nr_pages < 3) > goto fail_free_buffers; [Severity: High] Does meta->nr_subbufs + 1 overflow when nr_pages is exactly U32_MAX - 1? If nr_pages is exactly U32_MAX - 1, meta->nr_subbufs becomes U32_MAX. In rb_meta_validate_events(), there are loops with this condition: for (i = 0; i < meta->nr_subbufs + 1; i++, rb_dec_page(&head_page)) { U32_MAX + 1 overflows to 0 in 32-bit unsigned arithmetic. This makes the loop condition i < 0, causing the loops to terminate immediately without rewinding and validating the persistent ring buffer from a previous boot. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260814154823.755406-1-vdonnefort@google.com?part=2 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v6 2/2] ring-buffer: Improve nr_pages type 2026-08-14 15:59 ` sashiko-bot @ 2026-08-17 10:14 ` Vincent Donnefort 0 siblings, 0 replies; 8+ messages in thread From: Vincent Donnefort @ 2026-08-17 10:14 UTC (permalink / raw) To: sashiko-reviews; +Cc: linux-trace-kernel On Fri, Aug 14, 2026 at 03:59:44PM +0000, sashiko-bot@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: > > New issues: > - [High] When `nr_pages` is exactly `U32_MAX - 1`, the expression `meta->nr_subbufs + 1` overflows to 0, silently skipping persistent ring buffer validation and wiping data. > > Pre-existing issues: > - [Critical] Loop counters iterating over `meta->nr_subbufs` are typed as `signed int`, which wrap to negative values for extremely large buffers, leading to out-of-bounds memory accesses. > - [Critical] The `meta->nr_subbufs` field is read from potentially corrupted persistent memory and used without validation against `nr_pages`, causing a heap out-of-bounds write in `bitmap_clear()`. > - [High] Multiplication of `subbuf_size` and `idx` overflows 32 bits for ring buffers > 4GB, causing severe memory aliasing. > -- > > --- Patch [2]: [PATCH v6 2/2] ring-buffer: Improve nr_pages type --- > commit 030ec6a7ceec6e71123077bd1fc64e0cf5c27cc8 > Author: Vincent Donnefort <vdonnefort@google.com> > > ring-buffer: Improve nr_pages type > > If 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. > > [ ... ] > > > diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c > > index ec13779922ffb..ec127e2ad0527 100644 > > --- a/kernel/trace/ring_buffer.c > > +++ b/kernel/trace/ring_buffer.c > > [ ... ] > > > @@ -1746,8 +1745,8 @@ static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta) > > static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, 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) > > [Severity: High] > This is a pre-existing issue, but does the multiplication of subbuf_size and > idx overflow 32 bits for ring buffers larger than 4GB? > > In rb_range_buffer(), we have: > > subbuf_size = meta->subbuf_size; > idx = meta->buffers[idx]; > ptr = (unsigned long)rb_subbufs_from_meta(meta); > ptr += subbuf_size * idx; > > Since subbuf_size is an unsigned int and idx is an int, the multiplication > evaluates as a 32-bit unsigned integer. For ring buffers over 4GB, this can > overflow and wrap around before being zero-extended and added to the 64-bit > ptr, causing memory aliasing by overwriting earlier subbuffers. I can add this to this patch/ > > [ ... ] > > > @@ -1840,10 +1839,10 @@ 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 int subbuf_size = PAGE_SIZE; > > unsigned long buffers_start; > > unsigned long buffers_end; > > int i; > > [Severity: Critical] > This is a pre-existing issue, but will loop counters iterating over > meta->nr_subbufs wrap to negative values for extremely large buffers? > > In rb_cpu_meta_valid(): > > for (i = 0; i < meta->nr_subbufs; i++) { > if (meta->buffers[i] < 0 || ... > > And in rb_range_meta_init(): > > for (i = 0; i < meta->nr_subbufs; i++) { > meta->buffers[i] = i; > > The loop counter i is a signed int. Since nr_pages can now go up to > U32_MAX - 1, if the buffer size pushes meta->nr_subbufs past INT_MAX, i > will wrap to negative. i < meta->nr_subbufs will promote i to unsigned, > evaluating to true, and the negative index will be sign-extended on 64-bit > architectures, causing out-of-bounds reads and writes. Yeah sure, I'll make it unsigned int... doesn't sound too critical though! > > [Severity: Critical] > This is a pre-existing issue, but could meta->nr_subbufs cause a heap > out-of-bounds write if it doesn't match nr_pages? > > In rb_cpu_meta_valid(): > > bitmap_clear(subbuf_mask, 0, meta->nr_subbufs); > > for (i = 0; i < meta->nr_subbufs; i++) { > ... > set_bit(meta->buffers[i], subbuf_mask); > > meta->nr_subbufs is read directly from persistent memory and used without > being validated against the dynamically allocated subbuf_mask size, which is > based on nr_pages + 1. If meta->nr_subbufs is maliciously or accidentally set > larger than nr_pages + 1, bitmap_clear() and set_bit() will write zeroes and > ones past the end of the bitmap allocation. Sure, but that'll be another patch. > > [ ... ] > > > @@ -2837,6 +2837,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)); > > + > > + /* limited by ring_buffer_cpu_meta::nr_subbufs */ > > + if (nr_pages > U32_MAX - 1) > > + goto fail_free_buffers; > > /* Need at least two pages plus the reader page */ > > if (nr_pages < 3) > > goto fail_free_buffers; > > [Severity: High] > Does meta->nr_subbufs + 1 overflow when nr_pages is exactly U32_MAX - 1? > > If nr_pages is exactly U32_MAX - 1, meta->nr_subbufs becomes U32_MAX. > In rb_meta_validate_events(), there are loops with this condition: > > for (i = 0; i < meta->nr_subbufs + 1; i++, rb_dec_page(&head_page)) { > > U32_MAX + 1 overflows to 0 in 32-bit unsigned arithmetic. This makes the loop > condition i < 0, causing the loops to terminate immediately without rewinding > and validating the persistent ring buffer from a previous boot. Sure, this can be unsigned it as well... And there are plenty of other functions that need fixing then. rb_meta_subbuf_idx, rb_cpu_meta_valid, __rb_get_reader_page, rb_iter_peek... I need a bit more time to audit what must be modified. -- Vincent > > -- > Sashiko AI review · https://sashiko.dev/#/patchset/20260814154823.755406-1-vdonnefort@google.com?part=2 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v6 2/2] ring-buffer: Improve nr_pages type 2026-08-14 15:48 ` [PATCH v6 2/2] ring-buffer: Improve nr_pages type Vincent Donnefort 2026-08-14 15:59 ` sashiko-bot @ 2026-08-17 10:27 ` Vincent Donnefort 1 sibling, 0 replies; 8+ messages in thread From: Vincent Donnefort @ 2026-08-17 10:27 UTC (permalink / raw) To: rostedt, mhiramat, linux-trace-kernel Cc: mathieu.desnoyers, kernel-team, linux-kernel On Fri, Aug 14, 2026 at 04:48:23PM +0100, Vincent Donnefort wrote: > If 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. > > Make sure nr_pages is never capped to 32-bits (that includes nr_subbufs) > and reject a value over 32-bits for the user-mapped, persistent buffer > and remote buffer cases where the limiting factor is the shared > meta-data member for the number of pages/subbufs. > > While at it, make sure subbuf_size is 'unsigned int'. > > Signed-off-by: Vincent Donnefort <vdonnefort@google.com> > --- > kernel/trace/ring_buffer.c | 46 ++++++++++++++++++++++++-------------- > 1 file changed, 29 insertions(+), 17 deletions(-) > > diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c > index ec13779922ff..ec127e2ad052 100644 > --- a/kernel/trace/ring_buffer.c > +++ b/kernel/trace/ring_buffer.c > @@ -1669,7 +1669,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; > @@ -1679,13 +1679,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) > @@ -1731,7 +1730,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; > @@ -1746,8 +1745,8 @@ static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta) > static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, 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) > @@ -1840,10 +1839,10 @@ 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 int subbuf_size = PAGE_SIZE; > unsigned long buffers_start; > unsigned long buffers_end; > int i; > @@ -2231,7 +2230,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; > @@ -2417,7 +2417,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; > @@ -2545,7 +2545,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); > @@ -2702,8 +2702,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; > @@ -2773,8 +2773,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; > @@ -2837,6 +2837,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)); > + > + /* limited by ring_buffer_cpu_meta::nr_subbufs */ > + if (nr_pages > U32_MAX - 1) > + goto fail_free_buffers; > /* Need at least two pages plus the reader page */ > if (nr_pages < 3) > goto fail_free_buffers; > @@ -2869,6 +2873,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; > + > + /* limited by ring_buffer_desc::nr_page_va */ > + if (nr_pages > U32_MAX - 1) > + goto fail_free_buffers; > if (nr_pages < 2) > goto fail_free_buffers; > } else { > @@ -7421,8 +7429,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; > @@ -7604,7 +7612,7 @@ 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; > @@ -7834,6 +7842,10 @@ int ring_buffer_map(struct trace_buffer *buffer, int cpu, > /* prevent another thread from changing buffer/sub-buffer sizes */ > guard(mutex)(&buffer->mutex); > > + /* limited by trace_buffer_meta::nr_subbufs */ And actually I have realised the limiting factor is bpage::id which is only 30-bits. > + if (cpu_buffer->nr_pages > U32_MAX - 1) > + return -E2BIG; > + > err = rb_alloc_meta_page(cpu_buffer); > if (err) > return err; > -- > 2.55.0.691.gc56d675ccc-goog > -- Vincent ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-17 10:27 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-14 15:48 [PATCH v6 0/2] ring-buffer: Fixes for subbuf resizing and persistent buffers Vincent Donnefort 2026-08-14 15:48 ` [PATCH v6 1/2] tracing: Fix subbuf resize races with trace_pipe_raw readers Vincent Donnefort 2026-08-14 16:01 ` sashiko-bot 2026-08-17 8:59 ` Vincent Donnefort 2026-08-14 15:48 ` [PATCH v6 2/2] ring-buffer: Improve nr_pages type Vincent Donnefort 2026-08-14 15:59 ` sashiko-bot 2026-08-17 10:14 ` Vincent Donnefort 2026-08-17 10:27 ` Vincent Donnefort
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.