phoff (e_phoff) comes straight from the untrusted file header, and the
sum is computed in unsigned 64-bit with no overflow check.

        * libdwfl/elf-from-memory.c (elf_from_remote_memory): Use an
        overflow-safe bound when checking whether the phdrs are already
        in the initial read.
        * tests/elf-from-memory.c: New file.
        * tests/Makefile.am (check_PROGRAMS): Add elf-from-memory.
        (TESTS): Likewise.
        (elf_from_memory_LDADD): New variable.

Signed-off-by: Matej Smycka <[email protected]>
---
 libdwfl/elf-from-memory.c |   3 +-
 tests/Makefile.am         |   6 +-
 tests/elf-from-memory.c   | 116 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 122 insertions(+), 3 deletions(-)
 create mode 100644 tests/elf-from-memory.c

diff --git a/libdwfl/elf-from-memory.c b/libdwfl/elf-from-memory.c
index 14b2f5c..775d5a1 100644
--- a/libdwfl/elf-from-memory.c
+++ b/libdwfl/elf-from-memory.c
@@ -170,7 +170,8 @@ elf_from_remote_memory (GElf_Addr ehdr_vma,
   xlatefrom.d_type = xlateto.d_type = ELF_T_PHDR;
   xlatefrom.d_size = phnum * phentsize;
 
-  if ((size_t) nread >= phoff + phnum * phentsize)
+  if (phoff <= (size_t) nread
+      && (size_t) phnum * phentsize <= (size_t) nread - phoff)
     /* We already have all the phdrs from the initial read.  */
     xlatefrom.d_buf = buffer + phoff;
   else
diff --git a/tests/Makefile.am b/tests/Makefile.am
index fb0690e..7936ddb 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -68,7 +68,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames 
sectiondump \
                  cu-dwp-section-info declfiles test-manyfuncs \
                  eu_search_cfi eu_search_macros \
                  eu_search_lines eu_search_die \
-                 ar-extract-ar \
+                 ar-extract-ar elf-from-memory \
                  $(asm_TESTS)
 
 asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \
@@ -238,7 +238,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile 
test-nlist \
        run-sysroot.sh \
        run-test-manyfuncs.sh \
        run-eu-search-cfi.sh run-eu-search-macros.sh \
-       run-eu-search-lines.sh run-eu-search-die.sh
+       run-eu-search-lines.sh run-eu-search-die.sh \
+       elf-from-memory
 
 if !BIARCH
 export ELFUTILS_DISABLE_BIARCH = 1
@@ -867,6 +868,7 @@ deleted_lib_so_CFLAGS = $(fpic_CFLAGS) 
-fasynchronous-unwind-tables
 aggregate_size_LDADD = $(libdw) $(libelf) $(argp_LDADD)
 peel_type_LDADD = $(libdw) $(libelf) $(argp_LDADD)
 vdsosyms_LDADD = $(libeu) $(libdw) $(libelf)
+elf_from_memory_LDADD = ../libdw/libdw.a -lz $(zip_LIBS) $(libelf) $(libebl) 
$(libeu) -ldl -lpthread
 getsrc_die_LDADD = $(libeu) $(libdw) $(libelf)
 strptr_LDADD = $(libelf)
 newdata_LDADD = $(libelf)
diff --git a/tests/elf-from-memory.c b/tests/elf-from-memory.c
new file mode 100644
index 0000000..2e99157
--- /dev/null
+++ b/tests/elf-from-memory.c
@@ -0,0 +1,116 @@
+/* Test elf_from_remote_memory phdr bounds handling.
+   Copyright (C) 2026 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <elf.h>
+#include <gelf.h>
+#include <libelf.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+extern Elf *elf_from_remote_memory (GElf_Addr ehdr_vma, GElf_Xword pagesize,
+                                   GElf_Addr *loadbasep,
+                                   ssize_t (*read_memory) (void *, void *,
+                                                           GElf_Addr, size_t,
+                                                           size_t),
+                                   void *arg);
+
+#define BACKSZ 256
+static unsigned char backing[BACKSZ];
+
+static ssize_t
+read_mem (void *arg, void *data, GElf_Addr address, size_t minread,
+         size_t maxread)
+{
+  (void) arg;
+  if (address >= BACKSZ)
+    return 0;
+  size_t avail = BACKSZ - (size_t) address;
+  size_t n = avail < maxread ? avail : maxread;
+  if (n < minread)
+    return 0;
+  memcpy (data, backing + address, n);
+  return (ssize_t) n;
+}
+
+static void
+init_ehdr (Elf64_Ehdr *ehdr)
+{
+  memset (ehdr, 0, sizeof *ehdr);
+  memcpy (ehdr->e_ident, ELFMAG, SELFMAG);
+  ehdr->e_ident[EI_CLASS] = ELFCLASS64;
+  ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
+  ehdr->e_ident[EI_VERSION] = EV_CURRENT;
+  ehdr->e_type = ET_CORE;
+  ehdr->e_machine = EM_X86_64;
+  ehdr->e_version = EV_CURRENT;
+  ehdr->e_phnum = 1;
+  ehdr->e_phentsize = sizeof (Elf64_Phdr);
+}
+
+int
+main (void)
+{
+  Elf64_Ehdr ehdr;
+  int result = 0;
+
+  elf_version (EV_CURRENT);
+
+  init_ehdr (&ehdr);
+  ehdr.e_phoff = (Elf64_Off) (0ULL - (uint64_t) sizeof (Elf64_Phdr));
+  memset (backing, 0, sizeof backing);
+  memcpy (backing, &ehdr, sizeof ehdr);
+  Elf *elf = elf_from_remote_memory (0, 4096, NULL, read_mem, NULL);
+  if (elf != NULL)
+    {
+      printf ("FAIL: wrapping e_phoff did not fail safely\n");
+      elf_end (elf);
+      result = 1;
+    }
+  else
+    printf ("PASS: wrapping e_phoff handled without OOB read\n");
+
+  init_ehdr (&ehdr);
+  ehdr.e_phoff = sizeof (Elf64_Ehdr);
+  Elf64_Phdr phdr;
+  memset (&phdr, 0, sizeof phdr);
+  phdr.p_type = PT_LOAD;
+  phdr.p_filesz = BACKSZ;
+  phdr.p_memsz = BACKSZ;
+  phdr.p_align = 4096;
+  memset (backing, 0, sizeof backing);
+  memcpy (backing, &ehdr, sizeof ehdr);
+  memcpy (backing + ehdr.e_phoff, &phdr, sizeof phdr);
+  elf = elf_from_remote_memory (0, 4096, NULL, read_mem, NULL);
+  GElf_Ehdr got;
+  if (elf == NULL || gelf_getehdr (elf, &got) == NULL || got.e_phnum != 1)
+    {
+      printf ("FAIL: legitimate image rejected\n");
+      result = 1;
+    }
+  else
+    printf ("PASS: legitimate image accepted\n");
+  if (elf != NULL)
+    elf_end (elf);
+
+  return result;
+}
-- 
2.53.0

Reply via email to