Hi,
I'm Ananta Srikar Puranam, a PhD student at Washington State University.
I'm reporting this bug on behalf of my team: Dr. Swarn Priya and
Dr. Basavesh Ammanaghatta Shivakumar (both Cc'd).
The module loader accepts an allocated ELF section whose sh_addralign is
not zero, one, or a power of two, and then places the section at an
address that does not satisfy the declared alignment. ELF requires
sh_addralign to be 0, 1, or a positive power of two, but nothing in the
section-header validation checks it.
Trigger
=======
A matched pair built from one minimal external module whose only data
is an 8-byte-aligned u64 in .data:
control.ko .data sh_addralign = 8 (as built)
invalid.ko .data sh_addralign = 5 (only that header field changed;
the files differ in one byte)
The module's init function prints the live address of the object and
its remainder modulo 5 and modulo 8.
Observed (current mainline, x86_64)
===================================
control (sh_addralign=8):
module_align_repro: address=ffffffffc03f6010 remainder_mod_5=2
remainder_mod_8=0 value=7
invalid (sh_addralign=5):
insmod exit status: 0
module_align_repro: address=ffffffffc03f6010 remainder_mod_5=2
remainder_mod_8=0 value=7
(each message is one line in dmesg; wrapped here)
The invalid module loads, and its section address is not a multiple of
the declared alignment. There is no oops, warning, or other kernel
message.
Tested
======
- Mainline a91337fb5e8e ("Merge tag 'configfs-7.4-rc2' ..."),
7.4.0-rc1-00590-ga91337fb5e8e, x86_64 under KVM:
invalid twin accepted with the alignment not honored in 3/3 loads;
control loaded 8-byte aligned in 3/3
- Earlier validation on the same commit (separate build and guest):
invalid twin accepted at ffffffffa0201010, remainder 4 modulo 5.
Cause
=====
The section layout in module_get_offset_and_type() does:
offset = ALIGN(mod->mem[type].size, sechdr->sh_addralign ?: 1);
ALIGN() is mask-based and assumes a power of two:
((x) + (a - 1)) & ~(a - 1). For a = 5 this is (x + 4) & ~4, which only
clears bit 2, so the result is not, in general, a multiple of 5.
elf_validity_cache_sechdrs() validates section types, offsets and
sizes, but never checks sh_addralign, so the value reaches ALIGN()
unchecked.
The same unchecked value is also used for the per-cpu section
(percpu_modalloc()), the kallsyms layout (kernel/module/kallsyms.c), and
codetag sections. I have only exercised the ordinary .data layout path.
This behavior is already present in the initial git import,
1da177e4c3f4 ("Linux-2.6.12-rc2").
Impact
======
Triggering requires CONFIG_MODULES=y and permission to load a module
(CAP_SYS_MODULE). I observed a successful load with an alignment the
loader does not honor, not a crash, memory corruption, or an
unprivileged boundary crossing. Per
Documentation/process/threat-model.rst I am treating this as an ELF
validation and loader correctness bug, not a vulnerability. Standard
toolchains do not emit such values; the input is a malformed module.
Setup
=====
Host: Ubuntu 26.04.1 LTS, x86_64
Toolchain: Ubuntu GCC 15.2.0 / GNU ld 2.46 (built with O=)
Config: x86_64_defconfig plus kernel/configs/kvm_guest.config,
resolved with olddefconfig; no other changes
(full .config available)
Module: out-of-tree, built with M= against the same build tree;
the invalid twin is the built .ko with only .data's
sh_addralign rewritten from 8 to 5
QEMU: qemu-system-x86_64 10.2.1 with KVM, -smp 16 -m 8G
Guest: Ubuntu 26.04 amd64 rootfs (debootstrap); modules loaded
with insmod over SSH
The source reproducer (module and header-patching script) is available
privately on request. Per Documentation/process/security-bugs.rst I
have not posted it to the list.
Fix
===
A proposed patch rejects a nonzero sh_addralign that is not a power of
two in elf_validity_cache_sechdrs(), before any layout or allocation,
with -ENOEXEC and:
Invalid ELF section alignment 5 in section 9
On a91337fb5e8e with the patch applied (x86_64 KVM), the control still
loaded and the invalid twin was rejected with ENOEXEC, with no oops or
warning. I will send it as a separate patch.
Tool transparency
=================
The root-cause analysis, original reproducer and proposed fix were
prepared with the help of an AI coding assistant (OpenAI Codex). The
QEMU/KVM reproduction environment and the reproducer used for the
results above were set up with Anthropic's Claude Code. I re-ran the
reproduction myself and have checked the statements in this report.
Thanks,
Srikar