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()).unwrap_or_else(|e| panic!("umount failed: {e}"));
94 }
95
96 pub fn test_mount_rdonly_disallows_write() {
97 let tempdir = tempfile::tempdir().unwrap();
98
99 mount(
100 NONE,
101 tempdir.path(),
102 Some(b"tmpfs".as_ref()),
103 MsFlags::MS_RDONLY,
104 NONE,
105 )
106 .unwrap_or_else(|e| panic!("mount failed: {e}"));
107
108 // EROFS: Read-only file system
109 assert_eq!(
110 EROFS,
111 File::create(tempdir.path().join("test"))
112 .unwrap_err()
113 .raw_os_error()
114 .unwrap()
115 );
116
117 umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
118 }
119
120 pub fn test_mount_noexec_disallows_exec() {
121 let tempdir = tempfile::tempdir().unwrap();
122
123 mount(
124 NONE,
125 tempdir.path(),
126 Some(b"tmpfs".as_ref()),
127 MsFlags::MS_NOEXEC,
128 NONE,
129 )
130 .unwrap_or_else(|e| panic!("mount failed: {e}"));
131
132 let test_path = tempdir.path().join("test");
133
134 fs::OpenOptions::new()
135 .create(true)
136 .write(true)
137 .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
138 .open(&test_path)
139 .and_then(|mut f| f.write(SCRIPT_CONTENTS))
140 .unwrap_or_else(|e| panic!("write failed: {e}"));
141
142 // Verify that we cannot execute despite a+x permissions being set.
143 let mode = stat::Mode::from_bits_truncate(
144 fs::metadata(&test_path)
145 .map(|md| md.permissions().mode())
146 .unwrap_or_else(|e| panic!("metadata failed: {e}")),
147 );
148
149 assert!(
150 mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
151 "{:?} did not have execute permissions",
152 &test_path
153 );
154
155 // EACCES: Permission denied
156 assert_eq!(
157 EACCES,
158 Command::new(&test_path)
159 .status()
160 .unwrap_err()
161 .raw_os_error()
162 .unwrap()
163 );
164
165 umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
166 }
167
168 pub fn test_mount_bind() {
169 let tempdir = tempfile::tempdir().unwrap();
170 let file_name = "test";
171
172 {
173 let mount_point = tempfile::tempdir().unwrap();
174
175 mount(
176 Some(tempdir.path()),
177 mount_point.path(),
178 NONE,
179 MsFlags::MS_BIND,
180 NONE,
181 )
182 .unwrap_or_else(|e| panic!("mount failed: {e}"));
183
184 fs::OpenOptions::new()
185 .create(true)
186 .write(true)
187 .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
188 .open(mount_point.path().join(file_name))
189 .and_then(|mut f| f.write(SCRIPT_CONTENTS))
190 .unwrap_or_else(|e| panic!("write failed: {e}"));
191
192 umount(mount_point.path())
193 .unwrap_or_else(|e| panic!("umount failed: {e}"));
194 }
195
196 // Verify the file written in the mount shows up in source directory, even
197 // after unmounting.
198
199 let mut buf = Vec::new();
200 File::open(tempdir.path().join(file_name))
201 .and_then(|mut f| f.read_to_end(&mut buf))
202 .unwrap_or_else(|e| panic!("read failed: {e}"));
203 assert_eq!(buf, SCRIPT_CONTENTS);
204 }
205
206 pub fn setup_namespaces() {
207 // Hold on to the uid in the parent namespace.
208 let uid = getuid();
209
210 unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| {
211 let stderr = io::stderr();
212 let mut handle = stderr.lock();
213 writeln!(handle,
214 "unshare failed: {e}. Are unprivileged user namespaces available?").unwrap();
215 writeln!(handle, "mount is not being tested").unwrap();
216 // Exit with success because not all systems support unprivileged user namespaces, and
217 // that's not what we're testing for.
218 process::exit(0);
219 });
220
221 // Map user as uid 1000.
222 fs::OpenOptions::new()
223 .write(true)
224 .open("/proc/self/uid_map")
225 .and_then(|mut f| f.write(format!("1000 {uid} 1\n").as_bytes()))
226 .unwrap_or_else(|e| panic!("could not write uid map: {e}"));
227 }
228}
229
230// Test runner
231
232/// Mimic normal test output (hackishly).
233#[cfg(target_os = "linux")]
234macro_rules! run_tests {
235 ( $($test_fn:ident),* ) => {{
236 println!();
237
238 $(
239 print!("test test_mount::{} ... ", stringify!($test_fn));
240 $test_fn();
241 println!("ok");
242 )*
243
244 println!();
245 }}
246}
247
248#[cfg(target_os = "linux")]
249fn main() {
250 use test_mount::{
251 setup_namespaces, test_mount_bind, test_mount_noexec_disallows_exec,
252 test_mount_rdonly_disallows_write,
253 test_mount_tmpfs_without_flags_allows_rwx,
254 };
255 skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1351");
256 setup_namespaces();
257
258 run_tests!(
259 test_mount_tmpfs_without_flags_allows_rwx,
260 test_mount_rdonly_disallows_write,
261 test_mount_noexec_disallows_exec,
262 test_mount_bind
263 );
264}
265
266#[cfg(not(target_os = "linux"))]
267fn main() {}
268