]> git.proxmox.com Git - efi-boot-shim.git/blob - sbat.c
Improve how the dbx hashes are handled
[efi-boot-shim.git] / sbat.c
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 #include "string.h"
8
9 CHAR8 *
10 get_sbat_field(CHAR8 *current, CHAR8 *end, const CHAR8 **field, char delim)
11 {
12 CHAR8 *offset;
13
14 if (!field || !current || !end || current >= end)
15 return NULL;
16
17 offset = strchrnula(current, delim);
18 *field = current;
19
20 if (!offset || !*offset)
21 return NULL;
22
23 *offset = '\0';
24 return offset + 1;
25 }
26
27 EFI_STATUS
28 parse_sbat_entry(CHAR8 **current, CHAR8 *end, struct sbat_entry **sbat_entry)
29 {
30 struct sbat_entry *entry = NULL;
31
32 entry = AllocateZeroPool(sizeof(*entry));
33 if (!entry)
34 return EFI_OUT_OF_RESOURCES;
35
36 *current = get_sbat_field(*current, end, &entry->component_name, ',');
37 if (!entry->component_name)
38 goto error;
39
40 *current = get_sbat_field(*current, end, &entry->component_generation,
41 ',');
42 if (!entry->component_generation)
43 goto error;
44
45 *current = get_sbat_field(*current, end, &entry->vendor_name, ',');
46 if (!entry->vendor_name)
47 goto error;
48
49 *current =
50 get_sbat_field(*current, end, &entry->vendor_package_name, ',');
51 if (!entry->vendor_package_name)
52 goto error;
53
54 *current = get_sbat_field(*current, end, &entry->vendor_version, ',');
55 if (!entry->vendor_version)
56 goto error;
57
58 *current = get_sbat_field(*current, end, &entry->vendor_url, '\n');
59 if (!entry->vendor_url)
60 goto error;
61
62 *sbat_entry = entry;
63
64 return EFI_SUCCESS;
65
66 error:
67 FreePool(entry);
68 return EFI_INVALID_PARAMETER;
69 }
70
71 void
72 cleanup_sbat_entries(size_t n, struct sbat_entry **entries)
73 {
74 size_t i;
75
76 if (!n || !entries)
77 return;
78
79 for (i = 0; i < n; i++) {
80 if (entries[i]) {
81 FreePool(entries[i]);
82 entries[i] = NULL;
83 }
84 }
85 FreePool(entries);
86 }
87
88 EFI_STATUS
89 parse_sbat(char *sbat_base, size_t sbat_size, size_t *sbats, struct sbat_entry ***sbat)
90 {
91 CHAR8 *current = (CHAR8 *)sbat_base;
92 CHAR8 *end = (CHAR8 *)sbat_base + sbat_size;
93 EFI_STATUS efi_status = EFI_SUCCESS;
94 struct sbat_entry *entry = NULL;
95 struct sbat_entry **entries;
96 size_t i = 0;
97 size_t pages = 1;
98 size_t n = PAGE_SIZE / sizeof(*entry);
99
100 if (!sbat_base || sbat_size == 0 || !sbats || !sbat)
101 return EFI_INVALID_PARAMETER;
102
103 if (current == end)
104 return EFI_INVALID_PARAMETER;
105
106 *sbats = 0;
107 *sbat = 0;
108
109 entries = AllocateZeroPool(pages * PAGE_SIZE);
110 if (!entries)
111 return EFI_OUT_OF_RESOURCES;
112
113 do {
114 entry = NULL;
115 efi_status = parse_sbat_entry(&current, end, &entry);
116 if (EFI_ERROR(efi_status))
117 goto error;
118
119 if (end < current) {
120 efi_status = EFI_INVALID_PARAMETER;
121 goto error;
122 }
123
124 if (i >= n) {
125 struct sbat_entry **new_entries;
126 unsigned int osize = PAGE_SIZE * pages;
127 unsigned int nsize = osize + PAGE_SIZE;
128
129 new_entries = ReallocatePool(entries, osize, nsize);
130 if (!new_entries) {
131 efi_status = EFI_OUT_OF_RESOURCES;
132 goto error;
133 }
134 entries = new_entries;
135 ZeroMem(&entries[i], PAGE_SIZE);
136 pages += 1;
137 n = nsize / sizeof(entry);
138 }
139 entries[i++] = entry;
140 } while (entry && current && *current != '\0');
141
142 *sbats = i;
143 *sbat = entries;
144
145 return efi_status;
146 error:
147 perror(L"Failed to parse SBAT data: %r\n", efi_status);
148 cleanup_sbat_entries(i, entries);
149 return efi_status;
150 }
151
152 EFI_STATUS
153 verify_single_entry(struct sbat_entry *entry, struct sbat_var *sbat_var_entry)
154 {
155 UINT16 sbat_gen, sbat_var_gen;
156
157 if (strcmp(entry->component_name, sbat_var_entry->component_name) == 0) {
158 dprint(L"component %a has a matching SBAT variable entry, verifying\n",
159 entry->component_name);
160
161 /*
162 * atoi returns zero for failed conversion, so essentially
163 * badly parsed component_generation will be treated as zero
164 */
165 sbat_gen = atoi(entry->component_generation);
166 sbat_var_gen = atoi(sbat_var_entry->component_generation);
167
168 if (sbat_gen < sbat_var_gen) {
169 dprint(L"component %a, generation %d, was revoked by SBAT variable",
170 entry->component_name, sbat_gen);
171 LogError(L"image did not pass SBAT verification\n");
172 return EFI_SECURITY_VIOLATION;
173 }
174 }
175 return EFI_SUCCESS;
176 }
177
178 void
179 cleanup_sbat_var(list_t *entries)
180 {
181 list_t *pos = NULL, *tmp = NULL;
182 struct sbat_var *entry;
183
184 list_for_each_safe(pos, tmp, entries) {
185 entry = list_entry(pos, struct sbat_var, list);
186 list_del(&entry->list);
187
188 if (entry->component_generation)
189 FreePool((CHAR8 *)entry->component_name);
190 if (entry->component_name)
191 FreePool((CHAR8 *)entry->component_generation);
192 FreePool(entry);
193 }
194 }
195
196 EFI_STATUS
197 verify_sbat(size_t n, struct sbat_entry **entries)
198 {
199 unsigned int i;
200 list_t *pos = NULL;
201 EFI_STATUS efi_status = EFI_SUCCESS;
202 struct sbat_var *sbat_var_entry;
203
204 if (list_empty(&sbat_var)) {
205 dprint(L"SBAT variable not present\n");
206 return EFI_SUCCESS;
207 }
208
209 for (i = 0; i < n; i++) {
210 list_for_each(pos, &sbat_var) {
211 sbat_var_entry = list_entry(pos, struct sbat_var, list);
212 efi_status = verify_single_entry(entries[i], sbat_var_entry);
213 if (EFI_ERROR(efi_status))
214 return efi_status;
215 }
216 }
217
218 dprint(L"all entries from SBAT section verified\n");
219 return efi_status;
220 }
221
222 static BOOLEAN
223 is_utf8_bom(CHAR8 *buf, size_t bufsize)
224 {
225 unsigned char bom[] = { 0xEF, 0xBB, 0xBF };
226
227 return CompareMem(buf, bom, MIN(sizeof(bom), bufsize)) == 0;
228 }
229
230 static struct sbat_var *
231 new_entry(const CHAR8 *comp_name, const CHAR8 *comp_gen)
232 {
233 struct sbat_var *new_entry = AllocatePool(sizeof(*new_entry));
234
235 if (!new_entry)
236 return NULL;
237
238 INIT_LIST_HEAD(&new_entry->list);
239 new_entry->component_name = comp_name;
240 new_entry->component_generation = comp_gen;
241
242 return new_entry;
243 }
244
245 EFI_STATUS
246 add_entry(list_t *list, const CHAR8 *comp_name, const CHAR8 *comp_gen)
247 {
248 struct sbat_var *new;
249
250 new = new_entry(comp_name, comp_gen);
251 if (!new)
252 return EFI_OUT_OF_RESOURCES;
253
254 list_add_tail(&new->list, list);
255 return EFI_SUCCESS;
256 }
257
258 EFI_STATUS
259 parse_sbat_var(list_t *entries)
260 {
261 UINT8 *data = 0;
262 UINTN datasize, i;
263 EFI_STATUS efi_status;
264 char delim;
265
266 if (!entries)
267 return EFI_INVALID_PARAMETER;
268
269 INIT_LIST_HEAD(entries);
270
271 efi_status = get_variable(L"SBAT", &data, &datasize, SHIM_LOCK_GUID);
272 if (EFI_ERROR(efi_status)) {
273 LogError(L"Failed to read SBAT variable\n",
274 efi_status);
275 return efi_status;
276 }
277
278 CHAR8 *start = (CHAR8 *)data;
279 CHAR8 *end = (CHAR8 *)data + datasize;
280 if (is_utf8_bom(start, datasize))
281 start += 3;
282
283 dprint(L"SBAT variable data:\n");
284
285 while (start[0] != '\0') {
286 const CHAR8 *fields[2] = {
287 NULL,
288 };
289 for (i = 0; i < 3; i++) {
290 const CHAR8 *tmp;
291 /*
292 * on third iteration we check if we had extra stuff on line while parsing
293 * component_name. If delimeter on 2nd iteration was ',', this means that
294 * we have comments after component_name. get_sbat_field in this if condition
295 * parses comments, if they are present and drops them.
296 */
297 if (i == 2 && start) {
298 if (delim == ',') {
299 start = get_sbat_field(start, end, &tmp,
300 '\n');
301 }
302 break;
303 }
304 delim = ',';
305 /* we do not want to jump to next line and grab stuff from that
306 */
307 if ((strchrnula(start, '\n') - start + 1) <=
308 (strchrnula(start, ',') - start + 1)) {
309 delim = '\n';
310 if (i == 0)
311 goto error;
312 }
313 if (!start) {
314 goto error;
315 }
316 start = get_sbat_field(start, end, &tmp, delim);
317 /* to be replaced when we have strdupa()
318 */
319 fields[i] = strndupa(tmp, strlen(tmp));
320 if (!fields[i]) {
321 goto error;
322 }
323 }
324 dprint(L"component %a with generation %a\n", fields[0], fields[1]);
325 efi_status =
326 add_entry(entries, fields[0], fields[1]);
327 if (EFI_ERROR(efi_status))
328 goto error;
329 }
330 FreePool(data);
331 return EFI_SUCCESS;
332 error:
333 perror(L"failed to parse SBAT variable\n");
334 cleanup_sbat_var(entries);
335 FreePool(data);
336 return EFI_INVALID_PARAMETER;
337 }
338 // vim:fenc=utf-8:tw=75:noet