Skip to content
Draft
9 changes: 9 additions & 0 deletions crates/osutils/src/blkid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ pub fn get_partition_label(device_path: impl AsRef<Path>) -> Result<String, Erro
run(device_path, "PARTLABEL")
}

/// Returns the filesystem label of the filesystem at `device_path`, which may
/// be a block device or a filesystem image file.
///
/// This is the label `/dev/disk/by-label/` is built from, and is distinct from
/// the GPT partition name returned by [`get_partition_label`].
pub fn get_filesystem_label(device_path: impl AsRef<Path>) -> Result<String, Error> {
run(device_path, "LABEL")
}

#[cfg(feature = "functional-test")]
#[cfg_attr(not(test), allow(unused_imports, dead_code))]
mod functional_test {
Expand Down
1 change: 1 addition & 0 deletions crates/osutils/src/dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ pub enum Dependency {
Efivar,
Efibootmgr,
Eject,
Fatlabel,
Findmnt,
#[strum(serialize = "grub2-mkconfig")]
Grub2Mkconfig,
Expand Down
92 changes: 92 additions & 0 deletions crates/osutils/src/fatlabel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//! Thin wrapper around `fatlabel`, which reads and writes the volume label of a
//! FAT filesystem.
//!
//! `mkfs.vfat` can only set a label at creation time, via `-n`, so this is the
//! way to label a FAT filesystem that already exists.

use std::path::Path;

use anyhow::{ensure, Error};

use crate::dependencies::Dependency;

/// Maximum length of a FAT volume label, in characters, as enforced by
/// `fatlabel` itself.
pub const MAX_LABEL_LENGTH: usize = 11;

/// Sets the volume label of the FAT filesystem at `device_path`.
///
/// The device may be mounted; the label is written to the boot sector and
/// survives the filesystem being unmounted.
pub fn set_label(device_path: impl AsRef<Path>, label: impl AsRef<str>) -> Result<(), Error> {
let device_path = device_path.as_ref();
let label = label.as_ref();

ensure!(
label.chars().count() <= MAX_LABEL_LENGTH,
"FAT volume label '{label}' is longer than the {MAX_LABEL_LENGTH} characters a FAT \
filesystem can hold"
);

Dependency::Fatlabel
.cmd()
.arg(device_path)
.arg(label)
.run_and_check()
.map_err(Error::from)
}

#[cfg(feature = "functional-test")]
#[cfg_attr(not(test), allow(unused_imports, dead_code))]
mod functional_test {
use super::*;

use pytest_gen::functional_test;

use crate::{blkid, filesystems::MkfsFileSystemType, mkfs};

#[functional_test(feature = "helpers")]
fn test_set_label() {
let device = Path::new("/dev/sda1");
mkfs::run(device, MkfsFileSystemType::Vfat).unwrap();

set_label(device, "TESTLABEL").unwrap();
assert_eq!(blkid::get_filesystem_label(device).unwrap(), "TESTLABEL");
}

#[functional_test(feature = "helpers", negative = true)]
fn test_set_label_too_long() {
set_label(Path::new("/dev/sda1"), "THIS-LABEL-IS-TOO-LONG").unwrap_err();
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_set_label_rejects_overlong_label() {
// Rejected before invoking fatlabel, so this needs no filesystem.
let err = set_label(Path::new("/dev/null"), "THIS-LABEL-IS-TOO-LONG").unwrap_err();
assert!(
err.to_string().contains("longer than"),
"got: {}",
err.to_string()
);
}

#[test]
fn test_max_label_length_is_accepted_by_the_length_check() {
// 11 characters, e.g. the conventional ESP label, must not be rejected
// by the length check. (The call itself will fail on /dev/null, which
// is not a FAT filesystem, so only the check is exercised here.)
let label = "EFI-SYSTEM!";
assert_eq!(label.chars().count(), MAX_LABEL_LENGTH);
let err = set_label(Path::new("/dev/null"), label).unwrap_err();
assert!(
!err.to_string().contains("longer than"),
"length check should have passed, got: {}",
err.to_string()
);
}
}
1 change: 1 addition & 0 deletions crates/osutils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod efibootmgr;
pub mod efivar;
pub mod encryption;
pub mod exe;
pub mod fatlabel;
pub mod files;
pub mod filesystems;
pub mod findmnt;
Expand Down
36 changes: 35 additions & 1 deletion crates/osutils/src/veritysetup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ pub struct VerityDevice {
data_device_path: PathBuf,
hash_device_path: PathBuf,
root_hash: String,

/// Byte offset of the verity superblock inside the hash device.
///
/// Set for *inline* verity, where the hash tree is stored inside the data
/// device itself, so `data_device_path == hash_device_path`.
hash_offset: Option<u64>,
}

impl VerityDevice {
Expand All @@ -53,16 +59,25 @@ impl VerityDevice {
data_device_path: data_device_path.into(),
hash_device_path: hash_device_path.into(),
root_hash: root_hash.into(),
hash_offset: None,
}
}

/// Sets the byte offset at which the verity hash tree starts, for images
/// that store the hash tree inline in the data device.
pub fn with_hash_offset(mut self, hash_offset: Option<u64>) -> Self {
self.hash_offset = hash_offset;
self
}

/// Will attempt to open the device with a signature file and verify it.
pub fn open_with_signature(&self, signature_file: impl AsRef<Path>) -> Result<(), Error> {
open_with_signature(
&self.device_name,
&self.data_device_path,
&self.hash_device_path,
&self.root_hash,
self.hash_offset,
signature_file,
)?;

Expand All @@ -76,6 +91,7 @@ impl VerityDevice {
&self.data_device_path,
&self.hash_device_path,
&self.root_hash,
self.hash_offset,
)?;

self.validate_or_close(EXPECTED_VERITY_DEVICE_STATUS)
Expand Down Expand Up @@ -191,12 +207,14 @@ pub fn open(
data_device_path: impl AsRef<Path>,
hash_device_path: impl AsRef<Path>,
root_hash: impl AsRef<str>,
hash_offset: Option<u64>,
) -> Result<(), Error> {
open_inner(
name,
data_device_path,
hash_device_path,
root_hash,
hash_offset,
None::<&Path>,
)
}
Expand All @@ -207,13 +225,15 @@ fn open_with_signature(
data_device_path: impl AsRef<Path>,
hash_device_path: impl AsRef<Path>,
root_hash: impl AsRef<str>,
hash_offset: Option<u64>,
signature_file: impl AsRef<Path>,
) -> Result<(), Error> {
open_inner(
name,
data_device_path,
hash_device_path,
root_hash,
hash_offset,
Some(signature_file),
)
}
Expand All @@ -224,6 +244,7 @@ fn open_inner(
data_device_path: impl AsRef<Path>,
hash_device_path: impl AsRef<Path>,
root_hash: impl AsRef<str>,
hash_offset: Option<u64>,
signature_file: Option<impl AsRef<Path>>,
) -> Result<(), Error> {
let mut cmd = Dependency::Veritysetup.cmd();
Expand All @@ -234,6 +255,12 @@ fn open_inner(
.arg(root_hash.as_ref())
.arg("--verbose");

// Inline verity: the hash tree lives inside the data device at this byte
// offset, so the device is passed as both the data and the hash device.
if let Some(hash_offset) = hash_offset {
cmd.arg(format!("--hash-offset={hash_offset}"));
}

// If a signature file is provided, add it to the command.
if let Some(signature_file) = signature_file {
let mut arg = OsString::from("--root-hash-signature=");
Expand Down Expand Up @@ -263,9 +290,16 @@ pub fn open_with_guard(
data_device_path: impl AsRef<Path>,
hash_device_path: impl AsRef<Path>,
root_hash: impl AsRef<str>,
hash_offset: Option<u64>,
) -> Result<VerityDeviceGuard, Error> {
let device_name = name.as_ref();
open(device_name, data_device_path, hash_device_path, root_hash)?;
open(
device_name,
data_device_path,
hash_device_path,
root_hash,
hash_offset,
)?;
Ok(VerityDeviceGuard::new(device_name.to_owned()))
}

Expand Down
5 changes: 5 additions & 0 deletions crates/trident/src/engine/boot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ pub mod uki;

pub(crate) const ESP_EXTRACTION_DIRECTORY: &str = VAR_TMP_PATH;

/// Mode to create [`ESP_EXTRACTION_DIRECTORY`] with when the OS image does not
/// ship it. `/var/tmp` is world-writable with the sticky bit set on a
/// conventional system, and systemd-tmpfiles expects to find it that way.
pub(crate) const ESP_EXTRACTION_DIRECTORY_MODE: u32 = 0o1777;

#[derive(Default, Debug)]
pub(super) struct BootSubsystem;
impl Subsystem for BootSubsystem {
Expand Down
Loading
Loading