Skip to content

Commit 34a1489

Browse files
committed
refactor(fs): refactor path traversal handling
1 parent 3cd03d6 commit 34a1489

File tree

4 files changed

+400
-459
lines changed

4 files changed

+400
-459
lines changed

src/fs/fuse.rs

Lines changed: 119 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ use crate::fs::{
2929
self, AccessPermission, DirectoryEntry, FileAttr, NodeKind, ObjectInterface, OpenOption,
3030
SeekWhence, VfsNode,
3131
};
32+
use crate::io;
3233
use crate::mm::device_alloc::DeviceAlloc;
3334
use crate::syscalls::Dirent64;
3435
use crate::time::{time_t, timespec};
35-
use crate::{arch, io};
3636

3737
// response out layout eg @ https://github.com/zargony/fuse-rs/blob/bf6d1cf03f3277e35b580f3c7b9999255d72ecf3/src/ll/request.rs#L44
3838
// op in/out sizes/layout: https://github.com/hanwen/go-fuse/blob/204b45dba899dfa147235c255908236d5fde2d32/fuse/opcode.go#L439
@@ -1093,61 +1093,99 @@ impl ObjectInterface for FuseDirectoryHandle {
10931093
}
10941094
}
10951095

1096-
#[derive(Debug)]
1096+
#[derive(Clone, Debug)]
1097+
// The `original_prefix` is the one the directory was originally initialized with,
1098+
// `prefix` is the current prefix (normally, `prefix` has `original_prefix` as its prefix).
1099+
// This distinction is used for symlink resolution and directory traversal.
10971100
pub(crate) struct FuseDirectory {
1098-
prefix: Option<String>,
1099-
attr: FileAttr,
1101+
original_prefix: Arc<str>,
1102+
prefix: String,
11001103
}
11011104

