1 use std::fs::{self, File};
2 use std::io::{Read, Write};
3 use std::os::unix::fs::OpenOptionsExt;
4 use std::os::unix::fs::PermissionsExt;
5 use std::process::Command;
6 
7 use libc::{EACCES, EROFS};
8 
9 use nix::mount::{mount, umount, MsFlags};
10 use nix::sys::stat::{self, Mode};
11 
12 use crate::*;
13 
14 static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh
15 exit 23";
16 
17 const EXPECTED_STATUS: i32 = 23;
18 
19 const NONE: Option<&'static [u8]> = None;
20 
21 #[test]
test_mount_tmpfs_without_flags_allows_rwx()22 fn test_mount_tmpfs_without_flags_allows_rwx() {
23     require_capability!(
24         "test_mount_tmpfs_without_flags_allows_rwx",
25         CAP_SYS_ADMIN
26     );
27     let tempdir = tempfile::tempdir().unwrap();
28 
29     mount(
30         NONE,
31         tempdir.path(),
32         Some(b"tmpfs".as_ref()),
33         MsFlags::empty(),
34         NONE,
35     )
36     .unwrap_or_else(|e| panic!("mount failed: {e}"));
37 
38     let test_path = tempdir.path().join("test");
39 
40     // Verify write.
41     fs::OpenOptions::new()
42         .create(true)
43         .write(true)
44         .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
45         .open(&test_path)
46         .and_then(|mut f| f.write(SCRIPT_CONTENTS))
47         .unwrap_or_else(|e| panic!("write failed: {e}"));
48 
49     // Verify read.
50     let mut buf = Vec::new();
51     File::open(&test_path)
52         .and_then(|mut f| f.read_to_end(&mut buf))
53         .unwrap_or_else(|e| panic!("read failed: {e}"));
54     assert_eq!(buf, SCRIPT_CONTENTS);
55 
56     // Verify execute.
57     assert_eq!(
58         EXPECTED_STATUS,
59         Command::new(&test_path)
60             .status()
61             .unwrap_or_else(|e| panic!("exec failed: {e}"))
62             .code()
63             .unwrap_or_else(|| panic!("child killed by signal"))
64     );
65 
66     umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
67 }
68 
69 #[test]
test_mount_rdonly_disallows_write()70 fn test_mount_rdonly_disallows_write() {
71     require_capability!("test_mount_rdonly_disallows_write", CAP_SYS_ADMIN);
72     let tempdir = tempfile::tempdir().unwrap();
73 
74     mount(
75         NONE,
76         tempdir.path(),
77         Some(b"tmpfs".as_ref()),
78         MsFlags::MS_RDONLY,
79         NONE,
80     )
81     .unwrap_or_else(|e| panic!("mount failed: {e}"));
82 
83     // EROFS: Read-only file system
84     assert_eq!(
85         EROFS,
86         File::create(tempdir.path().join("test"))
87             .unwrap_err()
88             .raw_os_error()
89             .unwrap()
90     );
91 
92     umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
93 }
94 
95 #[test]
test_mount_noexec_disallows_exec()96 fn test_mount_noexec_disallows_exec() {
97     require_capability!("test_mount_noexec_disallows_exec", CAP_SYS_ADMIN);
98     let tempdir = tempfile::tempdir().unwrap();
99 
100     mount(
101         NONE,
102         tempdir.path(),
103         Some(b"tmpfs".as_ref()),
104         MsFlags::MS_NOEXEC,
105         NONE,
106     )
107     .unwrap_or_else(|e| panic!("mount failed: {e}"));
108 
109     let test_path = tempdir.path().join("test");
110 
111     fs::OpenOptions::new()
112         .create(true)
113         .write(true)
114         .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
115         .open(&test_path)
116         .and_then(|mut f| f.write(SCRIPT_CONTENTS))
117         .unwrap_or_else(|e| panic!("write failed: {e}"));
118 
119     // Verify that we cannot execute despite a+x permissions being set.
120     let mode = stat::Mode::from_bits_truncate(
121         fs::metadata(&test_path)
122             .map(|md| md.permissions().mode())
123             .unwrap_or_else(|e| panic!("metadata failed: {e}")),
124     );
125 
126     assert!(
127         mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
128         "{:?} did not have execute permissions",
129         &test_path
130     );
131 
132     // EACCES: Permission denied
133     assert_eq!(
134         EACCES,
135         Command::new(&test_path)
136             .status()
137             .unwrap_err()
138             .raw_os_error()
139             .unwrap()
140     );
141 
142     umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
143 }
144 
145 #[test]
test_mount_bind()146 fn test_mount_bind() {
147     require_capability!("test_mount_bind", CAP_SYS_ADMIN);
148     let tempdir = tempfile::tempdir().unwrap();
149     let file_name = "test";
150 
151     {
152         let mount_point = tempfile::tempdir().unwrap();
153 
154         mount(
155             Some(tempdir.path()),
156             mount_point.path(),
157             NONE,
158             MsFlags::MS_BIND,
159             NONE,
160         )
161         .unwrap_or_else(|e| panic!("mount failed: {e}"));
162 
163         fs::OpenOptions::new()
164             .create(true)
165             .write(true)
166             .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
167             .open(mount_point.path().join(file_name))
168             .and_then(|mut f| f.write(SCRIPT_CONTENTS))
169             .unwrap_or_else(|e| panic!("write failed: {e}"));
170 
171         umount(mount_point.path())
172             .unwrap_or_else(|e| panic!("umount failed: {e}"));
173     }
174 
175     // Verify the file written in the mount shows up in source directory, even
176     // after unmounting.
177 
178     let mut buf = Vec::new();
179     File::open(tempdir.path().join(file_name))
180         .and_then(|mut f| f.read_to_end(&mut buf))
181         .unwrap_or_else(|e| panic!("read failed: {e}"));
182     assert_eq!(buf, SCRIPT_CONTENTS);
183 }
184