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}