]>
Commit | Line | Data |
---|---|---|
031e5cce SM |
1 | // SPDX-License-Identifier: BSD-2-Clause-Patent |
2 | /* | |
3 | * sbat.c - parse SBAT data from the .sbat section data | |
4 | */ | |
5 | ||
6 | #include "shim.h" | |
7 | ||
2dd2f760 SM |
8 | extern struct { |
9 | UINT32 previous_offset; | |
10 | UINT32 latest_offset; | |
11 | } sbat_var_payload_header; | |
12 | ||
031e5cce SM |
13 | EFI_STATUS |
14 | parse_sbat_section(char *section_base, size_t section_size, | |
15 | size_t *n_entries, | |
16 | struct sbat_section_entry ***entriesp) | |
17 | { | |
18 | struct sbat_section_entry *entry = NULL, **entries; | |
19 | EFI_STATUS efi_status = EFI_SUCCESS; | |
20 | list_t csv, *pos = NULL; | |
21 | char * end = section_base + section_size - 1; | |
22 | size_t allocsz = 0; | |
23 | size_t n; | |
24 | char *strtab; | |
25 | ||
8119f718 SM |
26 | if (!section_base || !section_size || !n_entries || !entriesp) { |
27 | dprint(L"section_base:0x%lx section_size:0x%lx\n", | |
28 | section_base, section_size); | |
29 | dprint(L"n_entries:0x%lx entriesp:0x%lx\n", | |
30 | n_entries, entriesp); | |
031e5cce | 31 | return EFI_INVALID_PARAMETER; |
8119f718 | 32 | } |
031e5cce SM |
33 | |
34 | INIT_LIST_HEAD(&csv); | |
35 | ||
36 | efi_status = | |
37 | parse_csv_data(section_base, end, SBAT_SECTION_COLUMNS, &csv); | |
38 | if (EFI_ERROR(efi_status)) { | |
8119f718 | 39 | dprint(L"parse_csv_data failed: %r\n", efi_status); |
031e5cce SM |
40 | return efi_status; |
41 | } | |
42 | ||
43 | n = 0; | |
44 | list_for_each(pos, &csv) { | |
45 | struct csv_row * row; | |
46 | size_t i; | |
47 | ||
48 | row = list_entry(pos, struct csv_row, list); | |
49 | ||
50 | if (row->n_columns < SBAT_SECTION_COLUMNS) { | |
51 | efi_status = EFI_INVALID_PARAMETER; | |
8119f718 SM |
52 | dprint(L"row->n_columns:%lu SBAT_SECTION_COLUMNS:%lu\n", |
53 | row->n_columns, SBAT_SECTION_COLUMNS); | |
031e5cce SM |
54 | goto err; |
55 | } | |
56 | ||
57 | allocsz += sizeof(struct sbat_section_entry *); | |
58 | allocsz += sizeof(struct sbat_section_entry); | |
59 | for (i = 0; i < row->n_columns; i++) { | |
60 | if (row->columns[i][0] == '\000') { | |
8119f718 | 61 | dprint(L"row[%lu].columns[%lu][0] == '\\000'\n", n, i); |
031e5cce SM |
62 | efi_status = EFI_INVALID_PARAMETER; |
63 | goto err; | |
64 | } | |
65 | allocsz += strlen(row->columns[i]) + 1; | |
66 | } | |
67 | n++; | |
68 | } | |
69 | ||
70 | strtab = AllocateZeroPool(allocsz); | |
71 | if (!strtab) { | |
72 | efi_status = EFI_OUT_OF_RESOURCES; | |
73 | goto err; | |
74 | } | |
75 | ||
76 | entries = (struct sbat_section_entry **)strtab; | |
77 | strtab += sizeof(struct sbat_section_entry *) * n; | |
78 | entry = (struct sbat_section_entry *)strtab; | |
79 | strtab += sizeof(struct sbat_section_entry) * n; | |
80 | n = 0; | |
81 | ||
82 | list_for_each(pos, &csv) { | |
83 | struct csv_row * row; | |
84 | size_t i; | |
85 | const char **ptrs[] = { | |
86 | &entry->component_name, | |
87 | &entry->component_generation, | |
88 | &entry->vendor_name, | |
89 | &entry->vendor_package_name, | |
90 | &entry->vendor_version, | |
91 | &entry->vendor_url, | |
92 | }; | |
93 | ||
94 | ||
95 | row = list_entry(pos, struct csv_row, list); | |
96 | for (i = 0; i < row->n_columns; i++) { | |
97 | *(ptrs[i]) = strtab; | |
98 | strtab = stpcpy(strtab, row->columns[i]) + 1; | |
99 | } | |
100 | entries[n] = entry; | |
101 | entry++; | |
102 | n++; | |
103 | } | |
104 | *entriesp = entries; | |
105 | *n_entries = n; | |
106 | err: | |
107 | free_csv_list(&csv); | |
108 | return efi_status; | |
109 | } | |
110 | ||
111 | void | |
112 | cleanup_sbat_section_entries(size_t n, struct sbat_section_entry **entries) | |
113 | { | |
114 | if (!n || !entries) | |
115 | return; | |
116 | ||
117 | FreePool(entries); | |
118 | } | |
119 | ||
120 | EFI_STATUS | |
e6ace38a | 121 | verify_single_entry(struct sbat_section_entry *entry, struct sbat_var_entry *sbat_var_entry, bool *found) |
031e5cce SM |
122 | { |
123 | UINT16 sbat_gen, sbat_var_gen; | |
124 | ||
125 | if (strcmp((const char *)entry->component_name, (const char *)sbat_var_entry->component_name) == 0) { | |
126 | dprint(L"component %a has a matching SBAT variable entry, verifying\n", | |
127 | entry->component_name); | |
e6ace38a | 128 | *found = true; |
031e5cce SM |
129 | |
130 | /* | |
131 | * atoi returns zero for failed conversion, so essentially | |
132 | * badly parsed component_generation will be treated as zero | |
133 | */ | |
134 | sbat_gen = atoi((const char *)entry->component_generation); | |
135 | sbat_var_gen = atoi((const char *)sbat_var_entry->component_generation); | |
136 | ||
137 | if (sbat_gen < sbat_var_gen) { | |
8119f718 SM |
138 | dprint(L"component %a, generation %d, was revoked by %s variable\n", |
139 | entry->component_name, sbat_gen, SBAT_VAR_NAME); | |
031e5cce SM |
140 | LogError(L"image did not pass SBAT verification\n"); |
141 | return EFI_SECURITY_VIOLATION; | |
142 | } | |
143 | } | |
144 | return EFI_SUCCESS; | |
145 | } | |
146 | ||
147 | void | |
148 | cleanup_sbat_var(list_t *entries) | |
149 | { | |
150 | list_t *pos = NULL, *tmp = NULL; | |
151 | struct sbat_var_entry *entry; | |
152 | void *first = NULL; | |
153 | ||
154 | list_for_each_safe(pos, tmp, entries) { | |
155 | entry = list_entry(pos, struct sbat_var_entry, list); | |
156 | ||
8119f718 | 157 | if (first == NULL || (uintptr_t)entry < (uintptr_t)first) |
031e5cce SM |
158 | first = entry; |
159 | ||
160 | list_del(&entry->list); | |
161 | } | |
162 | if (first) | |
163 | FreePool(first); | |
164 | } | |
165 | ||
166 | EFI_STATUS | |
167 | verify_sbat_helper(list_t *local_sbat_var, size_t n, struct sbat_section_entry **entries) | |
168 | { | |
169 | unsigned int i; | |
170 | list_t *pos = NULL; | |
171 | EFI_STATUS efi_status = EFI_SUCCESS; | |
172 | struct sbat_var_entry *sbat_var_entry; | |
173 | ||
174 | if (list_empty(local_sbat_var)) { | |
8119f718 | 175 | dprint(L"%s variable not present\n", SBAT_VAR_NAME); |
031e5cce SM |
176 | return EFI_SUCCESS; |
177 | } | |
178 | ||
179 | for (i = 0; i < n; i++) { | |
180 | list_for_each(pos, local_sbat_var) { | |
e6ace38a | 181 | bool found = false; |
031e5cce | 182 | sbat_var_entry = list_entry(pos, struct sbat_var_entry, list); |
e6ace38a | 183 | efi_status = verify_single_entry(entries[i], sbat_var_entry, &found); |
031e5cce SM |
184 | if (EFI_ERROR(efi_status)) |
185 | goto out; | |
e6ace38a SM |
186 | if (found) |
187 | break; | |
031e5cce SM |
188 | } |
189 | } | |
190 | ||
191 | out: | |
192 | dprint(L"finished verifying SBAT data: %r\n", efi_status); | |
193 | return efi_status; | |
194 | } | |
195 | ||
196 | EFI_STATUS | |
197 | verify_sbat(size_t n, struct sbat_section_entry **entries) | |
198 | { | |
199 | EFI_STATUS efi_status; | |
200 | ||
201 | efi_status = verify_sbat_helper(&sbat_var, n, entries); | |
202 | return efi_status; | |
203 | } | |
204 | ||
205 | EFI_STATUS | |
206 | parse_sbat_var_data(list_t *entry_list, UINT8 *data, UINTN datasize) | |
207 | { | |
208 | struct sbat_var_entry *entry = NULL, **entries; | |
209 | EFI_STATUS efi_status = EFI_SUCCESS; | |
210 | list_t csv, *pos = NULL; | |
211 | char * start = (char *)data; | |
212 | char * end = (char *)data + datasize - 1; | |
213 | size_t allocsz = 0; | |
214 | size_t n; | |
215 | char *strtab; | |
216 | ||
217 | if (!entry_list|| !data || datasize == 0) | |
218 | return EFI_INVALID_PARAMETER; | |
219 | ||
220 | INIT_LIST_HEAD(&csv); | |
221 | ||
222 | efi_status = parse_csv_data(start, end, SBAT_VAR_COLUMNS, &csv); | |
223 | if (EFI_ERROR(efi_status)) { | |
224 | return efi_status; | |
225 | } | |
226 | ||
227 | n = 0; | |
228 | list_for_each(pos, &csv) { | |
229 | struct csv_row * row; | |
230 | size_t i; | |
231 | ||
232 | row = list_entry(pos, struct csv_row, list); | |
233 | ||
234 | if (row->n_columns < SBAT_VAR_REQUIRED_COLUMNS) { | |
235 | efi_status = EFI_INVALID_PARAMETER; | |
236 | goto err; | |
237 | } | |
238 | ||
239 | ||
240 | allocsz += sizeof(struct sbat_var_entry *); | |
241 | allocsz += sizeof(struct sbat_var_entry); | |
242 | for (i = 0; i < row->n_columns; i++) { | |
243 | if (!row->columns[i][0]) { | |
244 | efi_status = EFI_INVALID_PARAMETER; | |
245 | goto err; | |
246 | } | |
247 | allocsz += strlen(row->columns[i]) + 1; | |
248 | } | |
249 | n++; | |
250 | } | |
251 | ||
252 | strtab = AllocateZeroPool(allocsz); | |
253 | if (!strtab) { | |
254 | efi_status = EFI_OUT_OF_RESOURCES; | |
255 | goto err; | |
256 | } | |
257 | ||
258 | INIT_LIST_HEAD(entry_list); | |
259 | ||
031e5cce SM |
260 | entry = (struct sbat_var_entry *)strtab; |
261 | strtab += sizeof(struct sbat_var_entry) * n; | |
8119f718 SM |
262 | entries = (struct sbat_var_entry **)strtab; |
263 | strtab += sizeof(struct sbat_var_entry *) * n; | |
031e5cce SM |
264 | n = 0; |
265 | ||
266 | list_for_each(pos, &csv) { | |
267 | struct csv_row * row; | |
268 | size_t i; | |
269 | const char **ptrs[] = { | |
270 | &entry->component_name, | |
271 | &entry->component_generation, | |
272 | &entry->sbat_datestamp, | |
273 | }; | |
274 | ||
275 | row = list_entry(pos, struct csv_row, list); | |
276 | for (i = 0; i < row->n_columns; i++) { | |
277 | *(ptrs[i]) = strtab; | |
278 | strtab = stpcpy(strtab, row->columns[i]) + 1; | |
279 | } | |
280 | INIT_LIST_HEAD(&entry->list); | |
281 | list_add_tail(&entry->list, entry_list); | |
282 | entries[n] = entry; | |
283 | entry++; | |
284 | n++; | |
285 | } | |
286 | err: | |
287 | free_csv_list(&csv); | |
288 | return efi_status; | |
289 | } | |
290 | ||
291 | EFI_STATUS | |
292 | parse_sbat_var(list_t *entries) | |
293 | { | |
294 | UINT8 *data = 0; | |
295 | UINTN datasize; | |
296 | EFI_STATUS efi_status; | |
e6ace38a | 297 | list_t *pos = NULL; |
031e5cce | 298 | |
8119f718 SM |
299 | if (!entries) { |
300 | dprint(L"entries is NULL\n"); | |
031e5cce | 301 | return EFI_INVALID_PARAMETER; |
8119f718 | 302 | } |
031e5cce SM |
303 | |
304 | efi_status = get_variable(SBAT_VAR_NAME, &data, &datasize, SHIM_LOCK_GUID); | |
305 | if (EFI_ERROR(efi_status)) { | |
306 | LogError(L"Failed to read SBAT variable\n", efi_status); | |
307 | return efi_status; | |
308 | } | |
309 | ||
310 | /* | |
311 | * We've intentionally made sure there's a NUL byte on all variable | |
312 | * allocations, so use that here. | |
313 | */ | |
e6ace38a SM |
314 | efi_status = parse_sbat_var_data(entries, data, datasize+1); |
315 | if (EFI_ERROR(efi_status)) | |
316 | return efi_status; | |
317 | ||
318 | dprint(L"SBAT variable entries:\n"); | |
319 | list_for_each(pos, entries) { | |
320 | struct sbat_var_entry *entry; | |
321 | ||
322 | entry = list_entry(pos, struct sbat_var_entry, list); | |
323 | dprint(L"%a, %a, %a\n", entry->component_name, | |
324 | entry->component_generation, entry->sbat_datestamp); | |
325 | } | |
326 | ||
327 | return efi_status; | |
031e5cce SM |
328 | } |
329 | ||
330 | static bool | |
331 | check_sbat_var_attributes(UINT32 attributes) | |
332 | { | |
333 | #ifdef ENABLE_SHIM_DEVEL | |
334 | return attributes == UEFI_VAR_NV_BS_RT; | |
335 | #else | |
336 | return attributes == UEFI_VAR_NV_BS || | |
337 | attributes == UEFI_VAR_NV_BS_TIMEAUTH; | |
338 | #endif | |
339 | } | |
340 | ||
e6ace38a SM |
341 | static char * |
342 | nth_sbat_field(char *str, size_t limit, int n) | |
343 | { | |
344 | size_t i; | |
345 | for (i = 0; i < limit && str[i] != '\0'; i++) { | |
346 | if (n == 0) | |
347 | return &str[i]; | |
348 | if (str[i] == ',') | |
349 | n--; | |
350 | } | |
351 | return &str[i]; | |
352 | } | |
353 | ||
8119f718 | 354 | bool |
e6ace38a SM |
355 | preserve_sbat_uefi_variable(UINT8 *sbat, UINTN sbatsize, UINT32 attributes, |
356 | char *sbat_var) | |
8119f718 | 357 | { |
e6ace38a SM |
358 | char *sbatc = (char *)sbat; |
359 | char *current_version, *new_version, | |
360 | *current_datestamp, *new_datestamp; | |
361 | int current_version_len, new_version_len; | |
362 | ||
363 | /* current metadata is not currupt somehow */ | |
364 | if (!check_sbat_var_attributes(attributes) || | |
365 | sbatsize < strlen(SBAT_VAR_ORIGINAL) || | |
366 | strncmp(sbatc, SBAT_VAR_SIG, strlen(SBAT_VAR_SIG))) | |
367 | return false; | |
368 | ||
369 | /* current metadata version not newer */ | |
370 | current_version = nth_sbat_field(sbatc, sbatsize, 1); | |
371 | new_version = nth_sbat_field(sbat_var, strlen(sbat_var)+1, 1); | |
372 | current_datestamp = nth_sbat_field(sbatc, sbatsize, 2); | |
373 | new_datestamp = nth_sbat_field(sbat_var, strlen(sbat_var)+1, 2); | |
374 | ||
375 | current_version_len = current_datestamp - current_version - 1; | |
376 | new_version_len = new_datestamp - new_version - 1; | |
377 | ||
378 | if (current_version_len > new_version_len || | |
379 | (current_version_len == new_version_len && | |
380 | strncmp(current_version, new_version, new_version_len) > 0)) | |
381 | return true; | |
382 | ||
383 | /* current datestamp is not newer or idential */ | |
384 | if (strncmp(current_datestamp, new_datestamp, | |
385 | strlen(SBAT_VAR_ORIGINAL_DATE)) >= 0) | |
386 | return true; | |
387 | ||
388 | return false; | |
389 | } | |
390 | ||
391 | static void | |
392 | clear_sbat_policy() | |
393 | { | |
394 | EFI_STATUS efi_status = EFI_SUCCESS; | |
395 | ||
396 | efi_status = del_variable(SBAT_POLICY, SHIM_LOCK_GUID); | |
397 | if (EFI_ERROR(efi_status)) | |
398 | console_error(L"Could not reset SBAT Policy", efi_status); | |
8119f718 SM |
399 | } |
400 | ||
031e5cce SM |
401 | EFI_STATUS |
402 | set_sbat_uefi_variable(void) | |
403 | { | |
404 | EFI_STATUS efi_status = EFI_SUCCESS; | |
405 | UINT32 attributes = 0; | |
406 | ||
2dd2f760 SM |
407 | char *sbat_var_previous; |
408 | char *sbat_var_latest; | |
409 | ||
031e5cce | 410 | UINT8 *sbat = NULL; |
e6ace38a | 411 | UINT8 *sbat_policy = NULL; |
031e5cce | 412 | UINTN sbatsize = 0; |
e6ace38a SM |
413 | UINTN sbat_policysize = 0; |
414 | ||
415 | char *sbat_var = NULL; | |
416 | bool reset_sbat = false; | |
417 | ||
2dd2f760 SM |
418 | sbat_var_previous = (char *)&sbat_var_payload_header + sbat_var_payload_header.previous_offset; |
419 | sbat_var_latest = (char *)&sbat_var_payload_header + sbat_var_payload_header.latest_offset; | |
420 | ||
e6ace38a SM |
421 | efi_status = get_variable_attr(SBAT_POLICY, &sbat_policy, |
422 | &sbat_policysize, SHIM_LOCK_GUID, | |
423 | &attributes); | |
424 | if (EFI_ERROR(efi_status)) { | |
425 | dprint("Default sbat policy: previous\n"); | |
2dd2f760 | 426 | sbat_var = sbat_var_previous; |
e6ace38a SM |
427 | } else { |
428 | switch (*sbat_policy) { | |
429 | case SBAT_POLICY_LATEST: | |
430 | dprint("Custom sbat policy: latest\n"); | |
2dd2f760 | 431 | sbat_var = sbat_var_latest; |
e6ace38a SM |
432 | clear_sbat_policy(); |
433 | break; | |
434 | case SBAT_POLICY_PREVIOUS: | |
435 | dprint("Custom sbat policy: previous\n"); | |
2dd2f760 | 436 | sbat_var = sbat_var_previous; |
e6ace38a SM |
437 | break; |
438 | case SBAT_POLICY_RESET: | |
439 | if (secure_mode()) { | |
440 | console_print(L"Cannot reset SBAT policy: Secure Boot is enabled.\n"); | |
2dd2f760 | 441 | sbat_var = sbat_var_previous; |
e6ace38a SM |
442 | } else { |
443 | dprint(L"Custom SBAT policy: reset OK\n"); | |
444 | reset_sbat = true; | |
445 | sbat_var = SBAT_VAR_ORIGINAL; | |
446 | } | |
447 | clear_sbat_policy(); | |
448 | break; | |
449 | default: | |
450 | console_error(L"SBAT policy state %llu is invalid", | |
451 | EFI_INVALID_PARAMETER); | |
2dd2f760 | 452 | sbat_var = sbat_var_previous; |
e6ace38a SM |
453 | clear_sbat_policy(); |
454 | break; | |
455 | } | |
456 | } | |
031e5cce SM |
457 | |
458 | efi_status = get_variable_attr(SBAT_VAR_NAME, &sbat, &sbatsize, | |
459 | SHIM_LOCK_GUID, &attributes); | |
460 | /* | |
8119f718 | 461 | * Always set the SbatLevel UEFI variable if it fails to read. |
031e5cce | 462 | * |
8119f718 SM |
463 | * Don't try to set the SbatLevel UEFI variable if attributes match |
464 | * and the signature matches. | |
031e5cce SM |
465 | */ |
466 | if (EFI_ERROR(efi_status)) { | |
467 | dprint(L"SBAT read failed %r\n", efi_status); | |
e6ace38a SM |
468 | } else if (preserve_sbat_uefi_variable(sbat, sbatsize, attributes, sbat_var) |
469 | && !reset_sbat) { | |
470 | dprint(L"preserving %s variable it is %d bytes, attributes are 0x%08x\n", | |
8119f718 | 471 | SBAT_VAR_NAME, sbatsize, attributes); |
031e5cce SM |
472 | FreePool(sbat); |
473 | return EFI_SUCCESS; | |
474 | } else { | |
475 | FreePool(sbat); | |
476 | ||
477 | /* delete previous variable */ | |
478 | dprint("%s variable is %d bytes, attributes are 0x%08x\n", | |
479 | SBAT_VAR_NAME, sbatsize, attributes); | |
480 | dprint("Deleting %s variable.\n", SBAT_VAR_NAME); | |
481 | efi_status = set_variable(SBAT_VAR_NAME, SHIM_LOCK_GUID, | |
482 | attributes, 0, ""); | |
483 | if (EFI_ERROR(efi_status)) { | |
8119f718 SM |
484 | dprint(L"%s variable delete failed %r\n", SBAT_VAR_NAME, |
485 | efi_status); | |
031e5cce SM |
486 | return efi_status; |
487 | } | |
488 | } | |
489 | ||
490 | /* set variable */ | |
491 | efi_status = set_variable(SBAT_VAR_NAME, SHIM_LOCK_GUID, SBAT_VAR_ATTRS, | |
e6ace38a | 492 | strlen(sbat_var), sbat_var); |
031e5cce | 493 | if (EFI_ERROR(efi_status)) { |
8119f718 SM |
494 | dprint(L"%s variable writing failed %r\n", SBAT_VAR_NAME, |
495 | efi_status); | |
031e5cce SM |
496 | return efi_status; |
497 | } | |
498 | ||
499 | /* verify that the expected data is there */ | |
500 | efi_status = get_variable(SBAT_VAR_NAME, &sbat, &sbatsize, | |
501 | SHIM_LOCK_GUID); | |
502 | if (EFI_ERROR(efi_status)) { | |
8119f718 | 503 | dprint(L"%s read failed %r\n", SBAT_VAR_NAME, efi_status); |
031e5cce SM |
504 | return efi_status; |
505 | } | |
506 | ||
e6ace38a SM |
507 | if (sbatsize != strlen(sbat_var) || |
508 | strncmp((const char *)sbat, sbat_var, strlen(sbat_var)) != 0) { | |
031e5cce | 509 | dprint("new sbatsize is %d, expected %d\n", sbatsize, |
e6ace38a | 510 | strlen(sbat_var)); |
031e5cce SM |
511 | efi_status = EFI_INVALID_PARAMETER; |
512 | } else { | |
8119f718 | 513 | dprint(L"%s variable initialization succeeded\n", SBAT_VAR_NAME); |
031e5cce SM |
514 | } |
515 | ||
516 | FreePool(sbat); | |
517 | ||
518 | return efi_status; | |
519 | } | |
520 | ||
521 | // vim:fenc=utf-8:tw=75:noet |