Skip to content

Commit da4b0b0

Browse files
committed
Allocate PHT & SHT at the end of the *.elf
1 parent 7c2f768 commit da4b0b0

File tree

5 files changed

+90
-102
lines changed

5 files changed

+90
-102
lines changed

BUGS

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

src/patchelf.cc

Lines changed: 82 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <stdexcept>
2828
#include <string>
2929
#include <string_view>
30+
#include <type_traits>
3031
#include <unordered_map>
3132
#include <unordered_set>
3233
#include <vector>
@@ -127,6 +128,14 @@ constexpr I ElfFile<ElfFileParamNames>::rdi(I i) const noexcept
127128
return r;
128129
}
129130

131+
static void warn(const char * format, ...)
132+
{
133+
va_list ap;
134+
va_start(ap, format);
135+
fprintf(stderr, "warning: ");
136+
vfprintf(stderr, format, ap);
137+
va_end(ap);
138+
}
130139

131140
static void debug(const char * format, ...)
132141
{
@@ -554,8 +563,7 @@ void ElfFile<ElfFileParamNames>::shiftFile(unsigned int extraPages, size_t start
554563

555564
assert(splitIndex != -1);
556565

557-
/* Add a segment that maps the new program/section headers and
558-
PT_INTERP segment into memory. Otherwise glibc will choke. */
566+
/* Add another PT_LOAD segment loading the data we've split above. */
559567
phdrs.resize(rdi(hdr()->e_phnum) + 1);
560568
wri(hdr()->e_phnum, rdi(hdr()->e_phnum) + 1);
561569
Elf_Phdr & phdr = phdrs.at(rdi(hdr()->e_phnum) - 1);
@@ -801,49 +809,37 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
801809
at the end of the file. They're mapped into memory by a
802810
PT_LOAD segment located directly after the last virtual address
803811
page of other segments. */
804-
Elf_Addr startPage = 0;
805-
Elf_Addr firstPage = 0;
806-
unsigned alignStartPage = getPageSize();
812+
Elf_Addr firstAddr = 0;
813+
Elf_Addr lastAddr = 0;
814+
unsigned alignLastAddr = getPageSize();
815+
807816
for (auto & phdr : phdrs) {
808-
Elf_Addr thisPage = rdi(phdr.p_vaddr) + rdi(phdr.p_memsz);
809-
if (thisPage > startPage) startPage = thisPage;
810-
if (rdi(phdr.p_type) == PT_PHDR) firstPage = rdi(phdr.p_vaddr) - rdi(phdr.p_offset);
817+
Elf_Addr thisAddr = rdi(phdr.p_vaddr) + rdi(phdr.p_memsz);
818+
if (thisAddr > lastAddr) lastAddr = thisAddr;
819+
if (rdi(phdr.p_type) == PT_PHDR) firstAddr = rdi(phdr.p_vaddr) - rdi(phdr.p_offset);
811820
unsigned thisAlign = rdi(phdr.p_align);
812-
alignStartPage = std::max(alignStartPage, thisAlign);
821+
alignLastAddr = std::max(alignLastAddr, thisAlign);
813822
}
814823

815-
startPage = roundUp(startPage, alignStartPage);
824+
lastAddr = roundUp(lastAddr, alignLastAddr);
816825

817-
debug("last page is 0x%llx\n", (unsigned long long) startPage);
818-
debug("first page is 0x%llx\n", (unsigned long long) firstPage);
826+
debug("first address is 0x%llx\n", (unsigned long long) firstAddr);
827+
debug("last address is 0x%llx\n", (unsigned long long) lastAddr);
819828

820829
/* When normalizing note segments we will in the worst case be adding
821830
1 program header for each SHT_NOTE section. */
822831
unsigned int num_notes = std::count_if(shdrs.begin(), shdrs.end(),
823832
[this](Elf_Shdr shdr) { return rdi(shdr.sh_type) == SHT_NOTE; });
824833

825-
/* Because we're adding a new section header, we're necessarily increasing
826-
the size of the program header table. This can cause the first section
827-
to overlap the program header table in memory; we need to shift the first
828-
few segments to someplace else. */
829-
/* Some sections may already be replaced so account for that */
830-
unsigned int i = 1;
831-
Elf_Addr pht_size = sizeof(Elf_Ehdr) + (phdrs.size() + num_notes + 1)*sizeof(Elf_Phdr);
832-
while( i < rdi(hdr()->e_shnum) && rdi(shdrs.at(i).sh_offset) <= pht_size ) {
833-
if (not haveReplacedSection(getSectionName(shdrs.at(i))))
834-
replaceSection(getSectionName(shdrs.at(i)), rdi(shdrs.at(i).sh_size));
835-
i++;
836-
}
837-
bool moveHeaderTableToTheEnd = rdi(hdr()->e_shoff) < pht_size;
834+
/* Compute the total space needed for the replaced sections, pessimistically
835+
assuming we're going to need one more to account for `needNewSegment` */
836+
Elf_Addr 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+
off_t neededSpace = phtSize + shtSize;
838839

839-
/* Compute the total space needed for the replaced sections */
840-
off_t neededSpace = 0;
841840
for (auto & s : replacedSections)
842841
neededSpace += roundUp(s.second.size(), sectionAlignment);
843842

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

849845
Elf_Off startOffset = roundUp(fileContents->size(), getPageSize());
@@ -852,37 +848,20 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
852848
// section segment is strictly smaller than the file (and not same size).
853849
// By making it one byte larger, we don't break readelf.
854850
off_t binutilsQuirkPadding = 1;
855-
fileContents->resize(startOffset + neededSpace + binutilsQuirkPadding, 0);
856-
857-
/* Even though this file is of type ET_DYN, it could actually be
858-
an executable. For instance, Gold produces executables marked
859-
ET_DYN as does LD when linking with pie. If we move PT_PHDR, it
860-
has to stay in the first PT_LOAD segment or any subsequent ones
861-
if they're continuous in memory due to linux kernel constraints
862-
(see BUGS). Since the end of the file would be after bss, we can't
863-
move PHDR there, we therefore choose to leave PT_PHDR where it is but
864-
move enough following sections such that we can add the extra PT_LOAD
865-
section to it. This PT_LOAD segment ensures the sections at the end of
866-
the file are mapped into memory for ld.so to process.
867-
We can't use the approach in rewriteSectionsExecutable()
868-
since DYN executables tend to start at virtual address 0, so
869-
rewriteSectionsExecutable() won't work because it doesn't have
870-
any virtual address space to grow downwards into. */
871-
if (isExecutable && startOffset > startPage) {
872-
debug("shifting new PT_LOAD segment by %d bytes to work around a Linux kernel bug\n", startOffset - startPage);
873-
startPage = startOffset;
874-
}
875851

876-
wri(hdr()->e_phoff, sizeof(Elf_Ehdr));
852+
fileContents->resize(startOffset + neededSpace + binutilsQuirkPadding, 0);
877853

878854
bool needNewSegment = true;
879855
auto& lastSeg = phdrs.back();
880-
/* Try to extend the last segment to include replaced sections */
856+
857+
/* As an optimization, instead of allocating a new PT_LOAD segment, try
858+
expanding the last segment */
881859
if (!phdrs.empty() &&
882860
rdi(lastSeg.p_type) == PT_LOAD &&
883861
rdi(lastSeg.p_flags) == (PF_R | PF_W) &&
884-
rdi(lastSeg.p_align) == alignStartPage) {
862+
rdi(lastSeg.p_align) == alignLastAddr) {
885863
auto segEnd = roundUp(rdi(lastSeg.p_offset) + rdi(lastSeg.p_memsz), getPageSize());
864+
886865
if (segEnd == startOffset) {
887866
auto newSz = startOffset + neededSpace - rdi(lastSeg.p_offset);
888867
wri(lastSeg.p_filesz, wri(lastSeg.p_memsz, newSz));
@@ -897,29 +876,39 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
897876
Elf_Phdr & phdr = phdrs.at(rdi(hdr()->e_phnum) - 1);
898877
wri(phdr.p_type, PT_LOAD);
899878
wri(phdr.p_offset, startOffset);
900-
wri(phdr.p_vaddr, wri(phdr.p_paddr, startPage));
879+
wri(phdr.p_vaddr, wri(phdr.p_paddr, lastAddr));
901880
wri(phdr.p_filesz, wri(phdr.p_memsz, neededSpace));
902881
wri(phdr.p_flags, PF_R | PF_W);
903-
wri(phdr.p_align, alignStartPage);
882+
wri(phdr.p_align, alignLastAddr);
904883
}
905884

906885
normalizeNoteSegments();
907886

908-
909887
/* Write out the replaced sections. */
910-
Elf_Off curOff = startOffset;
888+
Elf_Off currOffset = startOffset;
911889

912-
if (moveHeaderTableToTheEnd) {
913-
debug("Moving the shtable to offset %d\n", curOff);
914-
wri(hdr()->e_shoff, curOff);
915-
curOff += headerTableSpace;
916-
}
890+
debug("rewriting pht from offset 0x%x to offset 0x%x\n",
891+
rdi(hdr()->e_phoff), currOffset);
917892

918-
writeReplacedSections(curOff, startPage, startOffset);
919-
assert(curOff == startOffset + neededSpace);
893+
wri(hdr()->e_phoff, currOffset);
894+
currOffset += phtSize;
920895

921-
/* Write out the updated program and section headers */
922-
rewriteHeaders(firstPage + rdi(hdr()->e_phoff));
896+
// ---
897+
898+
debug("rewriting sht from offset 0x%x to offset 0x%x\n",
899+
rdi(hdr()->e_phoff), currOffset);
900+
901+
wri(hdr()->e_shoff, currOffset);
902+
currOffset += shtSize;
903+
904+
// ---
905+
906+
writeReplacedSections(currOffset, lastAddr, startOffset);
907+
assert(currOffset == startOffset + neededSpace);
908+
909+
// ---
910+
911+
rewriteHeaders(lastAddr);
923912
}
924913

925914
static bool noSort = false;
@@ -1033,31 +1022,33 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsExecutable()
10331022

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

1027+
Elf_Off currOffset = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);
10501028

1051-
/* Clear out the free space. */
1052-
Elf_Off curOff = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);
1053-
debug("clearing first %d bytes\n", startOffset - curOff);
1054-
memset(fileContents->data() + curOff, 0, startOffset - curOff);
1029+
/* Ensure PHDR is covered by a LOAD segment.
10551030
1031+
Because PHDR is supposed to have been covered by such section before, in
1032+
here we assume that we don't have to create any new section, but rather
1033+
extend the existing one. */
1034+
for (auto& phdr : phdrs)
1035+
if (rdi(phdr.p_type) == PT_LOAD &&
1036+
rdi(phdr.p_offset) <= currOffset &&
1037+
rdi(phdr.p_offset) + rdi(phdr.p_filesz) > currOffset &&
1038+
rdi(phdr.p_filesz) < neededSpace)
1039+
{
1040+
wri(phdr.p_filesz, neededSpace);
1041+
wri(phdr.p_memsz, neededSpace);
1042+
break;
1043+
}
10561044

