diff mbox series

[v6,1/1] elf: Handle static PIE loaded at the non-zero address [BZ #31799]

Message ID CAMe9rOqX-T9zLysCM3Gm0DttmgbqySYdZOR14dvYw9b2DHcv3A@mail.gmail.com
State New
Headers show
Series [v6,1/1] elf: Handle static PIE loaded at the non-zero address [BZ #31799] | expand

Commit Message

H.J. Lu Oct. 25, 2024, 10:18 p.m. UTC
On Wed, Oct 23, 2024 at 10:30 PM Noah Goldstein <goldstein.w.n@gmail.com> wrote:
...
> >>>> >> +LIBC_CONFIG_VAR([pde-load-address], [$libc_cv_pde_load_address])
> >>>> >> +
> >>>> >> +LIBC_LINKER_FEATURE([-Ttext-segment=$libc_cv_pde_load_address],
> >>>> >> +                   [-Wl,-Ttext-segment=$libc_cv_pde_load_address],
> >>>> >> +                   [libc_cv_load_address=-Wl,-Ttext-segment],
> >>>> >> +                   [libc_cv_load_address=])
> >>>> >> +LIBC_CONFIG_VAR([load-address-ldflag], [$libc_cv_load_address])
> >>>> >> +
>
> IIUC, all of the new conf code is for setting up tests.
> Can you make that more clear in either comment or var names?

Done.

> >>>> >>  AC_MSG_CHECKING(if we can build programs as PIE)
> >>>> >>  AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#ifdef PIE_UNSUPPORTED
> >>>> >>  # error PIE is not supported
> >>>> >> diff --git a/elf/Makefile b/elf/Makefile
> >>>> >> index 57b3a19d36..4e2d123b8e 100644
> >>>> >> --- a/elf/Makefile
> >>>> >> +++ b/elf/Makefile
> >>>> >> @@ -1046,6 +1046,25 @@ tests-pie += \
> >>>> >>    tst-pie1 \
> >>>> >>    tst-pie2 \
> >>>> >>    # tests-pie
> >>>> >> +ifneq (,$(load-address-ldflag))
> >>>> >> +tests += \
> >>>> >> +  tst-pie-address \
> >>>> >> +  # tests
> >>>> >> +tests-pie += \
> >>>> >> +  tst-pie-address \
> >>>> >> +  # tests-pie
> >>>> >> +LDFLAGS-tst-pie-address += $(load-address-ldflag)=$(pde-load-address)
> >>>> >> +ifeq (yes,$(enable-static-pie))
> >>>> >> +tests += \
> >>>> >> +  tst-pie-address-static \
> >>>> >> +  # tests
> >>>> >> +tests-static += \
> >>>> >> +  tst-pie-address-static \
> >>>> >> +  # tests-static
> >>>> >> +LDFLAGS-tst-pie-address-static += \
> >>>> >> +  $(load-address-ldflag)=$(pde-load-address)
> >>>> >> +endif
> >>>> >> +endif
> >>>> >>  ifeq (yes,$(have-protected-data))
> >>>> >>  tests += vismain
> >>>> >>  tests-pie += vismain
> >>>> >> @@ -1896,6 +1915,7 @@ $(objpfx)tst-array5-static-cmp.out: tst-array5-static.exp \
> >>>> >>
> >>>> >>  CFLAGS-tst-pie1.c += $(pie-ccflag)
> >>>> >>  CFLAGS-tst-pie2.c += $(pie-ccflag)
> >>>> >> +CFLAGS-tst-pie-address.c += $(pie-ccflag)
> >>>> >>
> >>>> >>  $(objpfx)tst-piemod1.so: $(libsupport)
> >>>> >>  $(objpfx)tst-pie1: $(objpfx)tst-piemod1.so
> >>>> >> diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
> >>>> >> index 10c23d0bf0..6286f19f1f 100644
> >>>> >> --- a/elf/dl-reloc-static-pie.c
> >>>> >> +++ b/elf/dl-reloc-static-pie.c
> >>>> >> @@ -37,21 +37,32 @@ _dl_relocate_static_pie (void)
> >>>> >>  {
> >>>> >>    struct link_map *main_map = _dl_get_dl_main_map ();
> >>>> >>
> >>>> >> -  /* Figure out the run-time load address of static PIE.  */
> >>>> >> -  main_map->l_addr = elf_machine_load_address ();
> >>>> >> -
> >>>> >> -  /* Read our own dynamic section and fill in the info array.  */
> >>>> >> -  main_map->l_ld = ((void *) main_map->l_addr + elf_machine_dynamic ());
> >>>> >> -
> >>>> >> +  ElfW(Addr) file_p_vaddr = 0;
> >>>> >>    const ElfW(Phdr) *ph, *phdr = GL(dl_phdr);
> >>>> >>    size_t phnum = GL(dl_phnum);
> >>>> >>    for (ph = phdr; ph < &phdr[phnum]; ++ph)
> >>>> >> -    if (ph->p_type == PT_DYNAMIC)
> >>>> >> +    switch (ph->p_type)
> >>>> >>        {
> >>>> >> +      case PT_LOAD:
> >>>> >> +       /* Get p_vaddr of the PT_LOAD segement which covers the file
> >>>> >> +          start.  */
> >>>> >> +       if (ph->p_offset == 0)
> >>>> >> +         file_p_vaddr = ph->p_vaddr;
> >>>> >> +       break;
> >>>> >> +      case PT_DYNAMIC:
> >>>> >>         main_map->l_ld_readonly = (ph->p_flags & PF_W) == 0;
> >>>> >>         break;
> >>>> >> +      default:
> >>>> >> +       break;
> >>>> >>        }
> >>>> >>
> >>>> >> +  /* Figure out the run-time load address of static PIE.  */
> >>>> >> +  ElfW(Addr) l_addr = elf_machine_load_address ();
> >>>> >> +  main_map->l_addr = l_addr - file_p_vaddr;
>
> Can you explain this? I though vaddr field was the true virtual-address
> but you seem to be treating it as an offset here.

The l_addr field contains the difference between the address in the ELF
file and the address in memory.  file_p_vaddr is used as the address in
static PIE.  I updated comments in the v6 patch,

Here is the v6 patch.  OK to install?

Thanks.

Comments

Noah Goldstein Oct. 26, 2024, 6:49 p.m. UTC | #1
On Fri, Oct 25, 2024 at 5:18 PM H.J. Lu <hjl.tools@gmail.com> wrote:
>
> On Wed, Oct 23, 2024 at 10:30 PM Noah Goldstein <goldstein.w.n@gmail.com> wrote:
> ...
> > >>>> >> +LIBC_CONFIG_VAR([pde-load-address], [$libc_cv_pde_load_address])
> > >>>> >> +
> > >>>> >> +LIBC_LINKER_FEATURE([-Ttext-segment=$libc_cv_pde_load_address],
> > >>>> >> +                   [-Wl,-Ttext-segment=$libc_cv_pde_load_address],
> > >>>> >> +                   [libc_cv_load_address=-Wl,-Ttext-segment],
> > >>>> >> +                   [libc_cv_load_address=])
> > >>>> >> +LIBC_CONFIG_VAR([load-address-ldflag], [$libc_cv_load_address])
> > >>>> >> +
> >
> > IIUC, all of the new conf code is for setting up tests.
> > Can you make that more clear in either comment or var names?
>
> Done.

Hmm? This doesn't seem done. I mean can you make it explicit
these config options are only used for tests internal to glibc.
>
> > >>>> >>  AC_MSG_CHECKING(if we can build programs as PIE)
> > >>>> >>  AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#ifdef PIE_UNSUPPORTED
> > >>>> >>  # error PIE is not supported
> > >>>> >> diff --git a/elf/Makefile b/elf/Makefile
> > >>>> >> index 57b3a19d36..4e2d123b8e 100644
> > >>>> >> --- a/elf/Makefile
> > >>>> >> +++ b/elf/Makefile
> > >>>> >> @@ -1046,6 +1046,25 @@ tests-pie += \
> > >>>> >>    tst-pie1 \
> > >>>> >>    tst-pie2 \
> > >>>> >>    # tests-pie
> > >>>> >> +ifneq (,$(load-address-ldflag))
> > >>>> >> +tests += \
> > >>>> >> +  tst-pie-address \
> > >>>> >> +  # tests
> > >>>> >> +tests-pie += \
> > >>>> >> +  tst-pie-address \
> > >>>> >> +  # tests-pie
> > >>>> >> +LDFLAGS-tst-pie-address += $(load-address-ldflag)=$(pde-load-address)
> > >>>> >> +ifeq (yes,$(enable-static-pie))
> > >>>> >> +tests += \
> > >>>> >> +  tst-pie-address-static \
> > >>>> >> +  # tests
> > >>>> >> +tests-static += \
> > >>>> >> +  tst-pie-address-static \
> > >>>> >> +  # tests-static
> > >>>> >> +LDFLAGS-tst-pie-address-static += \
> > >>>> >> +  $(load-address-ldflag)=$(pde-load-address)
> > >>>> >> +endif
> > >>>> >> +endif
> > >>>> >>  ifeq (yes,$(have-protected-data))
> > >>>> >>  tests += vismain
> > >>>> >>  tests-pie += vismain
> > >>>> >> @@ -1896,6 +1915,7 @@ $(objpfx)tst-array5-static-cmp.out: tst-array5-static.exp \
> > >>>> >>
> > >>>> >>  CFLAGS-tst-pie1.c += $(pie-ccflag)
> > >>>> >>  CFLAGS-tst-pie2.c += $(pie-ccflag)
> > >>>> >> +CFLAGS-tst-pie-address.c += $(pie-ccflag)
> > >>>> >>
> > >>>> >>  $(objpfx)tst-piemod1.so: $(libsupport)
> > >>>> >>  $(objpfx)tst-pie1: $(objpfx)tst-piemod1.so
> > >>>> >> diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
> > >>>> >> index 10c23d0bf0..6286f19f1f 100644
> > >>>> >> --- a/elf/dl-reloc-static-pie.c
> > >>>> >> +++ b/elf/dl-reloc-static-pie.c
> > >>>> >> @@ -37,21 +37,32 @@ _dl_relocate_static_pie (void)
> > >>>> >>  {
> > >>>> >>    struct link_map *main_map = _dl_get_dl_main_map ();
> > >>>> >>
> > >>>> >> -  /* Figure out the run-time load address of static PIE.  */
> > >>>> >> -  main_map->l_addr = elf_machine_load_address ();
> > >>>> >> -
> > >>>> >> -  /* Read our own dynamic section and fill in the info array.  */
> > >>>> >> -  main_map->l_ld = ((void *) main_map->l_addr + elf_machine_dynamic ());
> > >>>> >> -
> > >>>> >> +  ElfW(Addr) file_p_vaddr = 0;
> > >>>> >>    const ElfW(Phdr) *ph, *phdr = GL(dl_phdr);
> > >>>> >>    size_t phnum = GL(dl_phnum);
> > >>>> >>    for (ph = phdr; ph < &phdr[phnum]; ++ph)
> > >>>> >> -    if (ph->p_type == PT_DYNAMIC)
> > >>>> >> +    switch (ph->p_type)
> > >>>> >>        {
> > >>>> >> +      case PT_LOAD:
> > >>>> >> +       /* Get p_vaddr of the PT_LOAD segement which covers the file
> > >>>> >> +          start.  */
> > >>>> >> +       if (ph->p_offset == 0)
> > >>>> >> +         file_p_vaddr = ph->p_vaddr;
> > >>>> >> +       break;
> > >>>> >> +      case PT_DYNAMIC:
> > >>>> >>         main_map->l_ld_readonly = (ph->p_flags & PF_W) == 0;
> > >>>> >>         break;
> > >>>> >> +      default:
> > >>>> >> +       break;
> > >>>> >>        }
> > >>>> >>
> > >>>> >> +  /* Figure out the run-time load address of static PIE.  */
> > >>>> >> +  ElfW(Addr) l_addr = elf_machine_load_address ();
> > >>>> >> +  main_map->l_addr = l_addr - file_p_vaddr;
> >
> > Can you explain this? I though vaddr field was the true virtual-address
> > but you seem to be treating it as an offset here.
>
> The l_addr field contains the difference between the address in the ELF
> file and the address in memory.  file_p_vaddr is used as the address in
> static PIE.  I updated comments in the v6 patch,
>
Okay, I think I understand. We were implicitly assuming vaddr was zero
before by just using l_addr (v_addr + offset == true_addr if v_addr is zero).
Is that correct?

> Here is the v6 patch.  OK to install?
>
> Thanks.
>
> --
> H.J.
H.J. Lu Oct. 26, 2024, 10:02 p.m. UTC | #2
On Sun, Oct 27, 2024 at 2:49 AM Noah Goldstein <goldstein.w.n@gmail.com> wrote:
>
> On Fri, Oct 25, 2024 at 5:18 PM H.J. Lu <hjl.tools@gmail.com> wrote:
> >
> > On Wed, Oct 23, 2024 at 10:30 PM Noah Goldstein <goldstein.w.n@gmail.com> wrote:
> > ...
> > > >>>> >> +LIBC_CONFIG_VAR([pde-load-address], [$libc_cv_pde_load_address])
> > > >>>> >> +
> > > >>>> >> +LIBC_LINKER_FEATURE([-Ttext-segment=$libc_cv_pde_load_address],
> > > >>>> >> +                   [-Wl,-Ttext-segment=$libc_cv_pde_load_address],
> > > >>>> >> +                   [libc_cv_load_address=-Wl,-Ttext-segment],
> > > >>>> >> +                   [libc_cv_load_address=])
> > > >>>> >> +LIBC_CONFIG_VAR([load-address-ldflag], [$libc_cv_load_address])
> > > >>>> >> +
> > >
> > > IIUC, all of the new conf code is for setting up tests.
> > > Can you make that more clear in either comment or var names?
> >
> > Done.
>
> Hmm? This doesn't seem done. I mean can you make it explicit
> these config options are only used for tests internal to glibc.

Will update.

> >
> > > >>>> >>  AC_MSG_CHECKING(if we can build programs as PIE)
> > > >>>> >>  AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#ifdef PIE_UNSUPPORTED
> > > >>>> >>  # error PIE is not supported
> > > >>>> >> diff --git a/elf/Makefile b/elf/Makefile
> > > >>>> >> index 57b3a19d36..4e2d123b8e 100644
> > > >>>> >> --- a/elf/Makefile
> > > >>>> >> +++ b/elf/Makefile
> > > >>>> >> @@ -1046,6 +1046,25 @@ tests-pie += \
> > > >>>> >>    tst-pie1 \
> > > >>>> >>    tst-pie2 \
> > > >>>> >>    # tests-pie
> > > >>>> >> +ifneq (,$(load-address-ldflag))
> > > >>>> >> +tests += \
> > > >>>> >> +  tst-pie-address \
> > > >>>> >> +  # tests
> > > >>>> >> +tests-pie += \
> > > >>>> >> +  tst-pie-address \
> > > >>>> >> +  # tests-pie
> > > >>>> >> +LDFLAGS-tst-pie-address += $(load-address-ldflag)=$(pde-load-address)
> > > >>>> >> +ifeq (yes,$(enable-static-pie))
> > > >>>> >> +tests += \
> > > >>>> >> +  tst-pie-address-static \
> > > >>>> >> +  # tests
> > > >>>> >> +tests-static += \
> > > >>>> >> +  tst-pie-address-static \
> > > >>>> >> +  # tests-static
> > > >>>> >> +LDFLAGS-tst-pie-address-static += \
> > > >>>> >> +  $(load-address-ldflag)=$(pde-load-address)
> > > >>>> >> +endif
> > > >>>> >> +endif
> > > >>>> >>  ifeq (yes,$(have-protected-data))
> > > >>>> >>  tests += vismain
> > > >>>> >>  tests-pie += vismain
> > > >>>> >> @@ -1896,6 +1915,7 @@ $(objpfx)tst-array5-static-cmp.out: tst-array5-static.exp \
> > > >>>> >>
> > > >>>> >>  CFLAGS-tst-pie1.c += $(pie-ccflag)
> > > >>>> >>  CFLAGS-tst-pie2.c += $(pie-ccflag)
> > > >>>> >> +CFLAGS-tst-pie-address.c += $(pie-ccflag)
> > > >>>> >>
> > > >>>> >>  $(objpfx)tst-piemod1.so: $(libsupport)
> > > >>>> >>  $(objpfx)tst-pie1: $(objpfx)tst-piemod1.so
> > > >>>> >> diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
> > > >>>> >> index 10c23d0bf0..6286f19f1f 100644
> > > >>>> >> --- a/elf/dl-reloc-static-pie.c
> > > >>>> >> +++ b/elf/dl-reloc-static-pie.c
> > > >>>> >> @@ -37,21 +37,32 @@ _dl_relocate_static_pie (void)
> > > >>>> >>  {
> > > >>>> >>    struct link_map *main_map = _dl_get_dl_main_map ();
> > > >>>> >>
> > > >>>> >> -  /* Figure out the run-time load address of static PIE.  */
> > > >>>> >> -  main_map->l_addr = elf_machine_load_address ();
> > > >>>> >> -
> > > >>>> >> -  /* Read our own dynamic section and fill in the info array.  */
> > > >>>> >> -  main_map->l_ld = ((void *) main_map->l_addr + elf_machine_dynamic ());
> > > >>>> >> -
> > > >>>> >> +  ElfW(Addr) file_p_vaddr = 0;
> > > >>>> >>    const ElfW(Phdr) *ph, *phdr = GL(dl_phdr);
> > > >>>> >>    size_t phnum = GL(dl_phnum);
> > > >>>> >>    for (ph = phdr; ph < &phdr[phnum]; ++ph)
> > > >>>> >> -    if (ph->p_type == PT_DYNAMIC)
> > > >>>> >> +    switch (ph->p_type)
> > > >>>> >>        {
> > > >>>> >> +      case PT_LOAD:
> > > >>>> >> +       /* Get p_vaddr of the PT_LOAD segement which covers the file
> > > >>>> >> +          start.  */
> > > >>>> >> +       if (ph->p_offset == 0)
> > > >>>> >> +         file_p_vaddr = ph->p_vaddr;
> > > >>>> >> +       break;
> > > >>>> >> +      case PT_DYNAMIC:
> > > >>>> >>         main_map->l_ld_readonly = (ph->p_flags & PF_W) == 0;
> > > >>>> >>         break;
> > > >>>> >> +      default:
> > > >>>> >> +       break;
> > > >>>> >>        }
> > > >>>> >>
> > > >>>> >> +  /* Figure out the run-time load address of static PIE.  */
> > > >>>> >> +  ElfW(Addr) l_addr = elf_machine_load_address ();
> > > >>>> >> +  main_map->l_addr = l_addr - file_p_vaddr;
> > >
> > > Can you explain this? I though vaddr field was the true virtual-address
> > > but you seem to be treating it as an offset here.
> >
> > The l_addr field contains the difference between the address in the ELF
> > file and the address in memory.  file_p_vaddr is used as the address in
> > static PIE.  I updated comments in the v6 patch,
> >
> Okay, I think I understand. We were implicitly assuming vaddr was zero
> before by just using l_addr (v_addr + offset == true_addr if v_addr is zero).
> Is that correct?

offset isn't used explicitly.  l_addr is the difference between the
load address in the
ELF file and the actual address in memory.  The current code assumes that the
load address  in ELF file == 0.  My change computes the load address by using
p_vaddr of PT_LOAD segment at offset == 0.


>
> > Here is the v6 patch.  OK to install?
> >
> > Thanks.
> >
> > --
> > H.J.



--
H.J.
diff mbox series

Patch

From c309d6edd62b64d5d732ef9b662e6074a79c2eff Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <hjl.tools@gmail.com>
Date: Sun, 26 May 2024 04:55:32 -0700
Subject: [PATCH v6] elf: Handle static PIE loaded at the non-zero address [BZ
 #31799]

When a static PIE is loaded at the non-zero address, its PT_DYNAMIC
segment entries contain the relocated values for the load address in
static PIE.  Since static PIE usually doesn't have PT_PHDR segment,
use p_vaddr of the PT_LOAD segment which covers the file start as the
load address in static PIE and adjust the entries of PT_DYNAMIC segment
with the load address in static PIE by properly setting the l_addr field
for static PIE.  This fixes BZ #31799.

Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
---
 configure                    | 72 ++++++++++++++++++++++++++++++++++++
 configure.ac                 | 34 +++++++++++++++++
 elf/Makefile                 | 20 ++++++++++
 elf/dl-reloc-static-pie.c    | 30 +++++++++++----
 elf/tst-pie-address-static.c | 19 ++++++++++
 elf/tst-pie-address.c        | 28 ++++++++++++++
 6 files changed, 196 insertions(+), 7 deletions(-)
 create mode 100644 elf/tst-pie-address-static.c
 create mode 100644 elf/tst-pie-address.c

diff --git a/configure b/configure
index 9c0c0dce03..a84bb53fc8 100755
--- a/configure
+++ b/configure
@@ -8107,6 +8107,78 @@  printf "%s\n" "$libc_cv_cc_pie_default" >&6; }
 config_vars="$config_vars
 cc-pie-default = $libc_cv_cc_pie_default"
 
+# Get Position Dependent Executable (PDE) load address to be used to
+# load static Position Independent Executable (PIE) at a known working
+# non-zero address.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PDE load address" >&5
+printf %s "checking PDE load address... " >&6; }
+if test ${libc_cv_pde_load_address+y}
+then :
+  printf %s "(cached) " >&6
+else case e in #(
+  e) cat > conftest.S <<EOF
+.globl _start
+_start:
+.globl __start
+__start:
+EOF
+if test $libc_cv_cc_pie_default = yes; then
+  pde_ld_flags="-no-pie"
+fi
+if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
+	    -nostartfiles -nostdlib $no_ssp \
+	    -o conftest conftest.S 1>&5 2>&5; then
+  # Get the load address of the first PT_LOAD segment.
+  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
+			     | $AWK '/LOAD/ { print $3; exit 0; }')
+else
+  as_fn_error $? "${CC-cc} can not create PDE" "$LINENO" 5
+fi
+rm -f conftest* ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_pde_load_address" >&5
+printf "%s\n" "$libc_cv_pde_load_address" >&6; }
+config_vars="$config_vars
+pde-load-address = $libc_cv_pde_load_address"
+
+# Get the linker command-line option to load executable at a non-zero
+# address.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for linker that supports -Ttext-segment=$libc_cv_pde_load_address" >&5
+printf %s "checking for linker that supports -Ttext-segment=$libc_cv_pde_load_address... " >&6; }
+libc_linker_feature=no
+cat > conftest.c <<EOF
+int _start (void) { return 42; }
+EOF
+if { ac_try='${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp
+		  -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib -nostartfiles
+		  -fPIC -shared -o conftest.so conftest.c
+		  1>&5'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+then
+  if ${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib \
+      -nostartfiles -fPIC -shared -o conftest.so conftest.c 2>&1 \
+      | grep "warning: -Ttext-segment=$libc_cv_pde_load_address ignored" > /dev/null 2>&1; then
+    true
+  else
+    libc_linker_feature=yes
+  fi
+fi
+rm -f conftest*
+if test $libc_linker_feature = yes; then
+  libc_cv_load_address_ldflag=-Wl,-Ttext-segment
+else
+  libc_cv_load_address_ldflag=
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_linker_feature" >&5
+printf "%s\n" "$libc_linker_feature" >&6; }
+config_vars="$config_vars
+load-address-ldflag = $libc_cv_load_address_ldflag"
+
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can build programs as PIE" >&5
 printf %s "checking if we can build programs as PIE... " >&6; }
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
diff --git a/configure.ac b/configure.ac
index d5a00461ff..8dc7e80ca3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1763,6 +1763,40 @@  fi
 rm -f conftest.*])
 LIBC_CONFIG_VAR([cc-pie-default], [$libc_cv_cc_pie_default])
 
