DF-5710 / harness_strncpy.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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | /* Differential + guard-page rig for the audited file itself: * /usr/src/sys/libkern/strncpy.c (compiled UNMODIFIED, -Dstrncpy=kstrncpy) * * A. Semantics differential vs libc strncpy: byte-exact dst image + return * value over the full (n, srclen) matrix 0..64 plus 100k random cases * (random content incl. embedded NULs). * B. Pad-count math: for every (n, srclen), exactly n bytes are written: * [0,k)=src, [k,n)=NUL-pad, canary after n untouched, 0<n. * C. Guard-page src: with a NUL inside the window the impl must stop * reading src (pad loop may not re-read src); with no NUL and n equal * to the readable window it must read exactly n bytes and no more. * D. Guard-page dst: n == space -> exactly n writes, no fault; * n == space+1 -> fault (bound is exactly n; over-n is caller misuse). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <sys/mman.h> #include <sys/wait.h> #include <unistd.h> char *kstrncpy(char *dst, const char *src, size_t n); /* kernel source */ static int failures; #define CHECK(cond, ...) do { \ if (!(cond)) { failures++; printf("FAIL: " __VA_ARGS__); printf("\n"); } \ } while (0) #define MAXN 80 #define PAGESZ 4096 static void part_a(void) { size_t n, slen, i; char src[2 * MAXN], d1[MAXN], d2[MAXN], *r1, *r2; for (n = 0; n <= 64; n++) { for (slen = 0; slen <= 64; slen++) { memset(src, 'S', sizeof(src)); src[slen] = 0; /* NUL at slen */ memset(d1, 0x5A, sizeof(d1)); memset(d2, 0x5A, sizeof(d2)); r1 = kstrncpy(d1, src, n); r2 = strncpy(d2, src, n); CHECK(r1 == d1 && r2 == d2, "return n=%zu slen=%zu", n, slen); CHECK(memcmp(d1, d2, sizeof(d1)) == 0, "image n=%zu slen=%zu", n, slen); } } /* random fuzz incl. embedded NULs */ srandom(4242); for (i = 0; i < 100000; i++) { size_t len = (size_t)(random() % (2 * MAXN)); n = (size_t)(random() % 65); for (slen = 0; slen < len; slen++) src[slen] = (char)(1 + random() % 255); /* no NULs */ src[len] = 0; if (len > n && (random() & 1)) /* sometimes embed a NUL early */ src[random() % len] = 0; memset(d1, 0x5A, sizeof(d1)); memset(d2, 0x5A, sizeof(d2)); kstrncpy(d1, src, n); strncpy(d2, src, n); CHECK(memcmp(d1, d2, sizeof(d1)) == 0, "fuzz i=%zu n=%zu", i, n); } printf("part A (differential vs libc, matrix+fuzz): %s\n", failures ? "FAIL" : "identical"); } static void part_b(void) { size_t n, slen, k; char src[3 * MAXN], dst[MAXN + 16], ref[MAXN + 16]; for (n = 1; n <= 64; n++) { for (slen = 0; slen <= 72; slen++) { size_t kmax = slen < n ? slen : n; memset(src, 'S', sizeof(src)); src[slen] = 0; memset(dst, 0xA5, sizeof(dst)); memcpy(ref, dst, sizeof(ref)); kstrncpy(dst, src, n); /* expected: [0,kmax)=src bytes, [kmax,n)=0, rest canary */ for (k = 0; k < n; k++) { char want = k < kmax ? src[k] : 0; CHECK(dst[k] == want, "pad-math n=%zu slen=%zu k=%zu got %02x want %02x", n, slen, k, (unsigned char)dst[k], (unsigned char)want); } for (k = n; k < sizeof(dst); k++) CHECK((unsigned char)dst[k] == 0xA5, "canary n=%zu slen=%zu k=%zu (wrote past n)", n, slen, k); (void)ref; } } printf("part B (pad-count: exactly n bytes, canary intact): %s\n", failures ? "FAIL" : "exact"); } /* run fn in a forked child; return 0 clean exit, 1 SIGSEGV, -1 other */ static int fork_run(void (*fn)(void *), void *arg) { pid_t pid = fork(); int st; if (pid == 0) { fn(arg); _exit(0); } waitpid(pid, &st, 0); if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) return 1; if (WIFEXITED(st) && WEXITSTATUS(st) == 0) return 0; return -1; } struct gp_args { int mode; /* 0: src-NUL-inside, 1: src-full-window, 2: dst-exact, * 3: dst-overrun(self-check), 4: src-overrun(self-check) */ size_t n; }; static char * gp_map(size_t pages) { char *m = mmap(NULL, pages * PAGESZ, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (m == MAP_FAILED) { perror("mmap"); exit(2); } if (mprotect(m + (pages - 1) * PAGESZ, PAGESZ, PROT_NONE) != 0) { perror("mprotect"); exit(2); } return m; } static void gp_fn(void *v) { struct gp_args *a = v; char *m = gp_map(2); size_t room = PAGESZ; if (a->mode == 0) { /* NUL inside window; pad must not read src */ char *src = m + room - 24; char dst[64]; memset(src, 'Q', 24); src[4] = 0; /* NUL at src[4], 19 bytes to guard */ memset(dst, 0x5A, sizeof(dst)); kstrncpy(dst, src, 40); /* n=40 >> bytes-to-guard(19) */ _exit(memcmp(dst, "QQQQ", 4) == 0 && dst[5] == 0 ? 0 : 3); } if (a->mode == 1) { /* no NUL, n == readable bytes exactly */ char *src = m; /* fill whole page non-NUL */ char dst[PAGESZ]; memset(src, 'Q', room); kstrncpy(dst, src, room); /* reads exactly n, must not fault */ _exit(dst[room - 1] == 'Q' ? 0 : 3); } if (a->mode == 2) { /* dst exactly n bytes before guard */ char *dst = m + room - 40; const char *src = "hello world"; kstrncpy(dst, src, 40); /* writes exactly 40, no fault */ _exit(dst[39] == 0 ? 0 : 3); } if (a->mode == 3) { /* SELF-CHECK: dst n = room+1 -> MUST fault */ char *dst = m + room - 40; kstrncpy(dst, "x", 41); /* 41 > 40 bytes to guard */ _exit(0); /* reaching here = no fault = WRONG */ } if (a->mode == 4) { /* SELF-CHECK: src read past n -> fault */ char *src; /* only 4 readable bytes, no NUL */ char dst[64]; memset(m, 'Q', room); /* fresh anon mem is ZEROED: fill! */ src = m + room - 4; kstrncpy(dst, src, 8); /* reads 8 > 4 -> fault expected */ _exit(0); } _exit(9); } static void part_cd(void) { struct gp_args a; int r; a.n = 0; a.mode = 0; r = fork_run(gp_fn, &a); CHECK(r == 0, "C0 src-guard NUL-inside n=40 (r=%d)", r); printf("part C0 src-guard, NUL in window (pad must not read src): %s\n", r == 0 ? "no fault (correct)" : "FAIL"); a.mode = 1; r = fork_run(gp_fn, &a); CHECK(r == 0, "C1 src-guard full-window n=4096 (r=%d)", r); printf("part C1 src-guard, no NUL, n==window: %s\n", r == 0 ? "no fault (reads exactly n)" : "FAIL"); a.mode = 2; r = fork_run(gp_fn, &a); CHECK(r == 0, "D0 dst-guard n==space (r=%d)", r); printf("part D0 dst-guard, n==space: %s\n", r == 0 ? "no fault (writes exactly n)" : "FAIL"); a.mode = 3; r = fork_run(gp_fn, &a); CHECK(r == 1, "D1 dst-guard n==space+1 must fault (r=%d)", r); printf("part D1 dst-guard, n==space+1 (caller misuse): %s\n", r == 1 ? "SIGSEGV (bound is exactly n)" : "NO FAULT (wrong)"); a.mode = 4; r = fork_run(gp_fn, &a); CHECK(r == 1, "D2 src-guard n>window must fault (r=%d)", r); printf("part D2 src-guard, n==window+1 (caller misuse): %s\n", r == 1 ? "SIGSEGV (reads at most n)" : "NO FAULT (wrong)"); } int main(void) { printf("rig: kernel %s compiled from /usr/src/sys/libkern/strncpy.c\n", "strncpy"); part_a(); part_b(); part_cd(); printf("\n%s (%d failures)\n", failures ? "RIG FAILURES" : "ALL PARTS PASS: implementation is libc-identical, reads<=n, " "writes==n, pad-count exact", failures); return failures ? 1 : 0; } |