[PATCH] elf: fix pldd on targets that map objects above 2^63 [BZ #34641]
Stian Halseth
stian@itx.no
Wed Sep 16 18:02:55 GMT 2026
pldd reads target's memory with pread on /proc/PID/mem. The offset
argument of pread is signed, and ksys_pread64 rejects a negative
position. On sparc64 the dynamic linker and the shared objects are
mapped at 0xfff8000100000000 and above, so every such read fails with
EINVAL and pldd exits with "cannot read r_debug". elf/tst-pldd fails
as a result. This is not visible on x86_64, where objects are mapped
low.
Route the reads through a helper that tries pread first and, when it
fails with EINVAL, falls back to process_vm_readv, which takes the
address as a pointer. pread stays the primary path so that a 32-bit
pldd can still read a 64-bit target, which process_vm_readv cannot do
once the address no longer fits a pointer; the fallback is taken only
when it does fit. pldd already attaches with ptrace, so the permissions
are in place, and both calls report a partial transfer as a short count,
which the object-name loop in find_maps relies on.
Tested on sparc64: pldd fails on every process before this change and
lists them correctly after it. Under strace the reads of the main
executable, which is mapped low, go through pread, and the reads that
hit EINVAL, those into ld.so and libc above 2^63, are retried with
process_vm_readv.
Signed-off-by: Stian Halseth <stian@itx.no>
---
elf/pldd-xx.c | 27 +++++++++++++++++++++------
elf/pldd.c | 3 +++
2 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/elf/pldd-xx.c b/elf/pldd-xx.c
index 29bbb7307..073b145f9 100644
--- a/elf/pldd-xx.c
+++ b/elf/pldd-xx.c
@@ -73,6 +73,21 @@ _Static_assert (offsetof (struct r_debug, r_map)
#endif
+/* Read LEN bytes at ADDR in process PID into BUF. */
+static ssize_t
+E(read_mem) (int memfd, pid_t pid, void *buf, size_t len, EW(Addr) addr)
+{
+ ssize_t n = pread (memfd, buf, len, addr);
+ if (n != -1)
+ return n;
+ if (errno != EINVAL || (EW(Addr)) (uintptr_t) addr != addr)
+ return -1;
+ struct iovec local = { .iov_base = buf, .iov_len = len };
+ struct iovec remote = { .iov_base = (void *) (uintptr_t) addr,
+ .iov_len = len };
+ return process_vm_readv (pid, &local, 1, &remote, 1, 0);
+}
+
static int
E(find_maps) (const char *exe, int memfd, pid_t pid, void *auxv,
@@ -103,7 +118,7 @@ E(find_maps) (const char *exe, int memfd, pid_t pid, void *auxv,
error (EXIT_FAILURE, 0, gettext ("cannot find program header of process"));
EW(Phdr) *p = xmalloc (phnum * phent);
- if (pread (memfd, p, phnum * phent, phdr) != phnum * phent)
+ if (E(read_mem) (memfd, pid, p, phnum * phent, phdr) != phnum * phent)
error (EXIT_FAILURE, 0, gettext ("cannot read program header"));
/* Determine the load offset. We need this for interpreting the
@@ -124,7 +139,7 @@ E(find_maps) (const char *exe, int memfd, pid_t pid, void *auxv,
if (p[i].p_type == PT_DYNAMIC)
{
EW(Dyn) *dyn = xmalloc (p[i].p_filesz);
- if (pread (memfd, dyn, p[i].p_filesz, offset + p[i].p_vaddr)
+ if (E(read_mem) (memfd, pid, dyn, p[i].p_filesz, offset + p[i].p_vaddr)
!= p[i].p_filesz)
error (EXIT_FAILURE, 0, gettext ("cannot read dynamic section"));
@@ -136,7 +151,7 @@ E(find_maps) (const char *exe, int memfd, pid_t pid, void *auxv,
if (off != 0)
{
struct E(r_debug) r;
- if (pread (memfd, &r, sizeof (r), off)
+ if (E(read_mem) (memfd, pid, &r, sizeof (r), off)
!= sizeof (r))
error (EXIT_FAILURE, 0, gettext ("cannot read r_debug"));
@@ -154,7 +169,7 @@ E(find_maps) (const char *exe, int memfd, pid_t pid, void *auxv,
else if (p[i].p_type == PT_INTERP)
{
interp = xmalloc (p[i].p_filesz);
- if (pread (memfd, interp, p[i].p_filesz, offset + p[i].p_vaddr)
+ if (E(read_mem) (memfd, pid, interp, p[i].p_filesz, offset + p[i].p_vaddr)
!= p[i].p_filesz)
error (EXIT_FAILURE, 0, gettext ("cannot read program interpreter"));
}
@@ -184,13 +199,13 @@ E(find_maps) (const char *exe, int memfd, pid_t pid, void *auxv,
do
{
struct E(link_map) m;
- if (pread (memfd, &m, sizeof (m), list) != sizeof (m))
+ if (E(read_mem) (memfd, pid, &m, sizeof (m), list) != sizeof (m))
error (EXIT_FAILURE, 0, gettext ("cannot read link map"));
EW(Addr) name_offset = m.l_name;
while (1)
{
- ssize_t n = pread (memfd, tmpbuf.data, tmpbuf.length, name_offset);
+ ssize_t n = E(read_mem) (memfd, pid, tmpbuf.data, tmpbuf.length, name_offset);
if (n == -1)
error (EXIT_FAILURE, 0, gettext ("cannot read object name"));
diff --git a/elf/pldd.c b/elf/pldd.c
index 62e660c3f..762770026 100644
--- a/elf/pldd.c
+++ b/elf/pldd.c
@@ -27,6 +27,9 @@
#include <stdlib.h>
#include <unistd.h>
#include <sys/ptrace.h>
+#include <sys/uio.h>
+#include <stdint.h>
+#include <errno.h>
#include <sys/wait.h>
#include <scratch_buffer.h>
--
2.43.0
More information about the Libc-alpha
mailing list