mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 0/6] kbuild: fix UB hit by normal builds in host tools
@ 2026-11-09 23:59 Leizhen Zhang
  2026-11-09 23:59 ` [PATCH v3 1/6] genksyms: fix infinite loop on declarations with parameter lists Leizhen Zhang
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Leizhen Zhang @ 2026-11-09 23:59 UTC (permalink / raw)
  To: nathan, nsc
  Cc: masahiroy, ardb, rostedt, hpa, linux-kbuild, linux-kernel, Leizhen Zhang

This is v3 of the host tool fixes. The main change is what is left in
it: following the feedback on my elf-parse series, I went back over
every patch and kept only the ones a normal build or valid source can
trigger. Each of the six below was re-checked on mainline as of
b23545529c21 (the rc1 rebase changed no code) by doing a plain x86_64
defconfig build (MODVERSIONS and MODULE_SRCVERSION_ALL enabled, 12
modules) with the host tools built with sanitizers, and then rerunning
the same step with the patch applied:

  1/6 genksyms   'int a, f(int);' is valid C and makes genksyms loop
                 forever; 'int a; int f(int);' is fine.
  2/6 kallsyms   link-vmlinux.sh hands kallsyms an empty .syms file on
                 every build (the .tmp_vmlinux0 pass), and both qsort()
                 calls then get a NULL table. 2 UBSan reports -> 0, the
                 generated tables are byte-identical.
  3/6 modpost    read_text_file() rereads into the start of the buffer
                 after a short read. Not something a local filesystem
                 does in practice; shown with an LD_PRELOAD shim that
                 caps read() at 64 bytes, under which modpost rejects a
                 valid Module.symvers.
  4/6 modpost    parse_source_files() does arithmetic on the NULL that
                 strstr() returns for dependencies outside the module's
                 directory. Reported on the build's own modpost
                 invocation; Module.symvers is identical with the fix.
  5/6 sorttable  the sort_needed address is computed by adding a kernel
                 virtual address to a host pointer first. Reported on
                 the real vmlinux.
  6/6 tools      __get_unaligned_le32() shifts a byte >= 0x80 into the
                 sign bit. Hit 4 times during the defconfig link, by
                 sorttable.

The 4/6 and 5/6 reports need clang's -fsanitize=undefined; GCC 12 does
not instrument those two pointer-arithmetic cases, so they are silent
with a GCC host build.

The 2/6 and 6/6 reports reproduce unchanged on v7.4-rc2 (GCC host
build, same config).

Changes in v3:
- Rebased onto v7.4-rc1. No code changes in the patches that remain.
- Dropped "fixdep: fix out-of-bounds read on a comment ending with a
  backslash": it needs a hand-written .d file; the compiler never
  produces one.
- Dropped "modpost: fix stack out-of-bounds read for unterminated PNP
  ids": all 699 PNP ids in the tree are 7 characters, and the overflow
  needs an 8-character one, which is not a well-formed PNP id.
- Replaced the Assisted-by trailers, which checkpatch no longer
  accepts, with a sentence in each changelog saying how the problem was
  found and that an AI coding assistant helped prepare the fix.

v2: https://lore.kernel.org/r/20261008164035.3668885-1-lzsx618@gmail.com

Leizhen Zhang (6):
  genksyms: fix infinite loop on declarations with parameter lists
  kallsyms: do not call qsort() with a NULL table
  modpost: fix handling of short reads in read_text_file()
  modpost: fix pointer arithmetic on NULL in parse_source_files()
  sorttable: avoid pointer arithmetic overflow when locating sort_needed
  tools/include: fix signed shift overflow in 32-bit unaligned accessors

 scripts/genksyms/parse.y           | 23 +++++++++++++++++++----
 scripts/kallsyms.c                 |  6 ++++--
 scripts/mod/modpost.c              |  6 +++++-
 scripts/mod/sumversion.c           |  3 ++-
 scripts/sorttable.c                |  2 +-
 tools/include/tools/be_byteshift.h |  2 +-
 tools/include/tools/le_byteshift.h |  2 +-
 7 files changed, 33 insertions(+), 11 deletions(-)


base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3 1/6] genksyms: fix infinite loop on declarations with parameter lists
  2026-11-09 23:59 [PATCH v3 0/6] kbuild: fix UB hit by normal builds in host tools Leizhen Zhang
@ 2026-11-09 23:59 ` Leizhen Zhang
  2026-11-09 23:59 ` [PATCH v3 2/6] kallsyms: do not call qsort() with a NULL table Leizhen Zhang
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Leizhen Zhang @ 2026-11-09 23:59 UTC (permalink / raw)
  To: nathan, nsc
  Cc: masahiroy, ardb, rostedt, hpa, linux-kbuild, linux-kernel, Leizhen Zhang

decl_specifier_seq updates the global decl_spec, which is used by the ','
action of init_declarator_list to give each further declarator the
specifiers of the declaration. However, decl_specifier_seq is also used
for parameter declarations, so for a declaration such as

  int a, f(int);

decl_spec is clobbered by the specifiers of the parameter "int" by the
time the ',' action runs. The action then links the parameter's own token
node back into its chain, creating a cycle, and copy_list_range() loops
forever while allocating memory:

  $ printf 'int a, f(int);\n' | scripts/genksyms/genksyms
  (hangs)

Before commit 45c9c4101d3d ("genksyms: fix memory leak when the same
symbol is added from source") the same corruption did not hang but
silently recorded a wrong type for subsequent declarators (e.g. for
"int f(long), a;" the type of "a" was recorded as "int f ( long a").

Only set decl_spec in decl_specifier_seq_opt, which is used at the
declaration level, and let decl_specifier_seq just return the last
specifier. Struct and union member declarations use a new
member_decl_specifier_seq_opt that does not touch decl_spec, so a
struct body nested in a parameter list cannot clobber it either.

The generated symtypes and CRCs for 149 preprocessed kernel source files
(1128 exported symbols) are unchanged, and no new bison conflicts are
introduced.

Found by fuzzing genksyms with ASan/UBSan.

The problem was found with ASan and UBSan, and the analysis and fix were
prepared with the help of an AI coding assistant; I reviewed and
tested the result.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
 scripts/genksyms/parse.y | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/scripts/genksyms/parse.y b/scripts/genksyms/parse.y
index cabcd146f3aa..7cc0336a7954 100644
--- a/scripts/genksyms/parse.y
+++ b/scripts/genksyms/parse.y
@@ -201,13 +201,28 @@ init_declarator:
 /* Hang on to the specifiers so that we can reuse them.  */
 decl_specifier_seq_opt:
 	/* empty */				{ decl_spec = NULL; }
+	| decl_specifier_seq			{ decl_spec = *$1; }
+	;
+
+/*
+ * Unlike decl_specifier_seq_opt, this does not touch decl_spec, so that
+ * a struct/union body nested in a parameter list does not clobber the
+ * specifiers of the enclosing declaration.
+ */
+member_decl_specifier_seq_opt:
+	/* empty */				{ $$ = NULL; }
 	| decl_specifier_seq
 	;
 
+/*
+ * Do not set decl_spec here; decl_specifier_seq is also used for parameter
+ * declarations, which would clobber the specifiers of the enclosing
+ * declaration, e.g. "int a, f(long);".
+ */
 decl_specifier_seq:
-	attribute_opt decl_specifier		{ decl_spec = *$2; }
-	| decl_specifier_seq decl_specifier	{ decl_spec = *$2; }
-	| decl_specifier_seq ATTRIBUTE_PHRASE	{ decl_spec = *$2; }
+	attribute_opt decl_specifier		{ $$ = $2; }
+	| decl_specifier_seq decl_specifier	{ $$ = $2; }
+	| decl_specifier_seq ATTRIBUTE_PHRASE	{ $$ = $2; }
 	;
 
 decl_specifier:
@@ -456,7 +471,7 @@ member_specification:
 	;
 
 member_declaration:
-	decl_specifier_seq_opt member_declarator_list_opt ';'
+	member_decl_specifier_seq_opt member_declarator_list_opt ';'
 		{ $$ = $3; dont_want_type_specifier = false; }
 	| error ';'
 		{ $$ = $2; dont_want_type_specifier = false; }
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3 2/6] kallsyms: do not call qsort() with a NULL table
  2026-11-09 23:59 [PATCH v3 0/6] kbuild: fix UB hit by normal builds in host tools Leizhen Zhang
  2026-11-09 23:59 ` [PATCH v3 1/6] genksyms: fix infinite loop on declarations with parameter lists Leizhen Zhang
