DF-5701 / trunc_harness.c
/* * Truncation harness: bcmp.c:58 `int len = length;` (size_t -> int). * * Demonstrates two distinct breaks for lengths >= 2^31 on LP64: * (A) length == 2^32 -> len == 0 -> returns 0 "equal" immediately * (bcmp.c:62-63) with ZERO bytes compared. * (B) length == 2^32 + k -> len == k -> compares k bytes, returns * "equal" for buffers that differ beyond k. * (C) length == 2^31 (0x80000000) -> len == INT_MIN; the loop counter * `len -= 8` (bcmp.c:88/114) overflows * (UB) and wraps positive -> bcmp walks * FORWARD unbounded -> hits the guard page * -> SIGSEGV. (Child process per case.) * * Buffers are placed in guarded pages so (C) is contained to the child. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> #include <sys/wait.h> #include <unistd.h> int df_bcmp(const void *, const void *, size_t); #define PS 4096 static int run_child(size_t length, unsigned char *b1, unsigned char *b2) { pid_t pid = fork(); if (pid == 0) { volatile int r = df_bcmp(b1, b2, length); printf(" child: df_bcmp(b1,b2,0x%zx) = %d (survived)\n", length, r); _exit(0); } int st = 0; waitpid(pid, &st, 0); if (WIFSIGNALED(st)) return -WTERMSIG(st); return 0; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); unsigned char *a1 = mmap(NULL, 3 * PS, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); unsigned char *a2 = mmap(NULL, 3 * PS, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (a1 == MAP_FAILED || a2 == MAP_FAILED) { perror("mmap"); return 2; } if (mprotect(a1, PS, PROT_NONE) || mprotect(a1 + 2 * PS, PS, PROT_NONE) || mprotect(a2, PS, PROT_NONE) || mprotect(a2 + 2 * PS, PS, PROT_NONE)) { perror("mprotect"); return 2; } unsigned char *b1 = a1 + PS, *b2 = a2 + PS; memset(b1, 0x41, PS); memset(b2, 0x41, PS); printf("== (A) length = 2^32 : expect FALSE-EQUAL (returns 0, compared nothing)\n"); int r = df_bcmp(b1, b2, (size_t)1 << 32); printf(" df_bcmp(b1,b2,2^32) = %d -> %s\n", r, r == 0 ? "FALSE EQUAL (bug)" : "ok"); int badA = (r == 0); printf("== (B) length = 2^32+8 : compare 8 bytes only, differ at byte 8\n"); b2[8] = 0x42; /* differ inside first 16 bytes, beyond len==8 window */ r = df_bcmp(b1, b2, ((size_t)1 << 32) + 8); printf(" df_bcmp(b1,b2,2^32+8) = %d -> %s (bcmp.c:88 loop ran with len=8)\n", r, r == 0 ? "FALSE EQUAL (bug)" : "ok"); int badB = (r == 0); printf("== (C) length = 2^31 : int overflow in `len -= 8` -> unbounded forward walk\n"); b2[8] = 0x41; /* undo (B)'s mutation so both pages are identical again */ int sig = run_child((size_t)1 << 31, b1, b2); printf(" child outcome: %s\n", sig == -11 ? "SIGSEGV in df_bcmp (unbounded walk proven)" : sig == 0 ? "survived (no crash)" : "other signal"); int badC = (sig == -11); printf("RESULT: %s%s%s (A=%d B=%d C=%d)\n", badA ? "false-equal@2^32 " : "", badB ? "false-equal@2^32+8 " : "", badC ? "crash@2^31" : "", badA, badB, badC); return (badA || badB || badC) ? 0 : 1; } |