Skip to content

Commit

Permalink
PHD: write efivars in one go (#786)
Browse files Browse the repository at this point in the history
* 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 <data> > /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.
  • Loading branch information
iximeow authored Oct 10, 2024
1 parent f5f7bf8 commit 829fb74
Showing 1 changed file with 14 additions and 2 deletions.
16 changes: 14 additions & 2 deletions phd-tests/tests/src/boot_order/efi_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 829fb74

Please sign in to comment.