@ 2026-11-09 23:59 ` Leizhen Zhang
  2026-11-10  0:00 ` [PATCH v3 3/6] modpost: fix handling of short reads in read_text_file() Leizhen Zhang
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Leizhen Zhang @ 2026-11-09 23:59 UTC (permalink / raw)
  To: nathan, nsc
  Cc: masahiroy, ardb, rostedt, hpa, linux-kbuild, linux-kernel, Leizhen Zhang

link-vmlinux.sh runs kallsyms on an empty input for the first link, in
which case table is NULL and table_cnt is 0. Passing NULL to qsort() is
undefined behaviour even for zero elements, and UBSan reports:

  scripts/kallsyms.c: runtime error: null pointer passed as argument 1,
  which is declared to never be null

Skip the sort when there are no symbols.

The problem was found with UBSan, and the analysis and fix were
prepared with the help of an AI coding assistant; I reviewed and
tested the result.

Fixes: c442db3f49f2 ("kbuild: remove PROVIDE() for kallsyms symbols")
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
 scripts/kallsyms.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c
index ecb444078c1c..4bbf2e65cb38 100644
--- a/scripts/kallsyms.c
+++ b/scripts/kallsyms.c
@@ -389,7 +389,8 @@ static int compare_names(const void *a, const void *b)
 
 static void sort_symbols_by_name(void)
 {
-	qsort(table, table_cnt, sizeof(table[0]), compare_names);
+	if (table_cnt)
+		qsort(table, table_cnt, sizeof(table[0]), compare_names);
 }
 
 static void write_src(FILE *out_bin_file, const char *out_bin_name)
