[RFC PATCH 3/3] WIP: rust: tarfs: use untrusted data API

From: Benno Lossin
Date: Fri Sep 13 2024 - 07:27:55 EST


As an example here is how tarfs could use the untrusted data API. There
are two calls to `untrusted()` outside of `Validator` implementations. I
don't know if the names are used for some logic at a later point, and I
didn't want to mark the name as untrusted, because then I would have to
change more of the vfs API. I think if the names are not used for any
logic, then they can just stay as `Untrusted<CString>`.
---
fs/tarfs/defs.rs | 1 +
fs/tarfs/tar.rs | 143 ++++++++++++++++++++++++++++++++---------------
2 files changed, 99 insertions(+), 45 deletions(-)

diff --git a/fs/tarfs/defs.rs b/fs/tarfs/defs.rs
index 7481b75aaab2..db22e3299fef 100644
--- a/fs/tarfs/defs.rs
+++ b/fs/tarfs/defs.rs
@@ -70,6 +70,7 @@ pub struct DirEntry {

/// The super-block of a tarfs instance.
#[repr(C)]
+ #[derive(Clone)]
pub struct Header {
/// The offset to the beginning of the inode-table.
pub inode_table_offset: LE<u64>,
diff --git a/fs/tarfs/tar.rs b/fs/tarfs/tar.rs
index a3f6e468e566..8ca255306c8b 100644
--- a/fs/tarfs/tar.rs
+++ b/fs/tarfs/tar.rs
@@ -9,7 +9,14 @@
inode::Type, iomap, sb, sb::SuperBlock, Offset, Stat,
};
use kernel::types::{ARef, Either, FromBytes, Locked};
-use kernel::{c_str, prelude::*, str::CString, user};
+use kernel::{
+ c_str,
+ prelude::*,
+ str::CString,
+ types::{Untrusted, Validator},
+ user,
+};
+use std::fs::DirEntry;

pub mod defs;

@@ -29,7 +36,8 @@

static_assert!(SECTORS_PER_BLOCK > 0);

-struct INodeData {
+#[allow(missing_docs)]
+pub struct INodeData {
offset: u64,
flags: u8,
}
@@ -41,26 +49,13 @@ struct TarFs {
mapper: inode::Mapper,
}

-impl TarFs {
- fn iget(sb: &SuperBlock<Self>, ino: u64) -> Result<ARef<INode<Self>>> {
- // Check that the inode number is valid.
- let h = sb.data();
- if ino == 0 || ino > h.inode_count {
- return Err(ENOENT);
- }
+impl Validator for Inode {
+ type Input = [u8];
+ type Output = inode::Params<INodeData>;
+ type Err = Error;

- // Create an inode or find an existing (cached) one.
- let mut inode = match sb.get_or_create_inode(ino)? {
- Either::Left(existing) => return Ok(existing),
- Either::Right(new) => new,
- };
-
- static_assert!((TARFS_BSIZE as usize) % size_of::<Inode>() == 0);
-
- // Load inode details from storage.
- let offset = h.inode_table_offset + (ino - 1) * u64::try_from(size_of::<Inode>())?;
- let b = h.mapper.mapped_folio(offset.try_into()?)?;
- let idata = Inode::from_bytes(&b, 0).ok_or(EIO)?;
+ fn validate(untrusted: &Untrusted<Self::Input>) -> Result<Self::Output, Self::Err> {
+ let idata = Inode::from_bytes(untrusted.untrusted(), 0).ok_or(EIO)?;

let mode = idata.mode.value();

@@ -69,36 +64,21 @@ fn iget(sb: &SuperBlock<Self>, ino: u64) -> Result<ARef<INode<Self>>> {
return Err(ENOENT);
}

- const DIR_FOPS: file::Ops<TarFs> = file::Ops::new::<TarFs>();
- const DIR_IOPS: inode::Ops<TarFs> = inode::Ops::new::<TarFs>();
- const FILE_AOPS: address_space::Ops<TarFs> = iomap::ro_aops::<TarFs>();
-
let size = idata.size.value();
let doffset = idata.offset.value();
let secs = u64::from(idata.lmtime.value()) | (u64::from(idata.hmtime & 0xf) << 32);
let ts = kernel::time::Timespec::new(secs, 0)?;
let typ = match mode & fs::mode::S_IFMT {
- fs::mode::S_IFREG => {
- inode
- .set_fops(file::Ops::generic_ro_file())
- .set_aops(FILE_AOPS);
- Type::Reg
- }
- fs::mode::S_IFDIR => {
- inode.set_iops(DIR_IOPS).set_fops(DIR_FOPS);
- Type::Dir
- }
- fs::mode::S_IFLNK => {
- inode.set_iops(inode::Ops::simple_symlink_inode());
- Type::Lnk(Some(Self::get_link(sb, doffset, size)?))
- }
+ fs::mode::S_IFREG => Type::Reg,
+ fs::mode::S_IFDIR => Type::Dir,
+ fs::mode::S_IFLNK => Type::Lnk(None),
fs::mode::S_IFSOCK => Type::Sock,
fs::mode::S_IFIFO => Type::Fifo,
fs::mode::S_IFCHR => Type::Chr((doffset >> 32) as u32, doffset as u32),
fs::mode::S_IFBLK => Type::Blk((doffset >> 32) as u32, doffset as u32),
_ => return Err(ENOENT),
};
- inode.init(inode::Params {
+ Ok(inode::Params {
typ,
mode: mode & 0o777,
size: size.try_into()?,
@@ -115,13 +95,86 @@ fn iget(sb: &SuperBlock<Self>, ino: u64) -> Result<ARef<INode<Self>>> {
},
})
}
+}
+
+impl Validator for Header {
+ type Input = [u8];
+ type Output = Self;
+ type Err = Error;
+
+ fn validate(
+ untrusted: &Untrusted<Self::Input>,
+ ) -> core::result::Result<Self::Output, Self::Err> {
+ Header::from_bytes(untrusted.untrusted(), 0)
+ .ok_or(EIO)
+ .cloned()
+ }
+}
+
+impl Validator for DirEntry {
+ type Input = [u8];
+ type Output = Self;
+ type Err = Error;
+
+ fn validate(
+ untrusted: &Untrusted<Self::Input>,
+ ) -> core::result::Result<Self::Output, Self::Err> {
+ let raw = DirEntry::from_bytes_to_slice(untrusted.untrusted()).ok_or(EIO)?;
+ // TODO: actually verify that the `raw` entry is correct.
+ Ok(raw)
+ }
+}
+
+impl TarFs {
+ fn iget(sb: &SuperBlock<Self>, ino: u64) -> Result<ARef<INode<Self>>> {
+ // Check that the inode number is valid.
+ let h = sb.data();
+ if ino == 0 || ino > h.inode_count {
+ return Err(ENOENT);
+ }
+
+ // Create an inode or find an existing (cached) one.
+ let mut inode = match sb.get_or_create_inode(ino)? {
+ Either::Left(existing) => return Ok(existing),
+ Either::Right(new) => new,
+ };
+
+ static_assert!((TARFS_BSIZE as usize) % size_of::<Inode>() == 0);
+
+ // Load inode details from storage.
+ let offset = h.inode_table_offset + (ino - 1) * u64::try_from(size_of::<Inode>())?;
+ let b = h.mapper.mapped_folio(offset.try_into()?)?;
+
+ const DIR_FOPS: file::Ops<TarFs> = file::Ops::new::<TarFs>();
+ const DIR_IOPS: inode::Ops<TarFs> = inode::Ops::new::<TarFs>();
+ const FILE_AOPS: address_space::Ops<TarFs> = iomap::ro_aops::<TarFs>();
+
+ let mut params = b.deref().validate::<Inode>()?;
+ match &params.typ {
+ Type::Reg => {
+ inode
+ .set_fops(file::Ops::generic_ro_file())
+ .set_aops(FILE_AOPS);
+ }
+ Type::Dir => {
+ inode.set_iops(DIR_IOPS).set_fops(DIR_FOPS);
+ }
+ Type::Lnk(_) => {
+ inode.set_iops(inode::Ops::simple_symlink_inode());
+ let doffset = params.value.offset;
+ params.typ = Type::Lnk(Some(Self::get_link(sb, doffset, params.size as u64)?));
+ }
+ _ => {}
+ }
+ inode.init(params)
+ }

fn name_eq(sb: &SuperBlock<Self>, mut name: &[u8], offset: u64) -> Result<bool> {
let ret =
sb.data()
.mapper
.for_each_page(offset as Offset, name.len().try_into()?, |data| {
- if data != &name[..data.len()] {
+ if data.untrusted() != &name[..data.len()] {
return Ok(Some(()));
}
name = &name[data.len()..];
@@ -135,7 +188,7 @@ fn read_name(sb: &SuperBlock<Self>, name: &mut [u8], offset: u64) -> Result {
sb.data()
.mapper
.for_each_page(offset as Offset, name.len().try_into()?, |data| {
- name[copy_to..][..data.len()].copy_from_slice(data);
+ name[copy_to..][..data.len()].copy_from_slice(data.untrusted());
copy_to += data.len();
Ok(None::<()>)
})?;
@@ -179,7 +232,7 @@ fn fill_super(
let tarfs = {
let offset = (scount - 1) * SECTOR_SIZE;
let mapped = mapper.mapped_folio(offset.try_into()?)?;
- let hdr = Header::from_bytes(&mapped, 0).ok_or(EIO)?;
+ let hdr = mapped.deref().validate::<Header>()?;
let inode_table_offset = hdr.inode_table_offset.value();
let inode_count = hdr.inode_count.value();
drop(mapped);
@@ -316,7 +369,7 @@ fn lookup(
parent.data().offset.try_into()?,
parent.size(),
|data| {
- for e in DirEntry::from_bytes_to_slice(data).ok_or(EIO)? {
+ for e in data.validate::<DirEntry>()? {
if Self::name_eq(sb, name, e.name_offset.value())? {
return Ok(Some(Self::iget(sb, e.ino.value())?));
}
@@ -368,7 +421,7 @@ fn read_dir(
inode.data().offset as i64 + pos,
inode.size() - pos,
|data| {
- for e in DirEntry::from_bytes_to_slice(data).ok_or(EIO)? {
+ for e in data.validate::<DirEntry>()? {
let name_len = usize::try_from(e.name_len.value())?;
if name_len > name.len() {
name.resize(name_len, 0, GFP_NOFS)?;
--
2.46.0