unsharing namespaces
mapping user to root
initialising tmpfs
creating tmpfs
getting tmpfs fd
mkdir test
creating new mount namespace
moving into mount namespace
reading directory:
/: "test"
use ::{
errno::errno,
libc::{
AT_EMPTY_PATH,
CLONE_NEWNS,
//FSCONFIG_CMD_CREATE,
//FSOPEN_CLOEXEC,
//OPEN_TREE_CLONE,
CLONE_NEWUSER,
FD_CLOEXEC,
SYS_fsconfig,
SYS_fsmount,
SYS_fsopen,
SYS_getegid,
SYS_geteuid,
SYS_mkdirat,
SYS_open_tree,
SYS_setns,
SYS_unshare,
c_int,
syscall,
},
std::{
fs::{self, File},
io::Write,
},
};
const FSOPEN_CLOEXEC: c_int = FD_CLOEXEC;
const FSCONFIG_CMD_CREATE: c_int = 0x06;
const FSMOUNT_CLOEXEC: c_int = 0x01;
const OPEN_TREE_NAMESPACE: c_int = 0x2;
macro_rules! syscall_result {
($syscall:expr, $($arg:expr),*) => {{
let ret = unsafe { syscall($syscall, $($arg),*) };
if ret < 0 {
Err(errno())
} else {
Ok(ret)
}
}};
}
fn main() {
let gid = syscall_result!(SYS_getegid,).expect("getegid");
let uid = syscall_result!(SYS_geteuid,).expect("geteuid");
eprintln!("unsharing namespaces");
syscall_result!(SYS_unshare, CLONE_NEWUSER | CLONE_NEWNS).expect("unshare");
eprintln!("mapping user to root");
{
let mut setgroups = File::create("/proc/self/setgroups").expect("open setgroups");
setgroups.write_all(b"deny").expect("write setgroups");
let mut gid_file = File::create("/proc/self/gid_map").expect("open gid_map");
gid_file
.write_all(format!("0 {} 1", gid).as_bytes())
.expect("write gid_map");
let mut uid_file = File::create("/proc/self/uid_map").expect("open uid_map");
uid_file
.write_all(format!("0 {} 1", uid).as_bytes())
.expect("write uid_map");
}
eprintln!("initialising tmpfs");
let tmpfs_cfg =
syscall_result!(SYS_fsopen, c"tmpfs".as_ptr(), FSOPEN_CLOEXEC).expect("init tmpfs");
eprintln!("creating tmpfs");
syscall_result!(SYS_fsconfig, tmpfs_cfg, FSCONFIG_CMD_CREATE, 0, 0, 0).expect("creating tmpfs");
eprintln!("getting tmpfs fd");
let tmpfs = syscall_result!(SYS_fsmount, tmpfs_cfg, FSMOUNT_CLOEXEC, 0).expect("tmpfs fd");
eprintln!("mkdir test");
syscall_result!(SYS_mkdirat, tmpfs, c"test".as_ptr(), 0o0750).expect("mkdir");
eprintln!("creating new mount namespace");
let ns = syscall_result!(
SYS_open_tree,
tmpfs,
c"".as_ptr(),
OPEN_TREE_NAMESPACE | AT_EMPTY_PATH
)
.expect("open_tree");
eprintln!("moving into mount namespace");
syscall_result!(SYS_setns, ns, CLONE_NEWNS).expect("setns");
eprintln!("\nreading directory:");
for entry in fs::read_dir("/").expect("read_dir") {
let entry = entry.expect("dir entry");
let ft = entry.file_type().expect("file type");
let sym = match (ft.is_dir(), ft.is_file(), ft.is_symlink()) {
(true, _, _) => '/',
(_, true, _) => '.',
(_, _, true) => '@',
_ => '?',
};
println!("{}: {:?}", sym, entry.file_name());
}
}
x86_64-unknown-linux-gnu(and presumably basically all Linux platforms) currently lack the:OPEN_TREE_NAMESPACEin addition to the existingOPEN_TREE_CLONEFSCONFIG_*ones are currently defined as the fsconfig_command enum in include/linux/mount.h, I'm not sure if it's desirable or feasible to carry that over as an enum or whether flattening it into constants makes more senseThat's all that I came in contact with just now.
I'm not sure whether libc (i.e. the rust crate/this project) aims for complete coverage of the new mount API, but if so, it may be worth correlating an up-to-date version of the man-pages and the constants mentioned therein, or maybe even skimming the headers themselves.
Particularly the fsconfig(2) man-pages contain full examples on how to use which should translate 1:1 into unsafe Rust.
example program
The following program should1 print the following2:
The constants defined therein are the ones that should come from libc via the commented out parts of the
usedirective.Footnotes
On any Linux environment which allows the current user to create namespaces, which in most cases means unprivileged user namespaces, which should be the widespread default nowadays AFAIK, lest you're in some layers of sandboxing already (like the playground). ↩
No special permissions required, and no data will be written to your disk, it should be entirely ephemeral. ↩