DF-4467 / xtsrace.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 | /* * DF-4467 PoC: software XTS per-session shared tweak race. * * cryptoapi's software xts (aes_xts_crypt / twofish / serpent) keeps the * running tweak in the SESSION ctx (aes_xts_ctx.tweak) and reinits it per * crypt call. dm_target_crypt calls cryptoapi_cipher_crypt() on the SAME * session concurrently from multiple CPUs (write encryption runs in the * strategy context of whichever CPU dispatched the bio), so two * dmtc_bio_encdec loops interleave aes_xts_reinit()/crypt_block_xts() * mutations of ctx->tweak -> wrong tweaks -> silent ciphertext corruption. * * Round-trip test: writers write known distinct patterns to their own * blocks through the crypt device; afterwards we read every block back * (also through the crypt device). Any block that does not round-trip * is corrupted. Single-threaded control run must always round-trip. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h> #include <errno.h> struct plistref { void *pref_plist; size_t pref_len; }; #define DM_IOCTL_CMD 0 #define NETBSD_DM_IOCTL _IOWR(0xfd, DM_IOCTL_CMD, struct plistref) static int ctlfd = -1; static int dm_ioctl_xml(const char *xml) { struct plistref pref; char *buf; int error; buf = strdup(xml); pref.pref_plist = buf; pref.pref_len = strlen(buf) + 1; error = ioctl(ctlfd, NETBSD_DM_IOCTL, &pref); free(buf); return (error); } static const char *KEY = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; static void dm_create_load_resume(const char *name, const char *type, uint64_t len, const char *params) { char x[1024]; snprintf(x, sizeof(x), "<plist><dict><key>command</key><string>create</string>" "<key>version</key><array><integer>0x4</integer>" "<integer>0x10</integer><integer>0x0</integer></array>" "<key>name</key><string>%s</string>" "<key>flags</key><integer>0x0</integer></dict></plist>", name); if (dm_ioctl_xml(x) != 0) { printf("create %s failed\n", name); exit(1); } snprintf(x, sizeof(x), "<plist><dict><key>command</key><string>reload</string>" "<key>version</key><array><integer>0x4</integer>" "<integer>0x10</integer><integer>0x0</integer></array>" "<key>name</key><string>%s</string>" "<key>flags</key><integer>0x0</integer>" "<key>cmd_data</key><array><dict>" "<key>type</key><string>%s</string>" "<key>start</key><integer>0x0</integer>" "<key>length</key><integer>0x%llx</integer>" "<key>params</key><string>%s</string>" "</dict></array></dict></plist>", name, type, (unsigned long long)len, params); if (dm_ioctl_xml(x) != 0) { printf("load %s failed\n", name); exit(1); } snprintf(x, sizeof(x), "<plist><dict><key>command</key><string>resume</string>" "<key>version</key><array><integer>0x4</integer>" "<integer>0x10</integer><integer>0x0</integer></array>" "<key>name</key><string>%s</string>" "<key>flags</key><integer>0x0</integer></dict></plist>", name); if (dm_ioctl_xml(x) != 0) { printf("resume %s failed\n", name); exit(1); } } #define NBLOCKS 512 /* 4KB blocks */ #define NTHREADS 8 static int devfd; static char *expected[NBLOCKS]; static volatile int pattern_gen[NBLOCKS]; static volatile int g_stop; static void fill_pattern(char *buf, int gen, int block) { int i; for (i = 0; i < 4096; i += sizeof(unsigned)) *(unsigned *)(buf + i) = (gen << 24) ^ (block << 16) ^ i; } static void * writer(void *arg) { long id = (long)arg; char *buf; int b, gen = 1; buf = aligned_alloc(4096, 4096); int lo = (int)id * (NBLOCKS / NTHREADS); int hi = lo + (NBLOCKS / NTHREADS); while (!g_stop) { /* each writer OWNS a disjoint block range (no false races); * concurrency on the shared session is what we want */ for (b = lo; b < hi; b++) { fill_pattern(buf, gen, b); if (pwrite(devfd, buf, 4096, (off_t)b * 4096) != 4096) break; pattern_gen[b] = gen; } gen++; } free(buf); return (NULL); } int main(int argc, char **argv) { const char *pdev = argc > 1 ? argv[1] : "/dev/vn0"; int seconds = argc > 2 ? atoi(argv[2]) : 20; int control = argc > 3 ? atoi(argv[3]) : 0; pthread_t th[NTHREADS]; char params[512]; char *buf; int i, b, bad, rounds; ctlfd = open("/dev/mapper/control", O_RDWR); if (ctlfd < 0) { perror("control"); return 1; } snprintf(params, sizeof(params), "aes-xts-plain64 %s 0 %s 0", KEY, pdev); dm_create_load_resume("xt0", "crypt", 131072, params); devfd = open("/dev/mapper/xt0", O_RDWR); if (devfd < 0) { perror("open xt0"); return 1; } for (b = 0; b < NBLOCKS; b++) { expected[b] = malloc(4096); fill_pattern(expected[b], 0, b); pattern_gen[b] = 0; if (pwrite(devfd, expected[b], 4096, (off_t)b * 4096) != 4096) { perror("initial pwrite"); return 1; } } printf("[setup] %d blocks initialized (single threaded)\n", NBLOCKS); if (!control) { for (i = 0; i < NTHREADS; i++) pthread_create(&th[i], NULL, writer, (void *)(long)i); sleep(seconds); g_stop = 1; for (i = 0; i < NTHREADS; i++) pthread_join(th[i], NULL); printf("[race] %d writers stopped after %ds\n", NTHREADS, seconds); } /* verify: read each block 3x, decrypt must equal expected pattern */ buf = aligned_alloc(4096, 4096); bad = 0; for (rounds = 0; rounds < 3; rounds++) { for (b = 0; b < NBLOCKS; b++) { if (pread(devfd, buf, 4096, (off_t)b * 4096) != 4096) { perror("verify pread"); return 1; } fill_pattern(expected[b], pattern_gen[b], b); if (memcmp(buf, expected[b], 4096) != 0) { bad++; if (bad <= 4) printf("[CORRUPT] block %d round %d: " "got %02x%02x%02x%02x want " "%02x%02x%02x%02x\n", b, rounds, buf[0], buf[1], buf[2], buf[3], expected[b][0], expected[b][1], expected[b][2], expected[b][3]); } } } printf("[result] corrupted blocks: %d / %d (%s)\n", bad, NBLOCKS, bad ? "XTS TWEAK RACE REPRODUCED" : "no corruption detected"); return (bad ? 3 : 0); } |