Skip to content

Commit f901e39

Browse files
committed
Allocate PHT & SHT at the end of the *.elf file
1 parent 769337c commit f901e39

File tree

6 files changed

+121
-76
lines changed

6 files changed

+121
-76
lines changed

BUGS

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/patchelf.cc

Lines changed: 110 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -555,8 +555,7 @@ void ElfFile<ElfFileParamNames>::shiftFile(unsigned int extraPages, size_t start
555555

556556
assert(splitIndex != -1);
557557

558-
/* Add a segment that maps the new program/section headers and
559-
PT_INTERP segment into memory. Otherwise glibc will choke. */
558+
/* Add another PT_LOAD segment loading the data we've split above. */
560559
phdrs.resize(rdi(hdr()->e_phnum) + 1);
561560
wri(hdr()->e_phnum, rdi(hdr()->e_phnum) + 1);
562561
Elf_Phdr & phdr = phdrs.at(rdi(hdr()->e_phnum) - 1);
@@ -636,11 +635,19 @@ unsigned int ElfFile<ElfFileParamNames>::getSectionIndex(const SectionName & sec
636635
}
637636

638637
template<ElfFileParams>
639-
bool ElfFile<ElfFileParamNames>::haveReplacedSection(const SectionName & sectionName) const
638+
bool ElfFile<ElfFileParamNames>::hasReplacedSection(const SectionName & sectionName) const
640639
{
641640
return replacedSections.count(sectionName);
642641
}
643642

643+
template<ElfFileParams>
644+
bool ElfFile<ElfFileParamNames>::canReplaceSection(const SectionName & sectionName) const
645+
{
646+
auto shdr = findSectionHeader(sectionName);
647+
648+
return rdi(shdr.sh_type) != SHT_PROGBITS;
649+
}
650+
644651
template<ElfFileParams>
645652
std::string & ElfFile<ElfFileParamNames>::replaceSection(const SectionName & sectionName,
646653
unsigned int size)
@@ -823,28 +830,59 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
823830
unsigned int num_notes = std::count_if(shdrs.begin(), shdrs.end(),
824831
[this](Elf_Shdr shdr) { return rdi(shdr.sh_type) == SHT_NOTE; });
825832

826-
/* Because we're adding a new section header, we're necessarily increasing
827-
the size of the program header table. This can cause the first section
828-
to overlap the program header table in memory; we need to shift the first
829-
few segments to someplace else. */
830-
/* Some sections may already be replaced so account for that */
833+
/* Compute the total space needed for the replaced sections, pessimistically
834+
assuming we're going to need one more to account for new PT_LOAD covering
835+
relocated PHDR */
836+
off_t phtSize = roundUp((phdrs.size() + num_notes + 1) * sizeof(Elf_Phdr), sectionAlignment);
837+
off_t shtSize = roundUp(rdi(hdr()->e_shnum) * rdi(hdr()->e_shentsize), sectionAlignment);
838+
839+
/* Check if we can keep PHT at the beginning of the file. We'd like to do
840+
that, because it preverves compatibility with older kernels¹ - but if the
841+
PHT has grown too much, we have to no other option but to relocate it at
842+
the end of the file.
843+
844+
¹ older kernels had a bug that prevented them from loading ELFs with
845+
PHDRs not located at the beginning of the file; it was fixed over
846+
0da1d5002745cdc721bc018b582a8a9704d56c42 (2022-03-02) */
847+
bool relocatePht = false;
831848
unsigned int i = 1;
832-
Elf_Addr pht_size = sizeof(Elf_Ehdr) + (phdrs.size() + num_notes + 1)*sizeof(Elf_Phdr);
833-
while( i < rdi(hdr()->e_shnum) && rdi(shdrs.at(i).sh_offset) <= pht_size ) {
834-
if (not haveReplacedSection(getSectionName(shdrs.at(i))))
835-
replaceSection(getSectionName(shdrs.at(i)), rdi(shdrs.at(i).sh_size));
849+
850+
while (i < rdi(hdr()->e_shnum) && ((off_t) rdi(shdrs.at(i).sh_offset)) <= phtSize) {
851+
const auto & sectionName = getSectionName(shdrs.at(i));
852+
853+
if (!hasReplacedSection(sectionName) && !canReplaceSection(sectionName)) {
854+
relocatePht = true;
855+
break;
856+
}
857+
836858
i++;
837859
}
838-
bool moveHeaderTableToTheEnd = rdi(hdr()->e_shoff) < pht_size;
839860

840-
/* Compute the total space needed for the replaced sections */
841-
off_t neededSpace = 0;
861+
if (!relocatePht) {
862+
unsigned int i = 1;
863+
864+
while (i < rdi(hdr()->e_shnum) && ((off_t) rdi(shdrs.at(i).sh_offset)) <= phtSize) {
865+
const auto & sectionName = getSectionName(shdrs.at(i));
866+
const auto sectionSize = rdi(shdrs.at(i).sh_size);
867+
868+
if (!hasReplacedSection(sectionName)) {
869+
replaceSection(sectionName, sectionSize);
870+
}
871+
872+
i++;
873+
}
874+
}
875+
876+
/* Calculate how much space we'll need. */
877+
off_t neededSpace = shtSize;
878+
879+
if (relocatePht) {
880+
neededSpace += phtSize;
881+
}
882+
842883
for (auto & s : replacedSections)
843884
neededSpace += roundUp(s.second.size(), sectionAlignment);
844885

845-
off_t headerTableSpace = roundUp(rdi(hdr()->e_shnum) * rdi(hdr()->e_shentsize), sectionAlignment);
846-
if (moveHeaderTableToTheEnd)
847-
neededSpace += headerTableSpace;
848886
debug("needed space is %d\n", neededSpace);
849887

850888
Elf_Off startOffset = roundUp(fileContents->size(), alignStartPage);
@@ -853,45 +891,32 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
853891
// section segment is strictly smaller than the file (and not same size).
854892
// By making it one byte larger, we don't break readelf.
855893
off_t binutilsQuirkPadding = 1;
856-
fileContents->resize(startOffset + neededSpace + binutilsQuirkPadding, 0);
857894

858-
/* Even though this file is of type ET_DYN, it could actually be
859-
an executable. For instance, Gold produces executables marked
860-
ET_DYN as does LD when linking with pie. If we move PT_PHDR, it
861-
has to stay in the first PT_LOAD segment or any subsequent ones
862-
if they're continuous in memory due to linux kernel constraints
863-
(see BUGS). Since the end of the file would be after bss, we can't
864-
move PHDR there, we therefore choose to leave PT_PHDR where it is but
865-
move enough following sections such that we can add the extra PT_LOAD
866-
section to it. This PT_LOAD segment ensures the sections at the end of
867-
the file are mapped into memory for ld.so to process.
868-
We can't use the approach in rewriteSectionsExecutable()
869-
since DYN executables tend to start at virtual address 0, so
870-
rewriteSectionsExecutable() won't work because it doesn't have
871-
any virtual address space to grow downwards into. */
872-
if (isExecutable && startOffset > startPage) {
873-
debug("shifting new PT_LOAD segment by %d bytes to work around a Linux kernel bug\n", startOffset - startPage);
874-
startPage = startOffset;
875-
}
876-
877-
wri(hdr()->e_phoff, sizeof(Elf_Ehdr));
895+
fileContents->resize(startOffset + neededSpace + binutilsQuirkPadding, 0);
878896

879-
bool needNewSegment = true;
880897
auto& lastSeg = phdrs.back();
881-
/* Try to extend the last segment to include replaced sections */
898+
Elf_Addr lastSegAddr = 0;
899+
900+
/* As an optimization, instead of allocating a new PT_LOAD segment, try
901+
expanding the last one */
882902
if (!phdrs.empty() &&
883903
rdi(lastSeg.p_type) == PT_LOAD &&
884904
rdi(lastSeg.p_flags) == (PF_R | PF_W) &&
885905
rdi(lastSeg.p_align) == alignStartPage) {
886906
auto segEnd = roundUp(rdi(lastSeg.p_offset) + rdi(lastSeg.p_memsz), alignStartPage);
907+
887908
if (segEnd == startOffset) {
888909
auto newSz = startOffset + neededSpace - rdi(lastSeg.p_offset);
910+
889911
wri(lastSeg.p_filesz, wri(lastSeg.p_memsz, newSz));
890-
needNewSegment = false;
912+
913+
lastSegAddr = rdi(lastSeg.p_vaddr) + newSz - neededSpace;
891914
}
892915
}
893916

894-
if (needNewSegment) {
917+
if (lastSegAddr == 0) {
918+
debug("allocating new PT_LOAD segment\n");
919+
895920
/* Add a segment that maps the replaced sections into memory. */
896921
phdrs.resize(rdi(hdr()->e_phnum) + 1);
897922
wri(hdr()->e_phnum, rdi(hdr()->e_phnum) + 1);
@@ -903,25 +928,43 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
903928
wri(phdr.p_flags, PF_R | PF_W);
904929
wri(phdr.p_align, alignStartPage);
905930
assert(startPage % alignStartPage == startOffset % alignStartPage);
931+
932+
lastSegAddr = startPage;
906933
}
907934

908935
normalizeNoteSegments();
909936

910-
911937
/* Write out the replaced sections. */
912938
Elf_Off curOff = startOffset;
913939

914-
if (moveHeaderTableToTheEnd) {
915-
debug("Moving the shtable to offset %d\n", curOff);
916-
wri(hdr()->e_shoff, curOff);
917-
curOff += headerTableSpace;
940+
if (relocatePht) {
941+
debug("rewriting pht from offset 0x%x to offset 0x%x (size %d)\n",
942+
rdi(hdr()->e_phoff), curOff, phtSize);
943+
944+
wri(hdr()->e_phoff, curOff);
945+
curOff += phtSize;
918946
}
919947

948+
// ---
949+
950+
debug("rewriting sht from offset 0x%x to offset 0x%x (size %d)\n",
951+
rdi(hdr()->e_shoff), curOff, shtSize);
952+
953+
wri(hdr()->e_shoff, curOff);
954+
curOff += shtSize;
955+
956+
// ---
957+
958+
/* Write out the replaced sections. */
920959
writeReplacedSections(curOff, startPage, startOffset);
921960
assert(curOff == startOffset + neededSpace);
922961

923962
/* Write out the updated program and section headers */
924-
rewriteHeaders(firstPage + rdi(hdr()->e_phoff));
963+
if (relocatePht) {
964+
rewriteHeaders(lastSegAddr);
965+
} else {
966+
rewriteHeaders(firstPage + rdi(hdr()->e_phoff));
967+
}
925968
}
926969

927970
static bool noSort = false;
@@ -1035,32 +1078,35 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsExecutable()
10351078

10361079
firstPage -= neededPages * getPageSize();
10371080
startOffset += neededPages * getPageSize();
1038-
} else {
1039-
Elf_Off rewrittenSectionsOffset = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);
1040-
for (auto& phdr : phdrs)
1041-
if (rdi(phdr.p_type) == PT_LOAD &&
1042-
rdi(phdr.p_offset) <= rewrittenSectionsOffset &&
1043-
rdi(phdr.p_offset) + rdi(phdr.p_filesz) > rewrittenSectionsOffset &&
1044-
rdi(phdr.p_filesz) < neededSpace)
1045-
{
1046-
wri(phdr.p_filesz, neededSpace);
1047-
wri(phdr.p_memsz, neededSpace);
1048-
break;
1049-
}
10501081
}
10511082

1083+
Elf_Off curOff = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);
1084+
1085+
/* Ensure PHDR is covered by a LOAD segment.
1086+
1087+
Because PHDR is supposed to have been covered by such section before, in
1088+
here we assume that we don't have to create any new section, but rather
1089+
extend the existing one. */
1090+
for (auto& phdr : phdrs)
1091+
if (rdi(phdr.p_type) == PT_LOAD &&
1092+
rdi(phdr.p_offset) <= curOff &&
1093+
rdi(phdr.p_offset) + rdi(phdr.p_filesz) > curOff &&
1094+
rdi(phdr.p_filesz) < neededSpace)
1095+
{
1096+
wri(phdr.p_filesz, neededSpace);
1097+
wri(phdr.p_memsz, neededSpace);
1098+
break;
1099+
}
10521100