@@ -819,7 +820,8 @@ static int compare_symbols(const void *a, const void *b)
 
 static void sort_symbols(void)
 {
-	qsort(table, table_cnt, sizeof(table[0]), compare_symbols);
+	if (table_cnt)
+		qsort(table, table_cnt, sizeof(table[0]), compare_symbols);
 }
 
 int main(int argc, char **argv)
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3 3/6] modpost: fix handling of short reads in read_text_file()
  2026-11-09 23:59 [PATCH v3 0/6] kbuild: fix UB hit by normal builds in host tools Leizhen Zhang
  2026-11-09 23:59 ` [PATCH v3 1/6] genksyms: fix infinite loop on declarations with parameter lists Leizhen Zhang
  2026-11-09 23:59 ` [PATCH v3 2/6] kallsyms: do not call qsort() with a NULL table Leizhen Zhang
@ 2026-11-10  0:00 ` Leizhen Zhang
  2026-11-10  0:00 ` [PATCH v3 4/6] modpost: fix pointer arithmetic on NULL in parse_source_files() Leizhen Zhang
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Leizhen Zhang @ 2026-11-10  0:00 UTC (permalink / raw)
  To: nathan, nsc
  Cc: masahiroy, ardb, rostedt, hpa, linux-kbuild, linux-kernel, Leizhen Zhang

read_text_file() loops until the whole file has been read, but every
read() call writes to the start of the buffer, so after a short read the
data already read is overwritten and the end of the buffer is left
uninitialized. If read() returns 0 before the expected size has been
read, the loop never terminates.

Read into the correct offset of the buffer, and treat an unexpected end
of file as an error.

This was confirmed by limiting read() to 64 bytes per call with an
LD_PRELOAD shim, which makes modpost fail with a parse error on a valid
Module.symvers.

The analysis and fix were prepared with the help of an AI coding
assistant; I reviewed and tested the result.

Fixes: ac5100f54329 ("modpost: add read_text_file() and get_line() helpers")
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
 scripts/mod/modpost.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index 75374c64b8cc..1772068dc68e 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -143,11 +143,15 @@ char *read_text_file(const char *filename)
 	while (nbytes) {
 		ssize_t bytes_read;
 
-		bytes_read = read(fd, buf, nbytes);
+		bytes_read = read(fd, buf + st.st_size - nbytes, nbytes);
 		if (bytes_read < 0) {
 			perror(filename);
 			exit(1);
 		}
+		if (bytes_read == 0) {
+			fprintf(stderr, "%s: unexpected end of file\n", filename);
+			exit(1);
+		}
 
 		nbytes -= bytes_read;
 	}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3 4/6] modpost: fix pointer arithmetic on NULL in parse_source_files()
  2026-11-09 23:59 [PATCH v3 0/6] kbuild: fix UB hit by normal builds in host tools Leizhen Zhang
                   ` (2 preceding siblings ...)
  2026-11-10  0:00 ` [PATCH v3 3/6] modpost: fix handling of short reads in read_text_file() Leizhen Zhang
