]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
elf: Add ELF_DYNAMIC_AFTER_RELOC to rewrite PLT
authorH.J. Lu <hjl.tools@gmail.com>
Fri, 5 Jan 2024 04:19:39 +0000 (20:19 -0800)
committerH.J. Lu <hjl.tools@gmail.com>
Fri, 5 Jan 2024 13:49:49 +0000 (05:49 -0800)
Add ELF_DYNAMIC_AFTER_RELOC to allow target specific processing after
relocation.

For x86-64, add

 #define DT_X86_64_PLT     (DT_LOPROC + 0)
 #define DT_X86_64_PLTSZ   (DT_LOPROC + 1)
 #define DT_X86_64_PLTENT  (DT_LOPROC + 3)

1. DT_X86_64_PLT: The address of the procedure linkage table.
2. DT_X86_64_PLTSZ: The total size, in bytes, of the procedure linkage
table.
3. DT_X86_64_PLTENT: The size, in bytes, of a procedure linkage table
entry.

With the r_addend field of the R_X86_64_JUMP_SLOT relocation set to the
memory offset of the indirect branch instruction.

Define ELF_DYNAMIC_AFTER_RELOC for x86-64 to rewrite the PLT section
with direct branch after relocation when the lazy binding is disabled.

PLT rewrite is disabled by default since SELinux may disallow modifying
code pages and ld.so can't detect it in all cases.  Use

$ export GLIBC_TUNABLES=glibc.cpu.plt_rewrite=1

to enable PLT rewrite with 32-bit direct jump at run-time or

$ export GLIBC_TUNABLES=glibc.cpu.plt_rewrite=2

to enable PLT rewrite with 32-bit direct jump and on APX processors with
64-bit absolute jump at run-time.

Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
17 files changed:
elf/dynamic-link.h
elf/elf.h
elf/tst-glibcelf.py
manual/tunables.texi
scripts/glibcelf.py
sysdeps/x86/cet-control.h
sysdeps/x86/cpu-features.c
sysdeps/x86/dl-procruntime.c
sysdeps/x86/dl-tunables.list
sysdeps/x86_64/Makefile
sysdeps/x86_64/configure
sysdeps/x86_64/configure.ac
sysdeps/x86_64/dl-dtprocnum.h [new file with mode: 0644]
sysdeps/x86_64/dl-machine.h
sysdeps/x86_64/link_map.h [new file with mode: 0644]
sysdeps/x86_64/tst-plt-rewrite1.c [new file with mode: 0644]
sysdeps/x86_64/tst-plt-rewritemod1.c [new file with mode: 0644]

index 8cdf7bde09e92d5d8ea8310afe2963b58a408651..83d834ecaf609082506d161b9744a2b476469b5e 100644 (file)
@@ -177,6 +177,10 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[],
       }                                                                              \
   } while (0);
 
+# ifndef ELF_DYNAMIC_AFTER_RELOC
+#  define ELF_DYNAMIC_AFTER_RELOC(map, lazy)
+# endif
+
 /* This can't just be an inline function because GCC is too dumb
    to inline functions containing inlines themselves.  */
 # ifdef RTLD_BOOTSTRAP
@@ -192,6 +196,7 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[],
       ELF_DYNAMIC_DO_RELR (map);                                             \
     ELF_DYNAMIC_DO_REL ((map), (scope), edr_lazy, skip_ifunc);               \
     ELF_DYNAMIC_DO_RELA ((map), (scope), edr_lazy, skip_ifunc);                      \
+    ELF_DYNAMIC_AFTER_RELOC ((map), (edr_lazy));                             \
   } while (0)
 
 #endif
index ca6a7d9d67169174aba77a65fbd3bb79f560b26b..455731663c6ed339ea41378488be1378c480de47 100644 (file)
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -3639,6 +3639,11 @@ enum
 /* x86-64 sh_type values.  */
 #define SHT_X86_64_UNWIND      0x70000001 /* Unwind information.  */
 
+/* x86-64 d_tag values.  */
+#define DT_X86_64_PLT          (DT_LOPROC + 0)
+#define DT_X86_64_PLTSZ                (DT_LOPROC + 1)
+#define DT_X86_64_PLTENT       (DT_LOPROC + 3)
+#define DT_X86_64_NUM          4
 
 /* AM33 relocations.  */
 #define R_MN10300_NONE         0       /* No reloc.  */
index 00cd2bba855482939dede932d618d1cd36be3a1a..c191636a997c8e34535163cc53838c8b7631aada 100644 (file)
@@ -187,6 +187,7 @@ DT_VALNUM
 DT_VALRNGHI
 DT_VALRNGLO
 DT_VERSIONTAGNUM
+DT_X86_64_NUM
 ELFCLASSNUM
 ELFDATANUM
 EM_NUM
index b31f16da8426d2a9729a03f971756744c47e3a93..be97190d67b1c82e43eb5a3b2f87fe09dbe68a60 100644 (file)
@@ -57,6 +57,7 @@ glibc.pthread.stack_cache_size: 0x2800000 (min: 0x0, max: 0xffffffffffffffff)
 glibc.cpu.hwcap_mask: 0x6 (min: 0x0, max: 0xffffffffffffffff)
 glibc.malloc.mmap_max: 0 (min: 0, max: 2147483647)
 glibc.elision.skip_trylock_internal_abort: 3 (min: 0, max: 2147483647)
+glibc.cpu.plt_rewrite: 0 (min: 0, max: 2)
 glibc.malloc.tcache_unsorted_limit: 0x0 (min: 0x0, max: 0xffffffffffffffff)
 glibc.cpu.x86_ibt:
 glibc.cpu.hwcaps:
@@ -614,6 +615,16 @@ this tunable.
 This tunable is specific to 64-bit x86-64.
 @end deftp
 
+@deftp Tunable glibc.cpu.plt_rewrite
+When this tunable is set to @code{1}, the dynamic linker will rewrite
+the PLT section with 32-bit direct jump.  When it is set to @code{2},
+the dynamic linker will rewrite the PLT section with 32-bit direct
+jump and on APX processors with 64-bit absolute jump.
+
+This tunable is specific to x86-64 and effective only when the lazy
+binding is disabled.
+@end deftp
+
 @node Memory Related Tunables
 @section Memory Related Tunables
 @cindex memory related tunables
index c5e5dda48e525d4579989f29653dc70aeeacbf59..5f3813f326cccb8b7bf9a2e99f7d998bfa605c86 100644 (file)
@@ -439,6 +439,8 @@ class DtRISCV(Dt):
     """Supplemental DT_* constants for EM_RISCV."""
 class DtSPARC(Dt):
     """Supplemental DT_* constants for EM_SPARC."""
+class DtX86_64(Dt):
+    """Supplemental DT_* constants for EM_X86_64."""
 _dt_skip = '''
 DT_ENCODING DT_PROCNUM
 DT_ADDRRNGLO DT_ADDRRNGHI DT_ADDRNUM
@@ -451,6 +453,7 @@ DT_MIPS_NUM
 DT_PPC_NUM
 DT_PPC64_NUM
 DT_SPARC_NUM
+DT_X86_64_NUM
 '''.strip().split()
 _register_elf_h(DtAARCH64, prefix='DT_AARCH64_', skip=_dt_skip, parent=Dt)
 _register_elf_h(DtALPHA, prefix='DT_ALPHA_', skip=_dt_skip, parent=Dt)
@@ -461,6 +464,7 @@ _register_elf_h(DtPPC, prefix='DT_PPC_', skip=_dt_skip, parent=Dt)
 _register_elf_h(DtPPC64, prefix='DT_PPC64_', skip=_dt_skip, parent=Dt)
 _register_elf_h(DtRISCV, prefix='DT_RISCV_', skip=_dt_skip, parent=Dt)
 _register_elf_h(DtSPARC, prefix='DT_SPARC_', skip=_dt_skip, parent=Dt)
+_register_elf_h(DtX86_64, prefix='DT_X86_64_', skip=_dt_skip, parent=Dt)
 _register_elf_h(Dt, skip=_dt_skip, ranges=True)
 del _dt_skip
 
