Skip to content

Commit c7bbff6

Browse files
committed
harden: trusted PATH boundary, command timeouts, parser correctness
1 parent cf4f79d commit c7bbff6

20 files changed

Lines changed: 344 additions & 162 deletions

Makefile

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
CC ?= cc
22
CFLAGS ?= -std=c11 -O2 -Wall -Wextra -Wpedantic -Werror -D_POSIX_C_SOURCE=200809L -Iinclude
3+
TEST_CFLAGS = $(CFLAGS) -DBYTHOS_ALLOW_PATH_OVERRIDE
34
LDFLAGS ?=
45

56
prefix ?= /usr/local
@@ -97,28 +98,28 @@ uninstall:
9798
rm -f "$(DESTDIR)$(bindir)/$(BIN)" "$(DESTDIR)$(mandir)/bythos.1"
9899

99100
$(FIRMWARE_TEST_BIN): tests/firmware_parsers.c src/firmware_parsers.c include/firmware_parsers.h tests/assert_helpers.h
100-
$(CC) $(CFLAGS) tests/firmware_parsers.c src/firmware_parsers.c -o $@ $(LDFLAGS)
101+
$(CC) $(TEST_CFLAGS) tests/firmware_parsers.c src/firmware_parsers.c -o $@ $(LDFLAGS)
101102

102103
$(FIRMWARE_OWNERSHIP_TEST_BIN): tests/firmware_ownership.c src/firmware_ownership.c src/runtime.c src/firmware_parsers.c include/firmware_ownership.h include/runtime.h include/firmware_parsers.h tests/assert_helpers.h tests/test_harness.h
103-
$(CC) $(CFLAGS) tests/firmware_ownership.c src/firmware_ownership.c src/runtime.c src/firmware_parsers.c -o $@ $(LDFLAGS)
104+
$(CC) $(TEST_CFLAGS) tests/firmware_ownership.c src/firmware_ownership.c src/runtime.c src/firmware_parsers.c -o $@ $(LDFLAGS)
104105

105106
$(SILICON_TEST_BIN): tests/silicon_parsers.c src/silicon_parsers.c src/runtime.c include/silicon_parsers.h include/runtime.h tests/assert_helpers.h
106-
$(CC) $(CFLAGS) tests/silicon_parsers.c src/silicon_parsers.c src/runtime.c -o $@ $(LDFLAGS)
107+
$(CC) $(TEST_CFLAGS) tests/silicon_parsers.c src/silicon_parsers.c src/runtime.c -o $@ $(LDFLAGS)
107108

108109
$(STORAGE_TEST_BIN): tests/storage_parsers.c src/storage_parsers.c include/storage_parsers.h tests/assert_helpers.h
109-
$(CC) $(CFLAGS) tests/storage_parsers.c src/storage_parsers.c -o $@ $(LDFLAGS)
110+
$(CC) $(TEST_CFLAGS) tests/storage_parsers.c src/storage_parsers.c -o $@ $(LDFLAGS)
110111

111112
$(RUNTIME_TEST_BIN): tests/runtime_capture.c src/runtime.c include/runtime.h tests/assert_helpers.h tests/test_harness.h
112-
$(CC) $(CFLAGS) tests/runtime_capture.c src/runtime.c -o $@ $(LDFLAGS)
113+
$(CC) $(TEST_CFLAGS) tests/runtime_capture.c src/runtime.c -o $@ $(LDFLAGS)
113114

114115
$(EFI_BOOT_TEST_BIN): tests/efi_boot_parsers.c src/efi_boot_parsers.c src/runtime.c include/efi_boot_parsers.h include/runtime.h tests/assert_helpers.h
115-
$(CC) $(CFLAGS) tests/efi_boot_parsers.c src/efi_boot_parsers.c src/runtime.c -o $@ $(LDFLAGS)
116+
$(CC) $(TEST_CFLAGS) tests/efi_boot_parsers.c src/efi_boot_parsers.c src/runtime.c -o $@ $(LDFLAGS)
116117

117118
$(ESP_TEST_BIN): tests/esp_posture.c src/esp_parsers.c include/esp_parsers.h tests/assert_helpers.h
118-
$(CC) $(CFLAGS) tests/esp_posture.c src/esp_parsers.c -o $@ $(LDFLAGS)
119+
$(CC) $(TEST_CFLAGS) tests/esp_posture.c src/esp_parsers.c -o $@ $(LDFLAGS)
119120

