Skip to content

Commit

Permalink
Manual poisoning for pool/heap when using address sanitizer (#4584)
Browse files Browse the repository at this point in the history
So the address sanitizer can better detect issues

Including adding an address and undefined behavior sanitizer CI
build and a couple of minor fixes identified by the address
sanitizer.
  • Loading branch information
dipinhora authored Jan 22, 2025
1 parent 959e84b commit f8a2fee
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 11 deletions.
24 changes: 23 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,9 @@ jobs:
- image: ghcr.io/ponylang/ponyc-ci-x86-64-unknown-linux-ubuntu24.04-builder:20250115
debugger: lldb
directives: runtimestats
- image: ghcr.io/ponylang/ponyc-ci-x86-64-unknown-linux-ubuntu24.04-builder:20250115
debugger: lldb
directives: pool_memalign,address_sanitizer,undefined_behavior_sanitizer

name: use ${{ matrix.directives }}
container:
Expand All @@ -476,15 +479,34 @@ jobs:
path: build/libs
key: libs-${{ matrix.image }}-${{ hashFiles('Makefile', 'CMakeLists.txt', 'lib/CMakeLists.txt') }}
- name: Build Debug Runtime
if: matrix.directives != 'pool_memalign,address_sanitizer,undefined_behavior_sanitizer'
run: |
make configure arch=x86-64 config=debug use=${{ matrix.directives }}
make build config=debug
- name: Test with Debug Runtime
if: matrix.directives != 'pool_memalign,address_sanitizer,undefined_behavior_sanitizer'
run: make test-ci config=debug usedebugger='${{ matrix.debugger }}'
- name: Build Release Runtime
if: matrix.directives != 'pool_memalign,address_sanitizer,undefined_behavior_sanitizer'
run: |
make configure arch=x86-64 config=release use=${{ matrix.directives }}
make build config=release
- name: Test with Release Runtime
if: matrix.directives != 'pool_memalign,address_sanitizer,undefined_behavior_sanitizer'
run: make test-ci config=release usedebugger='${{ matrix.debugger }}'
- name: Build Debug Runtime (sanitizers)
if: matrix.directives == 'pool_memalign,address_sanitizer,undefined_behavior_sanitizer'
run: |
ASAN_OPTIONS=detect_leaks=0:external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer UBSAN_OPTIONS=external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer make configure arch=x86-64 config=debug use=${{ matrix.directives }}
ASAN_OPTIONS=detect_leaks=0:external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer UBSAN_OPTIONS=external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer make build config=debug
- name: Test with Debug Runtime (sanitizers)
if: matrix.directives == 'pool_memalign,address_sanitizer,undefined_behavior_sanitizer'
run: ASAN_OPTIONS=detect_leaks=0:external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer UBSAN_OPTIONS=external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer make test-ci config=debug
- name: Build Release Runtime (sanitizers)
if: matrix.directives == 'pool_memalign,address_sanitizer,undefined_behavior_sanitizer'
run: |
ASAN_OPTIONS=detect_leaks=0:external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer UBSAN_OPTIONS=external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer make configure arch=x86-64 config=release use=${{ matrix.directives }}
ASAN_OPTIONS=detect_leaks=0:external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer UBSAN_OPTIONS=external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer make build config=release
- name: Test with Release Runtime (sanitizers)
if: matrix.directives == 'pool_memalign,address_sanitizer,undefined_behavior_sanitizer'
run: ASAN_OPTIONS=detect_leaks=0:external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer UBSAN_OPTIONS=external_symbolizer_path=$PWD/build/libs/bin/llvm-symbolizer make test-ci config=release
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ endif()
if(PONY_USE_ADDRESS_SANITIZER)
set(PONY_OUTPUT_SUFFIX "${PONY_OUTPUT_SUFFIX}-address_sanitizer")
list(APPEND PONY_SANITIZERS_ENABLED "address")
add_compile_options(-DUSE_ADDRESS_SANITIZER)
add_link_options(-DUSE_ADDRESS_SANITIZER)
endif()

if(PONY_USE_UNDEFINED_BEHAVIOR_SANITIZER)
Expand Down
4 changes: 4 additions & 0 deletions src/libponyc/codegen/genfun.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ static void make_signature(compile_t* c, reach_type_t* t,
size_t tparam_size = count * sizeof(LLVMTypeRef);

if(message_type)
{
if (tparam_size == 0)
tparam_size = sizeof(LLVMTypeRef);
tparam_size += tparam_size + (2 * sizeof(LLVMTypeRef));
}

LLVMTypeRef* tparams = (LLVMTypeRef*)ponyint_pool_alloc_size(tparam_size);
LLVMTypeRef* mparams = NULL;
Expand Down
1 change: 1 addition & 0 deletions src/libponyrt/gc/cycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,7 @@ void ponyint_cycle_terminate(pony_ctx_t* ctx)
ponyint_become(ctx, cycle_detector);
final(ctx, cycle_detector);
ponyint_destroy(ctx, cycle_detector);
ponyint_become(ctx, NULL);
cycle_detector = NULL;
}

Expand Down
60 changes: 58 additions & 2 deletions src/libponyrt/mem/heap.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
#include <platform.h>
#include <dtrace.h>

#ifdef USE_ADDRESS_SANITIZER
#include <sanitizer/asan_interface.h>
#endif

typedef struct chunk_t
{
// used for pointer tagging
Expand Down Expand Up @@ -373,7 +377,7 @@ static void destroy_small(chunk_t* chunk, uint32_t mark)

char* m = get_m(chunk);
ponyint_pagemap_set(m, NULL, NULL);
#ifndef PONY_NDEBUG
#if !defined(PONY_NDEBUG) && !defined(USE_ADDRESS_SANITIZER)
memset(m, 0, sizeof(block_t));
#endif
POOL_FREE(block_t, m);
Expand All @@ -394,7 +398,7 @@ static void destroy_large(chunk_t* chunk, uint32_t mark)

if(m != NULL)
{
#ifndef PONY_NDEBUG
#if !defined(PONY_NDEBUG) && !defined(USE_ADDRESS_SANITIZER)
memset(m, 0, chunk->large.size);
#endif
ponyint_pool_free_size(chunk->large.size, m);
Expand Down Expand Up @@ -471,6 +475,24 @@ static size_t sweep_small(chunk_t* chunk, chunk_t** avail, chunk_t** full, chunk
// run finalisers for freed slots
final_small(chunk, FORCE_NO_FINALISERS);

#if defined(USE_ADDRESS_SANITIZER)
uint32_t empty_slots = chunk->small.slots;
void* p = NULL;
uint64_t bit = 0;

// if there's an empty slot to poison memory for
while(empty_slots != 0)
{
bit = __pony_ctz(empty_slots);
p = get_m(chunk) + (bit << HEAP_MINBITS);

ASAN_POISON_MEMORY_REGION(p, size);

// clear bit for the slot we just poisoned
empty_slots &= (empty_slots - 1);
}
#endif

chunk->next = *avail;
*avail = chunk;
}
Expand Down Expand Up @@ -693,6 +715,10 @@ void* ponyint_heap_alloc_small(pony_actor_t* actor, heap_t* heap,
n = (chunk_t*) POOL_ALLOC(chunk_t);
n->m = (char*) POOL_ALLOC(block_t);
ponyint_pagemap_set(get_m(n), n, actor);

#if defined(USE_ADDRESS_SANITIZER)
ASAN_POISON_MEMORY_REGION(get_m(n), sizeof(block_t));
#endif
}

// we should always have a chunk and m at this point
Expand Down Expand Up @@ -732,6 +758,11 @@ void* ponyint_heap_alloc_small(pony_actor_t* actor, heap_t* heap,
actor->actorstats.heap_num_allocated++;
actor->actorstats.heap_mem_used += SIZECLASS_SIZE(sizeclass);
#endif

#if defined(USE_ADDRESS_SANITIZER)
ASAN_UNPOISON_MEMORY_REGION(m, SIZECLASS_SIZE(sizeclass));
#endif

heap->used += SIZECLASS_SIZE(sizeclass);
return m;
}
Expand Down Expand Up @@ -803,6 +834,10 @@ void* ponyint_heap_alloc_large(pony_actor_t* actor, heap_t* heap, size_t size,
chunk->large.size = size;
chunk->m = (char*) ponyint_pool_alloc_size(size);
large_pagemap(get_m(chunk), size, chunk, actor);

#if defined(USE_ADDRESS_SANITIZER)
ASAN_POISON_MEMORY_REGION(get_m(chunk), size);
#endif
}

// we should always have a chunk and m of the right size at this point
Expand Down Expand Up @@ -831,6 +866,10 @@ void* ponyint_heap_alloc_large(pony_actor_t* actor, heap_t* heap, size_t size,
heap->large = chunk;
heap->used += chunk->large.size;

#if defined(USE_ADDRESS_SANITIZER)
ASAN_UNPOISON_MEMORY_REGION(get_m(chunk), chunk->large.size);
#endif

return get_m(chunk);
}

Expand Down Expand Up @@ -1153,6 +1192,23 @@ void ponyint_heap_endgc(heap_t* heap
heap->recyclable[i] = temp_heap->recyclable[i];
}

#if defined(USE_ADDRESS_SANITIZER)
for(int i = 0; i < HEAP_RECYCLE_SIZECLASSES; i++)
{
chunk_t* sc = heap->recyclable[i];
while (sc != NULL)
{
if(get_chunk_is_small_chunk(sc))
{
ASAN_POISON_MEMORY_REGION(get_m(sc), sizeof(block_t));
} else {
ASAN_POISON_MEMORY_REGION(get_m(sc), sc->large.size);
}
sc = sc->next;
}
}
#endif

// Foreign object sizes will have been added to heap->used already. Here we
// add local object sizes as well and set the next gc point for when memory
// usage has increased.
Expand Down
53 changes: 52 additions & 1 deletion src/libponyrt/mem/pool.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@

#ifdef POOL_USE_DEFAULT

#ifdef USE_ADDRESS_SANITIZER
#include <sanitizer/asan_interface.h>
#endif

#ifdef USE_VALGRIND
#include <valgrind/valgrind.h>
#include <valgrind/helgrind.h>
Expand Down Expand Up @@ -659,11 +663,28 @@ static void* pool_alloc_pages(size_t size)

// We have no free blocks big enough.
if(size >= POOL_MMAP)
return ponyint_virt_alloc(size);
{
p = ponyint_virt_alloc(size);

#ifdef USE_ADDRESS_SANITIZER
ASAN_POISON_MEMORY_REGION(p, size);
#endif

return p;
}

pool_block_t* block = (pool_block_t*)ponyint_virt_alloc(POOL_MMAP);

#ifdef USE_ADDRESS_SANITIZER
ASAN_POISON_MEMORY_REGION(block, POOL_MMAP);
#endif

size_t rem = POOL_MMAP - size;

#ifdef USE_ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(block, sizeof(pool_block_t));
#endif

block->size = rem;
block->next = NULL;
block->prev = NULL;
Expand All @@ -682,6 +703,10 @@ static void pool_free_pages(void* p, size_t size)
// TODO: ???
}

#ifdef USE_ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(p, sizeof(pool_block_t));
#endif

pool_block_t* block = (pool_block_t*)p;
block->prev = NULL;
block->next = NULL;
Expand Down Expand Up @@ -825,6 +850,10 @@ void* ponyint_pool_alloc(size_t index)
void* p = pool_get(pool, index);
TRACK_ALLOC(p, POOL_MIN << index);

#ifdef USE_ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(p, POOL_MIN << index);
#endif

#ifdef USE_VALGRIND
VALGRIND_ENABLE_ERROR_REPORTING;
VALGRIND_HG_CLEAN_MEMORY(p, POOL_SIZE(index));
Expand All @@ -844,6 +873,11 @@ void ponyint_pool_free(size_t index, void* p)
pony_assert(index < POOL_COUNT);
TRACK_FREE(p, POOL_MIN << index);

#ifdef USE_ADDRESS_SANITIZER
ASAN_POISON_MEMORY_REGION(p, POOL_MIN << index);
ASAN_UNPOISON_MEMORY_REGION(p, sizeof(pool_central_t));
#endif

pool_local_t* thread = &pool_local[index];
pool_global_t* global = &pool_global[index];

Expand Down Expand Up @@ -890,6 +924,10 @@ void* ponyint_pool_alloc_size(size_t size)
size = ponyint_pool_adjust_size(size);
void* p = pool_alloc_size(size);

#ifdef USE_ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(p, size);
#endif

return p;
}

Expand All @@ -900,6 +938,10 @@ static void pool_free_size(size_t size, void* p)
VALGRIND_DISABLE_ERROR_REPORTING;
#endif

#ifdef USE_ADDRESS_SANITIZER
ASAN_POISON_MEMORY_REGION(p, size);
#endif

TRACK_FREE(p, size);
pool_free_pages(p, size);

Expand Down Expand Up @@ -952,6 +994,10 @@ void* ponyint_pool_realloc_size(size_t old_size, size_t new_size, void* p)
}

new_p = pool_alloc_size(new_adj_size);

#ifdef USE_ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(new_p, new_adj_size);
#endif
}

memcpy(new_p, p, old_size < new_size ? old_size : new_size);
Expand All @@ -977,6 +1023,11 @@ void ponyint_pool_thread_cleanup()
pool_push(thread, global);

pool_item_t* item = (pool_item_t*)thread->start;

#ifdef USE_ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(item, sizeof(pool_central_t));
#endif

thread->start += global->size;
item->next = thread->pool;
thread->pool = item;
Expand Down
31 changes: 30 additions & 1 deletion src/libponyrt/mem/pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@
PONY_EXTERN_C_BEGIN

/* Because of the way free memory is reused as its own linked list container,
* the minimum allocation size is 32 bytes.
* the minimum allocation size is 32 bytes for the default pool implementation
* and 16 bytes for the memalign pool implementation.
*/

#ifndef POOL_USE_DEFAULT
#define POOL_MIN_BITS 4
#else
#define POOL_MIN_BITS 5
#endif

#define POOL_MAX_BITS 20
#define POOL_ALIGN_BITS 10

Expand All @@ -43,6 +49,7 @@ size_t ponyint_pool_used_size(size_t index);

size_t ponyint_pool_adjust_size(size_t size);

#ifdef POOL_USE_DEFAULT
#define POOL_INDEX(SIZE) \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 0)), 0, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 1)), 1, \
Expand All @@ -62,6 +69,28 @@ size_t ponyint_pool_adjust_size(size_t size);
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 15)), 15, \
EXPR_NONE \
))))))))))))))))
#else
#define POOL_INDEX(SIZE) \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 0)), 0, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 1)), 1, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 2)), 2, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 3)), 3, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 4)), 4, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 5)), 5, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 6)), 6, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 7)), 7, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 8)), 8, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 9)), 9, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 10)), 10, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 11)), 11, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 12)), 12, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 13)), 13, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 14)), 14, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 15)), 15, \
__pony_choose_expr(SIZE <= (1 << (POOL_MIN_BITS + 16)), 16, \
EXPR_NONE \
)))))))))))))))))
#endif

#define POOL_ALLOC(TYPE) \
(TYPE*) ponyint_pool_alloc(POOL_INDEX(sizeof(TYPE)))
Expand Down
Loading

0 comments on commit f8a2fee

Please sign in to comment.