1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "crazy_linker_elf_relro.h"
6
7 #include <errno.h>
8 #include <limits.h>
9 #include <stdlib.h>
10
11 #include "crazy_linker_elf_relocations.h"
12 #include "crazy_linker_elf_view.h"
13 #include "crazy_linker_memory_mapping.h"
14 #include "crazy_linker_util.h"
15
16 namespace crazy {
17
18 namespace {
19
PageEquals(const char * p1,const char * p2)20 inline bool PageEquals(const char* p1, const char* p2) {
21 return ::memcmp(p1, p2, PAGE_SIZE) == 0;
22 }
23
24 // Swap pages between |addr| and |addr + size| with the bytes
25 // from the ashmem region identified by |fd|, starting from
26 // a given |offset|. On failure return false and set |error| message.
SwapPagesFromFd(void * addr,size_t size,int fd,size_t offset,Error * error)27 bool SwapPagesFromFd(void* addr,
28 size_t size,
29 int fd,
30 size_t offset,
31 Error* error) {
32 // Unmap current pages.
33 if (::munmap(addr, size) < 0) {
34 error->Format("%s: Could not unmap %p-%p: %s",
35 __FUNCTION__,
36 addr,
37 (char*)addr + size,
38 strerror(errno));
39 return false;
40 }
41
42 // Remap the fd pages at the same location now.
43 void* new_map = ::mmap(addr,
44 size,
45 PROT_READ,
46 MAP_FIXED | MAP_SHARED,
47 fd,
48 static_cast<off_t>(offset));
49 if (new_map == MAP_FAILED) {
50 char* p = reinterpret_cast<char*>(addr);
51 error->Format("%s: Could not map %p-%p: %s",
52 __FUNCTION__,
53 p,
54 p + size,
55 strerror(errno));
56 return false;
57 }
58
59 // TODO(digit): Is this necessary?
60 #ifdef __arm__
61 __clear_cache(addr, (char*)addr + size);
62 #endif
63
64 // Done.
65 return true;
66 }
67
68 } // namespace
69
Allocate(size_t relro_size,const char * library_name,Error * error)70 bool SharedRelro::Allocate(size_t relro_size,
71 const char* library_name,
72 Error* error) {
73 // Allocate a new ashmem region.
74 String name("RELRO:");
75 name += library_name;
76 if (!ashmem_.Allocate(relro_size, name.c_str())) {
77 error->Format("Could not allocate RELRO ashmem region for %s: %s",
78 library_name,
79 strerror(errno));
80 return false;
81 }
82
83 start_ = 0;
84 size_ = relro_size;
85 return true;
86 }
87
CopyFrom(size_t relro_start,size_t relro_size,Error * error)88 bool SharedRelro::CopyFrom(size_t relro_start,
89 size_t relro_size,
90 Error* error) {
91 // Map it in the process.
92 ScopedMemoryMapping map;
93 if (!map.Allocate(NULL, relro_size, MemoryMapping::CAN_WRITE, ashmem_.fd())) {
94 error->Format("Could not allocate RELRO mapping: %s", strerror(errno));
95 return false;
96 }
97
98 // Copy process' RELRO into it.
99 ::memcpy(map.Get(), reinterpret_cast<void*>(relro_start), relro_size);
100
101 // Unmap it.
102 map.Deallocate();
103
104 // Everything's good.
105 start_ = relro_start;
106 size_ = relro_size;
107 return true;
108 }
109
CopyFromRelocated(const ElfView * view,size_t load_address,size_t relro_start,size_t relro_size,Error * error)110 bool SharedRelro::CopyFromRelocated(const ElfView* view,
111 size_t load_address,
112 size_t relro_start,
113 size_t relro_size,
114 Error* error) {
115 // Offset of RELRO section in current library.
116 size_t relro_offset = relro_start - view->load_address();
117
118 ElfRelocations relocations;
119 if (!relocations.Init(view, error))
120 return false;
121
122 // Map the region in memory (any address).
123 ScopedMemoryMapping map;
124 if (!map.Allocate(
125 NULL, relro_size, MemoryMapping::CAN_READ_WRITE, ashmem_.fd())) {
126 error->Format("Could not allocate RELRO mapping for: %s", strerror(errno));
127 return false;
128 }
129
130 // Copy and relocate.
131 relocations.CopyAndRelocate(relro_start,
132 reinterpret_cast<size_t>(map.Get()),
133 load_address + relro_offset,
134 relro_size);
135 // Unmap it.
136 map.Deallocate();
137 start_ = load_address + relro_offset;
138 size_ = relro_size;
139 return true;
140 }
141
ForceReadOnly(Error * error)142 bool SharedRelro::ForceReadOnly(Error* error) {
143 // Ensure the ashmem region content isn't writable anymore.
144 if (!ashmem_.SetProtectionFlags(PROT_READ)) {
145 error->Format("Could not make RELRO ashmem region read-only: %s",
146 strerror(errno));
147 return false;
148 }
149 return true;
150 }
151
InitFrom(size_t relro_start,size_t relro_size,int ashmem_fd,Error * error)152 bool SharedRelro::InitFrom(size_t relro_start,
153 size_t relro_size,
154 int ashmem_fd,
155 Error* error) {
156 // Create temporary mapping of the ashmem region.
157 ScopedMemoryMapping fd_map;
158
159 LOG("%s: Entering addr=%p size=%p fd=%d\n",
160 __FUNCTION__,
161 (void*)relro_start,
162 (void*)relro_size,
163 ashmem_fd);
164
165 // Sanity check: Ashmem file descriptor must be read-only.
166 if (!AshmemRegion::CheckFileDescriptorIsReadOnly(ashmem_fd)) {
167 error->Format("Ashmem file descriptor is not read-only: %s\n",
168 strerror(errno));
169 return false;
170 }
171
172 if (!fd_map.Allocate(NULL, relro_size, MemoryMapping::CAN_READ, ashmem_fd)) {
173 error->Format("Cannot map RELRO ashmem region as read-only: %s\n",
174 strerror(errno));
175 return false;
176 }
177
178 LOG("%s: mapping allocated at %p\n", __FUNCTION__, fd_map.Get());
179
180 char* cur_page = reinterpret_cast<char*>(relro_start);
181 char* fd_page = static_cast<char*>(fd_map.Get());
182 size_t p = 0;
183 size_t size = relro_size;
184 size_t similar_size = 0;
185
186 do {
187 // Skip over dissimilar pages.
188 while (p < size && !PageEquals(cur_page + p, fd_page + p)) {
189 p += PAGE_SIZE;
190 }
191
192 // Count similar pages.
193 size_t p2 = p;
194 while (p2 < size && PageEquals(cur_page + p2, fd_page + p2)) {
195 p2 += PAGE_SIZE;
196 }
197
198 if (p2 > p) {
199 // Swap pages between |pos| and |pos2|.
200 LOG("%s: Swap pages at %p-%p\n",
201 __FUNCTION__,
202 cur_page + p,
203 cur_page + p2);
204 if (!SwapPagesFromFd(cur_page + p, p2 - p, ashmem_fd, p, error))
205 return false;
206
207 similar_size += (p2 - p);
208 }
209
210 p = p2;
211 } while (p < size);
212
213 LOG("%s: Swapped %d pages over %d (%d %%, %d KB not shared)\n",
214 __FUNCTION__,
215 similar_size / PAGE_SIZE,
216 size / PAGE_SIZE,
217 similar_size * 100 / size,
218 (size - similar_size) / 4096);
219
220 if (similar_size == 0)
221 return false;
222
223 start_ = relro_start;
224 size_ = relro_size;
225 return true;
226 }
227
228 } // namespace crazy
229