[PATCH 2/2] module: Validate the __version_ext_names section offset
From: Fang Xieyan
Date: Wed Sep 16 2026 - 13:55:34 EST
elf_validity_check_sectionheaders() validates the size and offset of
every section, unless its type is SHT_NULL or SHT_NOBITS:
switch (shdr->sh_type) {
case SHT_NULL:
case SHT_NOBITS:
/* No contents, offset/size don't mean anything */
continue;
default:
err = validate_section_offset(info, shdr);
elf_validity_cache_index_versions() then reads the extended version
names by their sh_offset, without checking that the section holds
data:
if (vers_ext_crc) {
crc_count = info->sechdrs[vers_ext_crc].sh_size / sizeof(u32);
name = (void *)info->hdr +
info->sechdrs[vers_ext_name].sh_offset;
remaining_len = info->sechdrs[vers_ext_name].sh_size;
while (crc_count--) {
name_size = strnlen(name, remaining_len) + 1;
A __version_ext_names section of type SHT_NOBITS reaches here with an
unvalidated sh_offset, so the name lookup reads past the in-memory
copy of the module:
BUG: KASAN: vmalloc-out-of-bounds in strnlen+0x73/0x80
Read of size 1 at addr ffa00000005534ff by task insmod/79
...
strnlen+0x73/0x80
load_module+0xef6/0x8600
The buggy address belongs to a 43-page vmalloc region starting at
0xffa0000000529000 allocated at kernel_read_file+0x7b4/0x9f0
A real __version_ext_names section is SHT_PROGBITS and has already
passed validate_section_offset(), so this only rejects a SHT_NOBITS
section, which holds no names to read. Validate the offset before the
dereference.
Fixes: 54ac1ac8edeb ("modules: Support extended MODVERSIONS info")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Hawkeye:GLM-5.3-flash
Assisted-by: Qoder:Qwen3.8-Max
Signed-off-by: Fang Xieyan <fangxy@xxxxxxxxxxxx>
---
Found in the same review as patch 1/2. elf_validity_check_sectionheaders()
exempts SHT_NOBITS from validate_section_offset(), and
elf_validity_cache_index_versions() then dereferences the __version_ext_names
section as hdr + sh_offset without re-checking it, so a section of that name and
type SHT_NOBITS carries an unvalidated offset into the extended version name
walk. This patch stands on its own; it does not depend on patch 1/2.
Reproducer: build a .ko that carries __version_ext_crcs and a
__version_ext_names section of type SHT_NOBITS whose sh_offset points past the
end of the module image. insmod it. Before the change the name walk reads out of
bounds; after it validate_section_offset() rejects the section and insmod fails
with -ENOEXEC (rc=8, "invalid module format"), which is what a module with a
malformed version section should do.
Both cases ran on 704340f1cd0d (v7.3-rc4): x86_64 defconfig plus
CONFIG_KASAN_GENERIC and CONFIG_KASAN_VMALLOC, gcc 13.2.0, QEMU under TCG. The
unpatched and patched kernels are built from byte-identical .config files and
differ only by this patch.
The same vermagic caveat as patch 1/2 applies: LOCALVERSION is pinned so the
patched kernel's release string matches the payload, and the loader reaches the
version walk instead of stopping at the version magic check.
kernel/module/main.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/kernel/module/main.c b/kernel/module/main.c
index e36bfe4..adaaaca 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -2296,6 +2296,16 @@ static int elf_validity_cache_index_versions(struct load_info *info, int flags)
* number of entries in every section.
*/
if (vers_ext_crc) {
+ /*
+ * The name section is walked by its sh_offset and sh_size
+ * below. elf_validity_check_sectionheaders() exempts SHT_NOBITS
+ * sections from validate_section_offset(), so a __version_ext_names
+ * section of that type reaches here with an unvalidated sh_offset.
+ * Bound it before dereferencing hdr + sh_offset.
+ */
+ if (validate_section_offset(info, &info->sechdrs[vers_ext_name]))
+ return -ENOEXEC;
+
crc_count = info->sechdrs[vers_ext_crc].sh_size / sizeof(u32);
name = (void *)info->hdr +
info->sechdrs[vers_ext_name].sh_offset;
--
2.50.1 (Apple Git-155)