@@ -1345,3 +1345,291 @@ impl<S: BitmapSlice + Send + Sync> FileSystem for PassthroughFs<S> {
1345
1345
}
1346
1346
}
1347
1347
}
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