1use crate::abi::{Abi, Variant};
24use crate::fs::Fs;
25use crate::fs::canon::{RETRY, Resolved, fd_path, readlinkat};
26use crate::fs::translate::{detranslate_path, guest_cwd, guest_dirfd_path, read_link, translate};
27use crate::note;
28use crate::seccomp::notify::{reply, reply_continue, reply_fd, still_valid};
29use crate::state::{Ctx, View};
30use crate::sys::{Errno, Result, SeccompNotif, check};
31use crate::syscall::direct;
32use crate::syscall::nr::*;
33use std::cell::OnceCell;
34use std::ffi::CString;
35use std::io::Read;
36use std::os::fd::FromRawFd;
37use std::os::unix::ffi::OsStrExt;
38use std::sync::Arc;
39
40const DROPPED: Errno = -1;
42
43const RETRIES: u32 = 3;
46
47const XATTR_MAX: usize = 65536;
49
50const WRITE_INTENT: i64 =
56 (libc::O_WRONLY | libc::O_RDWR | libc::O_CREAT | libc::O_TRUNC | libc::O_APPEND) as i64;
57
58const SYS_FCHMODAT2_HOST: libc::c_long = 452;
60
61const IN_DONT_FOLLOW: u64 = 0x0200_0000;
63
64pub(crate) enum Outcome {
66 Value(Result<i64>),
68 Fd { fd: i32, cloexec: bool },
70 Continue,
72 Replied,
74}
75
76pub(crate) struct Req<'a> {
78 pub ctx: &'a Ctx,
79 pub listener: i32,
80 pub id: u64,
81 pub pid: u32,
82 pub args: [u64; 6],
83 pub raw_nr: i32,
85 pub abi: Abi,
87 pub variant: Variant,
89 view: OnceCell<Arc<View>>,
91}
92
93impl<'a> Req<'a> {
94 pub fn arg(&self, i: usize) -> u64 {
95 self.args[i]
96 }
97
98 pub fn view(&self) -> &View {
100 self.view.get_or_init(|| self.ctx.view(self.pid as i32))
101 }
102
103 pub fn fs(&self) -> &Fs {
105 &self.view().fs
106 }
107
108 fn arg_i64(&self, i: usize) -> i64 {
109 self.args[i] as i64
110 }
111
112 fn dirfd(&self, i: usize) -> i32 {
113 self.args[i] as i32
114 }
115
116 pub fn tgid(&self) -> i32 {
118 self.ctx.tgid(self.pid as i32)
119 }
120
121 pub fn path(&self, i: usize) -> Result<Vec<u8>> {
125 let raw = self.ctx.mem.read_cstr(self.pid, self.arg(i))?;
126 Ok(rewrite_proc_self(&raw, self.pid))
127 }
128
129 pub fn translate(&self, dirfd: i32, path: &[u8], deref_final: bool) -> Result<Resolved> {
131 let resolved = translate(self.fs(), self.pid, dirfd, path, deref_final)?;
132 note!(
133 self.ctx,
134 2,
135 "pid {} {} -> {}",
136 self.pid,
137 String::from_utf8_lossy(path),
138 String::from_utf8_lossy(&resolved.host)
139 );
140 Ok(resolved)
141 }
142
143 pub fn translate_or_dirfd(&self, dirfd: i32, path: &[u8], deref_final: bool) -> Result<Resolved> {
156 if !path.is_empty() {
157 return self.translate(dirfd, path, deref_final);
158 }
159 let guest = guest_dirfd_path(self.fs(), self.pid, dirfd)?;
160 self.translate(libc::AT_FDCWD, &guest, deref_final)
161 }
162
163 pub fn valid(&self) -> Result<()> {
165 if still_valid(self.listener, self.id) {
166 Ok(())
167 } else {
168 Err(DROPPED)
169 }
170 }
171
172 pub fn check_writable(&self, resolved: &Resolved) -> Result<()> {
174 match self.fs().binding_for_guest(&resolved.guest) {
175 Some(b) if b.read_only => Err(libc::EROFS),
176 _ => Ok(()),
177 }
178 }
179
180 pub fn write(&self, addr: u64, data: &[u8]) -> Result<()> {
181 self.ctx.mem.write(self.pid, addr, data)
182 }
183
184 pub fn read(&self, addr: u64, len: usize) -> Result<Vec<u8>> {
185 let mut buf = vec![0u8; len];
186 let n = self.ctx.mem.read(self.pid, addr, &mut buf)?;
187 if n != len {
188 return Err(libc::EFAULT);
189 }
190 Ok(buf)
191 }
192}
193
194pub fn handle(ctx: &Ctx, listener: i32, req: &SeccompNotif) {
202 let Some(abi) = Abi::from_audit(req.data.arch) else {
203 reply(listener, req.id, Err(libc::ENOSYS));
204 return;
205 };
206 let raw_nr = req.data.nr as i64;
207 let hello = match abi {
208 Abi::X86_64 | Abi::Aarch64 => crate::guest_stub::layout::HELLO64,
209 Abi::I386 | Abi::Arm => crate::guest_stub::layout::HELLO32,
210 };
211 if ctx.trap_mode() && raw_nr == hello && req.data.args[0] == crate::guest_stub::layout::HELLO_MARK {
212 direct::hello(ctx, req.pid);
213 reply(listener, req.id, Ok(0));
214 return;
215 }
216 let (nr, variant) = if ctx.no_ptrace && abi.socketcall_nr() == Some(raw_nr) {
217 (SOCKETCALL, Variant::Same)
218 } else {
219 match abi.to_canonical(raw_nr) {
220 Some(x) => x,
221 None => {
222 reply_continue(listener, req.id);
224 return;
225 }
226 }
227 };
228 let r = Req {
229 ctx,
230 listener,
231 id: req.id,
232 pid: req.pid,
233 args: req.data.args,
234 raw_nr: req.data.nr,
235 abi,
236 variant,
237 view: OnceCell::new(),
238 };
239 match dispatch(&r, nr) {
240 Ok(Outcome::Value(v)) => reply(listener, req.id, v),
241 Ok(Outcome::Fd { fd, cloexec }) => reply_fd(listener, req.id, fd, cloexec),
242 Ok(Outcome::Continue) => reply_continue(listener, req.id),
243 Ok(Outcome::Replied) => {}
244 Err(DROPPED) => {}
245 Err(e) => {
246 note!(
247 ctx,
248 2,
249 "pid {} {}: {}",
250 req.pid,
251 name(nr),
252 crate::sys::strerror(e)
253 );
254 reply(listener, req.id, Err(e));
255 }
256 }
257}
258
259fn dispatch(r: &Req, nr: i64) -> Result<Outcome> {
261 if let Some(o) = direct::shim_passthrough(r, nr) {
262 return Ok(o);
263 }
264 if r.ctx.no_ptrace {
265 if nr == SOCKETCALL {
266 return direct::do_socketcall(r);
267 }
268 if let Some(o) = direct::dispatch(r, nr) {
269 return o;
270 }
271 }
272 match nr {
273 OPEN => do_open(
274 r,
275 libc::AT_FDCWD,
276 &r.path(0)?,
277 r.arg(0),
278 r.arg_i64(1),
279 r.arg_i64(2),
280 ),
281 CREAT => {
282 let flags = (libc::O_CREAT | libc::O_WRONLY | libc::O_TRUNC) as i64;
283 do_open(r, libc::AT_FDCWD, &r.path(0)?, r.arg(0), flags, r.arg_i64(1))
284 }
285 OPENAT => do_open(r, r.dirfd(0), &r.path(1)?, r.arg(1), r.arg_i64(2), r.arg_i64(3)),
286 OPENAT2 => do_openat2(r),
287 STAT => do_stat(r, libc::AT_FDCWD, &r.path(0)?, r.arg(1), 0),
288 LSTAT => do_stat(
289 r,
290 libc::AT_FDCWD,
291 &r.path(0)?,
292 r.arg(1),
293 libc::AT_SYMLINK_NOFOLLOW as i64,
294 ),
295 NEWFSTATAT => do_stat(r, r.dirfd(0), &r.path(1)?, r.arg(2), r.arg_i64(3)),
296 STATX => do_statx(r),
297 STATFS => do_statfs(r),
298 ACCESS => do_access(r, libc::AT_FDCWD, &r.path(0)?, r.arg_i64(1), 0),
299 FACCESSAT => do_access(r, r.dirfd(0), &r.path(1)?, r.arg_i64(2), 0),
300 FACCESSAT2 => do_access(r, r.dirfd(0), &r.path(1)?, r.arg_i64(2), r.arg_i64(3)),
301 READLINK => do_readlink(r, libc::AT_FDCWD, &r.path(0)?, r.arg(1), r.arg(2) as usize),
302 READLINKAT => do_readlink(r, r.dirfd(0), &r.path(1)?, r.arg(2), r.arg(3) as usize),
303 GETCWD => do_getcwd(r),
304 UNAME => do_uname(r),
305 GETUID | GETGID | GETEUID | GETEGID | GETRESUID | GETRESGID | SETUID | SETGID | SETREUID
308 | SETREGID | SETRESUID | SETRESGID | SETFSUID | SETFSGID | SETGROUPS | GETGROUPS | CAPSET
309 | FCHOWN
310 if !r.view().fake_root =>
311 {
312 Ok(Outcome::Continue)
313 }
314 GETUID | GETGID | GETEUID | GETEGID | GETRESUID | GETRESGID | SETUID | SETGID | SETREUID
315 | SETREGID | SETRESUID | SETRESGID | SETFSUID | SETFSGID => do_identity(r, nr),
316 SETGROUPS => do_setgroups(r),
317 GETGROUPS => do_getgroups(r),
318 CAPSET => Ok(Outcome::Value(Ok(0))),
319 FCHOWN => do_fchown(r),
320 MKDIR => do_mkdir(r, libc::AT_FDCWD, &r.path(0)?, r.arg_i64(1)),
321 MKDIRAT => do_mkdir(r, r.dirfd(0), &r.path(1)?, r.arg_i64(2)),
322 RMDIR => do_unlink(r, libc::AT_FDCWD, &r.path(0)?, libc::AT_REMOVEDIR as i64),
323 UNLINK => do_unlink(r, libc::AT_FDCWD, &r.path(0)?, 0),
324 UNLINKAT => do_unlink(r, r.dirfd(0), &r.path(1)?, r.arg_i64(2)),
325 RENAME => do_rename(r, libc::AT_FDCWD, &r.path(0)?, libc::AT_FDCWD, &r.path(1)?, 0),
326 RENAMEAT => do_rename(r, r.dirfd(0), &r.path(1)?, r.dirfd(2), &r.path(3)?, 0),
327 RENAMEAT2 => do_rename(r, r.dirfd(0), &r.path(1)?, r.dirfd(2), &r.path(3)?, r.arg_i64(4)),
328 LINK => do_link(r, libc::AT_FDCWD, &r.path(0)?, libc::AT_FDCWD, &r.path(1)?, 0),
329 LINKAT => do_link(r, r.dirfd(0), &r.path(1)?, r.dirfd(2), &r.path(3)?, r.arg_i64(4)),
330 SYMLINK => do_symlink(r, &r.path(0)?, libc::AT_FDCWD, &r.path(1)?),
331 SYMLINKAT => do_symlink(r, &r.path(0)?, r.dirfd(1), &r.path(2)?),
332 CHMOD => do_chmod(r, libc::AT_FDCWD, &r.path(0)?, r.arg_i64(1), 0),
333 FCHMODAT => do_chmod(r, r.dirfd(0), &r.path(1)?, r.arg_i64(2), 0),
334 FCHMODAT2 => do_chmod(r, r.dirfd(0), &r.path(1)?, r.arg_i64(2), r.arg_i64(3)),
335 CHOWN => do_chown(r, libc::AT_FDCWD, &r.path(0)?, r.arg_i64(1), r.arg_i64(2), 0),
336 LCHOWN => {
337 let flags = libc::AT_SYMLINK_NOFOLLOW as i64;
338 do_chown(r, libc::AT_FDCWD, &r.path(0)?, r.arg_i64(1), r.arg_i64(2), flags)
339 }
340 FCHOWNAT => do_chown(
341 r,
342 r.dirfd(0),
343 &r.path(1)?,
344 r.arg_i64(2),
345 r.arg_i64(3),
346 r.arg_i64(4),
347 ),
348 TRUNCATE => do_truncate(r),
349 MKNOD => do_mknod(r, libc::AT_FDCWD, &r.path(0)?, r.arg_i64(1), r.arg_i64(2)),
350 MKNODAT => do_mknod(r, r.dirfd(0), &r.path(1)?, r.arg_i64(2), r.arg_i64(3)),
351 UTIME => do_utime(r),
352 UTIMES => do_utimes(r, libc::AT_FDCWD, 0, 1),
353 FUTIMESAT => do_utimes(r, r.dirfd(0), 1, 2),
354 UTIMENSAT => do_utimensat(r),
355 SETXATTR | LSETXATTR => do_setxattr(r, nr == LSETXATTR),
356 GETXATTR | LGETXATTR => do_getxattr(r, nr == LGETXATTR),
357 LISTXATTR | LLISTXATTR => do_listxattr(r, nr == LLISTXATTR),
358 REMOVEXATTR | LREMOVEXATTR => do_removexattr(r, nr == LREMOVEXATTR),
359 INOTIFY_ADD_WATCH => do_inotify_add_watch(r),
360 CLONE3 => direct::do_clone(r, CLONE3),
361 PRCTL => do_prctl(r),
362 NAME_TO_HANDLE_AT => Ok(Outcome::Value(Err(libc::EOPNOTSUPP))),
363 _ => Ok(Outcome::Continue),
364 }
365}
366
367fn cstr(path: &[u8]) -> Result<CString> {
371 CString::new(path.to_vec()).map_err(|_| libc::EINVAL)
372}
373
374fn rewrite_proc_self(path: &[u8], pid: u32) -> Vec<u8> {
376 let rules: [(&[u8], String); 2] = [
377 (b"/proc/thread-self", format!("/proc/{pid}/task/{pid}")),
378 (b"/proc/self", format!("/proc/{pid}")),
379 ];
380 for (prefix, repl) in rules {
381 if path.starts_with(prefix) && (path.len() == prefix.len() || path[prefix.len()] == b'/') {
382 let mut out = repl.into_bytes();
383 out.extend_from_slice(&path[prefix.len()..]);
384 return out;
385 }
386 }
387 path.to_vec()
388}
389
390fn lexical_clean(path: &[u8]) -> Vec<u8> {
392 let mut parts: Vec<&[u8]> = Vec::new();
393 for comp in path.split(|&c| c == b'/') {
394 match comp {
395 b"" | b"." => {}
396 b".." => {
397 parts.pop();
398 }
399 c => parts.push(c),
400 }
401 }
402 let mut out = Vec::with_capacity(path.len() + 1);
403 for p in &parts {
404 out.push(b'/');
405 out.extend_from_slice(p);
406 }
407 if out.is_empty() {
408 out.push(b'/');
409 }
410 out
411}
412
413fn proc_magic_link(guest: &[u8]) -> Option<(i32, bool)> {
419 let rest = guest.strip_prefix(b"/proc/")?;
420 let mut parts = rest.split(|&c| c == b'/');
421 let pid: i32 = std::str::from_utf8(parts.next()?).ok()?.parse().ok()?;
422 let mut tail: Vec<&[u8]> = parts.collect();
423 if tail.len() >= 3 && tail[0] == b"task" {
424 tail.drain(0..2);
425 }
426 match tail.as_slice() {
427 [b"exe"] => Some((pid, true)),
428 [b"cwd"] | [b"root"] => Some((pid, false)),
429 [b"fd", _] | [b"map_files", _] => Some((pid, false)),
430 _ => None,
431 }
432}
433
434fn retrying<T>(mut f: impl FnMut() -> Result<T>) -> Result<T> {
436 for _ in 0..RETRIES {
437 match f() {
438 Err(RETRY) => continue,
439 r => return r,
440 }
441 }
442 Err(libc::ELOOP)
443}
444
445fn is_fifo(resolved: &Resolved) -> bool {
447 resolved
448 .mode_nofollow()
449 .is_ok_and(|m| m & libc::S_IFMT == libc::S_IFIFO)
450}
451
452fn read_all(fd: i32) -> Result<Vec<u8>> {
454 let file = std::mem::ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(fd) });
455 let mut out = Vec::new();
456 (&*file)
457 .read_to_end(&mut out)
458 .map_err(|e| e.raw_os_error().unwrap_or(libc::EIO))?;
459 Ok(out)
460}
461
462fn nofollow_flag(nofollow: bool) -> i64 {
464 if nofollow {
465 libc::AT_SYMLINK_NOFOLLOW as i64
466 } else {
467 0
468 }
469}
470
471fn timevals_to_timespecs(r: &Req, addr: u64) -> Result<Option<[u8; 32]>> {
475 if addr == 0 {
476 return Ok(None);
477 }
478 let (word, read_word): (usize, fn(&[u8]) -> i64) = match r.variant {
479 Variant::Time32 => (4, |b| i32::from_ne_bytes(b.try_into().unwrap()) as i64),
480 _ => (8, |b| i64::from_ne_bytes(b.try_into().unwrap())),
481 };
482 let tv = r.read(addr, 4 * word)?;
483 let mut ts = [0u8; 32];
484 for i in 0..2 {
485 let sec = read_word(&tv[i * 2 * word..i * 2 * word + word]);
486 let usec = read_word(&tv[i * 2 * word + word..i * 2 * word + 2 * word]);
487 ts[i * 16..i * 16 + 8].copy_from_slice(&sec.to_ne_bytes());
488 ts[i * 16 + 8..i * 16 + 16].copy_from_slice(&(usec * 1000).to_ne_bytes());
489 }
490 Ok(Some(ts))
491}
492
493fn timespecs_from_tracee(r: &Req, addr: u64) -> Result<Option<[u8; 32]>> {
497 if addr == 0 {
498 return Ok(None);
499 }
500 if r.variant != Variant::Timespec32 {
501 return Ok(Some(r.read(addr, 32)?.try_into().map_err(|_| libc::EFAULT)?));
502 }
503 let raw = r.read(addr, 16)?;
504 let mut ts = [0u8; 32];
505 for i in 0..4 {
506 let v = i32::from_ne_bytes(raw[i * 4..i * 4 + 4].try_into().unwrap()) as i64;
507 ts[i * 8..i * 8 + 8].copy_from_slice(&v.to_ne_bytes());
508 }
509 Ok(Some(ts))
510}
511
512fn host_utimensat(r: &Req, resolved: &Resolved, times: Option<&[u8; 32]>, nofollow: bool) -> Result<i64> {
514 r.check_writable(resolved)?;
515 let (dfd, c, nofollow) = resolved.at(nofollow)?;
516 let tp = times.map(|t| t.as_ptr()).unwrap_or(std::ptr::null());
517 check(unsafe { libc::syscall(libc::SYS_utimensat, dfd, c.as_ptr(), tp, nofollow_flag(nofollow)) })
518}
519
520fn nofollow_open_flag(nofollow: bool) -> i64 {
533 if nofollow { libc::O_NOFOLLOW as i64 } else { 0 }
534}
535
536fn follow(flags: i64) -> bool {
538 flags & libc::AT_SYMLINK_NOFOLLOW as i64 == 0
539}
540
541fn targets_fd(path: &[u8], flags: i64) -> bool {
543 path.is_empty() && flags & libc::AT_EMPTY_PATH as i64 != 0
544}
545
546fn do_open(r: &Req, dirfd: i32, path: &[u8], path_addr: u64, flags: i64, mode: i64) -> Result<Outcome> {
553 let flags = r.abi.open_flags_to_host(flags);
554 if flags & libc::O_PATH as i64 != 0 && r.ctx.no_ptrace {
555 return direct::do_open_path(r, dirfd, path, path_addr, flags);
556 }
557 let o_excl_create = flags & libc::O_CREAT as i64 != 0 && flags & libc::O_EXCL as i64 != 0;
558 let deref = flags & libc::O_NOFOLLOW as i64 == 0 && !o_excl_create;
559 let cloexec = flags & libc::O_CLOEXEC as i64 != 0;
560 retrying(|| {
561 let resolved = r.translate(dirfd, path, deref)?;
562 r.valid()?;
563 if flags & WRITE_INTENT != 0 {
564 r.check_writable(&resolved)?;
565 }
566 if resolved.host == path && is_fifo(&resolved) {
567 return Ok(Outcome::Continue);
569 }
570 let (dfd, c, nofollow) = resolved.at(!deref)?;
571 let host_flags =
572 (flags & !(libc::O_NOFOLLOW as i64)) | libc::O_CLOEXEC as i64 | nofollow_open_flag(nofollow);
573 let fd = match check(unsafe { libc::syscall(libc::SYS_openat, dfd, c.as_ptr(), host_flags, mode) }) {
574 Err(libc::ELOOP) if deref && resolved.pinned() => return Err(RETRY),
576 rc => rc? as i32,
577 };
578 if flags & WRITE_INTENT == 0 {
579 match fake_proc_file(r, &resolved.host, fd) {
580 Ok(Some(fake)) => {
581 unsafe { libc::close(fd) };
582 return Ok(Outcome::Fd { fd: fake, cloexec });
583 }
584 Ok(None) => {}
585 Err(e) => {
586 unsafe { libc::close(fd) };
587 return Err(e);
588 }
589 }
590 }
591 Ok(Outcome::Fd { fd, cloexec })
592 })
593}
594
595fn fake_proc_file(r: &Req, host: &[u8], fd: i32) -> Result<Option<i32>> {
604 let Some(rest) = host.strip_prefix(b"/proc/") else {
605 return Ok(None);
606 };
607 let (rest, status) = match (rest.strip_suffix(b"/status"), rest.strip_suffix(b"/cmdline")) {
608 (Some(p), _) => (p, true),
609 (_, Some(p)) => (p, false),
610 _ => return Ok(None),
611 };
612 let is_num = |b: &[u8]| !b.is_empty() && b.iter().all(|c| c.is_ascii_digit());
613 let ok = match rest.splitn(3, |&c| c == b'/').collect::<Vec<_>>().as_slice() {
614 [pid] => is_num(pid),
615 [pid, b"task", tid] => is_num(pid) && is_num(tid),
616 _ => false,
617 };
618 if !ok {
619 return Ok(None);
620 }
621 let text = read_all(fd)?;
622 let fake = if status {
623 Some(fake_proc_status(&text, fake_ids_for_status(r, host))?)
624 } else {
625 fake_proc_cmdline(r.ctx, rest, &text)?
626 };
627 if fake.is_none() {
628 unsafe { libc::lseek(fd, 0, libc::SEEK_SET) };
629 }
630 Ok(fake)
631}
632
633fn fake_proc_status(text: &[u8], fake_root: Option<crate::procs::Ids>) -> Result<i32> {
647 let me = format!("TracerPid:\t{}\n", std::process::id()).into_bytes();
648 let mut out = Vec::with_capacity(text.len());
649 for line in text.split_inclusive(|&c| c == b'\n') {
650 if line == me.as_slice() {
651 out.extend_from_slice(b"TracerPid:\t0\n");
652 } else if let Some(ids) = fake_root
653 && (line.starts_with(b"Uid:") || line.starts_with(b"Gid:"))
654 {
655 let (a, b, c, d) = if line.starts_with(b"Uid:") {
656 (ids.ruid, ids.euid, ids.suid, ids.fsuid)
657 } else {
658 (ids.rgid, ids.egid, ids.sgid, ids.fsgid)
659 };
660 out.extend_from_slice(&line[..4]);
661 out.extend_from_slice(format!("\t{a}\t{b}\t{c}\t{d}\n").as_bytes());
662 } else {
663 out.extend_from_slice(line);
664 }
665 }
666 readonly_memfd("proc-status", &out)
667}
668
669fn fake_proc_cmdline(ctx: &Ctx, rest: &[u8], text: &[u8]) -> Result<Option<i32>> {
696 let args: Vec<&[u8]> = text.split(|&c| c == 0).collect();
697 if ctx.trap_mode() {
698 let pid: i32 = std::str::from_utf8(rest.split(|&c| c == b'/').next().unwrap_or_default())
699 .ok()
700 .and_then(|p| p.parse().ok())
701 .unwrap_or(0);
702 let Some(prefix) = ctx.procs.info(pid).cmdline_prefix.filter(|_| !text.is_empty()) else {
703 return Ok(None);
704 };
705 let out: Vec<u8> = prefix
706 .iter()
707 .map(|a| a.as_slice())
708 .chain(args.iter().skip(1).copied())
709 .collect::<Vec<_>>()
710 .join(&0u8);
711 return Ok(Some(readonly_memfd("proc-cmdline", &out)?));
712 }
713 let alias_dir = crate::exec::alias_dir();
714 let in_alias = args
715 .first()
716 .is_some_and(|a| crate::fs::binding::is_prefix(alias_dir.as_os_str().as_bytes(), a));
717 if !in_alias {
718 return Ok(None);
719 }
720 let tail: Vec<&[u8]> = match args.as_slice() {
721 [alias, ..] if crate::exec::is_loader_alias(alias) => {
722 let Some(rest) = args.get(crate::exec::LOADER_ARGS..) else {
723 return Ok(None);
724 };
725 rest.to_vec()
726 }
727 [_, b"--argv0", argv0, _, rest @ ..] => std::iter::once(*argv0).chain(rest.iter().copied()).collect(),
728 [_, prog, rest @ ..] => std::iter::once(*prog).chain(rest.iter().copied()).collect(),
729 _ => return Ok(None),
730 };
731 let out: Vec<u8> = tail.join(&0u8);
732 Ok(Some(readonly_memfd("proc-cmdline", &out)?))
733}
734
735fn readonly_memfd(name: &str, content: &[u8]) -> Result<i32> {
745 let name = CString::new(name).unwrap();
746 let fd =
747 check(unsafe { libc::syscall(libc::SYS_memfd_create, name.as_ptr(), libc::MFD_CLOEXEC) })? as i32;
748 let mut off = 0;
749 while off < content.len() {
750 let n = unsafe { libc::write(fd, content[off..].as_ptr() as *const _, content.len() - off) };
751 if n <= 0 {
752 unsafe { libc::close(fd) };
753 return Err(crate::sys::errno());
754 }
755 off += n as usize;
756 }
757 let link = CString::new(format!("/proc/self/fd/{fd}")).unwrap();
758 let ro = unsafe { libc::open(link.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
759 unsafe { libc::close(fd) };
760 if ro < 0 {
761 return Err(crate::sys::errno());
762 }
763 Ok(ro)
764}
765
766fn do_openat2(r: &Req) -> Result<Outcome> {
768 let path = r.path(1)?;
769 let size = (r.arg(3) as usize).min(24);
770 let mut how = [0u8; 24];
771 if size > 0 {
772 how[..size].copy_from_slice(&r.read(r.arg(2), size)?);
773 }
774 let flags = r
775 .abi
776 .open_flags_to_host(u64::from_ne_bytes(how[0..8].try_into().unwrap()) as i64);
777 if flags & libc::O_PATH as i64 != 0 {
778 return Err(libc::ENOSYS);
781 }
782 let o_excl_create = flags & libc::O_CREAT as i64 != 0 && flags & libc::O_EXCL as i64 != 0;
783 let deref = flags & libc::O_NOFOLLOW as i64 == 0 && !o_excl_create;
784 retrying(|| {
785 let resolved = r.translate(r.dirfd(0), &path, deref)?;
786 r.valid()?;
787 if flags & WRITE_INTENT != 0 {
788 r.check_writable(&resolved)?;
789 }
790 let (dfd, c, nofollow) = resolved.at(!deref)?;
791 let mut how = how;
792 let host_flags = (flags & !(libc::O_NOFOLLOW as i64)) | nofollow_open_flag(nofollow);
793 how[0..8].copy_from_slice(&(host_flags as u64).to_ne_bytes());
794 if resolved.is_mount_point() {
795 let resolve = u64::from_ne_bytes(how[16..24].try_into().unwrap());
796 let keep = resolve & !(libc::RESOLVE_NO_SYMLINKS | libc::RESOLVE_NO_MAGICLINKS);
797 how[16..24].copy_from_slice(&keep.to_ne_bytes());
798 }
799 let fd = match check(unsafe {
800 libc::syscall(libc::SYS_openat2, dfd, c.as_ptr(), how.as_ptr(), 24usize)
801 }) {
802 Err(libc::ELOOP) if deref && resolved.pinned() => return Err(RETRY),
803 rc => rc? as i32,
804 };
805 Ok(Outcome::Fd {
806 fd,
807 cloexec: flags & libc::O_CLOEXEC as i64 != 0,
808 })
809 })
810}
811
812fn do_stat(r: &Req, dirfd: i32, path: &[u8], buf: u64, flags: i64) -> Result<Outcome> {
817 if targets_fd(path, flags) {
818 return Ok(Outcome::Continue);
819 }
820 let st = retrying(|| {
821 let resolved = r.translate(dirfd, path, follow(flags))?;
822 r.valid()?;
823 let (dfd, c, nofollow) = resolved.at(!follow(flags))?;
824 let mut st: libc::stat = unsafe { std::mem::zeroed() };
825 let host_flags = flags & libc::AT_NO_AUTOMOUNT as i64 | nofollow_flag(nofollow);
826 check(unsafe {
827 libc::syscall(
828 libc::SYS_newfstatat,
829 dfd,
830 c.as_ptr(),
831 &mut st as *mut libc::stat,
832 host_flags,
833 )
834 })?;
835 if follow(flags) && resolved.pinned() && st.st_mode & libc::S_IFMT == libc::S_IFLNK {
836 return Err(RETRY);
837 }
838 Ok(st)
839 })?;
840 let mut st = st;
841 fake_owner(r, &mut st.st_uid, &mut st.st_gid);
842 match r.variant {
843 Variant::Stat64 => r.write(buf, &crate::abi::stat_to_stat64(&st))?,
844 Variant::ArmStat64 => r.write(buf, &crate::abi::stat_to_arm_stat64(&st))?,
845 Variant::OldStat => r.write(buf, &crate::abi::stat_to_old_stat(&st)?)?,
846 _ => r.write(buf, unsafe {
847 std::slice::from_raw_parts(
848 &st as *const libc::stat as *const u8,
849 std::mem::size_of::<libc::stat>(),
850 )
851 })?,
852 }
853 Ok(Outcome::Value(Ok(0)))
854}
855
856fn do_statx(r: &Req) -> Result<Outcome> {
858 const STATX_TYPE: u32 = 0x1;
859 let flags = r.arg_i64(2);
860 if r.arg(1) == 0 {
861 return Ok(Outcome::Continue);
862 }
863 let path = r.path(1)?;
864 if targets_fd(&path, flags) {
865 return Ok(Outcome::Continue);
866 }
867 let mut st = retrying(|| {
868 let resolved = r.translate(r.dirfd(0), &path, follow(flags))?;
869 r.valid()?;
870 let (dfd, c, nofollow) = resolved.at(!follow(flags))?;
871 let mut st = [0u8; 256];
872 let host_flags =
873 flags & !(libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW) as i64 | nofollow_flag(nofollow);
874 check(unsafe {
876 libc::syscall(
877 libc::SYS_statx,
878 dfd,
879 c.as_ptr(),
880 host_flags,
881 r.arg(3) as u32 | STATX_TYPE,
882 st.as_mut_ptr(),
883 )
884 })?;
885 let mode = u16::from_ne_bytes(st[28..30].try_into().unwrap()) as u32;
886 if follow(flags) && resolved.pinned() && mode & libc::S_IFMT == libc::S_IFLNK {
887 return Err(RETRY);
888 }
889 Ok(st)
890 })?;
891 let (mut uid, mut gid) = (
892 u32::from_ne_bytes(st[20..24].try_into().unwrap()),
893 u32::from_ne_bytes(st[24..28].try_into().unwrap()),
894 );
895 fake_owner(r, &mut uid, &mut gid);
896 st[20..24].copy_from_slice(&uid.to_ne_bytes());
897 st[24..28].copy_from_slice(&gid.to_ne_bytes());
898 r.write(r.arg(4), &st)?;
899 Ok(Outcome::Value(Ok(0)))
900}
901
902fn do_statfs(r: &Req) -> Result<Outcome> {
904 let path = r.path(0)?;
905 let st = retrying(|| {
906 let resolved = r.translate(libc::AT_FDCWD, &path, true)?;
907 r.valid()?;
908 let (fd, _) = resolved.object(true)?;
909 let c = fd_path(&fd);
910 let mut st: libc::statfs = unsafe { std::mem::zeroed() };
911 check(unsafe { libc::syscall(libc::SYS_statfs, c.as_ptr(), &mut st as *mut libc::statfs) })?;
912 Ok(st)
913 })?;
914 match r.variant {
915 Variant::Statfs64 => r.write(r.arg(2), &crate::abi::statfs_to_statfs64(&st))?,
917 Variant::OldStatfs => r.write(r.arg(1), &crate::abi::statfs_to_old_statfs(&st))?,
918 _ => r.write(r.arg(1), unsafe {
919 std::slice::from_raw_parts(
920 &st as *const libc::statfs as *const u8,
921 std::mem::size_of::<libc::statfs>(),
922 )
923 })?,
924 }
925 Ok(Outcome::Value(Ok(0)))
926}
927
928fn do_access(r: &Req, dirfd: i32, path: &[u8], mode: i64, flags: i64) -> Result<Outcome> {
930 let resolved = if flags & libc::AT_EMPTY_PATH as i64 != 0 {
931 r.translate_or_dirfd(dirfd, path, follow(flags))?
932 } else {
933 r.translate(dirfd, path, follow(flags))?
934 };
935 r.valid()?;
936 let (dfd, c, nofollow) = resolved.at(!follow(flags))?;
937 let host_flags = flags & libc::AT_EACCESS as i64 | nofollow_flag(nofollow);
938 let rc = check(unsafe { libc::syscall(libc::SYS_faccessat2, dfd, c.as_ptr(), mode, host_flags) });
939 Ok(Outcome::Value(rc))
940}
941
942fn do_readlink(r: &Req, dirfd: i32, path: &[u8], buf: u64, bufsiz: usize) -> Result<Outcome> {
949 let target = match direct::readlink_alias_fd(r, dirfd, path) {
950 Some(target) => target?,
951 None => readlink_by_path(r, dirfd, path)?,
952 };
953 let len = target.len().min(bufsiz);
954 if len > 0 {
955 r.write(buf, &target[..len])?;
956 }
957 Ok(Outcome::Value(Ok(len as i64)))
958}
959
960fn readlink_by_path(r: &Req, dirfd: i32, path: &[u8]) -> Result<Vec<u8>> {
971 let resolved = r.translate_or_dirfd(dirfd, path, false)?;
972 r.valid()?;
973 let read = || -> Result<Vec<u8>> {
974 let target = match &resolved.at {
975 crate::fs::canon::At::Child { parent, name } => readlinkat(parent, name),
976 crate::fs::canon::At::Object(_) => Err(libc::EINVAL),
978 crate::fs::canon::At::Path => read_link(&resolved.host),
979 };
980 target.map_err(|e| {
981 if e == libc::EINVAL && path.is_empty() {
982 libc::ENOENT
983 } else {
984 e
985 }
986 })
987 };
988 let guest = lexical_clean(&resolved.guest);
989 let target = match proc_magic_link(&guest) {
990 Some((pid, true)) => match r.ctx.tracee(pid).exe_guest {
991 Some(exe) => exe,
992 None => detranslate_path(r.fs(), &read()?),
993 },
994 Some((pid, false)) => match r.fs().virtual_cwd(pid).filter(|_| guest.ends_with(b"/cwd")) {
995 Some(cwd) => cwd,
996 None => detranslate_path(r.fs(), &read()?),
997 },
998 None => read()?,
999 };
1000 Ok(target)
1001}
1002
1003fn do_getcwd(r: &Req) -> Result<Outcome> {
1005 let mut cwd = guest_cwd(r.fs(), r.pid)?;
1006 r.valid()?;
1007 cwd.push(0);
1008 if cwd.len() > r.arg(1) as usize {
1009 return Err(libc::ERANGE);
1010 }
1011 r.write(r.arg(0), &cwd)?;
1012 Ok(Outcome::Value(Ok(cwd.len() as i64)))
1013}
1014
1015fn do_uname(r: &Req) -> Result<Outcome> {
1017 let mut u: libc::utsname = unsafe { std::mem::zeroed() };
1018 check(unsafe { libc::uname(&mut u) } as i64)?;
1019 let mut bytes = [0u8; 390];
1020 let src = unsafe { std::slice::from_raw_parts(&u as *const _ as *const u8, 390) };
1021 bytes.copy_from_slice(src);
1022 if let Some(rel) = &r.view().kernel_release {
1023 let n = rel.len().min(64);
1024 bytes[130..195].fill(0);
1025 bytes[130..130 + n].copy_from_slice(&rel[..n]);
1026 }
1027 if let Some(host) = &r.view().hostname {
1028 let n = host.len().min(64);
1030 bytes[65..130].fill(0);
1031 bytes[65..65 + n].copy_from_slice(&host[..n]);
1032 }
1033 r.valid()?;
1034 r.write(r.arg(0), &bytes)?;
1035 Ok(Outcome::Value(Ok(0)))
1036}
1037
1038fn do_prctl(r: &Req) -> Result<Outcome> {
1053 let tgid = r.tgid();
1054 match r.arg(0) as i32 {
1055 libc::PR_SET_DUMPABLE => match r.arg(1) {
1056 v @ (0 | 1) => {
1057 r.ctx.set_dumpable(tgid, v == 1);
1058 Ok(Outcome::Value(Ok(0)))
1059 }
1060 _ => Ok(Outcome::Value(Err(libc::EINVAL))),
1061 },
1062 libc::PR_GET_DUMPABLE => Ok(Outcome::Value(Ok(r.ctx.dumpable(tgid) as i64))),
1063 _ => Ok(Outcome::Continue),
1064 }
1065}
1066
1067fn do_identity(r: &Req, nr: i64) -> Result<Outcome> {
1073 let tgid = r.tgid();
1074 let mut ids = r.ctx.ids(tgid);
1075 let id16 = r.variant == Variant::Id16;
1076 let write_three = |a: u32, b: u32, c: u32| -> Result<()> {
1077 for (i, v) in [a, b, c].into_iter().enumerate() {
1078 if r.arg(i) != 0 {
1079 if id16 {
1080 r.write(r.arg(i), &(v as u16).to_ne_bytes())?
1081 } else {
1082 r.write(r.arg(i), &v.to_ne_bytes())?
1083 }
1084 }
1085 }
1086 Ok(())
1087 };
1088 let arg = |i: usize| r.arg(i) as u32;
1089 let keep = |v: u32| v == u32::MAX;
1090 let allowed = |v: u32, (a, b, c): (u32, u32, u32)| v == a || v == b || v == c;
1091 let uids = (ids.ruid, ids.euid, ids.suid);
1092 let gids = (ids.rgid, ids.egid, ids.sgid);
1093 let root = ids.euid == 0;
1094 let rc: i64 = match nr {
1095 GETUID => ids.ruid as i64,
1096 GETEUID => ids.euid as i64,
1097 GETGID => ids.rgid as i64,
1098 GETEGID => ids.egid as i64,
1099 GETRESUID => {
1100 write_three(ids.ruid, ids.euid, ids.suid)?;
1101 0
1102 }
1103 GETRESGID => {
1104 write_three(ids.rgid, ids.egid, ids.sgid)?;
1105 0
1106 }
1107 SETUID => {
1108 let v = arg(0);
1109 if root {
1110 (ids.ruid, ids.euid, ids.suid) = (v, v, v);
1111 } else if allowed(v, uids) {
1112 ids.euid = v;
1113 } else {
1114 return Err(libc::EPERM);
1115 }
1116 ids.fsuid = ids.euid;
1117 0
1118 }
1119 SETGID => {
1120 let v = arg(0);
1121 if root {
1122 (ids.rgid, ids.egid, ids.sgid) = (v, v, v);
1123 } else if allowed(v, gids) {
1124 ids.egid = v;
1125 } else {
1126 return Err(libc::EPERM);
1127 }
1128 ids.fsgid = ids.egid;
1129 0
1130 }
1131 SETREUID | SETRESUID => {
1132 let (nr_, ne, ns) = if nr == SETREUID {
1133 (arg(0), arg(1), u32::MAX)
1134 } else {
1135 (arg(0), arg(1), arg(2))
1136 };
1137 for v in [nr_, ne, ns] {
1138 if !keep(v) && !root && !allowed(v, uids) {
1139 return Err(libc::EPERM);
1140 }
1141 }
1142 if !keep(nr_) {
1143 ids.ruid = nr_;
1144 }
1145 if !keep(ne) {
1146 ids.euid = ne;
1147 }
1148 if !keep(ns) {
1149 ids.suid = ns;
1150 } else if nr == SETREUID && (!keep(nr_) || (!keep(ne) && ne != uids.0)) {
1151 ids.suid = ids.euid;
1152 }
1153 ids.fsuid = ids.euid;
1154 0
1155 }
1156 SETREGID | SETRESGID => {
1157 let (ng, ne, ns) = if nr == SETREGID {
1158 (arg(0), arg(1), u32::MAX)
1159 } else {
1160 (arg(0), arg(1), arg(2))
1161 };
1162 for v in [ng, ne, ns] {
1163 if !keep(v) && !root && !allowed(v, gids) {
1164 return Err(libc::EPERM);
1165 }
1166 }
1167 if !keep(ng) {
1168 ids.rgid = ng;
1169 }
1170 if !keep(ne) {
1171 ids.egid = ne;
1172 }
1173 if !keep(ns) {
1174 ids.sgid = ns;
1175 } else if nr == SETREGID && (!keep(ng) || (!keep(ne) && ne != gids.0)) {
1176 ids.sgid = ids.egid;
1177 }
1178 ids.fsgid = ids.egid;
1179 0
1180 }
1181 SETFSUID => {
1182 let prev = ids.fsuid;
1183 let v = arg(0);
1184 if root || allowed(v, uids) || v == ids.fsuid {
1185 ids.fsuid = v;
1186 }
1187 prev as i64
1188 }
1189 SETFSGID => {
1190 let prev = ids.fsgid;
1191 let v = arg(0);
1192 if root || allowed(v, gids) || v == ids.fsgid {
1193 ids.fsgid = v;
1194 }
1195 prev as i64
1196 }
1197 _ => return Ok(Outcome::Continue),
1198 };
1199 r.ctx.set_ids(tgid, ids);
1200 Ok(Outcome::Value(Ok(rc)))
1201}
1202
1203fn do_setgroups(r: &Req) -> Result<Outcome> {
1205 let tgid = r.tgid();
1206 if r.ctx.ids(tgid).euid != 0 {
1207 return Err(libc::EPERM);
1208 }
1209 let n = (r.arg(0) as usize).min(65536);
1210 let width = if r.variant == Variant::Id16 { 2 } else { 4 };
1211 let raw = if n > 0 {
1212 r.read(r.arg(1), n * width)?
1213 } else {
1214 Vec::new()
1215 };
1216 let groups: Vec<u32> = raw
1217 .chunks_exact(width)
1218 .map(|c| {
1219 if width == 2 {
1220 u16::from_ne_bytes([c[0], c[1]]) as u32
1221 } else {
1222 u32::from_ne_bytes(c.try_into().unwrap())
1223 }
1224 })
1225 .collect();
1226 r.ctx.set_groups(tgid, groups);
1227 Ok(Outcome::Value(Ok(0)))
1228}
1229
1230fn do_getgroups(r: &Req) -> Result<Outcome> {
1232 let tgid = r.tgid();
1233 let Some(groups) = r.ctx.groups(tgid) else {
1234 return Ok(Outcome::Continue);
1235 };
1236 let size = r.arg(0) as usize;
1237 if size == 0 {
1238 return Ok(Outcome::Value(Ok(groups.len() as i64)));
1239 }
1240 if size < groups.len() {
1241 return Err(libc::EINVAL);
1242 }
1243 let mut out = Vec::with_capacity(groups.len() * 4);
1244 for g in &groups {
1245 if r.variant == Variant::Id16 {
1246 out.extend_from_slice(&(*g as u16).to_ne_bytes())
1247 } else {
1248 out.extend_from_slice(&g.to_ne_bytes())
1249 }
1250 }
1251 if !out.is_empty() {
1252 r.write(r.arg(1), &out)?;
1253 }
1254 Ok(Outcome::Value(Ok(groups.len() as i64)))
1255}
1256
1257fn do_fchown(r: &Req) -> Result<Outcome> {
1259 let c = cstr(format!("/proc/{}/fd/{}", r.pid, r.arg(0) as i32).as_bytes())?;
1260 let rc = check(unsafe {
1261 libc::syscall(
1262 libc::SYS_fchownat,
1263 libc::AT_FDCWD,
1264 c.as_ptr(),
1265 r.arg_i64(1),
1266 r.arg_i64(2),
1267 0,
1268 )
1269 });
1270 Ok(Outcome::Value(forgive_as_root(r, rc)))
1271}
1272
1273fn forgive_as_root(r: &Req, rc: Result<i64>) -> Result<i64> {
1275 match rc {
1276 Err(libc::EPERM) | Err(libc::EACCES) if r.view().fake_root => Ok(0),
1277 other => other,
1278 }
1279}
1280
1281fn fake_owner(r: &Req, uid: &mut u32, gid: &mut u32) {
1283 if r.view().fake_root {
1284 if *uid == unsafe { libc::getuid() } {
1285 *uid = 0;
1286 }
1287 if *gid == unsafe { libc::getgid() } {
1288 *gid = 0;
1289 }
1290 }
1291}
1292
1293fn fake_ids_for_status(r: &Req, host: &[u8]) -> Option<crate::procs::Ids> {
1295 if !r.view().fake_root {
1296 return None;
1297 }
1298 let pid: i32 = host
1299 .strip_prefix(b"/proc/")?
1300 .split(|&c| c == b'/')
1301 .next()
1302 .and_then(|p| std::str::from_utf8(p).ok()?.parse().ok())?;
1303 Some(r.ctx.ids(pid))
1304}
1305
1306fn do_mkdir(r: &Req, dirfd: i32, path: &[u8], mode: i64) -> Result<Outcome> {
1308 let resolved = r.translate(dirfd, path, false)?;
1309 r.valid()?;
1310 r.check_writable(&resolved)?;
1311 let (dfd, c, _) = resolved.at(true)?;
1312 let rc = check(unsafe { libc::syscall(libc::SYS_mkdirat, dfd, c.as_ptr(), mode) });
1313 r.fs().flush_cache();
1314 Ok(Outcome::Value(rc))
1315}
1316
1317fn do_unlink(r: &Req, dirfd: i32, path: &[u8], flags: i64) -> Result<Outcome> {
1320 let resolved = r.translate(dirfd, path, false)?;
1321 r.valid()?;
1322 r.check_writable(&resolved)?;
1323 if resolved.is_mount_point() {
1324 return Err(libc::EBUSY);
1325 }
1326 let (dfd, c, _) = resolved.at(true)?;
1327 let rc = check(unsafe { libc::syscall(libc::SYS_unlinkat, dfd, c.as_ptr(), flags) });
1328 r.fs().flush_cache();
1329 Ok(Outcome::Value(rc))
1330}
1331
1332fn do_rename(r: &Req, olddirfd: i32, old: &[u8], newdirfd: i32, new: &[u8], flags: i64) -> Result<Outcome> {
1335 let from = r.translate(olddirfd, old, false)?;
1336 let to = r.translate(newdirfd, new, false)?;
1337 r.valid()?;
1338 r.check_writable(&from)?;
1339 r.check_writable(&to)?;
1340 if from.is_mount_point() || to.is_mount_point() {
1341 return Err(libc::EBUSY);
1342 }
1343 let (dfo, co, _) = from.at(true)?;
1344 let (dfn, cn, _) = to.at(true)?;
1345 let rc = check(unsafe { libc::syscall(libc::SYS_renameat2, dfo, co.as_ptr(), dfn, cn.as_ptr(), flags) });
1346 r.fs().flush_cache();
1347 Ok(Outcome::Value(rc))
1348}
1349
1350fn do_link(r: &Req, olddirfd: i32, old: &[u8], newdirfd: i32, new: &[u8], flags: i64) -> Result<Outcome> {
1354 let from = if targets_fd(old, flags) {
1355 None
1356 } else {
1357 Some(r.translate(olddirfd, old, flags & libc::AT_SYMLINK_FOLLOW as i64 != 0)?)
1358 };
1359 let to = r.translate(newdirfd, new, false)?;
1360 r.valid()?;
1361 r.check_writable(&to)?;
1362 let (dfo, co, host_flags) = match &from {
1363 None => (
1364 libc::AT_FDCWD,
1365 cstr(format!("/proc/{}/fd/{}", r.pid, olddirfd).as_bytes())?,
1366 libc::AT_SYMLINK_FOLLOW as i64,
1367 ),
1368 Some(from) => {
1369 let (dfd, c, _) = from.at(true)?;
1370 let follow = if from.is_mount_point()
1373 || (flags & libc::AT_SYMLINK_FOLLOW as i64 != 0 && from.is_proc_fd_link())
1374 {
1375 libc::AT_SYMLINK_FOLLOW as i64
1376 } else {
1377 0
1378 };
1379 (dfd, c, follow)
1380 }
1381 };
1382 let (dfn, cn, _) = to.at(true)?;
1383 let rc =
1384 check(unsafe { libc::syscall(libc::SYS_linkat, dfo, co.as_ptr(), dfn, cn.as_ptr(), host_flags) });
1385 r.fs().flush_cache();
1386 Ok(Outcome::Value(rc))
1387}
1388
1389fn do_symlink(r: &Req, target: &[u8], newdirfd: i32, linkpath: &[u8]) -> Result<Outcome> {
1391 let resolved = r.translate(newdirfd, linkpath, false)?;
1392 r.valid()?;
1393 r.check_writable(&resolved)?;
1394 let (dfd, cl, _) = resolved.at(true)?;
1395 let ct = cstr(target)?;
1396 let rc = check(unsafe { libc::syscall(libc::SYS_symlinkat, ct.as_ptr(), dfd, cl.as_ptr()) });
1397 r.fs().flush_cache();
1398 Ok(Outcome::Value(rc))
1399}
1400
1401fn do_chmod(r: &Req, dirfd: i32, path: &[u8], mode: i64, flags: i64) -> Result<Outcome> {
1408 let rc = retrying(|| {
1409 let resolved = if flags & libc::AT_EMPTY_PATH as i64 != 0 {
1410 r.translate_or_dirfd(dirfd, path, follow(flags))?
1411 } else {
1412 r.translate(dirfd, path, follow(flags))?
1413 };
1414 r.valid()?;
1415 r.check_writable(&resolved)?;
1416 let (fd, st_mode) = resolved.object(follow(flags))?;
1417 if st_mode & libc::S_IFMT == libc::S_IFLNK {
1418 return Ok(Err(libc::EOPNOTSUPP));
1419 }
1420 let c = fd_path(&fd);
1421 Ok(check(unsafe {
1422 libc::syscall(SYS_FCHMODAT2_HOST, libc::AT_FDCWD, c.as_ptr(), mode, 0)
1423 }))
1424 })?;
1425 Ok(Outcome::Value(rc))
1426}
1427
1428fn do_chown(r: &Req, dirfd: i32, path: &[u8], uid: i64, gid: i64, flags: i64) -> Result<Outcome> {
1430 if targets_fd(path, flags) {
1431 return Ok(Outcome::Continue);
1432 }
1433 let resolved = r.translate(dirfd, path, follow(flags))?;
1434 r.valid()?;
1435 r.check_writable(&resolved)?;
1436 let (dfd, c, nofollow) = resolved.at(!follow(flags))?;
1437 let rc = check(unsafe {
1438 libc::syscall(
1439 libc::SYS_fchownat,
1440 dfd,
1441 c.as_ptr(),
1442 uid,
1443 gid,
1444 nofollow_flag(nofollow),
1445 )
1446 });
1447 Ok(Outcome::Value(forgive_as_root(r, rc)))
1448}
1449
1450fn do_truncate(r: &Req) -> Result<Outcome> {
1452 let path = r.path(0)?;
1453 let len = match r.variant {
1455 Variant::Truncate64 => (r.arg(1) | (r.arg(2) << 32)) as i64,
1456 Variant::Truncate64Arm => (r.arg(2) | (r.arg(3) << 32)) as i64,
1457 _ => r.arg_i64(1),
1458 };
1459 let rc = retrying(|| {
1460 let resolved = r.translate(libc::AT_FDCWD, &path, true)?;
1461 r.valid()?;
1462 r.check_writable(&resolved)?;
1463 let (fd, _) = resolved.object(true)?;
1464 let c = fd_path(&fd);
1465 Ok(check(unsafe {
1466 libc::syscall(libc::SYS_truncate, c.as_ptr(), len)
1467 }))
1468 })?;
1469 Ok(Outcome::Value(rc))
1470}
1471
1472fn do_mknod(r: &Req, dirfd: i32, path: &[u8], mode: i64, dev: i64) -> Result<Outcome> {
1474 let resolved = r.translate(dirfd, path, false)?;
1475 r.valid()?;
1476 r.check_writable(&resolved)?;
1477 let (dfd, c, _) = resolved.at(true)?;
1478 let rc = check(unsafe { libc::syscall(libc::SYS_mknodat, dfd, c.as_ptr(), mode, dev) });
1479 Ok(Outcome::Value(forgive_as_root(r, rc)))
1480}
1481
1482fn do_utime(r: &Req) -> Result<Outcome> {
1484 let path = r.path(0)?;
1485 let times = if r.arg(1) == 0 {
1486 None
1487 } else {
1488 let mut ts = [0u8; 32];
1489 if r.variant == Variant::Time32 {
1490 let ub = r.read(r.arg(1), 8)?;
1491 for (dst, src) in [(0usize, 0usize), (16, 4)] {
1492 let v = i32::from_ne_bytes(ub[src..src + 4].try_into().unwrap()) as i64;
1493 ts[dst..dst + 8].copy_from_slice(&v.to_ne_bytes());
1494 }
1495 } else {
1496 let ub = r.read(r.arg(1), 16)?;
1497 ts[0..8].copy_from_slice(&ub[0..8]);
1498 ts[16..24].copy_from_slice(&ub[8..16]);
1499 }
1500 Some(ts)
1501 };
1502 let resolved = r.translate(libc::AT_FDCWD, &path, true)?;
1503 r.valid()?;
1504 Ok(Outcome::Value(host_utimensat(
1505 r,
1506 &resolved,
1507 times.as_ref(),
1508 false,
1509 )))
1510}
1511
1512fn do_utimes(r: &Req, dirfd: i32, path_idx: usize, times_idx: usize) -> Result<Outcome> {
1514 let path = r.path(path_idx)?;
1515 let times = timevals_to_timespecs(r, r.arg(times_idx))?;
1516 let resolved = r.translate(dirfd, &path, true)?;
1517 r.valid()?;
1518 Ok(Outcome::Value(host_utimensat(
1519 r,
1520 &resolved,
1521 times.as_ref(),
1522 false,
1523 )))
1524}
1525
1526fn do_utimensat(r: &Req) -> Result<Outcome> {
1528 if r.arg(1) == 0 {
1529 return Ok(Outcome::Continue);
1530 }
1531 let flags = r.arg_i64(3);
1532 let path = r.path(1)?;
1533 if targets_fd(&path, flags) {
1534 return Ok(Outcome::Continue);
1535 }
1536 let times = timespecs_from_tracee(r, r.arg(2))?;
1537 let resolved = r.translate(r.dirfd(0), &path, follow(flags))?;
1538 r.valid()?;
1539 Ok(Outcome::Value(host_utimensat(
1540 r,
1541 &resolved,
1542 times.as_ref(),
1543 !follow(flags),
1544 )))
1545}
1546
1547fn xattr_target(resolved: &Resolved, nofollow: bool) -> Result<(CString, Option<Arc<std::os::fd::OwnedFd>>)> {
1558 if nofollow && !resolved.is_mount_point() {
1559 return Ok((resolved.parent_path()?, None));
1560 }
1561 let (fd, _) = resolved.object(true)?;
1562 Ok((fd_path(&fd), Some(fd)))
1563}
1564
1565fn do_setxattr(r: &Req, nofollow: bool) -> Result<Outcome> {
1567 let path = r.path(0)?;
1568 let name = cstr(&r.ctx.mem.read_cstr(r.pid, r.arg(1))?)?;
1569 let size = r.arg(3) as usize;
1570 if size > XATTR_MAX {
1571 return Err(libc::E2BIG);
1572 }
1573 let value = r.read(r.arg(2), size)?;
1574 let rc = retrying(|| {
1575 let resolved = r.translate(libc::AT_FDCWD, &path, !nofollow)?;
1576 r.valid()?;
1577 r.check_writable(&resolved)?;
1578 let (c, _keep) = xattr_target(&resolved, nofollow)?;
1579 let nr = if nofollow && !resolved.is_mount_point() {
1580 libc::SYS_lsetxattr
1581 } else {
1582 libc::SYS_setxattr
1583 };
1584 Ok(check(unsafe {
1585 libc::syscall(nr, c.as_ptr(), name.as_ptr(), value.as_ptr(), size, r.arg_i64(4))
1586 }))
1587 })?;
1588 Ok(Outcome::Value(rc))
1589}
1590
1591fn do_getxattr(r: &Req, nofollow: bool) -> Result<Outcome> {
1593 let path = r.path(0)?;
1594 let name = cstr(&r.ctx.mem.read_cstr(r.pid, r.arg(1))?)?;
1595 let size = (r.arg(3) as usize).min(XATTR_MAX);
1596 let mut buf = vec![0u8; size];
1597 let rc = retrying(|| {
1598 let resolved = r.translate(libc::AT_FDCWD, &path, !nofollow)?;
1599 r.valid()?;
1600 let (c, _keep) = xattr_target(&resolved, nofollow)?;
1601 let nr = if nofollow && !resolved.is_mount_point() {
1602 libc::SYS_lgetxattr
1603 } else {
1604 libc::SYS_getxattr
1605 };
1606 check(unsafe { libc::syscall(nr, c.as_ptr(), name.as_ptr(), buf.as_mut_ptr(), size) })
1607 })?;
1608 if size > 0 && rc > 0 {
1609 r.write(r.arg(2), &buf[..rc as usize])?;
1610 }
1611 Ok(Outcome::Value(Ok(rc)))
1612}
1613
1614fn do_listxattr(r: &Req, nofollow: bool) -> Result<Outcome> {
1616 let path = r.path(0)?;
1617 let size = (r.arg(2) as usize).min(XATTR_MAX);
1618 let mut buf = vec![0u8; size];
1619 let rc = retrying(|| {
1620 let resolved = r.translate(libc::AT_FDCWD, &path, !nofollow)?;
1621 r.valid()?;
1622 let (c, _keep) = xattr_target(&resolved, nofollow)?;
1623 let nr = if nofollow && !resolved.is_mount_point() {
1624 libc::SYS_llistxattr
1625 } else {
1626 libc::SYS_listxattr
1627 };
1628 check(unsafe { libc::syscall(nr, c.as_ptr(), buf.as_mut_ptr(), size) })
1629 })?;
1630 if size > 0 && rc > 0 {
1631 r.write(r.arg(1), &buf[..rc as usize])?;
1632 }
1633 Ok(Outcome::Value(Ok(rc)))
1634}
1635
1636fn do_removexattr(r: &Req, nofollow: bool) -> Result<Outcome> {
1638 let path = r.path(0)?;
1639 let name = cstr(&r.ctx.mem.read_cstr(r.pid, r.arg(1))?)?;
1640 let rc = retrying(|| {
1641 let resolved = r.translate(libc::AT_FDCWD, &path, !nofollow)?;
1642 r.valid()?;
1643 r.check_writable(&resolved)?;
1644 let (c, _keep) = xattr_target(&resolved, nofollow)?;
1645 let nr = if nofollow && !resolved.is_mount_point() {
1646 libc::SYS_lremovexattr
1647 } else {
1648 libc::SYS_removexattr
1649 };
1650 Ok(check(unsafe { libc::syscall(nr, c.as_ptr(), name.as_ptr()) }))
1651 })?;
1652 Ok(Outcome::Value(rc))
1653}
1654
1655fn do_inotify_add_watch(r: &Req) -> Result<Outcome> {
1662 let path = r.path(1)?;
1663 let deref = r.arg(2) & IN_DONT_FOLLOW == 0;
1664 let resolved = r.translate(libc::AT_FDCWD, &path, deref)?;
1665 r.valid()?;
1666 if resolved.host == path {
1667 return Ok(Outcome::Continue);
1668 }
1669 note!(
1670 r.ctx,
1671 1,
1672 "pid {} inotify_add_watch on translated path {} unsupported",
1673 r.pid,
1674 String::from_utf8_lossy(&path)
1675 );
1676 Ok(Outcome::Value(Err(libc::ENOENT)))
1677}
1678
1679#[cfg(test)]
1680mod tests {
1681 use super::*;
1682
1683 #[test]
1684 fn rewrites_proc_self() {
1685 assert_eq!(rewrite_proc_self(b"/proc/self/exe", 42), b"/proc/42/exe".to_vec());
1686 assert_eq!(rewrite_proc_self(b"/proc/selfish", 42), b"/proc/selfish".to_vec());
1687 assert_eq!(
1688 rewrite_proc_self(b"/proc/thread-self/stat", 7),
1689 b"/proc/7/task/7/stat".to_vec()
1690 );
1691 }
1692
1693 #[test]
1694 fn classifies_magic_links() {
1695 assert_eq!(proc_magic_link(b"/proc/12/exe"), Some((12, true)));
1696 assert_eq!(proc_magic_link(b"/proc/12/task/13/exe"), Some((12, true)));
1697 assert_eq!(proc_magic_link(b"/proc/12/fd/3"), Some((12, false)));
1698 assert_eq!(proc_magic_link(b"/proc/12/cwd"), Some((12, false)));
1699 assert_eq!(proc_magic_link(b"/proc/12/status"), None);
1700 assert_eq!(proc_magic_link(b"/etc/passwd"), None);
1701 }
1702
1703 #[test]
1704 fn cleans_lexically() {
1705 assert_eq!(lexical_clean(b"/a//b/./../c"), b"/a/c".to_vec());
1706 assert_eq!(lexical_clean(b"/.."), b"/".to_vec());
1707 }
1708}