mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [Bug report] x86-64 module loader: relocation r_offset and symbol index are used without bounds checks - OOB write on module load
@ 2026-11-13  4:00 Guikun Yang
  0 siblings, 0 replies; only message in thread
From: Guikun Yang @ 2026-11-13  4:00 UTC (permalink / raw)
  To: Luis Chamberlain
  Cc: Petr Pavlu, Sami Tolvanen, Igor Solodrai, linux-modules, linux-kernel

From: Guikun Yang <yangguikun19@qq.com>
To: Luis Chamberlain <mcgrof@kernel.org>
Cc: Petr Pavlu <petr.pavlu@suse.com>, Sami Tolvanen <samitolvanen@google.com>,
    Igor Solodrai <ihor.solodrai@linux.dev>, linux-modules@vger.kernel.org,
    linux-kernel@vger.kernel.org
Subject: [Bug report] x86-64 module loader: relocation r_offset and symbol
 index are used without bounds checks - OOB write on module load

Hi Luis, Petr, Igor,

Loading a crafted x86-64 module where one relocation entry's r_offset
(or its symbol index) points outside the target section ends in a wild
read and, for a target that is mapped and zero-filled, a controlled
out-of-bounds write. Both the ELF validity check and
__write_relocate_add() itself leave these two fields completely
unchecked. A/B reproducer below.

  Oops: general protection fault, probably for non-canonical address
  0xe00013fff8040000: 0000 [#1] SMP DEBUG_PAGEALLOC KASAN NOPTI
  KASAN: maybe wild-memory-access in range [0x0000bfffc0200000-...]
  RIP: memcmp+0x67/0x1d0
  RDI: 0000bfffc0200000
  Call Trace:
   apply_relocate_add+0x20e
   load_module+0x40f7
   __do_sys_init_module

## The gap

__write_relocate_add() (arch/x86/kernel/module.c) takes the target
address and symbol pointer straight from the relocation entry:

  loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr
          + rel[i].r_offset;

  sym = (Elf64_Sym *)sechdrs[symindex].sh_addr
          + ELF64_R_SYM(rel[i].r_info);

  ...
  if (memcmp(loc, &zero, size))   /* wild read */
  ...
  write(loc, &val, size);         /* val = sym->st_value + r_addend */

r_offset is a 64-bit value from the module file, with no check against
the target section size. r_info's symbol index is likewise unchecked
against the symbol table entry count. apply_relocations() in
kernel/module/main.c only verifies infosec < e_shnum and SHF_ALLOC.

The ELF validity layer does not cover these either:
elf_validity_cache_sechdrs() validates sh_offset/sh_size (file-side,
via validate_section_offset()), and the recent series added checks for
section types and the symbol string table - but nothing bounds the
per-entry r_offset or the symbol index of the relocation entries.

## This is the same pattern that f9d69d5e7b fixed for st_shndx

f9d69d5e7b ("module: Fix kernel panic when a symbol st_shndx is out of
bounds", December 2025) added a bounds check for st_shndx indexing
sechdrs[] in simplify_symbols():

  if (sym[i].st_shndx >= info->hdr->e_shnum) { ... return -ENOEXEC; }

That check is in the current tree. The relocation entries in
__write_relocate_add() index memory the same way (an ELF-controlled
offset into an array base) and are still unchecked:

  simplify_symbols():   st_shndx -> sechdrs[st_shndx]   /* bounded */
  __write_relocate_add(): r_offset -> base + r_offset    /* unbounded */

391e982bfa (2022) already accepted that a crafted module file is a
realistic input ("It is trivial to craft a module to trigger OOB
access"), and the elf_validity_check() series exists precisely to
defend against malformed ELF. The relocation entries are the part of
that input that the validity layer does not reach.

## Impact: more than a crash

The memcmp(loc, &zero, size) / write(loc, &val, size) pair means the
loader reads the target first and only writes if it is zero. With a
non-canonical r_offset this is a deterministic #GP (reproduced). With
a r_offset aimed at a mapped, zero-filled kernel address, the write
lands - val is sym->st_value + r_addend, both attacker-controlled
within the module. Since init_module() can be retried with different
r_offset values, this is a usable probe-and-write primitive against
kernel memory, not just a crash.

## Reproduction

The carrier module is a real xt_mark.ko built from the same tree as
the test kernel (vermagic, this_module size, all 11 undefined symbols
matching), so the only variable is the eight modified bytes
(.rela.text[0].r_offset at file offset 0x17a0, changed to
0xc00000000000 so loc lands in the non-canonical range):

  before: init_module -> 0, delete_module -> 0 (module loads, unloads)
  after:  init_module -> Oops (trace above)

Same-tree build, same boot, single field changed. The A/B pair and
the init_reloc.c reproducer are available on request.

## Suggested fix

In apply_relocations(), before dispatching to per-arch
apply_relocate_add(), verify each entry points inside its target
section and its symbol index inside the symbol table. This covers all
architectures (arm64, powerpc, riscv, s390, loongarch have the same
raw `base + rel[i].r_offset` form):

  for (i = 1; i < info->hdr->e_shnum; i++) {
          ...
          if (info->sechdrs[i].sh_type == SHT_REL ||
              info->sechdrs[i].sh_type == SHT_RELA) {
                  for (j = 0; j < sechdrs[i].sh_size / sizeof(*rel); j++) {
                          /* bounds per entry, arch-dependent size */
                          ...
                  }
          }
  }

or, minimally, a per-arch check at the top of each relocation loop:

  loc_off = rel[i].r_offset;
  if (loc_off > sechdrs[sechdrs[relsec].sh_info].sh_size - sizeof(u64))
          return -ENOEXEC;
  if (ELF_R_SYM(rel[i].r_info) >=
      sechdrs[symindex].sh_size / sizeof(Elf_Sym))
          return -ENOEXEC;

(The minimal per-arch form matches how f9d69d5e7b added the st_shndx
bound locally in simplify_symbols().)

Tested on 7.3.0; arch/x86/kernel/module.c is unchanged in mainline as
of 2026-11-13.

Thanks,
Guikun Yang


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-11-13 14:09 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-13  4:00 [Bug report] x86-64 module loader: relocation r_offset and symbol index are used without bounds checks - OOB write on module load Guikun Yang

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®