Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
22 changes: 14 additions & 8 deletions crates/trident/src/engine/storage/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,20 @@ pub(super) fn deploy_images(ctx: &EngineContext) -> Result<(), TridentError> {
FileSystemResize::NoResize,
),
);
combined_images.insert(
image_file_verity.hash_image_file.path.clone(),
(
verity_device.hash_device_id.clone(),
&image_file_verity.hash_image_file,
FileSystemResize::NoResize,
),
);

// For inline verity the hash tree is already part of the data image
// we just queued, so writing that image lands both at once and
// there is no separate hash image to deploy.
if !verity_device.is_inline() {
combined_images.insert(
image_file_verity.hash_image_file.path.clone(),
(
verity_device.hash_device_id.clone(),
&image_file_verity.hash_image_file,
FileSystemResize::NoResize,
),
);
}
} else {
// For non-verity devices, we can deploy the image directly.

Expand Down
68 changes: 46 additions & 22 deletions crates/trident/src/engine/storage/verity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,17 @@ pub(crate) fn get_updated_device_name(device_name: &str) -> String {
format!("{device_name}_new")
}

/// Get the root-verity root hash.
fn get_root_verity_root_hash(ctx: &EngineContext) -> Result<String, Error> {
/// Verity facts that come from the OS image rather than the Host Configuration.
#[derive(Debug)]
struct VerityImageInfo {
roothash: String,

/// Byte offset of the hash tree, for images that store it inline.
hash_offset: Option<u64>,
}

/// Get the root-verity information from the OS image.
fn get_root_verity_info(ctx: &EngineContext) -> Result<VerityImageInfo, Error> {
// Extract information from the OS image.
let Some(os_img) = ctx.image.as_ref() else {
bail!("Image is not available");
Expand All @@ -51,11 +60,14 @@ fn get_root_verity_root_hash(ctx: &EngineContext) -> Result<String, Error> {
bail!("Root filesystem in OS image is not verity enabled");
};

Ok(verity.roothash.clone())
Ok(VerityImageInfo {
roothash: verity.roothash.clone(),
hash_offset: verity.hash_offset,
})
}

/// Gets the usr-verity root hash.
fn get_usr_verity_root_hash(ctx: &EngineContext) -> Result<String, Error> {
/// Gets the usr-verity information from the OS image.
fn get_usr_verity_info(ctx: &EngineContext) -> Result<VerityImageInfo, Error> {
// Extract information from the OS image.
let Some(os_img) = ctx.image.as_ref() else {
bail!("Image is not available");
Expand All @@ -71,7 +83,10 @@ fn get_usr_verity_root_hash(ctx: &EngineContext) -> Result<String, Error> {
bail!("usr filesystem in OS image is not verity enabled");
};

Ok(verity.roothash.clone())
Ok(VerityImageInfo {
roothash: verity.roothash.clone(),
hash_offset: verity.hash_offset,
})
}

/// Setup verity devices.
Expand All @@ -89,29 +104,40 @@ pub(super) fn setup_verity_devices(ctx: &EngineContext) -> Result<(), Error> {
let (data_dev, hash_dev) = get_verity_device_paths(ctx, verity_device)?;
let update_name = get_updated_device_name(&verity_device.name);

let root_hash = if ctx.storage_graph.root_fs_is_verity() {
let verity_info = if ctx.storage_graph.root_fs_is_verity() {
debug!(
"Setting up verity device '{}' for root filesystem",
verity_device.id
);

get_root_verity_root_hash(ctx)?
get_root_verity_info(ctx)?
} else if ctx.storage_graph.usr_fs_is_verity() {
debug!(
"Setting up verity device '{}' for usr filesystem",
verity_device.id
);

get_usr_verity_root_hash(ctx)?
get_usr_verity_info(ctx)?
} else {
bail!(
"Verity device '{}' is not on a supported filesystem.",
verity_device.name
);
};

// Inline verity has no hash device of its own, so the OS image must tell us
// where inside the data device the hash tree starts.
if verity_device.is_inline() && verity_info.hash_offset.is_none() {
bail!(
"Verity device '{}' has no hash device, but the OS image does not provide a hash \
offset for it.",
verity_device.name
);
}

// Create the internal representation of the verity device.
let verity_dev = VerityDeviceUtils::new(update_name, data_dev, hash_dev, root_hash);
let verity_dev = VerityDeviceUtils::new(update_name, data_dev, hash_dev, verity_info.roothash)
.with_hash_offset(verity_info.hash_offset);

// Check internal parameters for verity signatures.
if let Some(signature_file_map) = ctx
Expand Down Expand Up @@ -315,6 +341,8 @@ pub fn get_verity_device_paths(
verity_device.data_device_id
))?;

// For inline verity this resolves to the same path as the data device,
// which is exactly what `veritysetup` expects alongside `--hash-offset`.
let verity_hash_path = ctx
.get_block_device_path(&verity_device.hash_device_id)
.context(format!(
Expand Down Expand Up @@ -457,7 +485,7 @@ mod tests {
}

#[test]
fn test_get_usr_verity_root_hash() {
fn test_get_usr_verity_info() {
let expected_root_hash = "sample-roothash";
let mut mock = MockOsImage::new().with_image(MockImage::new(
USR_MOUNT_POINT_PATH,
Expand All @@ -472,34 +500,30 @@ mod tests {
};

assert_eq!(
get_usr_verity_root_hash(&as_ctx(&mock)).unwrap(),
get_usr_verity_info(&as_ctx(&mock)).unwrap().roothash,
expected_root_hash,
"Root hash does not match expected"
);

// test failure when root filesystem is not verity enabled
mock.images[0].verity = None;
assert_eq!(
get_usr_verity_root_hash(&as_ctx(&mock))
.unwrap_err()
.to_string(),
get_usr_verity_info(&as_ctx(&mock)).unwrap_err().to_string(),
"usr filesystem in OS image is not verity enabled",
"Got unexpected error"
);

// test failure when root filesystem is not found
mock.images.clear();
assert_eq!(
get_usr_verity_root_hash(&as_ctx(&mock))
.unwrap_err()
.to_string(),
get_usr_verity_info(&as_ctx(&mock)).unwrap_err().to_string(),
"Failed to get usr filesystem from OS image",
"Got unexpected error"
);
}

#[test]
fn test_get_root_verity_root_hash() {
fn test_get_root_verity_info() {
let expected_root_hash = "sample-roothash";
let mut mock = MockOsImage::new().with_image(MockImage::new(
ROOT_MOUNT_POINT_PATH,
Expand All @@ -514,15 +538,15 @@ mod tests {
};

assert_eq!(
get_root_verity_root_hash(&as_ctx(&mock)).unwrap(),
get_root_verity_info(&as_ctx(&mock)).unwrap().roothash,
expected_root_hash,
"Root hash does not match expected"
);

// test failure when root filesystem is not verity enabled
mock.images[0].verity = None;
assert_eq!(
get_root_verity_root_hash(&as_ctx(&mock))
get_root_verity_info(&as_ctx(&mock))
.unwrap_err()
.to_string(),
"Root filesystem in OS image is not verity enabled",
Expand All @@ -532,7 +556,7 @@ mod tests {
// test failure when root filesystem is not found
mock.images.clear();
assert_eq!(
get_root_verity_root_hash(&as_ctx(&mock))
get_root_verity_info(&as_ctx(&mock))
.unwrap_err()
.to_string(),
"Failed to get root filesystem from OS image",
Expand Down
Loading