boot: honour "read-only" vfat flag on random seed file (#43012)

Inspired by #42979
This commit is contained in:
Luca Boccassi
2026-07-14 11:51:43 +01:00
committed by GitHub
5 changed files with 71 additions and 4 deletions
+7
View File
@@ -216,6 +216,13 @@ boot, in order to ensure the entropy pool is filled up quickly.
too), which should be safe even with FAT file system drivers built into
low-quality EFI firmwares.
If the random seed file has the read-only FAT file attribute set, or if the
ESP file system is read-only as a whole, the seed is neither updated nor
used: a seed that cannot be updated would be identical on every boot, and
hence must not be credited. Setting the read-only file attribute on
`/loader/random-seed` may thus be used to explicitly turn off random seed
handling, for example in OS images that are replicated to multiple systems.
4. A kernel command line option `systemd.random_seed=` may be used to pass in a
base64 encoded seed to initialize the kernel's entropy pool from during
early service manager initialization.
+4 -1
View File
@@ -390,7 +390,10 @@
<filename>/EFI/Linux/</filename> on the ESP and the Extended Boot Loader partition.</para>
<para>Optionally, a random seed for early boot entropy pool provisioning is stored in
<filename>/loader/random-seed</filename> in the ESP.</para>
<filename>/loader/random-seed</filename> in the ESP. If the read-only file attribute is set on this
file, it is neither updated nor used: a seed that cannot be updated would be identical on every boot,
and hence must not be credited. The file attribute may thus be used to explicitly turn off random seed
handling, for example in OS images that are replicated to multiple systems.</para>
<para>During initialization, <command>systemd-boot</command> automatically loads all driver files placed
in the <filename>/EFI/systemd/drivers/</filename> directory of the ESP. The files placed there must have
+7 -2
View File
@@ -223,7 +223,7 @@ static EFI_STATUS acquire_secret_mixin(
(char16_t *) BOOT_SECRET_MIXIN_PATH,
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE,
/* Attributes= */ 0);
if (err == EFI_WRITE_PROTECTED) {
if (EFI_STATUS_IS_WRITE_REFUSED(err)) {
err = root_dir->Open(
root_dir,
&handle,
@@ -243,8 +243,13 @@ static EFI_STATUS acquire_secret_mixin(
err = get_file_info(handle, &info, /* ret_size= */ NULL);
if (err != EFI_SUCCESS)
return log_debug_status(err, "Failed to get boot secret mixin file '%ls' info: %m", BOOT_SECRET_MIXIN_PATH);
if (info->FileSize == 0 && writable) /* New file? Fill it. */
if (info->FileSize == 0 && writable) { /* New file? Fill it. */
/* If the mixin file is marked read-only take this as a hint that it shall not be initialized. */
if (FLAGS_SET(info->Attribute, EFI_FILE_READ_ONLY))
return log_debug_status(EFI_WRITE_PROTECTED, "Boot secret mixin file '%ls' is marked read-only, not initializing it.", BOOT_SECRET_MIXIN_PATH);
return setup_secret_mixin(handle, seed_table, ret_mixin);
}
/* If the mixin file is too small we won't overwrite it (in order to not destroy some potentially
* load bearing key), but we won't use it either. */
+11
View File
@@ -1326,6 +1326,8 @@ static EFI_STATUS boot_entry_bump_counters(BootEntry *entry) {
old_path = xasprintf("%ls\\%ls", entry->directory, entry->current_name);
err = root->Open(root, &handle, old_path, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL);
if (EFI_STATUS_IS_WRITE_REFUSED(err))
goto read_only;
if (err != EFI_SUCCESS)
return log_error_status(err, "Error opening boot entry '%ls': %m", old_path);
@@ -1333,6 +1335,11 @@ static EFI_STATUS boot_entry_bump_counters(BootEntry *entry) {
if (err != EFI_SUCCESS)
return log_error_status(err, "Error getting boot entry file info: %m");
/* If the boot entry file is marked read-only take this as a hint that the boot counter logic shall
* not be applied to it, and skip it. */
if (FLAGS_SET(file_info->Attribute, EFI_FILE_READ_ONLY))
goto read_only;
/* And rename the file */
strcpy16(file_info->FileName, entry->next_name);
err = handle->SetInfo(handle, MAKE_GUID_PTR(EFI_FILE_INFO), file_info_size, file_info);
@@ -1357,6 +1364,10 @@ static EFI_STATUS boot_entry_bump_counters(BootEntry *entry) {
}
return EFI_SUCCESS;
read_only:
log_debug("Boot entry '%ls' is read-only, skipping boot counter logic.", old_path);
return EFI_SUCCESS;
}
static EFI_STATUS call_image_start(const BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image);
+42 -1
View File
@@ -21,6 +21,8 @@
/* Some basic domain separation in case somebody uses this data elsewhere */
#define HASH_LABEL "systemd-boot random seed label v1"
#define RANDOM_SEED_PATH u"\\loader\\random-seed"
static EFI_STATUS acquire_rng(void *ret, size_t size) {
EFI_RNG_PROTOCOL *rng;
EFI_STATUS err;
@@ -109,6 +111,31 @@ static void validate_sha256(void) {
#endif
}
static bool random_seed_file_read_only(EFI_FILE *root_dir) {
_cleanup_file_close_ EFI_FILE *handle = NULL;
_cleanup_free_ EFI_FILE_INFO *info = NULL;
EFI_STATUS err;
assert(root_dir);
/* Checks whether the random seed file exists and has the read-only file attribute set. */
err = root_dir->Open(root_dir, &handle, (char16_t *) RANDOM_SEED_PATH, EFI_FILE_MODE_READ, 0);
if (err != EFI_SUCCESS) {
if (err != EFI_NOT_FOUND)
log_debug_status(err, "Failed to open random seed file for reading, ignoring: %m");
return false;
}
err = get_file_info(handle, &info, /* ret_size= */ NULL);
if (err != EFI_SUCCESS) {
log_debug_status(err, "Failed to get file info of random seed file, ignoring: %m");
return false;
}
return FLAGS_SET(info->Attribute, EFI_FILE_READ_ONLY);
}
EFI_STATUS process_random_seed(EFI_FILE *root_dir) {
uint8_t random_bytes[DESIRED_SEED_SIZE], hash_key[HASH_VALUE_SIZE];
_cleanup_free_ struct linux_efi_random_seed *new_seed_table = NULL;
@@ -143,6 +170,13 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) {
return EFI_SUCCESS;
}
/* Similarly, if the random seed file is marked read-only, take this as a hint that the seed shall
* not be updated — and hence not be used either, since a seed we cannot update would be the same on
* every boot. This provides a way to explicitly turn off random seed handling, for example for
* pre-built OS images replicated to many systems. */
if (random_seed_file_read_only(root_dir))
goto read_only;
/* hash = LABEL || sizeof(input1) || input1 || ... || sizeof(inputN) || inputN */
sha256_init_ctx(&hash);
@@ -200,7 +234,7 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) {
err = root_dir->Open(
root_dir,
&handle,
(char16_t *) u"\\loader\\random-seed",
(char16_t *) RANDOM_SEED_PATH,
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE,
0);
if (err == EFI_NOT_FOUND && seeded_by_efi) {
@@ -252,6 +286,9 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) {
if (err != EFI_SUCCESS)
return log_error_status(err, "Failed to get file info for random seed: %m");
if (FLAGS_SET(info->Attribute, EFI_FILE_READ_ONLY))
goto read_only;
/* Treat a short file just like a freshly created one for robustness reasons: consider a case
* where in a previous run a file was just created and the system was then powered off. In
* such a case the file will already exist, but be too short. */
@@ -387,4 +424,8 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) {
}
return EFI_SUCCESS;
read_only:
log_debug("Random seed file is read-only, not updating random seed.");
return EFI_SUCCESS;
}