10531101
/* Clear out the free space. */
1054-
Elf_Off curOff = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);
10551102
debug("clearing first %d bytes\n", startOffset - curOff);
10561103
memset(fileContents->data() + curOff, 0, startOffset - curOff);
10571104

1058-
10591105
/* Write out the replaced sections. */
10601106
writeReplacedSections(curOff, firstPage, 0);
10611107
assert(curOff == neededSpace);
10621108

1063-
1109+
/* Write out the updated program and section headers */
10641110
rewriteHeaders(firstPage + rdi(hdr()->e_phoff));
10651111
}
10661112

src/patchelf.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ class ElfFile
120120
std::string & replaceSection(const SectionName & sectionName,
121121
unsigned int size);
122122

123-
[[nodiscard]] bool haveReplacedSection(const SectionName & sectionName) const;
123+
[[nodiscard]] bool hasReplacedSection(const SectionName & sectionName) const;
124+
[[nodiscard]] bool canReplaceSection(const SectionName & sectionName) const;
124125

125126
void writeReplacedSections(Elf_Off & curOff,
126127
Elf_Addr startAddr, Elf_Off startOffset);

tests/grow-file.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ mkdir -p "${SCRATCH}"
77

88
cp simple-pie "${SCRATCH}/simple-pie"
99

