Skip to content

Commit 43b75fb

Browse files
Patryk27Mic92
authored andcommitted
Allocate PHT & SHT at the end of the *.elf file
1 parent 769337c commit 43b75fb

File tree

6 files changed

+122
-76
lines changed

6 files changed

+122
-76
lines changed

BUGS

-6
This file was deleted.

src/patchelf.cc

+111-64
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 sectionName == ".interp" || 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,60 @@ 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) + sizeof(Elf_Ehdr), 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.
840+
841+
We'd like to do that, because it preverves compatibility with older
842+
kernels¹ - but if the PHT has grown too much, we have no other option but
843+
to move it at the end of the file.
844+
845+
¹ older kernels had a bug that prevented them from loading ELFs with
846+
PHDRs not located at the beginning of the file; it was fixed over
847+
0da1d5002745cdc721bc018b582a8a9704d56c42 (2022-03-02) */
848+
bool relocatePht = false;
831849
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));
850+
851+
while (i < rdi(hdr()->e_shnum) && ((off_t) rdi(shdrs.at(i).sh_offset)) <= phtSize) {
852+
const auto & sectionName = getSectionName(shdrs.at(i));
853+
854+
if (!hasReplacedSection(sectionName) && !canReplaceSection(sectionName)) {
855+
relocatePht = true;
856+
break;
857+
}
858+
836859
i++;
837860
}
838-
bool moveHeaderTableToTheEnd = rdi(hdr()->e_shoff) < pht_size;
839861

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

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

850889
Elf_Off startOffset = roundUp(fileContents->size(), alignStartPage);
@@ -853,45 +892,32 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
853892
// section segment is strictly smaller than the file (and not same size).
854893
// By making it one byte larger, we don't break readelf.
855894
off_t binutilsQuirkPadding = 1;
856-
fileContents->resize(startOffset + neededSpace + binutilsQuirkPadding, 0);
857-
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-
}
876895

877-
wri(hdr()->e_phoff, sizeof(Elf_Ehdr));
896+
fileContents->resize(startOffset + neededSpace + binutilsQuirkPadding, 0);
878897

879-
bool needNewSegment = true;
880898
auto& lastSeg = phdrs.back();
881-
/* Try to extend the last segment to include replaced sections */
899+
Elf_Addr lastSegAddr = 0;
900+
901+
/* As an optimization, instead of allocating a new PT_LOAD segment, try
902+
expanding the last one */
882903
if (!phdrs.empty() &&
883904
rdi(lastSeg.p_type) == PT_LOAD &&
884905
rdi(lastSeg.p_flags) == (PF_R | PF_W) &&
885906
rdi(lastSeg.p_align) == alignStartPage) {
886907
auto segEnd = roundUp(rdi(lastSeg.p_offset) + rdi(lastSeg.p_memsz), alignStartPage);
908+
887909
if (segEnd == startOffset) {
888910
auto newSz = startOffset + neededSpace - rdi(lastSeg.p_offset);
911+
889912
wri(lastSeg.p_filesz, wri(lastSeg.p_memsz, newSz));
890-
needNewSegment = false;
913+
914+
lastSegAddr = rdi(lastSeg.p_vaddr) + newSz - neededSpace;
891915
}
892916
}
893917

894-
if (needNewSegment) {
918+
if (lastSegAddr == 0) {
919+
debug("allocating new PT_LOAD segment\n");
920+
895921
/* Add a segment that maps the replaced sections into memory. */
896922
phdrs.resize(rdi(hdr()->e_phnum) + 1);
897923
wri(hdr()->e_phnum, rdi(hdr()->e_phnum) + 1);
@@ -903,25 +929,43 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
903929
wri(phdr.p_flags, PF_R | PF_W);
904930
wri(phdr.p_align, alignStartPage);
905931
assert(startPage % alignStartPage == startOffset % alignStartPage);
932+
933+
lastSegAddr = startPage;
906934
}
907935

908936
normalizeNoteSegments();
909937

910-
911938
/* Write out the replaced sections. */
912939
Elf_Off curOff = startOffset;
913940

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

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

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

927971
static bool noSort = false;
@@ -1035,32 +1079,35 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsExecutable()
10351079

10361080
firstPage -= neededPages * getPageSize();
10371081
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-
}
10501082
}
10511083

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

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

1058-
10591106
/* Write out the replaced sections. */
10601107
writeReplacedSections(curOff, firstPage, 0);
10611108
assert(curOff == neededSpace);
10621109

1063-
1110+
/* Write out the updated program and section headers */
10641111
rewriteHeaders(firstPage + rdi(hdr()->e_phoff));
10651112
}
10661113

src/patchelf.h

+2-1
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

+3-2
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

+1-1
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

+5-2
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)