From: Ihor Solodrai <[email protected]>

[ Upstream commit f9d69d5e7bde2295eb7488a56f094ac8f5383b92 ]

The module loader doesn't check for bounds of the ELF section index in
simplify_symbols():

       for (i = 1; i < symsec->sh_size / sizeof(Elf_Sym); i++) {
                const char *name = info->strtab + sym[i].st_name;

                switch (sym[i].st_shndx) {
                case SHN_COMMON:

                [...]

                default:
                        /* Divert to percpu allocation if a percpu var. */
                        if (sym[i].st_shndx == info->index.pcpu)
                                secbase = (unsigned long)mod_percpu(mod);
                        else
  /** HERE --> **/              secbase = 
info->sechdrs[sym[i].st_shndx].sh_addr;
                        sym[i].st_value += secbase;
                        break;
                }
        }

A symbol with an out-of-bounds st_shndx value, for example 0xffff
(known as SHN_XINDEX or SHN_HIRESERVE), may cause a kernel panic:

  BUG: unable to handle page fault for address: ...
  RIP: 0010:simplify_symbols+0x2b2/0x480
  ...
  Kernel panic - not syncing: Fatal exception

This can happen when module ELF is legitimately using SHN_XINDEX or
when it is corrupted.

Add a bounds check in simplify_symbols() to validate that st_shndx is
within the valid range before using it.

This issue was discovered due to a bug in llvm-objcopy, see relevant
discussion for details [1].

[1] 
https://lore.kernel.org/linux-modules/[email protected]/

Signed-off-by: Ihor Solodrai <[email protected]>
Reviewed-by: Daniel Gomez <[email protected]>
Reviewed-by: Petr Pavlu <[email protected]>
Signed-off-by: Sami Tolvanen <[email protected]>
Signed-off-by: Sasha Levin <[email protected]>
---

LLM Generated explanations, may be completely bogus:

Now I have all the information needed for a thorough analysis.

## Analysis

### 1. Commit Message Analysis
The commit message is clear and detailed: it fixes a **kernel panic**
(page fault) in `simplify_symbols()` when a symbol's `st_shndx` (ELF
section header index) is out of bounds. The commit includes a concrete
crash trace (`BUG: unable to handle page fault`, `Kernel panic - not
syncing: Fatal exception`). The bug was discovered via a real-world
trigger (llvm-objcopy bug) with a linked mailing list discussion.

### 2. Code Change Analysis
The fix adds a **6-line bounds check** before the out-of-bounds array
access at line 1582:
```c
if (sym[i].st_shndx >= info->hdr->e_shnum) {
    pr_err(...);
    ret = -ENOEXEC;
    break;
}
```

Without this check, `info->sechdrs[sym[i].st_shndx]` can access memory
far beyond the allocated `sechdrs` array, causing a page fault and
kernel panic. Values like `SHN_XINDEX` (0xffff) or any corrupted value
>= `e_shnum` trigger this.

### 3. Bug Severity
- **Impact**: Kernel panic (crash) - the most severe possible outcome
- **Trigger**: Loading a module with an out-of-bounds `st_shndx` value,
  which can happen legitimately (SHN_XINDEX) or via corrupted/malicious
  ELF
- **Security angle**: This is an out-of-bounds read that could
  potentially be exploited for information disclosure or denial of
  service by anyone who can load kernel modules

### 4. Code History
The vulnerable code has existed since Linux 2.6.12 (2005), with the
specific `sechdrs` access pattern dating to Rusty Russell's 2010
refactoring. The bug is present in **every stable kernel tree**.

### 5. Fix Quality
- **Small and surgical**: 6 lines added, no lines removed, single file
  changed
- **Obviously correct**: Simple bounds check comparing `st_shndx`
  against `e_shnum`
- **Pattern consistent**: The `apply_relocations()` function right below
  (line 1597) already does equivalent validation with `infosec >=
  info->hdr->e_shnum`
- **Reviewed**: Has `Reviewed-by` from Daniel Gomez (Samsung) and Petr
  Pavlu (SUSE)
- **No side effects**: Returns `-ENOEXEC` on invalid module, which is
  the correct error

### 6. Backport Risk
- **Minimal**: The fix is a pure addition of a bounds check before
  existing code
- **No dependencies**: Standalone fix, no prerequisites
- **Clean backport**: The surrounding code is stable and unchanged for
  many years; this should apply cleanly to all stable trees

### 7. Stable Kernel Rules Compliance
- Obviously correct and tested: Yes (reviewed by two engineers, clear
  logic)
- Fixes a real bug: Yes (kernel panic)
- Important issue: Yes (crash/panic, potential security issue)
- Small and contained: Yes (6 lines, 1 file)
- No new features: Correct (pure safety check)

### Verification

- **git blame** confirmed the vulnerable line (`secbase =
  info->sechdrs[sym[i].st_shndx].sh_addr`) has existed since commit
  `49668688dd5a5f` by Rusty Russell (2010), with the overall pattern
  from `1da177e4c3f41` (2005)
- **Read of main.c:1570-1589** confirmed the fix is correctly placed
  before the array access in the `default` case
- **Read of main.c:1591-1598** confirmed `apply_relocations()` already
  validates section indices with the same pattern (`infosec >=
  info->hdr->e_shnum`)
- **git log** confirmed this fix commit is `6186212750892` merged
  2025-12-30
- **Explored** `is_core_symbol()` in kallsyms.c which has an equivalent
  `st_shndx >= shnum` check, confirming the pattern is well-established
- **git log** on stable tree versions confirmed the vulnerable code path
  exists in all current stable trees

This is a textbook stable backport candidate: a small, surgical fix for
a kernel panic caused by a missing bounds check on an array access, in
code that has been vulnerable for 20 years, with clear real-world
triggers (llvm-objcopy bug) and proper review.

**YES**

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

diff --git a/kernel/module/main.c b/kernel/module/main.c
index 710ee30b3beab..5bf456fad63e1 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1568,6 +1568,13 @@ static int simplify_symbols(struct module *mod, const 
struct load_info *info)
                        break;
 
                default:
+                       if (sym[i].st_shndx >= info->hdr->e_shnum) {
+                               pr_err("%s: Symbol %s has an invalid section 
index %u (max %u)\n",
+                                      mod->name, name, sym[i].st_shndx, 
info->hdr->e_shnum - 1);
+                               ret = -ENOEXEC;
+                               break;
+                       }
+
                        /* Divert to percpu allocation if a percpu var. */
                        if (sym[i].st_shndx == info->index.pcpu)
                                secbase = (unsigned long)mod_percpu(mod);
-- 
2.51.0


Reply via email to