forked from nix-rust/nix
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
3 changed files
with
221 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
// Impelmentation 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")] | ||
|
||
extern crate libc; | ||
extern crate nix; | ||
extern crate tempdir; | ||
|
||
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, MS_BIND, MS_RDONLY, MS_NOEXEC}; | ||
use nix::sched::{unshare, CLONE_NEWNS, CLONE_NEWUSER}; | ||
use nix::sys::stat::{self, S_IRWXU, S_IRWXG, S_IRWXO, S_IXUSR, S_IXGRP, S_IXOTH}; | ||
|
||
use tempdir::TempDir; | ||
|
||
static SCRIPT_CONTENTS: &'static [u8] = b"#!/bin/sh | ||
exit 23"; | ||
|
||
const EXPECTED_STATUS: i32 = 23; | ||
|
||
const NONE: Option<&'static [u8]> = None; | ||
|
||
fn test_mount_tmpfs_without_flags_allows_rwx() { | ||
let tempdir = TempDir::new("nix-test_mount") | ||
.unwrap_or_else(|e| panic!("tempdir failed: {}", e)); | ||
|
||
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((S_IRWXU | S_IRWXG | 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)); | ||
} | ||
|
||
fn test_mount_rdonly_disallows_write() { | ||
let tempdir = TempDir::new("nix-test_mount") | ||
.unwrap_or_else(|e| panic!("tempdir failed: {}", e)); | ||
|
||
mount(NONE, | ||
tempdir.path(), | ||
Some(b"tmpfs".as_ref()), | ||
MS_RDONLY, | ||
NONE) | ||
.unwrap_or_else(|e| panic!("mount failed: {}", e)); | ||
|
||
// EROFS: Read-only file system | ||
assert_eq!(EROFS as i32, | ||
File::create(tempdir.path().join("test")).unwrap_err().raw_os_error().unwrap()); | ||
|
||
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e)); | ||
} | ||
|
||
fn test_mount_noexec_disallows_exec() { | ||
let tempdir = TempDir::new("nix-test_mount") | ||
.unwrap_or_else(|e| panic!("tempdir failed: {}", e)); | ||
|
||
mount(NONE, | ||
tempdir.path(), | ||
Some(b"tmpfs".as_ref()), | ||
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((S_IRWXU | S_IRWXG | 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(S_IXUSR | S_IXGRP | S_IXOTH), | ||
"{:?} did not have execute permissions", | ||
&test_path); | ||
|
||
// EACCES: Permission denied | ||
assert_eq!(EACCES as i32, | ||
Command::new(&test_path).status().unwrap_err().raw_os_error().unwrap()); | ||
|
||
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e)); | ||
} | ||
|
||
fn test_mount_bind() { | ||
let tempdir = TempDir::new("nix-test_mount") | ||
.unwrap_or_else(|e| panic!("tempdir failed: {}", e)); | ||
let file_name = "test"; | ||
|
||
{ | ||
let mount_point = TempDir::new("nix-test_mount") | ||
.unwrap_or_else(|e| panic!("tempdir failed: {}", e)); | ||
|
||
mount(Some(tempdir.path()), | ||
mount_point.path(), | ||
NONE, | ||
MS_BIND, | ||
NONE) | ||
.unwrap_or_else(|e| panic!("mount failed: {}", e)); | ||
|
||
fs::OpenOptions::new() | ||
.create(true) | ||
.write(true) | ||
.mode((S_IRWXU | S_IRWXG | 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); | ||
} | ||
|
||
|
||
// Test runner | ||
|
||
/// Mimic normal test output (hackishly). | ||
macro_rules! run_tests { | ||
( $($test_fn:ident),* ) => {{ | ||
print!("\n"); | ||
|
||
$( | ||
print!("test test_mount::{} ... ", stringify!($test_fn)); | ||
$test_fn(); | ||
print!("ok\n"); | ||
)* | ||
|
||
print!("\n"); | ||
}} | ||
} | ||
|
||
fn main() { | ||
setup_namespaces(); | ||
|
||
run_tests!(test_mount_tmpfs_without_flags_allows_rwx, | ||
test_mount_rdonly_disallows_write, | ||
test_mount_noexec_disallows_exec, | ||
test_mount_bind); | ||
} | ||
|
||
fn setup_namespaces() { | ||
// Hold on to the uid in the parent namespace. | ||
let uid = unsafe { libc::getuid() }; | ||
|
||
unshare(CLONE_NEWNS | CLONE_NEWUSER).unwrap_or_else(|e| { | ||
panic!("unshare failed: {}. Are unprivileged user namespaces available?", | ||
e) | ||
}); | ||
|
||
// Map user as uid 1000. | ||
fs::OpenOptions::new() | ||
.write(true) | ||
.open("/proc/self/uid_map") | ||
.and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes())) | ||
.unwrap_or_else(|e| panic!("could not write uid map: {}", e)); | ||
} |