]>
Commit | Line | Data |
---|---|---|
031e5cce | 1 | // SPDX-License-Identifier: BSD-2-Clause-Patent |
f892ac66 | 2 | /* |
031e5cce | 3 | * mok.c - MoK variable processing |
f892ac66 | 4 | * Copyright 2017 Peter Jones <pjones@redhat.com> |
f892ac66 MTL |
5 | */ |
6 | ||
7 | #include "shim.h" | |
8 | ||
9 | /* | |
10 | * Check if a variable exists | |
11 | */ | |
12 | static BOOLEAN check_var(CHAR16 *varname) | |
13 | { | |
14 | EFI_STATUS efi_status; | |
15 | UINTN size = sizeof(UINT32); | |
16 | UINT32 MokVar; | |
17 | UINT32 attributes; | |
18 | ||
8529e0f7 SM |
19 | efi_status = RT->GetVariable(varname, &SHIM_LOCK_GUID, &attributes, |
20 | &size, (void *)&MokVar); | |
f892ac66 MTL |
21 | if (!EFI_ERROR(efi_status) || efi_status == EFI_BUFFER_TOO_SMALL) |
22 | return TRUE; | |
23 | ||
24 | return FALSE; | |
25 | } | |
26 | ||
031e5cce SM |
27 | #define SetVariable(name, guid, attrs, varsz, var) \ |
28 | ({ \ | |
29 | EFI_STATUS efi_status_; \ | |
8529e0f7 | 30 | efi_status_ = RT->SetVariable(name, guid, attrs, varsz, var); \ |
031e5cce SM |
31 | dprint_(L"%a:%d:%a() SetVariable(\"%s\", ... varsz=0x%llx) = %r\n", \ |
32 | __FILE__, __LINE__ - 5, __func__, name, varsz, \ | |
33 | efi_status_); \ | |
34 | efi_status_; \ | |
35 | }) | |
36 | ||
f892ac66 MTL |
37 | /* |
38 | * If the OS has set any of these variables we need to drop into MOK and | |
39 | * handle them appropriately | |
40 | */ | |
41 | static EFI_STATUS check_mok_request(EFI_HANDLE image_handle) | |
42 | { | |
43 | EFI_STATUS efi_status; | |
44 | ||
45 | if (check_var(L"MokNew") || check_var(L"MokSB") || | |
46 | check_var(L"MokPW") || check_var(L"MokAuth") || | |
47 | check_var(L"MokDel") || check_var(L"MokDB") || | |
48 | check_var(L"MokXNew") || check_var(L"MokXDel") || | |
8529e0f7 | 49 | check_var(L"MokXAuth") || check_var(L"MokListTrustedNew")) { |
f892ac66 MTL |
50 | efi_status = start_image(image_handle, MOK_MANAGER); |
51 | ||
52 | if (EFI_ERROR(efi_status)) { | |
53 | perror(L"Failed to start MokManager: %r\n", efi_status); | |
54 | return efi_status; | |
55 | } | |
56 | } | |
57 | ||
58 | return EFI_SUCCESS; | |
59 | } | |
60 | ||
031e5cce SM |
61 | static vendor_addend_category_t |
62 | categorize_authorized(struct mok_state_variable *v) | |
63 | { | |
64 | if (!(v->addend && v->addend_size && | |
65 | *v->addend && *v->addend_size)) { | |
66 | return VENDOR_ADDEND_NONE; | |
67 | } | |
68 | ||
69 | return vendor_authorized_category; | |
70 | } | |
71 | ||
72 | static vendor_addend_category_t | |
73 | categorize_deauthorized(struct mok_state_variable *v) | |
74 | { | |
75 | if (!(v->addend && v->addend_size && | |
76 | *v->addend && *v->addend_size)) { | |
77 | return VENDOR_ADDEND_NONE; | |
78 | } | |
79 | ||
80 | return VENDOR_ADDEND_DB; | |
81 | } | |
82 | ||
f892ac66 MTL |
83 | #define MOK_MIRROR_KEYDB 0x01 |
84 | #define MOK_MIRROR_DELETE_FIRST 0x02 | |
85 | #define MOK_VARIABLE_MEASURE 0x04 | |
86 | #define MOK_VARIABLE_LOG 0x08 | |
87 | ||
8529e0f7 | 88 | struct mok_state_variable mok_state_variable_data[] = { |
f892ac66 MTL |
89 | {.name = L"MokList", |
90 | .name8 = "MokList", | |
91 | .rtname = L"MokListRT", | |
031e5cce | 92 | .rtname8 = "MokListRT", |
f892ac66 MTL |
93 | .guid = &SHIM_LOCK_GUID, |
94 | .yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
95 | EFI_VARIABLE_NON_VOLATILE, | |
96 | .no_attr = EFI_VARIABLE_RUNTIME_ACCESS, | |
031e5cce SM |
97 | .categorize_addend = categorize_authorized, |
98 | .addend = &vendor_authorized, | |
99 | .addend_size = &vendor_authorized_size, | |
100 | #if defined(ENABLE_SHIM_CERT) | |
101 | .build_cert = &build_cert, | |
102 | .build_cert_size = &build_cert_size, | |
103 | #endif /* defined(ENABLE_SHIM_CERT) */ | |
f892ac66 | 104 | .flags = MOK_MIRROR_KEYDB | |
031e5cce | 105 | MOK_MIRROR_DELETE_FIRST | |
f892ac66 MTL |
106 | MOK_VARIABLE_LOG, |
107 | .pcr = 14, | |
108 | }, | |
109 | {.name = L"MokListX", | |
110 | .name8 = "MokListX", | |
111 | .rtname = L"MokListXRT", | |
031e5cce | 112 | .rtname8 = "MokListXRT", |
f892ac66 MTL |
113 | .guid = &SHIM_LOCK_GUID, |
114 | .yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
115 | EFI_VARIABLE_NON_VOLATILE, | |
116 | .no_attr = EFI_VARIABLE_RUNTIME_ACCESS, | |
031e5cce SM |
117 | .categorize_addend = categorize_deauthorized, |
118 | .addend = &vendor_deauthorized, | |
119 | .addend_size = &vendor_deauthorized_size, | |
f892ac66 | 120 | .flags = MOK_MIRROR_KEYDB | |
031e5cce | 121 | MOK_MIRROR_DELETE_FIRST | |
f892ac66 MTL |
122 | MOK_VARIABLE_LOG, |
123 | .pcr = 14, | |
124 | }, | |
125 | {.name = L"MokSBState", | |
126 | .name8 = "MokSBState", | |
127 | .rtname = L"MokSBStateRT", | |
031e5cce | 128 | .rtname8 = "MokSBStateRT", |
f892ac66 MTL |
129 | .guid = &SHIM_LOCK_GUID, |
130 | .yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
131 | EFI_VARIABLE_NON_VOLATILE, | |
132 | .no_attr = EFI_VARIABLE_RUNTIME_ACCESS, | |
133 | .flags = MOK_MIRROR_DELETE_FIRST | | |
134 | MOK_VARIABLE_MEASURE | | |
135 | MOK_VARIABLE_LOG, | |
136 | .pcr = 14, | |
137 | .state = &user_insecure_mode, | |
138 | }, | |
139 | {.name = L"MokDBState", | |
140 | .name8 = "MokDBState", | |
141 | .rtname = L"MokIgnoreDB", | |
031e5cce | 142 | .rtname8 = "MokIgnoreDB", |
f892ac66 MTL |
143 | .guid = &SHIM_LOCK_GUID, |
144 | .yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
145 | EFI_VARIABLE_NON_VOLATILE, | |
146 | .no_attr = EFI_VARIABLE_RUNTIME_ACCESS, | |
147 | .state = &ignore_db, | |
148 | }, | |
031e5cce SM |
149 | {.name = SBAT_VAR_NAME, |
150 | .name8 = SBAT_VAR_NAME8, | |
151 | .rtname = SBAT_RT_VAR_NAME, | |
152 | .rtname8 = SBAT_RT_VAR_NAME8, | |
153 | .guid = &SHIM_LOCK_GUID, | |
154 | .yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
155 | EFI_VARIABLE_NON_VOLATILE, | |
156 | /* | |
157 | * we're enforcing that SBAT can't have an RT flag here because | |
158 | * there's no way to tell whether it's an authenticated variable. | |
159 | */ | |
160 | #if !defined(ENABLE_SHIM_DEVEL) | |
161 | .no_attr = EFI_VARIABLE_RUNTIME_ACCESS, | |
162 | #else | |
163 | .no_attr = 0, | |
164 | #endif | |
165 | .flags = MOK_MIRROR_DELETE_FIRST | | |
166 | MOK_VARIABLE_MEASURE, | |
167 | .pcr = 7, | |
168 | }, | |
8529e0f7 SM |
169 | {.name = L"MokListTrusted", |
170 | .name8 = "MokListTrusted", | |
171 | .rtname = L"MokListTrustedRT", | |
172 | .rtname8 = "MokListTrustedRT", | |
173 | .guid = &SHIM_LOCK_GUID, | |
174 | .yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
175 | EFI_VARIABLE_NON_VOLATILE, | |
176 | .no_attr = EFI_VARIABLE_RUNTIME_ACCESS, | |
177 | .flags = MOK_MIRROR_DELETE_FIRST | | |
178 | MOK_VARIABLE_MEASURE | | |
179 | MOK_VARIABLE_LOG, | |
180 | .pcr = 14, | |
181 | .state = &trust_mok_list, | |
182 | }, | |
f892ac66 MTL |
183 | { NULL, } |
184 | }; | |
8529e0f7 SM |
185 | size_t n_mok_state_variables = sizeof(mok_state_variable_data) / sizeof(mok_state_variable_data[0]); |
186 | struct mok_state_variable *mok_state_variables = &mok_state_variable_data[0]; | |
f892ac66 | 187 | |
031e5cce SM |
188 | #define should_mirror_addend(v) (((v)->categorize_addend) && ((v)->categorize_addend(v) != VENDOR_ADDEND_NONE)) |
189 | ||
190 | static inline BOOLEAN NONNULL(1) | |
191 | should_mirror_build_cert(struct mok_state_variable *v) | |
192 | { | |
193 | return (v->build_cert && v->build_cert_size && | |
194 | *v->build_cert && *v->build_cert_size) ? TRUE : FALSE; | |
195 | } | |
196 | ||
197 | static const uint8_t null_sha256[32] = { 0, }; | |
198 | ||
199 | typedef UINTN SIZE_T; | |
200 | ||
8529e0f7 SM |
201 | #define EFI_MAJOR_VERSION(tablep) ((UINT16)((((tablep)->Hdr.Revision) >> 16) & 0xfffful)) |
202 | #define EFI_MINOR_VERSION(tablep) ((UINT16)(((tablep)->Hdr.Revision) & 0xfffful)) | |
203 | ||
031e5cce SM |
204 | static EFI_STATUS |
205 | get_max_var_sz(UINT32 attrs, SIZE_T *max_var_szp) | |
206 | { | |
207 | EFI_STATUS efi_status; | |
208 | uint64_t max_storage_sz = 0; | |
209 | uint64_t remaining_sz = 0; | |
210 | uint64_t max_var_sz = 0; | |
211 | ||
212 | *max_var_szp = 0; | |
8529e0f7 SM |
213 | if (EFI_MAJOR_VERSION(RT) < 2) { |
214 | dprint(L"EFI %d.%d; no RT->QueryVariableInfo(). Using 1024!\n", | |
215 | EFI_MAJOR_VERSION(RT), EFI_MINOR_VERSION(RT)); | |
216 | max_var_sz = remaining_sz = max_storage_sz = 1024; | |
217 | efi_status = EFI_SUCCESS; | |
218 | } else { | |
219 | dprint(L"calling RT->QueryVariableInfo() at 0x%lx\n", | |
220 | RT->QueryVariableInfo); | |
221 | efi_status = RT->QueryVariableInfo(attrs, &max_storage_sz, | |
222 | &remaining_sz, &max_var_sz); | |
223 | if (EFI_ERROR(efi_status)) { | |
224 | perror(L"Could not get variable storage info: %r\n", | |
225 | efi_status); | |
226 | return efi_status; | |
227 | } | |
031e5cce SM |
228 | } |
229 | ||
230 | /* | |
231 | * I just don't trust implementations to not be showing static data | |
232 | * for max_var_sz | |
233 | */ | |
234 | *max_var_szp = (max_var_sz < remaining_sz) ? max_var_sz : remaining_sz; | |
235 | dprint("max_var_sz:%lx remaining_sz:%lx max_storage_sz:%lx\n", | |
236 | max_var_sz, remaining_sz, max_storage_sz); | |
237 | return efi_status; | |
238 | } | |
239 | ||
240 | /* | |
241 | * If any entries fit in < maxsz, and nothing goes wrong, create a variable | |
242 | * of the given name and guid with as many esd entries as possible in it, | |
243 | * and updates *esdp with what would be the next entry (even if makes *esdp | |
244 | * > esl+esl->SignatureListSize), and returns whatever SetVariable() | |
245 | * returns | |
246 | * | |
247 | * If no entries fit (i.e. sizeof(esl) + esl->SignatureSize > maxsz), | |
248 | * returns EFI_BUFFER_TOO_SMALL; | |
249 | */ | |
250 | static EFI_STATUS | |
251 | mirror_one_esl(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, | |
252 | EFI_SIGNATURE_LIST *esl, EFI_SIGNATURE_DATA *esd, | |
253 | SIZE_T howmany) | |
254 | { | |
255 | EFI_STATUS efi_status; | |
256 | SIZE_T varsz = 0; | |
257 | UINT8 *var; | |
258 | ||
259 | /* | |
260 | * We always assume esl->SignatureHeaderSize is 0 (and so far, | |
261 | * that's true as per UEFI 2.8) | |
262 | */ | |
263 | dprint(L"Trying to add %lx signatures to \"%s\" of size %lx\n", | |
264 | howmany, name, esl->SignatureSize); | |
265 | ||
266 | /* | |
267 | * Because of the semantics of variable_create_esl(), the first | |
268 | * owner guid from the data is not part of esdsz, or the data. | |
269 | * | |
270 | * Compensate here. | |
271 | */ | |
272 | efi_status = variable_create_esl(esd, howmany, | |
273 | &esl->SignatureType, | |
274 | esl->SignatureSize, | |
275 | &var, &varsz); | |
276 | if (EFI_ERROR(efi_status) || !var || !varsz) { | |
277 | LogError(L"Couldn't allocate %lu bytes for mok variable \"%s\": %r\n", | |
278 | varsz, var, efi_status); | |
279 | return efi_status; | |
280 | } | |
281 | ||
282 | dprint(L"new esl:\n"); | |
283 | dhexdumpat(var, varsz, 0); | |
284 | ||
285 | efi_status = SetVariable(name, guid, attrs, varsz, var); | |
286 | FreePool(var); | |
287 | if (EFI_ERROR(efi_status)) { | |
288 | LogError(L"Couldn't create mok variable \"%s\": %r\n", | |
289 | varsz, var, efi_status); | |
290 | return efi_status; | |
291 | } | |
292 | ||
293 | return efi_status; | |
294 | } | |
295 | ||
296 | static EFI_STATUS | |
297 | mirror_mok_db(CHAR16 *name, CHAR8 *name8, EFI_GUID *guid, UINT32 attrs, | |
298 | UINT8 *FullData, SIZE_T FullDataSize, BOOLEAN only_first) | |
f892ac66 MTL |
299 | { |
300 | EFI_STATUS efi_status = EFI_SUCCESS; | |
031e5cce SM |
301 | SIZE_T max_var_sz; |
302 | ||
303 | efi_status = get_max_var_sz(attrs, &max_var_sz); | |
8529e0f7 | 304 | if (EFI_ERROR(efi_status) && efi_status != EFI_UNSUPPORTED) { |
031e5cce SM |
305 | LogError(L"Could not get maximum variable size: %r", |
306 | efi_status); | |
307 | return efi_status; | |
308 | } | |
309 | ||
8529e0f7 SM |
310 | /* Some UEFI environment such as u-boot doesn't implement |
311 | * QueryVariableInfo() and we will only get EFI_UNSUPPORTED when | |
312 | * querying the available space. In this case, we just mirror | |
313 | * the variable directly. */ | |
314 | if (FullDataSize <= max_var_sz || efi_status == EFI_UNSUPPORTED) { | |
315 | efi_status = EFI_SUCCESS; | |
031e5cce SM |
316 | if (only_first) |
317 | efi_status = SetVariable(name, guid, attrs, | |
318 | FullDataSize, FullData); | |
f892ac66 | 319 | |
031e5cce SM |
320 | return efi_status; |
321 | } | |
322 | ||
323 | CHAR16 *namen; | |
324 | CHAR8 *namen8; | |
325 | UINTN namelen, namesz; | |
326 | ||
327 | namelen = StrLen(name); | |
328 | namesz = namelen * 2; | |
329 | if (only_first) { | |
330 | namen = name; | |
331 | namen8 = name8; | |
332 | } else { | |
333 | namelen += 18; | |
334 | namesz += 34; | |
335 | namen = AllocateZeroPool(namesz); | |
336 | if (!namen) { | |
337 | LogError(L"Could not allocate %lu bytes", namesz); | |
338 | return EFI_OUT_OF_RESOURCES; | |
339 | } | |
340 | namen8 = AllocateZeroPool(namelen); | |
341 | if (!namen8) { | |
342 | FreePool(namen); | |
343 | LogError(L"Could not allocate %lu bytes", namelen); | |
f892ac66 MTL |
344 | return EFI_OUT_OF_RESOURCES; |
345 | } | |
031e5cce SM |
346 | } |
347 | ||
348 | UINTN pos, i; | |
349 | const SIZE_T minsz = sizeof(EFI_SIGNATURE_LIST) | |
350 | + sizeof(EFI_SIGNATURE_DATA) | |
351 | + SHA1_DIGEST_SIZE; | |
352 | BOOLEAN did_one = FALSE; | |
353 | ||
354 | /* | |
355 | * Create any entries that can fit. | |
356 | */ | |
357 | if (!only_first) { | |
358 | dprint(L"full data for \"%s\":\n", name); | |
359 | dhexdumpat(FullData, FullDataSize, 0); | |
360 | } | |
361 | EFI_SIGNATURE_LIST *esl = NULL; | |
362 | UINTN esl_end_pos = 0; | |
363 | for (i = 0, pos = 0; FullDataSize - pos >= minsz && FullData; ) { | |
364 | EFI_SIGNATURE_DATA *esd = NULL; | |
365 | ||
366 | dprint(L"pos:0x%llx FullDataSize:0x%llx\n", pos, FullDataSize); | |
367 | if (esl == NULL || pos >= esl_end_pos) { | |
368 | UINT8 *nesl = FullData + pos; | |
369 | dprint(L"esl:0x%llx->0x%llx\n", esl, nesl); | |
370 | esl = (EFI_SIGNATURE_LIST *)nesl; | |
371 | esl_end_pos = pos + esl->SignatureListSize; | |
372 | dprint(L"pos:0x%llx->0x%llx\n", pos, pos + sizeof(*esl)); | |
373 | pos += sizeof(*esl); | |
374 | } | |
375 | esd = (EFI_SIGNATURE_DATA *)(FullData + pos); | |
376 | if (pos >= FullDataSize) | |
377 | break; | |
378 | if (esl->SignatureListSize == 0 || esl->SignatureSize == 0) | |
379 | break; | |
380 | ||
381 | dprint(L"esl[%lu] 0x%llx = {sls=0x%lx, ss=0x%lx} esd:0x%llx\n", | |
382 | i, esl, esl->SignatureListSize, esl->SignatureSize, esd); | |
383 | ||
384 | if (!only_first) { | |
385 | SPrint(namen, namelen, L"%s%lu", name, i); | |
386 | namen[namelen-1] = 0; | |
387 | /* uggggh */ | |
388 | UINTN j; | |
389 | for (j = 0; j < namelen; j++) | |
390 | namen8[j] = (CHAR8)(namen[j] & 0xff); | |
391 | namen8[namelen - 1] = 0; | |
392 | } | |
393 | ||
394 | /* | |
395 | * In case max_var_sz is computed dynamically, refresh the | |
396 | * value here. | |
397 | */ | |
398 | efi_status = get_max_var_sz(attrs, &max_var_sz); | |
399 | if (EFI_ERROR(efi_status)) { | |
400 | LogError(L"Could not get maximum variable size: %r", | |
401 | efi_status); | |
402 | if (!only_first) { | |
403 | FreePool(namen); | |
404 | FreePool(namen8); | |
405 | } | |
406 | return efi_status; | |
407 | } | |
408 | ||
409 | /* The name counts towards the size of the variable */ | |
410 | max_var_sz -= (StrLen(namen) + 1) * 2; | |
411 | dprint(L"max_var_sz - name: %lx\n", max_var_sz); | |
412 | ||
413 | SIZE_T howmany; | |
414 | howmany = MIN((max_var_sz - sizeof(*esl)) / esl->SignatureSize, | |
415 | (esl_end_pos - pos) / esl->SignatureSize); | |
416 | if (howmany == 0) { | |
417 | /* No signatures from this ESL can be mirrored in to a | |
418 | * single variable, so skip it. | |
419 | */ | |
420 | dprint(L"skipping esl, pos:0x%llx->0x%llx\n", pos, esl_end_pos); | |
421 | pos = esl_end_pos; | |
422 | continue; | |
423 | } | |
424 | ||
425 | UINTN adj = howmany * esl->SignatureSize; | |
426 | ||
427 | if (!only_first && i == 0) { | |
428 | dprint(L"pos:0x%llx->0x%llx\n", pos, pos + adj); | |
429 | pos += adj; | |
430 | i++; | |
431 | continue; | |
432 | ||
433 | } | |
434 | ||
435 | efi_status = mirror_one_esl(namen, guid, attrs, | |
436 | esl, esd, howmany); | |
437 | dprint(L"esd:0x%llx adj:0x%llx\n", esd, adj); | |
438 | if (EFI_ERROR(efi_status)) { | |
439 | LogError(L"Could not mirror mok variable \"%s\": %r\n", | |
440 | namen, efi_status); | |
441 | break; | |
442 | } | |
443 | ||
444 | dprint(L"pos:0x%llx->0x%llx\n", pos, pos + adj); | |
445 | pos += adj; | |
446 | did_one = TRUE; | |
447 | if (only_first) | |
448 | break; | |
449 | i++; | |
450 | } | |
451 | ||
452 | if (EFI_ERROR(efi_status)) { | |
453 | perror(L"Failed to set %s: %r\n", name, efi_status); | |
454 | } else if (only_first && !did_one) { | |
455 | /* | |
456 | * In this case we're going to try to create a | |
457 | * dummy variable so that there's one there. It | |
458 | * may or may not work, because on some firmware | |
459 | * builds when the SetVariable call above fails it | |
460 | * does actually set the variable(!), so aside from | |
461 | * not using the allocation if it doesn't work, we | |
462 | * don't care about failures here. | |
463 | */ | |
464 | UINT8 *var; | |
465 | UINTN varsz; | |
f892ac66 | 466 | |
031e5cce SM |
467 | efi_status = variable_create_esl_with_one_signature( |
468 | null_sha256, sizeof(null_sha256), | |
469 | &EFI_CERT_SHA256_GUID, &SHIM_LOCK_GUID, | |
470 | &var, &varsz); | |
471 | /* | |
472 | * from here we don't really care if it works or | |
473 | * doesn't. | |
474 | */ | |
475 | if (!EFI_ERROR(efi_status) && var && varsz) { | |
476 | efi_status = SetVariable(name, guid, | |
477 | EFI_VARIABLE_BOOTSERVICE_ACCESS | |
478 | | EFI_VARIABLE_RUNTIME_ACCESS, | |
479 | varsz, var); | |
480 | FreePool(var); | |
481 | } | |
482 | } | |
483 | return efi_status; | |
484 | } | |
485 | ||
486 | ||
487 | static EFI_STATUS NONNULL(1) | |
488 | mirror_one_mok_variable(struct mok_state_variable *v, | |
489 | BOOLEAN only_first) | |
490 | { | |
491 | EFI_STATUS efi_status = EFI_SUCCESS; | |
492 | uint8_t *FullData = NULL; | |
493 | size_t FullDataSize = 0; | |
494 | vendor_addend_category_t addend_category = VENDOR_ADDEND_NONE; | |
495 | uint8_t *p = NULL; | |
496 | uint32_t attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
497 | EFI_VARIABLE_RUNTIME_ACCESS; | |
498 | BOOLEAN measure = v->flags & MOK_VARIABLE_MEASURE; | |
499 | BOOLEAN log = v->flags & MOK_VARIABLE_LOG; | |
500 | size_t build_cert_esl_sz = 0, addend_esl_sz = 0; | |
501 | bool reuse = FALSE; | |
502 | ||
503 | if (v->categorize_addend) | |
504 | addend_category = v->categorize_addend(v); | |
505 | ||
506 | /* | |
507 | * if it is, there's more data | |
508 | */ | |
509 | if (v->flags & MOK_MIRROR_KEYDB) { | |
510 | ||
511 | /* | |
512 | * We're mirroring (into) an efi security database, aka an | |
513 | * array of EFI_SIGNATURE_LIST. Its layout goes like: | |
514 | * | |
515 | * existing_variable_data | |
516 | * existing_variable_data_size | |
517 | * if flags & MOK_MIRROR_KEYDB | |
518 | * if build_cert | |
519 | * build_cert_esl | |
520 | * build_cert_header (always sz=0) | |
521 | * build_cert_esd[0] { owner, data } | |
522 | * if addend==vendor_db | |
523 | * for n=[1..N] | |
524 | * vendor_db_esl_n | |
525 | * vendor_db_header_n (always sz=0) | |
526 | * vendor_db_esd_n[m] {{ owner, data }, ... } | |
527 | * elif addend==vendor_cert | |
528 | * vendor_cert_esl | |
529 | * vendor_cert_header (always sz=0) | |
530 | * vendor_cert_esd[1] { owner, data } | |
531 | * | |
532 | * first we determine the size of the variable, then alloc | |
533 | * and add the data. | |
534 | */ | |
535 | ||
536 | /* | |
537 | * *first* vendor_db or vendor_cert | |
538 | */ | |
539 | switch (addend_category) { | |
540 | case VENDOR_ADDEND_DB: | |
541 | /* | |
542 | * if it's an ESL already, we use it wholesale | |
543 | */ | |
544 | FullDataSize += *v->addend_size; | |
545 | dprint(L"FullDataSize:%lu FullData:0x%llx\n", | |
546 | FullDataSize, FullData); | |
547 | break; | |
548 | case VENDOR_ADDEND_X509: | |
549 | efi_status = fill_esl_with_one_signature(*v->addend, | |
550 | *v->addend_size, | |
551 | &EFI_CERT_TYPE_X509_GUID, | |
552 | &SHIM_LOCK_GUID, | |
553 | NULL, | |
554 | &addend_esl_sz); | |
555 | if (efi_status != EFI_BUFFER_TOO_SMALL) { | |
556 | perror(L"Could not add built-in cert to %s: %r\n", | |
557 | v->name, efi_status); | |
558 | return efi_status; | |
559 | } | |
560 | FullDataSize += addend_esl_sz; | |
561 | dprint(L"FullDataSize:%lu FullData:0x%llx\n", | |
562 | FullDataSize, FullData); | |
563 | break; | |
564 | default: | |
565 | case VENDOR_ADDEND_NONE: | |
566 | dprint(L"FullDataSize:%lu FullData:0x%llx\n", | |
567 | FullDataSize, FullData); | |
568 | break; | |
569 | } | |
570 | ||
571 | /* | |
572 | * then the build cert if it's there | |
573 | */ | |
574 | if (should_mirror_build_cert(v)) { | |
575 | efi_status = fill_esl_with_one_signature(*v->build_cert, | |
576 | *v->build_cert_size, | |
577 | &EFI_CERT_TYPE_X509_GUID, | |
578 | &SHIM_LOCK_GUID, | |
579 | NULL, &build_cert_esl_sz); | |
580 | if (efi_status != EFI_BUFFER_TOO_SMALL) { | |
581 | perror(L"Could not add built-in cert to %s: %r\n", | |
582 | v->name, efi_status); | |
583 | return efi_status; | |
584 | } | |
585 | FullDataSize += build_cert_esl_sz; | |
586 | dprint(L"FullDataSize:0x%lx FullData:0x%llx\n", | |
587 | FullDataSize, FullData); | |
588 | } | |
589 | ||
590 | } | |
591 | ||
592 | /* | |
593 | * we're always mirroring the original data, whether this is an efi | |
594 | * security database or not | |
595 | */ | |
596 | dprint(L"v->name:\"%s\" v->rtname:\"%s\"\n", v->name, v->rtname); | |
597 | dprint(L"v->data_size:%lu v->data:0x%llx\n", v->data_size, v->data); | |
598 | dprint(L"FullDataSize:%lu FullData:0x%llx\n", FullDataSize, FullData); | |
599 | if (v->data_size) { | |
600 | FullDataSize += v->data_size; | |
601 | dprint(L"FullDataSize:%lu FullData:0x%llx\n", | |
602 | FullDataSize, FullData); | |
603 | } | |
604 | if (v->data_size == FullDataSize) | |
605 | reuse = TRUE; | |
606 | ||
607 | /* | |
608 | * Now we have the full size | |
609 | */ | |
610 | if (FullDataSize) { | |
611 | /* | |
612 | * allocate the buffer, or use the old one if it's just the | |
613 | * existing data. | |
614 | */ | |
615 | if (FullDataSize == v->data_size) { | |
616 | FullData = v->data; | |
617 | FullDataSize = v->data_size; | |
618 | p = FullData + FullDataSize; | |
619 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
620 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
621 | v->data = NULL; | |
622 | v->data_size = 0; | |
623 | } else { | |
624 | dprint(L"FullDataSize:%lu FullData:0x%llx allocating FullData\n", | |
625 | FullDataSize, FullData); | |
626 | /* | |
627 | * make sure we've got some zeroes at the end, just | |
628 | * in case. | |
629 | */ | |
630 | UINTN new, allocsz; | |
631 | ||
632 | allocsz = FullDataSize + sizeof(EFI_SIGNATURE_LIST); | |
633 | new = ALIGN_VALUE(allocsz, 4096); | |
634 | allocsz = new == allocsz ? new + 4096 : new; | |
635 | FullData = AllocateZeroPool(allocsz); | |
636 | if (!FullData) { | |
637 | perror(L"Failed to allocate %lu bytes for %s\n", | |
638 | FullDataSize, v->name); | |
639 | return EFI_OUT_OF_RESOURCES; | |
640 | } | |
641 | p = FullData; | |
642 | } | |
643 | } | |
644 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
645 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
646 | ||
647 | /* | |
648 | * Now fill it. | |
649 | */ | |
650 | if (v->flags & MOK_MIRROR_KEYDB) { | |
651 | /* | |
652 | * first vendor_cert or vendor_db | |
653 | */ | |
654 | switch (addend_category) { | |
655 | case VENDOR_ADDEND_DB: | |
656 | CopyMem(p, *v->addend, *v->addend_size); | |
657 | p += *v->addend_size; | |
658 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
659 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
660 | break; | |
661 | case VENDOR_ADDEND_X509: | |
662 | efi_status = fill_esl_with_one_signature(*v->addend, | |
663 | *v->addend_size, | |
664 | &EFI_CERT_TYPE_X509_GUID, | |
665 | &SHIM_LOCK_GUID, | |
666 | p, &addend_esl_sz); | |
667 | if (EFI_ERROR(efi_status)) { | |
668 | perror(L"Could not add built-in cert to %s: %r\n", | |
669 | v->name, efi_status); | |
670 | return efi_status; | |
671 | } | |
672 | p += addend_esl_sz; | |
673 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
674 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
675 | break; | |
676 | default: | |
677 | case VENDOR_ADDEND_NONE: | |
678 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
679 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
680 | break; | |
681 | } | |
682 | ||
683 | /* | |
684 | * then is the build cert | |
685 | */ | |
686 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
687 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
688 | if (should_mirror_build_cert(v)) { | |
689 | efi_status = fill_esl_with_one_signature(*v->build_cert, | |
690 | *v->build_cert_size, | |
691 | &EFI_CERT_TYPE_X509_GUID, | |
692 | &SHIM_LOCK_GUID, | |
693 | p, &build_cert_esl_sz); | |
694 | if (EFI_ERROR(efi_status)) { | |
695 | perror(L"Could not add built-in cert to %s: %r\n", | |
696 | v->name, efi_status); | |
697 | return efi_status; | |
698 | } | |
699 | p += build_cert_esl_sz; | |
700 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
701 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
702 | } | |
703 | } | |
704 | ||
705 | /* | |
706 | * last bit is existing data, unless it's the only thing, | |
707 | * in which case it's already there. | |
708 | */ | |
709 | if (!reuse) { | |
710 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
711 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
712 | if (v->data && v->data_size) { | |
f892ac66 MTL |
713 | CopyMem(p, v->data, v->data_size); |
714 | p += v->data_size; | |
715 | } | |
031e5cce SM |
716 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", |
717 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
f892ac66 MTL |
718 | } |
719 | ||
031e5cce SM |
720 | /* |
721 | * We always want to create our key databases, so in this case we | |
722 | * need a dummy entry | |
723 | */ | |
724 | if ((v->flags & MOK_MIRROR_KEYDB) && FullDataSize == 0) { | |
725 | efi_status = variable_create_esl_with_one_signature( | |
726 | null_sha256, sizeof(null_sha256), | |
727 | &EFI_CERT_SHA256_GUID, &SHIM_LOCK_GUID, | |
728 | &FullData, &FullDataSize); | |
f892ac66 | 729 | if (EFI_ERROR(efi_status)) { |
031e5cce SM |
730 | perror(L"Failed to allocate %lu bytes for %s\n", |
731 | FullDataSize, v->name); | |
732 | return efi_status; | |
f892ac66 | 733 | } |
031e5cce SM |
734 | p = FullData + FullDataSize; |
735 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
736 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
737 | } | |
738 | ||
739 | dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", | |
740 | FullDataSize, FullData, p, p-(uintptr_t)FullData); | |
741 | if (FullDataSize && v->flags & MOK_MIRROR_KEYDB) { | |
742 | dprint(L"calling mirror_mok_db(\"%s\", datasz=%lu)\n", | |
743 | v->rtname, FullDataSize); | |
744 | efi_status = mirror_mok_db(v->rtname, (CHAR8 *)v->rtname8, v->guid, | |
745 | attrs, FullData, FullDataSize, | |
746 | only_first); | |
747 | dprint(L"mirror_mok_db(\"%s\", datasz=%lu) returned %r\n", | |
748 | v->rtname, FullDataSize, efi_status); | |
749 | } else if (FullDataSize && only_first) { | |
750 | efi_status = SetVariable(v->rtname, v->guid, attrs, | |
751 | FullDataSize, FullData); | |
f892ac66 | 752 | } |
031e5cce SM |
753 | if (FullDataSize && only_first) { |
754 | if (measure) { | |
755 | /* | |
756 | * Measure this into PCR 7 in the Microsoft format | |
757 | */ | |
758 | efi_status = tpm_measure_variable(v->name, *v->guid, | |
759 | FullDataSize, FullData); | |
760 | if (EFI_ERROR(efi_status)) { | |
761 | dprint(L"tpm_measure_variable(\"%s\",%lu,0x%llx)->%r\n", | |
762 | v->name, FullDataSize, FullData, efi_status); | |
763 | return efi_status; | |
764 | } | |
765 | } | |
f892ac66 | 766 | |
031e5cce SM |
767 | if (log) { |
768 | /* | |
769 | * Log this variable into whichever PCR the table | |
770 | * says. | |
771 | */ | |
772 | EFI_PHYSICAL_ADDRESS datap = | |
773 | (EFI_PHYSICAL_ADDRESS)(UINTN)FullData, | |
774 | efi_status = tpm_log_event(datap, FullDataSize, | |
775 | v->pcr, (CHAR8 *)v->name8); | |
776 | if (EFI_ERROR(efi_status)) { | |
777 | dprint(L"tpm_log_event(0x%llx, %lu, %lu, \"%s\")->%r\n", | |
778 | FullData, FullDataSize, v->pcr, v->name, | |
779 | efi_status); | |
780 | return efi_status; | |
781 | } | |
782 | } | |
783 | ||
784 | } | |
785 | if (v->data && v->data_size && v->data != FullData) { | |
786 | FreePool(v->data); | |
787 | v->data = NULL; | |
788 | v->data_size = 0; | |
789 | } | |
790 | v->data = FullData; | |
791 | v->data_size = FullDataSize; | |
792 | dprint(L"returning %r\n", efi_status); | |
f892ac66 MTL |
793 | return efi_status; |
794 | } | |
795 | ||
031e5cce SM |
796 | /* |
797 | * Mirror a variable if it has an rtname, and preserve any | |
798 | * EFI_SECURITY_VIOLATION status at the same time. | |
799 | */ | |
800 | static EFI_STATUS NONNULL(1) | |
801 | maybe_mirror_one_mok_variable(struct mok_state_variable *v, | |
802 | EFI_STATUS ret, BOOLEAN only_first) | |
803 | { | |
804 | EFI_STATUS efi_status; | |
805 | BOOLEAN present = FALSE; | |
806 | ||
807 | if (v->rtname) { | |
8529e0f7 | 808 | if (only_first && (v->flags & MOK_MIRROR_DELETE_FIRST)) { |
031e5cce SM |
809 | dprint(L"deleting \"%s\"\n", v->rtname); |
810 | efi_status = LibDeleteVariable(v->rtname, v->guid); | |
811 | dprint(L"LibDeleteVariable(\"%s\",...) => %r\n", v->rtname, efi_status); | |
812 | } | |
813 | ||
814 | efi_status = mirror_one_mok_variable(v, only_first); | |
815 | if (EFI_ERROR(efi_status)) { | |
816 | if (ret != EFI_SECURITY_VIOLATION) | |
817 | ret = efi_status; | |
818 | perror(L"Could not create %s: %r\n", v->rtname, | |
819 | efi_status); | |
820 | } | |
821 | } | |
822 | ||
823 | present = (v->data && v->data_size) ? TRUE : FALSE; | |
824 | if (!present) | |
825 | return ret; | |
826 | ||
827 | if (v->data_size == sizeof(UINT8) && v->state) { | |
828 | *v->state = v->data[0]; | |
829 | } | |
830 | ||
831 | return ret; | |
832 | } | |
833 | ||
031e5cce SM |
834 | EFI_STATUS import_one_mok_state(struct mok_state_variable *v, |
835 | BOOLEAN only_first) | |
836 | { | |
837 | EFI_STATUS ret = EFI_SUCCESS; | |
838 | EFI_STATUS efi_status; | |
839 | ||
031e5cce SM |
840 | UINT32 attrs = 0; |
841 | BOOLEAN delete = FALSE; | |
842 | ||
843 | dprint(L"importing mok state for \"%s\"\n", v->name); | |
844 | ||
8529e0f7 SM |
845 | if (!v->data && !v->data_size) { |
846 | efi_status = get_variable_attr(v->name, | |
847 | &v->data, &v->data_size, | |
848 | *v->guid, &attrs); | |
849 | if (efi_status == EFI_NOT_FOUND) { | |
850 | v->data = NULL; | |
851 | v->data_size = 0; | |
852 | } else if (EFI_ERROR(efi_status)) { | |
853 | perror(L"Could not verify %s: %r\n", v->name, | |
854 | efi_status); | |
031e5cce | 855 | delete = TRUE; |
8529e0f7 SM |
856 | } else { |
857 | if (!(attrs & v->yes_attr)) { | |
858 | perror(L"Variable %s is missing attributes:\n", | |
859 | v->name); | |
860 | perror(L" 0x%08x should have 0x%08x set.\n", | |
861 | attrs, v->yes_attr); | |
862 | delete = TRUE; | |
863 | } | |
864 | if (attrs & v->no_attr) { | |
865 | perror(L"Variable %s has incorrect attribute:\n", | |
866 | v->name); | |
867 | perror(L" 0x%08x should not have 0x%08x set.\n", | |
868 | attrs, v->no_attr); | |
869 | delete = TRUE; | |
870 | } | |
031e5cce SM |
871 | } |
872 | } | |
873 | if (delete == TRUE) { | |
874 | perror(L"Deleting bad variable %s\n", v->name); | |
875 | efi_status = LibDeleteVariable(v->name, v->guid); | |
876 | if (EFI_ERROR(efi_status)) { | |
877 | perror(L"Failed to erase %s\n", v->name); | |
878 | ret = EFI_SECURITY_VIOLATION; | |
879 | } | |
880 | FreePool(v->data); | |
881 | v->data = NULL; | |
882 | v->data_size = 0; | |
883 | } | |
884 | ||
885 | dprint(L"maybe mirroring \"%s\". original data:\n", v->name); | |
8529e0f7 SM |
886 | if (v->data && v->data_size) { |
887 | dhexdumpat(v->data, v->data_size, 0); | |
888 | } | |
031e5cce SM |
889 | |
890 | ret = maybe_mirror_one_mok_variable(v, ret, only_first); | |
891 | dprint(L"returning %r\n", ret); | |
892 | return ret; | |
893 | } | |
894 | ||
f892ac66 MTL |
895 | /* |
896 | * Verify our non-volatile MoK state. This checks the variables above | |
897 | * accessable and have valid attributes. If they don't, it removes | |
898 | * them. If any of them can't be removed, our ability to do this is | |
899 | * comprimized, so return EFI_SECURITY_VIOLATION. | |
900 | * | |
901 | * Any variable that isn't deleted and has ->measure == TRUE is then | |
902 | * measured into the tpm. | |
903 | * | |
904 | * Any variable with a ->rtname element is then mirrored to a | |
905 | * runtime-accessable version. The new ones won't be marked NV, so the OS | |
906 | * can't modify them. | |
907 | */ | |
908 | EFI_STATUS import_mok_state(EFI_HANDLE image_handle) | |
909 | { | |
910 | UINTN i; | |
911 | EFI_STATUS ret = EFI_SUCCESS; | |
912 | EFI_STATUS efi_status; | |
913 | ||
914 | user_insecure_mode = 0; | |
915 | ignore_db = 0; | |
8529e0f7 | 916 | trust_mok_list = 0; |
f892ac66 | 917 | |
031e5cce SM |
918 | UINT64 config_sz = 0; |
919 | UINT8 *config_table = NULL; | |
920 | size_t npages = 0; | |
921 | struct mok_variable_config_entry config_template; | |
922 | ||
923 | dprint(L"importing minimal mok state variables\n"); | |
f892ac66 MTL |
924 | for (i = 0; mok_state_variables[i].name != NULL; i++) { |
925 | struct mok_state_variable *v = &mok_state_variables[i]; | |
f892ac66 | 926 | |
031e5cce | 927 | efi_status = import_one_mok_state(v, TRUE); |
f892ac66 | 928 | if (EFI_ERROR(efi_status)) { |
031e5cce SM |
929 | dprint(L"import_one_mok_state(ih, \"%s\", TRUE): %r\n", |
930 | v->rtname); | |
f892ac66 MTL |
931 | /* |
932 | * don't clobber EFI_SECURITY_VIOLATION from some | |
933 | * other variable in the list. | |
934 | */ | |
935 | if (ret != EFI_SECURITY_VIOLATION) | |
936 | ret = efi_status; | |
f892ac66 MTL |
937 | } |
938 | ||
031e5cce SM |
939 | if (v->data && v->data_size) { |
940 | config_sz += v->data_size; | |
941 | config_sz += sizeof(config_template); | |
f892ac66 | 942 | } |
031e5cce | 943 | } |
f892ac66 | 944 | |
031e5cce SM |
945 | /* |
946 | * Alright, so we're going to copy these to a config table. The | |
947 | * table is a packed array of N+1 struct mok_variable_config_entry | |
948 | * items, with the last item having all zero's in name and | |
949 | * data_size. | |
950 | */ | |
951 | if (config_sz) { | |
952 | config_sz += sizeof(config_template); | |
953 | npages = ALIGN_VALUE(config_sz, PAGE_SIZE) >> EFI_PAGE_SHIFT; | |
954 | config_table = NULL; | |
8529e0f7 SM |
955 | efi_status = BS->AllocatePages( |
956 | AllocateAnyPages, EfiRuntimeServicesData, npages, | |
957 | (EFI_PHYSICAL_ADDRESS *)&config_table); | |
031e5cce SM |
958 | if (EFI_ERROR(efi_status) || !config_table) { |
959 | console_print(L"Allocating %lu pages for mok config table failed: %r\n", | |
960 | npages, efi_status); | |
961 | config_table = NULL; | |
962 | } else { | |
963 | ZeroMem(config_table, npages << EFI_PAGE_SHIFT); | |
f892ac66 | 964 | } |
031e5cce | 965 | } |
f892ac66 | 966 | |
031e5cce SM |
967 | UINT8 *p = (UINT8 *)config_table; |
968 | for (i = 0; p && mok_state_variables[i].name != NULL; i++) { | |
969 | struct mok_state_variable *v = &mok_state_variables[i]; | |
f892ac66 | 970 | |
031e5cce SM |
971 | ZeroMem(&config_template, sizeof(config_template)); |
972 | strncpy(config_template.name, (CHAR8 *)v->rtname8, 255); | |
973 | config_template.name[255] = '\0'; | |
f892ac66 | 974 | |
031e5cce | 975 | config_template.data_size = v->data_size; |
f892ac66 | 976 | |
8529e0f7 SM |
977 | if (v->data && v->data_size) { |
978 | CopyMem(p, &config_template, sizeof(config_template)); | |
979 | p += sizeof(config_template); | |
980 | CopyMem(p, v->data, v->data_size); | |
981 | p += v->data_size; | |
982 | } | |
031e5cce SM |
983 | } |
984 | if (p) { | |
985 | ZeroMem(&config_template, sizeof(config_template)); | |
986 | CopyMem(p, &config_template, sizeof(config_template)); | |
f892ac66 | 987 | |
8529e0f7 SM |
988 | efi_status = BS->InstallConfigurationTable(&MOK_VARIABLE_STORE, |
989 | config_table); | |
031e5cce SM |
990 | if (EFI_ERROR(efi_status)) { |
991 | console_print(L"Couldn't install MoK configuration table\n"); | |
f892ac66 MTL |
992 | } |
993 | } | |
994 | ||
031e5cce SM |
995 | /* |
996 | * This is really just to make it easy for userland. | |
997 | */ | |
998 | dprint(L"importing full mok state variables\n"); | |
999 | for (i = 0; mok_state_variables[i].name != NULL; i++) { | |
1000 | struct mok_state_variable *v = &mok_state_variables[i]; | |
1001 | ||
1002 | import_one_mok_state(v, FALSE); | |
1003 | } | |
1004 | ||
f892ac66 MTL |
1005 | /* |
1006 | * Enter MokManager if necessary. Any actual *changes* here will | |
1007 | * cause MokManager to demand a machine reboot, so this is safe to | |
1008 | * have after the entire loop. | |
1009 | */ | |
031e5cce | 1010 | dprint(L"checking mok request\n"); |
f892ac66 | 1011 | efi_status = check_mok_request(image_handle); |
031e5cce | 1012 | dprint(L"mok returned %r\n", efi_status); |
f892ac66 | 1013 | if (EFI_ERROR(efi_status)) { |
031e5cce SM |
1014 | /* |
1015 | * don't clobber EFI_SECURITY_VIOLATION | |
1016 | */ | |
f892ac66 MTL |
1017 | if (ret != EFI_SECURITY_VIOLATION) |
1018 | ret = efi_status; | |
1019 | return ret; | |
1020 | } | |
1021 | ||
031e5cce | 1022 | dprint(L"returning %r\n", ret); |
f892ac66 MTL |
1023 | return ret; |
1024 | } | |
1025 | ||
031e5cce | 1026 | // vim:fenc=utf-8:tw=75:noet |