[PATCH 14/31] btf_encoder, libctf: Add elf_strptr NULL checks and fix kfunc bounds

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

Several elf_strptr() calls in btf_encoder and libctf lacked NULL checks,
which would cause segfaults on malformed ELF files:

1. btf_encoder__new(): strcmp(secname, PERCPU_SECTION) crashes if
   elf_section_by_idx() returns a valid section but elf_strptr() fails
   internally.

2. btf_encoder__collect_kfuncs(): two elf_strptr() calls for symbol
   names passed to strstarts()/get_func_name() without NULL guards.

3. libctf.c ctf__encode(): strcmp(secname, ".SUNW_ctf") without
   NULL check.

Also fix is_sym_kfunc_set() bounds check: the original `off >= d_size`
only verified the start offset, but accessing set->flags could read
past the buffer.  Changed to `off + sizeof(*set) > d_size` and added
an `off < 0` guard to prevent signed-to-unsigned wraparound when the
symbol address is below the section base.

Before: malformed ELF with invalid string table causes SIGSEGV
After: gracefully skips bad entries

Fixes: 72e88f29c6f7e142 ("pahole: Inject kfunc decl tags into BTF")
Fixes: ff34e733a0c23bf4 ("btf_encoder: Allow encoding VARs from many sections")
Fixes: dcef613288086156 ("libctf: give up "for now" on using libelf to add a section to an existing file")
Reported-by: Sashiko:gemini-3-1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
 btf_encoder.c | 10 ++++++----
 libctf.c      |  2 +-
 pahole.c      | 14 +++++++++++---
 3 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/btf_encoder.c b/btf_encoder.c
index 5c12e79f5ef648ba..c7b71b5b741bfa6f 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -2099,14 +2099,14 @@ static int is_sym_kfunc_set(GElf_Sym *sym, const char *name, Elf_Data *idlist, s
 {
 	void *ptr = idlist->d_buf;
 	struct btf_id_set8 *set;
-	size_t off;
+	ptrdiff_t off;
 
 	/* kfuncs are only found in BTF_SET8's */
 	if (!strstarts(name, BTF_ID_SET8_PFX))
 		return false;
 
 	off = sym->st_value - idlist_addr;
-	if (off >= idlist->d_size) {
+	if (off < 0 || (size_t)off + sizeof(*set) > idlist->d_size) {
 		fprintf(stderr, "%s: symbol '%s' out of bounds\n", __func__, name);
 		return false;
 	}
@@ -2276,7 +2276,7 @@ static int btf_encoder__collect_kfuncs(struct btf_encoder *encoder)
 			continue;
 
 		name = elf_strptr(elf, strtabidx, sym.st_name);
-		if (!is_sym_kfunc_set(&sym, name, idlist, idlist_addr))
+		if (name == NULL || !is_sym_kfunc_set(&sym, name, idlist, idlist_addr))
 			continue;
 
 		range.start = sym.st_value;
@@ -2305,6 +2305,8 @@ static int btf_encoder__collect_kfuncs(struct btf_encoder *encoder)
 			continue;
 
 		name = elf_strptr(elf, strtabidx, sym.st_name);
+		if (name == NULL)
+			continue;
 		func = get_func_name(name);
 		if (!func)
 			continue;
@@ -2879,7 +2881,7 @@ struct btf_encoder *btf_encoder__new(struct cu *cu, const char *detached_filenam
 			if (encoder->encode_vars & BTF_VAR_GLOBAL)
 				encoder->secinfo[shndx].include = true;
 
-			if (strcmp(secname, PERCPU_SECTION) == 0) {
+			if (secname != NULL && strcmp(secname, PERCPU_SECTION) == 0) {
 				found_percpu = true;
 				if (encoder->encode_vars & BTF_VAR_PERCPU)
 					encoder->secinfo[shndx].include = true;
diff --git a/libctf.c b/libctf.c
index 8e31e3d550ebd51a..72f9949a2d25b3d9 100644
--- a/libctf.c
+++ b/libctf.c
@@ -674,7 +674,7 @@ int ctf__encode(struct ctf *ctf, uint8_t flags)
 		if (shdr == NULL)
 			continue;
 		char *secname = elf_strptr(elf, strndx, shdr->sh_name);
-		if (strcmp(secname, ".SUNW_ctf") == 0) {
+		if (secname != NULL && strcmp(secname, ".SUNW_ctf") == 0) {
 			data = elf_getdata(scn, data);
 			goto out_update;
 		}
diff --git a/pahole.c b/pahole.c
index 6ba3f578c28570a1..0e2acc35d6d679f0 100644
--- a/pahole.c
+++ b/pahole.c
@@ -2361,6 +2361,11 @@ static int pipe_seek(FILE *fp, off_t offset)
 			chunk = offset;
 	}
 
+	/* On EOF (not I/O error), clear errno so callers don't
+	 * pick up a stale value from an earlier successful fread. */
+	if (!ferror(fp))
+		errno = 0;
+
 	return offset == 0 ? 0 : -1;
 }
 
@@ -2583,8 +2588,9 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
 	if (instance == NULL)
 		return -ENOMEM;
 
+	errno = 0;
 	if (type__instance_read_once(header, input) < 0) {
-		printed = -errno;
+		printed = errno ? -errno : -EIO;
 		fprintf(stderr, "pahole: --header (%s) type couldn't be read\n", conf.header_type);
 		goto out;
 	}
@@ -2666,8 +2672,9 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
 
 		free(member_name);
 
+		errno = 0;
 		if (pipe_seek(input, seek_bytes) < 0) {
-			printed = -errno;
+			printed = errno ? -errno : -EIO;
 			fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
 			goto out;
 		}
@@ -2717,8 +2724,9 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
 			seek_bytes -= ftell(input);
 		}
 
+		errno = 0;
 		if (pipe_seek(input, seek_bytes) < 0) {
-			printed = -errno;
+			printed = errno ? -errno : -EIO;
 			fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
 			goto out;
 		}
-- 
2.55.0