Skip to content

Commit 80f130e

Browse files
committed
secure-boot: validate booted shim and signature databases
1 parent 4adfd35 commit 80f130e

5 files changed

Lines changed: 112 additions & 62 deletions

File tree

include/efi_boot_parsers.h

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,7 @@ bool bythos_parse_efi_boot_entry(const unsigned char *data, size_t len,
3838
bool bythos_parse_efi_boot_next(const unsigned char *data, size_t len,
3939
uint16_t *number);
4040

41-
/* Secure Boot signature database (db/dbx) payload classification */
42-
typedef enum {
43-
BYTHOS_EFI_SIGDB_NONEMPTY = 0,
44-
BYTHOS_EFI_SIGDB_EMPTY,
45-
BYTHOS_EFI_SIGDB_INVALID,
46-
} bythos_efi_sigdb_status_t;
47-
48-
bythos_efi_sigdb_status_t bythos_classify_efi_sigdb(
49-
const unsigned char *data, size_t len);
50-
41+
/* Secure Boot signature database (db/dbx): count of well-formed EFI_SIGNATURE_LISTs */
5142
size_t bythos_count_efi_sigdb_lists(const unsigned char *data, size_t len);
5243

5344
#endif

src/check_boot_chain.c

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -113,34 +113,40 @@ static bool find_efi_binary(const char *const *candidates, size_t candidate_coun
113113

114114
#define EFI_GLOBAL_VAR_GUID "8be4df61-93ca-11d2-aa0d-00e098032b8c"
115115

116-
/* resolve the shim the firmware actually boots via BootCurrent, not a readdir guess */
117-
static bool find_booted_shim(char *path_out, size_t path_out_size) {
116+
typedef enum {
117+
SHIM_RESOLUTION_FOUND = 0,
118+
SHIM_RESOLUTION_FALLBACK_ALLOWED,
119+
SHIM_RESOLUTION_BOOTED_NON_SHIM,
120+
SHIM_RESOLUTION_BOOTCURRENT_UNRESOLVED,
121+
} shim_resolution_t;
122+
123+
static shim_resolution_t find_booted_shim(char *path_out, size_t path_out_size) {
118124
unsigned char cur[8];
119125
size_t cur_len = 0;
120126
if (!bythos_read_file_binary(
121127
"/sys/firmware/efi/efivars/BootCurrent-" EFI_GLOBAL_VAR_GUID,
122128
cur, sizeof(cur), &cur_len) || cur_len < 6) {
123-
return false;
129+
return SHIM_RESOLUTION_FALLBACK_ALLOWED;
124130
}
125131
unsigned int num = (unsigned int)cur[4] | ((unsigned int)cur[5] << 8);
126132

127133
char var_path[PATH_MAX];
128134
if (snprintf(var_path, sizeof(var_path),
129135
"/sys/firmware/efi/efivars/Boot%04X-" EFI_GLOBAL_VAR_GUID, num)
130136
>= (int)sizeof(var_path)) {
131-
return false;
137+
return SHIM_RESOLUTION_BOOTCURRENT_UNRESOLVED;
132138
}
133139

134140
unsigned char buf[4096];
135141
size_t buf_len = 0;
136142
if (!bythos_read_file_binary(var_path, buf, sizeof(buf), &buf_len)) {
137-
return false;
143+
return SHIM_RESOLUTION_BOOTCURRENT_UNRESOLVED;
138144
}
139145

140146
bythos_efi_boot_entry_t entry;
141147
if (!bythos_parse_efi_boot_entry(buf, buf_len, (uint16_t)num, &entry) ||
142148
entry.filepath[0] == '\0') {
143-
return false;
149+
return SHIM_RESOLUTION_BOOTCURRENT_UNRESOLVED;
144150
}
145151

146152
char norm[256];
@@ -155,33 +161,50 @@ static bool find_booted_shim(char *path_out, size_t path_out_size) {
155161

156162
if (strstr(norm_lower, "shimx64.efi") == NULL &&
157163
strstr(norm_lower, "shimaa64.efi") == NULL) {
158-
return false;
164+
return SHIM_RESOLUTION_BOOTED_NON_SHIM;
159165
}
160166

161167
const char *efi = strstr(norm_lower, "/efi/");
162168
if (efi == NULL) {
163-
return false;
169+
return SHIM_RESOLUTION_BOOTCURRENT_UNRESOLVED;
164170
}
165171
size_t rel_off = (size_t)(efi - norm_lower) + 5;
166172
if (rel_off >= k) {
167-
return false;
173+
return SHIM_RESOLUTION_BOOTCURRENT_UNRESOLVED;
168174
}
169175

170176
if (snprintf(path_out, path_out_size, "%s/%s",
171177
bythos_esp_efi_base(), norm + rel_off) >= (int)path_out_size) {
172-
return false;
178+
return SHIM_RESOLUTION_BOOTCURRENT_UNRESOLVED;
173179
}
174-
return bythos_file_exists(path_out);
180+
return bythos_file_exists(path_out) ? SHIM_RESOLUTION_FOUND :
181+
SHIM_RESOLUTION_BOOTCURRENT_UNRESOLVED;
175182
}
176183

177-
static bool find_shim(char *path_out, size_t path_out_size) {
178-
if (find_booted_shim(path_out, path_out_size)) {
184+
static bool find_shim(char *path_out, size_t path_out_size,
185+
shim_resolution_t *resolution_out) {
186+
shim_resolution_t resolution = find_booted_shim(path_out, path_out_size);
187+
if (resolution == SHIM_RESOLUTION_FOUND) {
188+
if (resolution_out != NULL) {
189+
*resolution_out = resolution;
190+
}
179191
return true;
180192
}
193+
if (resolution != SHIM_RESOLUTION_FALLBACK_ALLOWED) {
194+
if (resolution_out != NULL) {
195+
*resolution_out = resolution;
196+
}
197+
return false;
198+
}
181199
static const char *const candidates[] = {"shimx64.efi", "shimaa64.efi"};
182-
return find_efi_binary(candidates,
183-
sizeof(candidates) / sizeof(candidates[0]),
184-
path_out, path_out_size);
200+
bool found = find_efi_binary(candidates,
201+
sizeof(candidates) / sizeof(candidates[0]),
202+
path_out, path_out_size);
203+
if (resolution_out != NULL) {
204+
*resolution_out = found ? SHIM_RESOLUTION_FOUND :
205+
SHIM_RESOLUTION_FALLBACK_ALLOWED;
206+
}
207+
return found;
185208
}
186209

187210
static bool find_grub(char *path_out, size_t path_out_size) {
@@ -198,8 +221,15 @@ static size_t check_shim_signature(check_result_t *results, size_t max_results)
198221
}
199222

200223
char shim_path[PATH_MAX] = {0};
201-
if (!find_shim(shim_path, sizeof(shim_path))) {
202-
EMIT_SKIP_SUBJECT("shim signature", "shim");
224+
shim_resolution_t shim_resolution = SHIM_RESOLUTION_FALLBACK_ALLOWED;
225+
if (!find_shim(shim_path, sizeof(shim_path), &shim_resolution)) {
226+
if (shim_resolution == SHIM_RESOLUTION_BOOTED_NON_SHIM) {
227+
EMIT_SKIP("shim signature", SKIP_SUBJECT_ABSENT, "booted via non-shim path");
228+
} else if (shim_resolution == SHIM_RESOLUTION_BOOTCURRENT_UNRESOLVED) {
229+
EMIT_SKIP("shim signature", SKIP_OUTPUT_UNPARSEABLE, "BootCurrent path unresolved");
230+
} else {
231+
EMIT_SKIP_SUBJECT("shim signature", "shim");
232+
}
203233
return used;
204234
}
205235

@@ -249,7 +279,7 @@ static void scan_initramfs_dir(const char *dir_path, int max_depth,
249279
}
250280

251281
struct stat st;
252-
if (stat(path, &st) != 0) continue;
282+
if (lstat(path, &st) != 0) continue;
253283

254284
if (S_ISREG(st.st_mode)) {
255285
if (strncmp(name, "initrd", 6) != 0 && strncmp(name, "initramfs", 9) != 0) {
@@ -353,7 +383,7 @@ static size_t check_bootloader_sbat(check_result_t *results, size_t max_results)
353383

354384
char shim_path[PATH_MAX] = {0};
355385
char grub_path[PATH_MAX] = {0};
356-
bool have_shim = find_shim(shim_path, sizeof(shim_path));
386+
bool have_shim = find_shim(shim_path, sizeof(shim_path), NULL);
357387
bool have_grub = find_grub(grub_path, sizeof(grub_path));
358388

359389
if (!have_shim && !have_grub) {

src/check_secureboot.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ static void check_sigdb_variable(const char *path, const char *name,
4646

4747
if (bythos_count_efi_sigdb_lists(buf, len) > 0) {
4848
results[(*used)++] = make_result(name, CHECK_OK, "visible and non-empty");
49+
} else if (len > 4u) {
50+
results[(*used)++] = make_result(name, CHECK_WARN, "visible but unparseable");
4951
} else {
5052
results[(*used)++] = make_result(name, CHECK_WARN, "visible but empty");
5153
}
@@ -176,7 +178,11 @@ size_t bythos_check_secureboot(check_result_t *results, size_t max_results) {
176178
} else {
177179
size_t lists = bythos_count_efi_sigdb_lists(db_buf, db_len);
178180
if (lists == 0) {
179-
EMIT("Secure Boot db keys", CHECK_WARN, "empty; Secure Boot allowlist missing");
181+
if (db_len > 4u) {
182+
EMIT("Secure Boot db keys", CHECK_WARN, "allowlist visible but unparseable");
183+
} else {
184+
EMIT("Secure Boot db keys", CHECK_WARN, "empty; Secure Boot allowlist missing");
185+
}
180186
} else {
181187
char detail[BYTHOS_DETAIL_MAX];
182188
snprintf(detail, sizeof(detail),

src/efi_boot_parsers.c

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -279,20 +279,6 @@ bool bythos_parse_efi_boot_next(const unsigned char *data, size_t len,
279279
return true;
280280
}
281281

282-
bythos_efi_sigdb_status_t bythos_classify_efi_sigdb(
283-
const unsigned char *data, size_t len) {
284-
/* db/dbx are EFI attributes followed by the signature-list payload. */
285-
if (data == NULL || len < EFI_VAR_ATTR_SIZE) {
286-
return BYTHOS_EFI_SIGDB_INVALID;
287-
}
288-
289-
if (len == EFI_VAR_ATTR_SIZE) {
290-
return BYTHOS_EFI_SIGDB_EMPTY;
291-
}
292-
293-
return BYTHOS_EFI_SIGDB_NONEMPTY;
294-
}
295-
296282
size_t bythos_count_efi_sigdb_lists(const unsigned char *data, size_t len) {
297283
if (data == NULL || len <= EFI_VAR_ATTR_SIZE) {
298284
return 0;
@@ -302,12 +288,29 @@ size_t bythos_count_efi_sigdb_lists(const unsigned char *data, size_t len) {
302288
size_t remaining = len - EFI_VAR_ATTR_SIZE;
303289
size_t count = 0;
304290

305-
/* Each EFI_SIGNATURE_LIST is at least 28 bytes; advance by SignatureListSize. */
306-
while (remaining >= 28) {
307-
uint32_t list_size = read_le32(p + 16);
291+
while (remaining > 0) {
292+
if (remaining < 28) {
293+
return 0;
294+
}
295+
296+
size_t list_size = read_le32(p + 16);
297+
size_t header_size = read_le32(p + 20);
298+
size_t signature_size = read_le32(p + 24);
308299
if (list_size < 28 || list_size > remaining) {
309-
break;
300+
return 0;
301+
}
302+
303+
size_t body_size = list_size - 28;
304+
if (header_size > body_size) {
305+
return 0;
310306
}
307+
308+
size_t payload_size = body_size - header_size;
309+
if (signature_size <= 16 || payload_size < signature_size ||
310+
payload_size % signature_size != 0) {
311+
return 0;
312+
}
313+
311314
count++;
312315
p += list_size;
313316
remaining -= list_size;

tests/efi_boot_parsers.c

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <stdint.h>
12
#include <stdio.h>
23
#include <string.h>
34

@@ -13,6 +14,13 @@ static void assert_type(const char *name, bythos_efi_boot_type_t got,
1314
}
1415
}
1516

17+
static void put_le32(unsigned char *p, uint32_t v) {
18+
p[0] = (unsigned char)(v & 0xFF);
19+
p[1] = (unsigned char)((v >> 8) & 0xFF);
20+
p[2] = (unsigned char)((v >> 16) & 0xFF);
21+
p[3] = (unsigned char)((v >> 24) & 0xFF);
22+
}
23+
1624
/* Minimal Boot#### fixture with a legacy BBS device path. */
1725
static size_t build_bbs_entry(unsigned char *buf, size_t buf_size,
1826
uint32_t load_attrs, const char *desc_ascii,
@@ -359,25 +367,37 @@ int main(void) {
359367
}
360368

361369
{
362-
unsigned char data[] = {
363-
0x07, 0x00, 0x00, 0x00,
364-
0x30, 0x06,
365-
0x00, 0x00, 0x00, 0x00,
366-
};
367-
assert_true("sigdb_nonempty",
368-
bythos_classify_efi_sigdb(data, sizeof(data)) == BYTHOS_EFI_SIGDB_NONEMPTY);
370+
unsigned char data[80] = {0x07, 0x00, 0x00, 0x00};
371+
size_t off = 4;
372+
for (size_t i = 0; i < 16; i++) data[off + i] = (unsigned char)i;
373+
put_le32(data + off + 16, 76);
374+
put_le32(data + off + 20, 0);
375+
put_le32(data + off + 24, 48);
376+
assert_eq_sz("sigdb_valid_one_list", bythos_count_efi_sigdb_lists(data, sizeof(data)), 1);
369377
}
370378

371379
{
372-
unsigned char data[] = {0x07, 0x00, 0x00, 0x00};
373-
assert_true("sigdb_empty",
374-
bythos_classify_efi_sigdb(data, sizeof(data)) == BYTHOS_EFI_SIGDB_EMPTY);
380+
unsigned char data[81] = {0x07, 0x00, 0x00, 0x00};
381+
size_t off = 4;
382+
for (size_t i = 0; i < 16; i++) data[off + i] = (unsigned char)i;
383+
put_le32(data + off + 16, 76);
384+
put_le32(data + off + 20, 0);
385+
put_le32(data + off + 24, 48);
386+
assert_eq_sz("sigdb_trailing_junk", bythos_count_efi_sigdb_lists(data, sizeof(data)), 0);
375387
}
376388

377389
{
378-
unsigned char data[] = {0x07, 0x00, 0x00};
379-
assert_true("sigdb_short",
380-
bythos_classify_efi_sigdb(data, sizeof(data)) == BYTHOS_EFI_SIGDB_INVALID);
390+
unsigned char data[32] = {0x07, 0x00, 0x00, 0x00};
391+
size_t off = 4;
392+
put_le32(data + off + 16, 28);
393+
put_le32(data + off + 20, 0);
394+
put_le32(data + off + 24, 48);
395+
assert_eq_sz("sigdb_zero_entry_list", bythos_count_efi_sigdb_lists(data, sizeof(data)), 0);
396+
}
397+
398+
{
399+
unsigned char data[] = {0x07, 0x00, 0x00, 0x00};
400+
assert_eq_sz("sigdb_empty", bythos_count_efi_sigdb_lists(data, sizeof(data)), 0);
381401
}
382402

383403
printf("efi boot parser: all tests passed\n");

0 commit comments

Comments
 (0)