* [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