10-
# Add a 40MB rpath
11-
tr -cd 'a-z0-9' < /dev/urandom | dd count=40 bs=1000000 > "${SCRATCH}/foo.bin"
10+
# Add a large rpath
11+
printf '=%.0s' $(seq 1 4096) > "${SCRATCH}/foo.bin"
1212

1313
# Grow the file
1414
../src/patchelf --add-rpath @"${SCRATCH}/foo.bin" "${SCRATCH}/simple-pie"
15+
1516
# Make sure we can still run it
1617
"${SCRATCH}/simple-pie"

tests/repeated-updates.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ load_segments_after=$(${READELF} -W -l libbar.so | grep -c LOAD)
3434
# To be even more strict, check that we don't add too many extra LOAD entries
3535
###############################################################################
3636
echo "Segments before: ${load_segments_before} and after: ${load_segments_after}"
37-
if [ "${load_segments_after}" -gt $((load_segments_before + 2)) ]
37+
if [ "${load_segments_after}" -gt $((load_segments_before + 3)) ]
3838
then
3939
exit 1
4040
fi

tests/short-first-segment.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ cd "${SCRATCH}"
2424

2525
ldd "${EXEC_NAME}"
2626

27-
${PATCHELF} --add-rpath lalalalalalalala --output modified1 "${EXEC_NAME}"
27+
28+
${PATCHELF} --set-rpath "$(printf '=%.0s' $(seq 1 4096))" --output modified1 "${EXEC_NAME}"
29+
${PATCHELF} --add-rpath "$(printf '=%.0s' $(seq 1 4096))" modified1
30+
2831
ldd modified1
2932

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

0 commit comments

Comments
 (0)