#5 Allocate PHT & SHT at the end of the *.elf file
Merged a year ago by jsanders. Opened a year ago by churchyard.
rpms/ churchyard/patchelf backport  into  rawhide

@@ -0,0 +1,344 @@ 

+ From a97399ab349ef06c4f1ebb0ef002a5e6ef17a8d1 Mon Sep 17 00:00:00 2001

+ From: Patryk Wychowaniec <[email protected]>

+ Date: Sat, 28 Dec 2024 17:00:44 +0100

+ Subject: [PATCH] Allocate PHT & SHT at the end of the *.elf file

+ 

+ ---

+  src/patchelf.cc              | 172 ++++++++++++++++++++++-------------

+  src/patchelf.h               |   3 +-

+  tests/grow-file.sh           |   5 +-

+  tests/repeated-updates.sh    |   2 +-

+  tests/short-first-segment.sh |   7 +-

+  5 files changed, 120 insertions(+), 69 deletions(-)

+ 

+ diff --git a/src/patchelf.cc b/src/patchelf.cc

+ index 82b4b46..074e6d5 100644

+ --- a/src/patchelf.cc

+ +++ b/src/patchelf.cc

+ @@ -553,8 +553,7 @@ void ElfFile<ElfFileParamNames>::shiftFile(unsigned int extraPages, size_t start

+  

+      assert(splitIndex != -1);

+  

+ -    /* Add a segment that maps the new program/section headers and

+ -       PT_INTERP segment into memory.  Otherwise glibc will choke. */

+ +    /* Add another PT_LOAD segment loading the data we've split above. */

+      phdrs.resize(rdi(hdr()->e_phnum) + 1);

+      wri(hdr()->e_phnum, rdi(hdr()->e_phnum) + 1);

+      Elf_Phdr & phdr = phdrs.at(rdi(hdr()->e_phnum) - 1);

+ @@ -634,11 +633,19 @@ unsigned int ElfFile<ElfFileParamNames>::getSectionIndex(const SectionName & sec

+  }

+  

+  template<ElfFileParams>

+ -bool ElfFile<ElfFileParamNames>::haveReplacedSection(const SectionName & sectionName) const

+ +bool ElfFile<ElfFileParamNames>::hasReplacedSection(const SectionName & sectionName) const

+  {

+      return replacedSections.count(sectionName);

+  }

+  

+ +template<ElfFileParams>

+ +bool ElfFile<ElfFileParamNames>::canReplaceSection(const SectionName & sectionName) const

+ +{

+ +    auto shdr = findSectionHeader(sectionName);

+ +

+ +    return sectionName == ".interp" || rdi(shdr.sh_type) != SHT_PROGBITS;

+ +}

+ +

+  template<ElfFileParams>

+  std::string & ElfFile<ElfFileParamNames>::replaceSection(const SectionName & sectionName,

+      unsigned int size)

+ @@ -819,28 +826,59 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()

+      unsigned int num_notes = std::count_if(shdrs.begin(), shdrs.end(),

+          [this](Elf_Shdr shdr) { return rdi(shdr.sh_type) == SHT_NOTE; });

+  

+ -    /* Because we're adding a new section header, we're necessarily increasing

+ -       the size of the program header table.  This can cause the first section

+ -       to overlap the program header table in memory; we need to shift the first

+ -       few segments to someplace else. */

+ -    /* Some sections may already be replaced so account for that */

+ +    /* Compute the total space needed for the replaced sections, pessimistically

+ +       assuming we're going to need one more to account for new PT_LOAD covering

+ +       relocated PHDR */

+ +    off_t phtSize = roundUp((phdrs.size() + num_notes + 1) * sizeof(Elf_Phdr) + sizeof(Elf_Ehdr), sectionAlignment);

+ +    off_t shtSize = roundUp(rdi(hdr()->e_shnum) * rdi(hdr()->e_shentsize), sectionAlignment);

+ +

+ +    /* Check if we can keep PHT at the beginning of the file.

+ +

+ +       We'd like to do that, because it preverves compatibility with older

+ +       kernels¹ - but if the PHT has grown too much, we have no other option but

+ +       to move it at the end of the file.

+ +

+ +       ¹ older kernels had a bug that prevented them from loading ELFs with

+ +         PHDRs not located at the beginning of the file; it was fixed over

+ +         0da1d5002745cdc721bc018b582a8a9704d56c42 (2022-03-02) */

+ +    bool relocatePht = false;

+      unsigned int i = 1;

+ -    Elf_Addr pht_size = sizeof(Elf_Ehdr) + (phdrs.size() + num_notes + 1)*sizeof(Elf_Phdr);

+ -    while( i < rdi(hdr()->e_shnum) && rdi(shdrs.at(i).sh_offset) <= pht_size ) {

+ -        if (not haveReplacedSection(getSectionName(shdrs.at(i))))

+ -            replaceSection(getSectionName(shdrs.at(i)), rdi(shdrs.at(i).sh_size));

+ +

+ +    while (i < rdi(hdr()->e_shnum) && ((off_t) rdi(shdrs.at(i).sh_offset)) <= phtSize) {

+ +        const auto & sectionName = getSectionName(shdrs.at(i));

+ +

+ +        if (!hasReplacedSection(sectionName) && !canReplaceSection(sectionName)) {

+ +            relocatePht = true;

+ +            break;

+ +        }

+ +

+          i++;

+      }

+ -    bool moveHeaderTableToTheEnd = rdi(hdr()->e_shoff) < pht_size;

+ +    if (!relocatePht) {

+ +        unsigned int i = 1;

+ +

+ +        while (i < rdi(hdr()->e_shnum) && ((off_t) rdi(shdrs.at(i).sh_offset)) <= phtSize) {

+ +            const auto & sectionName = getSectionName(shdrs.at(i));

+ +            const auto sectionSize = rdi(shdrs.at(i).sh_size);

+ +

+ +            if (!hasReplacedSection(sectionName)) {

+ +                replaceSection(sectionName, sectionSize);

+ +            }

+ +

+ +            i++;

+ +        }

+ +    }

+ +

+ +    /* Calculate how much space we'll need. */

+ +    off_t neededSpace = shtSize;

+ +

+ +    if (relocatePht) {

+ +        neededSpace += phtSize;

+ +    }

+  

+ -    /* Compute the total space needed for the replaced sections */

+ -    off_t neededSpace = 0;

+      for (auto & s : replacedSections)

+          neededSpace += roundUp(s.second.size(), sectionAlignment);

+  

+ -    off_t headerTableSpace = roundUp(rdi(hdr()->e_shnum) * rdi(hdr()->e_shentsize), sectionAlignment);

+ -    if (moveHeaderTableToTheEnd)

+ -        neededSpace += headerTableSpace;

+      debug("needed space is %d\n", neededSpace);

+  

+      Elf_Off startOffset = roundUp(fileContents->size(), getPageSize());

+ @@ -851,43 +889,29 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()

+      off_t binutilsQuirkPadding = 1;

+      fileContents->resize(startOffset + neededSpace + binutilsQuirkPadding, 0);

+  

+ -    /* Even though this file is of type ET_DYN, it could actually be

+ -       an executable.  For instance, Gold produces executables marked

+ -       ET_DYN as does LD when linking with pie. If we move PT_PHDR, it

+ -       has to stay in the first PT_LOAD segment or any subsequent ones

+ -       if they're continuous in memory due to linux kernel constraints

+ -       (see BUGS). Since the end of the file would be after bss, we can't

+ -       move PHDR there, we therefore choose to leave PT_PHDR where it is but

+ -       move enough following sections such that we can add the extra PT_LOAD

+ -       section to it. This PT_LOAD segment ensures the sections at the end of

+ -       the file are mapped into memory for ld.so to process.

+ -       We can't use the approach in rewriteSectionsExecutable()

+ -       since DYN executables tend to start at virtual address 0, so

+ -       rewriteSectionsExecutable() won't work because it doesn't have

+ -       any virtual address space to grow downwards into. */

+ -    if (isExecutable && startOffset > startPage) {

+ -        debug("shifting new PT_LOAD segment by %d bytes to work around a Linux kernel bug\n", startOffset - startPage);

+ -        startPage = startOffset;

+ -    }

+ -

+ -    wri(hdr()->e_phoff, sizeof(Elf_Ehdr));

+ -

+ -    bool needNewSegment = true;

+      auto& lastSeg = phdrs.back();

+ -    /* Try to extend the last segment to include replaced sections */

+ +    Elf_Addr lastSegAddr = 0;

+ +

+ +    /* As an optimization, instead of allocating a new PT_LOAD segment, try

+ +       expanding the last one */

+      if (!phdrs.empty() &&

+          rdi(lastSeg.p_type) == PT_LOAD &&

+          rdi(lastSeg.p_flags) == (PF_R | PF_W) &&

+          rdi(lastSeg.p_align) == alignStartPage) {

+          auto segEnd = roundUp(rdi(lastSeg.p_offset) + rdi(lastSeg.p_memsz), getPageSize());

+ +

+          if (segEnd == startOffset) {

+              auto newSz = startOffset + neededSpace - rdi(lastSeg.p_offset);

+ +

+              wri(lastSeg.p_filesz, wri(lastSeg.p_memsz, newSz));

+ -            needNewSegment = false;

+ +

+ +            lastSegAddr = rdi(lastSeg.p_vaddr) + newSz - neededSpace;

+          }

+      }

+  

+ -    if (needNewSegment) {

+ +    if (lastSegAddr == 0) {

+ +        debug("allocating new PT_LOAD segment\n");

+ +

+          /* Add a segment that maps the replaced sections into memory. */

+          phdrs.resize(rdi(hdr()->e_phnum) + 1);

+          wri(hdr()->e_phnum, rdi(hdr()->e_phnum) + 1);

+ @@ -898,6 +922,8 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()

+          wri(phdr.p_filesz, wri(phdr.p_memsz, neededSpace));

+          wri(phdr.p_flags, PF_R | PF_W);

+          wri(phdr.p_align, alignStartPage);

+ +

+ +        lastSegAddr = startPage;

+      }

+  

+      normalizeNoteSegments();

+ @@ -906,17 +932,34 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()

+      /* Write out the replaced sections. */

+      Elf_Off curOff = startOffset;

+  

+ -    if (moveHeaderTableToTheEnd) {

+ -        debug("Moving the shtable to offset %d\n", curOff);

+ -        wri(hdr()->e_shoff, curOff);

+ -        curOff += headerTableSpace;

+ +    if (relocatePht) {

+ +        debug("rewriting pht from offset 0x%x to offset 0x%x (size %d)\n",

+ +            rdi(hdr()->e_phoff), curOff, phtSize);

+ +

+ +        wri(hdr()->e_phoff, curOff);

+ +        curOff += phtSize;

+      }

+  

+ +    // ---

+ +

+ +    debug("rewriting sht from offset 0x%x to offset 0x%x (size %d)\n",

+ +        rdi(hdr()->e_shoff), curOff, shtSize);

+ +

+ +    wri(hdr()->e_shoff, curOff);

+ +    curOff += shtSize;

+ +

+ +    // ---

+ +

+ +    /* Write out the replaced sections. */

+      writeReplacedSections(curOff, startPage, startOffset);

+      assert(curOff == startOffset + neededSpace);

+  

+      /* Write out the updated program and section headers */

+ -    rewriteHeaders(firstPage + rdi(hdr()->e_phoff));

+ +    if (relocatePht) {

+ +        rewriteHeaders(lastSegAddr);

+ +    } else {

+ +        rewriteHeaders(firstPage + rdi(hdr()->e_phoff));

+ +    }

+  }

+  

+  static bool noSort = false;

+ @@ -1030,32 +1073,35 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsExecutable()

+  

+          firstPage -= neededPages * getPageSize();

+          startOffset += neededPages * getPageSize();

+ -    } else {

+ -        Elf_Off rewrittenSectionsOffset = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);

+ -        for (auto& phdr : phdrs)

+ -            if (rdi(phdr.p_type) == PT_LOAD &&

+ -                rdi(phdr.p_offset) <= rewrittenSectionsOffset &&

+ -                rdi(phdr.p_offset) + rdi(phdr.p_filesz) > rewrittenSectionsOffset &&

+ -                rdi(phdr.p_filesz) < neededSpace)

+ -            {

+ -                wri(phdr.p_filesz, neededSpace);

+ -                wri(phdr.p_memsz, neededSpace);

+ -                break;

+ -            }

+      }

+  

+ +    Elf_Off curOff = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);

