Skip to content

Commit

Permalink
c: add asan instrumentation for mmap/munmap/mremap
Browse files Browse the repository at this point in the history
TEST
  • Loading branch information
mandesero committed Jul 19, 2024
1 parent 01f4586 commit bc27b62
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 7 deletions.
10 changes: 7 additions & 3 deletions .github/workflows/sanitizers-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,22 @@ jobs:
# XXX: Let's start with only Linux/x86_64
BUILDTYPE: [Debug, Release]
CC: [gcc-10, clang-11]
ALLOCATOR: [SYSMALLOC, LUAJIT_ALLOCATOR]
include:
- BUILDTYPE: Debug
CMAKEFLAGS: -DCMAKE_BUILD_TYPE=Debug -DLUA_USE_ASSERT=ON -DLUA_USE_APICHECK=ON
- BUILDTYPE: Release
CMAKEFLAGS: -DCMAKE_BUILD_TYPE=RelWithDebInfo
- ALLOCATOR: SYSMALLOC
ASANFLAGS: -DLUAJIT_USE_ASAN=ON -DLUAJIT_USE_SYSMALLOC=ON
- ALLOCATOR: LUAJIT_ALLOCATOR
ASANFLAGS: -DLUAJIT_USE_ASAN_HARDENING=ON
runs-on: [self-hosted, regular, Linux, x86_64]
name: >
LuaJIT with ASan and UBSan (Linux/x86_64)
${{ matrix.BUILDTYPE }}
CC:${{ matrix.CC }}
GC64:ON SYSMALLOC:ON
GC64:ON ALLOCATOR=${{ matrix.ALLOCATOR }}
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -71,8 +76,7 @@ jobs:
-G Ninja
${{ matrix.CMAKEFLAGS }}
-DLUAJIT_ENABLE_GC64=ON
-DLUAJIT_USE_ASAN=ON
-DLUAJIT_USE_SYSMALLOC=ON
${{ matrix.ASANFLAGS }}
-DLUAJIT_USE_UBSAN=ON
- name: build
run: cmake --build . --parallel
Expand Down
32 changes: 28 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,10 @@ option(LUAJIT_USE_ASAN "Build LuaJIT with AddressSanitizer" OFF)
if(LUAJIT_USE_ASAN)
if(NOT LUAJIT_USE_SYSMALLOC)
message(WARNING
"Unfortunately, internal LuaJIT memory allocator is not instrumented yet,"
" so to find any memory errors it's better to build LuaJIT with system"
" provided memory allocator (i.e. run CMake configuration phase with"
" -DLUAJIT_USE_SYSMALLOC=ON)."
"Run CMake configuration phase with -DLUAJIT_USE_SYSMALLOC=ON"
"to use system memory allocator or replace -DLUAJIT_USE_ASAN=ON"
"to -DLUAJIT_USE_ASAN_HARDENING=ON to use internal LuaJIT memory"
"allocator."
)
endif()
# Use all recommendations described in AddressSanitize docs:
Expand All @@ -309,6 +309,30 @@ if(LUAJIT_USE_ASAN)
)
endif()

# Same as LUAJIT_USE_ASAN, but using the internal LuaJIT memory
# allocator instrumented with ASAN.
option(LUAJIT_USE_ASAN_HARDENING "Build LuaJIT with an internal allocator with integrated AddressSanitizer" OFF)
if(LUAJIT_USE_ASAN_HARDENING)
if(NOT LUAJIT_ENABLE_GC64)
message(FATAL_ERROR
"ASAN only with GC64."
)
endif()
# Use all recommendations described in AddressSanitize docs:
# https://clang.llvm.org/docs/AddressSanitizer.html.
AppendFlags(CMAKE_C_FLAGS
# Enable hints for AddressSanitizer (see src/lj_str.c).
-DLUAJIT_USE_ASAN
# Enable ASAN instrumentation of internal LuaJIT memory allocator
# see (src/lj_alloc.c)
-DLUAJIT_USE_ASAN_HARDENING
# XXX: To get nicer stack traces in error messages.
-fno-omit-frame-pointer
# Enable AddressSanitizer support.
-fsanitize=address
)
endif()

