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()) 94 .unwrap_or_else(|e| panic!("umount failed: {}", e)); 95 } 96 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 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 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 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")] 238macro_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")] 253fn 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"))] 271fn main() {} 272