]>
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 | ||
8 | EFI_STATUS | |
9 | parse_sbat_section(char *section_base, size_t section_size, | |
10 | size_t *n_entries, | |
11 | struct sbat_section_entry ***entriesp) | |
12 | { | |
13 | struct sbat_section_entry *entry = NULL, **entries; | |
14 | EFI_STATUS efi_status = EFI_SUCCESS; | |
15 | list_t csv, *pos = NULL; | |
16 | char * end = section_base + section_size - 1; | |
17 | size_t allocsz = 0; | |
18 | size_t n; | |
19 | char *strtab; | |
20 | ||
8119f718 SM |
21 | if (!section_base || !section_size || !n_entries || !entriesp) { |
22 | dprint(L"section_base:0x%lx section_size:0x%lx\n", | |
23 | section_base, section_size); | |
24 | dprint(L"n_entries:0x%lx entriesp:0x%lx\n", | |
25 | n_entries, entriesp); | |
031e5cce | 26 | return EFI_INVALID_PARAMETER; |
8119f718 | 27 | } |
031e5cce SM |
28 | |
29 | INIT_LIST_HEAD(&csv); | |
30 | ||
31 | efi_status = | |
32 | parse_csv_data(section_base, end, SBAT_SECTION_COLUMNS, &csv); | |
33 | if (EFI_ERROR(efi_status)) { | |
8119f718 | 34 | dprint(L"parse_csv_data failed: %r\n", efi_status); |
031e5cce SM |
35 | return efi_status; |
36 | } | |
37 | ||
38 | n = 0; | |
39 | list_for_each(pos, &csv) { | |
40 | struct csv_row * row; | |
41 | size_t i; | |
42 | ||
43 | row = list_entry(pos, struct csv_row, list); | |
44 | ||
45 | if (row->n_columns < SBAT_SECTION_COLUMNS) { | |
46 | efi_status = EFI_INVALID_PARAMETER; | |
8119f718 SM |
47 | dprint(L"row->n_columns:%lu SBAT_SECTION_COLUMNS:%lu\n", |
48 | row->n_columns, SBAT_SECTION_COLUMNS); | |
031e5cce SM |
49 | goto err; |
50 | } | |
51 | ||
52 | allocsz += sizeof(struct sbat_section_entry *); | |
53 | allocsz += sizeof(struct sbat_section_entry); | |
54 | for (i = 0; i < row->n_columns; i++) { | |
55 | if (row->columns[i][0] == '\000') { | |
8119f718 | 56 | dprint(L"row[%lu].columns[%lu][0] == '\\000'\n", n, i); |
031e5cce SM |
57 | efi_status = EFI_INVALID_PARAMETER; |
58 | goto err; | |
59 | } | |
60 | allocsz += strlen(row->columns[i]) + 1; | |
61 | } | |
62 | n++; | |
63 | } | |
64 | ||
65 | strtab = AllocateZeroPool(allocsz); | |
66 | if (!strtab) { | |
67 | efi_status = EFI_OUT_OF_RESOURCES; | |
68 | goto err; | |
69 | } | |
70 | ||
71 | entries = (struct sbat_section_entry **)strtab; | |
72 | strtab += sizeof(struct sbat_section_entry *) * n; | |
73 | entry = (struct sbat_section_entry *)strtab; | |
74 | strtab += sizeof(struct sbat_section_entry) * n; | |
75 | n = 0; | |
76 | ||
77 | list_for_each(pos, &csv) { | |
78 | struct csv_row * row; | |
79 | size_t i; | |
80 | const char **ptrs[] = { | |
81 | &entry->component_name, | |
82 | &entry->component_generation, | |
83 | &entry->vendor_name, | |
84 | &entry->vendor_package_name, | |
85 | &entry->vendor_version, | |
86 | &entry->vendor_url, | |
87 | }; | |
88 | ||
89 | ||
90 | row = list_entry(pos, struct csv_row, list); | |
91 | for (i = 0; i < row->n_columns; i++) { | |
92 | *(ptrs[i]) = strtab; | |
93 | strtab = stpcpy(strtab, row->columns[i]) + 1; | |
94 | } | |
95 | entries[n] = entry; | |
96 | entry++; | |
97 | n++; | |
98 | } | |
99 | *entriesp = entries; | |
100 | *n_entries = n; | |
101 | err: | |
102 | free_csv_list(&csv); | |
103 | return efi_status; | |
104 | } | |
105 | ||
106 | void | |
107 | cleanup_sbat_section_entries(size_t n, struct sbat_section_entry **entries) | |
108 | { | |
109 | if (!n || !entries) | |
110 | return; | |
111 | ||
112 | FreePool(entries); | |
113 | } | |
114 | ||
115 | EFI_STATUS | |
116 | verify_single_entry(struct sbat_section_entry *entry, struct sbat_var_entry *sbat_var_entry) | |
117 | { | |
118 | UINT16 sbat_gen, sbat_var_gen; | |
119 | ||
120 | if (strcmp((const char *)entry->component_name, (const char *)sbat_var_entry->component_name) == 0) { | |
121 | dprint(L"component %a has a matching SBAT variable entry, verifying\n", | |
122 | entry->component_name); | |
123 | ||
124 | /* | |
125 | * atoi returns zero for failed conversion, so essentially | |
126 | * badly parsed component_generation will be treated as zero | |
127 | */ | |
128 | sbat_gen = atoi((const char *)entry->component_generation); | |
129 | sbat_var_gen = atoi((const char *)sbat_var_entry->component_generation); | |
130 | ||
131 | if (sbat_gen < sbat_var_gen) { | |
8119f718 SM |
132 | dprint(L"component %a, generation %d, was revoked by %s variable\n", |
133 | entry->component_name, sbat_gen, SBAT_VAR_NAME); | |
031e5cce SM |
134 | LogError(L"image did not pass SBAT verification\n"); |
135 | return EFI_SECURITY_VIOLATION; | |
136 | } | |
137 | } | |
138 | return EFI_SUCCESS; | |
139 | } | |
140 | ||
141 | void | |
142 | cleanup_sbat_var(list_t *entries) | |
143 | { | |
144 | list_t *pos = NULL, *tmp = NULL; | |
145 | struct sbat_var_entry *entry; | |
146 | void *first = NULL; | |
147 | ||
148 | list_for_each_safe(pos, tmp, entries) { | |
149 | entry = list_entry(pos, struct sbat_var_entry, list); | |
150 | ||
8119f718 | 151 | if (first == NULL || (uintptr_t)entry < (uintptr_t)first) |
031e5cce SM |
152 | first = entry; |
153 | ||
154 | list_del(&entry->list); | |
155 | } | |
156 | if (first) | |
157 | FreePool(first); | |
158 | } | |
159 | ||
160 | EFI_STATUS | |
161 | verify_sbat_helper(list_t *local_sbat_var, size_t n, struct sbat_section_entry **entries) | |
162 | { | |
163 | unsigned int i; | |
164 | list_t *pos = NULL; | |
165 | EFI_STATUS efi_status = EFI_SUCCESS; | |
166 | struct sbat_var_entry *sbat_var_entry; | |
167 | ||
168 | if (list_empty(local_sbat_var)) { | |
8119f718 | 169 | dprint(L"%s variable not present\n", SBAT_VAR_NAME); |
031e5cce SM |
170 | return EFI_SUCCESS; |
171 | } | |
172 | ||
173 | for (i = 0; i < n; i++) { | |
174 | list_for_each(pos, local_sbat_var) { | |
175 | sbat_var_entry = list_entry(pos, struct sbat_var_entry, list); | |
176 | efi_status = verify_single_entry(entries[i], sbat_var_entry); | |
177 | if (EFI_ERROR(efi_status)) | |
178 | goto out; | |
179 | } | |
180 | } | |
181 | ||
182 | out: | |
183 | dprint(L"finished verifying SBAT data: %r\n", efi_status); | |
184 | return efi_status; | |
185 | } | |
186 | ||
187 | EFI_STATUS | |
188 | verify_sbat(size_t n, struct sbat_section_entry **entries) | |
189 | { | |
190 | EFI_STATUS efi_status; | |
191 | ||
192 | efi_status = verify_sbat_helper(&sbat_var, n, entries); | |
193 | return efi_status; | |
194 | } | |
195 | ||
196 | EFI_STATUS | |
197 | parse_sbat_var_data(list_t *entry_list, UINT8 *data, UINTN datasize) | |
198 | { | |
199 | struct sbat_var_entry *entry = NULL, **entries; | |
200 | EFI_STATUS efi_status = EFI_SUCCESS; | |
201 | list_t csv, *pos = NULL; | |
202 | char * start = (char *)data; | |
203 | char * end = (char *)data + datasize - 1; | |
204 | size_t allocsz = 0; | |
205 | size_t n; | |
206 | char *strtab; | |
207 | ||
208 | if (!entry_list|| !data || datasize == 0) | |
209 | return EFI_INVALID_PARAMETER; | |
210 | ||
211 | INIT_LIST_HEAD(&csv); | |
212 | ||
213 | efi_status = parse_csv_data(start, end, SBAT_VAR_COLUMNS, &csv); | |
214 | if (EFI_ERROR(efi_status)) { | |
215 | return efi_status; | |
216 | } | |
217 | ||
218 | n = 0; | |
219 | list_for_each(pos, &csv) { | |
220 | struct csv_row * row; | |
221 | size_t i; | |
222 | ||
223 | row = list_entry(pos, struct csv_row, list); | |
224 | ||
225 | if (row->n_columns < SBAT_VAR_REQUIRED_COLUMNS) { | |
226 | efi_status = EFI_INVALID_PARAMETER; | |
227 | goto err; | |
228 | } | |
229 | ||
230 | ||
231 | allocsz += sizeof(struct sbat_var_entry *); | |
232 | allocsz += sizeof(struct sbat_var_entry); | |
233 | for (i = 0; i < row->n_columns; i++) { | |
234 | if (!row->columns[i][0]) { | |
235 | efi_status = EFI_INVALID_PARAMETER; | |
236 | goto err; | |
237 | } | |
238 | allocsz += strlen(row->columns[i]) + 1; | |
239 | } | |
240 | n++; | |
241 | } | |
242 | ||
243 | strtab = AllocateZeroPool(allocsz); | |
244 | if (!strtab) { | |
245 | efi_status = EFI_OUT_OF_RESOURCES; | |
246 | goto err; | |
247 | } | |
248 | ||
249 | INIT_LIST_HEAD(entry_list); | |
250 | ||
031e5cce SM |
251 | entry = (struct sbat_var_entry *)strtab; |
252 | strtab += sizeof(struct sbat_var_entry) * n; | |
8119f718 SM |
253 | entries = (struct sbat_var_entry **)strtab; |
254 | strtab += sizeof(struct sbat_var_entry *) * n; | |
031e5cce SM |
255 | n = 0; |
256 | ||
257 | list_for_each(pos, &csv) { | |
258 | struct csv_row * row; | |
259 | size_t i; | |
260 | const char **ptrs[] = { | |
261 | &entry->component_name, | |
262 | &entry->component_generation, | |
263 | &entry->sbat_datestamp, | |
264 | }; | |
265 | ||
266 | row = list_entry(pos, struct csv_row, list); | |
267 | for (i = 0; i < row->n_columns; i++) { | |
268 | *(ptrs[i]) = strtab; | |
269 | strtab = stpcpy(strtab, row->columns[i]) + 1; | |
270 | } | |
271 | INIT_LIST_HEAD(&entry->list); | |
272 | list_add_tail(&entry->list, entry_list); | |
273 | entries[n] = entry; | |
274 | entry++; | |
275 | n++; | |
276 | } | |
277 | err: | |
278 | free_csv_list(&csv); | |
279 | return efi_status; | |
280 | } | |
281 | ||
282 | EFI_STATUS | |
283 | parse_sbat_var(list_t *entries) | |
284 | { | |
285 | UINT8 *data = 0; | |
286 | UINTN datasize; | |
287 | EFI_STATUS efi_status; | |
288 | ||
8119f718 SM |
289 | if (!entries) { |
290 | dprint(L"entries is NULL\n"); | |
031e5cce | 291 | return EFI_INVALID_PARAMETER; |
8119f718 | 292 | } |
031e5cce SM |
293 | |
294 | efi_status = get_variable(SBAT_VAR_NAME, &data, &datasize, SHIM_LOCK_GUID); | |
295 | if (EFI_ERROR(efi_status)) { | |
296 | LogError(L"Failed to read SBAT variable\n", efi_status); | |
297 | return efi_status; | |
298 | } | |
299 | ||
300 | /* | |
301 | * We've intentionally made sure there's a NUL byte on all variable | |
302 | * allocations, so use that here. | |
303 | */ | |
304 | return parse_sbat_var_data(entries, data, datasize+1); | |
305 | } | |
306 | ||
307 | static bool | |
308 | check_sbat_var_attributes(UINT32 attributes) | |
309 | { | |
310 | #ifdef ENABLE_SHIM_DEVEL | |
311 | return attributes == UEFI_VAR_NV_BS_RT; | |
312 | #else | |
313 | return attributes == UEFI_VAR_NV_BS || | |
314 | attributes == UEFI_VAR_NV_BS_TIMEAUTH; | |
315 | #endif | |
316 | } | |
317 | ||
8119f718 SM |
318 | bool |
319 | preserve_sbat_uefi_variable(UINT8 *sbat, UINTN sbatsize, UINT32 attributes) | |
320 | { | |
321 | return check_sbat_var_attributes(attributes) && | |
322 | sbatsize >= strlen(SBAT_VAR_SIG "1") && | |
323 | !strncmp((const char *)sbat, SBAT_VAR_SIG, strlen(SBAT_VAR_SIG)); | |
324 | } | |
325 | ||
031e5cce SM |
326 | EFI_STATUS |
327 | set_sbat_uefi_variable(void) | |
328 | { | |
329 | EFI_STATUS efi_status = EFI_SUCCESS; | |
330 | UINT32 attributes = 0; | |
331 | ||
332 | UINT8 *sbat = NULL; | |
333 | UINTN sbatsize = 0; | |
334 | ||
335 | efi_status = get_variable_attr(SBAT_VAR_NAME, &sbat, &sbatsize, | |
336 | SHIM_LOCK_GUID, &attributes); | |
337 | /* | |
8119f718 | 338 | * Always set the SbatLevel UEFI variable if it fails to read. |
031e5cce | 339 | * |
8119f718 SM |
340 | * Don't try to set the SbatLevel UEFI variable if attributes match |
341 | * and the signature matches. | |
031e5cce SM |
342 | */ |
343 | if (EFI_ERROR(efi_status)) { | |
344 | dprint(L"SBAT read failed %r\n", efi_status); | |
8119f718 SM |
345 | } else if (preserve_sbat_uefi_variable(sbat, sbatsize, attributes)) { |
346 | dprint(L"%s variable is %d bytes, attributes are 0x%08x\n", | |
347 | SBAT_VAR_NAME, sbatsize, attributes); | |
031e5cce SM |
348 | FreePool(sbat); |
349 | return EFI_SUCCESS; | |
350 | } else { | |
351 | FreePool(sbat); | |
352 | ||
353 | /* delete previous variable */ | |
354 | dprint("%s variable is %d bytes, attributes are 0x%08x\n", | |
355 | SBAT_VAR_NAME, sbatsize, attributes); | |
356 | dprint("Deleting %s variable.\n", SBAT_VAR_NAME); | |
357 | efi_status = set_variable(SBAT_VAR_NAME, SHIM_LOCK_GUID, | |
358 | attributes, 0, ""); | |
359 | if (EFI_ERROR(efi_status)) { | |
8119f718 SM |
360 | dprint(L"%s variable delete failed %r\n", SBAT_VAR_NAME, |
361 | efi_status); | |
031e5cce SM |
362 | return efi_status; |
363 | } | |
364 | } | |
365 | ||
366 | /* set variable */ | |
367 | efi_status = set_variable(SBAT_VAR_NAME, SHIM_LOCK_GUID, SBAT_VAR_ATTRS, | |
368 | sizeof(SBAT_VAR)-1, SBAT_VAR); | |
369 | if (EFI_ERROR(efi_status)) { | |
8119f718 SM |
370 | dprint(L"%s variable writing failed %r\n", SBAT_VAR_NAME, |
371 | efi_status); | |
031e5cce SM |
372 | return efi_status; |
373 | } | |
374 | ||
375 | /* verify that the expected data is there */ | |
376 | efi_status = get_variable(SBAT_VAR_NAME, &sbat, &sbatsize, | |
377 | SHIM_LOCK_GUID); | |
378 | if (EFI_ERROR(efi_status)) { | |
8119f718 | 379 | dprint(L"%s read failed %r\n", SBAT_VAR_NAME, efi_status); |
031e5cce SM |
380 | return efi_status; |
381 | } | |
382 | ||
383 | if (sbatsize != strlen(SBAT_VAR) || | |
384 | strncmp((const char *)sbat, SBAT_VAR, strlen(SBAT_VAR)) != 0) { | |
385 | dprint("new sbatsize is %d, expected %d\n", sbatsize, | |
386 | strlen(SBAT_VAR)); | |
387 | efi_status = EFI_INVALID_PARAMETER; | |
388 | } else { | |
8119f718 | 389 | dprint(L"%s variable initialization succeeded\n", SBAT_VAR_NAME); |
031e5cce SM |
390 | } |
391 | ||
392 | FreePool(sbat); | |
393 | ||
394 | return efi_status; | |
395 | } | |
396 | ||
397 | // vim:fenc=utf-8:tw=75:noet |