DF-2330 / amdsmb_rig.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 | /* * amdsmb_rig.c -- driver for the VERBATIM amdsmb_bread verification rig. * * The production functions (amdsmb_ec_wait_write/ec_wait_read/ec_read/ * ec_write/amdsmb_wait/amdsmb_bread) are machine-extracted byte-identical * from the guest's /usr/src/sys/bus/smbus/amdsmb/amdsmb.c by build.sh * (sha256-gated) into amdsmb_core.inc. This driver provides the kernel * environment emulation: * * - bus_read_1/bus_write_1 model the AMD-8111 SMBus 2.0 controller I/O * register file behind the EC_DATA/EC_CMD/EC_SC indirect protocol the * verbatim code drives. The model implements the SMB register file * (PRTCL 0x00 ... ALRM_D 0x26) that a NON-COMPLIANT / MALICIOUS SMBus * slave presents through the controller: BCNT (0x24) reads back 255. * - Every register-file fetch is instrumented: the "logical" SMB register * number fetched via the EC protocol is recorded, so reads beyond the * 32-entry data register file (SMB_DATA..SMB_DATA+31 == 0x04..0x23) * are counted and their values are traceable. * * Build: sh build.sh (extract + cc -O2 -o rig amdsmb_rig.c) * Run: ./rig attack | control */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /* ---------------- kernel environment shims (NOT verbatim) ------------- */ typedef void *device_t; struct resource { int dummy; }; static struct resource rig_res; struct lock { int x; }; #define KKASSERT(x) do { if (!(x)) { \ printf("RIG KKASSERT failed: %s\n", #x); abort(); } } while (0) static int lockowned(struct lock *l) { (void)l; return 1; } static int lockmgr(struct lock *l, int flags) { (void)l; (void)flags; return 0; } #define LK_EXCLUSIVE 0x80000 #define LK_RELEASE 0x40000 #define LK_CANRECURSE 0x0004 static void DELAY(int d) { (void)d; } #define kprintf printf static void device_printf(device_t d, const char *fmt, ...) { (void)d; (void)fmt; } static void *device_get_softc(device_t d) { return d; } static device_t rig_dev; static int amdsmb_debug = 0; #define AMDSMB_DEBUG(x) if (amdsmb_debug) (x) /* SMBbus error codes (smbconf.h) */ #define SMB_ENOERR 0x0 #define SMB_EINVAL 0x100 #define SMB_ENOACK 0x19 #define SMB_EBUSY 0x40 #define SMB_ETIMEOUT 0x20 #define SMB_ENOTSUPP 0x27 #define SMB_EBUSERR 0x1c /* ---------------- controller I/O model -------------------------------- * The verbatim code does EC-protocol accesses: writes EC_CMD(0x04) = 0x80 * (read) / 0x81 (write), then EC_DATA(0x00) = SMB register number, then * reads EC_DATA for the value. io[] is the I/O port space the resource * maps; smbreg[] is the SMB register file the controller fronts. */ static uint8_t io[512]; /* port space: EC_DATA 0x00, EC_SC/CMD 0x04 */ static uint8_t smbreg[512]; /* controller SMB register file */ /* instrumentation */ static long io_reads, io_writes; static long smb_fetches; /* logical SMB register fetches */ static long smb_fetch_oob; /* fetches at reg > 0x23 (past data file) */ static int smb_fetch_max = -1; /* max logical register fetched */ static uint8_t smb_fetch_vals[512]; /* value served per logical register */ static long ec_sc_polls; static void model_reset(uint8_t bcnt) { memset(io, 0, sizeof(io)); memset(smbreg, 0, sizeof(smbreg)); /* non-compliant slave delivered `bcnt` bytes; controller stored it */ smbreg[0x24] = bcnt; /* data the slave pushed: the 32-entry data register file only */ { int i; for (i = 0; i < 32; i++) smbreg[0x04 + i] = (uint8_t)(0xC0 ^ i); /* distinct */ } /* registers beyond the file (BCNT 0x24, ALRM 0x25.., whatever else * the chipset maps there) carry their own distinct values */ { int i; for (i = 0x25; i < 512; i++) smbreg[i] = (uint8_t)(i ^ 0x5A); } smbreg[0x00] = 0x00; /* PRTCL idle -> wait() happy */ smbreg[0x01] = 0x00; /* STS = OK */ /* re-fetch bookkeeping */ smb_fetches = smb_fetch_oob = 0; smb_fetch_max = -1; memset(smb_fetch_vals, 0, sizeof(smb_fetch_vals)); ec_sc_polls = 0; } static uint8_t bus_read_1(struct resource *r, int reg) { (void)r; io_reads++; if (reg == 0x00) { /* EC_DATA: serves last addressed SMB reg */ smb_fetches++; /* the last EC_DATA write selected the SMB register number */ { int sel = io[0x00]; /* value written to EC_DATA before read */ uint8_t v = smbreg[sel]; smb_fetch_vals[sel & 0x1ff] = v; if ((sel & 0xff) > 0x23) smb_fetch_oob++; if ((sel & 0xff) > smb_fetch_max) smb_fetch_max = sel & 0xff; return v; } } if (reg == 0x04) { /* EC_SC */ ec_sc_polls++; return 0x01; /* OBF set, IBF clear: always ready */ } return io[reg]; } static void bus_write_1(struct resource *r, int reg, uint8_t v) { (void)r; io_writes++; io[reg] = v; } /* ---------------- VERBATIM production code ---------------------------- */ #include "amdsmb_core.inc" /* ---------------- scenarios ------------------------------------------- */ static void report(const char *tag, int rc, uint8_t count_in, uint8_t count_out, char *buf) { int i; printf("[%s] rc=%d(SMB_ENOERR=%d) count_in=%u count_out=%u\n", tag, rc, SMB_ENOERR, count_in, count_out); printf("[%s] io: reads=%ld writes=%ld ec_sc_polls=%ld | " "smb register fetches=%ld OOB(>0x23 data-file end)=%ld " "max logical reg=0x%02x\n", tag, io_reads, io_writes, ec_sc_polls, smb_fetches, smb_fetch_oob, smb_fetch_max); printf("[%s] buf[0..7] =", tag); for (i = 0; i < 8; i++) printf(" %02x", (uint8_t)buf[i]); printf(" buf[30..33] ="); for (i = 30; i < 34 && i < 255; i++) printf(" %02x", (uint8_t)buf[i]); printf("\n"); /* traceability: value the model serves for registers beyond the file */ if (smb_fetch_max > 0x23) { printf("[%s] PROOF: logical SMB registers 0x24..0x%02x were " "fetched via the EC protocol -- beyond the 32-entry data " "register file (SMB_DATA=0x04 .. 0x23)\n", tag, smb_fetch_max); printf("[%s] e.g. buf[32] (i=32 -> reg 0x24=BCNT) model value " "0x%02x; buf[33] (reg 0x25=ALRM_A) 0x%02x; " "buf[36] (reg 0x28) 0x%02x\n", tag, smb_fetch_vals[0x24], smb_fetch_vals[0x25], smb_fetch_vals[0x28]); } if (count_out > count_in) printf("[%s] CONTRACT BREAK: *count reports %u but caller " "buffer holds only %u initialized byte(s) -- caller-side " "read of buf[count_in..count_out-1] reads the caller's " "uninitialized buffer\n", tag, count_out, count_in); } int main(int argc, char **argv) { struct amdsmb_softc sc; char buf[64]; uint8_t count; int rc; memset(&sc, 0, sizeof(sc)); sc.res = &rig_res; if (argc == 2 && strcmp(argv[1], "attack") == 0) { model_reset(255); /* malicious BCNT */ memset(buf, 0, sizeof(buf)); count = 32; /* caller contract: <=32 */ rc = amdsmb_bread(rig_dev, 0x2D, 0x07, &count, buf); report("attack bcnt=255", rc, 32, count, buf); return 0; } if (argc == 2 && strcmp(argv[1], "control") == 0) { model_reset(16); /* compliant slave */ memset(buf, 0, sizeof(buf)); count = 32; rc = amdsmb_bread(rig_dev, 0x2D, 0x07, &count, buf); report("control bcnt=16", rc, 32, count, buf); return 0; } if (argc == 2 && strcmp(argv[1], "edge") == 0) { model_reset(33); /* one past the file */ memset(buf, 0, sizeof(buf)); count = 32; rc = amdsmb_bread(rig_dev, 0x2D, 0x07, &count, buf); report("edge bcnt=33", rc, 32, count, buf); return 0; } fprintf(stderr, "usage: %s attack|control|edge\n", argv[0]); return 2; } |