crazy_linker_elf_relro.cpp revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
1// Copyright 2014 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
16namespace crazy {
17
18namespace {
19
20inline 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.
27bool 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
70bool 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
88bool 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
110bool 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
142bool 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
152bool 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