xref: /aosp_15_r20/bionic/linker/linker_phdr_16kib_compat.cpp (revision 8d67ca893c1523eb926b9080dbe4e2ffd2a27ba1)
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *  * Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  *  * Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in
12  *    the documentation and/or other materials provided with the
13  *    distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include "linker_phdr.h"
30 
31 #include <linux/prctl.h>
32 #include <sys/mman.h>
33 #include <sys/prctl.h>
34 #include <unistd.h>
35 
36 #include "linker_debug.h"
37 #include "linker_dlwarning.h"
38 #include "linker_globals.h"
39 
40 #include "platform/bionic/macros.h"
41 #include "platform/bionic/page.h"
42 
43 #include <string>
44 
45 static bool g_enable_16kb_app_compat;
46 
segment_contains_prefix(const ElfW (Phdr)* segment,const ElfW (Phdr)* prefix)47 static inline bool segment_contains_prefix(const ElfW(Phdr)* segment, const ElfW(Phdr)* prefix) {
48   return segment && prefix && segment->p_vaddr == prefix->p_vaddr;
49 }
50 
set_16kb_appcompat_mode(bool enable_app_compat)51 void set_16kb_appcompat_mode(bool enable_app_compat) {
52   g_enable_16kb_app_compat = enable_app_compat;
53 }
54 
get_16kb_appcompat_mode()55 bool get_16kb_appcompat_mode() {
56   return g_enable_16kb_app_compat;
57 }
58 
59 /*
60  * Returns true if the ELF contains at most 1 RELRO segment; and populates @relro_phdr
61  * with the relro phdr or nullptr if none.
62  *
63  * Returns false if more than 1 RELRO segments are found.
64  */
HasAtMostOneRelroSegment(const ElfW (Phdr)** relro_phdr)65 bool ElfReader::HasAtMostOneRelroSegment(const ElfW(Phdr)** relro_phdr) {
66   const ElfW(Phdr)* relro = nullptr;
67   for (size_t i = 0; i < phdr_num_; ++i) {
68     const ElfW(Phdr)* phdr = &phdr_table_[i];
69 
70     if (phdr->p_type != PT_GNU_RELRO) {
71       continue;
72     }
73 
74     if (relro == nullptr) {
75       relro = phdr;
76     } else {
77       return false;
78     }
79   }
80 
81   *relro_phdr = relro;
82 
83   return true;
84 }
85 
86 /*
87  * In 16KiB compatibility mode ELFs with the following segment layout
88  * can be loaded successfully:
89  *
90  *         ┌────────────┬─────────────────────────┬────────────┐
91  *         │            │                         │            │
92  *         │  (RO|RX)*  │   (RW - RELRO prefix)?  │    (RW)*   │
93  *         │            │                         │            │
94  *         └────────────┴─────────────────────────┴────────────┘
95  *
96  * In other words, compatible layouts have:
97  *         - zero or more RO or RX segments;
98  *         - followed by zero or one RELRO prefix;
99  *         - followed by zero or more RW segments (this can include the RW
100  *           suffix from the segment containing the RELRO prefix, if any)
101  *
102  * In 16KiB compat mode, after relocation, the ELF is layout in virtual
103  * memory is as shown below:
104  *         ┌──────────────────────────────────────┬────────────┐
105  *         │                                      │            │
106  *         │                (RX)?                 │    (RW)?   │
107  *         │                                      │            │
108  *         └──────────────────────────────────────┴────────────┘
109  *
110  * In compat mode:
111  *         - the RO and RX segments along with the RELRO prefix are protected
112  *           as RX;
113  *         - and the RW segments along with RW suffix from the relro segment,
114  *           if any; are RW protected.
115  *
116  * This allows for the single RX|RW permission boundary to be aligned with
117  * a 16KiB page boundary; since a single page cannot share multiple
118  * permissions.
119  *
120  * IsEligibleFor16KiBAppCompat() identifies compatible ELFs and populates @vaddr
121  * with the boundary between RX|RW portions.
122  *
123  * Returns true if the ELF can be loaded in compat mode, else false.
124  */
IsEligibleFor16KiBAppCompat(ElfW (Addr)* vaddr)125 bool ElfReader::IsEligibleFor16KiBAppCompat(ElfW(Addr)* vaddr) {
126   const ElfW(Phdr)* relro_phdr = nullptr;
127   if (!HasAtMostOneRelroSegment(&relro_phdr)) {
128     DL_WARN("\"%s\": Compat loading failed: Multiple RELRO segments found", name_.c_str());
129     return false;
130   }
131 
132   const ElfW(Phdr)* last_rw = nullptr;
133   const ElfW(Phdr)* first_rw = nullptr;
134 
135   for (size_t i = 0; i < phdr_num_; ++i) {
136     const ElfW(Phdr)* curr = &phdr_table_[i];
137     const ElfW(Phdr)* prev = (i > 0) ? &phdr_table_[i - 1] : nullptr;
138 
139     if (curr->p_type != PT_LOAD) {
140       continue;
141     }
142 
143     int prot = PFLAGS_TO_PROT(curr->p_flags);
144 
145     if ((prot & PROT_WRITE) && (prot & PROT_READ)) {
146       if (!first_rw) {
147         first_rw = curr;
148       }
149 
150       if (last_rw && last_rw != prev) {
151         DL_WARN("\"%s\": Compat loading failed: ELF contains multiple non-adjacent RW segments",
152                 name_.c_str());
153         return false;
154       }
155 
156       last_rw = curr;
157     }
158   }
159 
160   if (!relro_phdr) {
161     *vaddr = align_down(first_rw->p_vaddr, kCompatPageSize);
162     return true;
163   }
164 
165   // The RELRO segment is present, it must be the prefix of the first RW segment.
166   if (!segment_contains_prefix(first_rw, relro_phdr)) {
167     DL_WARN("\"%s\": Compat loading failed: RELRO is not in the first RW segment",
168             name_.c_str());
169     return false;
170   }
171 
172   uint64_t end;
173   if (__builtin_add_overflow(relro_phdr->p_vaddr, relro_phdr->p_memsz, &end)) {
174     DL_WARN("\"%s\": Compat loading failed: relro vaddr + memsz overflowed", name_.c_str());
175     return false;
176   }
177 
178   *vaddr = align_up(end, kCompatPageSize);
179   return true;
180 }
181 
182 /*
183  * Returns the offset/shift needed to align @vaddr to a page boundary.
184  */
perm_boundary_offset(const ElfW (Addr)addr)185 static inline ElfW(Addr) perm_boundary_offset(const ElfW(Addr) addr) {
186   ElfW(Addr) offset = page_offset(addr);
187 
188   return offset ? page_size() - offset : 0;
189 }
190 
Setup16KiBAppCompat()191 bool ElfReader::Setup16KiBAppCompat() {
192   if (!should_use_16kib_app_compat_) {
193     return true;
194   }
195 
196   ElfW(Addr) rx_rw_boundary;  // Permission bounadry for compat mode
197   if (!IsEligibleFor16KiBAppCompat(&rx_rw_boundary)) {
198     return false;
199   }
200 
201   // Adjust the load_bias to position the RX|RW boundary on a page boundary
202   load_bias_ += perm_boundary_offset(rx_rw_boundary);
203 
204   // RW region (.data, .bss ...)
205   ElfW(Addr) rw_start = load_bias_ + rx_rw_boundary;
206   ElfW(Addr) rw_size = load_size_ - (rw_start - reinterpret_cast<ElfW(Addr)>(load_start_));
207 
208   CHECK(rw_start % getpagesize() == 0);
209   CHECK(rw_size % getpagesize() == 0);
210 
211   // Compat RELRO (RX) region (.text, .data.relro, ...)
212   compat_relro_start_ = reinterpret_cast<ElfW(Addr)>(load_start_);
213   compat_relro_size_ = load_size_ - rw_size;
214 
215   // Label the ELF VMA, since compat mode uses anonymous mappings.
216   std::string compat_name = name_ + " (compat loaded)";
217   prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, load_start_, load_size_, compat_name.c_str());
218 
219   return true;
220 }
221 
CompatMapSegment(size_t seg_idx,size_t len)222 bool ElfReader::CompatMapSegment(size_t seg_idx, size_t len) {
223   const ElfW(Phdr)* phdr = &phdr_table_[seg_idx];
224 
225   // NOTE: The compat(legacy) page size (4096) must be used when aligning
226   // the 4KiB segments for loading (reading). The larger 16KiB page size
227   // will lead to overwriting adjacent segments since the ELF's segment(s)
228   // are not 16KiB aligned.
229 
230   void* start = reinterpret_cast<void*>(align_down(phdr->p_vaddr + load_bias_, kCompatPageSize));
231 
232   // The ELF could be being loaded directly from a zipped APK,
233   // the zip offset must be added to find the segment offset.
234   const ElfW(Addr) offset = file_offset_ + align_down(phdr->p_offset, kCompatPageSize);
235 
236   CHECK(should_use_16kib_app_compat_);
237 
238   // Since the 4KiB max-page-size ELF is not properly aligned, loading it by
239   // directly mmapping the ELF file is not feasible.
240   // Instead, read the ELF contents into the anonymous RW mapping.
241   if (TEMP_FAILURE_RETRY(pread64(fd_, start, len, offset)) == -1) {
242     DL_ERR("Compat loading: \"%s\" failed to read LOAD segment %zu: %m", name_.c_str(), seg_idx);
243     return false;
244   }
245 
246   return true;
247 }
248