AtlatestRepositorysigil-vt
1
/* Does the sanitizer build ACTUALLY detect a heap over-read?2
*3
* CONTRACT (fuzz.sh depends on this):4
* killed by the sanitizer, printing an AddressSanitizer diagnostic5
* = CAUGHT. The toolchain's ASan is real. This process never6
* regains control, so it can NEVER exit 0 on success.7
* exit 1 = NOT caught. The build is blind, and any clean fuzz run from8
* it is meaningless.9
*10
* fuzz.sh matches the DIAGNOSTIC, not the exit status: a bare non-zero exit11
* could equally mean the binary failed to launch, which must never read as a12
* pass.13
*14
* WHY THIS EXISTS (t-d4c7). The M2 gate reported "5,000,000 iterations,15
* ASan/UBSan clean" for months while ASan WAS NOT PRESENT AT ALL. The pinned16
* zig ships no ASan runtime: `zig cc -fsanitize=address` alone fails to link17
* (undefined __asan_report_load4), and `-fsanitize=address,undefined` links but18
* SILENTLY DROPS ASan — the binary contains zero __asan symbols. Only UBSan19
* survived. A real heap over-read shipped straight through that green.20
*21
* The trap that makes this hard to notice: a naive self-test PASSES anyway.22
* If the malloc is visible in the same function, UBSan's __builtin_object_size23
* check fires and you conclude "sanitizers work". They don't — you measured24
* UBSan. So the allocation here is deliberately behind a noinline function, out25
* of the compiler's static reach, exactly like sb_push. Then ONLY ASan's heap26
* redzones can catch it.27
*28
* Keep this shape. If you "simplify" the malloc back into main(), this file29
* silently starts passing on a broken toolchain again.30
*/31
#include <stdlib.h>32
#include <stdio.h>34
/* Opaque to __builtin_object_size — mirrors sb_push allocating a row. */35
__attribute__((noinline)) static int *make_block(int n) {36
int *p = (int *)malloc((size_t)n * sizeof(int));37
if (!p) exit(2);38
for (int i = 0; i < n; i++) p[i] = i;39
return p;40
}42
static long sink = 0;44
int main(void) {45
/* 160 ints = 640 bytes: the same shape as a 40-column scrollback row. */46
int *p = make_block(160);47
/* Read at int index 524 (byte 2096) — 1456 bytes past the end. This is48
* precisely the t-d4c7 over-read: a 40-wide row read at 132 columns. */49
sink ^= p[524];50
/* Reaching here means the sanitizer did NOT catch a 1456-byte heap51
* over-read. Print, then fail loudly. */52
printf("sink=%ld\n", sink);53
free(p);54
return 1;55
}