123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- use btlib::{
- blocktree::{Blocktree, ModeAuthorizer},
- crypto::{
- tpm::{TpmCredStore, TpmCreds},
- CredStore,
- },
- };
- use fuse_backend_rs::{
- api::server::Server,
- transport::{Error, FuseSession},
- };
- 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,
- sync::mpsc::Sender,
- };
- 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>>(mnt_point: P) -> File {
- let mountpoint = CString::new(mnt_point.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,
- /// An optional [Sender] which is called when this daemon has finished starting up.
- started_signal: Option<Sender<()>>,
- }
- impl<'a> FuseDaemon<'a> {
- fn new(path: &'a Path, tabrmd_config: &'a str) -> FuseDaemon<'_> {
- FuseDaemon {
- path,
- tabrmd_config,
- started_signal: None,
- }
- }
- fn tpm_state_path<P: AsRef<Path>>(path: P) -> PathBuf {
- path.as_ref().join("tpm_state")
- }
- fn mnt_path<P: AsRef<Path>>(path: P) -> PathBuf {
- path.as_ref().join("mnt")
- }
- fn server<P: AsRef<Path>>(&self, mnt_path: P) -> Server<Blocktree<TpmCreds, ModeAuthorizer>> {
- 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::tpm_state_path(self.path))
- .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, ModeAuthorizer {})
- } else {
- Blocktree::new_existing(bt_path, node_creds, ModeAuthorizer {})
- }
- .expect("failed to create blocktree");
- Server::new(fs)
- }
- fn fuse_session<P: AsRef<Path>>(&self, mnt_path: P) -> FuseSession {
- self.path
- .try_create_dir()
- .expect("failed to create main directory");
- let mut session = FuseSession::new(mnt_path.as_ref(), FSNAME, FSTYPE, false)
- .expect("failed to create FUSE session");
- session.set_fuse_file(mount_at(mnt_path));
- session
- }
- fn start(&self) {
- let mnt_path = Self::mnt_path(self.path);
- mnt_path
- .try_create_dir()
- .expect("failed to create mount directory");
- let server = self.server(&mnt_path);
- let session = self.fuse_session(&mnt_path);
- drop(mnt_path);
- let mut channel = session
- .new_channel()
- .expect("failed to create FUSE channel");
- if let Some(tx) = self.started_signal.as_ref() {
- tx.send(()).expect("failed to send started signal");
- }
- 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 std::{
- ffi::{OsStr, OsString},
- fs::{
- create_dir, hard_link, metadata, read, read_dir, remove_dir, remove_file,
- set_permissions, write, Permissions, ReadDir,
- },
- os::unix::fs::PermissionsExt,
- sync::mpsc::{channel, Receiver},
- thread::JoinHandle,
- time::Duration,
- };
- use btlib::{crypto::Creds, log::BuilderExt, Epoch, Principaled};
- use swtpm_harness::SwtpmHarness;
- use tempdir::TempDir;
- use super::*;
- /// Unmounts the file system at the given path.
- fn unmount<P: AsRef<Path>>(mnt_path: P) {
- const PROG: &str = "fusermount";
- let mnt_path = mnt_path
- .as_ref()
- .as_os_str()
- .to_str()
- .expect("failed to convert mnt_path to `str`");
- let code = std::process::Command::new(PROG)
- .args(["-u", mnt_path])
- .status()
- .expect("waiting for exit status failed")
- .code()
- .expect("code returned None");
- if code != 0 {
- panic!("{PROG} exited with a non-zero status: {code}");
- }
- }
- fn file_names(read_dir: ReadDir) -> impl Iterator<Item = OsString> {
- read_dir.map(|entry| entry.unwrap().file_name())
- }
- struct TestCase {
- temp_path_rx: Receiver<PathBuf>,
- mnt_path: Option<PathBuf>,
- handle: Option<JoinHandle<()>>,
- }
- impl TestCase {
- const ROOT_PASSWD: &str = "Gnurlingwheel";
- fn new() -> TestCase {
- let (tx, rx) = channel();
- let (started_tx, started_rx) = channel();
- let handle = std::thread::spawn(move || {
- let dir = TempDir::new("btfuse").expect("failed to create TempDir");
- tx.send(dir.path().to_owned()).expect("send failed");
- let swtpm = SwtpmHarness::new().expect("failed to start swtpm");
- {
- let context = swtpm.context().expect("failed to create TPM context");
- let cred_store =
- TpmCredStore::new(context, FuseDaemon::tpm_state_path(dir.path()))
- .expect("failed to create TpmCredStore");
- let root_creds = cred_store
- .gen_root_creds(Self::ROOT_PASSWD)
- .expect("failed to gen root creds");
- let mut node_creds = cred_store.node_creds().expect("failed to get node creds");
- let expires = Epoch::now() + Duration::from_secs(3600);
- let writecap = root_creds
- .issue_writecap(node_creds.principal(), vec![], expires)
- .expect("failed to issue writecap to node creds");
- cred_store
- .assign_node_writecap(&mut node_creds, writecap)
- .expect("failed to assign writecap");
- }
- let mut daemon = FuseDaemon::new(dir.path(), swtpm.tabrmd_config());
- daemon.started_signal = Some(started_tx);
- daemon.start()
- });
- started_rx
- .recv_timeout(Duration::from_secs(1))
- .expect("failed to received started signal from `FuseDaemon`");
- println!("sent started signal");
- TestCase {
- temp_path_rx: rx,
- mnt_path: None,
- handle: Some(handle),
- }
- }
- fn mnt_path(&mut self) -> &PathBuf {
- self.mnt_path.get_or_insert_with(|| {
- let temp_path = self
- .temp_path_rx
- .recv_timeout(Duration::from_secs(1))
- .expect("receive failed");
- FuseDaemon::mnt_path(temp_path)
- })
- }
- fn wait(&mut self) {
- if let Some(handle) = self.handle.take() {
- handle.join().expect("join failed");
- }
- }
- fn unmount_and_wait(&mut self) {
- // If `handle` has already been taken that means `wait` has already been called. If
- // this thread called `wait` and subsequently became unblocked, we know that the FUSE
- // thread halted, which only happens when the file system is unmounted. Hence
- // we don't have to unmount it, nor wait for the FUSE thread.
- if self.handle.is_none() {
- return;
- }
- let mnt_path = self.mnt_path();
- unmount(mnt_path);
- self.wait();
- }
- }
- impl Drop for TestCase {
- fn drop(&mut self) {
- self.unmount_and_wait();
- }
- }
- /// Creates a new file system and mounts it at `/tmp/btfuse.<random>/mnt` so it can be
- /// tested manually.
- //#[test]
- #[allow(dead_code)]
- fn manual_test() {
- std::env::set_var("RUST_LOG", "debug");
- env_logger::Builder::from_default_env().btformat().init();
- let mut case = TestCase::new();
- case.wait();
- }
- /// Tests if the file system can be mount then unmounted successfully.
- #[test]
- fn mount_then_unmount() {
- let _ = TestCase::new();
- }
- #[test]
- fn write_read() {
- const EXPECTED: &[u8] =
- b"The paths to failure are uncountable, yet to success there is but one.";
- let mut case = TestCase::new();
- let file_path = case.mnt_path().join("file");
- write(&file_path, EXPECTED).expect("write failed");
- let actual = read(&file_path).expect("read failed");
- assert_eq!(EXPECTED, actual);
- }
- #[test]
- fn create_file_then_readdir() {
- const DATA: &[u8] = b"Au revoir Shoshanna!";
- let file_name = OsStr::new("landa_dialog.txt");
- let expected = [file_name];
- let mut case = TestCase::new();
- let mnt_path = case.mnt_path();
- let file_path = mnt_path.join(file_name);
- write(&file_path, DATA).expect("write failed");
- let first = file_names(read_dir(&mnt_path).expect("read_dir failed"));
- assert!(first.eq(expected));
- let second = file_names(read_dir(&mnt_path).expect("read_dir failed"));
- assert!(second.eq(expected));
- }
- #[test]
- fn create_then_delete_file() {
- const DATA: &[u8] = b"The universe is hostile, so impersonal. Devour to survive";
- let file_name = OsStr::new("tool_lyrics.txt");
- let mut case = TestCase::new();
- let mnt_path = case.mnt_path();
- let file_path = mnt_path.join(file_name);
- write(&file_path, DATA).expect("write failed");
- remove_file(&file_path).expect("remove_file failed");
- let expected: [&OsStr; 0] = [];
- assert!(file_names(read_dir(&mnt_path).expect("read_dir failed")).eq(expected))
- }
- #[test]
- fn hard_link_then_remove() {
- const EXPECTED: &[u8] = b"And the lives we've reclaimed";
- let name1 = OsStr::new("refugee_lyrics.txt");
- let name2 = OsStr::new("rise_against_lyrics.txt");
- let mut case = TestCase::new();
- let mnt_path = case.mnt_path();
- let path1 = mnt_path.join(name1);
- let path2 = mnt_path.join(name2);
- write(&path1, EXPECTED).expect("write failed");
- hard_link(&path1, &path2).expect("hard_link failed");
- remove_file(&path1).expect("remove_file failed");
- let actual = read(&path2).expect("read failed");
- assert_eq!(EXPECTED, actual);
- }
- #[test]
- fn hard_link_then_remove_both() {
- const EXPECTED: &[u8] = b"And the lives we've reclaimed";
- let name1 = OsStr::new("refugee_lyrics.txt");
- let name2 = OsStr::new("rise_against_lyrics.txt");
- let mut case = TestCase::new();
- let mnt_path = case.mnt_path();
- let path1 = mnt_path.join(name1);
- let path2 = mnt_path.join(name2);
- write(&path1, EXPECTED).expect("write failed");
- hard_link(&path1, &path2).expect("hard_link failed");
- remove_file(&path1).expect("remove_file on path1 failed");
- remove_file(&path2).expect("remove_file on path2 failed");
- let expected: [&OsStr; 0] = [];
- assert!(file_names(read_dir(&mnt_path).expect("read_dir failed")).eq(expected));
- }
- #[test]
- fn set_mode_bits() {
- const EXPECTED: u32 = libc::S_IFREG | 0o777;
- let mut case = TestCase::new();
- let file_path = case.mnt_path().join("bagobits");
- write(&file_path, []).expect("write failed");
- let original = metadata(&file_path)
- .expect("metadata failed")
- .permissions()
- .mode();
- assert_ne!(EXPECTED, original);
- set_permissions(&file_path, Permissions::from_mode(EXPECTED))
- .expect("set_permissions failed");
- let actual = metadata(&file_path)
- .expect("metadata failed")
- .permissions()
- .mode();
- assert_eq!(EXPECTED, actual);
- }
- #[test]
- fn create_directory() {
- const EXPECTED: &str = "etc";
- let mut case = TestCase::new();
- let mnt_path = case.mnt_path();
- let dir_path = mnt_path.join(EXPECTED);
- create_dir(&dir_path).expect("create_dir failed");
- let actual = file_names(read_dir(mnt_path).expect("read_dir failed"));
- assert!(actual.eq([EXPECTED]));
- }
- #[test]
- fn create_file_under_new_directory() {
- const DIR_NAME: &str = "etc";
- const FILE_NAME: &str = "file";
- let mut case = TestCase::new();
- let mnt_path = case.mnt_path();
- let dir_path = mnt_path.join(DIR_NAME);
- let file_path = dir_path.join(FILE_NAME);
- create_dir(&dir_path).expect("create_dir failed");
- write(&file_path, []).expect("write failed");
- let actual = file_names(read_dir(dir_path).expect("read_dir failed"));
- assert!(actual.eq([FILE_NAME]));
- }
- #[test]
- fn create_then_remove_directory() {
- const DIR_NAME: &str = "etc";
- let mut case = TestCase::new();
- let mnt_path = case.mnt_path();
- let dir_path = mnt_path.join(DIR_NAME);
- create_dir(&dir_path).expect("create_dir failed");
- remove_dir(&dir_path).expect("remove_dir failed");
- let actual = file_names(read_dir(&mnt_path).expect("read_dir failed"));
- const EMPTY: [&str; 0] = [""; 0];
- assert!(actual.eq(EMPTY));
- }
- #[test]
- fn read_only_dir_cant_create_subdir() {
- const DIR_NAME: &str = "etc";
- let mut case = TestCase::new();
- let dir_path = case.mnt_path().join(DIR_NAME);
- create_dir(&dir_path).expect("create_dir failed");
- set_permissions(&dir_path, Permissions::from_mode(libc::S_IFDIR | 0o444))
- .expect("set_permissions failed");
- let result = create_dir(dir_path.join("sub"));
- let err = result.err().expect("create_dir returned `Ok`");
- let os_err = err.raw_os_error().expect("raw_os_error was empty");
- assert_eq!(os_err, libc::EACCES);
- }
- #[test]
- fn read_only_dir_cant_remove_subdir() {
- const DIR_NAME: &str = "etc";
- let mut case = TestCase::new();
- let dir_path = case.mnt_path().join(DIR_NAME);
- let sub_path = dir_path.join("sub");
- create_dir(&dir_path).expect("create_dir failed");
- create_dir(&sub_path).expect("create_dir failed");
- set_permissions(&dir_path, Permissions::from_mode(libc::S_IFDIR | 0o444))
- .expect("set_permissions failed");
- let result = remove_dir(&sub_path);
- let err = result.err().expect("remove_dir returned `Ok`");
- let os_err = err.raw_os_error().expect("raw_os_error was empty");
- assert_eq!(os_err, libc::EACCES);
- }
- }
|