@ 2026-11-10  0:00 ` Leizhen Zhang
  2026-11-10  0:00 ` [PATCH v3 5/6] sorttable: avoid pointer arithmetic overflow when locating sort_needed Leizhen Zhang
  2026-11-10  0:00 ` [PATCH v3 6/6] tools/include: fix signed shift overflow in 32-bit unaligned accessors Leizhen Zhang
  5 siblings, 0 replies; 7+ messages in thread
From: Leizhen Zhang @ 2026-11-10  0:00 UTC (permalink / raw)
  To: nathan, nsc
  Cc: masahiroy, ardb, rostedt, hpa, linux-kbuild, linux-kernel, Leizhen Zhang

parse_source_files() checks whether a dependency is in the same directory
as the object file with

  (strstr(line, dir) + strlen(dir) - 1) == strrchr(line, '/')

When the dependency is not below dir, strstr() returns NULL and the
pointer arithmetic on NULL is undefined behaviour. This happens for
every module with dependencies outside its own directory, and UBSan
reports:

  scripts/mod/sumversion.c: runtime error: applying non-zero offset
  to null pointer

Check the result of strstr() before using it.

The problem was found with UBSan, and the analysis and fix were
prepared with the help of an AI coding assistant; I reviewed and
tested the result.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
 scripts/mod/sumversion.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/scripts/mod/sumversion.c b/scripts/mod/sumversion.c
index 3dd28b4d0099..4c59e7cfbcbd 100644
--- a/scripts/mod/sumversion.c
+++ b/scripts/mod/sumversion.c
@@ -364,7 +364,8 @@ static int parse_source_files(const char *objfile, struct md4_ctx *md)
 		}
 
 		/* Check if this file is in same dir as objfile */
-		if ((strstr(line, dir)+strlen(dir)-1) == strrchr(line, '/')) {
+		p = strstr(line, dir);
+		if (p && p + dirlen - 1 == strrchr(line, '/')) {
 			if (!parse_file(line, md)) {
 				warn("could not open %s: %s\n",
 				     line, strerror(errno));
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3 5/6] sorttable: avoid pointer arithmetic overflow when locating sort_needed
  2026-11-09 23:59 [PATCH v3 0/6] kbuild: fix UB hit by normal builds in host tools Leizhen Zhang
                   ` (3 preceding siblings ...)
  2026-11-10  0:00 ` [PATCH v3 4/6] modpost: fix pointer arithmetic on NULL in parse_source_files() Leizhen Zhang
@ 2026-11-10  0:00 ` Leizhen Zhang
  2026-11-10  0:00 ` [PATCH v3 6/6] tools/include: fix signed shift overflow in 32-bit unaligned accessors Leizhen Zhang
  5 siblings, 0 replies; 7+ messages in thread
From: Leizhen Zhang @ 2026-11-10  0:00 UTC (permalink / raw)
  To: nathan, nsc
  Cc: masahiroy, ardb, rostedt, hpa, linux-kbuild, linux-kernel, Leizhen Zhang

The location of the main_extable_sort_needed variable is computed as

  (void *)ehdr + shdr_offset(sec) + sym_value(sym) - shdr_addr(sec)

which first adds the symbol's virtual address (e.g. 0xffffffff8...) to
the pointer and only then subtracts the section address. The
intermediate pointer overflows, which is undefined behaviour and is
reported by UBSan.

Subtract the section address from the symbol value first.

The problem was found with UBSan, and the analysis and fix were
prepared with the help of an AI coding assistant; I reviewed and
tested the result.

Fixes: a79f248b9b30 ("scripts: Add sortextable to sort the kernel's exception table.")
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
 scripts/sorttable.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/sorttable.c b/scripts/sorttable.c
index 88e49a5c4251..0425e00c490f 100644
--- a/scripts/sorttable.c
+++ b/scripts/sorttable.c
@@ -788,7 +788,7 @@ static int do_sort(Elf_Ehdr *ehdr,
 	sort_needed_sec = get_index(shdr_start, shentsize, sort_need_index);
 	sort_needed_loc = (void *)ehdr +
 		shdr_offset(sort_needed_sec) +
-		sym_value(sort_needed_sym) - shdr_addr(sort_needed_sec);
+		(sym_value(sort_needed_sym) - shdr_addr(sort_needed_sec));
 
 	/* extable has been sorted, clear the flag */
 	elf_parser.w(0, sort_needed_loc);
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3 6/6] tools/include: fix signed shift overflow in 32-bit unaligned accessors
  2026-11-09 23:59 [PATCH v3 0/6] kbuild: fix UB hit by normal builds in host tools Leizhen Zhang
                   ` (4 preceding siblings ...)
  2026-11-10  0:00 ` [PATCH v3 5/6] sorttable: avoid pointer arithmetic overflow when locating sort_needed Leizhen Zhang
@ 2026-11-10  0:00 ` Leizhen Zhang
  5 siblings, 0 replies; 7+ messages in thread
