1 mod 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")]
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 static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh
25 exit 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
test_mount_tmpfs_without_flags_allows_rwx()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
test_mount_rdonly_disallows_write()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
test_mount_noexec_disallows_exec()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
test_mount_bind()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
setup_namespaces()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")]
238 macro_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")]
main()253 fn 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"))]
main()271 fn main() {}
272