diff --git a/Cargo.toml b/Cargo.toml index 9d55f0c..fbbdc04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["rust/composefs-core"] +members = ["rust/composefs-core", "rust/composefs-sys"] resolver = "2" [profile.dev] @@ -15,4 +15,4 @@ debug = true [profile.releaselto] codegen-units = 1 inherits = "release" -lto = "yes" \ No newline at end of file +lto = "yes" diff --git a/rust/composefs-core/Cargo.toml b/rust/composefs-core/Cargo.toml index 7784d53..5bb593a 100644 --- a/rust/composefs-core/Cargo.toml +++ b/rust/composefs-core/Cargo.toml @@ -21,6 +21,7 @@ path = "src/lib.rs" [dependencies] anyhow = "1.0" libc = "0.2" +composefs-sys = { path = "../composefs-sys" } [dev-dependencies] tar = "0.4.38" diff --git a/rust/composefs-core/src/fsverity.rs b/rust/composefs-core/src/fsverity.rs new file mode 100644 index 0000000..a8c6614 --- /dev/null +++ b/rust/composefs-core/src/fsverity.rs @@ -0,0 +1,61 @@ +//! # Bindings for computing fsverity +//! +//! This collection of APIs is for computing fsverity digests as +//! used by composefs. + +use std::os::fd::{AsRawFd, BorrowedFd}; + +use composefs_sys::{map_result, LCFS_SHA256_DIGEST_LEN}; + +/// The binary composefs digest +#[derive(Debug, PartialEq, Eq, Clone, Default)] +pub struct Digest([u8; LCFS_SHA256_DIGEST_LEN]); + +impl Digest { + /// Create an uninitialized digest. + pub fn new() -> Self { + Self::default() + } + + /// Retrieve the digest bytes + pub fn get(&self) -> &[u8; LCFS_SHA256_DIGEST_LEN] { + &self.0 + } +} + +/// Compute the composefs fsverity digest from the provided file descriptor. +#[allow(unsafe_code)] +pub fn fsverity_digest_from_fd(fd: BorrowedFd, digest: &mut Digest) -> std::io::Result<()> { + unsafe { + map_result(composefs_sys::lcfs_compute_fsverity_from_fd( + digest.0.as_mut_ptr(), + fd.as_raw_fd(), + )) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use std::io::{Seek, Write}; + use std::os::fd::AsFd; + + use super::*; + + #[test] + fn test_digest() -> Result<()> { + let mut tf = tempfile::tempfile()?; + tf.write_all(b"hello world")?; + let mut digest = Digest::new(); + tf.seek(std::io::SeekFrom::Start(0))?; + fsverity_digest_from_fd(tf.as_fd(), &mut digest)?; + assert_eq!( + digest.get(), + &[ + 30, 46, 170, 66, 2, 215, 80, 164, 17, 116, 238, 69, 73, 112, 185, 44, 27, 194, 249, + 37, 177, 227, 80, 118, 216, 199, 213, 245, 99, 98, 186, 100 + ] + ); + Ok(()) + } +} diff --git a/rust/composefs-core/src/lib.rs b/rust/composefs-core/src/lib.rs index 2fdf1ef..3e3ccf5 100644 --- a/rust/composefs-core/src/lib.rs +++ b/rust/composefs-core/src/lib.rs @@ -23,6 +23,8 @@ use dumpfile::Entry; pub mod dumpfile; pub mod mkcomposefs; +pub mod fsverity; + /// Parse a composefs superblock. pub fn dump(f: File, mut callback: F) -> Result<()> where diff --git a/rust/composefs-sys/Cargo.toml b/rust/composefs-sys/Cargo.toml new file mode 100644 index 0000000..9ca1c49 --- /dev/null +++ b/rust/composefs-sys/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "composefs-sys" +version = "0.1.0" +edition = "2021" +links = "composefs" +build = "build.rs" + +[package.metadata.system-deps.composefs] +name = "composefs" +version = "1" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +system-deps = "6" + +[dev-dependencies] +anyhow = "1" +tempfile = "3" diff --git a/rust/composefs-sys/build.rs b/rust/composefs-sys/build.rs new file mode 100644 index 0000000..d66b1a5 --- /dev/null +++ b/rust/composefs-sys/build.rs @@ -0,0 +1,6 @@ +fn main() { + if let Err(s) = system_deps::Config::new().probe() { + println!("cargo:warning={s}"); + std::process::exit(1); + } +} diff --git a/rust/composefs-sys/src/lib.rs b/rust/composefs-sys/src/lib.rs new file mode 100644 index 0000000..4fc163e --- /dev/null +++ b/rust/composefs-sys/src/lib.rs @@ -0,0 +1,60 @@ +//! # Bindings for libcomposefs +//! +//! This crate contains a few manually maintained system bindings for libcomposefs. +pub const LCFS_SHA256_DIGEST_LEN: usize = 32; + +extern "C" { + pub fn lcfs_compute_fsverity_from_fd( + digest: *mut u8, + fd: std::os::raw::c_int, + ) -> std::os::raw::c_int; + pub fn lcfs_fd_enable_fsverity(fd: std::os::raw::c_int) -> std::os::raw::c_int; +} + +/// Convert an integer return value into a `Result`. +pub fn map_result(r: std::os::raw::c_int) -> std::io::Result<()> { + match r { + 0 => Ok(()), + _ => Err(std::io::Error::last_os_error()), + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use std::io::{Seek, Write}; + use std::os::fd::AsRawFd; + + use super::*; + + #[test] + fn test_fd_enable_fsverity() -> Result<()> { + // We can't require fsverity in our test suite, so just verify we can call the + // function. + let mut tf = tempfile::NamedTempFile::new()?; + tf.write_all(b"hello")?; + let tf = std::fs::File::open(tf.path())?; + let _ = unsafe { lcfs_fd_enable_fsverity(tf.as_raw_fd()) }; + Ok(()) + } + + #[test] + fn test_digest() -> Result<()> { + unsafe { + let mut tf = tempfile::tempfile()?; + tf.write_all(b"hello world")?; + let mut buf = [0u8; LCFS_SHA256_DIGEST_LEN]; + tf.seek(std::io::SeekFrom::Start(0))?; + let r = lcfs_compute_fsverity_from_fd(buf.as_mut_ptr(), tf.as_raw_fd()); + assert_eq!(r, 0); + assert_eq!( + buf, + [ + 30, 46, 170, 66, 2, 215, 80, 164, 17, 116, 238, 69, 73, 112, 185, 44, 27, 194, + 249, 37, 177, 227, 80, 118, 216, 199, 213, 245, 99, 98, 186, 100 + ] + ); + Ok(()) + } + } +}