From: Leizhen Zhang @ 2026-11-10  0:00 UTC (permalink / raw)
  To: nathan, nsc
  Cc: masahiroy, ardb, rostedt, hpa, linux-kbuild, linux-kernel, Leizhen Zhang

In __get_unaligned_le32() and __get_unaligned_be32() the most significant
byte is promoted to int before being shifted left by 24. If the byte is
0x80 or higher, the result does not fit in an int, which is undefined
behaviour. This happens for every kernel virtual address, e.g. in
sorttable, and UBSan reports:

  tools/include/tools/le_byteshift.h:14: runtime error: left shift of
  255 by 24 places cannot be represented in type 'int'

Cast the byte to uint32_t before shifting.

The problem was found with UBSan, and the analysis and fix were
prepared with the help of an AI coding assistant; I reviewed and
tested the result.

Fixes: a07f7672d7cf ("tools/include: Add byteshift headers for endian access")
Signed-off-by: Leizhen Zhang <lzsx618@gmail.com>
---
 tools/include/tools/be_byteshift.h | 2 +-
 tools/include/tools/le_byteshift.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/tools/include/tools/be_byteshift.h b/tools/include/tools/be_byteshift.h
index f7d1d1698938..2bda8d199c85 100644
--- a/tools/include/tools/be_byteshift.h
+++ b/tools/include/tools/be_byteshift.h
@@ -11,7 +11,7 @@ static inline uint16_t __get_unaligned_be16(const uint8_t *p)
 
 static inline uint32_t __get_unaligned_be32(const uint8_t *p)
 {
-	return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
+	return (uint32_t)p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
 }
 
 static inline uint64_t __get_unaligned_be64(const uint8_t *p)
diff --git a/tools/include/tools/le_byteshift.h b/tools/include/tools/le_byteshift.h
index dc8565f39717..e5c78a48a78f 100644
--- a/tools/include/tools/le_byteshift.h
+++ b/tools/include/tools/le_byteshift.h
@@ -11,7 +11,7 @@ static inline uint16_t __get_unaligned_le16(const uint8_t *p)
 
 static inline uint32_t __get_unaligned_le32(const uint8_t *p)
 {
-	return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
+	return p[0] | p[1] << 8 | p[2] << 16 | (uint32_t)p[3] << 24;
 }
 
 static inline uint64_t __get_unaligned_le64(const uint8_t *p)
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-11-10  0:01 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-09 23:59 [PATCH v3 0/6] kbuild: fix UB hit by normal builds in host tools Leizhen Zhang
2026-11-09 23:59 ` [PATCH v3 1/6] genksyms: fix infinite loop on declarations with parameter lists Leizhen Zhang
2026-11-09 23:59 ` [PATCH v3 2/6] kallsyms: do not call qsort() with a NULL table Leizhen Zhang
2026-11-10  0:00 ` [PATCH v3 3/6] modpost: fix handling of short reads in read_text_file() Leizhen Zhang
2026-11-10  0:00 ` [PATCH v3 4/6] modpost: fix pointer arithmetic on NULL in parse_source_files() Leizhen Zhang
2026-11-10  0:00 ` [PATCH v3 5/6] sorttable: avoid pointer arithmetic overflow when locating sort_needed Leizhen Zhang
2026-11-10  0:00 ` [PATCH v3 6/6] tools/include: fix signed shift overflow in 32-bit unaligned accessors Leizhen Zhang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®