diff --git a/puzzlefs-lib/src/builder.rs b/puzzlefs-lib/src/builder.rs index e4d381b..d2eea81 100644 --- a/puzzlefs-lib/src/builder.rs +++ b/puzzlefs-lib/src/builder.rs @@ -5,6 +5,7 @@ use crate::fsverity_helpers::{ }; use crate::oci::Digest; use std::any::Any; +use std::backtrace::Backtrace; use std::cmp::min; use std::collections::{BTreeMap, HashMap}; use std::ffi::{OsStr, OsString}; @@ -399,7 +400,7 @@ pub fn build_initial_rootfs( tag: &str, ) -> Result { let mut verity_data: VerityData = BTreeMap::new(); - let mut image_manifest = Image::get_empty_manifest()?; + let mut image_manifest = oci.get_empty_manifest()?; let inodes = build_delta::(rootfs, oci, None, &mut verity_data, &mut image_manifest)?; let rootfs_buf = serialize_metadata(Rootfs { @@ -430,7 +431,7 @@ pub fn add_rootfs_delta( base_layer: &str, ) -> Result<(Descriptor, Arc)> { let mut verity_data: VerityData = BTreeMap::new(); - let mut image_manifest = Image::get_empty_manifest()?; + let mut image_manifest = oci.get_empty_manifest()?; let pfs = PuzzleFS::open(oci, base_layer, None)?; let oci = Arc::clone(&pfs.oci); @@ -462,11 +463,9 @@ pub fn add_rootfs_delta( Ok((rootfs_descriptor, oci)) } -pub fn enable_fs_verity(oci: Image, tag: &str, manifest_root_hash: &str) -> Result<()> { - // first enable fs verity for the puzzlefs image manifest - let manifest_fd = oci.get_image_manifest_fd(tag)?; +fn enable_verity_for_file(file: &cap_std::fs::File) -> Result<()> { if let Err(e) = fsverity_enable( - manifest_fd.as_raw_fd(), + file.as_raw_fd(), FS_VERITY_BLOCK_SIZE_DEFAULT, InnerHashAlgorithm::Sha256, &[], @@ -476,26 +475,35 @@ pub fn enable_fs_verity(oci: Image, tag: &str, manifest_root_hash: &str) -> Resu return Err(WireFormatError::from(e)); } } - check_fs_verity(&manifest_fd, &hex::decode(manifest_root_hash)?[..])?; + Ok(()) +} + +fn enable_and_check_verity_for_file(file: &cap_std::fs::File, expected: &[u8]) -> Result<()> { + enable_verity_for_file(file)?; + check_fs_verity(file, expected) +} + +pub fn enable_fs_verity(oci: Image, tag: &str, manifest_root_hash: &str) -> Result<()> { + // first enable fs verity for the puzzlefs image manifest + let manifest_fd = oci.get_image_manifest_fd(tag)?; + enable_and_check_verity_for_file(&manifest_fd, &hex::decode(manifest_root_hash)?[..])?; let pfs = PuzzleFS::open(oci, tag, None)?; let oci = Arc::clone(&pfs.oci); let rootfs = oci.open_rootfs_blob(tag, None)?; let rootfs_fd = oci.get_pfs_rootfs(tag, None)?; - if let Err(e) = fsverity_enable( - rootfs_fd.as_raw_fd(), - FS_VERITY_BLOCK_SIZE_DEFAULT, - InnerHashAlgorithm::Sha256, - &[], - ) { - // if fsverity is enabled, ignore the error - if e.kind() != std::io::ErrorKind::AlreadyExists { - return Err(WireFormatError::from(e)); - } - } let rootfs_verity = oci.get_pfs_rootfs_verity(tag)?; - check_fs_verity(&rootfs_fd, &rootfs_verity[..])?; + + enable_and_check_verity_for_file(&rootfs_fd, &rootfs_verity[..])?; + + let manifest = oci + .0 + .find_manifest_with_tag(tag)? + .ok_or_else(|| WireFormatError::MissingManifest(tag.to_string(), Backtrace::capture()))?; + let config_digest = manifest.config().digest().digest(); + let config_digest_path = oci.blob_path().join(config_digest); + enable_verity_for_file(&oci.0.dir.open(config_digest_path)?)?; for (content_addressed_file, verity_hash) in rootfs.get_verity_data()? { let file_path = oci diff --git a/puzzlefs-lib/src/oci.rs b/puzzlefs-lib/src/oci.rs index 950156c..abc01df 100644 --- a/puzzlefs-lib/src/oci.rs +++ b/puzzlefs-lib/src/oci.rs @@ -3,6 +3,7 @@ use std::any::Any; use std::backtrace::Backtrace; use std::fs; use std::io; +use std::io::Write; use std::io::{Read, Seek}; use std::path::{Path, PathBuf}; @@ -275,13 +276,8 @@ impl Image { .ok_or_else(|| OciSpecError::Other("missing OCI index".to_string()))?) } - pub fn get_empty_manifest() -> Result { + pub fn get_empty_manifest(&self) -> Result { // see https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidance-for-an-empty-descriptor - // TODO: write the empty blob to blobs/sha256, otherwise skopeo won't be able to copy the image - // the fs verity test will also need changes because currently it makes sure that verity is - // enabled for every blob in blobs/sha256; one thing we could do is enable verity but skip - // checking if the digests match or add the verity annotations for each descriptor; another - // way is to only check the verity data for the PuzzleFS rootfs and file chunks let config = DescriptorBuilder::default() .media_type(MediaType::EmptyJSON) .size(2_u32) @@ -290,6 +286,17 @@ impl Image { )?) .data("e30=") .build()?; + + if !self.0.dir.exists( + self.blob_path() + .join("44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"), + ) { + let mut blob = self.0.create_blob()?; + blob.write_all("{}".as_bytes())?; + // TODO: blob.complete_verified_as(&config)? once https://github.com/containers/ocidir-rs/pull/18 is merged + blob.complete()?; + } + let image_manifest = ImageManifestBuilder::default() .schema_version(2_u32) .config(config) @@ -310,8 +317,8 @@ mod tests { #[test] fn test_put_blob_correct_hash() -> anyhow::Result<()> { let dir = tempdir()?; - let mut image_manifest = Image::get_empty_manifest()?; let image: Image = Image::new(dir.path())?; + let mut image_manifest = image.get_empty_manifest()?; let (desc, ..) = image.put_blob::( "meshuggah rocks".as_bytes(), &mut image_manifest, @@ -341,7 +348,7 @@ mod tests { fn test_put_get_index() -> anyhow::Result<()> { let dir = tempdir()?; let image = Image::new(dir.path())?; - let mut image_manifest = Image::get_empty_manifest()?; + let mut image_manifest = image.get_empty_manifest()?; let mut annotations = HashMap::new(); annotations.insert(ANNOTATION_REF_NAME.to_string(), "foo".to_string()); image_manifest.set_annotations(Some(annotations)); @@ -364,8 +371,8 @@ mod tests { #[test] fn double_put_ok() -> anyhow::Result<()> { let dir = tempdir()?; - let mut image_manifest = Image::get_empty_manifest()?; let image = Image::new(dir.path())?; + let mut image_manifest = image.get_empty_manifest()?; let desc1 = image.put_blob::( "meshuggah rocks".as_bytes(), &mut image_manifest,