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