+ +

+ +    /* Ensure PHDR is covered by a LOAD segment.

+ +

+ +       Because PHDR is supposed to have been covered by such section before, in

+ +       here we assume that we don't have to create any new section, but rather

+ +       extend the existing one. */

+ +    for (auto& phdr : phdrs)

+ +        if (rdi(phdr.p_type) == PT_LOAD &&

+ +            rdi(phdr.p_offset) <= curOff &&

+ +            rdi(phdr.p_offset) + rdi(phdr.p_filesz) > curOff &&

+ +            rdi(phdr.p_filesz) < neededSpace)

+ +        {

+ +            wri(phdr.p_filesz, neededSpace);

+ +            wri(phdr.p_memsz, neededSpace);

+ +            break;

+ +        }

+  

+      /* Clear out the free space. */

+ -    Elf_Off curOff = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);

+      debug("clearing first %d bytes\n", startOffset - curOff);

+      memset(fileContents->data() + curOff, 0, startOffset - curOff);

+  

+ -

+      /* Write out the replaced sections. */

+      writeReplacedSections(curOff, firstPage, 0);

+      assert(curOff == neededSpace);

+  

+ -

+ +    /* Write out the updated program and section headers */

+      rewriteHeaders(firstPage + rdi(hdr()->e_phoff));

