@@ -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