| 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 | |