+  }

+  

+ diff --git a/src/patchelf.h b/src/patchelf.h

+ index 4e229d6..5ed1b58 100644

+ --- a/src/patchelf.h

+ +++ b/src/patchelf.h

+ @@ -120,7 +120,8 @@ private:

+      std::string & replaceSection(const SectionName & sectionName,

+          unsigned int size);

+  

+ -    [[nodiscard]] bool haveReplacedSection(const SectionName & sectionName) const;

+ +    [[nodiscard]] bool hasReplacedSection(const SectionName & sectionName) const;

+ +    [[nodiscard]] bool canReplaceSection(const SectionName & sectionName) const;

+  

+      void writeReplacedSections(Elf_Off & curOff,

+          Elf_Addr startAddr, Elf_Off startOffset);

+ diff --git a/tests/grow-file.sh b/tests/grow-file.sh

+ index ec5957d..29bba59 100755

+ --- a/tests/grow-file.sh

+ +++ b/tests/grow-file.sh

+ @@ -7,10 +7,11 @@ mkdir -p "${SCRATCH}"

+  

+  cp simple-pie "${SCRATCH}/simple-pie"

+  

+ -# Add a 40MB rpath

+ -tr -cd 'a-z0-9' < /dev/urandom | dd count=40 bs=1000000 > "${SCRATCH}/foo.bin"