11021105
impl FuseDirectory {
11031106
pub fn new(prefix: Option<String>) -> Self {
1104-
let microseconds = arch::kernel::systemtime::now_micros();
1105-
let t = timespec::from_usec(microseconds as i64);
1106-
11071107
FuseDirectory {
1108-
prefix,
1109-
attr: FileAttr {
1110-
st_mode: AccessPermission::from_bits(0o777).unwrap() | AccessPermission::S_IFDIR,
1111-
st_atim: t,
1112-
st_mtim: t,
1113-
st_ctim: t,
1114-
..Default::default()
1115-
},
1108+
original_prefix: Arc::from(prefix.as_deref().unwrap_or_default()),
1109+
prefix: prefix.unwrap_or_default(),
11161110
}
11171111
}
11181112

1119-
fn traversal_path(&self, components: &[&str]) -> CString {
1120-
let prefix_deref = self.prefix.as_deref();
1121-
let components_with_prefix = prefix_deref.iter().chain(components.iter().rev());
1122-
let path: String = components_with_prefix
1123-
.flat_map(|component| ["/", component])
1124-
.collect();
1125-
if path.is_empty() {
1126-
CString::new("/").unwrap()
1127-
} else {
1128-
CString::new(path).unwrap()
1113+
fn traversal_this(&self) -> CString {
1114+
let mut path = String::new();
1115+
path.push('/');
1116+
path.push_str(&self.prefix);
1117+
CString::new(path).unwrap()
1118+
}
1119+
1120+
fn traversal_path(&self, component: &str) -> CString {
1121+
let mut path = String::new();
1122+
if !self.prefix.is_empty() {
1123+
path.push('/');
1124+
path.push_str(&self.prefix);
11291125
}
1126+
path.push('/');
1127+
path.push_str(component);
1128+
CString::new(path).unwrap()
11301129
}
11311130
}
11321131

1132+
#[async_trait]
11331133
impl VfsNode for FuseDirectory {
11341134
/// Returns the node type
11351135
fn get_kind(&self) -> NodeKind {
11361136
NodeKind::Directory
11371137
}
11381138

1139-
fn get_file_attributes(&self) -> io::Result<FileAttr> {
1140-
Ok(self.attr)
1141-
}
1142-
11431139
fn get_object(&self) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
11441140
Ok(Arc::new(async_lock::RwLock::new(FuseDirectoryHandle::new(
1145-
self.prefix.clone(),
1141+
if self.prefix.is_empty() {
1142+
None
1143+
} else {
1144+
Some(self.prefix.clone())
1145+
},
11461146
))))
11471147
}
11481148

1149-
fn traverse_readdir(&self, components: &mut Vec<&str>) -> io::Result<Vec<DirectoryEntry>> {
1150-
let path = self.traversal_path(components);
1149+
fn dup(&self) -> Box<dyn VfsNode> {
1150+
Box::new(self.clone())
1151+
}
1152+
1153+
async fn traverse_once(&self, component: &str) -> io::Result<Box<dyn VfsNode>> {
1154+
let mut prefix = self.prefix.clone();
1155+
if !prefix.is_empty() {
1156+
prefix.push('/');
1157+
}
1158+
prefix.push_str(component);
1159+
1160+
Ok(Box::new(Self {
1161+
original_prefix: Arc::clone(&self.original_prefix),
1162+
prefix,
1163+
}))
1164+
}
1165+
1166+
async fn traverse_multiple(&self, mut path: &str) -> io::Result<Box<dyn VfsNode>> {
1167+
let mut prefix = self.prefix.clone();
1168+
// this part prevents inserting double-slashes or no slashes between prefix and path
1169+
if !path.is_empty() {
1170+
if let Some(x) = path.strip_prefix("/") {
1171+
path = x;
1172+
} else {
1173+
return Err(Errno::Nosys);
1174+
}
1175+
if !prefix.is_empty() {
1176+
prefix.push('/');
1177+
}
1178+
}
1179+
prefix.push_str(path);
1180+
1181+
Ok(Box::new(Self {
1182+
original_prefix: Arc::clone(&self.original_prefix),
1183+
prefix,
1184+
}))
1185+
}
1186+
1187+
async fn readdir(&self) -> io::Result<Vec<DirectoryEntry>> {
1188+
let path = self.traversal_this();
11511189

11521190
debug!("FUSE opendir: {path:#?}");
11531191

@@ -1226,36 +1264,55 @@ impl VfsNode for FuseDirectory {
12261264
Ok(entries)
12271265
}
12281266

1229-
fn traverse_stat(&self, components: &mut Vec<&str>) -> io::Result<FileAttr> {
1230-
let path = self.traversal_path(components);
1267+
async fn stat(&self) -> io::Result<FileAttr> {
1268+
let mut prefix = alloc::borrow::Cow::Borrowed(&*self.prefix);
12311269

1232-
debug!("FUSE stat: {path:#?}");
1270+
// handle infinite recursion
1271+
let mut encountered = alloc::collections::BTreeSet::<String>::new();
12331272

1234-
// Is there a better way to implement this?
1235-
let (cmd, rsp_payload_len) = ops::Lookup::create(path);
1236-
let rsp = get_filesystem_driver()
1237-
.unwrap()
1238-
.lock()
1239-
.send_command(cmd, rsp_payload_len)?;
1273+
while encountered.insert((*prefix).to_owned()) {
1274+
let mut path = String::new();
1275+
path.push('/');
1276+
path.push_str(&prefix);
1277+
let path = CString::new(path).unwrap();
12401278

1241-
if rsp.headers.out_header.error != 0 {
1242-
return Err(Errno::try_from(-rsp.headers.out_header.error).unwrap());
1243-
}
1279+
debug!("FUSE stat: {path:#?}");
12441280

1245-
let entry_out = rsp.headers.op_header;
1246-
let attr = entry_out.attr;
1281+
// Is there a better way to implement this?
1282+
let (cmd, rsp_payload_len) = ops::Lookup::create(path);
1283+
let rsp = get_filesystem_driver()
1284+
.unwrap()
1285+
.lock()
1286+
.send_command(cmd, rsp_payload_len)?;
1287+
1288+
if rsp.headers.out_header.error != 0 {
1289+
return Err(Errno::try_from(-rsp.headers.out_header.error).unwrap());
1290+
}
1291+
1292+
let entry_out = rsp.headers.op_header;
1293+
let attr = entry_out.attr;
1294+
1295+
if attr.mode & S_IFMT != S_IFLNK {
1296+
return Ok(FileAttr::from(attr));
1297+
}
1298+
1299+
let path = readlink(entry_out.nodeid)?;
1300+
1301+
// rewind and re-traverse
1302+
let mut new_prefix: String = (*self.original_prefix).to_owned();
1303+
if !path.starts_with('/') {
1304+
new_prefix.push('/');
1305+
}
12471306

1248-
if attr.mode & S_IFMT != S_IFLNK {
1249-
return Ok(FileAttr::from(attr));
1307+
new_prefix.push_str(&path);
1308+
prefix = alloc::borrow::Cow::Owned(new_prefix);
12501309
}
12511310

1252-
let path = readlink(entry_out.nodeid)?;
1253-
let mut components: Vec<&str> = path.split('/').collect();
1254-
self.traverse_stat(&mut components)
1311+
Err(Errno::Loop)
12551312
}
12561313

1257-
fn traverse_lstat(&self, components: &mut Vec<&str>) -> io::Result<FileAttr> {
1258-
let path = self.traversal_path(components);
1314+
async fn lstat(&self) -> io::Result<FileAttr> {
1315+
let path = self.traversal_this();
12591316

12601317
debug!("FUSE lstat: {path:#?}");
12611318

@@ -1267,13 +1324,13 @@ impl VfsNode for FuseDirectory {
12671324
Ok(FileAttr::from(rsp.headers.op_header.attr))
12681325
}
12691326

1270-
fn traverse_open(
1327+
async fn open(
12711328
&self,
1272-
components: &mut Vec<&str>,
1329+
component: &str,
12731330
opt: OpenOption,
12741331
mode: AccessPermission,
12751332
) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
1276-
let path = self.traversal_path(components);
1333+
let path = self.traversal_path(component);
12771334

12781335
debug!("FUSE open: {path:#?}, {opt:?} {mode:?}");
12791336

@@ -1345,8 +1402,8 @@ impl VfsNode for FuseDirectory {
13451402
}
13461403
}
13471404

1348-
fn traverse_unlink(&self, components: &mut Vec<&str>) -> io::Result<()> {
1349-
let path = self.traversal_path(components);
1405+
async fn unlink(&self, component: &str) -> io::Result<()> {
1406+
let path = self.traversal_path(component);
13501407

13511408
let (cmd, rsp_payload_len) = ops::Unlink::create(path);
13521409
let rsp = get_filesystem_driver()
@@ -1358,8 +1415,8 @@ impl VfsNode for FuseDirectory {
13581415
Ok(())
13591416
}
13601417

1361-
fn traverse_rmdir(&self, components: &mut Vec<&str>) -> io::Result<()> {
1362-
let path = self.traversal_path(components);
1418+
async fn rmdir(&self, component: &str) -> io::Result<()> {
1419+
let path = self.traversal_path(component);
13631420

13641421
let (cmd, rsp_payload_len) = ops::Rmdir::create(path);
13651422
let rsp = get_filesystem_driver()
@@ -1371,8 +1428,8 @@ impl VfsNode for FuseDirectory {
13711428
Ok(())
13721429
}
13731430

1374-
fn traverse_mkdir(&self, components: &mut Vec<&str>, mode: AccessPermission) -> io::Result<()> {
1375-
let path = self.traversal_path(components);
1431+
async fn mkdir(&self, component: &str, mode: AccessPermission) -> io::Result<()> {
1432+
let path = self.traversal_path(component);
13761433
let (cmd, rsp_payload_len) = ops::Mkdir::create(path, mode.bits());
13771434

13781435
let rsp = get_filesystem_driver()

0 commit comments

Comments
 (0)