Skip to content

linux "new" mount API constants, incl. 7.0 additions #5072

@benaryorg

Description

@benaryorg

x86_64-unknown-linux-gnu (and presumably basically all Linux platforms) currently lack the:

  • recently merged Linux 7.0 OPEN_TREE_NAMESPACE in addition to the existing OPEN_TREE_CLONE
  • fsmount(2) and fsconfig(2) related constants (move_mount(2) seems to be fully there)
    • note that the FSCONFIG_* 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 sense

That'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:

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"

The constants defined therein are the ones that should come from libc via the commented out parts of the use directive.

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());
    }
}
# Cargo.toml
[package]
name = "mount-test"
version = "0.1.0"
edition = "2024"

[dependencies]
libc = "^0.2.185"
errno = "^0.3.14"

Footnotes

  1. 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).

  2. No special permissions required, and no data will be written to your disk, it should be entirely ephemeral.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions