1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define TLOG_TAG "scudo_app"
18
19 #include <assert.h>
20 #include <lib/tipc/tipc.h>
21 #include <lib/tipc/tipc_srv.h>
22 #include <lk/err_ptr.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/mman.h>
26 #include <trusty/memref.h>
27 #include <trusty_log.h>
28 #include <uapi/err.h>
29 #include <uapi/mm.h>
30
31 #include <scudo_app.h>
32 #include <scudo_consts.h>
33
34 #define ARR_SIZE 10
35
36 /*
37 * Scudo supports dealloc type mismatch checking. That is, Scudo
38 * can be configured to report an error if a chunk is allocated
39 * using new but deallocated using free instead of delete, for
40 * example. By default, dealloc type mismatch is disabled, but we
41 * enable it here to check its functionality in
42 * SCUDO_DEALLOC_TYPE_MISMATCH and also to ensure default Scudo
43 * options can be overridden.
44 */
45 extern "C" __attribute__((visibility("default"))) const char*
__scudo_default_options()46 __scudo_default_options() {
47 return "dealloc_type_mismatch=true";
48 }
49
50 static int scudo_on_message(const struct tipc_port* port,
51 handle_t chan,
52 void* ctx);
53
54 static struct tipc_port_acl scudo_port_acl = {
55 .flags = IPC_PORT_ALLOW_TA_CONNECT,
56 .uuid_num = 0,
57 .uuids = NULL,
58 .extra_data = NULL,
59 };
60
61 static struct tipc_port scudo_port = {
62 .name = SCUDO_TEST_SRV_PORT,
63 .msg_max_size = sizeof(struct scudo_msg),
64 .msg_queue_len = 1,
65 .acl = &scudo_port_acl,
66 .priv = NULL,
67 };
68
69 /*
70 * To make sure the variable isn't optimized away.
71 */
touch(volatile void * a)72 static void touch(volatile void* a) {
73 *(reinterpret_cast<volatile char*>(a)) =
74 *(reinterpret_cast<volatile char*>(a));
75 }
76
77 /*
78 * Touch all bytes in a string (up to ARR_SIZE).
79 * String buffer should be at least ARR_SIZE long.
80 */
touch_string(char * arr)81 static void touch_string(char* arr) {
82 /* make sure we don't go out of the buffer */
83 arr[ARR_SIZE - 1] = '\0';
84
85 while (*arr != '\0') {
86 *(reinterpret_cast<volatile char*>(arr)) =
87 *(reinterpret_cast<volatile char*>(arr));
88 arr++;
89 }
90 }
91
92 /*
93 * In addition to touching arr, it is memset with fill_char
94 * and printed as a check that arr points to valid writable memory.
95 */
touch_and_print(char * arr,const char fill_char)96 static void touch_and_print(char* arr, const char fill_char) {
97 touch(arr);
98 memset(arr, fill_char, ARR_SIZE - 1);
99 arr[ARR_SIZE - 1] = '\0';
100 TLOG("arr = %s\n", arr);
101 }
102
retagged(void * taggedptr)103 static void* retagged(void* taggedptr) {
104 uint64_t tagged = reinterpret_cast<uint64_t>(taggedptr);
105 uint64_t tag = tagged & 0x0f00000000000000;
106 uint64_t untagged = tagged & 0x00ffffffffffffff;
107 uint64_t newtag = (tag + 0x0100000000000000) & 0x0f00000000000000;
108 ;
109 return reinterpret_cast<void*>(newtag | untagged);
110 }
111
recv_memref_msg(handle_t chan,size_t min_sz,void * buf,size_t buf_sz,int * memref)112 int recv_memref_msg(handle_t chan,
113 size_t min_sz,
114 void* buf,
115 size_t buf_sz,
116 int* memref) {
117 int rc;
118 ipc_msg_info_t msg_inf;
119
120 rc = get_msg(chan, &msg_inf);
121 if (rc)
122 return rc;
123
124 if (msg_inf.len < min_sz || msg_inf.len > buf_sz ||
125 msg_inf.num_handles > 1) {
126 /* unexpected msg size: buffer too small or too big */
127 rc = ERR_BAD_LEN;
128 } else {
129 struct iovec iov = {
130 .iov_base = buf,
131 .iov_len = buf_sz,
132 };
133 ipc_msg_t msg = {
134 .num_iov = 1,
135 .iov = &iov,
136 .num_handles = msg_inf.num_handles,
137 .handles = msg_inf.num_handles ? memref : NULL,
138 };
139 rc = read_msg(chan, msg_inf.id, 0, &msg);
140 }
141
142 put_msg(chan, msg_inf.id);
143 return rc;
144 }
145
scudo_on_message(const struct tipc_port * port,handle_t chan,void * ctx)146 static int scudo_on_message(const struct tipc_port* port,
147 handle_t chan,
148 void* ctx) {
149 struct scudo_msg msg;
150 int memref = -1;
151
152 int ret = recv_memref_msg(chan, sizeof(msg), &msg, sizeof(msg), &memref);
153 if (ret < 0 || ret != sizeof(msg)) {
154 TLOGE("Failed to receive message (%d)\n", ret);
155 return ret;
156 }
157
158 switch (msg.cmd) {
159 /*
160 * SCUDO_NOP test checks that the internal testing machinery
161 * is working properly even when no Scudo functions are called.
162 * Since some of the tests are expected to crash the server, we
163 * need to make sure the server isn't just always crashing.
164 */
165 case SCUDO_NOP: {
166 TLOGI("nop\n");
167 break;
168 }
169 /*
170 * SCUDO_ONE_MALLOC tests that a single call to malloc and free
171 * works as intended.
172 */
173 case SCUDO_ONE_MALLOC: {
174 TLOGI("one malloc\n");
175 char* arr = reinterpret_cast<char*>(malloc(ARR_SIZE));
176 touch_and_print(arr, 'a');
177 free(arr);
178 break;
179 }
180 /*
181 * Similar to SCUDO_ONE_MALLOC, SCUDO_ONE_CALLOC tests that a
182 * single call to calloc and free works as intended.
183 */
184 case SCUDO_ONE_CALLOC: {
185 TLOGI("one calloc\n");
186 char* arr = reinterpret_cast<char*>(calloc(ARR_SIZE, 1));
187 touch_and_print(arr, 'a');
188 free(arr);
189 break;
190 }
191 /* Tests that a single call to realloc works. */
192 case SCUDO_ONE_REALLOC: {
193 TLOGI("one realloc\n");
194 char* arr = reinterpret_cast<char*>(malloc(ARR_SIZE));
195 touch_and_print(arr, 'a');
196 arr = reinterpret_cast<char*>(realloc(arr, 2 * ARR_SIZE));
197 touch_and_print(arr + ARR_SIZE - 1, 'b');
198 TLOG("arr = %s\n", arr);
199 free(arr);
200 break;
201 }
202 /*
203 * SCUDO_MANY_MALLOC performs a series of allocations and
204 * deallocations to test (1) that deallocated chunks can be
205 * reused, and (2) that Scudo can service various different
206 * sizes of allocations requests. We know chunks are reused
207 * because this app has 2MB bytes of heap memory and ~10MB
208 * bytes are malloc-ed by SCUDO_MANY_MALLOC.
209 */
210 case SCUDO_MANY_MALLOC: {
211 TLOGI("many malloc\n");
212 // exercise a few of the smaller size classes in the primary allocator
213 for (int i = 0; i < 4000; ++i) {
214 char* arr = reinterpret_cast<char*>(malloc(ARR_SIZE + i));
215 touch(arr);
216 snprintf(arr, ARR_SIZE, "(%d)!", i);
217 touch_string(arr);
218 if ((i % 100) == 0) {
219 TLOG("arr = %s\n", arr);
220 }
221 free(arr);
222 }
223 // do some larger allocations to verify the secondary allocator
224 // releases memory
225 for (int i = 0; i < 20; ++i) {
226 TLOG("secondary %d\n", i);
227 char* arr = reinterpret_cast<char*>(malloc(128 * 1024));
228 touch(arr);
229 free(arr);
230 }
231 break;
232 }
233 /* Tests that a single allocation with new and delete works. */
234 case SCUDO_ONE_NEW: {
235 TLOGI("one new\n");
236 int* foo = new int(37);
237 touch(foo);
238 TLOG("*foo = %d\n", *foo);
239 delete foo;
240 break;
241 }
242 /* Tests that a single allocation with new[] and delete[] works. */
243 case SCUDO_ONE_NEW_ARR: {
244 TLOGI("one new arr\n");
245 char* arr = new char[ARR_SIZE];
246 touch_and_print(arr, 'a');
247 delete[] arr;
248 break;
249 }
250 /* Tests that Scudo can service allocation requests using both malloc and
251 * new. */
252 case SCUDO_MALLOC_AND_NEW: {
253 TLOGI("malloc and new\n");
254 char* arr1 = reinterpret_cast<char*>(malloc(ARR_SIZE));
255 touch_and_print(arr1, 'a');
256 char* arr2 = new char[ARR_SIZE];
257 touch_and_print(arr2, 'b');
258 free(arr1);
259 delete[] arr2;
260 break;
261 }
262 /*
263 * Scudo uses checksummed headers to protect against double-freeing,
264 * so this test which attempts to free a chunk twice should crash.
265 */
266 case SCUDO_DOUBLE_FREE: {
267 TLOGI("double free\n");
268 char* arr = reinterpret_cast<char*>(malloc(ARR_SIZE));
269 touch_and_print(arr, 'a');
270 free(arr);
271 free(arr);
272 break;
273 }
274 /*
275 * Scudo ensures that freed chunks cannot be realloc-ed, so this
276 * test which attempts to realloc a freed chunk should crash.
277 */
278 case SCUDO_REALLOC_AFTER_FREE: {
279 TLOGI("realloc after free\n");
280 char* arr = reinterpret_cast<char*>(malloc(ARR_SIZE));
281 touch_and_print(arr, 'a');
282 free(arr);
283 arr = reinterpret_cast<char*>(realloc(arr, 2 * ARR_SIZE));
284 /* touch arr so realloc is not optimized away */
285 touch(arr);
286 break;
287 }
288 /*
289 * When dealloc_type_mismatch is enabled, Scudo ensures that chunks
290 * are allocated and deallocated using corresponding functions. Since
291 * this test allocates a chunk with new and deallocates it with free,
292 * it should crash the server.
293 */
294 case SCUDO_DEALLOC_TYPE_MISMATCH: {
295 TLOGI("dealloc type mismatch\n");
296 char* arr = new char[ARR_SIZE];
297 touch_and_print(arr, 'a');
298 free(arr);
299 break;
300 }
301 /*
302 * Similar to SCUDO_DEALLOC_TYPE_MISMATCH, with dealloc_type_mismatch,
303 * Scudo should ensure that chunks from memalign() cannot be realloc()'d
304 * which could lose alignment.
305 */
306 case SCUDO_REALLOC_TYPE_MISMATCH: {
307 TLOGI("realloc type mismatch\n");
308 char* arr = reinterpret_cast<char*>(memalign(32, ARR_SIZE));
309 touch_and_print(arr, 'a');
310 arr = reinterpret_cast<char*>(realloc(arr, ARR_SIZE * 2));
311 break;
312 }
313
314 case SCUDO_ALLOC_LARGE: {
315 TLOGI("alloc 1.5MB\n");
316 char* arr = reinterpret_cast<char*>(malloc(1500000));
317 touch(arr);
318 free(arr);
319 break;
320 }
321
322 case SCUDO_TAGGED_MEMREF_SMALL:
323 case SCUDO_TAGGED_MEMREF_LARGE: {
324 size_t memrefsize = 4096;
325 if (msg.cmd == SCUDO_TAGGED_MEMREF_LARGE) {
326 memrefsize *= 32;
327 }
328 TLOGI("tagged memref (%d)\n", memref);
329 volatile char* mapped = (volatile char*)mmap(
330 0, memrefsize,
331 MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE | MMAP_FLAG_PROT_MTE,
332 0, memref, 0);
333 if (mapped != MAP_FAILED) {
334 TLOGI("Tagged memref should have failed\n");
335 msg.cmd = SCUDO_TEST_FAIL;
336 munmap((void*)mapped, memrefsize);
337 close(memref);
338 break;
339 }
340
341 mapped = (volatile char*)mmap(
342 0, memrefsize, MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE, 0,
343 memref, 0);
344 if (mapped == MAP_FAILED) {
345 TLOGI("Untagged mapping failed\n");
346 msg.cmd = SCUDO_TEST_FAIL;
347 close(memref);
348 break;
349 }
350 *mapped = 0x77;
351 munmap((void*)mapped, memrefsize);
352 close(memref);
353 break;
354 }
355
356 case SCUDO_UNTAGGED_MEMREF_SMALL:
357 case SCUDO_UNTAGGED_MEMREF_LARGE: {
358 size_t memrefsize = 4096;
359 if (msg.cmd == SCUDO_UNTAGGED_MEMREF_LARGE) {
360 memrefsize *= 32;
361 }
362 TLOGI("untagged memref (%d)\n", memref);
363 volatile char* mapped = (volatile char*)mmap(
364 0, memrefsize, MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE, 0,
365 memref, 0);
366
367 if (!mapped || *mapped != 0x33) {
368 TLOGI("no map or bad data in memref %p: %0x\n", mapped,
369 mapped ? *mapped : 0);
370 msg.cmd = SCUDO_TEST_FAIL;
371 close(memref);
372 break;
373 }
374 *mapped = 0x77;
375 munmap((void*)mapped, memrefsize);
376 close(memref);
377 break;
378 }
379
380 case SCUDO_MEMTAG_MISMATCHED_READ: {
381 void* mem = malloc(64);
382 char* arr = reinterpret_cast<char*>(mem);
383 *arr = 0x33;
384 volatile char* retagged_arr =
385 3 + reinterpret_cast<char*>(retagged(mem));
386 TLOGI("mismatched tag read %016lx %016lx\n", (uint64_t)arr,
387 (uint64_t)retagged_arr);
388 *arr = *retagged_arr;
389 TLOGI("should not be here\n");
390 free(mem);
391 break;
392 }
393
394 case SCUDO_MEMTAG_MISMATCHED_WRITE: {
395 void* mem = malloc(64);
396 char* arr = reinterpret_cast<char*>(mem);
397 *arr = 0x44;
398 volatile char* retagged_arr = reinterpret_cast<char*>(retagged(mem));
399 TLOGI("mismatched tag write %016lx %016lx\n", (uint64_t)arr,
400 (uint64_t)retagged_arr);
401 *retagged_arr = *arr;
402 TLOGI("should not be here\n");
403 free(mem);
404 break;
405 }
406
407 case SCUDO_MEMTAG_READ_AFTER_FREE: {
408 void* mem = malloc(64);
409 memset(mem, 64, 0xaa);
410 char* arr = reinterpret_cast<char*>(mem);
411 free(mem);
412 TLOGI("read after free %016lx\n", (uint64_t)arr);
413 touch(arr); // this reads before writing
414 TLOGI("should not be here\n");
415 break;
416 }
417
418 case SCUDO_MEMTAG_WRITE_AFTER_FREE: {
419 void* mem = malloc(64);
420 memset(mem, 64, 0xbb);
421 char* arr = reinterpret_cast<char*>(mem);
422 free(mem);
423 TLOGI("write after free %016lx\n", (uint64_t)arr);
424 *arr = 1;
425 TLOGI("should not be here\n");
426 break;
427 }
428
429 case SCUDO_ALLOC_BENCHMARK: {
430 TLOGI("alloc benchmark\n");
431 char* arr = reinterpret_cast<char*>(malloc(1500000));
432 touch(arr);
433 free(arr);
434 for (int i = 0; i < 1000; i++) {
435 uint num_allocs = rand() % 40 + 1;
436 char** arr2 = reinterpret_cast<char**>(
437 malloc(sizeof(char*) * num_allocs));
438 for (uint j = 0; j < num_allocs; j++) {
439 uint num_allocs_2 = rand() % 64 + 1;
440 arr2[j] = reinterpret_cast<char*>(malloc(num_allocs_2));
441 touch(arr2[j]);
442 }
443 for (uint j = 0; j < num_allocs; j++) {
444 free(arr2[j]);
445 }
446 free(arr2);
447 }
448 break;
449 }
450
451 default:
452 TLOGE("Bad command: %d\n", msg.cmd);
453 msg.cmd = SCUDO_BAD_CMD;
454 }
455 /*
456 * We echo the incoming command in the case where the app
457 * runs the test without crashing. This is effectively saying "did
458 * not crash when executing command X."
459 */
460 ret = tipc_send1(chan, &msg, sizeof(msg));
461 if (ret < 0 || ret != sizeof(msg)) {
462 TLOGE("Failed to send message (%d)\n", ret);
463 return ret < 0 ? ret : ERR_IO;
464 }
465
466 return 0;
467 }
468
469 static struct tipc_srv_ops scudo_ops = {
470 .on_message = scudo_on_message,
471 };
472
main(void)473 int main(void) {
474 struct tipc_hset* hset = tipc_hset_create();
475 if (IS_ERR(hset)) {
476 TLOGE("Failed to create handle set (%d)\n", PTR_ERR(hset));
477 return PTR_ERR(hset);
478 }
479
480 int rc = tipc_add_service(hset, &scudo_port, 1, 1, &scudo_ops);
481 if (rc < 0) {
482 TLOGE("Failed to add service (%d)\n", rc);
483 return rc;
484 }
485
486 /* if app exits, kernel will log that */
487 return tipc_run_event_loop(hset);
488 }
489