1 /**
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
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
16 #ifdef USE_GWP_ASAN
17
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include <string.h>
21 #include <sys/random.h>
22
23 #include "gwp_asan.h"
24
25 #include "musl_log.h"
26 #include "musl_malloc.h"
27 #include "pthread.h"
28 #include "pthread_impl.h"
29
30 #ifdef OHOS_ENABLE_PARAMETER
31 #include "sys_param.h"
32 #endif
33
34 #define MAX_SIMULTANEOUS_ALLOCATIONS 32
35 #define SAMPLE_RATE 2500
36 #define GWP_ASAN_NAME_LEN 256
37 #define GWP_ASAN_PREDICT_TRUE(exp) __builtin_expect((exp) != 0, 1)
38 #define GWP_ASAN_PREDICT_FALSE(exp) __builtin_expect((exp) != 0, 0)
39 #define GWP_ASAN_LOGD(...) // change it to MUSL_LOGD to get gwp_asan debug log.
40 #define GWP_ASAN_NO_ADDRESS __attribute__((no_sanitize("address", "hwaddress")))
41
42 static bool gwp_asan_initialized = false;
43 static uint8_t process_sample_rate = 128;
44 static uint8_t force_sample_alloctor = 0;
45 static uint8_t previous_random_value = 0;
46
47 typedef struct {
48 const char *dli_fname;
49 void *dli_fbase;
50 const char *dli_sname;
51 void *dli_saddr;
52 } Dl_info;
53
54 // C interfaces of gwp_asan provided by LLVM side.
55 extern void init_gwp_asan(void *init_options);
56 extern void* gwp_asan_malloc(size_t bytes);
57 extern void gwp_asan_free(void *mem);
58 extern bool gwp_asan_should_sample();
59 extern bool gwp_asan_pointer_is_mine(void *mem);
60 extern bool gwp_asan_has_free_mem();
61 extern size_t gwp_asan_get_size(void *mem);
62 extern void gwp_asan_disable();
63 extern void gwp_asan_enable();
64 extern void gwp_asan_iterate(void *base, size_t size,
65 void (*callback)(void* base, size_t size, void *arg), void *arg);
66
67
68 extern int dladdr(const void *addr, Dl_info *info);
69
get_process_short_name(char * buf,size_t length)70 static char *get_process_short_name(char *buf, size_t length)
71 {
72 char *app = NULL;
73 int fd = open("/proc/self/cmdline", O_RDONLY);
74 if (fd != -1) {
75 ssize_t ret = read(fd, buf, length - 1);
76 if (ret != -1) {
77 buf[ret] = 0;
78 app = strrchr(buf, '/');
79 if (app) {
80 app++;
81 } else {
82 app = buf;
83 }
84 char *app_end = strchr(app, ':');
85 if (app_end) {
86 *app_end = 0;
87 }
88 }
89 close(fd);
90 }
91 return app;
92 }
93
is_gwp_asan_disable()94 bool is_gwp_asan_disable()
95 {
96 char para_name[GWP_ASAN_NAME_LEN] = "gwp_asan.disable.all";
97 static CachedHandle para_handler = NULL;
98 if (para_handler == NULL) {
99 para_handler = CachedParameterCreate(para_name, "false");
100 }
101 char *para_value = CachedParameterGet(para_handler);
102 if (para_value != NULL && strcmp(para_value, "true") == 0) {
103 return true;
104 }
105 return false;
106 }
107
force_sample_process_by_env()108 bool force_sample_process_by_env()
109 {
110 char buf[GWP_ASAN_NAME_LEN];
111 char *path = get_process_short_name(buf, GWP_ASAN_NAME_LEN);
112 if (!path) {
113 return false;
114 }
115 char para_name[GWP_ASAN_NAME_LEN] = "gwp_asan.enable.app.";
116 strcat(para_name, path);
117 static CachedHandle app_enable_handle = NULL;
118 if (app_enable_handle == NULL) {
119 app_enable_handle = CachedParameterCreate(para_name, "false");
120 }
121 char *param_value = CachedParameterGet(app_enable_handle);
122 if (param_value != NULL) {
123 if (strcmp(param_value, "true") == 0) {
124 return true;
125 }
126 }
127 return false;
128 }
129
force_sample_alloctor_by_env()130 bool force_sample_alloctor_by_env()
131 {
132 char para_name[GWP_ASAN_NAME_LEN] = "gwp_asan.sample.all";
133 static CachedHandle para_handler = NULL;
134 if (para_handler == NULL) {
135 para_handler = CachedParameterCreate(para_name, "false");
136 }
137 char *para_value = CachedParameterGet(para_handler);
138 if (para_value != NULL && strcmp(para_value, "true") == 0) {
139 force_sample_alloctor = 1;
140 return true;
141 }
142 return false;
143 }
144
should_sample_process()145 bool should_sample_process()
146 {
147 #ifdef OHOS_ENABLE_PARAMETER
148 if (force_sample_process_by_env()) {
149 return true;
150 }
151 #endif
152
153 uint8_t random_value;
154 // If getting a random number using a non-blocking fails, the random value is incremented.
155 if (getrandom(&random_value, sizeof(random_value), GRND_RANDOM | GRND_NONBLOCK) == -1) {
156 random_value = previous_random_value + 1;
157 previous_random_value = random_value;
158 }
159
160 return (random_value % process_sample_rate) == 0 ? true : false;
161 }
162
163 #define ASAN_LOG_LIB "libasan_logger.z.so"
164 static void (*WriteGwpAsanLog)(char*, size_t);
165 static void* handle = NULL;
166 static bool try_load_asan_logger = false;
167 static pthread_mutex_t gwpasan_mutex = PTHREAD_MUTEX_INITIALIZER;
168
gwp_asan_printf(const char * fmt,...)169 void gwp_asan_printf(const char *fmt, ...)
170 {
171 char para_name[GWP_ASAN_NAME_LEN] = "gwp_asan.log.path";
172 static CachedHandle para_handler = NULL;
173 if (para_handler == NULL) {
174 para_handler = CachedParameterCreate(para_name, "file");
175 }
176 char *para_value = CachedParameterGet(para_handler);
177 if (strcmp(para_value, "file") == 0) {
178 char process_short_name[GWP_ASAN_NAME_LEN];
179 char *path = get_process_short_name(process_short_name, GWP_ASAN_NAME_LEN);
180 if (!path) {
181 MUSL_LOGE("[gwp_asan]: get_process_short_name failed!");
182 return;
183 }
184 char log_path[GWP_ASAN_NAME_LEN];
185 snprintf(log_path, GWP_ASAN_NAME_LEN, "%s%s.%s.%d.log", GWP_ASAN_LOG_DIR, GWP_ASAN_LOG_TAG, path, getpid());
186 FILE *fp = fopen(log_path, "a+");
187 if (!fp) {
188 MUSL_LOGE("[gwp_asan]: %{public}s fopen %{public}s failed!", path, log_path);
189 return;
190 } else {
191 MUSL_LOGE("[gwp_asan]: %{public}s fopen %{public}s succeed.", path, log_path);
192 }
193 va_list ap;
194 va_start(ap, fmt);
195 int result = vfprintf(fp, fmt, ap);
196 va_end(ap);
197 fclose(fp);
198 if (result < 0) {
199 MUSL_LOGE("[gwp_asan] %{public}s write log failed!\n", path);
200 }
201 return;
202 }
203 if (strcmp(para_value, "default") == 0) {
204 va_list ap;
205 va_start(ap, fmt);
206 char log_buffer[PATH_MAX];
207 int result = vsnprintf(log_buffer, PATH_MAX, fmt, ap);
208 va_end(ap);
209 if (result < 0) {
210 MUSL_LOGE("[gwp_asan] write log failed!\n");
211 }
212 if (WriteGwpAsanLog != NULL) {
213 WriteGwpAsanLog(log_buffer, strlen(log_buffer));
214 return;
215 }
216 if (try_load_asan_logger) {
217 return;
218 }
219 pthread_mutex_lock(&gwpasan_mutex);
220 if (WriteGwpAsanLog != NULL) {
221 WriteGwpAsanLog(log_buffer, strlen(log_buffer));
222 pthread_mutex_unlock(&gwpasan_mutex);
223 return;
224 }
225 if (!try_load_asan_logger && handle == NULL) {
226 try_load_asan_logger = true;
227 handle = dlopen(ASAN_LOG_LIB, RTLD_LAZY);
228 if (handle == NULL) {
229 pthread_mutex_unlock(&gwpasan_mutex);
230 return;
231 }
232 *(void**)(&WriteGwpAsanLog) = dlsym(handle, "WriteGwpAsanLog");
233 if (WriteGwpAsanLog != NULL) {
234 WriteGwpAsanLog(log_buffer, strlen(log_buffer));
235 }
236 }
237 pthread_mutex_unlock(&gwpasan_mutex);
238 return;
239 }
240 if (strcmp(para_value, "stdout") == 0) {
241 va_list ap;
242 va_start(ap, fmt);
243 int result = vfprintf(stdout, fmt, ap);
244 va_end(ap);
245 if (result < 0) {
246 MUSL_LOGE("[gwp_asan] write log failed!\n");
247 }
248 return;
249 }
250 }
251
gwp_asan_printf_backtrace(uintptr_t * trace_buffer,size_t trace_length,printf_t gwp_asan_printf)252 void gwp_asan_printf_backtrace(uintptr_t *trace_buffer, size_t trace_length, printf_t gwp_asan_printf)
253 {
254 if (trace_length == 0) {
255 gwp_asan_printf("It dosen't see any stack trace!\n");
256 }
257 for (size_t i = 0; i < trace_length; i++) {
258 if (trace_buffer[i]) {
259 Dl_info info;
260 if (dladdr(trace_buffer[i], &info)) {
261 size_t offset = trace_buffer[i] - (uintptr_t)info.dli_fbase;
262 gwp_asan_printf(" #%zu %p (%s+%p)\n", i, trace_buffer[i], info.dli_fname, offset);
263 } else {
264 gwp_asan_printf(" #%zu %p\n", i, trace_buffer[i]);
265 }
266 }
267 }
268 gwp_asan_printf("\n");
269 }
270
271 // Strip pc because pc may have been protected by pac(Pointer Authentication) when build with "-mbranch-protection".
strip_pac_pc(size_t ptr)272 size_t strip_pac_pc(size_t ptr)
273 {
274 #if defined(MUSL_AARCH64_ARCH)
275 register size_t x30 __asm__("x30") = ptr;
276 // "xpaclri" is a NOP on pre armv8.3-a arch.
277 __asm__ volatile("xpaclri" : "+r"(x30));
278 return x30;
279 #else
280 return ptr;
281 #endif
282 }
283
284 /* This function is used for gwp_asan to record the call stack when allocate and deallocate.
285 * So we implemented a fast unwind function by using fp.
286 * The unwind process may stop because the value of fp is incorrect(fp was not saved on the stack due to optimization)
287 * We can build library with "-fno-omit-frame-pointer" to get a more accurate call stack.
288 * The basic unwind principle is as follows:
289 * Stack: func1->func2->func3
290 * --------------------| [Low Adress]
291 * | fp | |-------->|
292 * | lr | func3 | |
293 * | ...... | | |
294 * --------------------|<--------|
295 * | fp | |-------->|
296 * | lr | func2 | |
297 * | ...... | | |
298 * --------------------| |
299 * | fp | |<--------|
300 * | lr | func1 |
301 * | ...... | |
302 * --------------------| [High Address]
303 */
libc_gwp_asan_unwind_fast(size_t * frame_buf,size_t max_record_stack,void * signal_context)304 GWP_ASAN_NO_ADDRESS size_t libc_gwp_asan_unwind_fast(size_t *frame_buf, size_t max_record_stack,
305 __attribute__((unused)) void *signal_context)
306 {
307 size_t current_frame_addr = __builtin_frame_address(0);
308 size_t stack_end = (size_t)(__pthread_self()->stack);
309 size_t num_frames = 0;
310 size_t prev_fp = 0;
311 size_t prev_lr = 0;
312 while (true) {
313 unwind_info *frame = (unwind_info*)(current_frame_addr);
314 GWP_ASAN_LOGD("[gwp_asan] unwind info:%{public}d cur:%{public}p, end:%{public}p fp:%{public}p lr:%{public}p \n",
315 num_frames, current_frame_addr, stack_end, frame->fp, frame->lr);
316 if (!frame->lr) {
317 break;
318 }
319 if (num_frames < max_record_stack) {
320 frame_buf[num_frames] = strip_pac_pc(frame->lr) - 4;
321 }
322 ++num_frames;
323 if (frame->fp == prev_fp || frame->lr == prev_lr || frame->fp < current_frame_addr + sizeof(unwind_info) ||
324 frame->fp >= stack_end || frame->fp % sizeof(void*) != 0) {
325 break;
326 }
327 prev_fp = frame->fp;
328 prev_lr = frame->lr;
329 current_frame_addr = frame->fp;
330 }
331
332 return num_frames;
333 }
334
may_init_gwp_asan(bool force_init)335 bool may_init_gwp_asan(bool force_init)
336 {
337 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan enter force_init:%{public}d.\n", force_init);
338 if (gwp_asan_initialized) {
339 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan return because gwp_asan_initialized is true.\n");
340 return false;
341 }
342 #ifdef OHOS_ENABLE_PARAMETER
343 // Turn off gwp_asan.
344 if (GWP_ASAN_PREDICT_FALSE(is_gwp_asan_disable())) {
345 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan return because gwp_asan is disable by env.\n");
346 return false;
347 }
348 #endif
349
350 if (!force_init && !should_sample_process()) {
351 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan return because sample not hit.\n");
352 return false;
353 }
354
355 #ifdef OHOS_ENABLE_PARAMETER
356 // All memory allocations use gwp_asan.
357 force_sample_alloctor_by_env();
358 #endif
359
360 gwp_asan_option gwp_asan_option = {
361 .enable = true,
362 .install_fork_handlers = true,
363 .install_signal_handlers = true,
364 .max_simultaneous_allocations = MAX_SIMULTANEOUS_ALLOCATIONS,
365 .sample_rate = SAMPLE_RATE,
366 .backtrace = libc_gwp_asan_unwind_fast,
367 .gwp_asan_printf = gwp_asan_printf,
368 .printf_backtrace = gwp_asan_printf_backtrace,
369 .segv_backtrace = libc_gwp_asan_unwind_fast,
370 };
371
372 char buf[GWP_ASAN_NAME_LEN];
373 char *path = get_process_short_name(buf, GWP_ASAN_NAME_LEN);
374 if (!path) {
375 return false;
376 }
377
378 MUSL_LOGE("[gwp_asan]: %{public}d %{public}s gwp_asan initializing.\n", getpid(), path);
379 init_gwp_asan((void*)&gwp_asan_option);
380 gwp_asan_initialized = true;
381 MUSL_LOGE("[gwp_asan]: %{public}d %{public}s gwp_asan initialized.\n", getpid(), path);
382 return true;
383 }
init_gwp_asan_by_libc(bool force_init)384 bool init_gwp_asan_by_libc(bool force_init)
385 {
386 char buf[GWP_ASAN_NAME_LEN];
387 char *path = get_process_short_name(buf, GWP_ASAN_NAME_LEN);
388 if (!path) {
389 return false;
390 }
391 // We don't sample appspawn, and the chaild process decides whether to sample or not.
392 if (strcmp(path, "appspawn") == 0 || strcmp(path, "sh") == 0) {
393 return false;
394 }
395 return may_init_gwp_asan(force_init);
396 }
397
get_platform_gwp_asan_tls_slot()398 void* get_platform_gwp_asan_tls_slot()
399 {
400 return (void*)(&(__pthread_self()->gwp_asan_tls));
401 }
402
libc_gwp_asan_malloc(size_t bytes)403 void* libc_gwp_asan_malloc(size_t bytes)
404 {
405 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
406 return MuslFunc(malloc)(bytes);
407 }
408 void *res = NULL;
409 if (GWP_ASAN_PREDICT_FALSE(force_sample_alloctor || gwp_asan_should_sample())) {
410 res = gwp_asan_malloc(bytes);
411 if (res != NULL) {
412 return res;
413 }
414 }
415 return MuslFunc(malloc)(bytes);
416 }
417
libc_gwp_asan_calloc(size_t nmemb,size_t size)418 void* libc_gwp_asan_calloc(size_t nmemb, size_t size)
419 {
420 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
421 return MuslFunc(calloc)(nmemb, size);
422 }
423
424 if (GWP_ASAN_PREDICT_FALSE(force_sample_alloctor || gwp_asan_should_sample())) {
425 size_t total_bytes;
426 void* result = NULL;
427 if (!__builtin_mul_overflow(nmemb, size, &total_bytes)) {
428 GWP_ASAN_LOGD("[gwp_asan]: call gwp_asan_malloc nmemb:%{public}d size:%{public}d.\n", nmemb, size);
429 result = gwp_asan_malloc(total_bytes);
430 if (result != NULL) {
431 return result;
432 }
433 }
434 }
435
436 return MuslFunc(calloc)(nmemb, size);
437 }
438
libc_gwp_asan_realloc(void * ptr,size_t size)439 void* libc_gwp_asan_realloc(void *ptr, size_t size)
440 {
441 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
442 return MuslFunc(realloc)(ptr, size);
443 }
444
445 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(ptr))) {
446 GWP_ASAN_LOGD("[gwp_asan]: call gwp_asan_malloc ptr:%{public}p size:%{public}d.\n", ptr, size);
447 if (GWP_ASAN_PREDICT_FALSE(size == 0)) {
448 gwp_asan_free(ptr);
449 return NULL;
450 }
451
452 void* new_addr = gwp_asan_malloc(size);
453 if (new_addr != NULL) {
454 size_t old_size = gwp_asan_get_size(ptr);
455 memcpy(new_addr, ptr, (size < old_size) ? size : old_size);
456 gwp_asan_free(ptr);
457 return new_addr;
458 } else {
459 // Use the default allocator if gwp malloc failed.
460 void* addr_of_default_allocator = MuslFunc(malloc)(size);
461 if (addr_of_default_allocator != NULL) {
462 size_t old_size = gwp_asan_get_size(ptr);
463 memcpy(addr_of_default_allocator, ptr, (size < old_size) ? size : old_size);
464 gwp_asan_free(ptr);
465 return addr_of_default_allocator;
466 } else {
467 return NULL;
468 }
469 }
470 }
471 return MuslFunc(realloc)(ptr, size);
472 }
473
libc_gwp_asan_free(void * addr)474 void libc_gwp_asan_free(void *addr)
475 {
476 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
477 return MuslFunc(free)(addr);
478 }
479 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(addr))) {
480 return gwp_asan_free(addr);
481 }
482 return MuslFunc(free)(addr);
483 }
484
libc_gwp_asan_malloc_usable_size(void * addr)485 size_t libc_gwp_asan_malloc_usable_size(void *addr)
486 {
487 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
488 return MuslMalloc(malloc_usable_size)(addr);
489 }
490 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(addr))) {
491 return gwp_asan_get_size(addr);
492 }
493 return MuslMalloc(malloc_usable_size)(addr);
494 }
495
libc_gwp_asan_malloc_iterate(void * base,size_t size,void (* callback)(uintptr_t base,size_t size,void * arg),void * arg)496 void libc_gwp_asan_malloc_iterate(void *base, size_t size,
497 void (*callback)(uintptr_t base, size_t size, void *arg), void *arg)
498 {
499 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
500 return;
501 }
502 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(base))) {
503 return gwp_asan_iterate(base, size, callback, arg);
504 }
505 return;
506 }
507
libc_gwp_asan_malloc_disable()508 void libc_gwp_asan_malloc_disable()
509 {
510 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
511 return;
512 }
513
514 return gwp_asan_disable();
515 }
516
libc_gwp_asan_malloc_enable()517 void libc_gwp_asan_malloc_enable()
518 {
519 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
520 return;
521 }
522
523 return gwp_asan_enable();
524 }
525
libc_gwp_asan_has_free_mem()526 bool libc_gwp_asan_has_free_mem()
527 {
528 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
529 return false;
530 }
531 gwp_asan_disable();
532 int res = gwp_asan_has_free_mem();
533 gwp_asan_enable();
534 return res;
535 }
libc_gwp_asan_ptr_is_mine(void * addr)536 bool libc_gwp_asan_ptr_is_mine(void *addr)
537 {
538 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
539 return false;
540 }
541
542 return gwp_asan_pointer_is_mine(addr);
543 }
544 #else
545 #include <stdbool.h>
546
547 // Used for appspawn.
may_init_gwp_asan(bool force_init)548 bool may_init_gwp_asan(bool force_init)
549 {
550 return false;
551 }
552 #endif
553