mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Fang Xieyan <fangxy@xiaopeng.com>
To: Bradley Morgan <brads@mainlining.org>,
	Luis Chamberlain <mcgrof@kernel.org>,
	Petr Pavlu <petr.pavlu@suse.com>,
	Daniel Gomez <da.gomez@kernel.org>,
	Sami Tolvanen <samitolvanen@google.com>,
	Aaron Tomlin <atomlin@atomlin.com>
Cc: Masahiro Yamada <masahiroy@kernel.org>,
	Matthew Maurer <mmaurer@google.com>,
	linux-modules@vger.kernel.org, linux-kernel@vger.kernel.org,
	stable@vger.kernel.org
Subject: [PATCH v4] module: Validate __version_ext_crcs and __version_ext_names section types
Date: Mon, 21 Sep 2026 20:37:35 +0800	[thread overview]
Message-ID: <20260921123735.72202-1-fangxy@xiaopeng.com> (raw)

elf_validity_cache_sechdrs() skips validate_section_offset() for
SHT_NOBITS sections, which hold no data in the ELF file. However, the
extended MODVERSIONS sections __version_ext_crcs and __version_ext_names
are later read as file contents without re-checking their type.

elf_validity_cache_index_versions() reads __version_ext_names directly
from the module image using sh_offset:

	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 SHT_NOBITS __version_ext_names therefore bypasses the generic offset
validation and can carry an out-of-bounds sh_offset, so strnlen() reads
past the end of the kernel's in-memory copy of the module:

  BUG: KASAN: vmalloc-out-of-bounds in strnlen+0x73/0x80
  Read of size 1 at addr ffa00000005834ff by task insmod/79
  CPU: 0 PID: 79 Comm: insmod Not tainted 7.3.0-rc3-g704340f1cd0d #1
  Call Trace:
   strnlen+0x73/0x80
   load_module+0xef6/0x8600

The CRC section is read the same way, only later. In early_mod_check(),
rewrite_section_headers() sets each sh_addr to hdr + sh_offset, and the
version sections are not copied into the final image, so
check_modstruct_version() reads the CRC data from that temporary image.
A SHT_NOBITS __version_ext_crcs then makes check_version() dereference
past the end of the image:

  BUG: KASAN: vmalloc-out-of-bounds in check_version+0x5c7/0x600
  Read of size 4 at addr ffa0000000583509 by task insmod/78
  Call Trace:
   check_version+0x5c7/0x600
   check_modstruct_version+0x101/0x140
   load_module+0x1442/0x8600

Both reads occur after module_sig_check() and before
layout_and_allocate(), on the temporary copy made by kernel_read_file(),
so an unvalidated sh_offset points outside that vmalloc region.

An offset bound is not enough: an in-bounds SHT_NOBITS section still
passes it, and the loader would read unrelated image data as extended
MODVERSIONS. A real __version_ext_crcs and __version_ext_names are
SHT_PROGBITS, so require that type before either is read.

Fixes: 54ac1ac8edeb ("modules: Support extended MODVERSIONS info")
Cc: stable@vger.kernel.org
Suggested-by: Bradley Morgan <brads@mainlining.org>
Link: https://lore.kernel.org/all/C04CC94F-26D0-4823-84F4-7AFEBCDCA9AE@mainlining.org/
Assisted-by: Hawkeye:GLM-5.3-flash
Assisted-by: Qoder:Qwen3.8-Max
Signed-off-by: Fang Xieyan <fangxy@xiaopeng.com>
---

Changes in v4:
  - Extended the type check to __version_ext_crcs, not just
    __version_ext_names (Bradley Morgan). The crc array is dereferenced
    at hdr + sh_offset in check_version(), the same exposure as the name
    walk, so a SHT_NOBITS __version_ext_crcs faults there too. Added a
    second splat for it (check_version, Read of size 4).
  - Shortened the in-code comment (Bradley Morgan).
  - Reworked the changelog as defect / splat / fix and trimmed it. The
    names walk in elf_validity_cache_index_versions() is inlined into
    load_module(), so its splat has no frame for it.

Standalone since v3 (20260919154139.52904-1-fangxy@xiaopeng.com). v1/v2 were
patch 2/2 of a two-patch series, split off when the sibling SHT_NULL fix
was withdrawn in favor of Liu Chao's fail-closed fix for the same bug. This
patch does not depend on it.

Reproducer: a .ko carrying __version_ext_crcs and __version_ext_names, in
three variants. (1) names SHT_NOBITS with sh_offset past the end of the
image: before, the name walk reads out of bounds (KASAN
vmalloc-out-of-bounds in strnlen, Read of size 1). (2) names SHT_NOBITS
with sh_offset in bounds, aimed at other file data: before, the walk
silently consumes it as a version name and no splat fires. (3) crcs
SHT_NOBITS with sh_offset past the end and names a valid SHT_PROGBITS
"module_layout": before, check_version() reads the crc out of bounds
(KASAN vmalloc-out-of-bounds, Read of size 4). On the patched kernel all
three are rejected before the dereference and insmod fails with -ENOEXEC
(rc=8); variant (3) prints "Invalid ELF __version_ext_* type: crc=8
name=1".

Tested on 704340f1cd0d (9 commits past v7.3-rc3), 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. LOCALVERSION is pinned so the loader
reaches the version walk instead of stopping at the vermagic check.

 kernel/module/main.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/kernel/module/main.c b/kernel/module/main.c
index d0e1e0b..e46eba6 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -2287,6 +2287,18 @@ static int elf_validity_cache_index_versions(struct load_info *info, int flags)
 	 * number of entries in every section.
 	 */
 	if (vers_ext_crc) {
+		/*
+		 * SHT_NOBITS skips offset validation; both sections
+		 * contain file data.
+		 */
+		if (info->sechdrs[vers_ext_crc].sh_type != SHT_PROGBITS ||
+		    info->sechdrs[vers_ext_name].sh_type != SHT_PROGBITS) {
+			pr_err("Invalid ELF __version_ext_* type: crc=%u name=%u\n",
+			       info->sechdrs[vers_ext_crc].sh_type,
+			       info->sechdrs[vers_ext_name].sh_type);
+			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)


                 reply	other threads:[~2026-09-21 12:37 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260921123735.72202-1-fangxy@xiaopeng.com \
    --to=fangxy@xiaopeng.com \
    --cc=atomlin@atomlin.com \
    --cc=brads@mainlining.org \
    --cc=da.gomez@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-modules@vger.kernel.org \
    --cc=masahiroy@kernel.org \
    --cc=mcgrof@kernel.org \
    --cc=mmaurer@google.com \
    --cc=petr.pavlu@suse.com \
    --cc=samitolvanen@google.com \
    --cc=stable@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®