+ +# Add a large rpath

+ +printf '=%.0s' $(seq 1 4096) > "${SCRATCH}/foo.bin"

+  

+  # Grow the file

+  ../src/patchelf --add-rpath @"${SCRATCH}/foo.bin" "${SCRATCH}/simple-pie"

+ +

+  # Make sure we can still run it

+  "${SCRATCH}/simple-pie"

+ diff --git a/tests/repeated-updates.sh b/tests/repeated-updates.sh

+ index 669b11d..1dc3fa2 100755

+ --- a/tests/repeated-updates.sh

+ +++ b/tests/repeated-updates.sh

+ @@ -33,7 +33,7 @@ load_segments_after=$(readelf -W -l libbar.so | grep -c LOAD)

+  # To be even more strict, check that we don't add too many extra LOAD entries

+  ###############################################################################

+  echo "Segments before: ${load_segments_before} and after: ${load_segments_after}"

+ -if [ "${load_segments_after}" -gt $((load_segments_before + 2)) ]

+ +if [ "${load_segments_after}" -gt $((load_segments_before + 3)) ]

+  then

+      exit 1

+  fi

+ diff --git a/tests/short-first-segment.sh b/tests/short-first-segment.sh

+ index 07019fc..ecdf716 100755

+ --- a/tests/short-first-segment.sh

