[PATCH v3] elf: Add ELF_DYNAMIC_AFTER_RELOC to rewrite PLT
H.J. Lu
hjl.tools@gmail.com
Tue Jan 2 18:03:58 GMT 2024
On Tue, Jan 2, 2024 at 9:56 AM Noah Goldstein <goldstein.w.n@gmail.com> wrote:
>
> On Tue, Jan 2, 2024 at 7:13 AM H.J. Lu <hjl.tools@gmail.com> wrote:
> >
> > Changes in v3:
> >
> > 1. Define and use macros for instruction opcodes and sizes.
> > 2. Use INT32_MIN and UINT32_MAX instead of 0x80000000ULL and
> > 0xffffffffULL.
> > 3. Replace 3 1-byte writes with a 4-byte write to write JMPABS.
> > 4. Verify that DT_X86_64_PLTSZ is a multiple of DT_X86_64_PLTENT.
> > 5. Add some comments for mprotect test.
> > 6. Update plt_rewrite logic.
> >
> > 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. Add
> >
> > $ GLIBC_TUNABLES=glibc.cpu.plt_rewrite=1
> >
> > to enable PLT rewrite at run-time.
> > ---
> > elf/dynamic-link.h | 5 +
> > elf/elf.h | 5 +
> > elf/tst-glibcelf.py | 1 +
> > manual/tunables.texi | 9 +
> > scripts/glibcelf.py | 4 +
> > sysdeps/x86/cet-control.h | 14 ++
> > sysdeps/x86/cpu-features.c | 10 ++
> > sysdeps/x86/dl-procruntime.c | 1 +
> > sysdeps/x86/dl-tunables.list | 5 +
> > sysdeps/x86_64/Makefile | 19 ++
> > sysdeps/x86_64/configure | 35 ++++
> > sysdeps/x86_64/configure.ac | 4 +
> > sysdeps/x86_64/dl-dtprocnum.h | 21 +++
> > sysdeps/x86_64/dl-machine.h | 250 ++++++++++++++++++++++++++-
> > sysdeps/x86_64/link_map.h | 22 +++
> > sysdeps/x86_64/tst-plt-rewrite1.c | 31 ++++
> > sysdeps/x86_64/tst-plt-rewritemod1.c | 32 ++++
> > 17 files changed, 467 insertions(+), 1 deletion(-)
> > create mode 100644 sysdeps/x86_64/dl-dtprocnum.h
> > create mode 100644 sysdeps/x86_64/link_map.h
> > create mode 100644 sysdeps/x86_64/tst-plt-rewrite1.c
> > create mode 100644 sysdeps/x86_64/tst-plt-rewritemod1.c
> >
...
> > diff --git a/sysdeps/x86_64/dl-machine.h b/sysdeps/x86_64/dl-machine.h
> > index e0a9b14469..51935a2fb2 100644
> > --- a/sysdeps/x86_64/dl-machine.h
> > +++ b/sysdeps/x86_64/dl-machine.h
> > @@ -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,9 @@ 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;
> > + case R_X86_64_GLOB_DAT:
> > *reloc_addr = value;
> > break;
> >
> > @@ -549,3 +554,246 @@ 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) plt_aligned)
> > +{
> > + ElfW(Addr) plt_rewrite_bias = plt_rewrite - plt_aligned;
> > + 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;
> > +
> > + branch_start += plt_rewrite_bias;
> > + plt_end = (branch_start & -pltent) + pltent;
> > +
> > + /* Update the PLT entry. */
> > + if (((uint64_t) disp + (uint64_t) ((uint32_t) INT32_MIN))
> > + <= (uint64_t) UINT32_MAX)
> > + {
> > + /* 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));
> > + }
> > +
> > + pad = branch_start + JMP32_INSN_SIZE;
> > +
> > + if (__glibc_unlikely (pad > plt_end))
> > + {
> > + if (__glibc_unlikely (GLRO(dl_debug_mask)
> > + & DL_DEBUG_BINDINGS))
> > + {
> > + const char *sym_name
> > + = x86_64_reloc_symbol_name (map, reloc);
> > + _dl_debug_printf ("\ninvalid r_addend of "
> > + "R_X86_64_JUMP_SLOT against '%s' "
> > + "in '%s'\n", sym_name,
> > + DSO_FILENAME (map->l_name));
> > + }
> > +
> > + continue;
> > + }
> > +
> > + /* 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)
> > + continue;
> > +
> > + pad = branch_start + JMPABS_INSN_SIZE;
> > +
> > + if (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;
> Should this be moved outside of the `else` scope so that we also pad for
> jmp_d32 case?
It is outside of the else. Both cases are padded.
> > + }
> > +}
> > +
> > +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, 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)
> > + 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)
> > + return;
> > +
> > + /* Ignore DT_X86_64_PLT on ld.so to avoid changing its own PLT. */
> > + if (map == &GL(dl_rtld_map) || map->l_real == &GL(dl_rtld_map))
> > + 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;
> > +
> > + if (GL(dl_x86_feature_control).plt_rewrite == plt_rewrite_enabled)
> > + {
> > + enum dl_plt_rewrite_control plt_rewrite = plt_rewrite_none;
> > +
> > + /* PLT rewrite is enabled. Check if mprotect works. */
> > + void *plt = __mmap (NULL, 4096, PROT_READ | PROT_WRITE,
> > + MAP_PRIVATE | MAP_ANONYMOUS,
> > + -1, 0);
> > + if (__glibc_unlikely (plt != MAP_FAILED))
> > + {
> > + /* Touch the PROT_READ | PROT_WRITE page. */
> > + *(int32_t *) plt = -1;
> Should this be +=1 so it does r/w instead of just w?
> I doubt it will matter, but it does seem more complete.
I will drop the whole mprotect check.
Thanks.
--
H.J.
More information about the Libc-alpha
mailing list