Skip to content

Commit

Permalink
Bring back mount
Browse files Browse the repository at this point in the history
  • Loading branch information
kamalmarhubi committed Jan 7, 2016
1 parent 881d471 commit 129d54a
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 14 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Expand Up @@ -25,6 +25,7 @@ bitflags = "0.3.2"

[dev-dependencies]
rand = "0.3.8"
tempdir = "0.3"

[dev-dependencies.nix-test]
path = "nix-test"
Expand All @@ -39,3 +40,8 @@ name = "test-signalfd"
path = "test/test_signalfd.rs"
harness = false
test = true

[[test]]
name = "test-mount"
path = "test/test_mount.rs"
harness = false
21 changes: 7 additions & 14 deletions src/mount.rs
Expand Up @@ -49,31 +49,25 @@ bitflags!(
);

mod ffi {
use libc::{c_char, c_int};
use libc::{c_char, c_int, c_ulong, c_void};

extern {
/*
* TODO: Bring back
pub fn mount(
source: *const c_char,
target: *const c_char,
fstype: *const c_char,
flags: c_ulong,
data: *const c_void) -> c_int;
*/

pub fn umount(target: *const c_char) -> c_int;

pub fn umount2(target: *const c_char, flags: c_int) -> c_int;
}
}

/*
* TODO: Bring this back with a test
*
pub fn mount<P1: ?Sized + NixPath, P2: ?Sized + NixPath, P3: ?Sized + NixPath, P4: ?Sized + NixPath>(
source: Option<&P1>,
target: P2,
target: &P2,
fstype: Option<&P3>,
flags: MsFlags,
data: Option<&P4>) -> Result<()> {
Expand All @@ -85,20 +79,19 @@ pub fn mount<P1: ?Sized + NixPath, P2: ?Sized + NixPath, P3: ?Sized + NixPath, P
fstype.with_nix_path(|fstype| {
data.with_nix_path(|data| {
unsafe {
ffi::mount(source.as_ext_str(),
target.as_ext_str(),
fstype,
ffi::mount(source.as_ptr(),
target.as_ptr(),
fstype.as_ptr(),
flags.bits,
data as *const libc::c_void)
data.as_ptr() as *const libc::c_void)
}
})
})
})
})))));

return from_ffi(res);
from_ffi(res)
}
*/

pub fn umount<P: ?Sized + NixPath>(target: &P) -> Result<()> {
let res = try!(target.with_nix_path(|cstr| {
Expand Down
196 changes: 196 additions & 0 deletions test/test_mount.rs
@@ -0,0 +1,196 @@
// 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.

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").expect("tempdir failed");

mount(NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::empty(),
NONE)
.expect("mount failed");

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))
.expect("write failed");

// Verify read.
let mut buf = Vec::new();
File::open(&test_path).and_then(|mut f| f.read_to_end(&mut buf)).expect("read failed");
assert_eq!(buf, SCRIPT_CONTENTS);

// Verify execute.
assert_eq!(EXPECTED_STATUS,
Command::new(&test_path)
.status()
.expect("exec failed")
.code()
.expect("child killed by signal"));

umount(tempdir.path()).expect("umount failed");
}

fn test_mount_rdonly_disallows_write() {
let tempdir = TempDir::new("nix-test_mount").expect("tempdir failed");

mount(NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MS_RDONLY,
NONE)
.expect("mount failed");

// 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()).expect("umount failed");
}

fn test_mount_noexec_disallows_exec() {
let tempdir = TempDir::new("nix-test_mount").expect("tempdir failed");

mount(NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MS_NOEXEC,
NONE)
.expect("mount failed");

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))
.expect("write failed");

// 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())
.expect("metadata failed"));

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()).expect("umount failed");
}

fn test_mount_bind() {
let tempdir = TempDir::new("nix-test_mount").expect("tempdir failed");
let file_name = "test";

{
let mount_point = TempDir::new("nix-test_mount").expect("tempdir failed");

mount(Some(tempdir.path()),
mount_point.path(),
NONE,
MS_BIND,
NONE)
.expect("mount failed");

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))
.expect("write failed");

umount(mount_point.path()).expect("umount failed");
}

// 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))
.expect("read failed");
assert_eq!(buf, SCRIPT_CONTENTS);

}


// Test runner
//

/// Mimic normal test output (hackishly).
macro_rules! run_test {
($test_fn:ident) => {{
print!("test test_mount::{} ... ", stringify!($test_fn));
$test_fn();
println!("ok");
}}
}

fn main() {
setup_namespaces();

println!("");
run_test!(test_mount_tmpfs_without_flags_allows_rwx);
run_test!(test_mount_rdonly_disallows_write);
run_test!(test_mount_noexec_disallows_exec);
run_test!(test_mount_bind);
println!("");
}

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()))
.expect("could not write uid map");
}

0 comments on commit 129d54a

Please sign in to comment.