index a45d59bf8c6cfaee425facc36b05d99b8a7c79eb..f09f193e9b14c096f0d403d8fd608412e9db1482 100644 (file)
@@ -32,10 +32,22 @@ enum dl_x86_cet_control
   cet_permissive
 };
 
+/* PLT rewrite control.  */
+enum dl_plt_rewrite_control
+{
+  /* No PLT rewrite.  */
+  plt_rewrite_none,
+  /* Rewrite PLT with JMP at run-time.  */
+  plt_rewrite_jmp,
+  /* Rewrite PLT with JMP and JMPABS at run-time.  */
+  plt_rewrite_jmpabs
+};
+
 struct dl_x86_feature_control
 {
   enum dl_x86_cet_control ibt : 2;
   enum dl_x86_cet_control shstk : 2;
+  enum dl_plt_rewrite_control plt_rewrite : 2;
 };
 
 #endif /* cet-control.h */
index f193ea7a2d9aa2127f306357c0108d40401f9c90..9ca307f521b53d5fdfd9445f07ef78c84c5b9f4c 100644 (file)
 extern void TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *)
   attribute_hidden;
 
+#ifdef SHARED
+static void
+TUNABLE_CALLBACK (set_plt_rewrite) (tunable_val_t *valp)
+{
+  if (valp->numval != 0)
+    {
+      /* Use JMPABS only on APX processors.  */
+      const struct cpu_features *cpu_features = __get_cpu_features ();
+      GL (dl_x86_feature_control).plt_rewrite
+         = ((valp->numval > 1 && CPU_FEATURE_PRESENT_P (cpu_features, APX_F))
+                ? plt_rewrite_jmpabs
+                : plt_rewrite_jmp);
+    }
+}
+#endif
+
 #ifdef __LP64__
 static void
 TUNABLE_CALLBACK (set_prefer_map_32bit_exec) (tunable_val_t *valp)
@@ -1108,7 +1124,10 @@ no_cpuid:
               TUNABLE_CALLBACK (set_x86_shstk));
 #endif
 
-#ifndef SHARED
+#ifdef SHARED
+  TUNABLE_GET (plt_rewrite, tunable_val_t *,
+              TUNABLE_CALLBACK (set_plt_rewrite));
+#else
   /* NB: In libc.a, call init_cacheinfo.  */
   init_cacheinfo ();
 #endif
index 4d25d9f32700fdecad3b649d3def5e138a200861..15b3d0d878fd225d587926ae582076aff3a378d2 100644 (file)
@@ -67,6 +67,7 @@ PROCINFO_CLASS struct dl_x86_feature_control _dl_x86_feature_control
 = {
     .ibt = DEFAULT_DL_X86_CET_CONTROL,
     .shstk = DEFAULT_DL_X86_CET_CONTROL,
+    .plt_rewrite = plt_rewrite_none,
   }
 # endif
 # if !defined SHARED || defined PROCINFO_DECL
index 147a7270ec1ef75d912b992ad18ddd3107692cf5..7d82da0dece49c4593ddec375cf88b58f86fd390 100644 (file)
@@ -66,5 +66,10 @@ glibc {
     x86_shared_cache_size {
       type: SIZE_T
     }
+    plt_rewrite {
+      type: INT_32
+      minval: 0
+      maxval: 2
+    }
   }
 }
index 00120ca9caf41ffd16ca71006e619b15c575fdc9..374bca80d0aa28c31cd03c8153a47fd953be4be3 100644 (file)
@@ -1,6 +1,14 @@
 # The i387 `long double' is a distinct type we support.
 long-double-fcts = yes
 
+ifeq (yes,$(have-z-mark-plt))
+# Always generate DT_X86_64_PLT* tags.
+sysdep-LDFLAGS += -Wl,-z,mark-plt
+# Never generate DT_X86_64_PLT* tags on ld.so to avoid changing its own
+# PLT.
+LDFLAGS-rtld += -Wl,-z,nomark-plt
+endif
+
 ifeq ($(subdir),csu)
 gen-as-const-headers += link-defines.sym
 endif
