Open6
Rustで名前空間の検証(unshareコマンドの再実装)
まずはRustのインストール
公式サイトの記載があるようにコマンドを実行するだけ。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
あと追加でやっておくこと
Ubuntuの場合
apt install build-essential clang pkg-config libssl-dev
インストール後、Rust環境の整備
echo 'source $HOME/.cargo/env' >> ~/.bashrc
source $HOME/.cargo/env
cargo install cargo-edit
cargo install cargo-crev
namespaceの確認をしたいため nix crate を使って、疑似unshareを実施
Cargo.toml
[package]
name = "nstest"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
nix = "0.23"
sys-mount = "1.3"
main.rs
use std::ffi::CString;
use anyhow::Result;
use nix::sched::{unshare, CloneFlags};
use nix::sys::wait::waitpid;
use nix::unistd::{execvp, fork, getpid, getuid, ForkResult};
fn main() -> Result<()> {
// Start program
println!("Main PID: {}, User: {}", getpid(), getuid());
// unshare start
unshare(
CloneFlags::CLONE_NEWCGROUP
| CloneFlags::CLONE_NEWIPC
| CloneFlags::CLONE_NEWNET
| CloneFlags::CLONE_NEWNS
| CloneFlags::CLONE_NEWPID
| CloneFlags::CLONE_NEWUSER
| CloneFlags::CLONE_NEWUTS,
)
.expect("failed unshare");
println!("Under unshare PID: {} User: {}", getpid(), getuid());
// fork start (for run command)
match unsafe { fork().expect("failed fork") } {
ForkResult::Parent { child } => {
println!(
"I'm parent. PID: {} User: {} Child: {}",
getpid(),
getuid(),
child
);
// wait child process
waitpid(child, None).expect("failed waitpid");
println!("exit");
}
ForkResult::Child => {
println!("I'm child. PID: {} User: {}", getpid(), getuid());
// Run command
let path = CString::new("/bin/echo").expect("CString::new failed");
let argv =
["echo", "Hello", "World"].map(|s| CString::new(s).expect("CString::new failed"));
execvp(&path, &argv).expect("failed execv");
}
}
Ok(())
}
実行結果
ryoh@dormouse:~/nstest$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/nstest`
Main PID: 343, User: 1000
Under unshare PID: 343 User: 65534
I'm parent. PID: 343 User: 65534 Child: 345
I'm child. PID: 1 User: 65534
Hello World
exit
nix::mount::mount で次のようなコードが通らない
mount(None, "/", None, MsFlags::MS_REC | MsFlags::MS_PRIVATE, None)?;
ryoh@dormouse:~/rushare$ cargo run -- -mnuipCU -- cat /proc/self/mountinfo
Compiling rushare v0.1.0 (/home/ryoh/rushare)
error[E0283]: type annotations needed
--> src/main.rs:123:5
|
123 | mount(None, "/", None, MsFlags::MS_REC | MsFlags::MS_PRIVATE, None)?;
| ^^^^^ cannot infer type for type parameter `P3` declared on the function `mount`
|
= note: cannot satisfy `_: NixPath`
note: required by a bound in `nix::mount::mount`
--> /home/ryoh/.cargo/registry/src/github.com-1ecc6299db9ec823/nix-0.23.1/src/mount/linux.rs:59:71
|
59 | pub fn mount<P1: ?Sized + NixPath, P2: ?Sized + NixPath, P3: ?Sized + NixPath, P4: ?Sized + NixPath>(
| ^^^^^^^ required by this bound in `nix::mount::mount`
help: consider specifying the type arguments in the function call
|
123 | mount::<P1, P2, P3, P4>(Some("none"), "/", None, MsFlags::MS_REC | MsFlags::MS_PRIVATE, None)?;
| ++++++++++++++++++
For more information about this error, try `rustc --explain E0283`.
error: could not compile `rushare` due to previous error
次のサイトより、NixPathの型を指定する必要がありそう。(Someだと型情報があるがNoneの場合は型情報がないため型推論ができない)
次の形に直して、ビルドが通るようになった
mount::<Path, Path, Path, Path>(None, Path::new("/"), None, MsFlags::MS_REC | MsFlags::MS_PRIVATE, None)?;
NixPathみるとstrでもよさそうなので楽するためstrに変更
mount::<str, str, str, str>(None, "/", None, MsFlags::MS_REC | MsFlags::MS_PRIVATE, None)?;
一旦作成したかった機能は実装
// To immutable
let unshare_flags = unshare_flags;
// command building
let path = match opt.prog {
// Case no value
None => env::var("SHELL").unwrap_or("/bin/sh".to_string()),
// Case program value exists
Some(prog) => prog,
};
let path = CString::new(path).expect("CString::new error");
let mut argv: Vec<CString> = opt.args.iter()
.map(|s| CString::new(s.as_str()).expect("CString::new error"))
.collect();
argv.insert(0, path.clone());
// unshare and run command
unsafe { signal(signal::SIGCHLD, signal::SigHandler::SigDfl) }?;
// unshare
unshare(unshare_flags).with_context(|| format!("{}: unshare failed", progname))?;
// fork
if opt.fork {
unsafe{ signal(signal::SIGINT, signal::SigHandler::SigIgn) }?;
unsafe{ signal(signal::SIGTERM, signal::SigHandler::SigIgn) }?;
match unsafe{ fork().with_context(|| format!("{}: fork failed", progname))? } {
ForkResult::Parent { child } => {
let status = waitpid(child, None).with_context(|| format!("{}: waitpid failed", progname))?;
unsafe{ signal(signal::SIGINT, signal::SigHandler::SigDfl) }?;
unsafe{ signal(signal::SIGTERM, signal::SigHandler::SigDfl) }?;
match status {
WaitStatus::Exited(_pid, status) => {
exit(status);
}
WaitStatus::Signaled(pid, signal, _) => {
kill(pid, signal)?;
}
_ => {
eprintln!("child exit failed");
}
}
exit(EXIT_FAILURE);
}
ForkResult::Child => {
println!("forked.");
}
}
}
if opt.mount {
// change propagation
mount::<str, str, str, str>(None, "/", None, MsFlags::MS_PRIVATE | MsFlags::MS_REC, None)
.with_context(|| format!("{}: change propagation failed", progname))?;
}
if opt.mount_proc {
// proc mount
mount::<str, str, str, str>(Some("proc"), "/proc", Some("proc"), MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV | MsFlags::MS_NOATIME, None)
.with_context(|| format!("{}: proc remount failed", progname))?;
}
// execvp
execvp(&path, &argv).with_context(|| format!("{}: execvp failed", progname))?;
nix::sched::unshare の使用法
unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUTS)