diff --git a/internal/cbm/result_spill.c b/internal/cbm/result_spill.c index b75f06fb5..b79dc0e6c 100644 --- a/internal/cbm/result_spill.c +++ b/internal/cbm/result_spill.c @@ -21,6 +21,24 @@ #include "foundation/log.h" #include "foundation/mem_core.h" +/* The park writes a result as an opaque image: the record header (a struct + * copy) and the compacted arena block. Both carry padding bytes no code ever + * wrote -- inside structs, between objects -- and MemorySanitizer tracks + * that mark through the compaction's memcpy, so it refuses the fwrite of an + * image that is read back whole and never interpreted byte by byte (CI MSan + * lane on #2202: offset 4087, then 6714, of a 6,952-byte block). Under MSan + * the image is declared defined right before the write; every other build + * compiles this to nothing. */ +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#include +#define SPILL_IMAGE_DEFINED(p, n) __msan_unpoison((p), (n)) +#endif +#endif +#ifndef SPILL_IMAGE_DEFINED +#define SPILL_IMAGE_DEFINED(p, n) ((void)0) +#endif + #include #include #include @@ -133,6 +151,8 @@ bool cbm_result_spill_park(cbm_result_spill_t *sp, int writer, int slot, CBMFile hdr.old_base = (uint64_t)(uintptr_t)result->arena.blocks[0]; hdr.header = *result; hdr.header.cached_tree = NULL; /* never on disk: the loader gets no tree */ + SPILL_IMAGE_DEFINED(&hdr, sizeof(hdr)); + SPILL_IMAGE_DEFINED(result->arena.blocks[0], hdr.block_len); cbm_mutex_lock(&f->mu); uint64_t offset = f->end; bool ok = SPILL_SEEK(f->fp, offset) == 0 && fwrite(&hdr, sizeof(hdr), 1, f->fp) == 1 && diff --git a/src/foundation/arena.c b/src/foundation/arena.c index 20d604de0..82a2f2af6 100644 --- a/src/foundation/arena.c +++ b/src/foundation/arena.c @@ -45,6 +45,15 @@ void cbm_arena_init_exact(CBMArena *a, size_t bytes) { size_t block = (bytes + ARENA_ALIGN) & ~(size_t)ARENA_ALIGN; cbm_arena_init_sized(a, block); a->grow_size = CBM_ARENA_DEFAULT_BLOCK_SIZE; + /* An exact block is a compacted result's image and may be written to + * disk whole (result_spill): the alignment gaps between objects and the + * padding inside structs are never written by the copy, and MemorySanitizer + * refuses an fwrite of uninitialized bytes (CI MSan lane on #2202, offset + * 4087 of a 6,952-byte block). Zeroed once here, every byte of the image + * is defined; the cost is one pass over memory the copy is about to touch. */ + if (a->blocks[0]) { + memset(a->blocks[0], 0, a->block_sizes[0]); + } } static int arena_grow(CBMArena *a, size_t min_size) {