Skip to content

Commit 356ad90

Browse files
committed
Bring back mount
Fixes #85
1 parent 3537f21 commit 356ad90

File tree

3 files changed

+210
-14
lines changed

3 files changed

+210
-14
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@ name = "test-signalfd"
4040
path = "test/test_signalfd.rs"
4141
harness = false
4242
test = true
43+
44+
[[test]]
45+
name = "test-mount"
46+
path = "test/test_mount.rs"
47+
harness = false

src/mount.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,31 +49,25 @@ bitflags!(
4949
);
5050

5151
mod ffi {
52-
use libc::{c_char, c_int};
52+
use libc::{c_char, c_int, c_ulong, c_void};
5353

5454
extern {
55-
/*
56-
* TODO: Bring back
5755
pub fn mount(
5856
source: *const c_char,
5957
target: *const c_char,
6058
fstype: *const c_char,
6159
flags: c_ulong,
6260
data: *const c_void) -> c_int;
63-
*/
6461

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

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

71-
/*
72-
* TODO: Bring this back with a test
73-
*
7468
pub fn mount<P1: ?Sized + NixPath, P2: ?Sized + NixPath, P3: ?Sized + NixPath, P4: ?Sized + NixPath>(
7569
source: Option<&P1>,
76-
target: P2,
70+
target: &P2,
7771
fstype: Option<&P3>,
7872
flags: MsFlags,
7973
data: Option<&P4>) -> Result<()> {
@@ -85,20 +79,19 @@ pub fn mount<P1: ?Sized + NixPath, P2: ?Sized + NixPath, P3: ?Sized + NixPath, P
8579
fstype.with_nix_path(|fstype| {
8680
data.with_nix_path(|data| {
8781
unsafe {
88-
ffi::mount(source.as_ext_str(),
89-
target.as_ext_str(),
90-
fstype,
82+
ffi::mount(source.as_ptr(),
83+
target.as_ptr(),
84+
fstype.as_ptr(),
9185
flags.bits,
92-
data as *const libc::c_void)
86+
data.as_ptr() as *const libc::c_void)
9387
}
9488
})
9589
})
9690
})
9791
})))));
9892

99-
return from_ffi(res);
93+
from_ffi(res)
10094
}
101-
*/
10295

10396
pub fn umount<P: ?Sized + NixPath>(target: &P) -> Result<()> {
10497
let res = try!(target.with_nix_path(|cstr| {

test/test_mount.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Impelmentation note: to allow unprivileged users to run it, this test makes
2+
// use of user and mount namespaces. On systems that allow unprivileged user
3+
// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run
4+
// without root.
5+
6+
extern crate libc;
7+
extern crate nix;
8+
extern crate tempdir;
9+
10+
use std::fs::{self, File};
11+
use std::io::{Read, Write};
12+
use std::os::unix::fs::OpenOptionsExt;
13+
use std::os::unix::fs::PermissionsExt;
14+
use std::process::Command;
15+
16+
use libc::{EACCES, EROFS};
17+
18+
use nix::mount::{mount, umount, MsFlags, MS_BIND, MS_RDONLY, MS_NOEXEC};
19+
use nix::sched::{unshare, CLONE_NEWNS, CLONE_NEWUSER};
20+
use nix::sys::stat::{self, S_IRWXU, S_IRWXG, S_IRWXO, S_IXUSR, S_IXGRP, S_IXOTH};
21+
22+
use tempdir::TempDir;
23+
24+
static SCRIPT_CONTENTS: &'static [u8] = b"#!/bin/sh
25+
exit 23";
26+
27+
const EXPECTED_STATUS: i32 = 23;
28+
29+
const NONE: Option<&'static [u8]> = None;
30+
31+
fn test_mount_tmpfs_without_flags_allows_rwx() {
32+
let tempdir = TempDir::new("nix-test_mount").expect("tempdir failed");
33+
34+
mount(NONE,
35+
tempdir.path(),
36+
Some(b"tmpfs".as_ref()),
37+
MsFlags::empty(),
38+
NONE)
39+
.expect("mount failed");
40+
41+
let test_path = tempdir.path().join("test");
42+
43+
// Verify write.
44+
fs::OpenOptions::new()
45+
.create(true)
46+
.write(true)
47+
.mode((S_IRWXU | S_IRWXG | S_IRWXO).bits())
48+
.open(&test_path)
49+
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
50+
.expect("write failed");
51+
52+
// Verify read.
53+
let mut buf = Vec::new();
54+
File::open(&test_path).and_then(|mut f| f.read_to_end(&mut buf)).expect("read failed");
55+
assert_eq!(buf, SCRIPT_CONTENTS);
56+
57+
// Verify execute.
58+
assert_eq!(EXPECTED_STATUS,
59+
Command::new(&test_path)
60+
.status()
61+
.expect("exec failed")
62+
.code()
63+
.expect("child killed by signal"));
64+
65+
umount(tempdir.path()).expect("umount failed");
66+
}
67+
68+
fn test_mount_rdonly_disallows_write() {
69+
let tempdir = TempDir::new("nix-test_mount").expect("tempdir failed");
70+
71+
mount(NONE,
72+
tempdir.path(),
73+
Some(b"tmpfs".as_ref()),
74+
MS_RDONLY,
75+
NONE)
76+
.expect("mount failed");
77+
78+
// EROFS: Read-only file system
79+
assert_eq!(EROFS as i32,
80+
File::create(tempdir.path().join("test")).unwrap_err().raw_os_error().unwrap());
81+
82+
umount(tempdir.path()).expect("umount failed");
83+
}
84+
85+
fn test_mount_noexec_disallows_exec() {
86+
let tempdir = TempDir::new("nix-test_mount").expect("tempdir failed");
87+
88+
mount(NONE,
89+
tempdir.path(),
90+
Some(b"tmpfs".as_ref()),
91+
MS_NOEXEC,
92+
NONE)
93+
.expect("mount failed");
94+
95+
let test_path = tempdir.path().join("test");
96+
97+
fs::OpenOptions::new()
98+
.create(true)
99+
.write(true)
100+
.mode((S_IRWXU | S_IRWXG | S_IRWXO).bits())
101+
.open(&test_path)
102+
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
103+
.expect("write failed");
104+
105+
// Verify that we cannot execute despite a+x permissions being set.
106+
let mode = stat::Mode::from_bits_truncate(fs::metadata(&test_path)
107+
.map(|md| md.permissions().mode())
108+
.expect("metadata failed"));
109+
110+
assert!(mode.contains(S_IXUSR | S_IXGRP | S_IXOTH),
111+
"{:?} did not have execute permissions",
112+
&test_path);
113+
114+
// EACCES: Permission denied
115+
assert_eq!(EACCES as i32,
116+
Command::new(&test_path).status().unwrap_err().raw_os_error().unwrap());
117+
118+
umount(tempdir.path()).expect("umount failed");
119+
}
120+
121+
fn test_mount_bind() {
122+
let tempdir = TempDir::new("nix-test_mount").expect("tempdir failed");
123+
let file_name = "test";
124+
125+
{
126+
let mount_point = TempDir::new("nix-test_mount").expect("tempdir failed");
127+
128+
mount(Some(tempdir.path()),
129+
mount_point.path(),
130+
NONE,
131+
MS_BIND,
132+
NONE)
133+
.expect("mount failed");
134+
135+
fs::OpenOptions::new()
136+
.create(true)
137+
.write(true)
138+
.mode((S_IRWXU | S_IRWXG | S_IRWXO).bits())
139+
.open(mount_point.path().join(file_name))
140+
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
141+
.expect("write failed");
142+
143+
umount(mount_point.path()).expect("umount failed");
144+
}
145+
146+
// Verify the file written in the mount shows up in source directory, even
147+
// after unmounting.
148+
149+
let mut buf = Vec::new();
150+
File::open(tempdir.path().join(file_name))
151+
.and_then(|mut f| f.read_to_end(&mut buf))
152+
.expect("read failed");
153+
assert_eq!(buf, SCRIPT_CONTENTS);
154+
}
155+
156+
157+
// Test runner
158+
159+
/// Mimic normal test output (hackishly).
160+
macro_rules! run_tests {
161+
( $($test_fn:ident),* ) => {{
162+
print!("\n");
163+
164+
$(
165+
print!("test test_mount::{} ... ", stringify!($test_fn));
166+
$test_fn();
167+
print!("ok\n");
168+
)*
169+
170+
print!("\n");
171+
}}
172+
}
173+
174+
fn main() {
175+
setup_namespaces();
176+
177+
run_tests!(test_mount_tmpfs_without_flags_allows_rwx,
178+
test_mount_rdonly_disallows_write,
179+
test_mount_noexec_disallows_exec,
180+
test_mount_bind);
181+
}
182+
183+
fn setup_namespaces() {
184+
// Hold on to the uid in the parent namespace.
185+
let uid = unsafe { libc::getuid() };
186+
187+
unshare(CLONE_NEWNS | CLONE_NEWUSER).unwrap_or_else(|e| {
188+
panic!("unshare failed: {}. Are unprivileged user namespaces available?",
189+
e)
190+
});
191+
192+
// Map user as uid 1000.
193+
fs::OpenOptions::new()
194+
.write(true)
195+
.open("/proc/self/uid_map")
196+
.and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes()))
197+
.expect("could not write uid map");
198+
}

0 commit comments

Comments
 (0)