@@ -175,6 +183,25 @@ ifeq (no,$(build-hardcoded-path-in-tests))
 tests-container += tst-glibc-hwcaps-cache
 endif
 
+ifeq (yes,$(have-z-mark-plt))
+tests += \
+  tst-plt-rewrite1 \
+# tests
+modules-names += \
+  tst-plt-rewritemod1 \
+# modules-names
+
+tst-plt-rewrite1-no-pie = yes
+LDFLAGS-tst-plt-rewrite1 = -Wl,-z,now
+LDFLAGS-tst-plt-rewritemod1.so = -Wl,-z,now
+tst-plt-rewrite1-ENV = GLIBC_TUNABLES=glibc.cpu.plt_rewrite=1 LD_DEBUG=files:bindings
+$(objpfx)tst-plt-rewrite1: $(objpfx)tst-plt-rewritemod1.so
+$(objpfx)tst-plt-rewrite1.out: /dev/null $(objpfx)tst-plt-rewrite1
+       $(tst-plt-rewrite1-ENV) $(make-test-out) > $@ 2>&1; \
+       grep -q -E "changing 'bar' PLT entry in .*/elf/tst-plt-rewritemod1.so' to direct branch" $@; \
+       $(evaluate-test)
+endif
+
 endif # $(subdir) == elf
 
 ifeq ($(subdir),csu)
index b4a80b80352c47a1f0570f29cbeef6730e721ee9..418cc4a9b862f7e05bec199c381d9530518eb78d 100755 (executable)
@@ -25,6 +25,41 @@ printf "%s\n" "$libc_cv_cc_mprefer_vector_width" >&6; }
 config_vars="$config_vars
 config-cflags-mprefer-vector-width = $libc_cv_cc_mprefer_vector_width"
 
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for linker that supports -z mark-plt" >&5
+printf %s "checking for linker that supports -z mark-plt... " >&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,-z,mark-plt -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,-z,mark-plt -nostdlib \
+      -nostartfiles -fPIC -shared -o conftest.so conftest.c 2>&1 \
+      | grep "warning: -z mark-plt 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_z_mark_plt=yes
+else
+  libc_cv_z_mark_plt=no
+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
+have-z-mark-plt = $libc_cv_z_mark_plt"
+
 if test x"$build_mathvec" = xnotset; then
   build_mathvec=yes
 fi
