1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
base / android / linker / linker_unittest.cc [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif
#include <link.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/utsname.h>
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/system/sys_info.h"
#include "testing/gtest/include/gtest/gtest.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "base/android/linker/linker_jni.h"
extern char __executable_start;
extern "C" {
// This function is exported by the dynamic linker but never declared in any
// official header for some architecture/version combinations.
int dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data),
void* data) __attribute__((weak_import));
} // extern "C"
namespace chromium_android_linker {
namespace {
// Implements the old method of finding library and RELRO ranges by providing a
// callback for use with dl_iterate_phdr(3). Data from the field has shown that
// this method makes library loading significantly slower than
// android_dlopen_ext(), it was replaced by the exuivalent one:
// NativeLibInfo::FindRelroAndLibraryRangesInElf().
class LibraryRangeFinder {
public:
explicit LibraryRangeFinder(uintptr_t address) : load_address_(address) {}
uintptr_t load_address() const { return load_address_; }
size_t load_size() const { return load_size_; }
uintptr_t relro_start() const { return relro_start_; }
size_t relro_size() const { return relro_size_; }
static int VisitLibraryPhdrs(dl_phdr_info* info,
[[maybe_unused]] size_t size,
void* data);
private:
uintptr_t load_address_;
size_t load_size_ = 0;
uintptr_t relro_start_ = 0;
size_t relro_size_ = 0;
};
// Callback for dl_iterate_phdr(). From program headers (phdr(s)) of a loaded
// library determines its load address, and in case it is equal to
// |load_address()|, extracts the RELRO and size information from
// corresponding phdr(s).
// static
int LibraryRangeFinder::VisitLibraryPhdrs(dl_phdr_info* info,
[[maybe_unused]] size_t size,
void* data) {
auto* finder = reinterpret_cast<LibraryRangeFinder*>(data);
ElfW(Addr) lookup_address = static_cast<ElfW(Addr)>(finder->load_address());
// Use max and min vaddr to compute the library's load size.
auto min_vaddr = std::numeric_limits<ElfW(Addr)>::max();
ElfW(Addr) max_vaddr = 0;
ElfW(Addr) min_relro_vaddr = ~0;
ElfW(Addr) max_relro_vaddr = 0;
const size_t kPageSize = sysconf(_SC_PAGESIZE);
bool is_matching = false;
for (int i = 0; i < info->dlpi_phnum; ++i) {
const ElfW(Phdr)* phdr = &info->dlpi_phdr[i];
switch (phdr->p_type) {
case PT_LOAD:
// See if this segment's load address matches the value passed to
// android_dlopen_ext as |extinfo.reserved_addr|.
//
// Here and below, the virtual address in memory is computed by
// address == info->dlpi_addr + program_header->p_vaddr
// that is, the p_vaddr fields is relative to the object base address.
// See dl_iterate_phdr(3) for details.
if (lookup_address == info->dlpi_addr + phdr->p_vaddr)
is_matching = true;
if (phdr->p_vaddr < min_vaddr)
min_vaddr = phdr->p_vaddr;
if (phdr->p_vaddr + phdr->p_memsz > max_vaddr)
max_vaddr = phdr->p_vaddr + phdr->p_memsz;
break;
case PT_GNU_RELRO:
min_relro_vaddr = PageStart(kPageSize, phdr->p_vaddr);
max_relro_vaddr = phdr->p_vaddr + phdr->p_memsz;
// As of 2020-11 in libmonochrome.so RELRO is covered by a LOAD segment.
// It is not clear whether this property is going to be guaranteed in
// the future. Include the RELRO segment as part of the 'load size'.
// This way a potential future change in layout of LOAD segments would
// not open address space for racy mmap(MAP_FIXED).
if (min_relro_vaddr < min_vaddr)
min_vaddr = min_relro_vaddr;
if (max_vaddr < max_relro_vaddr)
max_vaddr = max_relro_vaddr;
break;
default:
break;
}
}
// Fill out size and relro information if there was a match.
if (is_matching) {
finder->load_size_ =
PageEnd(kPageSize, max_vaddr) - PageStart(kPageSize, min_vaddr);
finder->relro_size_ = PageEnd(kPageSize, max_relro_vaddr) -
PageStart(kPageSize, min_relro_vaddr);
finder->relro_start_ =
info->dlpi_addr + PageStart(kPageSize, min_relro_vaddr);
return 1;
}
return 0;
}
} // namespace
// These tests get linked with base_unittests and leave JNI uninitialized. The
// tests must not execute any parts relying on initialization with JNI_Onload().
class LinkerTest : public testing::Test {
public:
LinkerTest() = default;
~LinkerTest() override = default;
};
// Checks that NativeLibInfo::CreateSharedRelroFd() creates a shared memory
// region that is 'sealed' as read-only.
TEST_F(LinkerTest, CreatedRegionIsSealed) {
if (!NativeLibInfo::SharedMemoryFunctionsSupportedForTesting()) {
// The Linker uses functions from libandroid.so that are not available
// on Android releases before O. Disable unittests for old releases.
return;
}
// Fill a synthetic RELRO region with 0xEE in private anonynous memory.
constexpr size_t kRelroSize = 1 << 21; // 2 MiB.
void* relro_address = mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(MAP_FAILED, relro_address);
NativeLibInfo lib_info = {0, 0};
lib_info.set_relro_info_for_testing(
reinterpret_cast<uintptr_t>(relro_address), kRelroSize);
memset(relro_address, 0xEE, kRelroSize);
// Create shared RELRO.
ASSERT_EQ(true, lib_info.CreateSharedRelroFdForTesting());
int relro_fd = lib_info.get_relro_fd_for_testing();
ASSERT_NE(-1, relro_fd);
base::ScopedFD scoped_fd(relro_fd);
// Check that a read-only mapping contains the data originally filled in.
void* ro_address =
mmap(nullptr, kRelroSize, PROT_READ, MAP_SHARED, relro_fd, 0);
ASSERT_NE(MAP_FAILED, ro_address);
EXPECT_EQ(0xEEEEEEEEU, *reinterpret_cast<uint32_t*>(ro_address));
int not_equal = memcmp(relro_address, ro_address, kRelroSize);
EXPECT_EQ(0, not_equal);
munmap(ro_address, kRelroSize);
// Check that attempts to mmap with PROT_WRITE fail.
EXPECT_EQ(MAP_FAILED, mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
MAP_SHARED, relro_fd, 0));
EXPECT_EQ(MAP_FAILED, mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE, relro_fd, 0));
EXPECT_EQ(MAP_FAILED,
mmap(nullptr, kRelroSize, PROT_WRITE, MAP_SHARED, relro_fd, 0));
EXPECT_EQ(MAP_FAILED,
mmap(nullptr, kRelroSize, PROT_WRITE, MAP_PRIVATE, relro_fd, 0));
}
TEST_F(LinkerTest, FindReservedMemoryRegion) {
size_t address, size;
// Find the existing reservation in the current process. The unittest runner
// is forked from the system zygote. The reservation should be found when
// running on recent Android releases, where it is made by the
// reserveAddressSpaceInZygote().
bool found_reservation = FindWebViewReservation(&address, &size);
if (found_reservation) {
// Check that the size is at least the minimum reserved by Android, as of
// 2021-04.
EXPECT_LE(130U * 1024 * 1024, size);
return;
}
// TODO(crbug.com/40774803): Check that only non-low-end Android Q+ devices
// reach this point.
// Create a properly named synthetic region with a size smaller than a real
// library would need, but still aligned well.
static const size_t kSize = 19U * 1024 * 1024;
void* synthetic_region_start =
mmap(nullptr, kSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(MAP_FAILED, synthetic_region_start);
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, synthetic_region_start, kSize,
"[anon:libwebview reservation]");
// Now the region must be found.
EXPECT_TRUE(FindWebViewReservation(&address, &size));
EXPECT_EQ(kSize, size);
EXPECT_EQ(reinterpret_cast<void*>(address), synthetic_region_start);
munmap(synthetic_region_start, kSize);
}
TEST_F(LinkerTest, FindLibraryRanges) {
static int var_inside = 3;
NativeLibInfo lib_info = {0, 0};
uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
lib_info.set_load_address(executable_start);
EXPECT_TRUE(lib_info.FindRelroAndLibraryRangesInElfForTesting());
EXPECT_EQ(executable_start, lib_info.load_address());
uintptr_t inside_library = reinterpret_cast<uintptr_t>(&var_inside);
EXPECT_LE(executable_start, inside_library);
EXPECT_LE(inside_library,
lib_info.load_address() + lib_info.get_load_size_for_testing());
EXPECT_LE(lib_info.load_address(), lib_info.get_relro_start_for_testing());
EXPECT_LE(lib_info.get_relro_start_for_testing(),
lib_info.load_address() + lib_info.get_load_size_for_testing());
}
TEST_F(LinkerTest, FindLibraryRangesWhenLoadAddressWasReset) {
NativeLibInfo other_lib_info = {0, 0};
uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
other_lib_info.set_load_address(executable_start);
other_lib_info.set_relro_fd_for_testing(123);
NativeLibInfo lib_info = {0, 0};
EXPECT_FALSE(lib_info.CompareRelroAndReplaceItBy(other_lib_info));
}
// Check that discovering RELRO segment address ranges and the DSO ranges agrees
// with the method based on dl_iterate_phdr(3). The check is performed on the
// test library, not on libmonochrome.
TEST_F(LinkerTest, LibraryRangesViaIteratePhdr) {
// Find the ranges using dl_iterate_phdr().
if (!dl_iterate_phdr) {
ASSERT_TRUE(false) << "dl_iterate_phdr() not found";
}
uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
LibraryRangeFinder finder(executable_start);
ASSERT_EQ(1, dl_iterate_phdr(&LibraryRangeFinder::VisitLibraryPhdrs,
reinterpret_cast<void*>(&finder)));
ASSERT_LE(finder.relro_start() + finder.relro_size(),
finder.load_address() + finder.load_size());
// Find the ranges by parsing ELF.
NativeLibInfo lib_info2 = {0, 0};
lib_info2.set_load_address(executable_start);
EXPECT_TRUE(lib_info2.FindRelroAndLibraryRangesInElfForTesting());
// Compare results.
EXPECT_EQ(finder.load_address(), lib_info2.load_address());
EXPECT_EQ(finder.load_size(), lib_info2.get_load_size_for_testing());
EXPECT_EQ(finder.relro_start(), lib_info2.get_relro_start_for_testing());
}
} // namespace chromium_android_linker