1 |
efrain |
1 |
// Copyright 2019 Google
|
|
|
2 |
//
|
|
|
3 |
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
4 |
// you may not use this file except in compliance with the License.
|
|
|
5 |
// You may obtain a copy of the License at
|
|
|
6 |
//
|
|
|
7 |
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
8 |
//
|
|
|
9 |
// Unless required by applicable law or agreed to in writing, software
|
|
|
10 |
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
11 |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
12 |
// See the License for the specific language governing permissions and
|
|
|
13 |
// limitations under the License.
|
|
|
14 |
|
|
|
15 |
#include <stdatomic.h>
|
|
|
16 |
|
|
|
17 |
#include "Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.h"
|
|
|
18 |
#include "Crashlytics/Crashlytics/Components/FIRCLSHost.h"
|
|
|
19 |
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
|
|
|
20 |
|
|
|
21 |
#include <errno.h>
|
|
|
22 |
#include <libkern/OSAtomic.h>
|
|
|
23 |
#include <mach/vm_map.h>
|
|
|
24 |
#include <mach/vm_param.h>
|
|
|
25 |
#include <stdio.h>
|
|
|
26 |
#include <stdlib.h>
|
|
|
27 |
#include <sys/mman.h>
|
|
|
28 |
|
|
|
29 |
void* FIRCLSAllocatorSafeAllocateFromRegion(FIRCLSAllocationRegion* region, size_t size);
|
|
|
30 |
|
|
|
31 |
FIRCLSAllocatorRef FIRCLSAllocatorCreate(size_t writableSpace, size_t readableSpace) {
|
|
|
32 |
FIRCLSAllocatorRef allocator;
|
|
|
33 |
FIRCLSAllocationRegion writableRegion;
|
|
|
34 |
FIRCLSAllocationRegion readableRegion;
|
|
|
35 |
size_t allocationSize;
|
|
|
36 |
vm_size_t pageSize;
|
|
|
37 |
void* buffer;
|
|
|
38 |
|
|
|
39 |
// | GUARD | WRITABLE_REGION | GUARD | READABLE_REGION | GUARD |
|
|
|
40 |
|
|
|
41 |
pageSize = FIRCLSHostGetPageSize();
|
|
|
42 |
|
|
|
43 |
readableSpace += sizeof(FIRCLSAllocator); // add the space for our allocator itself
|
|
|
44 |
|
|
|
45 |
// we can only protect at the page level, so we need all of our regions to be
|
|
|
46 |
// exact multples of pages. But, we don't need anything in the special-case of zero.
|
|
|
47 |
|
|
|
48 |
writableRegion.size = 0;
|
|
|
49 |
if (writableSpace > 0) {
|
|
|
50 |
writableRegion.size = ((writableSpace / pageSize) + 1) * pageSize;
|
|
|
51 |
}
|
|
|
52 |
|
|
|
53 |
readableRegion.size = 0;
|
|
|
54 |
if (readableSpace > 0) {
|
|
|
55 |
readableRegion.size = ((readableSpace / pageSize) + 1) * pageSize;
|
|
|
56 |
}
|
|
|
57 |
|
|
|
58 |
// Make one big, continous allocation, adding additional pages for our guards. Note
|
|
|
59 |
// that we cannot use malloc (or valloc) in this case, because we need to assert full
|
|
|
60 |
// ownership over these allocations. mmap is a much better choice. We also mark these
|
|
|
61 |
// pages as MAP_NOCACHE.
|
|
|
62 |
allocationSize = writableRegion.size + readableRegion.size + pageSize * 3;
|
|
|
63 |
buffer =
|
|
|
64 |
mmap(0, allocationSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_NOCACHE, -1, 0);
|
|
|
65 |
if (buffer == MAP_FAILED) {
|
|
|
66 |
FIRCLSSDKLogError("Mapping failed %s\n", strerror(errno));
|
|
|
67 |
return NULL;
|
|
|
68 |
}
|
|
|
69 |
|
|
|
70 |
// move our cursors into position
|
|
|
71 |
writableRegion.cursor = (void*)((uintptr_t)buffer + pageSize);
|
|
|
72 |
readableRegion.cursor = (void*)((uintptr_t)buffer + pageSize + writableRegion.size + pageSize);
|
|
|
73 |
writableRegion.start = writableRegion.cursor;
|
|
|
74 |
readableRegion.start = readableRegion.cursor;
|
|
|
75 |
|
|
|
76 |
FIRCLSSDKLogInfo("Mapping: %p %p %p, total: %zu K\n", buffer, writableRegion.start,
|
|
|
77 |
readableRegion.start, allocationSize / 1024);
|
|
|
78 |
|
|
|
79 |
// protect first guard page
|
|
|
80 |
if (mprotect(buffer, pageSize, PROT_NONE) != 0) {
|
|
|
81 |
FIRCLSSDKLogError("First guard protection failed %s\n", strerror(errno));
|
|
|
82 |
return NULL;
|
|
|
83 |
}
|
|
|
84 |
|
|
|
85 |
// middle guard
|
|
|
86 |
if (mprotect((void*)((uintptr_t)buffer + pageSize + writableRegion.size), pageSize, PROT_NONE) !=
|
|
|
87 |
0) {
|
|
|
88 |
FIRCLSSDKLogError("Middle guard protection failed %s\n", strerror(errno));
|
|
|
89 |
return NULL;
|
|
|
90 |
}
|
|
|
91 |
|
|
|
92 |
// end guard
|
|
|
93 |
if (mprotect((void*)((uintptr_t)buffer + pageSize + writableRegion.size + pageSize +
|
|
|
94 |
readableRegion.size),
|
|
|
95 |
pageSize, PROT_NONE) != 0) {
|
|
|
96 |
FIRCLSSDKLogError("Last guard protection failed %s\n", strerror(errno));
|
|
|
97 |
return NULL;
|
|
|
98 |
}
|
|
|
99 |
|
|
|
100 |
// now, perform our first "allocation", which is to place our allocator into the read-only region
|
|
|
101 |
allocator = FIRCLSAllocatorSafeAllocateFromRegion(&readableRegion, sizeof(FIRCLSAllocator));
|
|
|
102 |
|
|
|
103 |
// set up its data structure
|
|
|
104 |
allocator->buffer = buffer;
|
|
|
105 |
allocator->protectionEnabled = false;
|
|
|
106 |
allocator->readableRegion = readableRegion;
|
|
|
107 |
allocator->writeableRegion = writableRegion;
|
|
|
108 |
|
|
|
109 |
FIRCLSSDKLogDebug("Allocator successfully created %p", allocator);
|
|
|
110 |
|
|
|
111 |
return allocator;
|
|
|
112 |
}
|
|
|
113 |
|
|
|
114 |
void FIRCLSAllocatorDestroy(FIRCLSAllocatorRef allocator) {
|
|
|
115 |
if (allocator) {
|
|
|
116 |
}
|
|
|
117 |
}
|
|
|
118 |
|
|
|
119 |
bool FIRCLSAllocatorProtect(FIRCLSAllocatorRef allocator) {
|
|
|
120 |
void* address;
|
|
|
121 |
|
|
|
122 |
if (!FIRCLSIsValidPointer(allocator)) {
|
|
|
123 |
FIRCLSSDKLogError("Invalid allocator");
|
|
|
124 |
return false;
|
|
|
125 |
}
|
|
|
126 |
|
|
|
127 |
if (allocator->protectionEnabled) {
|
|
|
128 |
FIRCLSSDKLogWarn("Write protection already enabled");
|
|
|
129 |
return true;
|
|
|
130 |
}
|
|
|
131 |
|
|
|
132 |
// This has to be done first
|
|
|
133 |
allocator->protectionEnabled = true;
|
|
|
134 |
|
|
|
135 |
vm_size_t pageSize = FIRCLSHostGetPageSize();
|
|
|
136 |
|
|
|
137 |
// readable region
|
|
|
138 |
address =
|
|
|
139 |
(void*)((uintptr_t)allocator->buffer + pageSize + allocator->writeableRegion.size + pageSize);
|
|
|
140 |
|
|
|
141 |
return mprotect(address, allocator->readableRegion.size, PROT_READ) == 0;
|
|
|
142 |
}
|
|
|
143 |
|
|
|
144 |
bool FIRCLSAllocatorUnprotect(FIRCLSAllocatorRef allocator) {
|
|
|
145 |
size_t bufferSize;
|
|
|
146 |
|
|
|
147 |
if (!allocator) {
|
|
|
148 |
return false;
|
|
|
149 |
}
|
|
|
150 |
|
|
|
151 |
vm_size_t pageSize = FIRCLSHostGetPageSize();
|
|
|
152 |
|
|
|
153 |
bufferSize = (uintptr_t)allocator->buffer + pageSize + allocator->writeableRegion.size +
|
|
|
154 |
pageSize + allocator->readableRegion.size + pageSize;
|
|
|
155 |
|
|
|
156 |
allocator->protectionEnabled =
|
|
|
157 |
!(mprotect(allocator->buffer, bufferSize, PROT_READ | PROT_WRITE) == 0);
|
|
|
158 |
|
|
|
159 |
return allocator->protectionEnabled;
|
|
|
160 |
}
|
|
|
161 |
|
|
|
162 |
void* FIRCLSAllocatorSafeAllocateFromRegion(FIRCLSAllocationRegion* region, size_t size) {
|
|
|
163 |
void* newCursor;
|
|
|
164 |
void* originalCursor;
|
|
|
165 |
|
|
|
166 |
// Here's the idea
|
|
|
167 |
// - read the current cursor
|
|
|
168 |
// - compute what our new cursor should be
|
|
|
169 |
// - attempt a swap
|
|
|
170 |
// if the swap fails, some other thread has modified stuff, and we have to start again
|
|
|
171 |
// if the swap works, everything has been updated correctly and we are done
|
|
|
172 |
do {
|
|
|
173 |
originalCursor = region->cursor;
|
|
|
174 |
|
|
|
175 |
// this shouldn't happen unless we make a mistake with our size pre-computations
|
|
|
176 |
if ((uintptr_t)originalCursor - (uintptr_t)region->start + size > region->size) {
|
|
|
177 |
FIRCLSSDKLog("Unable to allocate sufficient memory, falling back to malloc\n");
|
|
|
178 |
void* ptr = malloc(size);
|
|
|
179 |
if (!ptr) {
|
|
|
180 |
FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocateFromRegion\n");
|
|
|
181 |
return NULL;
|
|
|
182 |
}
|
|
|
183 |
return ptr;
|
|
|
184 |
}
|
|
|
185 |
|
|
|
186 |
newCursor = (void*)((uintptr_t)originalCursor + size);
|
|
|
187 |
} while (!atomic_compare_exchange_strong(®ion->cursor, &originalCursor, newCursor));
|
|
|
188 |
|
|
|
189 |
return originalCursor;
|
|
|
190 |
}
|
|
|
191 |
|
|
|
192 |
void* FIRCLSAllocatorSafeAllocate(FIRCLSAllocatorRef allocator,
|
|
|
193 |
size_t size,
|
|
|
194 |
FIRCLSAllocationType type) {
|
|
|
195 |
FIRCLSAllocationRegion* region;
|
|
|
196 |
|
|
|
197 |
if (!allocator) {
|
|
|
198 |
// fall back to malloc in this case
|
|
|
199 |
FIRCLSSDKLog("Allocator invalid, falling back to malloc\n");
|
|
|
200 |
void* ptr = malloc(size);
|
|
|
201 |
if (!ptr) {
|
|
|
202 |
FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocate\n");
|
|
|
203 |
return NULL;
|
|
|
204 |
}
|
|
|
205 |
return ptr;
|
|
|
206 |
}
|
|
|
207 |
|
|
|
208 |
if (allocator->protectionEnabled) {
|
|
|
209 |
FIRCLSSDKLog("Allocator already protected, falling back to malloc\n");
|
|
|
210 |
void* ptr = malloc(size);
|
|
|
211 |
if (!ptr) {
|
|
|
212 |
FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocate\n");
|
|
|
213 |
return NULL;
|
|
|
214 |
}
|
|
|
215 |
return ptr;
|
|
|
216 |
}
|
|
|
217 |
|
|
|
218 |
switch (type) {
|
|
|
219 |
case CLS_READONLY:
|
|
|
220 |
region = &allocator->readableRegion;
|
|
|
221 |
break;
|
|
|
222 |
case CLS_READWRITE:
|
|
|
223 |
region = &allocator->writeableRegion;
|
|
|
224 |
break;
|
|
|
225 |
default:
|
|
|
226 |
return NULL;
|
|
|
227 |
}
|
|
|
228 |
|
|
|
229 |
return FIRCLSAllocatorSafeAllocateFromRegion(region, size);
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
void FIRCLSAllocatorFree(FIRCLSAllocatorRef allocator, void* ptr) {
|
|
|
233 |
if (!allocator) {
|
|
|
234 |
free(ptr);
|
|
|
235 |
}
|
|
|
236 |
|
|
|
237 |
// how do we do deallocations?
|
|
|
238 |
}
|