* Re: [PATCH v2 2/2] perf trace-event: Fix integer truncation in do_read() and skip()
[not found] ` <20260723101054.09ABE1F000E9@smtp.kernel.org>
@ 2026-07-24 8:40 ` Tanushree Shah
0 siblings, 0 replies; 3+ messages in thread
From: Tanushree Shah @ 2026-07-24 8:40 UTC (permalink / raw)
To: sashiko-reviews, Arnaldo Carvalho de Melo, Namhyung Kim,
Ian Rogers
Cc: linux-perf-users, Madhavan Srinivasan, hbathini, atrajeev
Thank you for the review.
On 23/07/26 15:40, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Critical] Heap buffer overflow in `read_ftrace_printk` unmasked by `size_t` conversion.
> - [High] Infinite loop in `skip()` leading to Denial of Service.
> --
>
> --- Patch [2]: [PATCH v2 2/2] perf trace-event: Fix integer truncation in do_read() and skip() ---
> commit b30cf605f4d2ba7957c47c5106a821726355dd7e
> Author: Tanushree Shah <tshah@linux.ibm.com>
>
> perf trace-event: Fix integer truncation in do_read() and skip()
>
> This commit modifies do_read(), __do_read(), and skip() to use size_t and
> ssize_t instead of int for sizes and return values. This addresses integer
> truncation of 64-bit sizes, fixing uninitialized memory dumps and out-of-bounds
> reads in the perf tools.
>
>> diff --git a/tools/perf/util/trace-event-read.c b/tools/perf/util/trace-event-read.c
>> index afd458cf1387d..52ed496d92c3b 100644
>> --- a/tools/perf/util/trace-event-read.c
>> +++ b/tools/perf/util/trace-event-read.c
>> @@ -51,13 +51,13 @@ static int __do_read(int fd, void *buf, int size)
>> return rsize;
>> }
>>
>> -static int do_read(void *data, int size)
>> +static ssize_t do_read(void *data, size_t size)
>
> [Severity: Critical]
> Does changing the size parameter to size_t unmask a heap buffer overflow in
> read_ftrace_printk()?
>
> If a crafted file supplies 0xFFFFFFFF, it triggers an overflow in
> read_ftrace_printk():
>
> read_ftrace_printk() {
> size = read4(pevent);
> ...
> buf = malloc(size + 1);
> if (buf == NULL)
> return -1;
>
> if (do_read(buf, size) < 0) {
> ...
> }
>
> Because size + 1 overflows to 0, malloc(0) returns a minimal heap chunk.
>
> Previously, passing 0xFFFFFFFF to do_read() truncated the size parameter
> to -1. When this -1 was passed to the read() syscall on 64-bit systems,
> it became ULLONG_MAX. This failed boundary checks against TASK_SIZE_MAX,
> causing the kernel to immediately return -EFAULT and preventing the read.
>
> With size now being size_t, 0xFFFFFFFF is passed directly to the read()
> syscall as 4294967295ULL. Because this is below the 64-bit TASK_SIZE_MAX
> limit, the kernel will read up to MAX_RW_COUNT of file data into the tiny
> allocated buffer.
>
> Could this be prevented by adding a bounds check before the allocation?
Thanks for catching this. yes, this is a real heap overflow.
size is attacker-controlled, and size + 1 can overflow to 0,
causing a tiny buffer to be allocated while a huge read is still
performed into it.
Fix: reject, size == UINT_MAX before it's used, so the overflow
can't happen. Will include this in v3.
>
>> {
>> - int r;
>> + ssize_t r;
>>
>> r = __do_read(input_fd, data, size);
>
> [ ... ]
>
>> /* If it fails, the next read will report it */
>> -static void skip(int size)
>> +static void skip(size_t size)
>> {
>> char buf[BUFSIZ];
>> - int r;
>> + size_t r;
>>
>> while (size) {
>> r = size > BUFSIZ ? BUFSIZ : size;
>> do_read(buf, r);
>> size -= r;
>> }
>> }
>
> [Severity: High]
> Does this change introduce an infinite loop when a maliciously crafted
> perf.data file provides a massive 64-bit size?
>
> Before the patch, passing ULLONG_MAX was implicitly truncated to -1.
> In the first loop iteration, r = size > BUFSIZ ? BUFSIZ : size evaluated
> -1 > 8192 as false, setting r = -1. Then size -= r set size to 0,
> terminating the loop immediately.
>
> Now that size is widened to size_t, skip() will continuously subtract
> BUFSIZ from ULLONG_MAX. Because skip() ignores the return value of
> do_read(), does this cause the perf process to spin endlessly even
> after reaching EOF?
>
Yes, its right that widening size to size_t removes the accidental
truncation that was previously capping the loop early on bogus input,
and the missing check on do_read()'s return value turns that into a real
infinite loop on a malicious size
Fix: Check do_read()'s return value and break out on failure/EOF instead
of blindly subtracting the requested length. Will include this in v3.
Thanks
Tanushree Shah
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2 1/2] perf trace-event: Fix buffer overflow in read_string()
[not found] ` <20260723095827.659211F000E9@smtp.kernel.org>
@ 2026-07-24 10:17 ` Tanushree Shah
2026-07-24 10:18 ` Tanushree Shah
1 sibling, 0 replies; 3+ messages in thread
From: Tanushree Shah @ 2026-07-24 10:17 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-perf-users
On 23/07/26 15:28, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
>
> Pre-existing issues:
> - [High] Error path in trace_report() leads to a double free of tevent->pevent when session initialization fails.
> - [High] Integer truncation in do_read() allows a malicious trace file to cause the parser to process uninitialized heap memory.
> --
>
> --- Patch [1]: [PATCH v2 1/2] perf trace-event: Fix buffer overflow in read_string() ---
> commit fca4f0cc3071265252215647838a7b30c9c0baaf
> Author: Tanushree Shah <tshah@linux.ibm.com>
>
> perf trace-event: Fix buffer overflow in read_string()
>
> This patch fixes a potential stack buffer overflow in read_string() by
> adding a bounds check before writing each byte into the buffer. If the
> string exceeds the buffer size, it logs a debug message and returns NULL,
> matching other error handling paths.
>
>> diff --git a/tools/perf/util/trace-event-read.c b/tools/perf/util/trace-event-read.c
>> index ecbbb93f01853..afd458cf1387d 100644
>> --- a/tools/perf/util/trace-event-read.c
>> +++ b/tools/perf/util/trace-event-read.c
>> @@ -127,6 +127,11 @@ static char *read_string(void)
>> }
>> }
>>
>> + if (size >= (int)sizeof(buf) - 1) {
>> + pr_debug("string too long (max %zu bytes)", sizeof(buf) - 1);
>> + goto out;
>
> [Severity: High]
> This is a pre-existing issue, but could this error path trigger a double free
> of tevent->pevent?
>
> If read_string() returns NULL due to the bounds check, the error propagates
> and trace_report() jumps to its error path:
>
> tools/perf/util/trace-event-read.c:trace_report() {
> ...
> out:
> if (pevent)
> trace_event__cleanup(tevent);
> ...
> }
>
> trace_event__cleanup() frees t->pevent but leaves the pointer dangling:
>
> tools/perf/util/trace-event.c:trace_event__cleanup() {
> ...
> tep_free(t->pevent);
> ...
> }
>
> Later, during session teardown, perf_session__delete() unconditionally calls
> trace_event__cleanup(&session->tevent), which will attempt to free the same
> pointer again.
Thanks for catching this. trace_report()'s error path and
perf_session__delete() can both call trace_event__cleanup() on the same
tevent, with nothing preventing a double-free.
Fix: clear t->pevent to NULL after cleanup, and guard the function to
no-op if it is already NULL, so a second call is safe.
>
> [Severity: High]
> This is also a pre-existing issue, but does do_read() suffer from an integer
> truncation issue?
>
> Functions like read_event_file() allocate memory based on a 64-bit size:
>
> tools/perf/util/trace-event-read.c:read_event_file() {
> ...
> ret = do_read(buf, size);
> if (ret < 0)
> goto out;
>
> ret = parse_event_file(pevent, buf, size, sys);
> ...
> }
>
> But do_read() takes size as an int:
>
> tools/perf/util/trace-event-read.c:do_read() {
> static int do_read(void *data, int size)
> ...
> }
>
> If a maliciously crafted perf.data provides a size larger than 32 bits, the
> size is truncated when passed to do_read(), causing it to read fewer bytes.
> Since read_event_file() only checks if ret < 0 rather than verifying
> ret == size, it will pass the large, mostly uninitialized buffer to
> parse_event_file().
>
>> + }
>> +
>> buf[size++] = c;
>>
>> if (!c)
>
Thanks: this is exactly what patch 2 fixes. Widening do_read()'s
size to size_t removes the truncation, so it reads (or fails on)
the full 64-bit size.
do_read() either reads everything or returns -1, so callers only need to
check ret < 0, no ret == size check needed.
Thanks
Tanushree Shah
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2 1/2] perf trace-event: Fix buffer overflow in read_string()
[not found] ` <20260723095827.659211F000E9@smtp.kernel.org>
2026-07-24 10:17 ` [PATCH v2 1/2] perf trace-event: Fix buffer overflow in read_string() Tanushree Shah
@ 2026-07-24 10:18 ` Tanushree Shah
1 sibling, 0 replies; 3+ messages in thread
From: Tanushree Shah @ 2026-07-24 10:18 UTC (permalink / raw)
To: sashiko-reviews, Arnaldo Carvalho de Melo, Ian Rogers,
Namhyung Kim
Cc: linux-perf-users, atrajeev, Madhavan Srinivasan, hbathini
On 23/07/26 15:28, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
>
> Pre-existing issues:
> - [High] Error path in trace_report() leads to a double free of tevent->pevent when session initialization fails.
> - [High] Integer truncation in do_read() allows a malicious trace file to cause the parser to process uninitialized heap memory.
> --
>
> --- Patch [1]: [PATCH v2 1/2] perf trace-event: Fix buffer overflow in read_string() ---
> commit fca4f0cc3071265252215647838a7b30c9c0baaf
> Author: Tanushree Shah <tshah@linux.ibm.com>
>
> perf trace-event: Fix buffer overflow in read_string()
>
> This patch fixes a potential stack buffer overflow in read_string() by
> adding a bounds check before writing each byte into the buffer. If the
> string exceeds the buffer size, it logs a debug message and returns NULL,
> matching other error handling paths.
>
>> diff --git a/tools/perf/util/trace-event-read.c b/tools/perf/util/trace-event-read.c
>> index ecbbb93f01853..afd458cf1387d 100644
>> --- a/tools/perf/util/trace-event-read.c
>> +++ b/tools/perf/util/trace-event-read.c
>> @@ -127,6 +127,11 @@ static char *read_string(void)
>> }
>> }
>>
>> + if (size >= (int)sizeof(buf) - 1) {
>> + pr_debug("string too long (max %zu bytes)", sizeof(buf) - 1);
>> + goto out;
>
> [Severity: High]
> This is a pre-existing issue, but could this error path trigger a double free
> of tevent->pevent?
>
> If read_string() returns NULL due to the bounds check, the error propagates
> and trace_report() jumps to its error path:
>
> tools/perf/util/trace-event-read.c:trace_report() {
> ...
> out:
> if (pevent)
> trace_event__cleanup(tevent);
> ...
> }
>
> trace_event__cleanup() frees t->pevent but leaves the pointer dangling:
>
> tools/perf/util/trace-event.c:trace_event__cleanup() {
> ...
> tep_free(t->pevent);
> ...
> }
>
> Later, during session teardown, perf_session__delete() unconditionally calls
> trace_event__cleanup(&session->tevent), which will attempt to free the same
> pointer again.
Thanks for catching this. trace_report()'s error path and
perf_session__delete() can both call trace_event__cleanup() on the same
tevent, with nothing preventing a double-free.
Fix: clear t->pevent to NULL after cleanup, and guard the function to
no-op if it is already NULL, so a second call is safe.
>
> [Severity: High]
> This is also a pre-existing issue, but does do_read() suffer from an integer
> truncation issue?
>
> Functions like read_event_file() allocate memory based on a 64-bit size:
>
> tools/perf/util/trace-event-read.c:read_event_file() {
> ...
> ret = do_read(buf, size);
> if (ret < 0)
> goto out;
>
> ret = parse_event_file(pevent, buf, size, sys);
> ...
> }
>
> But do_read() takes size as an int:
>
> tools/perf/util/trace-event-read.c:do_read() {
> static int do_read(void *data, int size)
> ...
> }
>
> If a maliciously crafted perf.data provides a size larger than 32 bits, the
> size is truncated when passed to do_read(), causing it to read fewer bytes.
> Since read_event_file() only checks if ret < 0 rather than verifying
> ret == size, it will pass the large, mostly uninitialized buffer to
> parse_event_file().
>
>> + }
>> +
>> buf[size++] = c;
>>
>> if (!c)
>
Thanks: this is exactly what patch 2 fixes. Widening do_read()'s
size to size_t removes the truncation, so it reads (or fails on)
the full 64-bit size.
do_read() either reads everything or returns -1, so callers only need to
check ret < 0, no ret == size check needed.
Thanks
Tanushree Shah
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-24 10:18 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260723093958.416650-3-tshah@linux.ibm.com>
[not found] ` <20260723093958.416650-6-tshah@linux.ibm.com>
[not found] ` <20260723101054.09ABE1F000E9@smtp.kernel.org>
2026-07-24 8:40 ` [PATCH v2 2/2] perf trace-event: Fix integer truncation in do_read() and skip() Tanushree Shah
[not found] ` <20260723093958.416650-5-tshah@linux.ibm.com>
[not found] ` <20260723095827.659211F000E9@smtp.kernel.org>
2026-07-24 10:17 ` [PATCH v2 1/2] perf trace-event: Fix buffer overflow in read_string() Tanushree Shah
2026-07-24 10:18 ` Tanushree Shah
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox