[Devel] [PATCH hci-8.1 0/2] mm, binfmt_elf: two fixes also posted to mainline

Konstantin Khorenko khorenko at virtuozzo.com
Wed Oct 14 20:03:18 MSK 2026


Two independent fixes for bugs that mainline has as well. Both are
posted to mainline; the backport notes name what differs in this tree.
The MADV_DOEXEC series that follow (2 to 5) apply on top of this one,
and series 4 relies on patch 1.

Mainline
========

- Patch 1 is patch 1/2 of "mm/huge_memory: fix the copy of special
  PMDs at fork", v3:
  https://lore.kernel.org/r/20261014-b4-prep-vstor-149847-copy_huge_pmd-recheck-special-pmd-v3-0-85eaec8417f6@virtuozzo.com
  Sashiko review:
  https://sashiko.dev/#/patchset/20261014-b4-prep-vstor-149847-copy_huge_pmd-recheck-special-pmd-v3-0-85eaec8417f6@virtuozzo.com
  The Sashiko review of v1 found the ppc64 issue that patch 2/2 there
  fixes:
  https://sashiko.dev/#/patchset/20261014-b4-prep-vstor-149847-copy_huge_pmd-recheck-special-pmd-v1-1-d225c60bc316@virtuozzo.com
- Patch 2 is "binfmt_elf: return the real error when program headers
  cannot be loaded", v1:
  https://lore.kernel.org/r/20261014-b4-prep-vstor-149847-binfmt_elf-enomem-load-elf-phdrs-v1-1-f39c74d55e42@virtuozzo.com
  Sashiko review:
  https://sashiko.dev/#/patchset/20261014-b4-prep-vstor-149847-binfmt_elf-enomem-load-elf-phdrs-v1-1-f39c74d55e42@virtuozzo.com

Patch 1: copy_huge_pmd() recheck
================================

fork copies a special (PFN) huge PMD from a value read without the page
table lock. vfio-pci zaps its BAR mappings with unmap_mapping_range(),
which takes no mmap lock, when device memory is disabled or the device
is reset. A fork that races with that zap leaves the child with a BAR
mapping the zap removed: the child keeps reaching MMIO that vfio-pci has
disabled instead of getting SIGBUS. Series 4 copies these PMDs on every
live-update exec.

In mainline this patch is now the first of two. The second deposits a
page table for the copied PMD on ppc64 in hash mode; it is not needed
here, as this tree has no huge pfnmap support on ppc64.

Patch 2: the real error from load_elf_phdrs()
=============================================

load_elf_phdrs() returns NULL on any failure, and load_elf_binary()
turns that into a fixed error: -ENOEXEC for the executable, -ELIBBAD
for its interpreter. The real cause is lost:

  cause                                  really      execve() returns
  -------------------------------------  ----------  ----------------
  bad e_phentsize, e_phnum 0 or too big  bad file    ENOEXEC (right)
  file ends before the header table      bad file    ENOEXEC (right)
  kmalloc() of the table fails           ENOMEM      ENOEXEC (wrong)
  kernel_read() fails                    EIO/ENOMEM  ENOEXEC (wrong)

For the interpreter every row gives ELIBBAD.

ENOEXEC says the file is not an executable, which is a property of the
file, not a transient failure:

- glibc's execvp() and execlp() take ENOEXEC as "not a binary" and run
  the ELF file with /bin/sh, which reports a binary it cannot execute
  instead of the ENOMEM or EIO.
- search_binary_handler() tries the next binary format instead of
  returning the error.
- For the interpreter the caller gets ELIBBAD, "Accessing a corrupted
  shared library", about an ld.so that is fine.

When it happens here: this tree lacks the v6.17 change that lets the
table grow past ELF_MIN_ALIGN, so the allocation fails in practice only
under fault injection; the read fails with an I/O error, or with
-ENOMEM when the page cache cannot take the pages.

Where it came from:

- Until commit 6a8d38945cf4 ("binfmt_elf: Hoist ELF program header
  loading to a function") load_elf_binary() returned -ENOMEM and the
  read error. That commit documented an ERR_PTR return but returned
  NULL; commit cfc46ca4fdca later fixed the comment, not the code.
- Commit a9d9ef133f44 ("binfmt_elf: load interpreter program headers
  earlier") gave the interpreter's headers the same NULL check, and with
  it -ELIBBAD.

Testing
=======

On a KVM guest with the whole stack (series 1 to 5), release and debug
kernels:
- a fork-vs-revoke reproducer on a vfio-pci BAR (QXL in no-IOMMU mode):
  no stale child mapping; on mainline the same reproducer with a delay
  in the window shows the bug and that the patch closes it;
- failslab sweep over one execve(): ENOMEM instead of ENOEXEC/ELIBBAD,
  execvp() no longer falls back to /bin/sh; a header table whose second
  page fails to read gives EIO; truncated tables and e_phoff = 2^63
  still give ENOEXEC/ELIBBAD;
- selftests/exec, the MADV_DOEXEC selftests, gup_test, a Container
  start/exec/stop.

https://virtuozzo.atlassian.net/browse/VSTOR-150144

---
Konstantin Khorenko (2):
      mm/huge_memory: recheck a special PMD under the lock in copy_huge_pmd()
      binfmt_elf: return the real error when program headers cannot be loaded

 fs/binfmt_elf.c  | 51 +++++++++++++++++++++++++++++++++------------------
 mm/huge_memory.c | 23 ++++++++++++++++-------
 2 files changed, 49 insertions(+), 25 deletions(-)
---
base-commit: defa968cbe8c38f1ff0231042dee07f00d27b2c9
change-id: 20261014-b4-prep-khorenko-vstor-149847-1-upstream-fixes-4bb22a75a079

Best regards,
--  
Konstantin Khorenko <khorenko at virtuozzo.com>



More information about the Devel mailing list