From 47f3990c6f5b744b40fbb38df6046bb03c9c5c98 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 14 Jun 2024 15:44:21 +0300 Subject: [PATCH] patches: support instrumenting dlmalloc by ASAN --- cmake/BuildLuaJIT.cmake | 6 +- patches/luajit-dmalloc-asan_instr-v2.1.patch | 425 +++++++++++++++++++ 2 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 patches/luajit-dmalloc-asan_instr-v2.1.patch diff --git a/cmake/BuildLuaJIT.cmake b/cmake/BuildLuaJIT.cmake index ffe9176..62d054e 100644 --- a/cmake/BuildLuaJIT.cmake +++ b/cmake/BuildLuaJIT.cmake @@ -13,7 +13,7 @@ macro(build_luajit LJ_VERSION) set(CFLAGS "${CFLAGS} -fsanitize=fuzzer-no-link") set(LDFLAGS "-fsanitize=fuzzer-no-link") - set(LUAJIT_PATCH_PATH ${PROJECT_SOURCE_DIR}/patches/luajit-v2.1.patch) + set(LUAJIT_BASEDIR ${PROJECT_SOURCE_DIR}/patches/) if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(CFLAGS "${CFLAGS} ${CMAKE_C_FLAGS_DEBUG}") @@ -99,7 +99,9 @@ macro(build_luajit LJ_VERSION) TMP_DIR ${LJ_BINARY_DIR}/tmp STAMP_DIR ${LJ_BINARY_DIR}/stamp - PATCH_COMMAND git reset --hard && cd && patch -p1 -i ${LUAJIT_PATCH_PATH} + PATCH_COMMAND git reset --hard && cd && + patch -p1 -i ${LUAJIT_BASEDIR}/luajit-v2.1.patch && + patch -p1 -i ${LUAJIT_BASEDIR}/luajit-dmalloc-asan_instr-v2.1.patch CONFIGURE_COMMAND "" BUILD_COMMAND cd && make -j CC=${CMAKE_C_COMPILER} CFLAGS=${CFLAGS} diff --git a/patches/luajit-dmalloc-asan_instr-v2.1.patch b/patches/luajit-dmalloc-asan_instr-v2.1.patch new file mode 100644 index 0000000..e636e00 --- /dev/null +++ b/patches/luajit-dmalloc-asan_instr-v2.1.patch @@ -0,0 +1,425 @@ +diff --git a/src/lj_alloc.c b/src/lj_alloc.c +index cb704f7b..b6bbb023 100644 +--- a/src/lj_alloc.c ++++ b/src/lj_alloc.c +@@ -230,12 +230,116 @@ static int CALL_MUNMAP(void *ptr, size_t size) + + #define LJ_ALLOC_MMAP_PROBE_LOWER ((uintptr_t)0x4000) + ++/* No point in a giant ifdef mess. Just try to open /dev/urandom. ++** It doesn't really matter if this fails, since we get some ASLR bits from ++** every unsuitable allocation, too. And we prefer linear allocation, anyway. ++*/ ++#include ++#include ++ ++#if LUAJIT_USE_ASAN ++ ++/* ++** The work of asan (AddressSanitizer) is to detect memory errors during program execution. ++** One way to achieve this is by adding redzones around memory allocations. The redzone is a ++** specially allocated area of memory before and after the allocated block, which is filled ++** with a unique value. If the program tries to access memory outside of the allocation, ++** asan detects this attempt and generates an error message, allowing the developer to ++** detect and fix the issue early. ++** ++** - Original paper: https://www.usenix.org/system/files/conference/atc12/atc12-final39.pdf ++** ++** LuaJIT ASAN instrumentation (mmap and others): ++** ++** - Memory map around allocation: ++** ------------------------------------------------------------------------------------- ++** .. .. | [f7] ... [f7] | [00] ... [0(0-7)] | [f7] ... [f7] | .. .. ++** | left redzone | data | right redzone | ++** | REDZONE_SIZE bytes | N bytes | REDZONE_SIZE bytes | ++** ------------------------------------------------------------------------------------- ++** ++** left redzone: ++** The first SIZE_T_SIZE bytes of the redzone contain the data size N, the next SIZE_T_SIZE bytes ++** of the redzone contain the full size of the allocation, including the alignment of the size N ++** and the size of the redzones themselves. ++*/ ++ ++#include ++ ++/* Recommended redzone size from 16 to 2048 bytes (must be a a power of two) ++** https://github.com/google/sanitizers/wiki/AddressSanitizerFlags ++*/ ++#define REDZONE_SIZE FOUR_SIZE_T_SIZES ++ ++/* Total redzone size around allocation */ ++#define TOTAL_REDZONE_SIZE (REDZONE_SIZE << 1) ++ ++/* Multiple of the allocated memory size */ ++#define SIZE_ALIGNMENT MALLOC_ALIGNMENT ++ ++/* Multiple of the allocated memory address */ ++#define ADDR_ALIGNMENT MALLOC_ALIGNMENT ++ ++/* Casting to the nearest multiple of alignment from above */ ++void *align_up(void *ptr, size_t alignment) ++{ ++ uintptr_t p = (uintptr_t)ptr; ++ return (void *)((p + alignment - 1) & ~(alignment - 1)); ++} ++ ++void *mark_memory_region(void *ptr, size_t mem_size, size_t poison_size) ++{ ++ if (ptr == NULL) ++ return NULL; ++ size_t *sptr = (size_t *)ptr; ++ ASAN_UNPOISON_MEMORY_REGION(ptr, TWO_SIZE_T_SIZES); ++ sptr[0] = mem_size; ++ sptr[1] = poison_size; ++ ASAN_POISON_MEMORY_REGION(ptr, poison_size); ++ ptr += REDZONE_SIZE; ++ ASAN_UNPOISON_MEMORY_REGION(ptr, mem_size); ++ return ptr; ++} ++ ++typedef enum { ++ MEM_SIZE, ++ POISON_SIZE ++} SizeType; ++ ++size_t asan_get_size(void *ptr, SizeType type) ++{ ++ size_t offset = (type == MEM_SIZE) ? 0 : SIZE_T_SIZE; ++ ASAN_UNPOISON_MEMORY_REGION(ptr - REDZONE_SIZE + offset, SIZE_T_SIZE); ++ size_t size = *((size_t *)(ptr - REDZONE_SIZE + offset)); ++ ASAN_POISON_MEMORY_REGION(ptr - REDZONE_SIZE + offset, SIZE_T_SIZE); ++ return size; ++} ++ ++#endif ++ ++static uintptr_t mmap_probe_seed(void) ++{ ++ uintptr_t val; ++ int fd = open("/dev/urandom", O_RDONLY); ++ if (fd != -1) { ++ int ok = ((size_t)read(fd, &val, sizeof(val)) == sizeof(val)); ++ (void)close(fd); ++ if (ok) return val; ++ } ++ return 1; /* Punt. */ ++} ++ ++ + static void *mmap_probe(PRNGState *rs, size_t size) + { + /* Hint for next allocation. Doesn't need to be thread-safe. */ + static uintptr_t hint_addr = 0; + int olderr = errno; + int retry; ++#if LUAJIT_USE_ASAN ++ size_t mem_size = size; ++ size = (size_t)align_up((void *)size, SIZE_ALIGNMENT) + TOTAL_REDZONE_SIZE; ++#endif + for (retry = 0; retry < LJ_ALLOC_MMAP_PROBE_MAX; retry++) { + void *p = mmap((void *)hint_addr, size, MMAP_PROT, MMAP_FLAGS_PROBE, -1, 0); + uintptr_t addr = (uintptr_t)p; +@@ -244,6 +348,9 @@ static void *mmap_probe(PRNGState *rs, size_t size) + /* We got a suitable address. Bump the hint address. */ + hint_addr = addr + size; + errno = olderr; ++#if LUAJIT_USE_ASAN ++ p = mark_memory_region(p, mem_size, size); ++#endif + return p; + } + if (p != MFAIL) { +@@ -296,7 +403,17 @@ static void *mmap_map32(size_t size) + #endif + { + int olderr = errno; ++#if LUAJIT_USE_ASAN ++ size_t mem_size = size; ++ size = (size_t)align_up((void *)size, SIZE_ALIGNMENT) + TOTAL_REDZONE_SIZE; ++#endif + void *ptr = mmap((void *)LJ_ALLOC_MMAP32_START, size, MMAP_PROT, MAP_32BIT|MMAP_FLAGS, -1, 0); ++#if LUAJIT_USE_ASAN ++ if (ptr != MFAIL) ++ ptr = mark_memory_region(ptr, mem_size, size); ++ ++ size = mem_size; ++#endif + errno = olderr; + /* This only allows 1GB on Linux. So fallback to probing to get 2GB. */ + #if LJ_ALLOC_MMAP_PROBE +@@ -323,8 +440,15 @@ static void *mmap_map32(size_t size) + static void *mmap_plain(size_t size) + { + int olderr = errno; ++#if LUAJIT_USE_ASAN ++ size_t mem_size = size; ++ size = (size_t)align_up((void *)size, SIZE_ALIGNMENT) + TOTAL_REDZONE_SIZE; ++#endif + void *ptr = mmap(NULL, size, MMAP_PROT, MMAP_FLAGS, -1, 0); + errno = olderr; ++#if LUAJIT_USE_ASAN ++ ptr = mark_memory_region(ptr, mem_size, size); ++#endif + return ptr; + } + #define CALL_MMAP(prng, size) mmap_plain(size) +@@ -347,7 +471,17 @@ static void init_mmap(void) + static int CALL_MUNMAP(void *ptr, size_t size) + { + int olderr = errno; ++#if LUAJIT_USE_ASAN ++ memmove(ptr, ptr, size); /* check that memory is not poisoned */ ++ size = asan_get_size(ptr, POISON_SIZE); ++ ptr -= REDZONE_SIZE; ++#endif + int ret = munmap(ptr, size); ++#if LUAJIT_USE_ASAN ++ if (ret == 0) { ++ ASAN_POISON_MEMORY_REGION(ptr, size); ++ } ++#endif + errno = olderr; + return ret; + } +@@ -357,7 +491,21 @@ static int CALL_MUNMAP(void *ptr, size_t size) + static void *CALL_MREMAP_(void *ptr, size_t osz, size_t nsz, int flags) + { + int olderr = errno; ++#if LUAJIT_USE_ASAN ++ void *old_ptr = ptr; ++ size_t nms = nsz; /* new memory size */ ++ osz = asan_get_size(old_ptr, POISON_SIZE); ++ nsz = (size_t)align_up((void *)nsz, SIZE_ALIGNMENT) + TOTAL_REDZONE_SIZE; ++ ptr -= REDZONE_SIZE; ++#endif + ptr = mremap(ptr, osz, nsz, flags); ++#if LUAJIT_USE_ASAN ++ if (ptr != MFAIL) { ++ /* can return a pointer to the same memory */ ++ ASAN_POISON_MEMORY_REGION(old_ptr, osz); ++ ptr = mark_memory_region(ptr, nms, nsz); ++ } ++#endif + errno = olderr; + return ptr; + } +@@ -418,9 +566,15 @@ typedef unsigned int flag_t; /* The type of various bit flag sets */ + #define MIN_CHUNK_SIZE\ + ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + ++#if LUAJIT_USE_ASAN ++/* conversion from malloc headers to user pointers, and back */ ++#define chunk2mem(p) ((void *)((char *)(p) + TWO_SIZE_T_SIZES + REDZONE_SIZE)) ++#define mem2chunk(mem) ((mchunkptr)((char *)(mem) - TWO_SIZE_T_SIZES - REDZONE_SIZE)) ++#else + /* conversion from malloc headers to user pointers, and back */ + #define chunk2mem(p) ((void *)((char *)(p) + TWO_SIZE_T_SIZES)) + #define mem2chunk(mem) ((mchunkptr)((char *)(mem) - TWO_SIZE_T_SIZES)) ++#endif + /* chunk associated with aligned address A */ + #define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A))) + +@@ -875,7 +1029,12 @@ static mchunkptr direct_resize(mchunkptr oldp, size_t nb) + static void init_top(mstate m, mchunkptr p, size_t psize) + { + /* Ensure alignment */ +- size_t offset = align_offset(chunk2mem(p)); ++ void *t = chunk2mem(p); ++#if LUAJIT_USE_ASAN ++ t -= REDZONE_SIZE; ++#endif ++ size_t offset = align_offset(t); ++ + p = (mchunkptr)((char *)p + offset); + psize -= offset; + +@@ -937,6 +1096,9 @@ static void add_segment(mstate m, char *tbase, size_t tsize) + /* Determine locations and sizes of segment, fenceposts, old top */ + char *old_top = (char *)m->top; + msegmentptr oldsp = segment_holding(m, old_top); ++#if LUAJIT_USE_ASAN ++ ASAN_UNPOISON_MEMORY_REGION(oldsp, sizeof(struct malloc_segment)); ++#endif + char *old_end = oldsp->base + oldsp->size; + size_t ssize = pad_request(sizeof(struct malloc_segment)); + char *rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK); +@@ -945,6 +1107,9 @@ static void add_segment(mstate m, char *tbase, size_t tsize) + char *csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp; + mchunkptr sp = (mchunkptr)csp; + msegmentptr ss = (msegmentptr)(chunk2mem(sp)); ++#if LUAJIT_USE_ASAN ++ ss = (msegmentptr)((void *)ss - REDZONE_SIZE); ++#endif + mchunkptr tnext = chunk_plus_offset(sp, ssize); + mchunkptr p = tnext; + +@@ -1226,6 +1391,9 @@ static void *tmalloc_small(mstate m, size_t nb) + void *lj_alloc_create(PRNGState *rs) + { + size_t tsize = DEFAULT_GRANULARITY; ++#if LUAJIT_USE_ASAN ++ tsize -= TOTAL_REDZONE_SIZE; ++#endif + char *tbase; + INIT_MMAP(); + UNUSED(rs); +@@ -1233,15 +1401,24 @@ void *lj_alloc_create(PRNGState *rs) + if (tbase != CMFAIL) { + size_t msize = pad_request(sizeof(struct malloc_state)); + mchunkptr mn; ++#if LUAJIT_USE_ASAN ++ mchunkptr msp = (mchunkptr)(tbase + align_offset(chunk2mem(tbase) - REDZONE_SIZE)); ++ mstate m = (mstate)(chunk2mem(msp) - REDZONE_SIZE); ++#else + mchunkptr msp = align_as_chunk(tbase); + mstate m = (mstate)(chunk2mem(msp)); ++#endif + memset(m, 0, msize); + msp->head = (msize|PINUSE_BIT|CINUSE_BIT); + m->seg.base = tbase; + m->seg.size = tsize; + m->release_checks = MAX_RELEASE_CHECK_RATE; + init_bins(m); ++#if LUAJIT_USE_ASAN ++ mn = next_chunk((mchunkptr)((char *)(m) - TWO_SIZE_T_SIZES)); ++#else + mn = next_chunk(mem2chunk(m)); ++#endif + init_top(m, mn, (size_t)((tbase + tsize) - (char *)mn) - TOP_FOOT_SIZE); + return m; + } +@@ -1262,12 +1439,20 @@ void lj_alloc_destroy(void *msp) + char *base = sp->base; + size_t size = sp->size; + sp = sp->next; ++#if LUAJIT_USE_ASAN ++ ASAN_UNPOISON_MEMORY_REGION(base, size); ++#endif + CALL_MUNMAP(base, size); + } + } + + static LJ_NOINLINE void *lj_alloc_malloc(void *msp, size_t nsize) + { ++#if LUAJIT_USE_ASAN ++ size_t mem_size = nsize; ++ size_t poison_size = (size_t)align_up((void *)nsize, SIZE_ALIGNMENT) + TOTAL_REDZONE_SIZE; ++ nsize = poison_size; ++#endif + mstate ms = (mstate)msp; + void *mem; + size_t nb; +@@ -1286,6 +1471,9 @@ static LJ_NOINLINE void *lj_alloc_malloc(void *msp, size_t nsize) + unlink_first_small_chunk(ms, b, p, idx); + set_inuse_and_pinuse(ms, p, small_index2size(idx)); + mem = chunk2mem(p); ++#if LUAJIT_USE_ASAN ++ mem = mark_memory_region(mem - REDZONE_SIZE, mem_size, poison_size); ++#endif + return mem; + } else if (nb > ms->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ +@@ -1307,8 +1495,14 @@ static LJ_NOINLINE void *lj_alloc_malloc(void *msp, size_t nsize) + replace_dv(ms, r, rsize); + } + mem = chunk2mem(p); ++#if LUAJIT_USE_ASAN ++ mem = mark_memory_region(mem - REDZONE_SIZE, mem_size, poison_size); ++#endif + return mem; + } else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) { ++#if LUAJIT_USE_ASAN ++ mem = mark_memory_region(mem - REDZONE_SIZE, mem_size, poison_size); ++#endif + return mem; + } + } +@@ -1317,6 +1511,9 @@ static LJ_NOINLINE void *lj_alloc_malloc(void *msp, size_t nsize) + } else { + nb = pad_request(nsize); + if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) { ++#if LUAJIT_USE_ASAN ++ mem = mark_memory_region(mem - REDZONE_SIZE, mem_size, poison_size); ++#endif + return mem; + } + } +@@ -1336,6 +1533,9 @@ static LJ_NOINLINE void *lj_alloc_malloc(void *msp, size_t nsize) + set_inuse_and_pinuse(ms, p, dvs); + } + mem = chunk2mem(p); ++#if LUAJIT_USE_ASAN ++ mem = mark_memory_region(mem - REDZONE_SIZE, mem_size, poison_size); ++#endif + return mem; + } else if (nb < ms->topsize) { /* Split top */ + size_t rsize = ms->topsize -= nb; +@@ -1344,13 +1544,30 @@ static LJ_NOINLINE void *lj_alloc_malloc(void *msp, size_t nsize) + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + mem = chunk2mem(p); ++#if LUAJIT_USE_ASAN ++ mem = mark_memory_region(mem - REDZONE_SIZE, mem_size, poison_size); ++#endif + return mem; + } ++#if LUAJIT_USE_ASAN ++ return mark_memory_region(alloc_sys(ms, nb) - REDZONE_SIZE, mem_size, poison_size); ++#else + return alloc_sys(ms, nb); ++#endif + } + + static LJ_NOINLINE void *lj_alloc_free(void *msp, void *ptr) + { ++#if LUAJIT_USE_ASAN ++ if (ptr != 0) { ++ size_t mem_size = asan_get_size(ptr, MEM_SIZE); ++ size_t poison_size = asan_get_size(ptr, POISON_SIZE); ++ ++ memmove(ptr, ptr, mem_size); ++ ASAN_POISON_MEMORY_REGION(ptr - REDZONE_SIZE, poison_size); ++ } ++ return NULL; ++#else + if (ptr != 0) { + mchunkptr p = mem2chunk(ptr); + mstate fm = (mstate)msp; +@@ -1418,10 +1635,29 @@ static LJ_NOINLINE void *lj_alloc_free(void *msp, void *ptr) + } + } + return NULL; ++#endif + } + + static LJ_NOINLINE void *lj_alloc_realloc(void *msp, void *ptr, size_t nsize) + { ++#if LUAJIT_USE_ASAN ++ if (nsize >= MAX_REQUEST) ++ return NULL; ++ ++ mstate m = (mstate)msp; ++ ++ size_t mem_size = asan_get_size(ptr, MEM_SIZE); ++ size_t poison_size = asan_get_size(ptr, POISON_SIZE); ++ ++ void *newmem = lj_alloc_malloc(m, nsize); ++ ++ if (newmem == NULL) ++ return NULL; ++ ++ memcpy(newmem, ptr, nsize > mem_size ? mem_size : nsize); ++ ASAN_POISON_MEMORY_REGION(ptr - REDZONE_SIZE, poison_size); ++ return newmem; ++#else + if (nsize >= MAX_REQUEST) { + return NULL; + } else { +@@ -1468,6 +1704,7 @@ static LJ_NOINLINE void *lj_alloc_realloc(void *msp, void *ptr, size_t nsize) + return newmem; + } + } ++#endif + } + + void *lj_alloc_f(void *msp, void *ptr, size_t osize, size_t nsize)