[PATCH] elf: fix pldd on targets that map objects above 2^63 [BZ #34641]

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Wed Sep 16 19:01:09 GMT 2026



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);
  }

>  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>
>  



More information about the Libc-alpha mailing list