summaryrefslogtreecommitdiffstats
path: root/third_party/rust/nix/test/test_mount.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/nix/test/test_mount.rs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/nix/test/test_mount.rs')
-rw-r--r--third_party/rust/nix/test/test_mount.rs267
1 files changed, 267 insertions, 0 deletions
diff --git a/third_party/rust/nix/test/test_mount.rs b/third_party/rust/nix/test/test_mount.rs
new file mode 100644
index 0000000000..5cf00408e8
--- /dev/null
+++ b/third_party/rust/nix/test/test_mount.rs
@@ -0,0 +1,267 @@
+mod common;
+
+// Implementation note: to allow unprivileged users to run it, this test makes
+// use of user and mount namespaces. On systems that allow unprivileged user
+// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run
+// without root.
+
+#[cfg(target_os = "linux")]
+mod test_mount {
+ use std::fs::{self, File};
+ use std::io::{self, Read, Write};
+ use std::os::unix::fs::OpenOptionsExt;
+ use std::os::unix::fs::PermissionsExt;
+ use std::process::{self, Command};
+
+ use libc::{EACCES, EROFS};
+
+ use nix::errno::Errno;
+ use nix::mount::{mount, umount, MsFlags};
+ use nix::sched::{unshare, CloneFlags};
+ use nix::sys::stat::{self, Mode};
+ use nix::unistd::getuid;
+
+ static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh
+exit 23";
+
+ const EXPECTED_STATUS: i32 = 23;
+
+ const NONE: Option<&'static [u8]> = None;
+ #[allow(clippy::bind_instead_of_map)] // False positive
+ pub fn test_mount_tmpfs_without_flags_allows_rwx() {
+ let tempdir = tempfile::tempdir().unwrap();
+
+ mount(
+ NONE,
+ tempdir.path(),
+ Some(b"tmpfs".as_ref()),
+ MsFlags::empty(),
+ NONE,
+ )
+ .unwrap_or_else(|e| panic!("mount failed: {e}"));
+
+ let test_path = tempdir.path().join("test");
+
+ // Verify write.
+ fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
+ .open(&test_path)
+ .or_else(|e| {
+ if Errno::from_i32(e.raw_os_error().unwrap())
+ == Errno::EOVERFLOW
+ {
+ // Skip tests on certain Linux kernels which have a bug
+ // regarding tmpfs in namespaces.
+ // Ubuntu 14.04 and 16.04 are known to be affected; 16.10 is
+ // not. There is no legitimate reason for open(2) to return
+ // EOVERFLOW here.
+ // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087
+ let stderr = io::stderr();
+ let mut handle = stderr.lock();
+ writeln!(
+ handle,
+ "Buggy Linux kernel detected. Skipping test."
+ )
+ .unwrap();
+ process::exit(0);
+ } else {
+ panic!("open failed: {e}");
+ }
+ })
+ .and_then(|mut f| f.write(SCRIPT_CONTENTS))
+ .unwrap_or_else(|e| panic!("write failed: {e}"));
+
+ // Verify read.
+ let mut buf = Vec::new();
+ File::open(&test_path)
+ .and_then(|mut f| f.read_to_end(&mut buf))
+ .unwrap_or_else(|e| panic!("read failed: {e}"));
+ assert_eq!(buf, SCRIPT_CONTENTS);
+
+ // Verify execute.
+ assert_eq!(
+ EXPECTED_STATUS,
+ Command::new(&test_path)
+ .status()
+ .unwrap_or_else(|e| panic!("exec failed: {e}"))
+ .code()
+ .unwrap_or_else(|| panic!("child killed by signal"))
+ );
+
+ umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
+ }
+
+ pub fn test_mount_rdonly_disallows_write() {
+ let tempdir = tempfile::tempdir().unwrap();
+
+ mount(
+ NONE,
+ tempdir.path(),
+ Some(b"tmpfs".as_ref()),
+ MsFlags::MS_RDONLY,
+ NONE,
+ )
+ .unwrap_or_else(|e| panic!("mount failed: {e}"));
+
+ // EROFS: Read-only file system
+ assert_eq!(
+ EROFS,
+ File::create(tempdir.path().join("test"))
+ .unwrap_err()
+ .raw_os_error()
+ .unwrap()
+ );
+
+ umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
+ }
+
+ pub fn test_mount_noexec_disallows_exec() {
+ let tempdir = tempfile::tempdir().unwrap();
+
+ mount(
+ NONE,
+ tempdir.path(),
+ Some(b"tmpfs".as_ref()),
+ MsFlags::MS_NOEXEC,
+ NONE,
+ )
+ .unwrap_or_else(|e| panic!("mount failed: {e}"));
+
+ let test_path = tempdir.path().join("test");
+
+ fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
+ .open(&test_path)
+ .and_then(|mut f| f.write(SCRIPT_CONTENTS))
+ .unwrap_or_else(|e| panic!("write failed: {e}"));
+
+ // Verify that we cannot execute despite a+x permissions being set.
+ let mode = stat::Mode::from_bits_truncate(
+ fs::metadata(&test_path)
+ .map(|md| md.permissions().mode())
+ .unwrap_or_else(|e| panic!("metadata failed: {e}")),
+ );
+
+ assert!(
+ mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
+ "{:?} did not have execute permissions",
+ &test_path
+ );
+
+ // EACCES: Permission denied
+ assert_eq!(
+ EACCES,
+ Command::new(&test_path)
+ .status()
+ .unwrap_err()
+ .raw_os_error()
+ .unwrap()
+ );
+
+ umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
+ }
+
+ pub fn test_mount_bind() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let file_name = "test";
+
+ {
+ let mount_point = tempfile::tempdir().unwrap();
+
+ mount(
+ Some(tempdir.path()),
+ mount_point.path(),
+ NONE,
+ MsFlags::MS_BIND,
+ NONE,
+ )
+ .unwrap_or_else(|e| panic!("mount failed: {e}"));
+
+ fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
+ .open(mount_point.path().join(file_name))
+ .and_then(|mut f| f.write(SCRIPT_CONTENTS))
+ .unwrap_or_else(|e| panic!("write failed: {e}"));
+
+ umount(mount_point.path())
+ .unwrap_or_else(|e| panic!("umount failed: {e}"));
+ }
+
+ // Verify the file written in the mount shows up in source directory, even
+ // after unmounting.
+
+ let mut buf = Vec::new();
+ File::open(tempdir.path().join(file_name))
+ .and_then(|mut f| f.read_to_end(&mut buf))
+ .unwrap_or_else(|e| panic!("read failed: {e}"));
+ assert_eq!(buf, SCRIPT_CONTENTS);
+ }
+
+ pub fn setup_namespaces() {
+ // Hold on to the uid in the parent namespace.
+ let uid = getuid();
+
+ unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| {
+ let stderr = io::stderr();
+ let mut handle = stderr.lock();
+ writeln!(handle,
+ "unshare failed: {e}. Are unprivileged user namespaces available?").unwrap();
+ writeln!(handle, "mount is not being tested").unwrap();
+ // Exit with success because not all systems support unprivileged user namespaces, and
+ // that's not what we're testing for.
+ process::exit(0);
+ });
+
+ // Map user as uid 1000.
+ fs::OpenOptions::new()
+ .write(true)
+ .open("/proc/self/uid_map")
+ .and_then(|mut f| f.write(format!("1000 {uid} 1\n").as_bytes()))
+ .unwrap_or_else(|e| panic!("could not write uid map: {e}"));
+ }
+}
+
+// Test runner
+
+/// Mimic normal test output (hackishly).
+#[cfg(target_os = "linux")]
+macro_rules! run_tests {
+ ( $($test_fn:ident),* ) => {{
+ println!();
+
+ $(
+ print!("test test_mount::{} ... ", stringify!($test_fn));
+ $test_fn();
+ println!("ok");
+ )*
+
+ println!();
+ }}
+}
+
+#[cfg(target_os = "linux")]
+fn main() {
+ use test_mount::{
+ setup_namespaces, test_mount_bind, test_mount_noexec_disallows_exec,
+ test_mount_rdonly_disallows_write,
+ test_mount_tmpfs_without_flags_allows_rwx,
+ };
+ skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1351");
+ setup_namespaces();
+
+ run_tests!(
+ test_mount_tmpfs_without_flags_allows_rwx,
+ test_mount_rdonly_disallows_write,
+ test_mount_noexec_disallows_exec,
+ test_mount_bind
+ );
+}
+
+#[cfg(not(target_os = "linux"))]
+fn main() {}