+# Get Position Dependent Executable (PDE) load address to be used to
+# load static Position Independent Executable (PIE) at a known working
+# non-zero address.
+AC_CACHE_CHECK([PDE load address],
+	       libc_cv_pde_load_address, [dnl
+cat > conftest.S <<EOF
+.globl _start
+_start:
+.globl __start
+__start:
+EOF
+if test $libc_cv_cc_pie_default = yes; then
+  pde_ld_flags="-no-pie"
+fi
+if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
+	    -nostartfiles -nostdlib $no_ssp \
+	    -o conftest conftest.S 1>&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD; then
+  # Get the load address of the first PT_LOAD segment.
+  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
+			     | $AWK '/LOAD/ { print $3; exit 0; }')
+else
+  AC_MSG_ERROR([${CC-cc} can not create PDE])
+fi
+rm -f conftest*])
+LIBC_CONFIG_VAR([pde-load-address], [$libc_cv_pde_load_address])
+
+# Get the linker command-line option to load executable at a non-zero
+# address.
+LIBC_LINKER_FEATURE([-Ttext-segment=$libc_cv_pde_load_address],
+		    [-Wl,-Ttext-segment=$libc_cv_pde_load_address],
+		    [libc_cv_load_address_ldflag=-Wl,-Ttext-segment],
+		    [libc_cv_load_address_ldflag=])
+LIBC_CONFIG_VAR([load-address-ldflag], [$libc_cv_load_address_ldflag])
+
 AC_MSG_CHECKING(if we can build programs as PIE)
 AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#ifdef PIE_UNSUPPORTED
 # error PIE is not supported
