[PATCH] elf: fix pldd on targets that map objects above 2^63 [BZ #34641]
Stian Halseth
stian@itx.no
Thu Sep 17 08:10:14 GMT 2026
Hi again,
One thing (perhaps) worth noting.
On sparc64 glibc's lseek returns a truncated value for these high
offsets (0x200b38 for 0xfff8000100200b38), with errno unset and the
seek itself working. It doesn't affect this patch, since the return is
only compared against (off_t) -1, but maybe a separate lseek issue?
Patch v2 coming along regardless, just wanted to mention it because I
saw it.
On Thu, 2026-09-17 at 10:02 +0200, Stian Halseth wrote:
> Hi,
>
> Ignore my email regarding the rationale on process_vm_readv. I hadn't
> seen this response before I wrote it.
>
>
> On Wed, 2026-09-16 at 16:01 -0300, Adhemerval Zanella Netto wrote:
> >
> >
> > On 16/09/26 15:02, Stian Halseth wrote:
> > > 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);
> > > +}
> > > +
> >
> > The process_vm_readv was not added at same time on all ABIs.
> > Although
> > for mostly
> > kABI it was done on 3.2, for arm32 compat (aarch64 kernel) it was
> > done on 3.7,
> > on hppa at 3.9.
> >
> > Maybe we can just lseek/read:
> >
> > static ssize_t
> > pread_mem (int fd, void *buf, size_t nbytes, uint64_t addr)
> > {
> > if (lseek (fd, (off_t) addr, SEEK_SET) == (off_t) -1)
> > return -1;
> >
> > return read (fd, buf, nbytes);
> > }
> >
>
> Right, I missed that, and you're right. I have tested your
> suggestion.
> It works and is much cleaner. Sending patch v2 now.
>
> > > 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>
> > >
> >
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 228 bytes
Desc: This is a digitally signed message part
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20260917/8bf25559/attachment-0001.sig>
More information about the Libc-alpha
mailing list