* [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file
@ 2026-10-19 0:02 Leizhen Zhang
2026-10-19 0:02 ` [PATCH 1/5] elf-parse: avoid an undefined shift when checking the ELF type Leizhen Zhang
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: Leizhen Zhang @ 2026-10-19 0:02 UTC (permalink / raw)
To: nathan, nsc; +Cc: linux-kbuild, linux-kernel, rostedt, ljs, Leizhen Zhang
scripts/elf-parse.c takes the section header table, the section indices and
the symbol table straight out of the file it maps, without checking any of
them against the mapping. All three users -- kallsyms, sorttable and
tracepoint-update -- then index with those values, so a truncated or
corrupted object file makes the tools read outside the mapping, allocate
from a file-supplied count, or divide by zero instead of printing an error.
These are build-time tools fed by the local toolchain, so under
Documentation/process/threat-model.rst this looks like a robustness problem
rather than a vulnerability: reaching it takes a corrupt or hostile object
in the build tree. I am leaving that classification for you to review.
How this was found: the three tools were built with
-fsanitize=address,undefined and driven by a mutational fuzzer that pokes
ELF structure fields (ELF header, section headers, symbol entries) in
otherwise valid objects. Every crash was then reduced to a reproducer that
changes exactly one field of a valid file, which is what each patch quotes.
1/5 e_type >= 32 shifts by at least the width of int (UB). Hits all three
tools, and is in mainline as well as -next.
2/5 nothing checks that the file is long enough for its ELF header, or
that e_shoff/e_shnum/e_shstrndx describe a table inside the mapping.
Also mainline.
3/5 elf_open_ro() resolves a count and a name index through section 0 and
a string table through sh_link, all unchecked. -next only.
4/5 a zero sh_entsize divides by zero, and an sh_size past the end of the
file yields an unbounded symbol count which the caller allocates.
-next only.
5/5 st_name is unbounded, so one symbol can point its name anywhere.
-next only.
Verification, per patch, on the reproducer it quotes and on a valid vmlinux,
a shared object and a .o: every reproducer now prints one line and exits 1,
and the three tools accept the valid objects unchanged. After the series the
fuzzer no longer reaches any of these paths (3420 kallsyms, 2149 sorttable
and 2594 tracepoint-update iterations). gcc -Wall -Wextra is clean on the
changed files, and checkpatch --strict reports nothing except CamelCase
complaints about the standard ELF type names.
What is NOT fixed, and what I could not do:
- An unaligned symbol-table sh_offset makes elf_symbol() form a misaligned
Elf_Sym pointer, which UBSan reports at scripts/elf-parse.h:233. Checking
alignment here would reject legitimate ELF32 objects, whose symbol tables
need 4-byte alignment while the Elf_Sym union wants 8, so this wants the
accessors to read unaligned instead. Patch 4/5 says so in its message.
- Reading e_shoff itself through get_unaligned_le32() is UB on a byte with
the top bit set. That is a separate bug in
tools/include/tools/le_byteshift.h which I have a patch pending for
("tools/include: fix signed shift overflow in 32-bit unaligned
accessors"). Until it lands, the e_shoff reproducer in 2/5 stops at the
byteshift report first; with it applied the behaviour is as described.
- sorttable.c and tracepoint-update.c read *section* names the same
unbounded way through the elf_map() path. Both crash, both have a
one-field reproducer, and both are sent as separate patches to their own
maintainers rather than buried in this series.
- The series is based on next-20261002, the newest linux-next I could fetch
here. Patches 1 and 2 apply to mainline as well.
- Per Documentation/process/coding-assistants.rst each patch carries an
Assisted-by tag recording that an LLM found and drafted it, along with
the sanitizers used. The Signed-off-by is mine: I reviewed the analysis
and the code.
Leizhen Zhang (5):
elf-parse: avoid an undefined shift when checking the ELF type
elf-parse: check that the section header table is inside the file
elf-parse: check the section indices that elf_open_ro() takes from the
file
elf-parse: check the symbol table before walking it
elf-parse: bound symbol names by the string table
scripts/elf-parse.c | 125 +++++++++++++++++++++++++++++++++++---
scripts/elf-parse.h | 14 +++++
scripts/kallsyms-sysmap.c | 4 +-
3 files changed, 133 insertions(+), 10 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 1/5] elf-parse: avoid an undefined shift when checking the ELF type
2026-10-19 0:02 [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Leizhen Zhang
@ 2026-10-19 0:02 ` Leizhen Zhang
2026-10-19 12:47 ` Steven Rostedt
2026-10-19 0:02 ` [PATCH 2/5] elf-parse: check that the section header table is inside the file Leizhen Zhang
` (4 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Leizhen Zhang @ 2026-10-19 0:02 UTC (permalink / raw)
To: nathan, nsc; +Cc: linux-kbuild, linux-kernel, rostedt, ljs, Leizhen Zhang
elf_parse() takes a bitmap of permitted ELF types and tests it with
type = elf_parser.r2(&ehdr->e32.e_type);
if (!((1 << type) & types)) {
e_type is a 16-bit field read straight out of the file, so a type of 32
or more shifts by at least the width of int and the result is undefined;
31 shifts into the sign bit. All three users of the parser -- kallsyms,
sorttable and tracepoint-update -- reach this with e_type unvalidated.
Found by fuzzing the host ELF tools with mutated object files under
ASan/UBSan. Reproducer, on any vmlinux (or any ET_REL object for
tracepoint-update), setting only e_type:
$ python3 -c 'import struct
d = bytearray(open("vmlinux","rb").read())
struct.pack_into("<H", d, 0x10, 40)
open("t.elf","wb").write(bytes(d))'
$ ./kallsyms --sysmap=/dev/null t.elf
scripts/elf-parse.c:94:11: runtime error: shift exponent 40 is too
large for 32-bit type 'int'
#0 in elf_parse scripts/elf-parse.c:94
(host tools built with -fsanitize=address,undefined)
Reject a type that cannot be in the bitmap, and shift an unsigned 1 so
that a type of 31 stays defined. No valid object changes behaviour: every
ET_* value the callers put in the bitmap is far below 32, and a file with
a larger e_type already took the error path -- it just took it through
undefined behaviour.
Fixes: b055f4c431e3 ("sorttable: Move ELF parsing into scripts/elf-parse.[ch]")
Assisted-by: LLM ASan UBSan
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
scripts/elf-parse.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c
index ce1c2a670244..9e7d1a4f263f 100644
--- a/scripts/elf-parse.c
+++ b/scripts/elf-parse.c
@@ -91,7 +91,8 @@ static int elf_parse(const char *fname, void *addr, uint32_t types)
}
type = elf_parser.r2(&ehdr->e32.e_type);
- if (!((1 << type) & types)) {
+ /* e_type comes from the file and the bitmap only has 32 slots. */
+ if (type >= 32 || !((1U << type) & types)) {
fprintf(stderr, "Invalid ELF type file %s\n", fname);
return -1;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 2/5] elf-parse: check that the section header table is inside the file
2026-10-19 0:02 [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Leizhen Zhang
2026-10-19 0:02 ` [PATCH 1/5] elf-parse: avoid an undefined shift when checking the ELF type Leizhen Zhang
@ 2026-10-19 0:02 ` Leizhen Zhang
2026-10-19 12:55 ` Steven Rostedt
2026-10-20 0:03 ` sashiko-bot
2026-10-19 0:02 ` [PATCH 3/5] elf-parse: check the section indices that elf_open_ro() takes from " Leizhen Zhang
` (3 subsequent siblings)
5 siblings, 2 replies; 11+ messages in thread
From: Leizhen Zhang @ 2026-10-19 0:02 UTC (permalink / raw)
To: nathan, nsc; +Cc: linux-kbuild, linux-kernel, rostedt, ljs, Leizhen Zhang
elf_parse() validates e_ehsize and e_shentsize, and nothing else. The
section header table that every caller then walks is described entirely
by unchecked file data, so a truncated or corrupted object makes the
parser read outside the mapping:
- a file shorter than one ELF header is read past its end, since e_type
at offset 16 and e_ehsize at 52/54 are read before any length check;
- e_shoff may point anywhere, including far past the end of the file;
- e_shnum may claim more sections than the file can hold;
- e_shstrndx may name a section that does not exist.
elf_map() hands the mapping to sorttable and tracepoint-update, which
index the table directly, and elf_open_ro() does the same for kallsyms,
so all three tools fault on the same inputs.
Found by fuzzing the host ELF tools with mutated object files under
ASan/UBSan. Each reproducer below changes one field of an otherwise
valid file; the sanitizer output is from host tools built with
-fsanitize=address,undefined:
e_shoff = filesize + 0x1000 (offset 0x28)
./kallsyms --sysmap=/dev/null t.elf
ERROR: AddressSanitizer: unknown-crash on address 0x7f914e32cbdc
#0 in __get_unaligned_le32 tools/include/tools/le_byteshift.h:14
(reading sh_type of section 0)
./sorttable t.elf -- same
e_shnum = 0xffff (offset 0x3c)
./sorttable t.elf
ERROR: AddressSanitizer: unknown-crash on address 0x7f64023b8000
#0 in __get_unaligned_le32 tools/include/tools/le_byteshift.h:14
./tracepoint-update t.o
ERROR: AddressSanitizer: SEGV on unknown address 0x7f9fc264db28
#0 in strcmp
e_shstrndx = 0xfff0 (offset 0x3e)
./tracepoint-update t.o
ERROR: AddressSanitizer: SEGV on unknown address 0x7f0f83b29544
#0 in __get_unaligned_le32 tools/include/tools/le_byteshift.h:14
Pass the mapped size down to elf_parse() and reject a file that cannot
hold its own ELF header, a section header table that does not fit in the
mapping, and a section name index outside the table. The division keeps
the extent check free of overflow. A count of SHN_UNDEF means the real
count lives in section 0, so one entry must still be present; resolving
that count, and the SHN_XINDEX name index, stays with elf_open_ro().
The three tools keep accepting the objects they accepted before: a
vmlinux, a shared object and a .o built from the same source are parsed
unchanged, and the reproducers above now print one line and exit 1.
Fixes: b055f4c431e3 ("sorttable: Move ELF parsing into scripts/elf-parse.[ch]")
Assisted-by: LLM ASan UBSan
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
scripts/elf-parse.c | 44 ++++++++++++++++++++++++++++++++++++++++----
1 file changed, 40 insertions(+), 4 deletions(-)
diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c
index 9e7d1a4f263f..0e16a9cf67f4 100644
--- a/scripts/elf-parse.c
+++ b/scripts/elf-parse.c
@@ -58,10 +58,17 @@ static void *map_file(char const *fname, size_t *size, bool writable)
return addr;
}
-static int elf_parse(const char *fname, void *addr, uint32_t types)
+static int elf_parse(const char *fname, void *addr, size_t size, uint32_t types)
{
Elf_Ehdr *ehdr = addr;
- uint16_t type;
+ size_t shoff, shentsize, shnum;
+ uint16_t type, shstrndx;
+
+ /* Every field read below lives in the smaller of the two headers. */
+ if (size < sizeof(Elf32_Ehdr)) {
+ fprintf(stderr, "ELF file too short: %s\n", fname);
+ return -1;
+ }
if (memcmp(ELFMAG, ehdr->e32.e_ident, SELFMAG) != 0 ||
ehdr->e32.e_ident[EI_VERSION] != EV_CURRENT) {
@@ -153,7 +160,8 @@ static int elf_parse(const char *fname, void *addr, uint32_t types)
elf_parser.rela_addend = rela64_addend;
elf_parser.rela_write_addend = rela64_write_addend;
- if (elf_parser.r2(&ehdr->e64.e_ehsize) != sizeof(Elf64_Ehdr) ||
+ if (size < sizeof(Elf64_Ehdr) ||
+ elf_parser.r2(&ehdr->e64.e_ehsize) != sizeof(Elf64_Ehdr) ||
elf_parser.r2(&ehdr->e64.e_shentsize) != sizeof(Elf64_Shdr)) {
fprintf(stderr,
"unrecognized ET_EXEC/ET_DYN file: %s\n",
@@ -168,6 +176,34 @@ static int elf_parse(const char *fname, void *addr, uint32_t types)
ehdr->e32.e_ident[EI_CLASS], fname);
return -1;
}
+
+ /*
+ * Everything past here walks the section headers with offsets and
+ * indices taken from the file, so the table the ELF header describes
+ * has to be inside the mapping first. e_shnum == 0 means the real
+ * count lives in section 0, which must then be present to be read.
+ */
+ shoff = ehdr_shoff(ehdr);
+ shentsize = ehdr_shentsize(ehdr);
+ shnum = ehdr_shnum(ehdr);
+ if (shnum == SHN_UNDEF)
+ shnum = 1;
+ if (shoff > size || shentsize == 0 ||
+ (size - shoff) / shentsize < shnum) {
+ fprintf(stderr, "ELF section headers out of range: %s\n",
+ fname);
+ return -1;
+ }
+
+ /* SHN_XINDEX defers the index to section 0; elf_open_ro() checks it. */
+ shstrndx = ehdr_shstrndx(ehdr);
+ if (shstrndx != SHN_XINDEX && ehdr_shnum(ehdr) != SHN_UNDEF &&
+ shstrndx >= ehdr_shnum(ehdr)) {
+ fprintf(stderr, "ELF section name index out of range: %s\n",
+ fname);
+ return -1;
+ }
+
return 0;
}
@@ -195,7 +231,7 @@ static void *__elf_map(char const *fname, size_t *size, uint32_t types,
if (!addr)
return NULL;
- ret = elf_parse(fname, addr, types);
+ ret = elf_parse(fname, addr, *size, types);
if (ret < 0) {
elf_unmap(addr, *size);
return NULL;
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 3/5] elf-parse: check the section indices that elf_open_ro() takes from the file
2026-10-19 0:02 [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Leizhen Zhang
2026-10-19 0:02 ` [PATCH 1/5] elf-parse: avoid an undefined shift when checking the ELF type Leizhen Zhang
2026-10-19 0:02 ` [PATCH 2/5] elf-parse: check that the section header table is inside the file Leizhen Zhang
@ 2026-10-19 0:02 ` Leizhen Zhang
2026-10-19 0:02 ` [PATCH 4/5] elf-parse: check the symbol table before walking it Leizhen Zhang
` (2 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Leizhen Zhang @ 2026-10-19 0:02 UTC (permalink / raw)
To: nathan, nsc; +Cc: linux-kbuild, linux-kernel, rostedt, ljs, Leizhen Zhang
elf_open_ro() resolves two values that the ELF header can delegate to
section 0 -- the section count when e_shnum is SHN_UNDEF, and the section
name index when e_shstrndx is SHN_XINDEX -- and then looks up the string
table of the symbol table through its sh_link. None of the three is
checked, so each one indexes the section header table with a value taken
straight from the file.
Found by fuzzing the host ELF tools with mutated object files under
ASan/UBSan. Setting only the sh_link of the symbol table in an otherwise
valid vmlinux (the strtab index it resolves):
./kallsyms --sysmap=/dev/null t.elf
ERROR: AddressSanitizer: SEGV on unknown address
#0 in strncmp
#1 in sysmap_omits scripts/kallsyms-sysmap.c
-- elf->strtab points outside the mapping, and every symbol name is
then read through it.
With the preceding patch in place the header-supplied count and index
are already rejected before this point; the values that come out of
section 0 are not, which is what this adds, together with the sh_link of
the symbol table. elf_shnum_out_of_range() repeats the extent check from
elf_parse() against the count actually in use.
The reproducer now prints "symbol table has no string table" and exits
1, and a vmlinux and a shared object are still read unchanged.
This code is only in linux-next, from the "kbuild: significantly speed
up kernel builds" series, so there is no mainline commit to reference in
a Fixes tag.
Assisted-by: LLM ASan UBSan
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
scripts/elf-parse.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c
index 0e16a9cf67f4..39ac25ddf188 100644
--- a/scripts/elf-parse.c
+++ b/scripts/elf-parse.c
@@ -256,6 +256,21 @@ void elf_unmap(void *addr, size_t size)
munmap(addr, size);
}
+/*
+ * True when the section header count does not fit in the mapping. The count
+ * can come from section 0, so it is not covered by the header check in
+ * elf_parse().
+ */
+static bool elf_shnum_out_of_range(const struct elf_file *elf)
+{
+ size_t shoff = (size_t)(elf->shdrs - (const char *)elf->base);
+
+ if (!elf->shnum || !elf->shentsize || shoff > elf->size)
+ return true;
+
+ return (elf->size - shoff) / elf->shentsize < elf->shnum;
+}
+
/*
* Open an ELF file and map it read-only, locating its section headers, section
* names and symbol table which are populated in the elf out parameter.
@@ -285,6 +300,17 @@ int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf)
shstrndx = ehdr_shstrndx(ehdr);
if (shstrndx == SHN_XINDEX)
shstrndx = shdr_link(first);
+
+ /*
+ * elf_parse() checked the count and the name index carried by the ELF
+ * header, but both can instead come from section 0, and that index is
+ * unchecked. Recheck whatever was actually used.
+ */
+ if (elf_shnum_out_of_range(elf) || shstrndx >= elf->shnum) {
+ fprintf(stderr, "%s: ELF section headers out of range\n", fname);
+ elf_close(elf);
+ return -1;
+ }
elf->shstrtab = (const char *)elf->base +
shdr_offset(elf_section(elf, shstrndx));
@@ -298,6 +324,11 @@ int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf)
return -1;
}
+ if (shdr_link(elf->symtab) >= elf->shnum) {
+ fprintf(stderr, "%s: symbol table has no string table\n", fname);
+ elf_close(elf);
+ return -1;
+ }
elf->strtab = (const char *)elf->base +
shdr_offset(elf_section(elf, shdr_link(elf->symtab)));
elf->nr_syms = shdr_size(elf->symtab) / shdr_entsize(elf->symtab);
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 4/5] elf-parse: check the symbol table before walking it
2026-10-19 0:02 [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Leizhen Zhang
` (2 preceding siblings ...)
2026-10-19 0:02 ` [PATCH 3/5] elf-parse: check the section indices that elf_open_ro() takes from " Leizhen Zhang
@ 2026-10-19 0:02 ` Leizhen Zhang
2026-10-19 0:02 ` [PATCH 5/5] elf-parse: bound symbol names by the string table Leizhen Zhang
2026-10-19 12:57 ` [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Steven Rostedt
5 siblings, 0 replies; 11+ messages in thread
From: Leizhen Zhang @ 2026-10-19 0:02 UTC (permalink / raw)
To: nathan, nsc; +Cc: linux-kbuild, linux-kernel, rostedt, ljs, Leizhen Zhang
elf_open_ro() ends with
elf->nr_syms = shdr_size(elf->symtab) / shdr_entsize(elf->symtab);
on a section header taken from the file. A zero sh_entsize divides by
zero, and an sh_size that runs past the end of the mapping produces a
symbol count bounded by nothing: scripts/kallsyms-sysmap.c allocates
nr_syms entries up front and then reads every one of them.
Found by fuzzing the host ELF tools with mutated object files under
ASan/UBSan. Both reproducers change one field of the symbol table's
section header in an otherwise valid vmlinux:
sh_entsize = 0
./kallsyms --sysmap=/dev/null t.elf
scripts/elf-parse.c:266:40: runtime error: division by zero
#0 in elf_open_ro scripts/elf-parse.c:266
sh_size = 0x7fffffff0000
./kallsyms --sysmap=/dev/null t.elf
ERROR: AddressSanitizer: requested allocation size 0x7ffffffefff0
exceeds maximum supported size
#0 in malloc
#1 in xmalloc scripts/include/xalloc.h
#2 in elf_read_symbols scripts/kallsyms-sysmap.c:195
Require an entry size that can hold a symbol of the file's class, and a
table that lies inside the mapping. Both reproducers then print "symbol
table out of range" and exit 1, while a vmlinux and a shared object are
read unchanged.
One UBSan report in this area is deliberately not addressed here: an
unaligned sh_offset makes elf_symbol() form a misaligned Elf_Sym
pointer, which the header's accessors then dereference. Rejecting it on
alignment would also reject legitimate ELF32 objects, whose symbol
tables need only 4-byte alignment while the Elf_Sym union requires 8, so
that one needs the accessors to read unaligned instead of a check here.
This code is only in linux-next, from the "kbuild: significantly speed
up kernel builds" series, so there is no mainline commit to reference in
a Fixes tag.
Assisted-by: LLM ASan UBSan
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
scripts/elf-parse.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c
index 39ac25ddf188..756843c433ee 100644
--- a/scripts/elf-parse.c
+++ b/scripts/elf-parse.c
@@ -271,6 +271,26 @@ static bool elf_shnum_out_of_range(const struct elf_file *elf)
return (elf->size - shoff) / elf->shentsize < elf->shnum;
}
+/*
+ * True when the symbol table cannot be walked inside the mapping: an entry
+ * size too small to hold a symbol divides by zero (or steps through the file
+ * in the wrong units) below, and a table running past the end of the file is
+ * read out of bounds -- its size also drives the allocation in the callers.
+ */
+static bool elf_symtab_out_of_range(const struct elf_file *elf)
+{
+ size_t offset = shdr_offset(elf->symtab);
+ size_t size = shdr_size(elf->symtab);
+ size_t entsize = shdr_entsize(elf->symtab);
+ size_t minsize = elf_map_long_size(elf->base) == 4 ?
+ sizeof(Elf32_Sym) : sizeof(Elf64_Sym);
+
+ if (entsize < minsize)
+ return true;
+
+ return offset > elf->size || elf->size - offset < size;
+}
+
/*
* Open an ELF file and map it read-only, locating its section headers, section
* names and symbol table which are populated in the elf out parameter.
@@ -324,6 +344,12 @@ int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf)
return -1;
}
+ if (elf_symtab_out_of_range(elf)) {
+ fprintf(stderr, "%s: symbol table out of range\n", fname);
+ elf_close(elf);
+ return -1;
+ }
+
if (shdr_link(elf->symtab) >= elf->shnum) {
fprintf(stderr, "%s: symbol table has no string table\n", fname);
elf_close(elf);
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 5/5] elf-parse: bound symbol names by the string table
2026-10-19 0:02 [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Leizhen Zhang
` (3 preceding siblings ...)
2026-10-19 0:02 ` [PATCH 4/5] elf-parse: check the symbol table before walking it Leizhen Zhang
@ 2026-10-19 0:02 ` Leizhen Zhang
2026-10-20 0:03 ` sashiko-bot
2026-10-19 12:57 ` [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Steven Rostedt
5 siblings, 1 reply; 11+ messages in thread
From: Leizhen Zhang @ 2026-10-19 0:02 UTC (permalink / raw)
To: nathan, nsc; +Cc: linux-kbuild, linux-kernel, rostedt, ljs, Leizhen Zhang
elf_read_symbols() builds every name as
const char *name = elf->strtab + sym_name(sym);
st_name is a 32-bit field read from the file and nothing bounds it, so a
single symbol can point the name anywhere, and sysmap_omits() then reads
a string from there.
Found by fuzzing the host ELF tools with mutated object files under
ASan/UBSan. Reproducer, setting only st_name of one object symbol in an
otherwise valid vmlinux (symbol 2 of its symbol table here, the first
one whose type makes the name be read):
./kallsyms --sysmap=/dev/null t.elf
ERROR: AddressSanitizer: SEGV on unknown address 0x7f54e308c740
#0 in strncmp
#1 in string_starts_with scripts/kallsyms.h:15
#2 in sysmap_omits scripts/kallsyms-sysmap.c:104
Record the size of the string table in struct elf_file and check, in
elf_open_ro(), that the table is inside the mapping and ends in a NUL.
A name that starts inside such a table also ends inside it, so a single
offset comparison is then enough, which elf_symbol_name() does; symbols
whose name is out of range are skipped like the other uninteresting ones
rather than failing the build, keeping the behaviour for every object a
toolchain actually produces identical.
scripts/sorttable.c reads symbol names the same way from its own
mapping, through elf_map() rather than elf_open_ro(). That path looks to
have the same gap, but every input I built was rejected earlier for
missing ORC tables, so I have no reproducer for it and have left it
alone rather than guess.
This code is only in linux-next, from the "kbuild: significantly speed
up kernel builds" series, so there is no mainline commit to reference in
a Fixes tag.
Assisted-by: LLM ASan UBSan
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
scripts/elf-parse.c | 21 ++++++++++++++++++---
scripts/elf-parse.h | 14 ++++++++++++++
scripts/kallsyms-sysmap.c | 4 ++--
3 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c
index 756843c433ee..8083b9ec3477 100644
--- a/scripts/elf-parse.c
+++ b/scripts/elf-parse.c
@@ -300,7 +300,8 @@ static bool elf_symtab_out_of_range(const struct elf_file *elf)
int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf)
{
Elf_Ehdr *ehdr;
- Elf_Shdr *first;
+ Elf_Shdr *first, *strtab;
+ size_t stroff;
unsigned int shstrndx, i;
memset(elf, 0, sizeof(*elf));
@@ -355,8 +356,22 @@ int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf)
elf_close(elf);
return -1;
}
- elf->strtab = (const char *)elf->base +
- shdr_offset(elf_section(elf, shdr_link(elf->symtab)));
+ strtab = elf_section(elf, shdr_link(elf->symtab));
+ stroff = shdr_offset(strtab);
+ elf->strtab_size = shdr_size(strtab);
+ elf->strtab = (const char *)elf->base + stroff;
+ /*
+ * Symbol names are read by offset into this table, so it has to be
+ * inside the mapping and has to end in a NUL for a name that starts
+ * inside it to end inside it as well.
+ */
+ if (stroff > elf->size || elf->size - stroff < elf->strtab_size ||
+ !elf->strtab_size || elf->strtab[elf->strtab_size - 1]) {
+ fprintf(stderr, "%s: symbol string table out of range\n",
+ fname);
+ elf_close(elf);
+ return -1;
+ }
elf->nr_syms = shdr_size(elf->symtab) / shdr_entsize(elf->symtab);
return 0;
diff --git a/scripts/elf-parse.h b/scripts/elf-parse.h
index c5ad754da254..548c80ead2bf 100644
--- a/scripts/elf-parse.h
+++ b/scripts/elf-parse.h
@@ -330,6 +330,7 @@ struct elf_file {
const char *shstrtab;
Elf_Shdr *symtab;
const char *strtab;
+ size_t strtab_size;
size_t nr_syms;
};
@@ -356,4 +357,17 @@ static inline Elf_Sym *elf_symbol(const struct elf_file *elf, size_t index)
index * shdr_entsize(elf->symtab));
}
+/*
+ * The name of a symbol, or NULL when st_name points outside the string table.
+ * elf_open_ro() has checked that the table is inside the mapping and ends in a
+ * NUL, so a name that starts inside it also ends inside it.
+ */
+static inline const char *elf_symbol_name(const struct elf_file *elf,
+ Elf_Sym *sym)
+{
+ size_t offset = sym_name(sym);
+
+ return offset < elf->strtab_size ? elf->strtab + offset : NULL;
+}
+
#endif /* _SCRIPTS_ELF_PARSE_H */
diff --git a/scripts/kallsyms-sysmap.c b/scripts/kallsyms-sysmap.c
index a482968eba51..efca13ab9dc3 100644
--- a/scripts/kallsyms-sysmap.c
+++ b/scripts/kallsyms-sysmap.c
@@ -198,10 +198,10 @@ static struct sysmap_symbol *elf_read_symbols(const struct elf_file *elf,
for (i = 1; i < elf->nr_syms; i++) {
Elf_Sym *sym = elf_symbol(elf, i);
- const char *name = elf->strtab + sym_name(sym);
+ const char *name = elf_symbol_name(elf, sym);
char type = elf_symbol_type(elf, sym);
- if (!type || sysmap_omits(name, type))
+ if (!name || !type || sysmap_omits(name, type))
continue;
syms[n].addr = sym_value(sym);
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 1/5] elf-parse: avoid an undefined shift when checking the ELF type
2026-10-19 0:02 ` [PATCH 1/5] elf-parse: avoid an undefined shift when checking the ELF type Leizhen Zhang
@ 2026-10-19 12:47 ` Steven Rostedt
0 siblings, 0 replies; 11+ messages in thread
From: Steven Rostedt @ 2026-10-19 12:47 UTC (permalink / raw)
To: Leizhen Zhang; +Cc: nathan, nsc, linux-kbuild, linux-kernel, ljs
On Sun, 18 Oct 2026 19:02:14 -0500
Leizhen Zhang <lzsx618@gmail.com> wrote:
> elf_parse() takes a bitmap of permitted ELF types and tests it with
>
> type = elf_parser.r2(&ehdr->e32.e_type);
> if (!((1 << type) & types)) {
>
> e_type is a 16-bit field read straight out of the file, so a type of 32
> or more shifts by at least the width of int and the result is undefined;
> 31 shifts into the sign bit. All three users of the parser -- kallsyms,
> sorttable and tracepoint-update -- reach this with e_type unvalidated.
>
> Found by fuzzing the host ELF tools with mutated object files under
> ASan/UBSan. Reproducer, on any vmlinux (or any ET_REL object for
> tracepoint-update), setting only e_type:
>
> $ python3 -c 'import struct
> d = bytearray(open("vmlinux","rb").read())
> struct.pack_into("<H", d, 0x10, 40)
> open("t.elf","wb").write(bytes(d))'
>
> $ ./kallsyms --sysmap=/dev/null t.elf
> scripts/elf-parse.c:94:11: runtime error: shift exponent 40 is too
> large for 32-bit type 'int'
> #0 in elf_parse scripts/elf-parse.c:94
>
> (host tools built with -fsanitize=address,undefined)
>
> Reject a type that cannot be in the bitmap, and shift an unsigned 1 so
> that a type of 31 stays defined. No valid object changes behaviour: every
> ET_* value the callers put in the bitmap is far below 32, and a file with
> a larger e_type already took the error path -- it just took it through
> undefined behaviour.
>
> Fixes: b055f4c431e3 ("sorttable: Move ELF parsing into scripts/elf-parse.[ch]")
> Assisted-by: LLM ASan UBSan
> Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
> ---
> scripts/elf-parse.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c
> index ce1c2a670244..9e7d1a4f263f 100644
> --- a/scripts/elf-parse.c
> +++ b/scripts/elf-parse.c
> @@ -91,7 +91,8 @@ static int elf_parse(const char *fname, void *addr, uint32_t types)
> }
>
> type = elf_parser.r2(&ehdr->e32.e_type);
> - if (!((1 << type) & types)) {
> + /* e_type comes from the file and the bitmap only has 32 slots. */
This comment is useless, nuke it.
> + if (type >= 32 || !((1U << type) & types)) {
> fprintf(stderr, "Invalid ELF type file %s\n", fname);
> return -1;
> }
Other than that, I'm fine with the patch.
-- Steve
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 2/5] elf-parse: check that the section header table is inside the file
2026-10-19 0:02 ` [PATCH 2/5] elf-parse: check that the section header table is inside the file Leizhen Zhang
@ 2026-10-19 12:55 ` Steven Rostedt
2026-10-20 0:03 ` sashiko-bot
1 sibling, 0 replies; 11+ messages in thread
From: Steven Rostedt @ 2026-10-19 12:55 UTC (permalink / raw)
To: Leizhen Zhang; +Cc: nathan, nsc, linux-kbuild, linux-kernel, ljs
On Sun, 18 Oct 2026 19:02:15 -0500
Leizhen Zhang <lzsx618@gmail.com> wrote:
> elf_parse() validates e_ehsize and e_shentsize, and nothing else. The
> section header table that every caller then walks is described entirely
> by unchecked file data, so a truncated or corrupted object makes the
> parser read outside the mapping:
>
> - a file shorter than one ELF header is read past its end, since e_type
> at offset 16 and e_ehsize at 52/54 are read before any length check;
> - e_shoff may point anywhere, including far past the end of the file;
> - e_shnum may claim more sections than the file can hold;
> - e_shstrndx may name a section that does not exist.
>
> elf_map() hands the mapping to sorttable and tracepoint-update, which
> index the table directly, and elf_open_ro() does the same for kallsyms,
> so all three tools fault on the same inputs.
>
> Found by fuzzing the host ELF tools with mutated object files under
> ASan/UBSan. Each reproducer below changes one field of an otherwise
> valid file; the sanitizer output is from host tools built with
> -fsanitize=address,undefined:
>
> e_shoff = filesize + 0x1000 (offset 0x28)
> ./kallsyms --sysmap=/dev/null t.elf
> ERROR: AddressSanitizer: unknown-crash on address 0x7f914e32cbdc
> #0 in __get_unaligned_le32 tools/include/tools/le_byteshift.h:14
> (reading sh_type of section 0)
> ./sorttable t.elf -- same
> e_shnum = 0xffff (offset 0x3c)
> ./sorttable t.elf
> ERROR: AddressSanitizer: unknown-crash on address 0x7f64023b8000
> #0 in __get_unaligned_le32 tools/include/tools/le_byteshift.h:14
> ./tracepoint-update t.o
> ERROR: AddressSanitizer: SEGV on unknown address 0x7f9fc264db28
> #0 in strcmp
> e_shstrndx = 0xfff0 (offset 0x3e)
> ./tracepoint-update t.o
> ERROR: AddressSanitizer: SEGV on unknown address 0x7f0f83b29544
> #0 in __get_unaligned_le32 tools/include/tools/le_byteshift.h:14
>
> Pass the mapped size down to elf_parse() and reject a file that cannot
> hold its own ELF header, a section header table that does not fit in the
> mapping, and a section name index outside the table. The division keeps
> the extent check free of overflow. A count of SHN_UNDEF means the real
> count lives in section 0, so one entry must still be present; resolving
> that count, and the SHN_XINDEX name index, stays with elf_open_ro().
>
> The three tools keep accepting the objects they accepted before: a
> vmlinux, a shared object and a .o built from the same source are parsed
> unchanged, and the reproducers above now print one line and exit 1.
>
> Fixes: b055f4c431e3 ("sorttable: Move ELF parsing into scripts/elf-parse.[ch]")
You can remove the above, because this doesn't "fix" anything. If the
object files are corrupted when getting here, there's much bigger issues
than this code breaking. The object files will be loaded in the vmlinux
file, and that would be a much bigger concern about corrected object files.
> Assisted-by: LLM ASan UBSan
> Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
> ---
> scripts/elf-parse.c | 44 ++++++++++++++++++++++++++++++++++++++++----
> 1 file changed, 40 insertions(+), 4 deletions(-)
>
> diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c
> index 9e7d1a4f263f..0e16a9cf67f4 100644
> --- a/scripts/elf-parse.c
> +++ b/scripts/elf-parse.c
> @@ -58,10 +58,17 @@ static void *map_file(char const *fname, size_t *size, bool writable)
> return addr;
> }
>
> -static int elf_parse(const char *fname, void *addr, uint32_t types)
> +static int elf_parse(const char *fname, void *addr, size_t size, uint32_t types)
> {
> Elf_Ehdr *ehdr = addr;
> - uint16_t type;
> + size_t shoff, shentsize, shnum;
> + uint16_t type, shstrndx;
> +
> + /* Every field read below lives in the smaller of the two headers. */
> + if (size < sizeof(Elf32_Ehdr)) {
> + fprintf(stderr, "ELF file too short: %s\n", fname);
> + return -1;
> + }
>
> if (memcmp(ELFMAG, ehdr->e32.e_ident, SELFMAG) != 0 ||
> ehdr->e32.e_ident[EI_VERSION] != EV_CURRENT) {
> @@ -153,7 +160,8 @@ static int elf_parse(const char *fname, void *addr, uint32_t types)
> elf_parser.rela_addend = rela64_addend;
> elf_parser.rela_write_addend = rela64_write_addend;
>
> - if (elf_parser.r2(&ehdr->e64.e_ehsize) != sizeof(Elf64_Ehdr) ||
> + if (size < sizeof(Elf64_Ehdr) ||
> + elf_parser.r2(&ehdr->e64.e_ehsize) != sizeof(Elf64_Ehdr) ||
> elf_parser.r2(&ehdr->e64.e_shentsize) != sizeof(Elf64_Shdr)) {
> fprintf(stderr,
> "unrecognized ET_EXEC/ET_DYN file: %s\n",
> @@ -168,6 +176,34 @@ static int elf_parse(const char *fname, void *addr, uint32_t types)
> ehdr->e32.e_ident[EI_CLASS], fname);
> return -1;
> }
> +
> + /*
> + * Everything past here walks the section headers with offsets and
> + * indices taken from the file, so the table the ELF header describes
> + * has to be inside the mapping first. e_shnum == 0 means the real
> + * count lives in section 0, which must then be present to be read.
> + */
> + shoff = ehdr_shoff(ehdr);
> + shentsize = ehdr_shentsize(ehdr);
> + shnum = ehdr_shnum(ehdr);
> + if (shnum == SHN_UNDEF)
> + shnum = 1;
> + if (shoff > size || shentsize == 0 ||
> + (size - shoff) / shentsize < shnum) {
> + fprintf(stderr, "ELF section headers out of range: %s\n",
> + fname);
> + return -1;
> + }
> +
> + /* SHN_XINDEX defers the index to section 0; elf_open_ro() checks it. */
> + shstrndx = ehdr_shstrndx(ehdr);
> + if (shstrndx != SHN_XINDEX && ehdr_shnum(ehdr) != SHN_UNDEF &&
> + shstrndx >= ehdr_shnum(ehdr)) {
> + fprintf(stderr, "ELF section name index out of range: %s\n",
> + fname);
> + return -1;
> + }
I really see this as overboard. As I said, if the object files are
corrupted, you'll likely see errors everywhere. Why is this code the one to
detect it?
I think this is adding more complexity than needed.
-- Steve
> +
> return 0;
> }
>
> @@ -195,7 +231,7 @@ static void *__elf_map(char const *fname, size_t *size, uint32_t types,
> if (!addr)
> return NULL;
>
> - ret = elf_parse(fname, addr, types);
> + ret = elf_parse(fname, addr, *size, types);
> if (ret < 0) {
> elf_unmap(addr, *size);
> return NULL;
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file
2026-10-19 0:02 [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Leizhen Zhang
` (4 preceding siblings ...)
2026-10-19 0:02 ` [PATCH 5/5] elf-parse: bound symbol names by the string table Leizhen Zhang
@ 2026-10-19 12:57 ` Steven Rostedt
5 siblings, 0 replies; 11+ messages in thread
From: Steven Rostedt @ 2026-10-19 12:57 UTC (permalink / raw)
To: Leizhen Zhang; +Cc: nathan, nsc, linux-kbuild, linux-kernel, ljs
On Sun, 18 Oct 2026 19:02:13 -0500
Leizhen Zhang <lzsx618@gmail.com> wrote:
> scripts/elf-parse.c takes the section header table, the section indices and
> the symbol table straight out of the file it maps, without checking any of
> them against the mapping. All three users -- kallsyms, sorttable and
> tracepoint-update -- then index with those values, so a truncated or
> corrupted object file makes the tools read outside the mapping, allocate
> from a file-supplied count, or divide by zero instead of printing an error.
>
> These are build-time tools fed by the local toolchain, so under
> Documentation/process/threat-model.rst this looks like a robustness problem
> rather than a vulnerability: reaching it takes a corrupt or hostile object
> in the build tree. I am leaving that classification for you to review.
Definitely not a security threat here. If the object files are corrupted,
then this is not where you should be detecting it. Unless we change the
purpose of this tool as a corruption detector, this code *expects* the
input to not be corrupt. The reasons for checking various ELF header info
is if someone is porting this code to a new architecture where it doesn't
support it yet, we want to warn.
I'm fine with adding robustness, but not complexity for an issue that
should never exist.
-- Steve
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 2/5] elf-parse: check that the section header table is inside the file
2026-10-19 0:02 ` [PATCH 2/5] elf-parse: check that the section header table is inside the file Leizhen Zhang
2026-10-19 12:55 ` Steven Rostedt
@ 2026-10-20 0:03 ` sashiko-bot
1 sibling, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-10-20 0:03 UTC (permalink / raw)
To: Leizhen Zhang, nathan, nsc; +Cc: linux-kbuild, rostedt, ljs
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] elf-parse: missing SHN_UNDEF and SHN_XINDEX bounds checks in elf_parse()
--
commit 8e951169d34059326033b08713c8c35ae69b423f
Author: Leizhen Zhang <lzsx618@gmail.com>
elf-parse: check that the section header table is inside the file
This commit passes the mapped file size to elf_parse() to validate that the
ELF header fits in the mapping, the section header table lies within the
mapping bounds, and the section name string table index is in range.
> diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c
> index 9e7d1a4f263fe..0e16a9cf67f45 100644
> --- a/scripts/elf-parse.c
> +++ b/scripts/elf-parse.c
[ ... ]
> @@ -168,6 +176,34 @@ static int elf_parse(const char *fname, void *addr, uint32_t types)
> ehdr->e32.e_ident[EI_CLASS], fname);
> return -1;
> }
> +
> + /*
> + * Everything past here walks the section headers with offsets and
> + * indices taken from the file, so the table the ELF header describes
> + * has to be inside the mapping first. e_shnum == 0 means the real
> + * count lives in section 0, which must then be present to be read.
> + */
> + shoff = ehdr_shoff(ehdr);
> + shentsize = ehdr_shentsize(ehdr);
> + shnum = ehdr_shnum(ehdr);
> + if (shnum == SHN_UNDEF)
> + shnum = 1;
> + if (shoff > size || shentsize == 0 ||
> + (size - shoff) / shentsize < shnum) {
> + fprintf(stderr, "ELF section headers out of range: %s\n",
> + fname);
> + return -1;
> + }
> +
> + /* SHN_XINDEX defers the index to section 0; elf_open_ro() checks it. */
> + shstrndx = ehdr_shstrndx(ehdr);
> + if (shstrndx != SHN_XINDEX && ehdr_shnum(ehdr) != SHN_UNDEF &&
> + shstrndx >= ehdr_shnum(ehdr)) {
> + fprintf(stderr, "ELF section name index out of range: %s\n",
> + fname);
> + return -1;
> + }
> +
> return 0;
> }
[Severity: Medium]
Can sorttable and tracepoint-update still read past the end of the mapping
when e_shnum is SHN_UNDEF or e_shstrndx is SHN_XINDEX?
The comment and commit message note that resolving the real section count for
SHN_UNDEF and the section name index for SHN_XINDEX stays with elf_open_ro()
(which is updated in the follow-up commit "elf-parse: check the section
indices that elf_open_ro() takes from the file").
However, sorttable and tracepoint-update do not call elf_open_ro(). Instead,
they call elf_map() and resolve section 0's sh_link and sh_size directly
without bounds checks:
scripts/sorttable.c:do_sort() and
scripts/tracepoint-update.c:process_tracepoints():
shstrndx = ehdr_shstrndx(ehdr);
if (shstrndx == SHN_XINDEX)
shstrndx = shdr_link(shdr_start);
string_sec = get_index(shdr_start, shentsize, shstrndx);
secstrings = (const char *)ehdr + shdr_offset(string_sec);
shnum = ehdr_shnum(ehdr);
if (shnum == SHN_UNDEF)
shnum = shdr_size(shdr_start);
If e_shnum == SHN_UNDEF and e_shstrndx is an out-of-range index (not equal to
SHN_XINDEX), elf_parse() skips the shstrndx check because ehdr_shnum(ehdr) ==
SHN_UNDEF, and both tools immediately index shdr_start out of bounds via
get_index(shdr_start, shentsize, shstrndx).
Similarly, if e_shnum == SHN_UNDEF and section 0's sh_size exceeds the number
of section headers that fit in the mapping, or if e_shstrndx == SHN_XINDEX
and section 0's sh_link >= shnum, elf_parse() succeeds and both tools read
past the end of the mapping.
Since elf_parse() already verifies that section 0 lies within the mapping when
shnum > 0, should elf_parse() resolve shdr_size() and shdr_link() from
section 0 and validate the real shnum and shstrndx right here so that all
callers of elf_map() are protected as well?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261019000218.3977398-1-lzsx618@gmail.com?part=2
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 5/5] elf-parse: bound symbol names by the string table
2026-10-19 0:02 ` [PATCH 5/5] elf-parse: bound symbol names by the string table Leizhen Zhang
@ 2026-10-20 0:03 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-10-20 0:03 UTC (permalink / raw)
To: Leizhen Zhang, nathan, nsc; +Cc: linux-kbuild, rostedt, ljs
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] elf-parse: pointer addition before bounds check in elf_open_ro()
- [Medium] elf-parse: unchecked shstrtab and sh_name in elf_section_name()
--
commit 33f3711117c58495bbc5517ccf237e3d77449cb0
Author: Leizhen Zhang <lzsx618@gmail.com>
elf-parse: bound symbol names by the string table
This commit validates that the ELF symbol string table lies within the file
mapping and ends in a NUL byte during elf_open_ro(). It also introduces
elf_symbol_name() to bounds-check symbol st_name offsets against the string
table size in elf_read_symbols().
> diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c
> index 756843c433ee8..8083b9ec3477f 100644
> --- a/scripts/elf-parse.c
> +++ b/scripts/elf-parse.c
[ ... ]
> @@ -355,8 +356,22 @@ int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf)
> elf_close(elf);
> return -1;
> }
> - elf->strtab = (const char *)elf->base +
> - shdr_offset(elf_section(elf, shdr_link(elf->symtab)));
> + strtab = elf_section(elf, shdr_link(elf->symtab));
> + stroff = shdr_offset(strtab);
> + elf->strtab_size = shdr_size(strtab);
> + elf->strtab = (const char *)elf->base + stroff;
> + /*
> + * Symbol names are read by offset into this table, so it has to be
> + * inside the mapping and has to end in a NUL for a name that starts
> + * inside it to end inside it as well.
> + */
> + if (stroff > elf->size || elf->size - stroff < elf->strtab_size ||
> + !elf->strtab_size || elf->strtab[elf->strtab_size - 1]) {
[Severity: Medium]
Can computing elf->strtab before checking stroff trigger a pointer overflow?
In elf_open_ro(), (const char *)elf->base + stroff is evaluated with the
unvalidated file-controlled stroff (such as 0xffffffffffffffff) before the
stroff > elf->size check runs. Under UBSan, this triggers a pointer-overflow
failure on malformed ELF inputs before the bounds check can return an error.
Should elf->strtab be assigned after validating that stroff and
elf->strtab_size fit within elf->size, with elf->strtab[elf->strtab_size - 1]
checked after that assignment?
> + fprintf(stderr, "%s: symbol string table out of range\n",
> + fname);
> + elf_close(elf);
> + return -1;
> + }
> elf->nr_syms = shdr_size(elf->symtab) / shdr_entsize(elf->symtab);
>
> return 0;
[ ... ]
> diff --git a/scripts/kallsyms-sysmap.c b/scripts/kallsyms-sysmap.c
> index a482968eba511..efca13ab9dc3d 100644
> --- a/scripts/kallsyms-sysmap.c
> +++ b/scripts/kallsyms-sysmap.c
> @@ -198,10 +198,10 @@ static struct sysmap_symbol *elf_read_symbols(const struct elf_file *elf,
>
> for (i = 1; i < elf->nr_syms; i++) {
> Elf_Sym *sym = elf_symbol(elf, i);
> - const char *name = elf->strtab + sym_name(sym);
> + const char *name = elf_symbol_name(elf, sym);
> char type = elf_symbol_type(elf, sym);
>
> - if (!type || sysmap_omits(name, type))
> + if (!name || !type || sysmap_omits(name, type))
> continue;
>
> syms[n].addr = sym_value(sym);
[Severity: Medium]
Does elf_read_symbols() -> elf_symbol_type() have the same out-of-bounds
read issue via elf->shstrtab and shdr_name(shdr)?
In scripts/elf-parse.c:elf_open_ro(), elf->shstrtab is set without verifying
that the section header string table fits within elf->size or ends in a NUL
byte:
elf->shstrtab = (const char *)elf->base +
shdr_offset(elf_section(elf, shstrndx));
Then in scripts/kallsyms-sysmap.c:elf_read_symbols(), elf_symbol_type() is
called on every symbol (even before checking !name). When a symbol references
a section index shndx < elf->shnum whose section has neither SHF_EXECINSTR
nor SHF_ALLOC set, elf_symbol_type() calls:
} else if (shndx < elf->shnum) {
shdr = elf_section(elf, shndx);
c = section_symbol_type(shdr, elf_section_name(elf, shdr));
}
where scripts/elf-parse.h:elf_section_name() adds the unchecked 32-bit
shdr_name(shdr) directly to elf->shstrtab:
static inline const char *elf_section_name(const struct elf_file *elf,
Elf_Shdr *shdr)
{
return elf->shstrtab + shdr_name(shdr);
}
and section_symbol_type() passes that pointer to string_starts_with() ->
strncmp(). Should elf->shstrtab and elf_section_name() also be bounds-checked
against the section header string table size?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261019000218.3977398-1-lzsx618@gmail.com?part=5
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-10-20 0:03 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19 0:02 [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Leizhen Zhang
2026-10-19 0:02 ` [PATCH 1/5] elf-parse: avoid an undefined shift when checking the ELF type Leizhen Zhang
2026-10-19 12:47 ` Steven Rostedt
2026-10-19 0:02 ` [PATCH 2/5] elf-parse: check that the section header table is inside the file Leizhen Zhang
2026-10-19 12:55 ` Steven Rostedt
2026-10-20 0:03 ` sashiko-bot
2026-10-19 0:02 ` [PATCH 3/5] elf-parse: check the section indices that elf_open_ro() takes from " Leizhen Zhang
2026-10-19 0:02 ` [PATCH 4/5] elf-parse: check the symbol table before walking it Leizhen Zhang
2026-10-19 0:02 ` [PATCH 5/5] elf-parse: bound symbol names by the string table Leizhen Zhang
2026-10-20 0:03 ` sashiko-bot
2026-10-19 12:57 ` [PATCH 0/5] elf-parse: validate what the host ELF tools read from the file Steven Rostedt
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.