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 uint8_t random_value;
153 // If getting a random number using a non-blocking fails, the random value is incremented.
154 if (getrandom(&random_value, sizeof(random_value), GRND_RANDOM | GRND_NONBLOCK) == -1) {
155 random_value = previous_random_value + 1;
156 previous_random_value = random_value;
157 }
158
159 return (random_value % process_sample_rate) == 0 ? true : false;
160 }
161
162 #define ASAN_LOG_LIB "libasan_logger.z.so"
163 static void (*WriteGwpAsanLog)(char*, size_t);
164 static void* handle = NULL;
165 static bool try_load_asan_logger = false;
166 static pthread_mutex_t gwpasan_mutex = PTHREAD_MUTEX_INITIALIZER;
167
gwp_asan_printf(const char * fmt,...)168 void gwp_asan_printf(const char *fmt, ...)
169 {
170 char para_name[GWP_ASAN_NAME_LEN] = "gwp_asan.log.path";
171 static CachedHandle para_handler = NULL;
172 if (para_handler == NULL) {
173 para_handler = CachedParameterCreate(para_name, "file");
174 }
175 char *para_value = CachedParameterGet(para_handler);
176 if (strcmp(para_value, "file") == 0) {
177 char process_short_name[GWP_ASAN_NAME_LEN];
178 char *path = get_process_short_name(process_short_name, GWP_ASAN_NAME_LEN);
179 if (!path) {
180 MUSL_LOGE("[gwp_asan]: get_process_short_name failed!");
181 return;
182 }
183 char log_path[GWP_ASAN_NAME_LEN];
184 snprintf(log_path, GWP_ASAN_NAME_LEN, "%s%s.%s.%d.log", GWP_ASAN_LOG_DIR, GWP_ASAN_LOG_TAG, path, getpid());
185 FILE *fp = fopen(log_path, "a+");
186 if (!fp) {
187 MUSL_LOGE("[gwp_asan]: %{public}s fopen %{public}s succeed.", path, log_path);
188 return;
189 } else {
190 MUSL_LOGE("[gwp_asan]: %{public}s fopen %{public}s failed!", path, log_path);
191 }
192 va_list ap;
193 va_start(ap, fmt);
194 int result = vfprintf(fp, fmt, ap);
195 va_end(ap);
196 fclose(fp);
197 if (result < 0) {
198 MUSL_LOGE("[gwp_asan] %{public}s write log failed!\n", path);
199 }
200 return;
201 }
202 if (strcmp(para_value, "default") == 0) {
203 va_list ap;
204 va_start(ap, fmt);
205 char log_buffer[PATH_MAX];
206 int result = vsnprintf(log_buffer, PATH_MAX, fmt, ap);
207 va_end(ap);
208 if (result < 0) {
209 MUSL_LOGE("[gwp_asan] write log failed!\n");
210 }
211 if(WriteGwpAsanLog != NULL) {
212 WriteGwpAsanLog(log_buffer, strlen(log_buffer));
213 return;
214 }
215 if(try_load_asan_logger) {
216 return;
217 }
218 pthread_mutex_lock(&gwpasan_mutex);
219 if(WriteGwpAsanLog != NULL) {
220 WriteGwpAsanLog(log_buffer, strlen(log_buffer));
221 pthread_mutex_unlock(&gwpasan_mutex);
222 return;
223 }
224 if (!try_load_asan_logger && handle == NULL) {
225 try_load_asan_logger = true;
226 handle = dlopen(ASAN_LOG_LIB, RTLD_LAZY);
227 if (handle == NULL) {
228 pthread_mutex_unlock(&gwpasan_mutex);
229 return;
230 }
231 *(void**)(&WriteGwpAsanLog) = dlsym(handle, "WriteGwpAsanLog");
232 if (WriteGwpAsanLog != NULL) {
233 WriteGwpAsanLog(log_buffer, strlen(log_buffer));
234 }
235 }
236 pthread_mutex_unlock(&gwpasan_mutex);
237 return;
238 }
239 if (strcmp(para_value, "stdout") == 0) {
240 va_list ap;
241 va_start(ap, fmt);
242 int result = vfprintf(stdout, fmt, ap);
243 va_end(ap);
244 if (result < 0) {
245 MUSL_LOGE("[gwp_asan] write log failed!\n");
246 }
247 return;
248 }
249 }
250
gwp_asan_printf_backtrace(uintptr_t * trace_buffer,size_t trace_length,printf_t gwp_asan_printf)251 void gwp_asan_printf_backtrace(uintptr_t *trace_buffer, size_t trace_length, printf_t gwp_asan_printf)
252 {
253 if (trace_length == 0) {
254 gwp_asan_printf("It dosen't see any stack trace!\n");
255 }
256 for (size_t i = 0; i < trace_length; i++) {
257 if (trace_buffer[i]) {
258 Dl_info info;
259 if (dladdr(trace_buffer[i], &info)) {
260 size_t offset = trace_buffer[i] - (uintptr_t)info.dli_fbase;
261 gwp_asan_printf(" #%zu %p (%s+%p)\n", i, trace_buffer[i], info.dli_fname, offset);
262 } else {
263 gwp_asan_printf(" #%zu %p\n", i, trace_buffer[i]);
264 }
265 }
266 }
267 gwp_asan_printf("\n");
268 }
269
270 // Strip pc because pc may have been protected by pac(Pointer Authentication) when build with "-mbranch-protection".
strip_pac_pc(size_t ptr)271 size_t strip_pac_pc(size_t ptr)
272 {
273 #if defined(MUSL_AARCH64_ARCH)
274 register size_t x30 __asm__("x30") = ptr;
275 // "xpaclri" is a NOP on pre armv8.3-a arch.
276 __asm__ volatile("xpaclri" : "+r"(x30));
277 return x30;
278 #else
279 return ptr;
280 #endif
281 }
282
283 /* This function is used for gwp_asan to record the call stack when allocate and deallocate.
284 * So we implemented a fast unwind function by using fp.
285 * The unwind process may stop because the value of fp is incorrect(fp was not saved on the stack due to optimization)
286 * We can build library with "-fno-omit-frame-pointer" to get a more accurate call stack.
287 * The basic unwind principle is as follows:
288 * Stack: func1->func2->func3
289 * --------------------| [Low Adress]
290 * | fp | |-------->|
291 * | lr | func3 | |
292 * | ...... | | |
293 * --------------------|<--------|
294 * | fp | |-------->|
295 * | lr | func2 | |
296 * | ...... | | |
297 * --------------------| |
298 * | fp | |<--------|
299 * | lr | func1 |
300 * | ...... | |
301 * --------------------| [High Address]
302 */
libc_gwp_asan_unwind_fast(size_t * frame_buf,size_t max_record_stack,void * signal_context)303 GWP_ASAN_NO_ADDRESS size_t libc_gwp_asan_unwind_fast(size_t *frame_buf, size_t max_record_stack,
304 __attribute__((unused)) void *signal_context)
305 {
306 size_t current_frame_addr = __builtin_frame_address(0);
307 size_t stack_end = (size_t)(__pthread_self()->stack);
308 size_t num_frames = 0;
309 while (true) {
310 unwind_info *frame = (unwind_info*)(current_frame_addr);
311 GWP_ASAN_LOGD("[gwp_asan] unwind info:%{public}d cur:%{public}p, end:%{public}p fp:%{public}p lr:%{public}p \n",
312 num_frames, current_frame_addr, stack_end, frame->fp, frame->lr);
313 if (!frame->lr) {
314 break;
315 }
316 if (num_frames < max_record_stack) {
317 frame_buf[num_frames] = strip_pac_pc(frame->lr) - 4;
318 GWP_ASAN_LOGD("[gwp_asan]: before strip pc:%{public}p after strip pc:%{public}p\n",
319 frame->lr, frame_buf[num_frames]);
320 }
321 ++num_frames;
322 if (frame->fp < current_frame_addr || frame->fp >= stack_end ||
323 frame->fp % sizeof(void*) != 0) {
324 break;
325 }
326 current_frame_addr = frame->fp;
327 }
328
329 return num_frames;
330 }
331
may_init_gwp_asan(bool force_init)332 bool may_init_gwp_asan(bool force_init)
333 {
334 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan enter force_init:%{public}d.\n", force_init);
335 if (gwp_asan_initialized) {
336 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan return because gwp_asan_initialized is true.\n");
337 return false;
338 }
339 #ifdef OHOS_ENABLE_PARAMETER
340 // Turn off gwp_asan.
341 if (GWP_ASAN_PREDICT_FALSE(is_gwp_asan_disable())) {
342 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan return because gwp_asan is disable by env.\n");
343 return false;
344 }
345 #endif
346
347 if (!force_init && !should_sample_process()) {
348 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan return because sample not hit.\n");
349 return false;
350 }
351
352 #ifdef OHOS_ENABLE_PARAMETER
353 // All memory allocations use gwp_asan.
354 force_sample_alloctor_by_env();
355 #endif
356
357 gwp_asan_option gwp_asan_option = {
358 .enable = true,
359 .install_fork_handlers = true,
360 .install_signal_handlers = true,
361 .max_simultaneous_allocations = MAX_SIMULTANEOUS_ALLOCATIONS,
362 .sample_rate = SAMPLE_RATE,
363 .backtrace = libc_gwp_asan_unwind_fast,
364 .gwp_asan_printf = gwp_asan_printf,
365 .printf_backtrace = gwp_asan_printf_backtrace,
366 .segv_backtrace = libc_gwp_asan_unwind_fast,
367 };
368
369 char buf[GWP_ASAN_NAME_LEN];
370 char *path = get_process_short_name(buf, GWP_ASAN_NAME_LEN);
371 if (!path) {
372 return false;
373 }
374
375 MUSL_LOGE("[gwp_asan]: %{public}d %{public}s gwp_asan initializing.\n", getpid(), path);
376 init_gwp_asan((void*)&gwp_asan_option);
377 gwp_asan_initialized = true;
378 MUSL_LOGE("[gwp_asan]: %{public}d %{public}s gwp_asan initialized.\n", getpid(), path);
379 return true;
380 }
init_gwp_asan_by_libc(bool force_init)381 bool init_gwp_asan_by_libc(bool force_init)
382 {
383 char buf[GWP_ASAN_NAME_LEN];
384 char *path = get_process_short_name(buf, GWP_ASAN_NAME_LEN);
385 if (!path) {
386 return false;
387 }
388 // We don't sample appspawn, and the chaild process decides whether to sample or not.
389 if (strcmp(path, "appspawn") == 0 || strcmp(path, "sh") == 0) {
390 return false;
391 }
392 return may_init_gwp_asan(force_init);
393 }
394
libc_gwp_asan_malloc(size_t bytes)395 void* libc_gwp_asan_malloc(size_t bytes)
396 {
397 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
398 return MuslFunc(malloc)(bytes);
399 }
400 void *res = NULL;
401 if (GWP_ASAN_PREDICT_FALSE(force_sample_alloctor || gwp_asan_should_sample())) {
402 res = gwp_asan_malloc(bytes);
403 if (res != NULL) {
404 return res;
405 }
406 }
407 return MuslFunc(malloc)(bytes);
408 }
409
libc_gwp_asan_calloc(size_t nmemb,size_t size)410 void* libc_gwp_asan_calloc(size_t nmemb, size_t size)
411 {
412 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
413 return MuslFunc(calloc)(nmemb, size);
414 }
415
416 if (GWP_ASAN_PREDICT_FALSE(force_sample_alloctor || gwp_asan_should_sample())) {
417 size_t total_bytes;
418 void* result = NULL;
419 if (!__builtin_mul_overflow(nmemb, size, &total_bytes)) {
420 GWP_ASAN_LOGD("[gwp_asan]: call gwp_asan_malloc nmemb:%{public}d size:%{public}d.\n", nmemb, size);
421 result = gwp_asan_malloc(total_bytes);
422 if (result != NULL) {
423 return result;
424 }
425 }
426 }
427
428 return MuslFunc(calloc)(nmemb, size);
429 }
430
libc_gwp_asan_realloc(void * ptr,size_t size)431 void* libc_gwp_asan_realloc(void *ptr, size_t size)
432 {
433 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
434 return MuslFunc(realloc)(ptr, size);
435 }
436
437 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(ptr))) {
438 GWP_ASAN_LOGD("[gwp_asan]: call gwp_asan_malloc ptr:%{public}p size:%{public}d.\n", ptr, size);
439 void* new_addr = gwp_asan_malloc(size);
440 if (new_addr != NULL) {
441 size_t old_size = gwp_asan_get_size(ptr);
442 memcpy(new_addr, ptr, (size < old_size) ? size : old_size);
443 gwp_asan_free(ptr);
444 return new_addr;
445 } else {
446 return 0;
447 }
448 }
449 return MuslFunc(realloc)(ptr, size);
450 }
451
libc_gwp_asan_free(void * addr)452 void libc_gwp_asan_free(void *addr)
453 {
454 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
455 return MuslFunc(free)(addr);
456 }
457 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(addr))) {
458 return gwp_asan_free(addr);
459 }
460 return MuslFunc(free)(addr);
461 }
462
libc_gwp_asan_malloc_usable_size(void * addr)463 size_t libc_gwp_asan_malloc_usable_size(void *addr)
464 {
465 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
466 return MuslMalloc(malloc_usable_size)(addr);
467 }
468 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(addr))) {
469 return gwp_asan_get_size(addr);
470 }
471 return MuslMalloc(malloc_usable_size)(addr);
472 }
473
libc_gwp_asan_malloc_iterate(void * base,size_t size,void (* callback)(uintptr_t base,size_t size,void * arg),void * arg)474 void libc_gwp_asan_malloc_iterate(void *base, size_t size,
475 void (*callback)(uintptr_t base, size_t size, void *arg), void *arg)
476 {
477 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
478 return;
479 }
480 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(base))) {
481 return gwp_asan_iterate(base, size, callback, arg);
482 }
483 return;
484 }
485
libc_gwp_asan_malloc_disable()486 void libc_gwp_asan_malloc_disable()
487 {
488 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
489 return;
490 }
491
492 return gwp_asan_disable();
493 }
494
libc_gwp_asan_malloc_enable()495 void libc_gwp_asan_malloc_enable()
496 {
497 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
498 return;
499 }
500
501 return gwp_asan_enable();
502 }
503
libc_gwp_asan_has_free_mem()504 bool libc_gwp_asan_has_free_mem()
505 {
506 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
507 return false;
508 }
509 gwp_asan_disable();
510 int res = gwp_asan_has_free_mem();
511 gwp_asan_enable();
512 return res;
513 }
libc_gwp_asan_ptr_is_mine(void * addr)514 bool libc_gwp_asan_ptr_is_mine(void *addr)
515 {
516 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
517 return false;
518 }
519
520 return gwp_asan_pointer_is_mine(addr);
521 }
522
523 #endif
524