1*4882a593SmuzhiyunFrom 79c093226e609b99fa889f6e37480b92b399610d Mon Sep 17 00:00:00 2001
2*4882a593SmuzhiyunFrom: Richard Purdie <richard.purdie@linuxfoundation.org>
3*4882a593SmuzhiyunDate: Tue, 7 Mar 2017 21:08:34 +0000
4*4882a593SmuzhiyunSubject: [PATCH] Avoid inflating file sizes needlessly and allow binaries to
5*4882a593Smuzhiyun be stripped
6*4882a593Smuzhiyun
7*4882a593SmuzhiyunThe current approach to changing sections in ET_DYN executables is to move
8*4882a593Smuzhiyunthe INTERP section to the end of the file. +This means changing PT_PHDR to
9*4882a593Smuzhiyunadd an extra PT_LOAD section so that the new section is mmaped into memory
10*4882a593Smuzhiyunby the elf loader in the kernel. In order to extend PHDR, this means moving
11*4882a593Smuzhiyunit to the end of the file.
12*4882a593Smuzhiyun
13*4882a593SmuzhiyunIts documented in BUGS there is a kernel 'bug' which means that if you have holes
14*4882a593Smuzhiyunin memory between the base load address and the PT_LOAD segment that contains PHDR,
15*4882a593Smuzhiyunit will pass an incorrect PHDR address to ld.so and fail to load the binary, segfaulting.
16*4882a593Smuzhiyun
17*4882a593SmuzhiyunTo avoid this, the code currently inserts space into the binary to ensure that when
18*4882a593Smuzhiyunloaded into memory there are no holes between the PT_LOAD sections. This inflates the
19*4882a593Smuzhiyunbinaries by many MBs in some cases. Whilst we could make them sparse, there is a second
20*4882a593Smuzhiyunissue which is that strip can fail to process these binaries:
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun$ strip fixincl
23*4882a593SmuzhiyunNot enough room for program headers, try linking with -N
24*4882a593Smuzhiyun[.note.ABI-tag]: Bad value
25*4882a593Smuzhiyun
26*4882a593SmuzhiyunThis turns out to be due to libbfd not liking the relocated PHDR section either
27*4882a593Smuzhiyun(https://github.com/NixOS/patchelf/issues/10).
28*4882a593Smuzhiyun
29*4882a593SmuzhiyunInstead this patch implements a different approach, leaving PHDR where it is but extending
30*4882a593Smuzhiyunit in place to allow addition of a new PT_LOAD section. This overwrites sections in the
31*4882a593Smuzhiyunbinary but those get moved to the end of the file in the new PT_LOAD section.
32*4882a593Smuzhiyun
33*4882a593SmuzhiyunThis is based on patches linked from the above github issue, however whilst the idea
34*4882a593Smuzhiyunwas good, the implementation wasn't correct and they've been rewritten here.
35*4882a593Smuzhiyun
36*4882a593SmuzhiyunSigned-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
37*4882a593Smuzhiyun
38*4882a593SmuzhiyunFetch from: https://github.com/NixOS/patchelf/commit/c4deb5e9e1ce9c98a48e0d5bb37d87739b8cfee4
39*4882a593Smuzhiyun
40*4882a593SmuzhiyunBackported to v0.9
41*4882a593Smuzhiyun
42*4882a593SmuzhiyunSigned-off-by: Conrad Ratschan <conrad.ratschan@rockwellcollins.com>
43*4882a593Smuzhiyun---
44*4882a593Smuzhiyun src/patchelf.cc | 71 ++++++++++++++++++++++++++++---------------------
45*4882a593Smuzhiyun 1 file changed, 40 insertions(+), 31 deletions(-)
46*4882a593Smuzhiyun
47*4882a593Smuzhiyundiff --git a/src/patchelf.cc b/src/patchelf.cc
48*4882a593Smuzhiyunindex 1d58061..c2147af 100644
49*4882a593Smuzhiyun--- a/src/patchelf.cc
50*4882a593Smuzhiyun+++ b/src/patchelf.cc
51*4882a593Smuzhiyun@@ -209,6 +209,8 @@ private:
52*4882a593Smuzhiyun     string & replaceSection(const SectionName & sectionName,
53*4882a593Smuzhiyun         unsigned int size);
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun+    bool haveReplacedSection(const SectionName & sectionName);
56*4882a593Smuzhiyun+
57*4882a593Smuzhiyun     void writeReplacedSections(Elf_Off & curOff,
58*4882a593Smuzhiyun         Elf_Addr startAddr, Elf_Off startOffset);
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun@@ -632,6 +634,15 @@ void ElfFile<ElfFileParamNames>::writeReplacedSections(Elf_Off & curOff,
61*4882a593Smuzhiyun     replacedSections.clear();
62*4882a593Smuzhiyun }
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun+template<ElfFileParams>
65*4882a593Smuzhiyun+bool ElfFile<ElfFileParamNames>::haveReplacedSection(const SectionName & sectionName)
66*4882a593Smuzhiyun+{
67*4882a593Smuzhiyun+    ReplacedSections::iterator i = replacedSections.find(sectionName);
68*4882a593Smuzhiyun+
69*4882a593Smuzhiyun+    if (i != replacedSections.end())
70*4882a593Smuzhiyun+        return true;
71*4882a593Smuzhiyun+    return false;
72*4882a593Smuzhiyun+}
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun template<ElfFileParams>
75*4882a593Smuzhiyun void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
76*4882a593Smuzhiyun@@ -648,52 +659,53 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun     debug("last page is 0x%llx\n", (unsigned long long) startPage);
79*4882a593Smuzhiyun
80*4882a593Smuzhiyun+    /* Because we're adding a new section header, we're necessarily increasing
81*4882a593Smuzhiyun+       the size of the program header table.  This can cause the first section
82*4882a593Smuzhiyun+       to overlap the program header table in memory; we need to shift the first
83*4882a593Smuzhiyun+       few segments to someplace else. */
84*4882a593Smuzhiyun+    /* Some sections may already be replaced so account for that */
85*4882a593Smuzhiyun+    unsigned int i = 1;
86*4882a593Smuzhiyun+    Elf_Addr pht_size = sizeof(Elf_Ehdr) + (phdrs.size() + 1)*sizeof(Elf_Phdr);
87*4882a593Smuzhiyun+    while( shdrs[i].sh_addr <= pht_size && i < rdi(hdr->e_shnum) ) {
88*4882a593Smuzhiyun+        if (not haveReplacedSection(getSectionName(shdrs[i])))
89*4882a593Smuzhiyun+            replaceSection(getSectionName(shdrs[i]), shdrs[i].sh_size);
90*4882a593Smuzhiyun+        i++;
91*4882a593Smuzhiyun+    }
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun-    /* Compute the total space needed for the replaced sections and
94*4882a593Smuzhiyun-       the program headers. */
95*4882a593Smuzhiyun-    off_t neededSpace = (phdrs.size() + 1) * sizeof(Elf_Phdr);
96*4882a593Smuzhiyun+    /* Compute the total space needed for the replaced sections */
97*4882a593Smuzhiyun+    off_t neededSpace = 0;
98*4882a593Smuzhiyun     for (ReplacedSections::iterator i = replacedSections.begin();
99*4882a593Smuzhiyun          i != replacedSections.end(); ++i)
100*4882a593Smuzhiyun         neededSpace += roundUp(i->second.size(), sectionAlignment);
101*4882a593Smuzhiyun     debug("needed space is %d\n", neededSpace);
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun-
104*4882a593Smuzhiyun     size_t startOffset = roundUp(fileSize, getPageSize());
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun     growFile(startOffset + neededSpace);
107*4882a593Smuzhiyun
108*4882a593Smuzhiyun-
109*4882a593Smuzhiyun     /* Even though this file is of type ET_DYN, it could actually be
110*4882a593Smuzhiyun        an executable.  For instance, Gold produces executables marked
111*4882a593Smuzhiyun-       ET_DYN.  In that case we can still hit the kernel bug that
112*4882a593Smuzhiyun-       necessitated rewriteSectionsExecutable().  However, such
113*4882a593Smuzhiyun-       executables also tend to start at virtual address 0, so
114*4882a593Smuzhiyun+       ET_DYN as does LD when linking with pie. If we move PT_PHDR, it
115*4882a593Smuzhiyun+       has to stay in the first PT_LOAD segment or any subsequent ones
116*4882a593Smuzhiyun+       if they're continuous in memory due to linux kernel constraints
117*4882a593Smuzhiyun+       (see BUGS). Since the end of the file would be after bss, we can't
118*4882a593Smuzhiyun+       move PHDR there, we therefore choose to leave PT_PHDR where it is but
119*4882a593Smuzhiyun+       move enough following sections such that we can add the extra PT_LOAD
120*4882a593Smuzhiyun+       section to it. This PT_LOAD segment ensures the sections at the end of
121*4882a593Smuzhiyun+       the file are mapped into memory for ld.so to process.
122*4882a593Smuzhiyun+       We can't use the approach in rewriteSectionsExecutable()
123*4882a593Smuzhiyun+       since DYN executables tend to start at virtual address 0, so
124*4882a593Smuzhiyun        rewriteSectionsExecutable() won't work because it doesn't have
125*4882a593Smuzhiyun-       any virtual address space to grow downwards into.  As a
126*4882a593Smuzhiyun-       workaround, make sure that the virtual address of our new
127*4882a593Smuzhiyun-       PT_LOAD segment relative to the first PT_LOAD segment is equal
128*4882a593Smuzhiyun-       to its offset; otherwise we hit the kernel bug.  This may
129*4882a593Smuzhiyun-       require creating a hole in the executable.  The bigger the size
130*4882a593Smuzhiyun-       of the uninitialised data segment, the bigger the hole. */
131*4882a593Smuzhiyun+       any virtual address space to grow downwards into. */
132*4882a593Smuzhiyun     if (isExecutable) {
133*4882a593Smuzhiyun         if (startOffset >= startPage) {
134*4882a593Smuzhiyun             debug("shifting new PT_LOAD segment by %d bytes to work around a Linux kernel bug\n", startOffset - startPage);
135*4882a593Smuzhiyun-        } else {
136*4882a593Smuzhiyun-            size_t hole = startPage - startOffset;
137*4882a593Smuzhiyun-            /* Print a warning, because the hole could be very big. */
138*4882a593Smuzhiyun-            fprintf(stderr, "warning: working around a Linux kernel bug by creating a hole of %zu bytes in ‘%s’\n", hole, fileName.c_str());
139*4882a593Smuzhiyun-            assert(hole % getPageSize() == 0);
140*4882a593Smuzhiyun-            /* !!! We could create an actual hole in the file here,
141*4882a593Smuzhiyun-               but it's probably not worth the effort. */
142*4882a593Smuzhiyun-            growFile(fileSize + hole);
143*4882a593Smuzhiyun-            startOffset += hole;
144*4882a593Smuzhiyun         }
145*4882a593Smuzhiyun         startPage = startOffset;
146*4882a593Smuzhiyun     }
147*4882a593Smuzhiyun
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun-    /* Add a segment that maps the replaced sections and program
150*4882a593Smuzhiyun-       headers into memory. */
151*4882a593Smuzhiyun+    /* Add a segment that maps the replaced sections into memory. */
152*4882a593Smuzhiyun     phdrs.resize(rdi(hdr->e_phnum) + 1);
153*4882a593Smuzhiyun     wri(hdr->e_phnum, rdi(hdr->e_phnum) + 1);
154*4882a593Smuzhiyun     Elf_Phdr & phdr = phdrs[rdi(hdr->e_phnum) - 1];
155*4882a593Smuzhiyun@@ -706,15 +718,12 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun
158*4882a593Smuzhiyun     /* Write out the replaced sections. */
159*4882a593Smuzhiyun-    Elf_Off curOff = startOffset + phdrs.size() * sizeof(Elf_Phdr);
160*4882a593Smuzhiyun+    Elf_Off curOff = startOffset;
161*4882a593Smuzhiyun     writeReplacedSections(curOff, startPage, startOffset);
162*4882a593Smuzhiyun     assert(curOff == startOffset + neededSpace);
163*4882a593Smuzhiyun
164*4882a593Smuzhiyun-
165*4882a593Smuzhiyun-    /* Move the program header to the start of the new area. */
166*4882a593Smuzhiyun-    wri(hdr->e_phoff, startOffset);
167*4882a593Smuzhiyun-
168*4882a593Smuzhiyun-    rewriteHeaders(startPage);
169*4882a593Smuzhiyun+    /* Write out the updated program and section headers */
170*4882a593Smuzhiyun+    rewriteHeaders(hdr->e_phoff);
171*4882a593Smuzhiyun }
172*4882a593Smuzhiyun
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun--
175*4882a593Smuzhiyun2.17.1
176*4882a593Smuzhiyun
177