option(LUAJIT_USE_UBSAN "Build LuaJIT with UndefinedBehaviorSanitizer" OFF)
if(LUAJIT_USE_UBSAN)
# Use all needed checks from the UndefinedBehaviorSanitizer
Expand Down
174 changes: 174 additions & 0 deletions src/lj_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,105 @@ static int CALL_MUNMAP(void *ptr, size_t size)

#define LJ_ALLOC_MMAP_PROBE_LOWER ((uintptr_t)0x4000)

#if LUAJIT_USE_ASAN_HARDENING

/*
** 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 <sanitizer/asan_interface.h>

/**
*
* Memory map for 64-bit (shift = 3)
* The shadow address is calculated by (Mem >> shift) + 0x7fff8000
*
* [0x10007fff8000, 0x7fffffffffff] HighMem
* [0x02008fff7000, 0x10007fff7fff] HighShadow
* [0x00008fff7000, 0x02008fff6fff] ShadowGap
* [0x00007fff8000, 0x00008fff6fff] LowShadow
* [0x000000000000, 0x00007fff7fff] LowMem
*
*/

/* 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

/**
* We can only use the address from HighMem, so we must force the system allocator (mmap)
* to return addresses starting from the lower bound of HighMem.
*/
static inline uintptr_t asan_lower_address()
{
size_t shadow_scale;
size_t shadow_offset;
__asan_get_shadow_mapping(&shadow_scale, &shadow_offset);
return (uintptr_t)(shadow_offset + (1ULL << (LJ_ALLOC_MBITS - shadow_scale)));
}

/* Casting to the nearest multiple of SIZE_ALIGNMENT from above */
#define ALIGN_SIZE(S, ALIGN) ((size_t)((S + (ALIGN) - 1) & ~((ALIGN) - 1)))