+ +++ b/tests/short-first-segment.sh

+ @@ -24,8 +24,11 @@ cd "${SCRATCH}"

+  

+  ldd "${EXEC_NAME}"

+  

+ -${PATCHELF} --add-rpath lalalalalalalala --output modified1 "${EXEC_NAME}"

+ +

+ +${PATCHELF} --set-rpath "$(printf '=%.0s' $(seq 1 4096))" --output modified1 "${EXEC_NAME}"

+ +${PATCHELF} --add-rpath "$(printf '=%.0s' $(seq 1 4096))" modified1

+ +

+  ldd modified1

+  

+ -${PATCHELF}  --add-needed "libXcursor.so.1" --output modified2 modified1

+ +${PATCHELF} --add-needed "libXcursor.so.1" --output modified2 modified1

+  ldd modified2

+ -- 

+ 2.48.1

+ 

file modified
+11 -2
@@ -3,7 +3,7 @@ 

  

  Name:           patchelf

  Version:        0.18.0

- Release:        7%{?dist}

+ Release:        8%{?dist}

  Summary:        A utility for patching ELF binaries

  

  # Automatically converted from old format: GPLv3+ - review is highly recommended.
@@ -11,6 +11,11 @@ 

  URL:            http://nixos.org/patchelf.html

  Source0:        https://github.com/NixOS/%{name}/archive/%{version}/%{name}-%{version}.tar.gz

  

+ # Allocate PHT & SHT at the end of the *.elf file

+ # This is needed after a change in binutils, see https://bugzilla.redhat.com/2321588

+ # Rebased form https://github.com/NixOS/patchelf/commit/43b75fbc9f

+ Patch:          0001-Allocate-PHT-SHT-at-the-end-of-the-.elf-file.patch

+ 

  BuildRequires:  gcc

  BuildRequires:  gcc-c++

  BuildRequires:  make
@@ -26,7 +31,7 @@ 

  of an executable and change the RPATH of an executable or library.

  

  %prep

- %setup -q

+ %autosetup -p1

  

  # package ships elf.h - delete to use glibc-headers one

  rm src/elf.h
@@ -55,6 +60,10 @@ 

  %{_datadir}/zsh/site-functions/_patchelf

  

  %changelog

+ * Fri Jan 31 2025 Miro Hrončok <[email protected]> - 0.18.0-8

+ - Allocate PHT & SHT at the end of the *.elf file

+ - Fxies: rhbz#2321588

+ 

  * Fri Jan 17 2025 Fedora Release Engineering <[email protected]> - 0.18.0-7

  - Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild

  

python-meson-python seems fixed by this

pypy3.9 keeps failing in copr for what seems to be an unrelated reason :/

Pull-Request has been merged by jsanders

a year ago

I've applied it, but a simple test with --set-rpath causes a segfault, unless built with -Wl,--no-rosegment

Oh. Perhaps we shouldn't have applied it then :/