>From 8425426d0fb256acf7c2e50f0aa642450adc366a Mon Sep 17 00:00:00 2001 From: Kevin Sheldrake <kevin.sheldrake@xxxxxxxxxxxxx> Date: Wed, 4 Nov 2020 15:42:54 +0000 Subject: [PATCH] Update perf ring buffer to prevent corruption from bpf_perf_output_event() The bpf_perf_output_event() helper takes a sample size parameter of u64, but the underlying perf ring buffer uses a u16 internally. This 64KB maximum size has to also accommodate a variable sized header. Failure to observe this restriction can result in corruption of the perf ring buffer as samples overlap. Truncate the raw sample type used by EBPF so that the total size of the sample is < U16_MAX. The size parameter of the received sample will match the size of the truncated sample, so users can be confident about how much data was received. Signed-off-by: Kevin Sheldrake <kevin.sheldrake@xxxxxxxxxxxxx> --- kernel/events/core.c | 83 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index da467e1..45684a6 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7016,6 +7016,21 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs) return callchain ?: &__empty_callchain; } +bool +__prep_frag_size(u32 sum, int *frag_size, u16 header_size) +{ + u32 size, diff; + size = round_up(sum + *frag_size + sizeof(u32), sizeof(u64)); + if (header_size + size < U16_MAX) + return false; + /* fragment is too big, need to truncate */ + diff = round_up(header_size + size - U16_MAX, sizeof(u64)); + *frag_size = round_up(*frag_size - diff, sizeof(u32)); + if (*frag_size % 8 == 0) + *frag_size += sizeof(u32); + return true; +} + void perf_prepare_sample(struct perf_event_header *header, struct perf_sample_data *data, struct perf_event *event, @@ -7045,31 +7060,6 @@ void perf_prepare_sample(struct perf_event_header *header, header->size += size * sizeof(u64); } - if (sample_type & PERF_SAMPLE_RAW) { - struct perf_raw_record *raw = data->raw; - int size; - - if (raw) { - struct perf_raw_frag *frag = &raw->frag; - u32 sum = 0; - - do { - sum += frag->size; - if (perf_raw_frag_last(frag)) - break; - frag = frag->next; - } while (1); - - size = round_up(sum + sizeof(u32), sizeof(u64)); - raw->size = size - sizeof(u32); - frag->pad = raw->size - sum; - } else { - size = sizeof(u64); - } - - header->size += size; - } - if (sample_type & PERF_SAMPLE_BRANCH_STACK) { int size = sizeof(u64); /* nr */ if (data->br_stack) { @@ -7170,6 +7160,49 @@ void perf_prepare_sample(struct perf_event_header *header, WARN_ON_ONCE(size + header->size > U16_MAX); header->size += size; } + + if (sample_type & PERF_SAMPLE_RAW) { + struct perf_raw_record *raw = data->raw; + int size; + bool truncate = false; + + /* + * Given the 16bit nature of header::size, a RAW sample can + * easily overflow it. Make sure this doesn't happen by using + * up to U16_MAX bytes per sample in total (rounded down to 8 + * byte boundary). This requires modification of the fragment + * sizes, so the first oversized fragment is truncated to + * the maximum safe size, and every subsequent fragment is + * truncated to 0 size. + */ + + if (raw) { + struct perf_raw_frag *frag = &raw->frag; + u32 sum = 0; + + do { + if (truncate) { + frag->size = 0; + } else { + truncate = __prep_frag_size(sum, + &frag->size, header->size); + } + sum += frag->size; + if (perf_raw_frag_last(frag)) + break; + frag = frag->next; + } while (1); + + size = round_up(sum + sizeof(u32), sizeof(u64)); + raw->size = size - sizeof(u32); + frag->pad = raw->size - sum; + } else { + size = sizeof(u64); + } + + header->size += size; + } + /* * If you're adding more sample types here, you likely need to do * something about the overflowing header::size, like repurpose the -- 2.7.4