index 937d1aff7e2074aeee7e4a946af8bfbed5d48eaa..d1f803c02ee67fc5effd5058f277b35e31f5ead2 100644 (file)
@@ -10,6 +10,10 @@ LIBC_TRY_CC_OPTION([-mprefer-vector-width=128],
 LIBC_CONFIG_VAR([config-cflags-mprefer-vector-width],
                [$libc_cv_cc_mprefer_vector_width])
 
+LIBC_LINKER_FEATURE([-z mark-plt], [-Wl,-z,mark-plt],
+                    [libc_cv_z_mark_plt=yes], [libc_cv_z_mark_plt=no])
+LIBC_CONFIG_VAR([have-z-mark-plt], [$libc_cv_z_mark_plt])
+
 if test x"$build_mathvec" = xnotset; then
   build_mathvec=yes
 fi
diff --git a/sysdeps/x86_64/dl-dtprocnum.h b/sysdeps/x86_64/dl-dtprocnum.h
new file mode 100644 (file)
index 0000000..01573aa
--- /dev/null
@@ -0,0 +1,21 @@
+/* Configuration of lookup functions.  x64-64 version.
+   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/>.  */
+
+/* Number of extra dynamic section entries for this architecture.  By
+   default there are none.  */
+#define DT_THISPROCNUM DT_X86_64_NUM
index e0a9b144692a6e390d47371dcc7ccab133a4ca8e..ca290ef0822b53fe5a64458a45a0c0f79f92d1ef 100644 (file)
@@ -22,6 +22,7 @@
 #define ELF_MACHINE_NAME "x86_64"
 
 #include <assert.h>
+#include <stdint.h>
 #include <sys/param.h>
 #include <sysdep.h>
 #include <tls.h>
@@ -35,6 +36,9 @@
 # define RTLD_START_ENABLE_X86_FEATURES
 #endif
 
+/* Translate a processor specific dynamic tag to the index in l_info array.  */
+#define DT_X86_64(x) (DT_X86_64_##x - DT_LOPROC + DT_NUM)
+
 /* Return nonzero iff ELF header is compatible with the running host.  */
 static inline int __attribute__ ((unused))
 elf_machine_matches_host (const ElfW(Ehdr) *ehdr)
@@ -312,8 +316,10 @@ and creates an unsatisfiable circular dependency.\n",
 
       switch (r_type)
        {
-       case R_X86_64_GLOB_DAT:
        case R_X86_64_JUMP_SLOT:
+         map->l_has_jump_slot_reloc = true;
+         /* fallthrough */
+       case R_X86_64_GLOB_DAT:
          *reloc_addr = value;
          break;
 
@@ -549,3 +555,211 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[],
 }
 
 #endif /* RESOLVE_MAP */
+
+#if !defined ELF_DYNAMIC_AFTER_RELOC && !defined RTLD_BOOTSTRAP \
+    && defined SHARED
+# define ELF_DYNAMIC_AFTER_RELOC(map, lazy) \
+  x86_64_dynamic_after_reloc (map, (lazy))
+
+# define JMP32_INSN_OPCODE     0xe9
+# define JMP32_INSN_SIZE       5
+# define JMPABS_INSN_OPCODE    0xa100d5
+# define JMPABS_INSN_SIZE      11
+# define INT3_INSN_OPCODE      0xcc
+
+static const char *
+x86_64_reloc_symbol_name (struct link_map *map, const ElfW(Rela) *reloc)
+{
+  const ElfW(Sym) *const symtab
+    = (const void *) map->l_info[DT_SYMTAB]->d_un.d_ptr;
+  const ElfW(Sym) *const refsym = &symtab[ELFW (R_SYM) (reloc->r_info)];
+  const char *strtab = (const char *) map->l_info[DT_STRTAB]->d_un.d_ptr;
+  return strtab + refsym->st_name;
+}
+
+static void
+x86_64_rewrite_plt (struct link_map *map, ElfW(Addr) plt_rewrite)
+{
+  ElfW(Addr) l_addr = map->l_addr;
+  ElfW(Addr) pltent = map->l_info[DT_X86_64 (PLTENT)]->d_un.d_val;
+  ElfW(Addr) start = map->l_info[DT_JMPREL]->d_un.d_ptr;
+  ElfW(Addr) size = map->l_info[DT_PLTRELSZ]->d_un.d_val;
+  const ElfW(Rela) *reloc = (const void *) start;
+  const ElfW(Rela) *reloc_end = (const void *) (start + size);
+
+  unsigned int feature_1 = THREAD_GETMEM (THREAD_SELF,
+                                         header.feature_1);
+  bool ibt_enabled_p
+    = (feature_1 & GNU_PROPERTY_X86_FEATURE_1_IBT) != 0;
+
+  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
+    _dl_debug_printf ("\nchanging PLT in '%s' to direct branch\n",
+                     DSO_FILENAME (map->l_name));
+
+  for (; reloc < reloc_end; reloc++)
+    if (ELFW(R_TYPE) (reloc->r_info) == R_X86_64_JUMP_SLOT)
+      {
+       /* Get the value from the GOT entry.  */
+       ElfW(Addr) value = *(ElfW(Addr) *) (l_addr + reloc->r_offset);
+
+       /* Get the corresponding PLT entry from r_addend.  */
+       ElfW(Addr) branch_start = l_addr + reloc->r_addend;
+       /* Skip ENDBR64 if IBT isn't enabled.  */
+       if (!ibt_enabled_p)
+         branch_start = ALIGN_DOWN (branch_start, pltent);
+       /* Get the displacement from the branch target.  */
+       ElfW(Addr) disp = value - branch_start - JMP32_INSN_SIZE;
+       ElfW(Addr) plt_end;
+       ElfW(Addr) pad;
+
+       plt_end = (branch_start | (pltent - 1)) + 1;
+
+       /* Update the PLT entry.  */
+       if (((uint64_t) disp + (uint64_t) ((uint32_t) INT32_MIN))
+           <= (uint64_t) UINT32_MAX)
+         {
+           pad = branch_start + JMP32_INSN_SIZE;
+
+           if (__glibc_unlikely (pad > plt_end))
+             continue;
+
+           /* If the target branch can be reached with a direct branch,
+              rewrite the PLT entry with a direct branch.  */
+           if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS))
+             {
+               const char *sym_name = x86_64_reloc_symbol_name (map,
+                                                                reloc);
+               _dl_debug_printf ("changing '%s' PLT entry in '%s' to "
+                                 "direct branch\n", sym_name,
+                                 DSO_FILENAME (map->l_name));
+             }
+
+           /* Write out direct branch.  */
+           *(uint8_t *) branch_start = JMP32_INSN_OPCODE;
+           *(uint32_t *) (branch_start + 1) = disp;
+         }
+       else
+         {
+           if (GL(dl_x86_feature_control).plt_rewrite
+               != plt_rewrite_jmpabs)
+             {
+               if (__glibc_unlikely (GLRO(dl_debug_mask)
+                                     & DL_DEBUG_BINDINGS))
+                 {
+                   const char *sym_name
+                     = x86_64_reloc_symbol_name (map, reloc);
+                   _dl_debug_printf ("skipping '%s' PLT entry in '%s'\n",
+                                     sym_name,
+                                     DSO_FILENAME (map->l_name));
+                 }
+               continue;
+             }
+
+           pad = branch_start + JMPABS_INSN_SIZE;
+
+           if (__glibc_unlikely (pad > plt_end))
+             continue;
+
+           /* Rewrite the PLT entry with JMPABS.  */
+           if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS))
+             {
+               const char *sym_name = x86_64_reloc_symbol_name (map,
+                                                                reloc);
+               _dl_debug_printf ("changing '%s' PLT entry in '%s' to "
+                                 "JMPABS\n", sym_name,
+                                 DSO_FILENAME (map->l_name));
+             }
+
+           /* "jmpabs $target" for 64-bit displacement.  NB: JMPABS has
+              a 3-byte opcode + 64bit address.  There is a 1-byte overlap
+              between 4-byte write and 8-byte write.  */
+           *(uint32_t *) (branch_start) = JMPABS_INSN_OPCODE;
+           *(uint64_t *) (branch_start + 3) = value;
+         }
+
+       /* Fill the unused part of the PLT entry with INT3.  */
+       for (; pad < plt_end; pad++)
+         *(uint8_t *) pad = INT3_INSN_OPCODE;
+      }
+}
+
+static inline void
+x86_64_rewrite_plt_in_place (struct link_map *map)
+{
+  /* Adjust DT_X86_64_PLT address and DT_X86_64_PLTSZ values.  */
+  ElfW(Addr) plt = (map->l_info[DT_X86_64 (PLT)]->d_un.d_ptr
+                   + map->l_addr);
+  size_t pagesize = GLRO(dl_pagesize);
+  ElfW(Addr) plt_aligned = ALIGN_DOWN (plt, pagesize);
+  size_t pltsz = (map->l_info[DT_X86_64 (PLTSZ)]->d_un.d_val
+                 + plt - plt_aligned);
+
+  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
+    _dl_debug_printf ("\nchanging PLT in '%s' to writable\n",
+                     DSO_FILENAME (map->l_name));
+
+  if (__glibc_unlikely (__mprotect ((void *) plt_aligned, pltsz,
+                                   PROT_WRITE | PROT_READ) < 0))
+    {
+      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
+       _dl_debug_printf ("\nfailed to change PLT in '%s' to writable\n",
+                         DSO_FILENAME (map->l_name));
+      return;
+    }
+
+  x86_64_rewrite_plt (map, plt_aligned);
+
+  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
+    _dl_debug_printf ("\nchanging PLT in '%s' back to read-only\n",
+                     DSO_FILENAME (map->l_name));
+
+  if (__glibc_unlikely (__mprotect ((void *) plt_aligned, pltsz,
+                                   PROT_EXEC | PROT_READ) < 0))
+    _dl_signal_error (0, DSO_FILENAME (map->l_name), NULL,
+                     "failed to change PLT back to read-only");
+}
+
+/* Rewrite PLT entries to direct branch if possible.  */
+
+static inline void
+x86_64_dynamic_after_reloc (struct link_map *map, int lazy)
+{
+  /* Ignore DT_X86_64_PLT if the lazy binding is enabled.  */
+  if (lazy != 0)
+    return;
+
+  /* Ignore DT_X86_64_PLT if PLT rewrite isn't enabled.  */
+  if (__glibc_likely (GL(dl_x86_feature_control).plt_rewrite
+                     == plt_rewrite_none))
+    return;
+
+  if (__glibc_likely (map->l_info[DT_X86_64 (PLT)] == NULL))
+    return;
+
+  /* Ignore DT_X86_64_PLT if there is no R_X86_64_JUMP_SLOT.  */
+  if (map->l_has_jump_slot_reloc == 0)
+    return;
+
+  /* Ignore DT_X86_64_PLT if
+     1. DT_JMPREL isn't available or its value is 0.
+     2. DT_PLTRELSZ is 0.
+     3. DT_X86_64_PLTENT isn't available or its value is smaller than
+       16 bytes.
+     4. DT_X86_64_PLTSZ isn't available or its value is smaller than
+       DT_X86_64_PLTENT's value or isn't a multiple of DT_X86_64_PLTENT's
+       value.  */
+  if (map->l_info[DT_JMPREL] == NULL
+      || map->l_info[DT_JMPREL]->d_un.d_ptr == 0
+      || map->l_info[DT_PLTRELSZ]->d_un.d_val == 0
+      || map->l_info[DT_X86_64 (PLTSZ)] == NULL
+      || map->l_info[DT_X86_64 (PLTENT)] == NULL
+      || map->l_info[DT_X86_64 (PLTENT)]->d_un.d_val < 16
+      || (map->l_info[DT_X86_64 (PLTSZ)]->d_un.d_val
+         < map->l_info[DT_X86_64 (PLTENT)]->d_un.d_val)
+      || (map->l_info[DT_X86_64 (PLTSZ)]->d_un.d_val
+         % map->l_info[DT_X86_64 (PLTENT)]->d_un.d_val) != 0)
+    return;
+
+  x86_64_rewrite_plt_in_place (map);
+}
+#endif
diff --git a/sysdeps/x86_64/link_map.h b/sysdeps/x86_64/link_map.h
new file mode 100644 (file)
index 0000000..537f56a
--- /dev/null
@@ -0,0 +1,22 @@
+/* Additional fields in struct link_map.  x86-64 version.
+   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/>.  */
+
+/* Has R_X86_64_JUMP_SLOT relocation.  */
+bool l_has_jump_slot_reloc;
+
+#include <sysdeps/x86/link_map.h>
diff --git a/sysdeps/x86_64/tst-plt-rewrite1.c b/sysdeps/x86_64/tst-plt-rewrite1.c
new file mode 100644 (file)
index 0000000..8678595
--- /dev/null
@@ -0,0 +1,31 @@
+/* Test PLT rewrite.
+   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 <string.h>
+#include <support/check.h>
+
+extern const char *foo (void);
+
+static int
+do_test (void)
+{
+  TEST_COMPARE (strcmp (foo (), "PLT rewrite works"), 0);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/x86_64/tst-plt-rewritemod1.c b/sysdeps/x86_64/tst-plt-rewritemod1.c
new file mode 100644 (file)
index 0000000..99f21fb
--- /dev/null
@@ -0,0 +1,32 @@
+/* Check PLT rewrite works correctly.
+   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/>.  */
+
+/* foo calls bar with indirect branch via PLT.  PLT rewrite should
+   change it to direct branch.  */
+
+const char *
+bar (void)
+{
+  return "PLT rewrite works";
+}
+
+const char *
+foo (void)
+{
+  return bar ();
+}