Skip to content

Commit a9aa808

Browse files
committed
* Support executables created by the Gold linker (NixOS/140). These
are marked as ET_DYN (not ET_EXEC) and have a starting virtual address of 0 so they cannot grow downwards. In order not to run into a Linux kernel bug, the virtual address and the offset of the new PT_LOAD segment have to be equal; otherwise ld-linux segfaults. To ensure this, it may be necessary to add some padding to the executable (potentially a lot of padding, if the executable has a large uninitialised data segment). * Use size_t rather than off_t in some places.
1 parent 92d4c4f commit a9aa808

File tree

1 file changed

+40
-5
lines changed

1 file changed

+40
-5
lines changed

src/patchelf.cc

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class ElfFile
5555

5656
bool changed;
5757

58+
bool isExecutable;
59+
5860
typedef string SectionName;
5961
typedef map<SectionName, string> ReplacedSections;
6062

@@ -245,6 +247,8 @@ static void checkPointer(void * p, unsigned int size)
245247
template<ElfFileParams>
246248
void ElfFile<ElfFileParamNames>::parse()
247249
{
250+
isExecutable = false;
251+
248252
/* Check the ELF header for basic validity. */
249253
if (fileSize < (off_t) sizeof(Elf_Ehdr)) error("missing ELF header");
250254

@@ -268,8 +272,10 @@ void ElfFile<ElfFileParamNames>::parse()
268272
error("program headers have wrong size");
269273

270274
/* Copy the program and section headers. */
271-
for (int i = 0; i < rdi(hdr->e_phnum); ++i)
275+
for (int i = 0; i < rdi(hdr->e_phnum); ++i) {
272276
phdrs.push_back(* ((Elf_Phdr *) (contents + rdi(hdr->e_phoff)) + i));
277+
if (rdi(phdrs[i].p_type) == PT_INTERP) isExecutable = true;
278+
}
273279

274280
for (int i = 0; i < rdi(hdr->e_shnum); ++i)
275281
shdrs.push_back(* ((Elf_Shdr *) (contents + rdi(hdr->e_shoff)) + i));
@@ -486,7 +492,7 @@ void ElfFile<ElfFileParamNames>::writeReplacedSections(Elf_Off & curOff,
486492
{
487493
string sectionName = i->first;
488494
Elf_Shdr & shdr = findSection(sectionName);
489-
debug("rewriting section `%s' from offset %d (size %d) to offset %d (size %d)\n",
495+
debug("rewriting section `%s' from offset 0x%x (size %d) to offset 0x%x (size %d)\n",
490496
sectionName.c_str(), rdi(shdr.sh_offset), rdi(shdr.sh_size), curOff, i->second.size());
491497

492498
memcpy(contents + curOff, (unsigned char *) i->second.c_str(),
@@ -552,11 +558,40 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
552558
debug("needed space is %d\n", neededSpace);
553559

554560

555-
off_t startOffset = roundUp(fileSize, pageSize);
561+
size_t startOffset = roundUp(fileSize, pageSize);
556562

557563
growFile(startOffset + neededSpace);
558564

559565

566+
/* Even though this file is of type ET_DYN, it could actually be
567+
an executable. For instance, Gold produces executables marked
568+
ET_DYN. In that case we can still hit the kernel bug that
569+
necessitated rewriteSectionsExecutable(). However, such
570+
executables also tend to start at virtual address 0, so
571+
rewriteSectionsExecutable() won't work because it doesn't have
572+
any virtual address space to grow downwards into. As a
573+
workaround, make sure that the virtual address of our new
574+
PT_LOAD segment relative to the first PT_LOAD segment is equal
575+
to its offset; otherwise we hit the kernel bug. This may
576+
require creating a hole in the executable. The bigger the size
577+
of the uninitialised data segment, the bigger the hole. */
578+
if (isExecutable) {
579+
if (startOffset >= startPage) {
580+
debug("shifting new PT_LOAD segment by %d bytes to work around a Linux kernel bug\n", startOffset - startPage);
581+
} else {
582+
size_t hole = startPage - startOffset;
583+
/* Print a warning, because the hole could be very big. */
584+
fprintf(stderr, "warning: working around a Linux kernel bug by creating a hole of %zu bytes in ‘%s’\n", hole, fileName.c_str());
585+
assert(hole % pageSize == 0);
586+
/* !!! We could create an actual hole in the file here,
587+
but it's probably not worth the effort. */
588+
growFile(fileSize + hole);
589+
startOffset += hole;
590+
}
591+
startPage = startOffset;
592+
}
593+
594+
560595
/* Add a segment that maps the replaced sections and program
561596
headers into memory. */
562597
phdrs.resize(rdi(hdr->e_phnum) + 1);
@@ -610,7 +645,7 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsExecutable()
610645
SHT_PROGBITS). These cannot be moved in virtual address space
611646
since that would invalidate absolute references to them. */
612647
assert(lastReplaced + 1 < shdrs.size()); /* !!! I'm lazy. */
613-
off_t startOffset = rdi(shdrs[lastReplaced + 1].sh_offset);
648+
size_t startOffset = rdi(shdrs[lastReplaced + 1].sh_offset);
614649
Elf_Addr startAddr = rdi(shdrs[lastReplaced + 1].sh_addr);
615650
string prevSection;
616651
for (unsigned int i = 1; i <= lastReplaced; ++i) {
@@ -651,7 +686,7 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsExecutable()
651686

652687
/* Compute the total space needed for the replaced sections, the
653688
ELF header, and the program headers. */
654-
off_t neededSpace = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);
689+
size_t neededSpace = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr);
655690
for (ReplacedSections::iterator i = replacedSections.begin();
656691
i != replacedSections.end(); ++i)
657692
neededSpace += roundUp(i->second.size(), sectionAlignment);

0 commit comments

Comments
 (0)