diff --git a/elf/Makefile b/elf/Makefile
index 9cfe738919..4eaca7af67 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -1091,6 +1091,25 @@  tests-pie += \
   tst-pie1 \
   tst-pie2 \
   # tests-pie
+ifneq (,$(load-address-ldflag))
+tests += \
+  tst-pie-address \
+  # tests
+tests-pie += \
+  tst-pie-address \
+  # tests-pie
+LDFLAGS-tst-pie-address += $(load-address-ldflag)=$(pde-load-address)
+ifeq (yes,$(enable-static-pie))
+tests += \
+  tst-pie-address-static \
+  # tests
+tests-static += \
+  tst-pie-address-static \
+  # tests-static
+LDFLAGS-tst-pie-address-static += \
+  $(load-address-ldflag)=$(pde-load-address)
+endif
+endif
 ifeq (yes,$(have-protected-data))
 tests += vismain
 tests-pie += vismain
@@ -1937,6 +1956,7 @@  $(objpfx)tst-array5-static-cmp.out: tst-array5-static.exp \
 
 CFLAGS-tst-pie1.c += $(pie-ccflag)
 CFLAGS-tst-pie2.c += $(pie-ccflag)
+CFLAGS-tst-pie-address.c += $(pie-ccflag)
 
 $(objpfx)tst-piemod1.so: $(libsupport)
 $(objpfx)tst-pie1: $(objpfx)tst-piemod1.so
diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
index 10c23d0bf0..66fabf5543 100644
--- a/elf/dl-reloc-static-pie.c
+++ b/elf/dl-reloc-static-pie.c
@@ -37,21 +37,37 @@  _dl_relocate_static_pie (void)
 {
   struct link_map *main_map = _dl_get_dl_main_map ();
 
-  /* Figure out the run-time load address of static PIE.  */
-  main_map->l_addr = elf_machine_load_address ();
-
-  /* Read our own dynamic section and fill in the info array.  */
-  main_map->l_ld = ((void *) main_map->l_addr + elf_machine_dynamic ());
-
+  /* NB: elf_machine_load_address () returns the run-time load address
+     of static PIE.  The l_addr field contains the difference between the
+     address in the ELF file and the address in memory.  If a static PIE
+     has a non-zero load address, we need to subtract it when computing
+     the l_addr field.  Since static PIE usually doesn't have PT_PHDR
+     segment, use p_vaddr of the PT_LOAD segment which covers the file
+     start as the load address in static PIE.  */
+  ElfW(Addr) file_p_vaddr = 0;
   const ElfW(Phdr) *ph, *phdr = GL(dl_phdr);
   size_t phnum = GL(dl_phnum);
   for (ph = phdr; ph < &phdr[phnum]; ++ph)
-    if (ph->p_type == PT_DYNAMIC)
+    switch (ph->p_type)
       {
+      case PT_LOAD:
+	if (ph->p_offset == 0)
+	  file_p_vaddr = ph->p_vaddr;
+	break;
+      case PT_DYNAMIC:
 	main_map->l_ld_readonly = (ph->p_flags & PF_W) == 0;
 	break;
+      default:
+	break;
       }
 
+  /* Figure out the run-time load address of static PIE.  */
+  ElfW(Addr) l_addr = elf_machine_load_address ();
+  main_map->l_addr = l_addr - file_p_vaddr;
+
+  /* Read our own dynamic section and fill in the info array.  */
+  main_map->l_ld = ((void *) l_addr + elf_machine_dynamic ());
+
   elf_get_dynamic_info (main_map, false, true);
 
 # ifdef ELF_MACHINE_BEFORE_RTLD_RELOC
diff --git a/elf/tst-pie-address-static.c b/elf/tst-pie-address-static.c
new file mode 100644
index 0000000000..25d27fb962
--- /dev/null
+++ b/elf/tst-pie-address-static.c
@@ -0,0 +1,19 @@ 
+/* Test static PIE loaded at the specific address.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include "tst-pie-address.c"
diff --git a/elf/tst-pie-address.c b/elf/tst-pie-address.c
new file mode 100644
index 0000000000..1f01783631
--- /dev/null
+++ b/elf/tst-pie-address.c
@@ -0,0 +1,28 @@ 
+/* Test PIE loaded at the specific address.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+
+static int
+do_test (void)
+{
+  printf ("Hello\n");
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.47.0