Browse Source

Improved the security of inode to path mapping.

Matthew Carr 2 năm trước cách đây
mục cha
commit
3770d845f4

+ 1 - 0
Cargo.lock

@@ -190,6 +190,7 @@ dependencies = [
  "positioned-io",
  "serde",
  "tokio",
+ "zeroize",
 ]
 
 [[package]]

+ 3 - 0
crates/btfproto-tests/src/local_fs_tests.rs

@@ -1041,6 +1041,9 @@ mod tests {
         path.push("x");
         for entry in entries {
             let entry = entry.unwrap();
+            if !entry.file_type().unwrap().is_dir() {
+                continue;
+            }
             path.pop();
             path.push(entry.file_name());
             let empty = read_dir(&path).unwrap().next().is_none();

+ 1 - 0
crates/btfproto/Cargo.toml

@@ -24,3 +24,4 @@ positioned-io = { version = "0.3.1", optional = true }
 fuse-backend-rs = { version = "0.9.6", optional = true }
 btserde = { path = "../btserde", optional = true }
 bytes = { version = "1.3.0", optional = true }
+zeroize = { version = "1.5.7" }

+ 72 - 71
crates/btfproto/src/local_fs.rs

@@ -4,7 +4,7 @@ use crate::{msg::*, server::FsProvider};
 use btlib::{
     accessor::Accessor,
     bterr,
-    crypto::{Creds, Decrypter, Signer, SymKey},
+    crypto::{rand_vec, Creds, Decrypter, HashKind, Signer, SymKey},
     error::BtErr,
     AuthzAttrs, BlockAccessor, BlockError, BlockMeta, BlockMetaSecrets, BlockOpenOptions,
     BlockPath, BlockReader, DirEntry, Directory, Epoch, FileBlock, FlushMeta, IssuedProcRec,
@@ -35,11 +35,11 @@ use tokio::sync::{
     Mutex, MutexGuard, OwnedMutexGuard, OwnedRwLockReadGuard, RwLock, RwLockReadGuard,
     RwLockWriteGuard,
 };
+use zeroize::ZeroizeOnDrop;
 
 pub use private::{Authorizer, AuthzContext, Error, LocalFs, ModeAuthorizer};
 
 mod private {
-    use btlib::crypto::HashKind;
 
     use super::*;
 
@@ -579,12 +579,18 @@ mod private {
     }
 
     /// Structure for metadata about a blocktree.
-    #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+    #[derive(Debug, Serialize, Deserialize, ZeroizeOnDrop)]
     struct Superblock {
         /// The generation number of the cluster this part of the blocktree is stored on.
         generation: u64,
         /// The next free inode available to the cluster.
-        next_inode: u64,
+        #[zeroize(skip)]
+        next_inode: AtomicU64,
+        /// The hash algorithm to use when computing inode paths.
+        #[zeroize(skip)]
+        inode_hash: HashKind,
+        /// The key to use when hashing inodes to file paths.
+        inode_key: Vec<u8>,
     }
 
     /// Structure for managing the part of a blocktree which is stored in the local filesystem.
@@ -593,11 +599,8 @@ mod private {
         path: PathBuf,
         /// A map from inode numbers to their reference counts.
         inodes: Arc<RwLock<InodeTable<C>>>,
-        /// The next inode that will be assigned to a new block.
-        next_inode: AtomicU64,
-        /// The generation number of this filesystem. This is the same for every other server in
-        /// the same cluster.
-        generation: u64,
+        /// An in-memory copy of the superblock.
+        sb: Superblock,
         /// The credentials this blocktree instance will use for all cryptographic operations.
         creds: C,
         authorizer: A,
@@ -623,17 +626,13 @@ mod private {
             let root_principal = writecap.root_principal();
 
             // Initialize the superblock.
-            let mut sb_block = Self::open_block(
-                &btdir,
-                SpecInodes::Sb.into(),
-                creds.clone(),
-                root_block_path.clone(),
-                None,
-                &root_principal,
-            )?;
+            let mut sb_block =
+                Self::open_superblock(&btdir, creds.clone(), root_block_path.clone())?;
             let sb = Superblock {
                 generation,
-                next_inode: SpecInodes::FirstFree.into(),
+                next_inode: AtomicU64::new(SpecInodes::FirstFree.into()),
+                inode_hash: HashKind::default(),
+                inode_key: rand_vec(HashKind::default().len())?,
             };
             write_to(&sb, &mut sb_block)?;
             sb_block.mut_meta_body().access_secrets(|secrets| {
@@ -654,7 +653,8 @@ mod private {
                 creds.clone(),
                 root_block_path.clone(),
                 None,
-                &root_principal,
+                sb.inode_hash,
+                &sb.inode_key,
             )?;
             write_to(&Directory::new(), &mut root_block)?;
             root_block.mut_meta_body().access_secrets(|secrets| {
@@ -705,22 +705,17 @@ mod private {
             let writecap = creds.writecap().ok_or(BlockError::MissingWritecap)?;
             let root_block_path = writecap.root_block_path();
             let root_principal = writecap.root_principal();
-            let mut sb_block = Self::open_block(
-                &btdir,
-                SpecInodes::Sb.into(),
-                creds.clone(),
-                root_block_path.to_owned(),
-                None,
-                &root_principal,
-            )?;
-            let sb = read_from(&mut sb_block)?;
+            let mut sb_block =
+                Self::open_superblock(&btdir, creds.clone(), root_block_path.clone())?;
+            let sb: Superblock = read_from(&mut sb_block)?;
             let root_block = Self::open_block(
                 &btdir,
                 SpecInodes::RootDir.into(),
                 creds.clone(),
                 root_block_path,
                 None,
-                &root_principal,
+                sb.inode_hash,
+                &sb.inode_key,
             )?;
             Self::new(
                 btdir,
@@ -758,45 +753,55 @@ mod private {
             Ok(LocalFs {
                 path: btdir,
                 inodes: Arc::new(RwLock::new(inodes)),
-                next_inode: AtomicU64::new(sb.next_inode),
-                generation: sb.generation,
+                sb,
                 creds,
                 authorizer,
                 root_principal,
             })
         }
 
-        fn hex_encode<'a>(dest: &'a mut [u8], src: &[u8]) -> Result<&'a str> {
-            if dest.len() < 2 * src.len() {
-                return Err(bterr!(
-                    "destination slice must be at least twice source slice length"
-                ));
-            }
-            for (index, byte) in src.iter().enumerate() {
-                let upper = (byte & 0xF0) >> 4;
-                let lower = byte & 0x0F;
-                let first = char::from_digit(upper as u32, 16).unwrap();
-                let second = char::from_digit(lower as u32, 16).unwrap();
-                first.encode_utf8(&mut dest[2 * index..]);
-                second.encode_utf8(&mut dest[2 * index + 1..]);
+        fn open_superblock<P: AsRef<Path>>(
+            btdir: P,
+            creds: C,
+            block_path: BlockPath,
+        ) -> Result<Accessor<FileBlock<C>>> {
+            let path = btdir.as_ref().join("super.blk");
+            let file = std::fs::OpenOptions::new()
+                .read(true)
+                .write(true)
+                .create(true)
+                .open(path)?;
+            let block = BlockOpenOptions::new()
+                .with_creds(creds)
+                .with_encrypt(true)
+                .with_inner(file)
+                .with_block_path(block_path)
+                .open()?;
+            Ok(block)
+        }
+
+        fn hex_encode(src: &[u8]) -> Result<String> {
+            use std::fmt::Write;
+            let mut string = String::with_capacity(2 * src.len());
+            for byte in src.iter() {
+                write!(string, "{byte:02x}")?;
             }
-            std::str::from_utf8(&dest[..2 * src.len()]).map_err(|err| err.into())
+            Ok(string)
         }
 
         /// Returns the path to the file storing the given inode's data.
         fn block_path<P: AsRef<Path>>(
             parent: P,
             inode: Inode,
-            root_principal: &Principal,
+            inode_hash: HashKind,
+            inode_key: &[u8],
         ) -> Result<PathBuf> {
-            const HASH: HashKind = HashKind::Sha2_256;
-            let mut buf = [0u8; HASH.len()];
-            HASH.digest(
+            let mut buf = vec![0u8; inode_hash.len()];
+            inode_hash.digest(
                 &mut buf,
-                [root_principal.as_slice(), inode.to_le_bytes().as_slice()].into_iter(),
+                [inode.to_le_bytes().as_slice(), inode_key].into_iter(),
             )?;
-            let mut hex_buf = [0u8; 2 * HASH.len()];
-            let hex_str = Self::hex_encode(&mut hex_buf, &buf)?;
+            let hex_str = Self::hex_encode(&buf)?;
             let mut path =
                 PathBuf::with_capacity(parent.as_ref().as_os_str().len() + 1 + hex_str.len() + 1);
             path.push(parent);
@@ -811,9 +816,10 @@ mod private {
             creds: C,
             block_path: BlockPath,
             parent_key: Option<SymKey>,
-            root_principal: &Principal,
+            inode_hash: HashKind,
+            inode_key: &[u8],
         ) -> Result<Accessor<FileBlock<C>>> {
-            let path = Self::block_path(&btdir, inode, root_principal)?;
+            let path = Self::block_path(&btdir, inode, inode_hash, inode_key)?;
             let dir = path.ancestors().nth(1).unwrap();
             if let Err(err) = std::fs::create_dir(dir) {
                 match err.kind() {
@@ -862,7 +868,8 @@ mod private {
                 self.creds.clone(),
                 block_path,
                 parent_key,
-                &self.root_principal,
+                self.sb.inode_hash,
+                &self.sb.inode_key,
             )?;
             let value = Arc::new(RwLock::new(InodeTableValue::new(block, from)));
             let mut inodes = self.inodes.write().await;
@@ -899,7 +906,8 @@ mod private {
         }
 
         fn delete_block_file(&self, inode: Inode) -> Result<()> {
-            let mut path = Self::block_path(&self.path, inode, &self.root_principal)?;
+            let mut path =
+                Self::block_path(&self.path, inode, self.sb.inode_hash, &self.sb.inode_key)?;
             std::fs::remove_file(&path)?;
             path.pop();
             let mut contents = std::fs::read_dir(&path)?;
@@ -948,13 +956,9 @@ mod private {
             let mut block = &mut value_guard.block;
             // We don't need strict ordering because the lock on the inode table value is already
             // serializing access.
-            let inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
-            let sb = Superblock {
-                generation: self.generation,
-                next_inode: inode + 1,
-            };
+            let inode = self.sb.next_inode.fetch_add(1, Ordering::Relaxed);
             block.rewind()?;
-            write_to(&sb, &mut block)?;
+            write_to(&self.sb, &mut block)?;
             block.flush()?;
             Ok(inode)
         }
@@ -1000,13 +1004,9 @@ mod private {
                     // superblock.
                     let mut value_guard = table_guard.write(SpecInodes::Sb.into()).await?;
                     let mut block = &mut value_guard.block;
-                    let next_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
-                    let sb = Superblock {
-                        generation: self.generation,
-                        next_inode: next_inode + 1,
-                    };
+                    let next_inode = self.sb.next_inode.fetch_add(1, Ordering::Relaxed);
                     block.rewind()?;
-                    write_to(&sb, &mut block)?;
+                    write_to(&self.sb, &mut block)?;
                     block
                         .mut_meta_body()
                         .add_readcap_for(principal.clone(), &proc_rec.pub_creds.enc)?;
@@ -1259,7 +1259,7 @@ mod private {
                 let entry = self.bt_entry(stat);
                 let reply = LookupReply {
                     inode,
-                    generation: self.generation,
+                    generation: self.sb.generation,
                     entry,
                 };
                 Ok(reply)
@@ -1321,7 +1321,7 @@ mod private {
                     let stat = {
                         let mut block = value_guard.handle_guard_mut(from, handle).await?;
                         let stat = block.mut_meta_body().access_secrets(|secrets| {
-                            secrets.block_id.generation = self.generation;
+                            secrets.block_id.generation = self.sb.generation;
                             secrets.block_id.inode = inode;
                             secrets.mode = mode & !umask;
                             if flags.directory() {
@@ -1637,7 +1637,8 @@ mod private {
                         self.creds.clone(),
                         block_path,
                         Some(parent_key),
-                        &self.root_principal,
+                        self.sb.inode_hash,
+                        &self.sb.inode_key,
                     )?;
                     let nlink = block.mut_meta_body().access_secrets(decr_nlink)?;
                     if nlink > 0 {