• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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