xref: /third_party/musl/src/gwp_asan/linux/gwp_asan.c (revision 570af302)
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
42static bool gwp_asan_initialized = false;
43static uint8_t process_sample_rate = 128;
44static uint8_t force_sample_alloctor = 0;
45static uint8_t previous_random_value = 0;
46
47typedef 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.
55extern void init_gwp_asan(void *init_options);
56extern void* gwp_asan_malloc(size_t bytes);
57extern void gwp_asan_free(void *mem);
58extern bool gwp_asan_should_sample();
59extern bool gwp_asan_pointer_is_mine(void *mem);
60extern bool gwp_asan_has_free_mem();
61extern size_t gwp_asan_get_size(void *mem);
62extern void gwp_asan_disable();
63extern void gwp_asan_enable();
64extern void gwp_asan_iterate(void *base, size_t size,
65                                  void (*callback)(void* base, size_t size, void *arg), void *arg);
66
67
68extern int dladdr(const void *addr, Dl_info *info);
69
70static 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
94bool 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
108bool 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
130bool 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
145bool 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"
164static void (*WriteGwpAsanLog)(char*, size_t);
165static void* handle = NULL;
166static bool try_load_asan_logger = false;
167static pthread_mutex_t gwpasan_mutex = PTHREAD_MUTEX_INITIALIZER;
168
169void 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
252void 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".
272size_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 */
304GWP_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
335bool 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}
384bool 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
398void* get_platform_gwp_asan_tls_slot()
399{
400    return (void*)(&(__pthread_self()->gwp_asan_tls));
401}
402
403void* 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
418void* 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
439void* 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
474void 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
485size_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
496void 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
508void 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
517void 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
526bool 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}
536bool 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.
548bool may_init_gwp_asan(bool force_init)
549{
550    return false;
551}
552#endif
553