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