DF-5706 / guard_harness.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | /* * Guard-page containment rig for sys/libkern/memcchr.c (compiled VERBATIM * as df_memcchr from the pristine kernel body, lines 36-117 byte-identical). * The usable data page is flanked by PROT_NONE guard pages on BOTH sides. * * Property under test (memcchr.c:83-91 loads): * every longword load is naturally aligned (8 divides the page size) and * its granule holds >= 1 in-bounds byte of [begin, begin+n) => the * over-read (up to 7 bytes below begin / past end) NEVER leaves the * buffer's valid page. * * Any load escaping the valid page => SIGSEGV => the harness dies with the * current case recorded in journal.txt (I/O-line-buffered). Clean exit = * containment held over the whole sweep. * * Content modes additionally verify the done-filter (memcchr.c:114): * 0: all bytes = c -> must return NULL * 1: mismatch at the LAST in-range byte -> must return &buf[n-1] * 2: canaries != c everywhere OUTSIDE * [begin, end), all c inside -> must return NULL * (this is the killer case: the word walk's last granule load reads * non-c bytes past end, TESTBYTE walks past end, done-filter must * reject it and return NULL) * 3: random content (expected recomputed) -> must match reference * 4: mismatch at the FIRST in-range byte -> must return begin */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> #include <unistd.h> void *df_memcchr(const void *, int, size_t); #define PS 4096 static unsigned char *P; /* the single usable page, guarded both sides */ static FILE *journal; static char jline[256]; static uint64_t s_state = 0x243f6a8885a308d3ULL; static uint64_t prng(void) { s_state ^= s_state << 13; s_state ^= s_state >> 7; s_state ^= s_state << 17; return s_state; } static long cases, nullrets, ptrrets, wrong; static void jwrite(void) __attribute__((__unused__)); static void fill(int c, int mode, int s, int l) { int i; for (i = 0; i < PS; i++) P[i] = (mode == 3) ? (unsigned char)prng() : (unsigned char)c; if (mode == 2) { for (i = 0; i < s; i++) P[i] = (unsigned char)~c; for (i = s + l; i < PS; i++) P[i] = (unsigned char)~c; } if (mode == 1 && l > 0) P[s + l - 1] = (unsigned char)~c; /* last in-range byte */ if (mode == 4 && l > 0) P[s] = (unsigned char)~c; /* first in-range byte */ } static void one(int s, int l, int mode, int c) { void *r, *exp; snprintf(jline, sizeof(jline), "s=%d l=%d mode=%d c=%d\n", s, l, mode, c); fill(c, mode, s, l); r = df_memcchr(P + s, c, (size_t)l); switch (mode) { case 0: case 2: exp = NULL; break; case 1: exp = (l > 0) ? (void *)(P + s + l - 1) : NULL; break; case 4: exp = (l > 0) ? (void *)(P + s) : NULL; break; default: { int i; exp = NULL; for (i = 0; i < l; i++) if (P[s + i] != (unsigned char)c) { exp = P + s + i; break; } } } cases++; if (r == NULL) nullrets++; else ptrrets++; if (r != exp) { printf("WRONG-RETURN s=%d l=%d mode=%d c=%d: got %p want %p\n", s, l, mode, c, r, exp); wrong++; } } static void jwrite(void) { fclose(journal); fprintf(stderr, "DIED IN: %s", jline); } int main(void) { unsigned char *a = mmap(NULL, 3 * PS, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); int lens[64], nl, s, k, mode; if (a == MAP_FAILED) { perror("mmap"); return 2; } P = a + PS; if (mprotect(a, PS, PROT_NONE) || mprotect(a + 2 * PS, PS, PROT_NONE)) { perror("mprotect"); return 2; } journal = fopen("journal.txt", "w"); if (!journal) { perror("fopen"); return 2; } setvbuf(journal, NULL, _IOLBF, 0); for (s = 0; s < PS; s++) { nl = 0; for (k = 0; k <= 24; k++) /* small lengths incl. 0 */ lens[nl++] = k; for (k = 0; k <= 9; k++) { /* page-edge-critical: end lands at PS-k */ int l = PS - s - k; if (l >= 0 && l <= PS - s) lens[nl++] = l; } if (137 <= PS - s) lens[nl++] = 137; if (1024 <= PS - s) lens[nl++] = 1024; for (k = 0; k < nl; k++) { int l = lens[k]; if (l < 0 || l > PS - s) continue; for (mode = 0; mode < 5; mode++) { one(s, l, mode, 0xff); /* ext2's c */ if ((s & 63) == 0) one(s, l, mode, 0x00); } } if ((s & 1023) == 1023) fprintf(journal, "progress s=%d cases=%ld\n", s, cases); } fprintf(journal, "done cases=%ld\n", cases); fclose(journal); printf("guard sweep: %ld calls (NULL=%ld ptr=%ld), wrong returns=%ld\n", cases, nullrets, ptrrets, wrong); if (wrong != 0) { printf("RESULT: FAIL - done-filter returned a bad pointer\n"); return 1; } printf("RESULT: PASS - no load escaped the valid page (both-side" " guards); done-filter never leaked a past-end mismatch\n"); return 0; } |