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