[PATCH 25/31] pahole: Guard pipe_seek() against negative offsets

Arnaldo Carvalho de Melo <[email protected]> Wed, 29 Jul 2026 16:07:25 -0300
Newsgroups org.kernel.vger.dwarves,org.kernel.vger.bpf
Message-ID <[email protected]>
From: Arnaldo Carvalho de Melo <[email protected]>

pipe_seek() simulates forward seeking on non-seekable streams by
reading and discarding chunks into a 4096-byte stack buffer.  When
called with a negative offset, 'chunk' gets a negative value that is
implicitly cast to a huge size_t by fread(), causing a stack buffer
overflow.

This can happen when a user passes a negative --seek_bytes value:
fseeko() fails (SEEK_SET rejects negatives), then the fallback calls
pipe_seek() with the unchecked negative value.

Guard against this by rejecting negative offsets at the top of
pipe_seek(), and short-circuit zero offsets as a no-op.

Before:

  $ echo | pahole --seek_bytes -1 --prettify - -C task_struct vmlinux
  (stack buffer overflow / crash)

After:

  $ echo | pahole --seek_bytes -1 --prettify - -C task_struct vmlinux
  Couldn't --seek_bytes -1 (18446744073709551615)

Uncovered during code coverage analysis work.

Fixes: fe284221448c950d ("pahole: Introduce --seek_bytes")
Reported-by: Sashiko:gemini-3.1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
 pahole.c | 58 ++++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 44 insertions(+), 14 deletions(-)

diff --git a/pahole.c b/pahole.c
index cceaf9030d442248..5cf92833e6e84df9 100644
--- a/pahole.c
+++ b/pahole.c
@@ -18,6 +18,7 @@
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <unistd.h>
 #include <bpf/btf.h>
 #include "bpf/libbpf.h"
@@ -2371,6 +2372,13 @@ static int pipe_seek(FILE *fp, off_t offset)
 	char bf[4096];
 	int chunk = sizeof(bf);
 
+	/* Negative offset would wrap chunk to a huge size_t in fread() */
+	if (offset < 0)
+		return -1;
+
+	if (offset == 0)
+		return 0;
+
 	if (chunk > offset)
 		chunk = offset;
 
@@ -2605,16 +2613,22 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
 	uint64_t size_bytes = ULLONG_MAX;
 	uint32_t count = 0;
 	uint32_t skip = conf.skip;
+	/* Bytes consumed from input so far, for absolute→relative seek on pipes */
+	off_t consumed = 0;
+	int64_t header_bytes;
 
 	if (instance == NULL)
 		return -ENOMEM;
 
+	/* Keep consumed in sync with bytes actually fread'd from input */
 	errno = 0;
-	if (type__instance_read_once(header, input) < 0) {
+	header_bytes = type__instance_read_once(header, input);
+	if (header_bytes < 0) {
 		printed = errno ? -errno : -EIO;
 		fprintf(stderr, "pahole: --header (%s) type couldn't be read\n", conf.header_type);
 		goto out;
 	}
+	consumed = header_bytes;
 
 	if (conf.range || prototype->range) {
 		off_t seek_bytes;
@@ -2662,21 +2676,25 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
 		 * for non-seekable streams (stdin, pipes).
 		 */
 		if (fseeko(input, seek_bytes, SEEK_SET) != 0) {
-			off_t total_read_bytes = ftell(input);
-
-			if (seek_bytes < total_read_bytes) {
-				fprintf(stderr, "pahole: can't seek backward in non-seekable input, already read %" PRIu64 " bytes, target %#" PRIx64 "\n",
-						total_read_bytes, seek_bytes);
+			if (seek_bytes < consumed) {
+				fprintf(stderr, "pahole: can't seek backward in non-seekable input, already read %" PRId64 " bytes, target %#" PRIx64 "\n",
+						(int64_t)consumed, seek_bytes);
 				printed = -ESPIPE;
 				goto out;
 			}
 
-			seek_bytes -= total_read_bytes;
-
 			errno = 0;
-			if (pipe_seek(input, seek_bytes) < 0) {
+			if (pipe_seek(input, seek_bytes - consumed) < 0) {
 				printed = errno ? -errno : -EIO;
-				fprintf(stderr, "Couldn't seek to offset %" PRIu64 " for range=%s\n", seek_bytes, range);
+				fprintf(stderr, "Couldn't seek to offset %" PRId64 " for range=%s\n", (int64_t)seek_bytes, range);
+				goto out;
+			}
+		} else {
+			struct stat sb;
+			if (fstat(fileno(input), &sb) == 0 && S_ISREG(sb.st_mode) && seek_bytes >= sb.st_size) {
+				fprintf(stderr, "pahole: seek offset %" PRId64 " is beyond file size %" PRId64 " for range=%s\n",
+						(int64_t)seek_bytes, (int64_t)sb.st_size, range);
+				printed = -EINVAL;
 				goto out;
 			}
 		}
@@ -2750,13 +2768,25 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
 		 * forward-reading pipe_seek() for pipes/stdin.
 		 */
 		if (fseeko(input, seek_bytes, SEEK_SET) != 0) {
-			if (header)
-				seek_bytes -= ftell(input);
+			if (seek_bytes < consumed) {
+				fprintf(stderr, "pahole: can't seek backward in non-seekable input, already read %" PRId64 " bytes, target %#" PRIx64 "\n",
+						(int64_t)consumed, seek_bytes);
+				printed = -ESPIPE;
+				goto out;
+			}
 
 			errno = 0;
-			if (pipe_seek(input, seek_bytes) < 0) {
+			if (pipe_seek(input, seek_bytes - consumed) < 0) {
 				printed = errno ? -errno : -EIO;
-				fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 ")\n", conf.seek_bytes, seek_bytes);
+				fprintf(stderr, "Couldn't --seek_bytes %s (%" PRId64 ")\n", conf.seek_bytes, (int64_t)seek_bytes);
+				goto out;
+			}
+		} else {
+			struct stat sb;
+			if (fstat(fileno(input), &sb) == 0 && S_ISREG(sb.st_mode) && seek_bytes >= sb.st_size) {
+				fprintf(stderr, "pahole: --seek_bytes %" PRId64 " is beyond file size %" PRId64 "\n",
+						(int64_t)seek_bytes, (int64_t)sb.st_size);
+				printed = -EINVAL;
 				goto out;
 			}
 		}
-- 
2.55.0