1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use rustix::{
7 mount::{self, MountFlags, MountPropagationFlags},
8 process,
9 thread::UnshareFlags,
10};
11use std::{
12 env,
13 ffi::CString,
14 fs, io,
15 os::unix::process::CommandExt,
16 process::{Command, ExitCode},
17};
18
19#[derive(Parser)]
20#[command(name = "unshare", about = "Run a program in new namespaces")]
21pub struct Args {
22 #[arg(short = 'm', long)]
24 mount: bool,
25
26 #[arg(short = 'u', long)]
28 uts: bool,
29
30 #[arg(short = 'i', long)]
32 ipc: bool,
33
34 #[arg(short = 'n', long)]
36 net: bool,
37
38 #[arg(short = 'p', long)]
40 pid: bool,
41
42 #[arg(short = 'U', long)]
44 user: bool,
45
46 #[arg(short = 'C', long)]
48 cgroup: bool,
49
50 #[arg(short = 'T', long)]
52 time: bool,
53
54 #[arg(short = 'f', long)]
56 fork: bool,
57
58 #[arg(short = 'r', long = "map-root-user")]
60 map_root_user: bool,
61
62 #[arg(short = 'c', long = "map-current-user")]
64 map_current_user: bool,
65
66 #[arg(long = "mount-proc", num_args = 0..=1, default_missing_value = "/proc")]
68 mount_proc: Option<String>,
69
70 #[arg(long, default_value = "private")]
72 propagation: String,
73
74 #[arg(short = 'R', long = "root")]
76 root_dir: Option<String>,
77
78 #[arg(short = 'w', long = "wd")]
80 work_dir: Option<String>,
81
82 #[arg(short = 'S', long = "setuid")]
84 set_uid: Option<u32>,
85
86 #[arg(short = 'G', long = "setgid")]
88 set_gid: Option<u32>,
89
90 #[arg(long = "setgroups")]
92 setgroups: Option<String>,
93
94 #[arg(trailing_var_arg = true)]
96 command: Vec<String>,
97}
98
99fn build_unshare_flags(args: &Args) -> UnshareFlags {
100 let mut flags = UnshareFlags::empty();
101 if args.mount || args.mount_proc.is_some() {
102 flags |= UnshareFlags::NEWNS;
103 }
104 if args.uts {
105 flags |= UnshareFlags::NEWUTS;
106 }
107 if args.ipc {
108 flags |= UnshareFlags::NEWIPC;
109 }
110 if args.net {
111 flags |= UnshareFlags::NEWNET;
112 }
113 if args.pid {
114 flags |= UnshareFlags::NEWPID;
115 }
116 if args.user || args.map_root_user || args.map_current_user {
117 flags |= UnshareFlags::NEWUSER;
118 }
119 if args.cgroup {
120 flags |= UnshareFlags::NEWCGROUP;
121 }
122 if args.time {
123 flags |= UnshareFlags::NEWTIME;
124 }
125 flags
126}
127
128fn map_user(
129 target_uid: u32,
130 target_gid: u32,
131 real_uid: u32,
132 real_gid: u32,
133 deny_setgroups: bool,
134) -> io::Result<()> {
135 if deny_setgroups {
136 fs::write("/proc/self/setgroups", "deny")?;
137 }
138
139 fs::write("/proc/self/uid_map", format!("{target_uid} {real_uid} 1\n"))?;
140 fs::write("/proc/self/gid_map", format!("{target_gid} {real_gid} 1\n"))?;
141
142 Ok(())
143}
144
145fn set_propagation(prop: &str) -> io::Result<()> {
146 let flags = match prop {
147 "private" => {
148 MountPropagationFlags::PRIVATE | MountPropagationFlags::REC
149 }
150 "shared" => MountPropagationFlags::SHARED | MountPropagationFlags::REC,
151 "slave" => {
152 MountPropagationFlags::DOWNSTREAM | MountPropagationFlags::REC
153 }
154 "unchanged" => return Ok(()),
155 other => {
156 return Err(io::Error::new(
157 io::ErrorKind::InvalidInput,
158 format!("unknown propagation type: {other}"),
159 ));
160 }
161 };
162 mount::mount_change("/", flags).map_err(io::Error::from)
163}
164
165fn do_mount_proc(target: &str) -> io::Result<()> {
166 let target_c = CString::new(target)
167 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
168 let proc_c = c"proc";
169 mount::mount(proc_c, &*target_c, proc_c, MountFlags::empty(), None)
170 .map_err(io::Error::from)
171}
172
173fn do_setuid(uid: u32) -> io::Result<()> {
174 if unsafe { libc::setuid(uid) } != 0 {
175 Err(io::Error::last_os_error())
176 } else {
177 Ok(())
178 }
179}
180
181fn do_setgid(gid: u32) -> io::Result<()> {
182 if unsafe { libc::setgid(gid) } != 0 {
183 Err(io::Error::last_os_error())
184 } else {
185 Ok(())
186 }
187}
188
189fn get_shell() -> String {
190 env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string())
191}
192
193fn child_setup(
194 root_dir: &Option<String>,
195 work_dir: &Option<String>,
196 mount_proc: &Option<String>,
197 set_gid: Option<u32>,
198 set_uid: Option<u32>,
199) -> io::Result<()> {
200 if let Some(dir) = root_dir {
201 std::os::unix::fs::chroot(dir)?;
202 env::set_current_dir("/")?;
203 }
204 if let Some(dir) = work_dir {
205 env::set_current_dir(dir)?;
206 }
207 if let Some(target) = mount_proc {
208 do_mount_proc(target)?;
209 }
210 if let Some(gid) = set_gid {
211 do_setgid(gid)?;
212 }
213 if let Some(uid) = set_uid {
214 do_setuid(uid)?;
215 }
216 Ok(())
217}
218
219pub fn run(args: Args) -> ExitCode {
220 let flags = build_unshare_flags(&args);
221
222 if flags.is_empty() {
223 eprintln!("unshare: no namespaces specified");
224 return ExitCode::FAILURE;
225 }
226
227 let real_uid = process::getuid().as_raw();
230 let real_gid = process::getgid().as_raw();
231
232 if let Err(e) = unsafe { rustix::thread::unshare_unsafe(flags) } {
235 eprintln!("unshare: unshare failed: {}", io::Error::from(e));
236 return ExitCode::FAILURE;
237 }
238
239 if args.map_root_user {
241 if let Err(e) = map_user(
242 0,
243 0,
244 real_uid,
245 real_gid,
246 args.setgroups.as_deref() != Some("allow"),
247 ) {
248 eprintln!("unshare: failed to map root user: {e}");
249 return ExitCode::FAILURE;
250 }
251 } else if args.map_current_user {
252 if let Err(e) = map_user(
253 real_uid,
254 real_gid,
255 real_uid,
256 real_gid,
257 args.setgroups.as_deref() != Some("allow"),
258 ) {
259 eprintln!("unshare: failed to map current user: {e}");
260 return ExitCode::FAILURE;
261 }
262 } else if let Some(ref val) = args.setgroups
263 && flags.contains(UnshareFlags::NEWUSER)
264 && let Err(e) = fs::write("/proc/self/setgroups", val)
265 {
266 eprintln!("unshare: failed to set setgroups: {e}");
267 return ExitCode::FAILURE;
268 }
269
270 if flags.contains(UnshareFlags::NEWNS)
272 && args.propagation != "unchanged"
273 && let Err(e) = set_propagation(&args.propagation)
274 {
275 eprintln!("unshare: failed to set propagation: {e}");
276 return ExitCode::FAILURE;
277 }
278
279 let program = if args.command.is_empty() {
280 get_shell()
281 } else {
282 args.command[0].clone()
283 };
284 let program_args: Vec<&str> = if args.command.len() > 1 {
285 args.command[1..].iter().map(|s| s.as_str()).collect()
286 } else {
287 vec![]
288 };
289
290 if args.fork || args.pid {
291 let root_dir = args.root_dir.clone();
292 let work_dir = args.work_dir.clone();
293 let mount_proc = args.mount_proc.clone();
294 let set_uid = args.set_uid;
295 let set_gid = args.set_gid;
296
297 let status = unsafe {
298 Command::new(&program)
299 .args(&program_args)
300 .pre_exec(move || {
301 child_setup(
302 &root_dir,
303 &work_dir,
304 &mount_proc,
305 set_gid,
306 set_uid,
307 )
308 })
309 .status()
310 };
311
312 match status {
313 Ok(s) => ExitCode::from(s.code().unwrap_or(1) as u8),
314 Err(e) => {
315 eprintln!("unshare: failed to execute {program}: {e}");
316 ExitCode::FAILURE
317 }
318 }
319 } else {
320 if let Err(e) = child_setup(
321 &args.root_dir,
322 &args.work_dir,
323 &args.mount_proc,
324 args.set_gid,
325 args.set_uid,
326 ) {
327 eprintln!("unshare: setup failed: {e}");
328 return ExitCode::FAILURE;
329 }
330
331 let err = Command::new(&program).args(&program_args).exec();
332 eprintln!("unshare: failed to execute {program}: {err}");
333 ExitCode::FAILURE
334 }
335}