| use std::fs::{self, File}; |
| use std::io::{Read, Write}; |
| use std::os::unix::fs::OpenOptionsExt; |
| use std::os::unix::fs::PermissionsExt; |
| use std::process::Command; |
| |
| use libc::{EACCES, EROFS}; |
| |
| use nix::mount::{mount, umount, MsFlags}; |
| use nix::sys::stat::{self, Mode}; |
| |
| use crate::*; |
| |
| static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh |
| exit 23"; |
| |
| const EXPECTED_STATUS: i32 = 23; |
| |
| const NONE: Option<&'static [u8]> = None; |
| |
| #[test] |
| fn test_mount_tmpfs_without_flags_allows_rwx() { |
| require_capability!( |
| "test_mount_tmpfs_without_flags_allows_rwx", |
| CAP_SYS_ADMIN |
| ); |
| 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) |
| .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}")); |
| } |
| |
| #[test] |
| fn test_mount_rdonly_disallows_write() { |
| require_capability!("test_mount_rdonly_disallows_write", CAP_SYS_ADMIN); |
| 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}")); |
| } |
| |
| #[test] |
| fn test_mount_noexec_disallows_exec() { |
| require_capability!("test_mount_noexec_disallows_exec", CAP_SYS_ADMIN); |
| 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}")); |
| } |
| |
| #[test] |
| fn test_mount_bind() { |
| require_capability!("test_mount_bind", CAP_SYS_ADMIN); |
| 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); |
| } |