120121
$(SKIP_REASON_TEST_BIN): tests/skip_reason.c src/output.c include/output.h include/types.h tests/assert_helpers.h
121-
$(CC) $(CFLAGS) tests/skip_reason.c src/output.c -o $@ $(LDFLAGS)
122+
$(CC) $(TEST_CFLAGS) tests/skip_reason.c src/output.c -o $@ $(LDFLAGS)
122123

123124
clean:
124125
rm -f src/*.o *.o $(BIN) $(TEST_BINS)

include/runtime.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <stdbool.h>
55
#include <stddef.h>
6+
#include <dirent.h>
67

78
typedef enum {
89
BYTHOS_SERVICE_STATE_UNKNOWN = 0,
@@ -18,9 +19,13 @@ bool bythos_command_exists(const char *name);
1819
bool bythos_file_exists(const char *path);
1920
bool bythos_read_file_text(const char *path, char *buffer, size_t size);
2021
bool bythos_read_file_binary(const char *path, unsigned char *buffer, size_t size, size_t *bytes_read);
22+
bool bythos_first_line_with_prefix(const char *path, const char *prefix, char *buffer, size_t size);
23+
bool bythos_find_mount_opts(const char *mounts, const char *fstype, char *opts_out, size_t opts_size);
2124
bool bythos_count_child_dirs(const char *path, size_t *count);
25+
struct dirent *bythos_readdir_safe(DIR *dir, int *err_out);
2226
bool bythos_read_key_value(const char *path, const char *key, char *buffer, size_t size);
2327
bool bythos_capture_argv_status(const char *const argv[], char *buffer, size_t size, int *exit_status);
28+
bool bythos_capture_argv_status_ex(const char *const argv[], char *buffer, size_t size, int *exit_status, bool *truncated);
2429
int bythos_run_argv_quiet(const char *const argv[]);
2530
bythos_service_state_t bythos_probe_systemd_service(const char *unit);
2631
const char *bythos_esp_efi_base(void);

include/types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ typedef struct {
5050
const char *name;
5151
check_result_t results[BYTHOS_MAX_SUBGROUP_RESULTS];
5252
size_t result_count;
53+
bool truncated;
5354
posture_summary_t summary;
5455
} check_subgroup_t;
5556

src/check_bios_boot.c

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,33 @@ static bool read_boot_entry(uint16_t number, bythos_efi_boot_entry_t *entry) {
3737
static size_t check_efivars_boot(check_result_t *results, size_t max_results) {
3838
size_t used = 0;
3939

40+
if (!bythos_file_exists("/sys/firmware/efi/efivars")) {
41+
EMIT_SKIP_FEATURE("EFI USB boot", "EFI runtime");
42+
EMIT_SKIP_FEATURE("EFI network boot", "EFI runtime");
43+
EMIT_SKIP_FEATURE("EFI CD/DVD boot", "EFI runtime");
44+
EMIT_SKIP_FEATURE("EFI one-shot boot", "EFI runtime");
45+
return used;
46+
}
47+
4048
unsigned char order_buf[256];
4149
size_t order_len = 0;
4250

4351
if (!bythos_read_file_binary(EFI_BOOT_ORDER_PATH, order_buf,
4452
sizeof(order_buf), &order_len)) {
45-
return 0;
53+
EMIT_SKIP_FEATURE("EFI USB boot", "BootOrder variable");
54+
EMIT_SKIP_FEATURE("EFI network boot", "BootOrder variable");
55+
EMIT_SKIP_FEATURE("EFI CD/DVD boot", "BootOrder variable");
56+
EMIT_SKIP_FEATURE("EFI one-shot boot", "BootOrder variable");
57+
return used;
4658
}
4759

4860
bythos_efi_boot_order_t order = {0};
4961
if (!bythos_parse_efi_boot_order(order_buf, order_len, &order)) {
50-
return 0;
62+
EMIT_SKIP("EFI USB boot", SKIP_OUTPUT_UNPARSEABLE, "BootOrder variable malformed");
63+
EMIT_SKIP("EFI network boot", SKIP_OUTPUT_UNPARSEABLE, "BootOrder variable malformed");
64+
EMIT_SKIP("EFI CD/DVD boot", SKIP_OUTPUT_UNPARSEABLE, "BootOrder variable malformed");
65+
EMIT_SKIP("EFI one-shot boot", SKIP_OUTPUT_UNPARSEABLE, "BootOrder variable malformed");
66+
return used;
5167
}
5268

5369
/* Only active risky entries are a real posture signal. */
@@ -154,7 +170,7 @@ static size_t check_firmware_attrs_boot(check_result_t *results, size_t max_resu
154170
char value[64] = {0};
155171
struct dirent *vendor;
156172

157-
while ((vendor = readdir(vendors)) != NULL) {
173+
while ((vendor = bythos_readdir_safe(vendors, NULL)) != NULL) {
158174
if (vendor->d_name[0] == '.') {
159175
continue;
160176
}
@@ -170,7 +186,7 @@ static size_t check_firmware_attrs_boot(check_result_t *results, size_t max_resu
170186
}
171187

172188
struct dirent *attr;
173-
while ((attr = readdir(attrs)) != NULL) {
189+
while ((attr = bythos_readdir_safe(attrs, NULL)) != NULL) {
174190
if (attr->d_name[0] == '.') {
175191
continue;
176192
}
@@ -281,6 +297,10 @@ static size_t check_firmware_password(check_result_t *results, size_t max_result
281297
while (*pos == ' ' || *pos == '\t') {
282298
pos++;
283299
}
300+
if ((size_t)((buf + sizeof(buf)) - pos) < 9) {
301+
EMIT_SKIP_PARSE(slots[i].name, "dmidecode");
302+
continue;
303+
}
284304
char term = pos[7];
285305
if (strncmp(pos, "Enabled", 7) == 0 &&
286306
(term == '\0' || term == '\n' || term == '\r' || term == ' ' || term == '\t')) {

src/check_bolt.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ static bool read_tb_domain_attr(const char *attr, char *buffer, size_t size) {
1616
if (dir == NULL) return false;
1717

1818
struct dirent *entry;
19-
while ((entry = readdir(dir)) != NULL) {
19+
while ((entry = bythos_readdir_safe(dir, NULL)) != NULL) {
2020
if (strncmp(entry->d_name, "domain", 6) != 0) continue;
2121

2222
char path[PATH_MAX];
@@ -39,7 +39,7 @@ static bool tb_controller_present(void) {
3939

4040
bool found = false;
4141
struct dirent *entry;
42-
while ((entry = readdir(dir)) != NULL) {
42+
while ((entry = bythos_readdir_safe(dir, NULL)) != NULL) {
4343
if (strncmp(entry->d_name, "domain", 6) == 0) {
4444
found = true;
4545
break;

src/check_boot_chain.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ static bool find_efi_binary(const char *const *candidates, size_t candidate_coun
6262
bool found = false;
6363
struct dirent *vendor;
6464

65-
while (!found && (vendor = readdir(efi_dir)) != NULL) {
65+
while (!found && (vendor = bythos_readdir_safe(efi_dir, NULL)) != NULL) {
6666
if (vendor->d_name[0] == '.') {
6767
continue;
6868
}
@@ -79,7 +79,7 @@ static bool find_efi_binary(const char *const *candidates, size_t candidate_coun
7979
}
8080

8181
struct dirent *entry;
82-
while ((entry = readdir(vendor_dir)) != NULL) {
82+
while ((entry = bythos_readdir_safe(vendor_dir, NULL)) != NULL) {
8383
char lower[256];
8484
bythos_to_lower_ascii(entry->d_name, lower, sizeof(lower));
8585

@@ -172,7 +172,7 @@ static void scan_initramfs_dir(const char *dir_path, int max_depth,
172172
if (d == NULL) return;
173173

174174
struct dirent *entry;
175-
while ((entry = readdir(d)) != NULL) {
175+
while ((entry = bythos_readdir_safe(d, NULL)) != NULL) {
176176
const char *name = entry->d_name;
177177
if (name[0] == '.') continue;
178178

src/check_esp_posture.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ static size_t check_efi_vendor_dirs(check_result_t *results, size_t max_results)
123123
size_t names_len = 0;
124124
struct dirent *entry;
125125

126-
while ((entry = readdir(dir)) != NULL) {
126+
while ((entry = bythos_readdir_safe(dir, NULL)) != NULL) {
127127
if (entry->d_name[0] == '.') continue;
128128
char path[PATH_MAX];
129129
if (snprintf(path, sizeof(path), "%s/%s", esp_base, entry->d_name) >=
@@ -174,7 +174,7 @@ static bool find_shim(char *path_out, size_t size) {
174174
if (efi_dir == NULL) return false;
175175
bool found = false;
176176
struct dirent *vendor;
177-
while (!found && (vendor = readdir(efi_dir)) != NULL) {
177+
while (!found && (vendor = bythos_readdir_safe(efi_dir, NULL)) != NULL) {
178178
if (vendor->d_name[0] == '.') continue;
179179
for (size_t i = 0; i < sizeof(shim_names) / sizeof(shim_names[0]); i++) {
180180
char candidate[PATH_MAX];
@@ -295,7 +295,7 @@ static size_t check_update_capsule(check_result_t *results, size_t max_results)
295295

296296
size_t count = 0;
297297
struct dirent *entry;
298-
while ((entry = readdir(dir)) != NULL) {
298+
while ((entry = bythos_readdir_safe(dir, NULL)) != NULL) {
299299
if (entry->d_name[0] != '.') count++;
300300
}
301301
closedir(dir);

src/check_groups.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99
#define SUBGROUP_TAIL(sg) \
1010
((sg)->results + (sg)->result_count)
1111

12+
#define BYTHOS_SUBGROUP_MAX_FNS 6
13+
1214
typedef size_t (*subgroup_check_fn)(check_result_t *, size_t);
1315

1416
typedef struct {
1517
const char *name;
16-
subgroup_check_fn fns[5];
18+
subgroup_check_fn fns[BYTHOS_SUBGROUP_MAX_FNS];
1719
} subgroup_def_t;
1820

1921
static void subgroup_init(check_subgroup_t *sg, const char *name) {
2022
sg->name = name;
2123
sg->result_count = 0;
24+
sg->truncated = false;
2225
sg->summary = (posture_summary_t){0};
2326
}
2427

@@ -36,7 +39,11 @@ static size_t run_subgroups(const subgroup_def_t *defs,
3639
for (size_t i = 0; defs[i].name != NULL && used < max_subgroups; i++) {
3740
check_subgroup_t *sg = &subgroups[used++];
3841
subgroup_init(sg, defs[i].name);
39-
for (size_t j = 0; defs[i].fns[j] != NULL; j++) {
42+
for (size_t j = 0; j < BYTHOS_SUBGROUP_MAX_FNS && defs[i].fns[j] != NULL; j++) {
43+
if (sg->result_count >= BYTHOS_MAX_SUBGROUP_RESULTS) {
44+
sg->truncated = true;
45+
break;
46+
}
4047
sg->result_count += defs[i].fns[j](
4148
SUBGROUP_TAIL(sg), SUBGROUP_REMAINING(sg));
4249
}

src/check_luks.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ size_t bythos_check_luks(check_result_t *results, size_t max_results) {
136136
dump_buf, sizeof(dump_buf), &dump_status) &&
137137
dump_status == 0) {
138138
dump_ok++;
139-
if (strstr(dump_buf, "tpm2") != NULL) {
139+
if (strstr(dump_buf, "systemd-tpm2") != NULL) {
140140
any_token = true;
141141
uint32_t mask = 0;
142142
if (bythos_parse_luks_pcr_mask(dump_buf, &mask)) {

src/check_memory_encryption.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#include <stddef.h>
2-
#include <string.h>
32

43
#include "checks.h"
54
#include "checks_internal.h"
@@ -23,16 +22,14 @@ size_t bythos_check_memory_encryption(check_result_t *results, size_t max_result
2322
return used;
2423
}
2524

26-
static char cpuinfo[65536];
27-
memset(cpuinfo, 0, sizeof(cpuinfo));
28-
29-
if (!bythos_read_file_text("/proc/cpuinfo", cpuinfo, sizeof(cpuinfo))) {
25+
char flags_line[4096] = {0};
26+
if (!bythos_first_line_with_prefix("/proc/cpuinfo", "flags", flags_line, sizeof(flags_line))) {
3027
EMIT_SKIP_EXEC("memory encryption", "cpuinfo");
3128
return used;
3229
}
3330

3431
bythos_mem_enc_flags_t flags;
35-
bythos_parse_memory_encryption_flags(cpuinfo, &flags);
32+
bythos_parse_memory_encryption_flags(flags_line, &flags);
3633

3734
if (vendor == BYTHOS_CPU_VENDOR_AMD) {
3835
if (!flags.amd_sme) {

0 commit comments

Comments
 (0)