Преглед изворни кода

Added a new binary crate to serve as a FUSE daemon.

Matthew Carr пре 2 година
родитељ
комит
7f33fa6093

+ 14 - 2
Cargo.lock

@@ -129,6 +129,18 @@ dependencies = [
  "alloc-stdlib",
 ]
 
+[[package]]
+name = "btfuse"
+version = "0.1.0"
+dependencies = [
+ "btlib",
+ "env_logger",
+ "fuse-backend-rs",
+ "log",
+ "tempdir",
+ "tss-esapi",
+]
+
 [[package]]
 name = "btlib"
 version = "0.1.0"
@@ -421,9 +433,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 
 [[package]]
 name = "libc"
-version = "0.2.133"
+version = "0.2.137"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
+checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
 
 [[package]]
 name = "libdbus-sys"

+ 2 - 1
Cargo.toml

@@ -4,5 +4,6 @@ members = [
     "crates/btnode",
     "crates/btserde",
     "crates/harness",
-    "crates/test-harness"
+    "crates/test-harness",
+    "crates/btfuse",
 ]

+ 17 - 0
crates/btfuse/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "btfuse"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+btlib = { path = "../btlib" }
+tss-esapi = { version = "7.1.0", features = ["generate-bindings"] }
+fuse-backend-rs = "0.9.6"
+log = "0.4.17"
+env_logger = "0.9.0"
+
+[dev-dependencies]
+btlib = { path = "../btlib", features = ["testing"] }
+tempdir = "0.3.7"

+ 162 - 0
crates/btfuse/src/main.rs

