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