xref: /third_party/rust/crates/nix/test/test_mount.rs (revision 3da5c369)
1mod common;
2
3// Implementation note: to allow unprivileged users to run it, this test makes
4// use of user and mount namespaces. On systems that allow unprivileged user
5// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run
6// without root.
7
8#[cfg(target_os = "linux")]
9mod test_mount {
10    use std::fs::{self, File};
11    use std::io::{self, Read, Write};
12    use std::os::unix::fs::OpenOptionsExt;
13    use std::os::unix::fs::PermissionsExt;
14    use std::process::{self, Command};
15
16    use libc::{EACCES, EROFS};
17
18    use nix::errno::Errno;
19    use nix::mount::{mount, umount, MsFlags};
20    use nix::sched::{unshare, CloneFlags};
21    use nix::sys::stat::{self, Mode};
22    use nix::unistd::getuid;
23
24    static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh
25exit 23";
26
27    const EXPECTED_STATUS: i32 = 23;
28
29    const NONE: Option<&'static [u8]> = None;
30    #[allow(clippy::bind_instead_of_map)] // False positive
31    pub fn test_mount_tmpfs_without_flags_allows_rwx() {
32        let tempdir = tempfile::tempdir().unwrap();
33
34        mount(
35            NONE,
36            tempdir.path(),
37            Some(b"tmpfs".as_ref()),
38            MsFlags::empty(),
39            NONE,
40        )
41        .unwrap_or_else(|e| panic!("mount failed: {}", e));
42
43        let test_path = tempdir.path().join("test");
44
45        // Verify write.
46        fs::OpenOptions::new()
47            .create(true)
48            .write(true)
49            .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
50            .open(&test_path)
51            .or_else(|e| {
52                if Errno::from_i32(e.raw_os_error().unwrap())
53                    == Errno::EOVERFLOW
54                {
55                    // Skip tests on certain Linux kernels which have a bug
56                    // regarding tmpfs in namespaces.
57                    // Ubuntu 14.04 and 16.04 are known to be affected; 16.10 is
58                    // not.  There is no legitimate reason for open(2) to return
59                    // EOVERFLOW here.
60                    // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087
61                    let stderr = io::stderr();
62                    let mut handle = stderr.lock();
63                    writeln!(
64                        handle,
65                        "Buggy Linux kernel detected.  Skipping test."
66                    )
67                    .unwrap();
68                    process::exit(0);
69                } else {
70                    panic!("open failed: {}", e);
71                }
72            })
73            .and_then(|mut f| f.write(SCRIPT_CONTENTS))
74            .unwrap_or_else(|e| panic!("write failed: {}", e));
75
76        // Verify read.
77        let mut buf = Vec::new();
78        File::open(&test_path)
79            .and_then(|mut f| f.read_to_end(&mut buf))
80            .unwrap_or_else(|e| panic!("read failed: {}", e));
81        assert_eq!(buf, SCRIPT_CONTENTS);
82
83        // Verify execute.
84        assert_eq!(
85            EXPECTED_STATUS,
86            Command::new(&test_path)
87                .status()
88                .unwrap_or_else(|e| panic!("exec failed: {}", e))
89                .code()
90                .unwrap_or_else(|| panic!("child killed by signal"))
91        );
92
93        umount(tempdir.path())
94            .unwrap_or_else(|e| panic!("umount failed: {}", e));
95    }
96
97    pub fn test_mount_rdonly_disallows_write() {
98        let tempdir = tempfile::tempdir().unwrap();
99
100        mount(
101            NONE,
102            tempdir.path(),
103            Some(b"tmpfs".as_ref()),
104            MsFlags::MS_RDONLY,
105            NONE,
106        )
107        .unwrap_or_else(|e| panic!("mount failed: {}", e));
108
109        // EROFS: Read-only file system
110        assert_eq!(
111            EROFS,
112            File::create(tempdir.path().join("test"))
113                .unwrap_err()
114                .raw_os_error()
115                .unwrap()
116        );
117
118        umount(tempdir.path())
119            .unwrap_or_else(|e| panic!("umount failed: {}", e));
120    }
121
122    pub fn test_mount_noexec_disallows_exec() {
123        let tempdir = tempfile::tempdir().unwrap();
124
125        mount(
126            NONE,
127            tempdir.path(),
128            Some(b"tmpfs".as_ref()),
129            MsFlags::MS_NOEXEC,
130            NONE,
131        )
132        .unwrap_or_else(|e| panic!("mount failed: {}", e));
133
134        let test_path = tempdir.path().join("test");
135
136        fs::OpenOptions::new()
137            .create(true)
138            .write(true)
139            .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
140            .open(&test_path)
141            .and_then(|mut f| f.write(SCRIPT_CONTENTS))
142            .unwrap_or_else(|e| panic!("write failed: {}", e));
143
144        // Verify that we cannot execute despite a+x permissions being set.
145        let mode = stat::Mode::from_bits_truncate(
146            fs::metadata(&test_path)
147                .map(|md| md.permissions().mode())
148                .unwrap_or_else(|e| panic!("metadata failed: {}", e)),
149        );
150
151        assert!(
152            mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
153            "{:?} did not have execute permissions",
154            &test_path
155        );
156
157        // EACCES: Permission denied
158        assert_eq!(
159            EACCES,
160            Command::new(&test_path)
161                .status()
162                .unwrap_err()
163                .raw_os_error()
164                .unwrap()
165        );
166
167        umount(tempdir.path())
168            .unwrap_or_else(|e| panic!("umount failed: {}", e));
169    }
170
171    pub fn test_mount_bind() {
172        let tempdir = tempfile::tempdir().unwrap();
173        let file_name = "test";
174
175        {
176            let mount_point = tempfile::tempdir().unwrap();
177
178            mount(
179                Some(tempdir.path()),
180                mount_point.path(),
181                NONE,
182                MsFlags::MS_BIND,
183                NONE,
184            )
185            .unwrap_or_else(|e| panic!("mount failed: {}", e));
186
187            fs::OpenOptions::new()
188                .create(true)
189                .write(true)
190                .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
191                .open(mount_point.path().join(file_name))
192                .and_then(|mut f| f.write(SCRIPT_CONTENTS))
193                .unwrap_or_else(|e| panic!("write failed: {}", e));
194
195            umount(mount_point.path())
196                .unwrap_or_else(|e| panic!("umount failed: {}", e));
197        }
198
199        // Verify the file written in the mount shows up in source directory, even
200        // after unmounting.
201
202        let mut buf = Vec::new();
203        File::open(tempdir.path().join(file_name))
204            .and_then(|mut f| f.read_to_end(&mut buf))
205            .unwrap_or_else(|e| panic!("read failed: {}", e));
206        assert_eq!(buf, SCRIPT_CONTENTS);
207    }
208
209    pub fn setup_namespaces() {
210        // Hold on to the uid in the parent namespace.
211        let uid = getuid();
212
213        unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| {
214            let stderr = io::stderr();
215            let mut handle = stderr.lock();
216            writeln!(handle,
217                     "unshare failed: {}. Are unprivileged user namespaces available?",
218                     e).unwrap();
219            writeln!(handle, "mount is not being tested").unwrap();
220            // Exit with success because not all systems support unprivileged user namespaces, and
221            // that's not what we're testing for.
222            process::exit(0);
223        });
224
225        // Map user as uid 1000.
226        fs::OpenOptions::new()
227            .write(true)
228            .open("/proc/self/uid_map")
229            .and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes()))
230            .unwrap_or_else(|e| panic!("could not write uid map: {}", e));
231    }
232}
233
234// Test runner
235
236/// Mimic normal test output (hackishly).
237#[cfg(target_os = "linux")]
238macro_rules! run_tests {
239    ( $($test_fn:ident),* ) => {{
240        println!();
241
242        $(
243            print!("test test_mount::{} ... ", stringify!($test_fn));
244            $test_fn();
245            println!("ok");
246        )*
247
248        println!();
249    }}
250}
251
252#[cfg(target_os = "linux")]
253fn main() {
254    use test_mount::{
255        setup_namespaces, test_mount_bind, test_mount_noexec_disallows_exec,
256        test_mount_rdonly_disallows_write,
257        test_mount_tmpfs_without_flags_allows_rwx,
258    };
259    skip_if_cirrus!("Fails for an unknown reason Cirrus CI.  Bug #1351");
260    setup_namespaces();
261
262    run_tests!(
263        test_mount_tmpfs_without_flags_allows_rwx,
264        test_mount_rdonly_disallows_write,
265        test_mount_noexec_disallows_exec,
266        test_mount_bind
267    );
268}
269
270#[cfg(not(target_os = "linux"))]
271fn main() {}
272