1057-
/* Write out the replaced sections. */
1058-
writeReplacedSections(curOff, firstPage, 0);
1059-
assert(curOff == neededSpace);
1045+
/* Clear out the free space. */
1046+
debug("clearing first %d bytes\n", startOffset - currOffset);
1047+
memset(fileContents->data() + currOffset, 0, startOffset - currOffset);
10601048

1049+
/* Write out the replaced sections. */
1050+
writeReplacedSections(currOffset, firstPage, 0);
1051+
assert(currOffset == neededSpace);
10611052

10621053
rewriteHeaders(firstPage + rdi(hdr()->e_phoff));
10631054
}
@@ -1152,7 +1143,7 @@ void ElfFile<ElfFileParamNames>::rewriteSections(bool force)
11521143

11531144

11541145
template<ElfFileParams>
1155-
void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
1146+
void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddr)
11561147
{
11571148
/* Rewrite the program header table. */
11581149

@@ -1161,7 +1152,7 @@ void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
11611152
for (auto & phdr : phdrs) {
11621153
if (rdi(phdr.p_type) == PT_PHDR) {
11631154
phdr.p_offset = hdr()->e_phoff;
1164-
wri(phdr.p_vaddr, wri(phdr.p_paddr, phdrAddress));
1155+
wri(phdr.p_vaddr, wri(phdr.p_paddr, phdrAddr));
11651156
wri(phdr.p_filesz, wri(phdr.p_memsz, phdrs.size() * sizeof(Elf_Phdr)));
11661157
break;
11671158
}
@@ -1178,9 +1169,11 @@ void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
11781169
/* Rewrite the section header table. For neatness, keep the
11791170
sections sorted. */
11801171
assert(rdi(hdr()->e_shnum) == shdrs.size());
1172+
11811173
if (!noSort) {
11821174
sortShdrs();
11831175
}
1176+
11841177
for (unsigned int i = 1; i < rdi(hdr()->e_shnum); ++i)
11851178
* ((Elf_Shdr *) (fileContents->data() + rdi(hdr()->e_shoff)) + i) = shdrs.at(i);
11861179

@@ -1262,7 +1255,7 @@ void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
12621255
is broken, and it's not our job to fix it; yet, we have
12631256
to find some location for dynamic loader to write the
12641257
debug pointer to; well, let's write it right here */
1265-
fprintf(stderr, "warning: DT_MIPS_RLD_MAP_REL entry is present, but .rld_map section is not\n");
1258+
warn("DT_MIPS_RLD_MAP_REL entry is present, but .rld_map section is not\n");
12661259
dyn->d_un.d_ptr = 0;
12671260
}
12681261
}
@@ -1281,7 +1274,7 @@ void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
12811274
unsigned int shndx = rdi(sym->st_shndx);
12821275
if (shndx != SHN_UNDEF && shndx < SHN_LORESERVE) {
12831276
if (shndx >= sectionsByOldIndex.size()) {
1284-
fprintf(stderr, "warning: entry %d in symbol table refers to a non-existent section, skipping\n", shndx);
1277+
warn("entry %d in symbol table refers to a non-existent section, skipping\n", shndx);
12851278
continue;
12861279
}
12871280
const std::string & section = sectionsByOldIndex.at(shndx);

tests/no-rpath-pie-powerpc.sh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ if [ "$(echo "$readelfData" | grep -c "PHDR")" != 1 ]; then
3737
fi
3838

3939
virtAddr=$(echo "$readelfData" | grep "PHDR" | awk '{print $3}')
40-
if [ "$virtAddr" != "0x00000034" ]; then
40+
if [ "$virtAddr" != "0x01040000" ]; then
4141
# Triggered if the virtual address is the incorrect endianness
42-
echo "Unexpected virt addr, expected [0x00000034] got [$virtAddr]"
42+
echo "Unexpected virt addr, expected [0x01040000] got [$virtAddr]"
4343
exit 1
4444
fi
4545

@@ -52,4 +52,3 @@ echo "$readelfData" | grep "LOAD" | while read -r line ; do
5252
exit 1
5353
fi
5454
done
55-

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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ if ! gzip --version >/dev/null; then
1212
fi
1313

1414
if test "$(uname -i)" != x86_64 || test "$(uname)" != Linux; then
15-
echo "skipping test: not supported on x86_64 Linux"
15+
echo "skipping test: supported only on x86_64 Linux"
1616
exit 77
1717
fi
1818

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

2525
ldd "${EXEC_NAME}"
2626

27-
${PATCHELF} --add-rpath lalalalalalalala --output modified1 "${EXEC_NAME}"
27+
${PATCHELF} --set-rpath $(tr -dc A-Za-z0-9 </dev/urandom | head -c 4096) --output modified1 "${EXEC_NAME}"
28+
${PATCHELF} --add-rpath $(tr -dc A-Za-z0-9 </dev/urandom | head -c 4096) modified1
29+
2830
ldd modified1
2931

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

0 commit comments

Comments
 (0)