/* Add redzones around allocation and keep the memory size and poison size. */
void *mark_memory_region(void *ptr, size_t msize, size_t psize)
{
if (ptr == NULL)
return NULL;

ASAN_UNPOISON_MEMORY_REGION(ptr, TWO_SIZE_T_SIZES);
*((size_t *)(ptr)) = msize;
*((size_t *)(ptr) + 1) = psize;
ASAN_POISON_MEMORY_REGION(ptr, psize);
ptr = (void *)((char *)(ptr) + REDZONE_SIZE);
ASAN_UNPOISON_MEMORY_REGION(ptr, msize);
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

/* 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.
Expand All @@ -252,14 +351,28 @@ static void *mmap_probe(size_t size)
static uintptr_t hint_prng = 0;
int olderr = errno;
int retry;
#if LUAJIT_USE_ASAN_HARDENING
/* Save the request memory size */
size_t msize = size;
/* Total allocation size corresponds to the memory size and the size of redzones */
size = ALIGN_SIZE(size + TOTAL_REDZONE_SIZE, LJ_PAGESIZE);
#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;
#if LUAJIT_USE_ASAN_HARDENING
if ((addr >> LJ_ALLOC_MBITS) == 0 && addr >= asan_lower_address() &&
((addr + size) >> LJ_ALLOC_MBITS) == 0) {
#else
if ((addr >> LJ_ALLOC_MBITS) == 0 && addr >= LJ_ALLOC_MMAP_PROBE_LOWER &&
((addr + size) >> LJ_ALLOC_MBITS) == 0) {
#endif
/* We got a suitable address. Bump the hint address. */
hint_addr = addr + size;
errno = olderr;
#if LUAJIT_USE_ASAN_HARDENING
p = mark_memory_region(p, msize, size);
#endif
return p;
}
if (p != MFAIL) {
Expand Down Expand Up @@ -338,8 +451,19 @@ static void *mmap_map32(size_t size)
static void *CALL_MMAP(size_t size)
{
int olderr = errno;
#if LUAJIT_USE_ASAN_HARDENING
size_t msize = size;
size = ALIGN_SIZE(size + TOTAL_REDZONE_SIZE, LJ_PAGESIZE);
#endif
#if LUAJIT_USE_ASAN_HARDENING
void *ptr = mmap((void *)asan_lower_address(), size, MMAP_PROT, MMAP_FLAGS, -1, 0);
#else
void *ptr = mmap(NULL, size, MMAP_PROT, MMAP_FLAGS, -1, 0);
#endif
errno = olderr;
#if LUAJIT_USE_ASAN_HARDENING
ptr = mark_memory_region(ptr, msize, size);
#endif
return ptr;
}
#endif
Expand All @@ -361,7 +485,18 @@ static void init_mmap(void)
static int CALL_MUNMAP(void *ptr, size_t size)
{
int olderr = errno;
#if LUAJIT_USE_ASAN_HARDENING
/* check that memory is not poisoned */
memmove(ptr, ptr, size);
size = asan_get_size(ptr, POISON_SIZE);
ptr = (void *)((char *)(ptr) - REDZONE_SIZE);
#endif
int ret = munmap(ptr, size);
#if LUAJIT_USE_ASAN_HARDENING
if (ret == 0) {
ASAN_POISON_MEMORY_REGION(ptr, size);
}
#endif
errno = olderr;
return ret;
}
Expand All @@ -371,7 +506,31 @@ 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_HARDENING && !(LJ_64 && (!LJ_GC64 || LJ_TARGET_ARM64))
void *new_ptr = mmap_probe(nsz);
if (new_ptr != MFAIL) {
size_t oms = asan_get_size(ptr, MEM_SIZE);
memcpy(new_ptr, ptr, oms);
munmap(ptr, osz);
ptr = new_ptr;
}
#else

#if LUAJIT_USE_ASAN_HARDENING
void *old_ptr = ptr;
size_t nms = nsz;
osz = asan_get_size(old_ptr, POISON_SIZE);
nsz = ALIGN_SIZE(nsz + TOTAL_REDZONE_SIZE, LJ_PAGESIZE);
ptr = (void *)((char *)(ptr) - REDZONE_SIZE);
#endif
ptr = mremap(ptr, osz, nsz, flags);
#if LUAJIT_USE_ASAN_HARDENING
if (ptr != MFAIL) {
ASAN_POISON_MEMORY_REGION((void *)((char *)(old_ptr) - REDZONE_SIZE), osz);
ptr = mark_memory_region(ptr, nms, nsz);
}
#endif
#endif
errno = olderr;
return ptr;
}
Expand Down Expand Up @@ -837,7 +996,13 @@ static int has_segment_link(mstate m, msegmentptr ss)

static void *direct_alloc(size_t nb)
{
#if LUAJIT_USE_ASAN_HARDENING
nb += TOTAL_REDZONE_SIZE;
#endif
size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK);
#if LUAJIT_USE_ASAN_HARDENING
mmsize -= TOTAL_REDZONE_SIZE;
#endif
if (LJ_LIKELY(mmsize > nb)) { /* Check for wrap around 0 */
char *mm = (char *)(DIRECT_MMAP(mmsize));
if (mm != CMFAIL) {
Expand Down Expand Up @@ -1006,7 +1171,13 @@ static void *alloc_sys(mstate m, size_t nb)

{
size_t req = nb + TOP_FOOT_SIZE + SIZE_T_ONE;
#if LUAJIT_USE_ASAN_HARDENING
req += TOTAL_REDZONE_SIZE;
#endif
size_t rsize = granularity_align(req);
#if LUAJIT_USE_ASAN_HARDENING
rsize -= TOTAL_REDZONE_SIZE;
#endif
if (LJ_LIKELY(rsize > nb)) { /* Fail if wraps around zero */
char *mp = (char *)(CALL_MMAP(rsize));
if (mp != CMFAIL) {
Expand Down Expand Up @@ -1238,6 +1409,9 @@ static void *tmalloc_small(mstate m, size_t nb)
void *lj_alloc_create(void)
{
size_t tsize = DEFAULT_GRANULARITY;
#if LUAJIT_USE_ASAN_HARDENING
tsize -= TOTAL_REDZONE_SIZE;
#endif
char *tbase;
INIT_MMAP();
tbase = (char *)(CALL_MMAP(tsize));
Expand Down
Loading

0 comments on commit bc27b62

Please sign in to comment.