]> git.proxmox.com Git - efi-boot-shim.git/blame - sbat.c
Switch to using gcc-12
[efi-boot-shim.git] / sbat.c
CommitLineData
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
8extern struct {
9 UINT32 previous_offset;
10 UINT32 latest_offset;
11} sbat_var_payload_header;
12
031e5cce
SM
13EFI_STATUS
14parse_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;
106err:
107 free_csv_list(&csv);
108 return efi_status;
109}
110
111void
112cleanup_sbat_section_entries(size_t n, struct sbat_section_entry **entries)
113{
114 if (!n || !entries)
115 return;
116
117 FreePool(entries);
118}
119
120EFI_STATUS
e6ace38a 121verify_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
147void
148cleanup_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
166EFI_STATUS
167verify_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
191out:
192 dprint(L"finished verifying SBAT data: %r\n", efi_status);
193 return efi_status;
194}
195
196EFI_STATUS
197verify_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
205EFI_STATUS
206parse_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 }
286err:
287 free_csv_list(&csv);
288 return efi_status;
289}
290
291EFI_STATUS
292parse_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
330static bool
331check_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
341static char *
342nth_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 354bool
e6ace38a
SM
355preserve_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
391static void
392clear_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
401EFI_STATUS
402set_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