Skip to content

Commit ecc1422

Browse files
六滔bergwolf
六滔
authored andcommitted
passthrough: add unit test for passthroughfs
Signed-off-by: Qinqi Qu <quqinqi@linux.alibaba.com>
1 parent 69feddf commit ecc1422

File tree

1 file changed

+288
-0
lines changed

1 file changed

+288
-0
lines changed

src/passthrough/sync_io.rs

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,3 +1345,291 @@ impl<S: BitmapSlice + Send + Sync> FileSystem for PassthroughFs<S> {
13451345
}
13461346
}
13471347
}
1348+
1349+
#[cfg(test)]
1350+
mod tests {
1351+
use std::convert::TryInto;
1352+
1353+
use super::*;
1354+
use crate::abi::fuse_abi::ROOT_ID;
1355+
use std::path::Path;
1356+
use vmm_sys_util::{tempdir::TempDir, tempfile::TempFile};
1357+
1358+
fn prepare_fs_tmpdir() -> (PassthroughFs, TempDir) {
1359+
let source = TempDir::new().expect("Cannot create temporary directory.");
1360+
let fs_cfg = Config {
1361+
writeback: true,
1362+
do_import: true,
1363+
no_open: false,
1364+
no_readdir: false,
1365+
inode_file_handles: true,
1366+
xattr: true,
1367+
killpriv_v2: true, //enable killpriv_v2
1368+
root_dir: source
1369+
.as_path()
1370+
.to_str()
1371+
.expect("source path to string")
1372+
.to_string(),
1373+
..Default::default()
1374+
};
1375+
let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1376+
fs.import().unwrap();
1377+
1378+
// enable all fuse options
1379+
let opt = FsOptions::all();
1380+
fs.init(opt).unwrap();
1381+
1382+
(fs, source)
1383+
}
1384+
1385+
fn prepare_context() -> Context {
1386+
Context {
1387+
uid: unsafe { libc::getuid() },
1388+
gid: unsafe { libc::getgid() },
1389+
pid: unsafe { libc::getpid() },
1390+
..Default::default()
1391+
}
1392+
}
1393+
1394+
fn create_file_with_sugid(ctx: &Context, fs: &PassthroughFs<()>) -> (Entry, Handle) {
1395+
let fname = CString::new("testfile").unwrap();
1396+
let args = CreateIn {
1397+
flags: libc::O_WRONLY as u32,
1398+
mode: 0o6777,
1399+
umask: 0,
1400+
fuse_flags: 0,
1401+
};
1402+
let (test_entry, handle, _, _) = fs.create(&ctx, ROOT_ID, &fname, args).unwrap();
1403+
1404+
(test_entry, handle.unwrap())
1405+
}
1406+
1407+
#[test]
1408+
fn test_dir_operations() {
1409+
let (fs, _source) = prepare_fs_tmpdir();
1410+
let ctx = prepare_context();
1411+
1412+
let dir = CString::new("testdir").unwrap();
1413+
fs.mkdir(&ctx, ROOT_ID, &dir, 0o755, 0).unwrap();
1414+
1415+
let (handle, _) = fs.opendir(&ctx, ROOT_ID, libc::O_RDONLY as u32).unwrap();
1416+
1417+
assert!(fs
1418+
.readdir(&ctx, ROOT_ID, handle.unwrap(), 10, 0, &mut |_| Ok(1))
1419+
.is_err());
1420+
1421+
assert!(fs
1422+
.readdirplus(&ctx, ROOT_ID, handle.unwrap(), 10, 0, &mut |_, _| Ok(1))
1423+
.is_err());
1424+
1425+
assert!(fs.fsyncdir(&ctx, ROOT_ID, true, handle.unwrap()).is_ok());
1426+
1427+
assert!(fs.releasedir(&ctx, ROOT_ID, 0, handle.unwrap()).is_ok());
1428+
assert!(fs.rmdir(&ctx, ROOT_ID, &dir).is_ok());
1429+
}
1430+
1431+
#[test]
1432+
fn test_link_rename() {
1433+
let (fs, _source) = prepare_fs_tmpdir();
1434+
let ctx = prepare_context();
1435+
1436+
let fname = CString::new("testfile").unwrap();
1437+
let args = CreateIn::default();
1438+
let (test_entry, _, _, _) = fs.create(&ctx, ROOT_ID, &fname, args).unwrap();
1439+
1440+
let link_name = CString::new("testlink").unwrap();
1441+
fs.link(&ctx, test_entry.inode, ROOT_ID, &link_name)
1442+
.unwrap();
1443+
1444+
let new_name = CString::new("newlink").unwrap();
1445+
fs.rename(&ctx, ROOT_ID, &link_name, ROOT_ID, &new_name, 0)
1446+
.unwrap();
1447+
1448+
let link_entry = fs.lookup(&ctx, ROOT_ID, &new_name).unwrap();
1449+
1450+
assert_eq!(link_entry.inode, test_entry.inode);
1451+
}
1452+
1453+
#[test]
1454+
fn test_unlink_delete_file() {
1455+
let (fs, source) = prepare_fs_tmpdir();
1456+
let child_path = TempFile::new_in(source.as_path()).expect("Cannot create temporary file.");
1457+
1458+
let ctx = prepare_context();
1459+
1460+
let child_str = child_path
1461+
.as_path()
1462+
.file_name()
1463+
.unwrap()
1464+
.to_str()
1465+
.expect("path to string");
1466+
let child = CString::new(child_str).unwrap();
1467+
1468+
fs.unlink(&ctx, ROOT_ID, &child).unwrap();
1469+
1470+
assert!(!Path::new(child_str).exists())
1471+
}
1472+
1473+
#[test]
1474+
// test virtiofs CVE-2020-35517, should not open device file
1475+
fn test_mknod_and_open_device() {
1476+
let (fs, _source) = prepare_fs_tmpdir();
1477+
1478+
let ctx = prepare_context();
1479+
1480+
let device_name = CString::new("test_device").unwrap();
1481+
let mode = libc::S_IFBLK;
1482+
let mask = 0o777;
1483+
let device_no = libc::makedev(0, 103) as u32;
1484+
1485+
let device_entry = fs
1486+
.mknod(&ctx, ROOT_ID, &device_name, mode, device_no, mask)
1487+
.unwrap();
1488+
let (d_st, _) = fs.getattr(&ctx, device_entry.inode, None).unwrap();
1489+
1490+
assert_eq!(d_st.st_mode & libc::S_IFMT, libc::S_IFBLK);
1491+
assert_eq!(d_st.st_rdev as u32, device_no);
1492+
1493+
// open device should fail because of is_safe_inode check
1494+
let err = fs
1495+
.open(&ctx, device_entry.inode, libc::O_RDWR as u32, 0)
1496+
.is_err();
1497+
assert_eq!(err, true);
1498+
}
1499+
1500+
#[test]
1501+
fn test_create_access() {
1502+
let (fs, _source) = prepare_fs_tmpdir();
1503+
let ctx = prepare_context();
1504+
1505+
let fname = CString::new("testfile").unwrap();
1506+
let args = CreateIn {
1507+
flags: libc::O_WRONLY as u32,
1508+
mode: 0644,
1509+
umask: 0,
1510+
fuse_flags: 0,
1511+
};
1512+
let (test_entry, _, _, _) = fs.create(&ctx, ROOT_ID, &fname, args).unwrap();
1513+
1514+
let mask = (libc::R_OK | libc::W_OK) as u32;
1515+
assert_eq!(fs.access(&ctx, test_entry.inode, mask).is_ok(), true);
1516+
let mask = (libc::R_OK | libc::W_OK | libc::X_OK) as u32;
1517+
assert_eq!(fs.access(&ctx, test_entry.inode, mask).is_ok(), false);
1518+
assert!(fs
1519+
.release(&ctx, test_entry.inode, 0, 0, false, false, Some(0))
1520+
.is_err());
1521+
}
1522+
1523+
#[test]
1524+
fn test_symlink_escape_root() {
1525+
let (fs, _source) = prepare_fs_tmpdir();
1526+
let child_path =
1527+
TempFile::new_in(_source.as_path()).expect("Cannot create temporary file.");
1528+
let ctx = prepare_context();
1529+
1530+
let eval_sym_dest = CString::new("/root").unwrap();
1531+
let eval_sym_name = CString::new("eval_sym").unwrap();
1532+
let normal_sym_dest = CString::new(child_path.as_path().to_str().unwrap()).unwrap();
1533+
let normal_sym_name = CString::new("normal_sym").unwrap();
1534+
1535+
let normal_sym_entry = fs
1536+
.symlink(&ctx, &normal_sym_dest, ROOT_ID, &normal_sym_name)
1537+
.unwrap();
1538+
1539+
let eval_sym_entry = fs
1540+
.symlink(&ctx, &eval_sym_dest, ROOT_ID, &eval_sym_name)
1541+
.unwrap();
1542+
1543+
let normal_buf = fs.readlink(&ctx, normal_sym_entry.inode).unwrap();
1544+
let eval_buf = fs.readlink(&ctx, eval_sym_entry.inode).unwrap();
1545+
let normal_dest_name = CString::new(String::from_utf8(normal_buf).unwrap()).unwrap();
1546+
let eval_dest_name = CString::new(String::from_utf8(eval_buf).unwrap()).unwrap();
1547+
1548+
assert_eq!(normal_dest_name, normal_sym_dest);
1549+
assert_eq!(eval_dest_name, eval_sym_dest);
1550+
}
1551+
1552+
#[test]
1553+
fn test_setattr_and_drop_priv() {
1554+
let (fs, _source) = prepare_fs_tmpdir();
1555+
let ctx = prepare_context();
1556+
1557+
let (test_entry, _) = create_file_with_sugid(&ctx, &fs);
1558+
1559+
let (mut old_att, _) = fs.getattr(&ctx, test_entry.inode, None).unwrap();
1560+
1561+
old_att.st_size = 4096;
1562+
let mut valid = SetattrValid::SIZE | SetattrValid::KILL_SUIDGID;
1563+
let (attr_not_drop, _) = fs
1564+
.setattr(&ctx, test_entry.inode, old_att, None, valid)
1565+
.unwrap();
1566+
// during file size change,
1567+
// suid/sgid should be dropped because of killpriv_v2
1568+
assert_eq!(attr_not_drop.st_mode, 0o100777);
1569+
1570+
old_att.st_size = 0;
1571+
old_att.st_uid = 1;
1572+
old_att.st_gid = 1;
1573+
old_att.st_atime = 0;
1574+
old_att.st_mtime = 0;
1575+
valid = SetattrValid::SIZE
1576+
| SetattrValid::ATIME
1577+
| SetattrValid::MTIME
1578+
| SetattrValid::UID
1579+
| SetattrValid::GID;
1580+
1581+
let (attr, _) = fs
1582+
.setattr(&ctx, test_entry.inode, old_att, None, valid)
1583+
.unwrap();
1584+
// suid/sgid is dropped because chmod is called
1585+
assert_eq!(attr.st_mode, 0o100777);
1586+
assert_eq!(attr.st_size, 0);
1587+
}
1588+
1589+
#[test]
1590+
// fallocate missing killpriv logic, should be fixed
1591+
fn test_fallocate_drop_priv() {
1592+
let (fs, _source) = prepare_fs_tmpdir();
1593+
let ctx = prepare_context();
1594+
1595+
let (test_entry, handle) = create_file_with_sugid(&ctx, &fs);
1596+
1597+
let offset = fs
1598+
.lseek(
1599+
&ctx,
1600+
test_entry.inode,
1601+
handle,
1602+
4096,
1603+
libc::SEEK_SET.try_into().unwrap(),
1604+
)
1605+
.unwrap();
1606+
fs.fallocate(&ctx, test_entry.inode, handle, 0, offset, 4096)
1607+
.unwrap();
1608+
1609+
let (att, _) = fs.getattr(&ctx, test_entry.inode, None).unwrap();
1610+
1611+
assert_eq!(att.st_size, 8192);
1612+
// suid/sgid not dropped
1613+
assert_eq!(att.st_mode, 0o106777);
1614+
}
1615+
1616+
#[test]
1617+
fn test_fsync_flush() {
1618+
let (fs, _source) = prepare_fs_tmpdir();
1619+
let ctx = prepare_context();
1620+
1621+
let (test_entry, handle) = create_file_with_sugid(&ctx, &fs);
1622+
1623+
assert!(fs.fsync(&ctx, test_entry.inode, false, handle).is_ok());
1624+
assert!(fs.flush(&ctx, test_entry.inode, handle, 0).is_ok());
1625+
}
1626+
1627+
#[test]
1628+
fn test_statfs() {
1629+
let (fs, _source) = prepare_fs_tmpdir();
1630+
let ctx = prepare_context();
1631+
1632+
let statfs = fs.statfs(&ctx, ROOT_ID).unwrap();
1633+
assert_eq!(statfs.f_namemax, 255);
1634+
}
1635+
}

0 commit comments

Comments
 (0)