DF-0220 / csprng_force_zero.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 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | /* * csprng_force_zero.c — DF-0220 manufactured pre-reseed window (root, kmem). * * DF-0220 claims /dev/urandom & friends emit a deterministic keystream while * a per-CPU csprng_state is un-reseeded (reseed_cnt == 0). On this kernel no * userspace reader can ever observe that condition: rand_initialize() * (kern_nrandom.c:485-560, SYSINIT SI_BOOT2_POST_SMP) pre-feeds every state's * pool[0] with ~15 KB (sizeof(struct globaldata) = 14976 B via * RAND_SRC_THREAD2) *before* any read_random can run, and csprng_get_random() * always attempts a reseed (ratecheck {0,0} first-call special case, * kern_time.c:1153-1157) *before* its emit loop (subr_csprng.c:134-149 vs * :151-166) — so the first successful reseed keys the cipher before the * first byte is emitted on every state. * * To characterize WHAT the window would emit (and prove the emit path is * real), this tool uses /dev/kmem (root) to manufacture the exact * precondition tuple on one idle per-CPU state: * * reseed_cnt = 0, key = 0^32, cipher_ctx = 0^64, pool[0].bytes = 0, * last_reseed = {0,0} * * after which an UNPRIVILEGED-style /dev/urandom read pinned to that CPU * returns: block 1 = all-zero (chacha fixed point), following blocks = * deterministic chacha (sigma constants present, key re-derived from the * zero keystream). The state self-heals within seconds (rand_thread_loop * refills pool[0]; next read reseeds). * * Struct offsets from kernel.debug DWARF (identical to the DF-5766 rig): * key@0 (32 B), reseed_cnt@32, cipher_ctx@40 (64 B = input[16]), * pool[0]@104 (pool.bytes is first member), pool stride 112, * last_reseed@4256, sizeof(struct csprng_state) = 6656. * * Usage (root): * csprng_force_zero <symaddr> info * csprng_force_zero <symaddr> force <cpu> * csprng_force_zero <symaddr> check <cpu> * csprng_force_zero <symaddr> restore <cpu> (from /tmp/stateK.bak) */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdint.h> #define OFF_KEY 0 #define OFF_RCNT 32 #define OFF_CTX 40 #define CTX_LEN 64 #define OFF_POOL 104 #define POOL_STRIDE 112 #define OFF_LAST 4256 #define STATE_SZ 6656 static int kd; static int rd(uint64_t addr, void *buf, size_t len) { if (pread(kd, buf, len, (off_t)addr) != (ssize_t)len) { perror("pread kmem"); return -1; } return 0; } static int wr(uint64_t addr, const void *buf, size_t len) { if (pwrite(kd, buf, len, (off_t)addr) != (ssize_t)len) { perror("pwrite kmem"); return -1; } return 0; } static void summary(uint64_t addr, int idx) { uint8_t st[STATE_SZ]; uint64_t rc, p0, tv[2]; uint8_t key[32]; int i, keynz = 0, ctxnz = 0; if (rd(addr, st, sizeof(st)) < 0) exit(1); memcpy(key, st + OFF_KEY, 32); memcpy(&rc, st + OFF_RCNT, 8); memcpy(&p0, st + OFF_POOL, 8); memcpy(tv, st + OFF_LAST, 16); for (i = 0; i < 32; i++) if (key[i]) keynz = 1; for (i = 0; i < CTX_LEN; i++) if (st[OFF_CTX + i]) ctxnz = 1; printf("state[%d] @%#llx: reseed_cnt=%llu pool0=%llu " "key=%s ctx=%s last_reseed=%lld.%06lld\n", idx, (unsigned long long)addr, (unsigned long long)rc, (unsigned long long)p0, keynz ? "NONZERO" : "ALL-ZERO", ctxnz ? "NONZERO" : "ALL-ZERO", (long long)tv[0], (long long)tv[1]); } int main(int argc, char **argv) { uint64_t sym, base; const char *act; int cpu, i, ncpu = 6; char bakpath[128]; uint8_t zero[STATE_SZ]; uint8_t bak[STATE_SZ]; if (argc < 3) { fprintf(stderr, "usage: %s <csprng_pcpu-symaddr> " "info|force|check|restore [cpu]\n", argv[0]); return 1; } sym = strtoull(argv[1], NULL, 0); act = argv[2]; cpu = (argc > 3) ? atoi(argv[3]) : 2; kd = open("/dev/kmem", O_RDWR); if (kd < 0) { perror("open /dev/kmem (need root)"); return 1; } if (rd(sym, &base, 8) < 0) return 1; printf("csprng_pcpu = %#llx\n", (unsigned long long)base); if (strcmp(act, "info") == 0) { for (i = 0; i < ncpu; i++) summary(base + (uint64_t)STATE_SZ * i, i); return 0; } snprintf(bakpath, sizeof(bakpath), "/tmp/state%d.bak", cpu); base += (uint64_t)STATE_SZ * cpu; if (strcmp(act, "force") == 0) { memset(zero, 0, sizeof(zero)); if (rd(base, bak, STATE_SZ) < 0) return 1; { FILE *f = fopen(bakpath, "w"); if (!f) { perror("backup"); return 1; } fwrite(bak, 1, STATE_SZ, f); fclose(f); } printf("backed up state[%d] (%d B) to %s\n", cpu, STATE_SZ, bakpath); /* key, cipher_ctx, reseed_cnt, pool[0].bytes, last_reseed -> 0 */ if (wr(base + OFF_KEY, zero, 32) < 0) return 1; if (wr(base + OFF_RCNT, zero, 8) < 0) return 1; if (wr(base + OFF_CTX, zero, CTX_LEN) < 0) return 1; if (wr(base + OFF_POOL, zero, 8) < 0) return 1; if (wr(base + OFF_LAST, zero, 16) < 0) return 1; printf("FORCED state[%d] into pre-reseed condition " "(reseed_cnt=0, key=0, ctx=0, pool0.bytes=0)\n", cpu); summary(base, cpu); return 0; } if (strcmp(act, "check") == 0) { summary(base, cpu); return 0; } if (strcmp(act, "restore") == 0) { FILE *f = fopen(bakpath, "r"); if (!f) { perror(bakpath); return 1; } if (fread(bak, 1, STATE_SZ, f) != STATE_SZ) { fprintf(stderr, "short backup read\n"); return 1; } fclose(f); if (wr(base, bak, STATE_SZ) < 0) return 1; printf("restored state[%d] from backup\n", cpu); summary(base, cpu); return 0; } if (strcmp(act, "peekctx") == 0) { /* Healthy keyed states must show the chacha sigma constants * "expand 32-byte k" at input[0..3] == offset 40..55. */ uint8_t b[64]; int j; if (rd(base + OFF_CTX, b, 64) < 0) return 1; printf("state[%d] ctx@%d:", cpu, OFF_CTX); for (j = 0; j < 64; j++) { if (j % 16 == 0) printf("\n %02d:", j); printf(" %02x", b[j]); } printf("\n sigma@40: %.16s\n", (char *)b); return 0; } if (strcmp(act, "window") == 0) { /* In-process force + read: NO exec between force and read, * so no exec-time consumer can burn the zero block first. * fd is opened BEFORE the force. Prints 4 x 32B blocks. */ unsigned char blk[32]; ssize_t r; int fd, k, j; if ((fd = open("/dev/urandom", O_RDONLY)) < 0) { perror("open urandom"); return 1; } /* force (same writes as 'force') */ { FILE *f; memset(zero, 0, sizeof(zero)); if (rd(base, bak, STATE_SZ) < 0) return 1; snprintf(bakpath, sizeof(bakpath), "/tmp/state%d.bak", cpu); f = fopen(bakpath, "w"); if (f) { fwrite(bak, 1, STATE_SZ, f); fclose(f); } if (wr(base + OFF_KEY, zero, 32) < 0) return 1; if (wr(base + OFF_RCNT, zero, 8) < 0) return 1; if (wr(base + OFF_CTX, zero, CTX_LEN) < 0) return 1; if (wr(base + OFF_POOL, zero, 8) < 0) return 1; if (wr(base + OFF_LAST, zero, 16) < 0) return 1; } printf("forced state[%d]; reading 4 x 32B in-process\n", cpu); for (k = 0; k < 4; k++) { r = read(fd, blk, 32); if (r != 32) { perror("read"); return 1; } printf("blk%d:", k + 1); for (j = 0; j < 32; j++) printf(" %02x", blk[j]); printf("\n"); } close(fd); return 0; } if (strcmp(act, "window2") == 0) { /* Like window, but dumps the live cipher_ctx (sigma/key/ctr) * after EACH read so the exact emit/rekey chain is visible. */ unsigned char blk[32]; uint8_t cx[64]; ssize_t r; int fd, k, j; if ((fd = open("/dev/urandom", O_RDONLY)) < 0) { perror("open urandom"); return 1; } memset(zero, 0, sizeof(zero)); if (rd(base, bak, STATE_SZ) < 0) return 1; snprintf(bakpath, sizeof(bakpath), "/tmp/state%d.bak", cpu); { FILE *f = fopen(bakpath, "w"); if (f) { fwrite(bak, 1, STATE_SZ, f); fclose(f); } } if (wr(base + OFF_KEY, zero, 32) < 0) return 1; if (wr(base + OFF_RCNT, zero, 8) < 0) return 1; if (wr(base + OFF_CTX, zero, CTX_LEN) < 0) return 1; if (wr(base + OFF_POOL, zero, 8) < 0) return 1; if (wr(base + OFF_LAST, zero, 16) < 0) return 1; printf("forced state[%d]; ctx after each 32B read:\n", cpu); for (k = 0; k < 4; k++) { r = read(fd, blk, 32); if (r != 32) { perror("read"); return 1; } if (rd(base + OFF_CTX, cx, 64) < 0) return 1; printf("blk%d:", k + 1); for (j = 0; j < 32; j++) printf(" %02x", blk[j]); printf("\n ctx sigma=%.4s key0=", cx); for (j = 16; j < 32; j++) printf("%02x", cx[j]); /* counter = input[12..15] LE */ { uint32_t c0, c1; memcpy(&c0, cx + 48, 4); memcpy(&c1, cx + 52, 4); printf(" ctr=%u(0x%x) ctr_hi=%u\n", c0, c0, c1); } } close(fd); return 0; } fprintf(stderr, "unknown action %s\n", act); return 1; } |