* 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
[parent not found: <20260723093958.416650-5-tshah@linux.ibm.com>]
[parent not found: <20260723095827.659211F000E9@smtp.kernel.org>]
* 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