From 829fb74f9e37b6fdd3fa6e3712c75f35ba163dce Mon Sep 17 00:00:00 2001 From: iximeow Date: Thu, 10 Oct 2024 22:45:08 +0000 Subject: [PATCH] PHD: write efivars in one go (#786) * PHD: write efivars in one go on Alpine and Ubuntu 22/24 the builtin `printf` seems to be enough to get a write the full input to somewhere in efivarfs, but on Debian 11 a `\n` in the output will cause an early partial write. If the data being written is easily determined to be an invalid value by the kernel (5.10 in the Debian 11 case), that write will fail with EINVAL, and in fact the UEFI variable will not be written at all. From a brief read, I believe this to be more related to a change in Bash between Debian 11 and Ubuntu 22, than a change on the kernel side. At least on Debian 11 I observed `printf > /sys/firmware/efi/../` producing two write calls, rather than one, separated exactly at an `0x0a` in the input. And I've not found any obviously-related kernel changes since 5.10 that would make a pair of writes valid if they occurred. Ensure that we write a full UEFI variable's worth of data at once by applying `dd` with output block size set high enough to hold variable's new data. --- phd-tests/tests/src/boot_order/efi_utils.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/phd-tests/tests/src/boot_order/efi_utils.rs b/phd-tests/tests/src/boot_order/efi_utils.rs index a57f4e3c4..adf1c1f50 100644 --- a/phd-tests/tests/src/boot_order/efi_utils.rs +++ b/phd-tests/tests/src/boot_order/efi_utils.rs @@ -363,20 +363,32 @@ pub(crate) async fn write_efivar( let mut new_value = attrs; new_value.extend_from_slice(data); + let data_len = new_value.len(); // The command to write this data back out will be, roughtly: // ``` - // printf "\xAA\xAA\xAA\xAA\xDD\xDD\xDD\xDD" > /sys/firmware/efi/efivars/... + // printf "\xAA\xAA\xAA\xAA\xDD\xDD\xDD\xDD" | \ + // dd obs={inlen} of=/sys/firmware/efi/efivars/... status=none // ``` // where AAAAAAAA are the attribute bytes and DDDDDDDD are caller-provided // data. + // + // notably do not printf directly to /sys/firmware/efi/efivars/*!! printf + // may flush output early if the data to write contains a `\n` (observed at + // least on Debian 11), and such a partial write to efivars may be rejected + // as invalid UEFI variable data. let escaped: String = new_value.into_iter().fold(String::new(), |mut out, b| { write!(out, "\\x{:02x}", b).expect("can append to String"); out }); - let cmd = format!("printf \"{}\" > {}", escaped, efipath(varname)); + let cmd = format!( + "printf \"{}\" | dd obs={} of={} status=none", + escaped, + data_len, + efipath(varname) + ); let res = run_long_command(vm, &cmd).await?; // If something went sideways and the write failed with something like