| From b96aeaf74e844796fdd5b68d65413a5e1434de0e Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Fri, 22 May 2026 13:08:58 -0400 |
| Subject: ring-buffer: Skip invalid sub-buffers when validating persistent ring |
| buffer |
| |
| From: Masami Hiramatsu (Google) <mhiramat@kernel.org> |
| |
| [ Upstream commit eb3bd277b37cd435d26a44a40d8f7c87ff16feb6 ] |
| |
| Skip invalid sub-buffers when validating the persistent ring buffer |
| instead of discarding the entire ring buffer. Only skipped buffers |
| are invalidated (cleared). |
| |
| If the cache data in memory fails to be synchronized during a reboot, |
| the persistent ring buffer may become partially corrupted, but other |
| sub-buffers may still contain readable event data. Only discard the |
| subbuffers that are found to be corrupted. |
| |
| Link: https://lore.kernel.org/all/20260520185018.051228084@kernel.org/ |
| |
| Link: https://patch.msgid.link/20260522171050.914418536@kernel.org |
| Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> |
| [SDR: Fixed max_loops in rb_iter_peek() as well ] |
| Signed-off-by: Steven Rostedt <rostedt@goodmis.org> |
| Stable-dep-of: 6c001a62c34f ("ring-buffer: Add checking nr_subbufs to persistent ring buffer validation") |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| kernel/trace/ring_buffer.c | 118 ++++++++++++++++++++++--------------- |
| 1 file changed, 72 insertions(+), 46 deletions(-) |
| |
| diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c |
| index fc1c0034bc985..eed5c45b86ae6 100644 |
| --- a/kernel/trace/ring_buffer.c |
| +++ b/kernel/trace/ring_buffer.c |
| @@ -412,6 +412,12 @@ static __always_inline unsigned int rb_page_capacity(struct buffer_page *bpage) |
| return (PAGE_SIZE << bpage->order) - BUF_PAGE_HDR_SIZE; |
| } |
| |
| +/* Size is determined by what has been committed */ |
| +static __always_inline unsigned int rb_page_size(struct buffer_page *bpage) |
| +{ |
| + return rb_page_commit(bpage) & ~RB_MISSED_MASK; |
| +} |
| + |
| static void free_buffer_page(struct buffer_page *bpage) |
| { |
| /* Range pages are not to be freed */ |
| @@ -1836,7 +1842,6 @@ static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu, |
| unsigned long *subbuf_mask) |
| { |
| int subbuf_size = PAGE_SIZE; |
| - struct buffer_data_page *subbuf; |
| unsigned long buffers_start; |
| unsigned long buffers_end; |
| int i; |
| @@ -1844,6 +1849,11 @@ static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu, |
| if (!subbuf_mask) |
| return false; |
| |
| + if (meta->subbuf_size != PAGE_SIZE) { |
| + pr_info("Ring buffer boot meta [%d] invalid subbuf_size\n", cpu); |
| + return false; |
| + } |
| + |
| buffers_start = meta->first_buffer; |
| buffers_end = meta->first_buffer + (subbuf_size * meta->nr_subbufs); |
| |
| @@ -1860,11 +1870,12 @@ static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu, |
| return false; |
| } |
| |
| - subbuf = rb_subbufs_from_meta(meta); |
| - |
| bitmap_clear(subbuf_mask, 0, meta->nr_subbufs); |
| |
| - /* Is the meta buffers and the subbufs themselves have correct data? */ |
| + /* |
| + * 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) { |
| @@ -1872,18 +1883,12 @@ static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu, |
| return false; |
| } |
| |
| - if ((unsigned)local_read(&subbuf->commit) > subbuf_size) { |
| - pr_info("Ring buffer boot meta [%d] buffer invalid commit\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); |
| - subbuf = (void *)subbuf + subbuf_size; |
| } |
| |
| return true; |
| @@ -1947,13 +1952,22 @@ static int rb_read_data_buffer(struct buffer_data_page *dpage, int tail, int cpu |
| return events; |
| } |
| |
| -static int rb_validate_buffer(struct buffer_data_page *dpage, int cpu) |
| +static int rb_validate_buffer(struct buffer_data_page *dpage, int cpu, |
| + struct ring_buffer_cpu_meta *meta) |
| { |
| unsigned long long ts; |
| + unsigned long tail; |
| u64 delta; |
| - int tail; |
| |
| - tail = local_read(&dpage->commit); |
| + /* |
| + * When a sub-buffer is recovered from a read, the commit value may |
| + * have RB_MISSED_* bits set, as these bits are reset on reuse. |
| + * Even after clearing these bits, a commit value greater than the |
| + * subbuf_size is considered invalid. |
| + */ |
| + tail = local_read(&dpage->commit) & ~RB_MISSED_MASK; |
| + if (tail > meta->subbuf_size - BUF_PAGE_HDR_SIZE) |
| + return -1; |
| return rb_read_data_buffer(dpage, tail, cpu, &ts, &delta); |
| } |
| |
| @@ -1964,6 +1978,7 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) |
| struct buffer_page *head_page, *orig_head, *orig_reader; |
| unsigned long entry_bytes = 0; |
| unsigned long entries = 0; |
| + int discarded = 0; |
| int ret; |
| u64 ts; |
| int i; |
| @@ -1975,14 +1990,19 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) |
| orig_reader = cpu_buffer->reader_page; |
| |
| /* Do the reader page first */ |
| - ret = rb_validate_buffer(orig_reader->page, cpu_buffer->cpu); |
| + ret = rb_validate_buffer(orig_reader->page, cpu_buffer->cpu, meta); |
| if (ret < 0) { |
| - pr_info("Ring buffer reader page is invalid\n"); |
| - goto invalid; |
| + pr_info("Ring buffer meta [%d] invalid reader page detected\n", |
| + cpu_buffer->cpu); |
| + discarded++; |
| + /* Instead of discard whole ring buffer, discard only this sub-buffer. */ |
| + local_set(&orig_reader->entries, 0); |
| + local_set(&orig_reader->page->commit, 0); |
| + } else { |
| + entries += ret; |
| + entry_bytes += rb_page_size(orig_reader); |
| + local_set(&orig_reader->entries, ret); |
| } |
| - entries += ret; |
| - entry_bytes += local_read(&orig_reader->page->commit); |
| - local_set(&orig_reader->entries, ret); |
| |
| ts = head_page->page->time_stamp; |
| |
| @@ -2010,7 +2030,7 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) |
| break; |
| |
| /* Stop rewind if the page is invalid. */ |
| - ret = rb_validate_buffer(head_page->page, cpu_buffer->cpu); |
| + ret = rb_validate_buffer(head_page->page, cpu_buffer->cpu, meta); |
| if (ret < 0) |
| break; |
| |
| @@ -2019,7 +2039,7 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) |
| if (ret) |
| local_inc(&cpu_buffer->pages_touched); |
| entries += ret; |
| - entry_bytes += rb_page_commit(head_page); |
| + entry_bytes += rb_page_size(head_page); |
| } |
| if (i) |
| pr_info("Ring buffer [%d] rewound %d pages\n", cpu_buffer->cpu, i); |
| @@ -2089,21 +2109,24 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) |
| if (head_page == orig_reader) |
| continue; |
| |
| - ret = rb_validate_buffer(head_page->page, cpu_buffer->cpu); |
| + ret = rb_validate_buffer(head_page->page, cpu_buffer->cpu, meta); |
| if (ret < 0) { |
| - pr_info("Ring buffer meta [%d] invalid buffer page\n", |
| - cpu_buffer->cpu); |
| - goto invalid; |
| - } |
| - |
| - /* If the buffer has content, update pages_touched */ |
| - if (ret) |
| - local_inc(&cpu_buffer->pages_touched); |
| - |
| - entries += ret; |
| - entry_bytes += local_read(&head_page->page->commit); |
| - local_set(&head_page->entries, ret); |
| + if (!discarded) |
| + pr_info("Ring buffer meta [%d] invalid buffer page detected\n", |
| + cpu_buffer->cpu); |
| + discarded++; |
| + /* Instead of discard whole ring buffer, discard only this sub-buffer. */ |
| + local_set(&head_page->entries, 0); |
| + local_set(&head_page->page->commit, 0); |
| + } else { |
| + /* If the buffer has content, update pages_touched */ |
| + if (ret) |
| + local_inc(&cpu_buffer->pages_touched); |
| |
| + entries += ret; |
| + entry_bytes += rb_page_size(head_page); |
| + local_set(&head_page->entries, ret); |
| + } |
| if (head_page == cpu_buffer->commit_page) |
| break; |
| } |
| @@ -2117,7 +2140,10 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) |
| local_set(&cpu_buffer->entries, entries); |
| local_set(&cpu_buffer->entries_bytes, entry_bytes); |
| |
| - pr_info("Ring buffer meta [%d] is from previous boot!\n", cpu_buffer->cpu); |
| + pr_info("Ring buffer meta [%d] is from previous boot!", cpu_buffer->cpu); |
| + if (discarded) |
| + pr_cont(" (%d pages discarded)", discarded); |
| + pr_cont("\n"); |
| return; |
| |
| invalid: |
| @@ -3303,12 +3329,6 @@ rb_iter_head_event(struct ring_buffer_iter *iter) |
| return NULL; |
| } |
| |
| -/* Size is determined by what has been committed */ |
| -static __always_inline unsigned rb_page_size(struct buffer_page *bpage) |
| -{ |
| - return rb_page_commit(bpage) & ~RB_MISSED_MASK; |
| -} |
| - |
| static __always_inline unsigned |
| rb_commit_index(struct ring_buffer_per_cpu *cpu_buffer) |
| { |
| @@ -5480,6 +5500,7 @@ rb_update_iter_read_stamp(struct ring_buffer_iter *iter, |
| 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; |
| struct buffer_page *reader = NULL; |
| unsigned long overwrite; |
| unsigned long flags; |
| @@ -5492,11 +5513,14 @@ rb_get_reader_page(struct ring_buffer_per_cpu *cpu_buffer) |
| again: |
| /* |
| * This should normally only loop twice. But because the |
| - * start of the reader inserts an empty page, it causes |
| - * a case where we will loop three times. There should be no |
| - * reason to loop four times (that I know of). |
| + * start of the reader inserts an empty page, it causes a |
| + * case where we will loop three times. There should be no |
| + * reason to loop four times unless the ring buffer is a |
| + * recovered persistent ring buffer. For persistent ring buffers, |
| + * invalid pages are reset during recovery, so there may be more |
| + * than 3 contiguous pages can be empty, but less than nr_pages. |
| */ |
| - if (RB_WARN_ON(cpu_buffer, ++nr_loops > 3)) { |
| + if (RB_WARN_ON(cpu_buffer, ++nr_loops > max_loops)) { |
| reader = NULL; |
| goto out; |
| } |
| @@ -5786,12 +5810,14 @@ rb_iter_peek(struct ring_buffer_iter *iter, u64 *ts) |
| struct ring_buffer_per_cpu *cpu_buffer; |
| struct ring_buffer_event *event; |
| int nr_loops = 0; |
| + int max_loops; |
| |
| if (ts) |
| *ts = 0; |
| |
| cpu_buffer = iter->cpu_buffer; |
| buffer = cpu_buffer->buffer; |
| + max_loops = cpu_buffer->ring_meta ? cpu_buffer->nr_pages : 3; |
| |
| /* |
| * Check if someone performed a consuming read to the buffer |
| @@ -5814,7 +5840,7 @@ rb_iter_peek(struct ring_buffer_iter *iter, u64 *ts) |
| * the ring buffer with an active write as the consumer is. |
| * Do not warn if the three failures is reached. |
| */ |
| - if (++nr_loops > 3) |
| + if (++nr_loops > max_loops) |
| return NULL; |
| |
| if (rb_per_cpu_empty(cpu_buffer)) |
| -- |
| 2.53.0 |
| |