• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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