@@ -0,0 +1,162 @@
+use btlib::{
+    blocktree::Blocktree,
+    crypto::{tpm::TpmCredStore, CredStore},
+};
+use fuse_backend_rs::{api::server::Server, transport::{FuseSession, Error}};
+use log::error;
+use std::{
+    ffi::{c_char, CString},
+    fs::{self, File},
+    io,
+    os::fd::FromRawFd,
+    os::{raw::c_int, unix::ffi::OsStrExt},
+    path::{Path, PathBuf},
+    str::FromStr,
+};
+use tss_esapi::{
+    tcti_ldr::{TabrmdConfig, TctiNameConf},
+    Context,
+};
+
+const DEFAULT_TABRMD: &str = "bus_type=session";
+const MOUNT_OPTIONS: &str = "default_permissions";
+const FSNAME: &str = "btfuse";
+const FSTYPE: &str = "bt";
+
+trait PathExt {
+    fn try_create_dir(&self) -> io::Result<()>;
+}
+
+impl<T: AsRef<Path>> PathExt for T {
+    fn try_create_dir(&self) -> io::Result<()> {
+        match fs::create_dir(self) {
+            Ok(_) => Ok(()),
+            Err(err) => match err.kind() {
+                io::ErrorKind::AlreadyExists => Ok(()),
+                _ => Err(err),
+            },
+        }
+    }
+}
+
+#[link(name = "fuse3")]
+extern "C" {
+    /// Opens a channel to the kernel.
+    fn fuse_open_channel(mountpoint: *const c_char, options: *const c_char) -> c_int;
+}
+
+/// Calls into libfuse3 to mount this file system at the given path. The file descriptor to use
+/// to communicate with the kernel is returned.
+fn mount_at<P: AsRef<Path>>(mountpoint: P) -> File {
+    let mountpoint = CString::new(mountpoint.as_ref().as_os_str().as_bytes()).unwrap();
+    let options = CString::new(MOUNT_OPTIONS).unwrap();
+    let raw_fd = unsafe { fuse_open_channel(mountpoint.as_ptr(), options.as_ptr()) };
+    unsafe { File::from_raw_fd(raw_fd) }
+}
+
+/// A fuse daemon process.
+struct FuseDaemon<'a> {
+    /// The path of the directory to store all file system information in.
+    path: &'a Path,
+    /// The configuration string to use to connect to a Tabrmd instance.
+    tabrmd_config: &'a str,
+}
+
+impl<'a> FuseDaemon<'a> {
+    fn new(path: &'a Path, tabrmd_config: &'a str) -> FuseDaemon<'_> {
+        FuseDaemon {
+            path,
+            tabrmd_config,
+        }
+    }
+
+    fn start(&self) {
+        self.path
+            .try_create_dir()
+            .expect("failed to create main directory");
+        let mnt_path = self.path.join("mnt");
+        mnt_path
+            .try_create_dir()
+            .expect("failed to create mount directory");
+        let empty = fs::read_dir(&mnt_path)
+            .expect("failed to read mountdir")
+            .next()
+            .is_none();
+        let context = Context::new(TctiNameConf::Tabrmd(
+            TabrmdConfig::from_str(self.tabrmd_config).expect("failed to parse Tabrmd config"),
+        ))
+        .expect("failed to connect to Tabrmd");
+        let cred_store = TpmCredStore::new(context, self.path.join("tpm_state"))
+            .expect("failed to create TpmCredStore");
+        let node_creds = cred_store
+            .node_creds()
+            .expect("failed to retrieve node creds");
+        let bt_path = self.path.join("bt");
+        bt_path
+            .try_create_dir()
+            .expect("failed to create blocktree directory");
+        let fs = if empty {
+            Blocktree::new_empty(bt_path, 0, node_creds)
+        } else {
+            Blocktree::new_existing(bt_path, node_creds)
+        }
+        .expect("failed to create blocktree");
+        let server = Server::new(fs);
+        let mut session = FuseSession::new(&mnt_path, FSNAME, FSTYPE, false)
+            .expect("failed to create FUSE session");
+        session.set_fuse_file(mount_at(&mnt_path));
+        let mut channel = session
+            .new_channel()
+            .expect("failed to create FUSE channel");
+
+        loop {
+            match channel.get_request() {
+                Ok(Some((reader, writer))) => {
+                    if let Err(err) = server.handle_message(reader, writer.into(), None, None) {
+                        error!("error while handling FUSE message: {err}");
+                    }
+                }
+                Ok(None) => break,
+                Err(err) => {
+                    match err {
+                        // Occurs when the file system is unmounted.
+                        Error::SessionFailure(_) => break,
+                        _ => error!("{err}"),
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn main() {
+    env_logger::init();
+    let main_dir = std::env::args().nth(1).expect("no mount point given");
+    let main_dir =
+        PathBuf::from_str(main_dir.as_str()).expect("failed to convert mount point to PathBuf");
+    let tabrmd_string = std::env::var("BT_TABRMD").ok();
+    let tabrmd_str = tabrmd_string
+        .as_ref()
+        .map_or(DEFAULT_TABRMD, |s| s.as_str());
+    let daemon = FuseDaemon::new(&main_dir, tabrmd_str);
+    daemon.start();
+}
+
+#[cfg(test)]
+mod test {
+    use btlib::test_helpers;
+    use tempdir::TempDir;
+
+    use super::*;
+
+    /// Starts the `FuseDaemon` in a new temporary directory prefixed by "btfuse".
+    #[test]
+    fn server_start() {
+        std::env::set_var("RUST_LOG", "info");
+        env_logger::init();
+        let temp_dir = TempDir::new("btfuse").expect("failed to create TempDir");
+        let swtpm = test_helpers::SwtpmHarness::new().expect("failed to start swtpm");
+        let daemon = FuseDaemon::new(temp_dir.path(), swtpm.tabrmd_config());
+        daemon.start();
+    }
+}

+ 24 - 9
crates/btlib/Cargo.toml

@@ -6,6 +6,19 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
+[features]
+# This feature allows test only code to be used in other crates. See the comment below for more
+# information.
+testing=[
+    "dep:tempdir",
+    "dep:ctor",
+    "dep:nix",
+    "dep:env_logger",
+    "dep:lazy_static",
+    "dep:dbus",
+    "dep:vm-memory"
+]
+
 [dependencies]
 btserde = { path = "../btserde" }
 harness = { path = "../harness" }
@@ -25,12 +38,14 @@ brotli = "3.3.4"
 os_pipe = { version = "1.1.1", features = ["io_safety"] }
 zerocopy = "0.6.1"
 fuse-backend-rs = "0.9.6"
-
-[dev-dependencies]
-tempdir = "0.3.7"
-ctor = "0.1.22"
-nix = "0.25.0"
-env_logger = "0.9.0"
-lazy_static = "1.4.0"
-dbus = "0.9.6"
-vm-memory = "0.9.0"
+# These are dev-dependencies. They are declared this way so test_helpers can be used in other
+# crates. The build.rs script also supports this by turning on "cfg(test)" when
+# the testing feature is enabled. This is a hack and should be removed once this issue is resolved:
+# https://github.com/rust-lang/cargo/issues/8379
+tempdir = { version = "0.3.7", optional = true }
+ctor = { version = "0.1.22", optional = true }
+nix = { version = "0.25.0", optional = true }
+env_logger = { version = "0.9.0", optional = true }
+lazy_static = { version = "1.4.0", optional = true }
+dbus = { version = "0.9.6", optional = true }
+vm-memory = { version = "0.9.0", optional = true }

+ 5 - 0
crates/btlib/build.rs

@@ -0,0 +1,5 @@
+fn main() {
+    // Turn on the testing feature when "cfg(test)" is given.
+    #[cfg(feature = "test")]
+    println!("cargo:rustc-cfg=feature=\"testing\"")
+}

+ 6 - 1
crates/btlib/scripts/swtpm.sh

@@ -33,7 +33,12 @@ start() {
         --tpm2 --log file=$TPM_PATH/log.txt,level=5 \
         --flags not-need-init,startup-clear --pid file=$TPM_PID \
         --tpmstate dir=$TPM_PATH --daemon
-    tpm2-abrmd --tcti="swtpm:host=$TPM_ADDR" --session &
+    if [ "$UID" = 0 ]; then
+        # If this script was run as root, then connect to the system bus.
+        tpm2-abrmd --tcti="swtpm:host=$TPM_ADDR" --allow-root &
+    else
+        tpm2-abrmd --tcti="swtpm:host=$TPM_ADDR" --session &
+    fi
     echo -n $! > $TPM_ABRMD_PID
 }
 

+ 1 - 0
crates/btlib/src/block_path.rs

@@ -186,6 +186,7 @@ mod private {
     }
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use crate::{

+ 5 - 0
crates/btlib/src/blocktree.rs

@@ -8,6 +8,7 @@ mod private {
             Context, Entry, FileSystem, FsOptions, OpenOptions, ZeroCopyReader, ZeroCopyWriter,
         },
     };
+    use log::info;
     use serde::{Deserialize, Serialize};
     use std::{
         collections::hash_map::{self, HashMap},
@@ -274,11 +275,14 @@ mod private {
         }
     }
 
+    unsafe impl<C: Sync> Sync for Blocktree<C> {}
+
     impl<C: Creds + Clone + 'static> FileSystem for Blocktree<C> {
         type Inode = Inode;
         type Handle = u64;
 
         fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
+            info!("Blocktree::init called");
             let options = FsOptions::empty()
                 .union(FsOptions::ASYNC_READ.complement())
                 .union(FsOptions::BIG_WRITES)
@@ -426,6 +430,7 @@ mod private {
     }
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use std::ffi::CString;

+ 1 - 0
crates/btlib/src/crypto/merkle_stream.rs

@@ -688,6 +688,7 @@ mod private {
     }
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 pub(crate) mod tests {
     use std::io::{Read, Seek, SeekFrom, Write};

+ 3 - 2
crates/btlib/src/crypto/mod.rs

@@ -740,7 +740,7 @@ impl Signature {
         Signature { kind, data }
     }
 
-    #[cfg(test)]
+    #[cfg(feature = "testing")]
     pub(crate) fn copy_from(kind: Sign, from: &[u8]) -> Signature {
         let mut data = vec![0; kind.key_len() as usize];
         data.as_mut_slice().copy_from_slice(from);
@@ -1583,7 +1583,7 @@ impl ConcreteCreds {
         }
     }
 
-    #[cfg(test)]
+    #[cfg(feature = "testing")]
     pub(crate) fn generate() -> Result<ConcreteCreds> {
         let encrypt = Encrypt::RSA_OAEP_3072_SHA_256.generate()?;
         let sign = Sign::RSA_PSS_3072_SHA_256.generate()?;
@@ -2061,6 +2061,7 @@ impl Writecap {
     }
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use super::*;

+ 1 - 0
crates/btlib/src/crypto/secret_stream.rs

@@ -211,6 +211,7 @@ mod private {
     impl<T: Read + Write + Seek + MetaAccess + FileReadWriteVolatile> Block for SecretStream<T> {}
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use std::io::{Read, Seek, SeekFrom, Write};

+ 1 - 0
crates/btlib/src/crypto/tpm.rs

@@ -1516,6 +1516,7 @@ impl HasResponseCode for TSS2_RC {
     }
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 mod test {
     use crate::test_helpers::{BtCursor, SwtpmHarness};

+ 4 - 3
crates/btlib/src/lib.rs

@@ -10,13 +10,13 @@ pub mod msg;
 mod sectored_buf;
 mod trailered;
 
-#[cfg(test)]
-mod test_helpers;
+#[cfg(feature = "testing")]
+pub mod test_helpers;
 
 #[macro_use]
 extern crate static_assertions;
 
-#[cfg(test)]
+#[cfg(feature = "testing")]
 #[macro_use]
 extern crate lazy_static;
 
@@ -1004,6 +1004,7 @@ impl<F: FnOnce()> Drop for DropTrigger<F> {
     }
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use std::{fs::OpenOptions, io::Cursor, path::PathBuf};

+ 1 - 0
crates/btlib/src/msg.rs

@@ -167,6 +167,7 @@ mod private {
     }
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use crate::test_helpers;

+ 1 - 0
crates/btlib/src/sectored_buf.rs

@@ -354,6 +354,7 @@ mod private {
     impl<T: Read + Write + Seek + MetaAccess + FileReadWriteVolatile> Block for SectoredBuf<T> {}
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use std::io::{Read, Seek, SeekFrom, Write};

+ 25 - 22
crates/btlib/src/test_helpers.rs

@@ -68,7 +68,7 @@ pub const SIGNATURE: [u8; 384] = [
 ];
 
 /// The key of the parent of test blocks.
-pub(crate) static PARENT_KEY: SymKey = {
+pub static PARENT_KEY: SymKey = {
     let key = [
         0x35, 0x3D, 0x8C, 0x95, 0x6C, 0x8D, 0xE6, 0xC0, 0xB0, 0xD5, 0x1C, 0xE9, 0x94, 0xB0, 0x58,
         0xD3, 0x80, 0x46, 0x12, 0x1C, 0xF3, 0x9B, 0x8A, 0xEC, 0x38, 0xD5, 0x8B, 0x05, 0x92, 0x8D,
@@ -88,7 +88,7 @@ lazy_static! {
 }
 
 /// The key used to encrypt test blocks.
-pub(crate) static BLOCK_KEY: SymKey = {
+pub static BLOCK_KEY: SymKey = {
     let key = [
         0xB2, 0xB3, 0xDA, 0x5A, 0x1A, 0xF6, 0xB3, 0x78, 0x30, 0xAB, 0x1D, 0x33, 0x33, 0xE7, 0xE3,
         0x5B, 0xBB, 0xF9, 0xFE, 0xD0, 0xC1, 0xF7, 0x90, 0x34, 0x69, 0xB7, 0xE7, 0xC6, 0x1C, 0x46,
@@ -113,11 +113,11 @@ fn convert_err<E: Display>(err: E) -> Error {
     Error::Message(err.to_string())
 }
 
-pub(crate) fn make_principal() -> Principal {
+pub fn make_principal() -> Principal {
     Principal(VarHash::Sha2_256(PRINCIPAL.into()))
 }
 
-pub(crate) fn make_path_with_root(root: Principal, rel_components: Vec<&str>) -> BlockPath {
+pub fn make_path_with_root(root: Principal, rel_components: Vec<&str>) -> BlockPath {
     let mut components = Vec::with_capacity(rel_components.len() + 1);
     components.push(root.0.to_string());
     for component in rel_components {
@@ -126,11 +126,11 @@ pub(crate) fn make_path_with_root(root: Principal, rel_components: Vec<&str>) ->
     BlockPath::new(root, components)
 }
 
-pub(crate) fn make_path(rel_components: Vec<&str>) -> BlockPath {
+pub fn make_path(rel_components: Vec<&str>) -> BlockPath {
     make_path_with_root(make_principal(), rel_components)
 }
 
-pub(crate) fn make_writecap_and_creds(rel_components: Vec<&str>) -> (Writecap, impl Creds) {
+pub fn make_writecap_and_creds(rel_components: Vec<&str>) -> (Writecap, impl Creds) {
     let (root_writecap, root_key) = make_self_signed_writecap();
     let issued_to = Principal(VarHash::Sha2_256(PRINCIPAL.into()));
     (
@@ -139,12 +139,12 @@ pub(crate) fn make_writecap_and_creds(rel_components: Vec<&str>) -> (Writecap, i
     )
 }
 
-pub(crate) fn make_writecap(rel_components: Vec<&str>) -> Writecap {
+pub fn make_writecap(rel_components: Vec<&str>) -> Writecap {
     let (writecap, ..) = make_writecap_and_creds(rel_components);
     writecap
 }
 
-pub(crate) fn make_writecap_trusted_by<C: Creds>(
+pub fn make_writecap_trusted_by<C: Creds>(
     next: Writecap,
     trusting_creds: &C,
     issued_to: Principal,
@@ -168,16 +168,16 @@ pub(crate) fn make_writecap_trusted_by<C: Creds>(
     writecap
 }
 
-pub(crate) fn make_key_pair() -> impl Creds {
+pub fn make_key_pair() -> impl Creds {
     ROOT_CREDS.clone()
 }
 
-pub(crate) fn make_self_signed_writecap() -> (Writecap, impl Creds) {
+pub fn make_self_signed_writecap() -> (Writecap, impl Creds) {
     let key = make_key_pair();
     (make_self_signed_writecap_with(&key), key)
 }
 
-pub(crate) fn make_self_signed_writecap_with<C: Creds>(key: &C) -> Writecap {
+pub fn make_self_signed_writecap_with<C: Creds>(key: &C) -> Writecap {
     let root_principal = key.principal();
     let hour_hence = Epoch::now() + Duration::from_secs(3600);
     let mut writecap = Writecap {
@@ -196,7 +196,7 @@ pub(crate) fn make_self_signed_writecap_with<C: Creds>(key: &C) -> Writecap {
     writecap
 }
 
-pub(crate) fn make_block_with<C: CredsPub>(creds: &C) -> Box<dyn Block> {
+pub fn make_block_with<C: CredsPub>(creds: &C) -> Box<dyn Block> {
     let block_key = SymKey::generate(SymKeyKind::default()).unwrap();
     let mut readcaps = BTreeMap::new();
     readcaps.insert(creds.principal(), creds.ser_encrypt(&block_key).unwrap());
@@ -409,7 +409,7 @@ pub struct BtCursor<T: FromVec> {
 }
 
 impl<T: FromVec> BtCursor<T> {
-    pub(crate) fn new(inner: T) -> BtCursor<T> {
+    pub fn new(inner: T) -> BtCursor<T> {
         BtCursor {
             cursor: RefCell::new(Cursor::new(inner)),
         }
@@ -727,12 +727,12 @@ impl DbusBlocker {
     }
 }
 
-pub(crate) struct SwtpmHarness {
+pub struct SwtpmHarness {
     dir: TempDir,
-    port: u16,
     state_path: PathBuf,
     pid_path: PathBuf,
     tabrmd: Child,
+    tabrmd_config: String,
 }
 
 impl SwtpmHarness {
@@ -748,7 +748,7 @@ impl SwtpmHarness {
         format!("com.intel.tss2.Tabrmd.{port_str}")
     }
 
-    pub(crate) fn new() -> crypto::Result<SwtpmHarness> {
+    pub fn new() -> crypto::Result<SwtpmHarness> {
         static PORT: AtomicU16 = AtomicU16::new(21901);
         let port = PORT.fetch_add(2, Ordering::SeqCst);
         let ctrl_port = port + 1;
@@ -811,24 +811,27 @@ active_pcr_banks = sha256
         blocker.block(Duration::from_secs(5))?;
         Ok(SwtpmHarness {
             dir,
-            port,
             state_path,
             pid_path,
             tabrmd,
+            tabrmd_config: format!("bus_name={},bus_type=session", Self::dbus_name(port)),
         })
     }
 
-    pub(crate) fn context(&self) -> crypto::Result<Context> {
-        let config_string = format!("bus_name={},bus_type=session", Self::dbus_name(self.port));
-        let config = TabrmdConfig::from_str(config_string.as_str())?;
+    pub fn tabrmd_config(&self) -> &str {
+        &self.tabrmd_config
+    }
+
+    pub fn context(&self) -> crypto::Result<Context> {
+        let config = TabrmdConfig::from_str(self.tabrmd_config())?;
         Ok(Context::new(TctiNameConf::Tabrmd(config))?)
     }
 
-    pub(crate) fn dir_path(&self) -> &std::path::Path {
+    pub fn dir_path(&self) -> &std::path::Path {
         self.dir.path()
     }
 
-    pub(crate) fn state_path(&self) -> &std::path::Path {
+    pub fn state_path(&self) -> &std::path::Path {
         &self.state_path
     }
 }

+ 1 - 0
crates/btlib/src/trailered.rs

@@ -184,6 +184,7 @@ mod private {
     }
 }
 
+#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use crate::Decompose;