code wiki / _hdl_build / nx_efivarstore.nx

nx_efivarstore.nx source

↩ module page · 250 lines · 12745 B

1// nx_efivarstore.nx -- F103e RUNG 11a: READ a UEFI authenticated variable store. 2// 3// The enrolment rung needs PK / KEK / db entries, which means knowing the varstore layout and the 4// EFI GUIDs. ★★★★★★I AM NOT WRITING THOSE GUIDs FROM MEMORY. `OVMF_VARS_4M.ms.fd` ships with 5// Microsoft's keys already enrolled -- it is a WORKED EXAMPLE OF THE EXACT FORMAT, on disk, free. 6// So: parse it, print what is really there, and take every constant from the artifact. 7// **THE FORMAT COMES FROM THE FILE, NOT FROM RECALL** -- the same rule that has caught me three 8// times today when I trusted a filename over a symbol table. 9// 10// Layout (EDK2 VariableFormat.h), all little-endian: 11// EFI_FIRMWARE_VOLUME_HEADER : ZeroVector[16], FileSystemGuid[16], FvLength[8], Signature "_FVH", 12// Attributes[4], HeaderLength[2], Checksum[2], ExtHeaderOffset[2], 13// Reserved[1], Revision[1], BlockMap... 14// VARIABLE_STORE_HEADER : Signature GUID[16], Size[4], Format[1], State[1], Reserved[6] 15// AUTHENTICATED_VARIABLE_HEADER (60 B) : StartId[2]=0x55AA, State[1], Reserved[1], Attributes[4], 16// MonotonicCount[8], TimeStamp[16], PubKeyIndex[4], NameSize[4], 17// DataSize[4], VendorGuid[16] then Name(UCS-2) then Data, 18// each entry 4-byte aligned. 19// 20// Usage: nx_efivarstore dump <vars.fd> -- enumerate every variable 21// nx_efivarstore selftest <vars.fd> -- teeth against a REAL MS-key varstore 22// Exit: 0 GREEN | 1 RED | 3 UNPROVEN (cannot read input). 23// license_tier: ORIGINAL 24import "nx_syscalls.nx" 25 26const EV_HDR: i64 = 60 // AUTHENTICATED_VARIABLE_HEADER 27const EV_STARTID: i64 = 0x55AA 28const EV_ADDED: i64 = 0x3F // VAR_ADDED 29const EV_MAXVARS: i64 = 512 30 31func ev_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 32func ev_fp(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 } 33func ev_fn(fd: i64, v: i64) -> i64 { 34 let bb: *u8 = sys_mmap(28); var m: i64 = v 35 if m < 0 { m = 0 - m; bb[0] = 45 as u8; sys_write(fd, bb, 1) } 36 let t: *u8 = sys_mmap(28); var k: i64 = 0 37 if m == 0 { t[0] = 48 as u8; k = 1 } 38 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 39 var i: i64 = 0 40 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 41 sys_write(fd, bb, k); return 0 42} 43func ev_hexb(b: *u8, off: i64, n: i64) -> i64 { 44 let hx: *u8 = "0123456789abcdef" as *u8 45 let o: *u8 = sys_mmap(n * 2 + 8) 46 var i: i64 = 0 47 while i < n { 48 o[i*2] = hx[(b[off+i] as i64) >> 4] 49 o[i*2+1] = hx[(b[off+i] as i64) & 0xf] 50 i = i + 1 51 } 52 sys_write(1, o, n * 2); return 0 53} 54func ev_eq_str(a: *u8, b: *u8) -> i64 { 55 var i: i64 = 0 56 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 57 if b[i] != (0 as u8) { return 0 } 58 return 1 59} 60func ev_r16(b: *u8, o: i64) -> i64 { return (b[o] as i64) | ((b[o+1] as i64) << 8) } 61func ev_r32(b: *u8, o: i64) -> i64 { 62 return (b[o] as i64) | ((b[o+1] as i64) << 8) | ((b[o+2] as i64) << 16) | ((b[o+3] as i64) << 24) 63} 64 65// find the VARIABLE_STORE_HEADER: it follows the FV header, whose length is at offset 0x30. 66// Returns the offset of the first variable entry, or negative. 67func ev_first_var(b: *u8, len: i64, store_off_p: *i64) -> i64 { 68 if len < 0x40 { return 0 - 1 } 69 // "_FVH" signature at offset 0x28 confirms we are looking at a firmware volume 70 if (b[0x28] as i64) != 0x5F { return 0 - 2 } // '_' 71 if (b[0x29] as i64) != 0x46 { return 0 - 2 } // 'F' 72 if (b[0x2A] as i64) != 0x56 { return 0 - 2 } // 'V' 73 if (b[0x2B] as i64) != 0x48 { return 0 - 2 } // 'H' 74 let hdrlen: i64 = ev_r16(b, 0x30) 75 if hdrlen <= 0 { return 0 - 3 } 76 if hdrlen >= len { return 0 - 3 } 77 let store: i64 = hdrlen // VARIABLE_STORE_HEADER starts here 78 store_off_p[0] = store 79 let ssize: i64 = ev_r32(b, store + 16) 80 if ssize <= 0 { return 0 - 4 } 81 if (store + ssize) > len { return 0 - 4 } 82 return store + 16 + 4 + 1 + 1 + 6 // = store + 28, first variable 83} 84 85// walk entries; calls back by printing. Returns the number of VAR_ADDED entries found. 86func ev_walk(b: *u8, len: i64, first: i64, store_off: i64, verbose: i64, 87 want: *u8, want_len: i64, found_off_p: *i64, found_dsz_p: *i64) -> i64 { 88 let ssize: i64 = ev_r32(b, store_off + 16) 89 let end: i64 = store_off + ssize 90 var off: i64 = first 91 var n: i64 = 0 92 var guard: i64 = 0 93 found_off_p[0] = 0 - 1 94 while guard < EV_MAXVARS { 95 guard = guard + 1 96 if (off + EV_HDR) > end { guard = EV_MAXVARS } else { 97 let sid: i64 = ev_r16(b, off) 98 if sid != EV_STARTID { guard = EV_MAXVARS } else { 99 let state: i64 = b[off + 2] as i64 100 let attrs: i64 = ev_r32(b, off + 4) 101 let nsz: i64 = ev_r32(b, off + 36) 102 let dsz: i64 = ev_r32(b, off + 40) 103 let nameoff: i64 = off + EV_HDR 104 let dataoff: i64 = nameoff + nsz 105 if nsz < 0 { guard = EV_MAXVARS } else { 106 if dsz < 0 { guard = EV_MAXVARS } else { 107 if (dataoff + dsz) > end { guard = EV_MAXVARS } else { 108 if state == EV_ADDED { 109 n = n + 1 110 if verbose == 1 { 111 ev_p(" var name=" as *u8) 112 var c: i64 = 0 113 while c < (nsz - 2) { // UCS-2 -> print the ASCII low bytes 114 let ch: *u8 = sys_mmap(2) 115 ch[0] = b[nameoff + c] 116 if (ch[0] as i64) >= 32 { sys_write(1, ch, 1) } 117 c = c + 2 118 } 119 // ⚠ev_fn prints DECIMAL. Labelling it "0x" made attrs=39 read as 0x39, 120 // which decodes to a DIFFERENT and nonsensical attribute set 121 // (HARDWARE_ERROR_RECORD). ★★★★★A LABEL THAT LIES ABOUT THE BASE TURNS A 122 // CORRECT MEASUREMENT INTO A WRONG FACT, AND THE NUMBER STILL LOOKS RIGHT. 123 // 39 = 0x27 = NV|BS|RT|TIME_BASED_AUTHENTICATED_WRITE_ACCESS -- correct 124 // for PK/KEK/db, and only legible once the base is stated honestly. 125 ev_p(" attrs(dec)=" as *u8); ev_fn(1, attrs) 126 ev_p(" datasize=" as *u8); ev_fn(1, dsz) 127 ev_p(" guid=" as *u8); ev_hexb(b, off + 44, 16) 128 ev_p("\n" as *u8) 129 } 130 // match a requested name (ASCII compared against UCS-2 low bytes) 131 if want_len > 0 { 132 if nsz == (want_len * 2 + 2) { 133 var ok: i64 = 1 134 var w: i64 = 0 135 while w < want_len { 136 if b[nameoff + w * 2] != want[w] { ok = 0 } 137 if (b[nameoff + w * 2 + 1] as i64) != 0 { ok = 0 } 138 w = w + 1 139 } 140 if ok == 1 { if found_off_p[0] < 0 { 141 found_off_p[0] = off 142 found_dsz_p[0] = dsz 143 } } 144 } 145 } 146 } 147 var adv: i64 = EV_HDR + nsz + dsz 148 while (adv % 4) != 0 { adv = adv + 1 } // entries are 4-byte aligned 149 off = off + adv 150 } } } 151 } 152 } 153 } 154 return n 155} 156 157func ev_selftest(path: *u8) -> i64 { 158 var pass: i64 = 0 159 var teeth: i64 = 0 160 let lp: *i64 = sys_mmap(16) as *i64 161 let b: *u8 = sys_read_file(path, lp) 162 let len: i64 = lp[0] 163 if len <= 0 { ev_p("EV UNPROVEN: cannot read " as *u8); ev_p(path); ev_p("\n" as *u8); sys_exit(3); return 3 } 164 165 // T1 it must be recognised as a firmware volume with a variable store 166 teeth = teeth + 1 167 let sop: *i64 = sys_mmap(16) as *i64 168 let first: i64 = ev_first_var(b, len, sop) 169 if first > 0 { pass = pass + 1 170 ev_p("EV-T1 firmware volume + variable store found, first var at " as *u8); ev_fn(1, first) 171 ev_p(" of " as *u8); ev_fn(1, len); ev_p(" bytes GREEN\n" as *u8) } 172 else { ev_p("EV-T1 RED rc=" as *u8); ev_fn(1, first); ev_p("\n" as *u8) } 173 174 // T2 the MS varstore must contain SEVERAL variables -- a parser that finds zero has "succeeded" 175 // at nothing, and that is the failure mode a structure walker fails silently into. 176 teeth = teeth + 1 177 let fo: *i64 = sys_mmap(16) as *i64 178 let fd2: *i64 = sys_mmap(16) as *i64 179 let empty: *u8 = "" as *u8 180 var nvars: i64 = 0 181 if first > 0 { nvars = ev_walk(b, len, first, sop[0], 1, empty, 0, fo, fd2) } 182 if nvars >= 3 { pass = pass + 1; ev_p("EV-T2 found " as *u8); ev_fn(1, nvars) 183 ev_p(" VAR_ADDED entries GREEN\n" as *u8) } 184 else { ev_p("EV-T2 RED nvars=" as *u8); ev_fn(1, nvars); ev_p(" [a walker that finds nothing looks like success]\n" as *u8) } 185 186 // T3 the three Secure Boot variables must be present BY NAME, with non-empty data. 187 teeth = teeth + 1 188 let pkn: *u8 = "PK" as *u8 189 let kekn: *u8 = "KEK" as *u8 190 let dbn: *u8 = "db" as *u8 191 var got: i64 = 0 192 if first > 0 { 193 ev_walk(b, len, first, sop[0], 0, pkn, 2, fo, fd2) 194 if fo[0] >= 0 { if fd2[0] > 0 { got = got + 1 195 ev_p(" PK present, datasize=" as *u8); ev_fn(1, fd2[0]); ev_p("\n" as *u8) } } 196 ev_walk(b, len, first, sop[0], 0, kekn, 3, fo, fd2) 197 if fo[0] >= 0 { if fd2[0] > 0 { got = got + 1 198 ev_p(" KEK present, datasize=" as *u8); ev_fn(1, fd2[0]); ev_p("\n" as *u8) } } 199 ev_walk(b, len, first, sop[0], 0, dbn, 2, fo, fd2) 200 if fo[0] >= 0 { if fd2[0] > 0 { got = got + 1 201 ev_p(" db present, datasize=" as *u8); ev_fn(1, fd2[0]) 202 ev_p(" guid=" as *u8); ev_hexb(b, fo[0] + 44, 16); ev_p("\n" as *u8) } } 203 } 204 if got == 3 { pass = pass + 1; ev_p("EV-T3 PK+KEK+db all present GREEN\n" as *u8) } 205 else { ev_p("EV-T3 RED got=" as *u8); ev_fn(1, got); ev_p("\n" as *u8) } 206 207 // T4 NEGATIVE CONTROL: a name that cannot be there must NOT be found, or T3 proves nothing. 208 teeth = teeth + 1 209 let bogus: *u8 = "NishiNoSuchVar" as *u8 210 if first > 0 { ev_walk(b, len, first, sop[0], 0, bogus, 14, fo, fd2) } 211 if fo[0] < 0 { pass = pass + 1; ev_p("EV-T4 neg-control-absent-name-not-found GREEN\n" as *u8) } 212 else { ev_p("EV-T4 RED [VACUOUS: the matcher matches anything]\n" as *u8) } 213 214 ev_p("EV-SELFTEST " as *u8); ev_fn(1, pass); ev_p("/" as *u8); ev_fn(1, teeth); ev_p("\n" as *u8) 215 let lf: i64 = sys_openat_append("knowledge/status/nishi_os.log" as *u8, 0x1a4) 216 if lf >= 0 { 217 ev_fp(lf, "EFIVARSTORE selftest teeth=" as *u8); ev_fn(lf, pass) 218 ev_fp(lf, "of" as *u8); ev_fn(lf, teeth) 219 ev_fp(lf, " subject=OVMF-MS-keys-varstore verdict=" as *u8) 220 if pass == teeth { ev_fp(lf, "GREEN\n" as *u8) } else { ev_fp(lf, "RED\n" as *u8) } 221 sys_close(lf) 222 } 223 if pass == teeth { sys_exit(0); return 0 } 224 sys_exit(1); return 1 225} 226 227func main(argc: i64, argv: *i64) -> i64 { 228 if argc < 3 { 229 ev_p("usage: nx_efivarstore dump <vars.fd> | nx_efivarstore selftest <vars.fd>\n" as *u8) 230 sys_exit(2); return 2 231 } 232 let path: *u8 = argv[2] as *u8 233 if ev_eq_str(argv[1] as *u8, "selftest" as *u8) == 1 { return ev_selftest(path) } 234 if ev_eq_str(argv[1] as *u8, "dump" as *u8) == 1 { 235 let lp: *i64 = sys_mmap(16) as *i64 236 let b: *u8 = sys_read_file(path, lp) 237 if lp[0] <= 0 { ev_p("cannot read\n" as *u8); sys_exit(3); return 3 } 238 let sop: *i64 = sys_mmap(16) as *i64 239 let first: i64 = ev_first_var(b, lp[0], sop) 240 if first < 0 { ev_p("not a variable store, rc=" as *u8); ev_fn(1, first); ev_p("\n" as *u8); sys_exit(1); return 1 } 241 let fo: *i64 = sys_mmap(16) as *i64 242 let fd2: *i64 = sys_mmap(16) as *i64 243 let empty: *u8 = "" as *u8 244 let n: i64 = ev_walk(b, lp[0], first, sop[0], 1, empty, 0, fo, fd2) 245 ev_p("total VAR_ADDED=" as *u8); ev_fn(1, n); ev_p("\n" as *u8) 246 sys_exit(0); return 0 247 } 248 ev_p("unknown verb\n" as *u8) 249 sys_exit(2); return 2 250}