13da5c369Sopenharmony_cimod common; 23da5c369Sopenharmony_ci 33da5c369Sopenharmony_ci// Implementation note: to allow unprivileged users to run it, this test makes 43da5c369Sopenharmony_ci// use of user and mount namespaces. On systems that allow unprivileged user 53da5c369Sopenharmony_ci// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run 63da5c369Sopenharmony_ci// without root. 73da5c369Sopenharmony_ci 83da5c369Sopenharmony_ci#[cfg(target_os = "linux")] 93da5c369Sopenharmony_cimod test_mount { 103da5c369Sopenharmony_ci use std::fs::{self, File}; 113da5c369Sopenharmony_ci use std::io::{self, Read, Write}; 123da5c369Sopenharmony_ci use std::os::unix::fs::OpenOptionsExt; 133da5c369Sopenharmony_ci use std::os::unix::fs::PermissionsExt; 143da5c369Sopenharmony_ci use std::process::{self, Command}; 153da5c369Sopenharmony_ci 163da5c369Sopenharmony_ci use libc::{EACCES, EROFS}; 173da5c369Sopenharmony_ci 183da5c369Sopenharmony_ci use nix::errno::Errno; 193da5c369Sopenharmony_ci use nix::mount::{mount, umount, MsFlags}; 203da5c369Sopenharmony_ci use nix::sched::{unshare, CloneFlags}; 213da5c369Sopenharmony_ci use nix::sys::stat::{self, Mode}; 223da5c369Sopenharmony_ci use nix::unistd::getuid; 233da5c369Sopenharmony_ci 243da5c369Sopenharmony_ci static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh 253da5c369Sopenharmony_ciexit 23"; 263da5c369Sopenharmony_ci 273da5c369Sopenharmony_ci const EXPECTED_STATUS: i32 = 23; 283da5c369Sopenharmony_ci 293da5c369Sopenharmony_ci const NONE: Option<&'static [u8]> = None; 303da5c369Sopenharmony_ci #[allow(clippy::bind_instead_of_map)] // False positive 313da5c369Sopenharmony_ci pub fn test_mount_tmpfs_without_flags_allows_rwx() { 323da5c369Sopenharmony_ci let tempdir = tempfile::tempdir().unwrap(); 333da5c369Sopenharmony_ci 343da5c369Sopenharmony_ci mount( 353da5c369Sopenharmony_ci NONE, 363da5c369Sopenharmony_ci tempdir.path(), 373da5c369Sopenharmony_ci Some(b"tmpfs".as_ref()), 383da5c369Sopenharmony_ci MsFlags::empty(), 393da5c369Sopenharmony_ci NONE, 403da5c369Sopenharmony_ci ) 413da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("mount failed: {}", e)); 423da5c369Sopenharmony_ci 433da5c369Sopenharmony_ci let test_path = tempdir.path().join("test"); 443da5c369Sopenharmony_ci 453da5c369Sopenharmony_ci // Verify write. 463da5c369Sopenharmony_ci fs::OpenOptions::new() 473da5c369Sopenharmony_ci .create(true) 483da5c369Sopenharmony_ci .write(true) 493da5c369Sopenharmony_ci .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) 503da5c369Sopenharmony_ci .open(&test_path) 513da5c369Sopenharmony_ci .or_else(|e| { 523da5c369Sopenharmony_ci if Errno::from_i32(e.raw_os_error().unwrap()) 533da5c369Sopenharmony_ci == Errno::EOVERFLOW 543da5c369Sopenharmony_ci { 553da5c369Sopenharmony_ci // Skip tests on certain Linux kernels which have a bug 563da5c369Sopenharmony_ci // regarding tmpfs in namespaces. 573da5c369Sopenharmony_ci // Ubuntu 14.04 and 16.04 are known to be affected; 16.10 is 583da5c369Sopenharmony_ci // not. There is no legitimate reason for open(2) to return 593da5c369Sopenharmony_ci // EOVERFLOW here. 603da5c369Sopenharmony_ci // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087 613da5c369Sopenharmony_ci let stderr = io::stderr(); 623da5c369Sopenharmony_ci let mut handle = stderr.lock(); 633da5c369Sopenharmony_ci writeln!( 643da5c369Sopenharmony_ci handle, 653da5c369Sopenharmony_ci "Buggy Linux kernel detected. Skipping test." 663da5c369Sopenharmony_ci ) 673da5c369Sopenharmony_ci .unwrap(); 683da5c369Sopenharmony_ci process::exit(0); 693da5c369Sopenharmony_ci } else { 703da5c369Sopenharmony_ci panic!("open failed: {}", e); 713da5c369Sopenharmony_ci } 723da5c369Sopenharmony_ci }) 733da5c369Sopenharmony_ci .and_then(|mut f| f.write(SCRIPT_CONTENTS)) 743da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("write failed: {}", e)); 753da5c369Sopenharmony_ci 763da5c369Sopenharmony_ci // Verify read. 773da5c369Sopenharmony_ci let mut buf = Vec::new(); 783da5c369Sopenharmony_ci File::open(&test_path) 793da5c369Sopenharmony_ci .and_then(|mut f| f.read_to_end(&mut buf)) 803da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("read failed: {}", e)); 813da5c369Sopenharmony_ci assert_eq!(buf, SCRIPT_CONTENTS); 823da5c369Sopenharmony_ci 833da5c369Sopenharmony_ci // Verify execute. 843da5c369Sopenharmony_ci assert_eq!( 853da5c369Sopenharmony_ci EXPECTED_STATUS, 863da5c369Sopenharmony_ci Command::new(&test_path) 873da5c369Sopenharmony_ci .status() 883da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("exec failed: {}", e)) 893da5c369Sopenharmony_ci .code() 903da5c369Sopenharmony_ci .unwrap_or_else(|| panic!("child killed by signal")) 913da5c369Sopenharmony_ci ); 923da5c369Sopenharmony_ci 933da5c369Sopenharmony_ci umount(tempdir.path()) 943da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("umount failed: {}", e)); 953da5c369Sopenharmony_ci } 963da5c369Sopenharmony_ci 973da5c369Sopenharmony_ci pub fn test_mount_rdonly_disallows_write() { 983da5c369Sopenharmony_ci let tempdir = tempfile::tempdir().unwrap(); 993da5c369Sopenharmony_ci 1003da5c369Sopenharmony_ci mount( 1013da5c369Sopenharmony_ci NONE, 1023da5c369Sopenharmony_ci tempdir.path(), 1033da5c369Sopenharmony_ci Some(b"tmpfs".as_ref()), 1043da5c369Sopenharmony_ci MsFlags::MS_RDONLY, 1053da5c369Sopenharmony_ci NONE, 1063da5c369Sopenharmony_ci ) 1073da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("mount failed: {}", e)); 1083da5c369Sopenharmony_ci 1093da5c369Sopenharmony_ci // EROFS: Read-only file system 1103da5c369Sopenharmony_ci assert_eq!( 1113da5c369Sopenharmony_ci EROFS, 1123da5c369Sopenharmony_ci File::create(tempdir.path().join("test")) 1133da5c369Sopenharmony_ci .unwrap_err() 1143da5c369Sopenharmony_ci .raw_os_error() 1153da5c369Sopenharmony_ci .unwrap() 1163da5c369Sopenharmony_ci ); 1173da5c369Sopenharmony_ci 1183da5c369Sopenharmony_ci umount(tempdir.path()) 1193da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("umount failed: {}", e)); 1203da5c369Sopenharmony_ci } 1213da5c369Sopenharmony_ci 1223da5c369Sopenharmony_ci pub fn test_mount_noexec_disallows_exec() { 1233da5c369Sopenharmony_ci let tempdir = tempfile::tempdir().unwrap(); 1243da5c369Sopenharmony_ci 1253da5c369Sopenharmony_ci mount( 1263da5c369Sopenharmony_ci NONE, 1273da5c369Sopenharmony_ci tempdir.path(), 1283da5c369Sopenharmony_ci Some(b"tmpfs".as_ref()), 1293da5c369Sopenharmony_ci MsFlags::MS_NOEXEC, 1303da5c369Sopenharmony_ci NONE, 1313da5c369Sopenharmony_ci ) 1323da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("mount failed: {}", e)); 1333da5c369Sopenharmony_ci 1343da5c369Sopenharmony_ci let test_path = tempdir.path().join("test"); 1353da5c369Sopenharmony_ci 1363da5c369Sopenharmony_ci fs::OpenOptions::new() 1373da5c369Sopenharmony_ci .create(true) 1383da5c369Sopenharmony_ci .write(true) 1393da5c369Sopenharmony_ci .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) 1403da5c369Sopenharmony_ci .open(&test_path) 1413da5c369Sopenharmony_ci .and_then(|mut f| f.write(SCRIPT_CONTENTS)) 1423da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("write failed: {}", e)); 1433da5c369Sopenharmony_ci 1443da5c369Sopenharmony_ci // Verify that we cannot execute despite a+x permissions being set. 1453da5c369Sopenharmony_ci let mode = stat::Mode::from_bits_truncate( 1463da5c369Sopenharmony_ci fs::metadata(&test_path) 1473da5c369Sopenharmony_ci .map(|md| md.permissions().mode()) 1483da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("metadata failed: {}", e)), 1493da5c369Sopenharmony_ci ); 1503da5c369Sopenharmony_ci 1513da5c369Sopenharmony_ci assert!( 1523da5c369Sopenharmony_ci mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH), 1533da5c369Sopenharmony_ci "{:?} did not have execute permissions", 1543da5c369Sopenharmony_ci &test_path 1553da5c369Sopenharmony_ci ); 1563da5c369Sopenharmony_ci 1573da5c369Sopenharmony_ci // EACCES: Permission denied 1583da5c369Sopenharmony_ci assert_eq!( 1593da5c369Sopenharmony_ci EACCES, 1603da5c369Sopenharmony_ci Command::new(&test_path) 1613da5c369Sopenharmony_ci .status() 1623da5c369Sopenharmony_ci .unwrap_err() 1633da5c369Sopenharmony_ci .raw_os_error() 1643da5c369Sopenharmony_ci .unwrap() 1653da5c369Sopenharmony_ci ); 1663da5c369Sopenharmony_ci 1673da5c369Sopenharmony_ci umount(tempdir.path()) 1683da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("umount failed: {}", e)); 1693da5c369Sopenharmony_ci } 1703da5c369Sopenharmony_ci 1713da5c369Sopenharmony_ci pub fn test_mount_bind() { 1723da5c369Sopenharmony_ci let tempdir = tempfile::tempdir().unwrap(); 1733da5c369Sopenharmony_ci let file_name = "test"; 1743da5c369Sopenharmony_ci 1753da5c369Sopenharmony_ci { 1763da5c369Sopenharmony_ci let mount_point = tempfile::tempdir().unwrap(); 1773da5c369Sopenharmony_ci 1783da5c369Sopenharmony_ci mount( 1793da5c369Sopenharmony_ci Some(tempdir.path()), 1803da5c369Sopenharmony_ci mount_point.path(), 1813da5c369Sopenharmony_ci NONE, 1823da5c369Sopenharmony_ci MsFlags::MS_BIND, 1833da5c369Sopenharmony_ci NONE, 1843da5c369Sopenharmony_ci ) 1853da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("mount failed: {}", e)); 1863da5c369Sopenharmony_ci 1873da5c369Sopenharmony_ci fs::OpenOptions::new() 1883da5c369Sopenharmony_ci .create(true) 1893da5c369Sopenharmony_ci .write(true) 1903da5c369Sopenharmony_ci .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) 1913da5c369Sopenharmony_ci .open(mount_point.path().join(file_name)) 1923da5c369Sopenharmony_ci .and_then(|mut f| f.write(SCRIPT_CONTENTS)) 1933da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("write failed: {}", e)); 1943da5c369Sopenharmony_ci 1953da5c369Sopenharmony_ci umount(mount_point.path()) 1963da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("umount failed: {}", e)); 1973da5c369Sopenharmony_ci } 1983da5c369Sopenharmony_ci 1993da5c369Sopenharmony_ci // Verify the file written in the mount shows up in source directory, even 2003da5c369Sopenharmony_ci // after unmounting. 2013da5c369Sopenharmony_ci 2023da5c369Sopenharmony_ci let mut buf = Vec::new(); 2033da5c369Sopenharmony_ci File::open(tempdir.path().join(file_name)) 2043da5c369Sopenharmony_ci .and_then(|mut f| f.read_to_end(&mut buf)) 2053da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("read failed: {}", e)); 2063da5c369Sopenharmony_ci assert_eq!(buf, SCRIPT_CONTENTS); 2073da5c369Sopenharmony_ci } 2083da5c369Sopenharmony_ci 2093da5c369Sopenharmony_ci pub fn setup_namespaces() { 2103da5c369Sopenharmony_ci // Hold on to the uid in the parent namespace. 2113da5c369Sopenharmony_ci let uid = getuid(); 2123da5c369Sopenharmony_ci 2133da5c369Sopenharmony_ci unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| { 2143da5c369Sopenharmony_ci let stderr = io::stderr(); 2153da5c369Sopenharmony_ci let mut handle = stderr.lock(); 2163da5c369Sopenharmony_ci writeln!(handle, 2173da5c369Sopenharmony_ci "unshare failed: {}. Are unprivileged user namespaces available?", 2183da5c369Sopenharmony_ci e).unwrap(); 2193da5c369Sopenharmony_ci writeln!(handle, "mount is not being tested").unwrap(); 2203da5c369Sopenharmony_ci // Exit with success because not all systems support unprivileged user namespaces, and 2213da5c369Sopenharmony_ci // that's not what we're testing for. 2223da5c369Sopenharmony_ci process::exit(0); 2233da5c369Sopenharmony_ci }); 2243da5c369Sopenharmony_ci 2253da5c369Sopenharmony_ci // Map user as uid 1000. 2263da5c369Sopenharmony_ci fs::OpenOptions::new() 2273da5c369Sopenharmony_ci .write(true) 2283da5c369Sopenharmony_ci .open("/proc/self/uid_map") 2293da5c369Sopenharmony_ci .and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes())) 2303da5c369Sopenharmony_ci .unwrap_or_else(|e| panic!("could not write uid map: {}", e)); 2313da5c369Sopenharmony_ci } 2323da5c369Sopenharmony_ci} 2333da5c369Sopenharmony_ci 2343da5c369Sopenharmony_ci// Test runner 2353da5c369Sopenharmony_ci 2363da5c369Sopenharmony_ci/// Mimic normal test output (hackishly). 2373da5c369Sopenharmony_ci#[cfg(target_os = "linux")] 2383da5c369Sopenharmony_cimacro_rules! run_tests { 2393da5c369Sopenharmony_ci ( $($test_fn:ident),* ) => {{ 2403da5c369Sopenharmony_ci println!(); 2413da5c369Sopenharmony_ci 2423da5c369Sopenharmony_ci $( 2433da5c369Sopenharmony_ci print!("test test_mount::{} ... ", stringify!($test_fn)); 2443da5c369Sopenharmony_ci $test_fn(); 2453da5c369Sopenharmony_ci println!("ok"); 2463da5c369Sopenharmony_ci )* 2473da5c369Sopenharmony_ci 2483da5c369Sopenharmony_ci println!(); 2493da5c369Sopenharmony_ci }} 2503da5c369Sopenharmony_ci} 2513da5c369Sopenharmony_ci 2523da5c369Sopenharmony_ci#[cfg(target_os = "linux")] 2533da5c369Sopenharmony_cifn main() { 2543da5c369Sopenharmony_ci use test_mount::{ 2553da5c369Sopenharmony_ci setup_namespaces, test_mount_bind, test_mount_noexec_disallows_exec, 2563da5c369Sopenharmony_ci test_mount_rdonly_disallows_write, 2573da5c369Sopenharmony_ci test_mount_tmpfs_without_flags_allows_rwx, 2583da5c369Sopenharmony_ci }; 2593da5c369Sopenharmony_ci skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1351"); 2603da5c369Sopenharmony_ci setup_namespaces(); 2613da5c369Sopenharmony_ci 2623da5c369Sopenharmony_ci run_tests!( 2633da5c369Sopenharmony_ci test_mount_tmpfs_without_flags_allows_rwx, 2643da5c369Sopenharmony_ci test_mount_rdonly_disallows_write, 2653da5c369Sopenharmony_ci test_mount_noexec_disallows_exec, 2663da5c369Sopenharmony_ci test_mount_bind 2673da5c369Sopenharmony_ci ); 2683da5c369Sopenharmony_ci} 2693da5c369Sopenharmony_ci 2703da5c369Sopenharmony_ci#[cfg(not(target_os = "linux"))] 2713da5c369Sopenharmony_cifn main() {} 272