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