use std::ffi::CString;
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::chroot::resolve::{confine, openat2_in_root, resolve_existing_in_root, resolve_in_root, to_virtual_path};
use crate::seccomp::notif::{read_child_mem, write_child_mem, NotifAction};
use crate::seccomp::state::{ChrootState, CowState};
use crate::sys::structs::{SeccompNotif, SeccompNotifAddfd, SECCOMP_IOCTL_NOTIF_ADDFD};
pub(crate) struct ChrootCtx<'a> {
pub root: &'a Path,
pub readable: &'a [PathBuf],
pub writable: &'a [PathBuf],
pub denied: &'a [PathBuf],
pub mounts: &'a [(PathBuf, PathBuf)],
}
impl ChrootCtx<'_> {
fn is_denied(&self, virtual_path: &Path) -> bool {
self.denied.iter().any(|p| virtual_path.starts_with(p))
}
fn can_read(&self, virtual_path: &Path) -> bool {
if self.is_denied(virtual_path) {
return false;
}
if self.is_mounted(virtual_path) {
return true;
}
self.readable.is_empty()
|| self.readable.iter().any(|p| virtual_path.starts_with(p) || p.starts_with(virtual_path))
|| self.writable.iter().any(|p| virtual_path.starts_with(p) || p.starts_with(virtual_path))
}
fn can_write(&self, virtual_path: &Path) -> bool {
if self.is_denied(virtual_path) {
return false;
}
if self.is_mounted(virtual_path) {
return true;
}
self.writable.iter().any(|p| virtual_path.starts_with(p))
}
fn is_mounted(&self, virtual_path: &Path) -> bool {
self.mounts.iter().any(|(vp, _)| virtual_path.starts_with(vp))
}
fn mount_target(&self, virtual_path: &Path) -> Option<(&Path, String)> {
let mut best: Option<(&Path, &Path)> = None;
for (vp, hp) in self.mounts {
if virtual_path.starts_with(vp) {
if best.is_none() || vp.as_os_str().len() > best.unwrap().0.as_os_str().len() {
best = Some((vp.as_path(), hp.as_path()));
}
}
}
let (mount_vp, mount_hp) = best?;
let sub = virtual_path.strip_prefix(mount_vp).ok()?;
let sub_str = if sub.as_os_str().is_empty() {
"/".to_string()
} else {
format!("/{}", sub.to_string_lossy())
};
Some((mount_hp, sub_str))
}
fn resolve_mount(&self, virtual_path: &str) -> Option<(PathBuf, PathBuf)> {
let confined = confine(virtual_path);
let (mount_target, sub_path) = self.mount_target(&confined)?;
if let Some(result) = resolve_in_root(mount_target, &sub_path) {
let vp = confined;
return Some((result.0, vp));
}
None
}
fn resolve_mount_existing(&self, virtual_path: &str) -> Option<(PathBuf, PathBuf)> {
let confined = confine(virtual_path);
let (mount_target, sub_path) = self.mount_target(&confined)?;
if let Some(result) = resolve_existing_in_root(mount_target, &sub_path) {
let vp = confined;
return Some((result.0, vp));
}
None
}
fn host_to_virtual(&self, host_path: &Path) -> Option<PathBuf> {
let mut best: Option<(&Path, &Path, usize)> = None;
for (vp, hp) in self.mounts {
if host_path.starts_with(hp) {
let len = hp.as_os_str().len();
if best.is_none() || len > best.unwrap().2 {
best = Some((vp.as_path(), hp.as_path(), len));
}
}
}
if let Some((mount_vp, mount_hp, _)) = best {
let rel = host_path.strip_prefix(mount_hp).ok()?;
return Some(mount_vp.join(rel));
}
to_virtual_path(self.root, host_path)
}
}
fn read_path(notif: &SeccompNotif, addr: u64, notif_fd: RawFd) -> Option<String> {
if addr == 0 {
return None;
}
const PAGE_SIZE: u64 = 4096;
let mut result = Vec::with_capacity(256);
let mut cur = addr;
while result.len() < 4096 {
let page_remaining = PAGE_SIZE - (cur % PAGE_SIZE);
let to_read = page_remaining.min((4096 - result.len()) as u64) as usize;
let bytes = read_child_mem(notif_fd, notif.id, notif.pid, cur, to_read).ok()?;
if let Some(nul) = bytes.iter().position(|&b| b == 0) {
result.extend_from_slice(&bytes[..nul]);
return String::from_utf8(result).ok();
}
result.extend_from_slice(&bytes);
cur += to_read as u64;
}
String::from_utf8(result).ok()
}
fn build_virtual_path(
notif: &SeccompNotif,
dirfd: i64,
path: &str,
ctx: &ChrootCtx<'_>,
) -> Option<String> {
if Path::new(path).is_absolute() {
Some(path.to_string())
} else {
let dirfd32 = dirfd as i32;
let base_host = if dirfd32 == libc::AT_FDCWD {
std::fs::read_link(format!("/proc/{}/cwd", notif.pid)).ok()?
} else {
std::fs::read_link(format!("/proc/{}/fd/{}", notif.pid, dirfd)).ok()?
};
let base_virtual = ctx.host_to_virtual(&base_host)?;
let combined = base_virtual.join(path);
Some(combined.to_string_lossy().to_string())
}
}
fn resolve_chroot_path(
notif: &SeccompNotif,
dirfd: i64,
path: &str,
ctx: &ChrootCtx<'_>,
) -> Option<(PathBuf, PathBuf)> {
let full_path = build_virtual_path(notif, dirfd, path, ctx)?;
if let Some(result) = ctx.resolve_mount(&full_path) {
return Some(result);
}
resolve_in_root(ctx.root, &full_path)
}
fn resolve_chroot_path_existing(
notif: &SeccompNotif,
dirfd: i64,
path: &str,
ctx: &ChrootCtx<'_>,
) -> Option<(PathBuf, PathBuf)> {
let full_path = build_virtual_path(notif, dirfd, path, ctx)?;
if let Some(result) = ctx.resolve_mount_existing(&full_path) {
return Some(result);
}
resolve_existing_in_root(ctx.root, &full_path)
}
fn path_cstr(path: &Path, err: i32) -> Result<CString, NotifAction> {
CString::new(path.to_str().unwrap_or("")).map_err(|_| NotifAction::Errno(err))
}
fn last_errno(fallback: i32) -> i32 {
std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(fallback)
}
async fn cow_resolve(
cow_state: &Arc<Mutex<CowState>>,
host_path: &Path,
) -> Result<PathBuf, NotifAction> {
let cs = cow_state.lock().await;
if let Some(ref cow) = cs.branch {
let host_str = host_path.to_string_lossy();
if cow.matches(&host_str) {
return cow
.handle_stat(&host_str)
.ok_or(NotifAction::Errno(libc::ENOENT));
}
}
Ok(host_path.to_path_buf())
}
fn read_and_resolve(
notif: &SeccompNotif,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
dirfd_idx: usize,
path_idx: usize,
) -> Result<(String, PathBuf, PathBuf), NotifAction> {
let path = read_path(notif, notif.data.args[path_idx], notif_fd)
.ok_or(NotifAction::Continue)?;
let dirfd = notif.data.args[dirfd_idx] as i64;
let (host_path, virtual_path) =
resolve_chroot_path(notif, dirfd, &path, ctx).ok_or(NotifAction::Errno(libc::EACCES))?;
Ok((path, host_path, virtual_path))
}
fn read_and_resolve_existing(
notif: &SeccompNotif,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
dirfd_idx: usize,
path_idx: usize,
) -> Result<(String, PathBuf, PathBuf), NotifAction> {
let path = read_path(notif, notif.data.args[path_idx], notif_fd)
.ok_or(NotifAction::Continue)?;
let dirfd = notif.data.args[dirfd_idx] as i64;
let (host_path, virtual_path) =
resolve_chroot_path_existing(notif, dirfd, &path, ctx)
.ok_or(NotifAction::Errno(libc::ENOENT))?;
Ok((path, host_path, virtual_path))
}
fn exec_on_host(f: impl FnOnce(*const libc::c_char) -> libc::c_int, host: &Path) -> NotifAction {
let c = match path_cstr(host, libc::EINVAL) {
Ok(c) => c,
Err(a) => return a,
};
if f(c.as_ptr()) < 0 {
NotifAction::Errno(last_errno(libc::EIO))
} else {
NotifAction::ReturnValue(0)
}
}
pub(crate) const SYS_FACCESSAT2: i64 = 439;
pub(crate) async fn handle_chroot_open(
notif: &SeccompNotif,
_chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let dirfd = notif.data.args[0] as i64;
let path_ptr = notif.data.args[1];
let flags = notif.data.args[2];
let rel_path = match read_path(notif, path_ptr, notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let (host_path, virtual_path) = match resolve_chroot_path(notif, dirfd, &rel_path, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::EACCES),
};
let is_write = (flags as i32 & (libc::O_WRONLY | libc::O_RDWR)) != 0;
if is_write {
if !ctx.can_write(&virtual_path) {
return NotifAction::Errno(libc::EACCES);
}
} else if !ctx.can_read(&virtual_path) {
return NotifAction::Errno(libc::EACCES);
}
{
let mut cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_mut() {
let host_str = host_path.to_string_lossy();
if cow.matches(&host_str) {
match cow.handle_open(&host_str, flags) {
Ok(Some(real_path)) => {
drop(cs);
let c_path = match path_cstr(&real_path, libc::EINVAL) {
Ok(c) => c,
Err(a) => return a,
};
let fd = unsafe { libc::open(c_path.as_ptr(), flags as i32, 0o666) };
if fd < 0 {
return NotifAction::Errno(last_errno(libc::EIO));
}
let newfd_flags = if flags & libc::O_CLOEXEC as u64 != 0 {
libc::O_CLOEXEC as u32
} else {
0
};
let owned = unsafe { OwnedFd::from_raw_fd(fd) };
return NotifAction::InjectFdSend { srcfd: owned, newfd_flags };
}
Ok(None) => {
}
Err(crate::error::BranchError::QuotaExceeded) => {
return NotifAction::Errno(libc::ENOSPC);
}
Err(crate::error::BranchError::Exists) => {
return NotifAction::Errno(libc::EEXIST);
}
Err(_) => return NotifAction::Errno(libc::EIO),
}
}
}
}
let vp_str = virtual_path.to_string_lossy();
let mode = if is_write { 0o666 } else { 0 };
let (resolve_root, resolve_path) = if let Some((mt, sub)) = ctx.mount_target(&virtual_path) {
(mt.to_path_buf(), sub)
} else {
(ctx.root.to_path_buf(), vp_str.to_string())
};
let fd = match openat2_in_root(&resolve_root, &resolve_path, flags as i32, mode) {
Ok(fd) => fd,
Err(errno) => return NotifAction::Errno(errno),
};
let newfd_flags = if flags & libc::O_CLOEXEC as u64 != 0 {
libc::O_CLOEXEC as u32
} else {
0
};
let owned = unsafe { OwnedFd::from_raw_fd(fd) };
NotifAction::InjectFdSend { srcfd: owned, newfd_flags }
}
fn read_pt_interp(fd: RawFd) -> Option<(String, u64, usize)> {
let mut file = unsafe { std::fs::File::from_raw_fd(fd) };
let mut header = [0u8; 64]; if file.read_exact(&mut header).is_err() {
std::mem::forget(file); return None;
}
if &header[..4] != b"\x7fELF" {
std::mem::forget(file);
return None;
}
let e_phoff = u64::from_le_bytes(header[32..40].try_into().ok()?);
let e_phentsize = u16::from_le_bytes(header[54..56].try_into().ok()?) as u64;
let e_phnum = u16::from_le_bytes(header[56..58].try_into().ok()?) as usize;
const PT_INTERP: u32 = 3;
for i in 0..e_phnum {
let ph_offset = e_phoff + (i as u64) * e_phentsize;
let mut phdr = [0u8; 56]; if file.seek(SeekFrom::Start(ph_offset)).is_err() {
break;
}
if file.read_exact(&mut phdr).is_err() {
break;
}
let p_type = u32::from_le_bytes(phdr[0..4].try_into().ok()?);
if p_type != PT_INTERP {
continue;
}
let p_offset = u64::from_le_bytes(phdr[8..16].try_into().ok()?);
let p_filesz = u64::from_le_bytes(phdr[32..40].try_into().ok()?) as usize;
if p_filesz == 0 || p_filesz > 256 {
break;
}
let mut buf = vec![0u8; p_filesz];
if file.seek(SeekFrom::Start(p_offset)).is_err() {
break;
}
if file.read_exact(&mut buf).is_err() {
break;
}
let nul = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
let interp = String::from_utf8_lossy(&buf[..nul]).to_string();
std::mem::forget(file);
return Some((interp, p_offset, p_filesz));
}
std::mem::forget(file);
None
}
fn memfd_with_patched_interp(
src_fd: RawFd,
new_interp: &str,
interp_offset: u64,
interp_capacity: usize,
) -> Option<OwnedFd> {
let size = {
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(src_fd, &mut stat) } < 0 {
return None;
}
stat.st_size as usize
};
let memfd = crate::sys::syscall::memfd_create("sandlock-exec", 0).ok()?;
let mfd = memfd.as_raw_fd();
if unsafe { libc::ftruncate(mfd, size as libc::off_t) } < 0 {
return None;
}
let mut offset: libc::off_t = 0;
let mut remaining = size;
while remaining > 0 {
let n = unsafe {
libc::sendfile(mfd, src_fd, &mut offset, remaining)
};
if n <= 0 {
return None;
}
remaining -= n as usize;
}
let new_bytes = new_interp.as_bytes();
if new_bytes.len() >= interp_capacity {
return None; }
let mut patch = vec![0u8; interp_capacity];
patch[..new_bytes.len()].copy_from_slice(new_bytes);
let mut mfd_file = unsafe { std::fs::File::from_raw_fd(mfd) };
if mfd_file.seek(SeekFrom::Start(interp_offset)).is_err() {
std::mem::forget(mfd_file);
return None;
}
if mfd_file.write_all(&patch).is_err() {
std::mem::forget(mfd_file);
return None;
}
std::mem::forget(mfd_file);
Some(memfd)
}
pub(crate) async fn handle_chroot_exec(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
_cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let nr = notif.data.nr as i64;
let (dirfd, path_ptr) = if nr == libc::SYS_execveat {
(notif.data.args[0] as i64, notif.data.args[1])
} else {
(libc::AT_FDCWD as i64, notif.data.args[0])
};
let rel_path = match read_path(notif, path_ptr, notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let full_path = if Path::new(&rel_path).is_absolute() {
rel_path
} else {
let dirfd32 = dirfd as i32;
let base_host = if dirfd32 == libc::AT_FDCWD {
match std::fs::read_link(format!("/proc/{}/cwd", notif.pid)) {
Ok(p) => p,
Err(_) => return NotifAction::Errno(libc::EACCES),
}
} else {
match std::fs::read_link(format!("/proc/{}/fd/{}", notif.pid, dirfd)) {
Ok(p) => p,
Err(_) => return NotifAction::Errno(libc::EACCES),
}
};
match ctx.host_to_virtual(&base_host) {
Some(base) => base.join(&rel_path).to_string_lossy().to_string(),
None => return NotifAction::Errno(libc::EACCES),
}
};
let virtual_path = crate::chroot::resolve::confine(&full_path);
if !ctx.can_read(&virtual_path) {
return NotifAction::Errno(libc::EACCES);
}
let (exec_root, exec_path) = if let Some((mt, sub)) = ctx.mount_target(&virtual_path) {
(mt.to_path_buf(), sub)
} else {
(ctx.root.to_path_buf(), virtual_path.to_string_lossy().to_string())
};
let src_fd = match openat2_in_root(
&exec_root,
&exec_path,
libc::O_RDONLY | libc::O_CLOEXEC,
0,
) {
Ok(fd) => fd,
Err(_) => return NotifAction::Errno(libc::ENOENT),
};
let exec_fd = if let Some((interp_path, interp_offset, interp_cap)) = read_pt_interp(src_fd) {
let interp_src = match openat2_in_root(
ctx.root,
&interp_path,
libc::O_RDONLY | libc::O_CLOEXEC,
0,
) {
Ok(fd) => fd,
Err(_) => {
unsafe { libc::close(src_fd) };
return NotifAction::Errno(libc::ENOENT);
}
};
let addfd_interp = SeccompNotifAddfd {
id: notif.id,
flags: 0,
srcfd: interp_src as u32,
newfd: 0,
newfd_flags: 0,
};
let child_interp_fd = unsafe {
libc::ioctl(
notif_fd,
SECCOMP_IOCTL_NOTIF_ADDFD as libc::c_ulong,
&addfd_interp as *const _,
)
};
unsafe { libc::close(interp_src) };
if child_interp_fd < 0 {
unsafe { libc::close(src_fd) };
return NotifAction::Errno(libc::EIO);
}
let new_interp = format!("/proc/self/fd/{}", child_interp_fd);
match memfd_with_patched_interp(src_fd, &new_interp, interp_offset, interp_cap) {
Some(memfd) => {
unsafe { libc::close(src_fd) };
memfd
}
None => {
unsafe { OwnedFd::from_raw_fd(src_fd) }
}
}
} else {
unsafe { OwnedFd::from_raw_fd(src_fd) }
};
{
let mut cs = chroot_state.lock().await;
cs.chroot_exe = Some(virtual_path.clone());
}
let addfd = SeccompNotifAddfd {
id: notif.id,
flags: 0,
srcfd: exec_fd.as_raw_fd() as u32,
newfd: 0,
newfd_flags: 0, };
let child_fd = unsafe {
libc::ioctl(
notif_fd,
SECCOMP_IOCTL_NOTIF_ADDFD as libc::c_ulong,
&addfd as *const _,
)
};
drop(exec_fd);
if child_fd < 0 {
return NotifAction::Errno(libc::EIO);
}
let fd_path = format!("/proc/self/fd/{}\0", child_fd);
if write_child_mem(notif_fd, notif.id, notif.pid, path_ptr, fd_path.as_bytes()).is_err() {
return NotifAction::Errno(libc::EFAULT);
}
NotifAction::Continue
}
pub(crate) async fn handle_chroot_write(
notif: &SeccompNotif,
_chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let nr = notif.data.nr as i64;
if nr == libc::SYS_unlinkat {
let (_, host_path, vp) = match read_and_resolve(notif, notif_fd, ctx, 0, 1) {
Ok(r) => r,
Err(a) => return a,
};
if !ctx.can_write(&vp) { return NotifAction::Errno(libc::EACCES); }
let is_dir = (notif.data.args[2] & libc::AT_REMOVEDIR as u64) != 0;
{
let mut cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_mut() {
let s = host_path.to_string_lossy();
if cow.matches(&s) {
match cow.handle_unlink(&s, is_dir) {
Ok(true) => return NotifAction::ReturnValue(0),
Err(errno) => return NotifAction::Errno(errno),
_ => {}
}
}
}
}
return exec_on_host(
|p| if is_dir { unsafe { libc::rmdir(p) } } else { unsafe { libc::unlink(p) } },
&host_path,
);
}
if nr == libc::SYS_mkdirat {
let (_, host_path, vp) = match read_and_resolve(notif, notif_fd, ctx, 0, 1) {
Ok(r) => r,
Err(a) => return a,
};
if !ctx.can_write(&vp) { return NotifAction::Errno(libc::EACCES); }
let mode = notif.data.args[2] as u32;
{
let mut cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_mut() {
let s = host_path.to_string_lossy();
if cow.matches(&s) {
match cow.handle_mkdir(&s) {
Ok(true) => return NotifAction::ReturnValue(0),
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
_ => {}
}
}
}
}
return exec_on_host(|p| unsafe { libc::mkdir(p, mode) }, &host_path);
}
if nr == libc::SYS_renameat2 {
let old_path = match read_path(notif, notif.data.args[1], notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let new_path = match read_path(notif, notif.data.args[3], notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let (old_host, old_vp) = match resolve_chroot_path(notif, notif.data.args[0] as i64, &old_path, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::EACCES),
};
let (new_host, new_vp) = match resolve_chroot_path(notif, notif.data.args[2] as i64, &new_path, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::EACCES),
};
if !ctx.can_write(&old_vp) || !ctx.can_write(&new_vp) {
return NotifAction::Errno(libc::EACCES);
}
{
let mut cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_mut() {
let old_str = old_host.to_string_lossy();
if cow.matches(&old_str) {
match cow.handle_rename(&old_str, &new_host.to_string_lossy()) {
Ok(true) => return NotifAction::ReturnValue(0),
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
_ => {}
}
}
}
}
let c_old = match path_cstr(&old_host, libc::EINVAL) { Ok(c) => c, Err(a) => return a };
let c_new = match path_cstr(&new_host, libc::EINVAL) { Ok(c) => c, Err(a) => return a };
return if unsafe { libc::rename(c_old.as_ptr(), c_new.as_ptr()) } < 0 {
NotifAction::Errno(last_errno(libc::EIO))
} else {
NotifAction::ReturnValue(0)
};
}
if nr == libc::SYS_symlinkat {
let target = match read_path(notif, notif.data.args[0], notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let linkpath = match read_path(notif, notif.data.args[2], notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let (host_link, link_vp) = match resolve_chroot_path(notif, notif.data.args[1] as i64, &linkpath, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::EACCES),
};
if !ctx.can_write(&link_vp) { return NotifAction::Errno(libc::EACCES); }
{
let mut cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_mut() {
let s = host_link.to_string_lossy();
if cow.matches(&s) {
match cow.handle_symlink(&target, &s) {
Ok(true) => return NotifAction::ReturnValue(0),
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
_ => {}
}
}
}
}
let c_target = match CString::new(target.as_str()) { Ok(c) => c, Err(_) => return NotifAction::Errno(libc::EINVAL) };
let c_link = match path_cstr(&host_link, libc::EINVAL) { Ok(c) => c, Err(a) => return a };
return if unsafe { libc::symlink(c_target.as_ptr(), c_link.as_ptr()) } < 0 {
NotifAction::Errno(last_errno(libc::EIO))
} else {
NotifAction::ReturnValue(0)
};
}
if nr == libc::SYS_linkat {
let old_path = match read_path(notif, notif.data.args[1], notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let new_path = match read_path(notif, notif.data.args[3], notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let (old_host, _) = match resolve_chroot_path(notif, notif.data.args[0] as i64, &old_path, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::EACCES),
};
let (new_host, new_vp) = match resolve_chroot_path(notif, notif.data.args[2] as i64, &new_path, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::EACCES),
};
if !ctx.can_write(&new_vp) { return NotifAction::Errno(libc::EACCES); }
{
let mut cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_mut() {
let s = new_host.to_string_lossy();
if cow.matches(&s) {
match cow.handle_link(&old_host.to_string_lossy(), &s) {
Ok(true) => return NotifAction::ReturnValue(0),
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
_ => {}
}
}
}
}
let c_old = match path_cstr(&old_host, libc::EINVAL) { Ok(c) => c, Err(a) => return a };
let c_new = match path_cstr(&new_host, libc::EINVAL) { Ok(c) => c, Err(a) => return a };
let flags = notif.data.args[4] as i32;
return if unsafe { libc::linkat(libc::AT_FDCWD, c_old.as_ptr(), libc::AT_FDCWD, c_new.as_ptr(), flags) } < 0 {
NotifAction::Errno(last_errno(libc::EIO))
} else {
NotifAction::ReturnValue(0)
};
}
if nr == libc::SYS_fchmodat {
let (_, host_path, vp) = match read_and_resolve(notif, notif_fd, ctx, 0, 1) {
Ok(r) => r,
Err(a) => return a,
};
if !ctx.can_write(&vp) { return NotifAction::Errno(libc::EACCES); }
let mode = (notif.data.args[2] & 0o7777) as u32;
{
let mut cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_mut() {
let s = host_path.to_string_lossy();
if cow.matches(&s) {
match cow.handle_chmod(&s, mode) {
Ok(true) => return NotifAction::ReturnValue(0),
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
_ => {}
}
}
}
}
return exec_on_host(|p| unsafe { libc::chmod(p, mode) }, &host_path);
}
if nr == libc::SYS_fchownat {
let (_, host_path, vp) = match read_and_resolve(notif, notif_fd, ctx, 0, 1) {
Ok(r) => r,
Err(a) => return a,
};
if !ctx.can_write(&vp) { return NotifAction::Errno(libc::EACCES); }
let uid = notif.data.args[2] as u32;
let gid = notif.data.args[3] as u32;
{
let mut cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_mut() {
let s = host_path.to_string_lossy();
if cow.matches(&s) {
match cow.handle_chown(&s, uid, gid) {
Ok(true) => return NotifAction::ReturnValue(0),
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
_ => {}
}
}
}
}
return exec_on_host(|p| unsafe { libc::chown(p, uid, gid) }, &host_path);
}
if nr == libc::SYS_truncate {
let path = match read_path(notif, notif.data.args[0], notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let (host_path, vp) = match resolve_chroot_path(notif, libc::AT_FDCWD as i64, &path, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::EACCES),
};
if !ctx.can_write(&vp) { return NotifAction::Errno(libc::EACCES); }
let length = notif.data.args[1] as i64;
{
let mut cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_mut() {
let s = host_path.to_string_lossy();
if cow.matches(&s) {
match cow.handle_truncate(&s, length) {
Ok(true) => return NotifAction::ReturnValue(0),
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
_ => {}
}
}
}
}
return exec_on_host(|p| unsafe { libc::truncate(p, length) }, &host_path);
}
NotifAction::Continue
}
fn stat_and_write(notif: &SeccompNotif, notif_fd: RawFd, path: &Path) -> NotifAction {
let statbuf_addr = notif.data.args[2];
let flags = notif.data.args[3];
let follow = (flags & libc::AT_SYMLINK_NOFOLLOW as u64) == 0;
let meta = if follow {
std::fs::metadata(path)
} else {
std::fs::symlink_metadata(path)
};
let meta = match meta {
Ok(m) => m,
Err(_) => return NotifAction::Errno(libc::ENOENT),
};
use std::os::unix::fs::MetadataExt;
let mut buf = vec![0u8; std::mem::size_of::<libc::stat>()];
let mut off = 0;
macro_rules! pack_u64 { ($v:expr) => { buf[off..off+8].copy_from_slice(&($v as u64).to_ne_bytes()); off += 8; }; }
macro_rules! pack_u32 { ($v:expr) => { buf[off..off+4].copy_from_slice(&($v as u32).to_ne_bytes()); off += 4; }; }
pack_u64!(meta.dev()); pack_u64!(meta.ino()); pack_u64!(meta.nlink());
pack_u32!(meta.mode()); pack_u32!(meta.uid()); pack_u32!(meta.gid()); pack_u32!(0u32);
pack_u64!(meta.rdev()); pack_u64!(meta.size() as u64);
pack_u64!(meta.blksize()); pack_u64!(meta.blocks() as u64);
pack_u64!(meta.atime() as u64); pack_u64!(meta.atime_nsec() as u64);
pack_u64!(meta.mtime() as u64); pack_u64!(meta.mtime_nsec() as u64);
pack_u64!(meta.ctime() as u64); pack_u64!(meta.ctime_nsec() as u64);
let _ = off;
if write_child_mem(notif_fd, notif.id, notif.pid, statbuf_addr, &buf).is_err() {
return NotifAction::Continue;
}
NotifAction::ReturnValue(0)
}
pub(crate) async fn handle_chroot_stat(
notif: &SeccompNotif,
_chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let nr = notif.data.nr as i64;
let flags = notif.data.args[3];
if (flags & libc::AT_EMPTY_PATH as u64) != 0 {
return NotifAction::Continue;
}
let (_, host_path, vp) = match read_and_resolve_existing(notif, notif_fd, ctx, 0, 1) {
Ok(r) => r,
Err(a) => return a,
};
if !ctx.can_read(&vp) { return NotifAction::Errno(libc::EACCES); }
let real_path = match cow_resolve(cow_state, &host_path).await {
Ok(p) => p,
Err(a) => return a,
};
if nr == libc::SYS_faccessat || nr == SYS_FACCESSAT2 {
return if real_path.exists() || real_path.is_symlink() {
NotifAction::ReturnValue(0)
} else {
NotifAction::Errno(libc::ENOENT)
};
}
stat_and_write(notif, notif_fd, &real_path)
}
pub(crate) async fn handle_chroot_statx(
notif: &SeccompNotif,
_chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let dirfd = notif.data.args[0] as i64;
let path_ptr = notif.data.args[1];
let flags = notif.data.args[2] as i32;
let mask = notif.data.args[3] as u32;
let statxbuf_addr = notif.data.args[4];
if (flags & libc::AT_EMPTY_PATH) != 0 {
return NotifAction::Continue;
}
let path = match read_path(notif, path_ptr, notif_fd) {
Some(p) if !p.is_empty() => p,
_ => return NotifAction::Continue,
};
let (host_path, vp) = match resolve_chroot_path_existing(notif, dirfd, &path, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::ENOENT),
};
if !ctx.can_read(&vp) { return NotifAction::Errno(libc::EACCES); }
let real_path = match cow_resolve(cow_state, &host_path).await {
Ok(p) => p,
Err(a) => return a,
};
let c_path = match path_cstr(&real_path, libc::ENOENT) {
Ok(c) => c,
Err(a) => return a,
};
let mut stx_buf = vec![0u8; 256];
let ret = unsafe {
libc::syscall(libc::SYS_statx, libc::AT_FDCWD, c_path.as_ptr(), flags, mask, stx_buf.as_mut_ptr())
};
if ret < 0 {
return NotifAction::Errno(last_errno(libc::ENOENT));
}
if write_child_mem(notif_fd, notif.id, notif.pid, statxbuf_addr, &stx_buf).is_err() {
return NotifAction::Continue;
}
NotifAction::ReturnValue(0)
}
pub(crate) async fn handle_chroot_readlink(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let dirfd = notif.data.args[0] as i64;
let path = match read_path(notif, notif.data.args[1], notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let buf_addr = notif.data.args[2];
let bufsiz = (notif.data.args[3] & 0xFFFFFFFF) as usize;
let write_target = |target: &[u8]| -> NotifAction {
let len = target.len().min(bufsiz);
if write_child_mem(notif_fd, notif.id, notif.pid, buf_addr, &target[..len]).is_err() {
return NotifAction::Continue;
}
NotifAction::ReturnValue(len as i64)
};
if path == "/proc/self/root" {
return write_target(b"/");
}
if path == "/proc/self/exe" {
let cs = chroot_state.lock().await;
if let Some(ref exe) = cs.chroot_exe {
let s = exe.to_string_lossy();
return write_target(s.as_bytes());
}
drop(cs);
if let Ok(real_exe) = std::fs::read_link(format!("/proc/{}/exe", notif.pid)) {
let virtual_exe = ctx.host_to_virtual(&real_exe).unwrap_or(real_exe);
let s = virtual_exe.to_string_lossy();
return write_target(s.as_bytes());
}
return NotifAction::Continue;
}
let full_path = if Path::new(&path).is_absolute() {
path.clone()
} else {
let dirfd32 = dirfd as i32;
let base_host = if dirfd32 == libc::AT_FDCWD {
match std::fs::read_link(format!("/proc/{}/cwd", notif.pid)) {
Ok(p) => p,
Err(_) => return NotifAction::Errno(libc::EACCES),
}
} else {
match std::fs::read_link(format!("/proc/{}/fd/{}", notif.pid, dirfd)) {
Ok(p) => p,
Err(_) => return NotifAction::Errno(libc::EACCES),
}
};
let base_virtual = match ctx.host_to_virtual(&base_host) {
Some(p) => p,
None => return NotifAction::Errno(libc::EACCES),
};
base_virtual.join(&path).to_string_lossy().to_string()
};
let confined = crate::chroot::resolve::confine(&full_path);
let file_name = match confined.file_name() {
Some(f) => f.to_os_string(),
None => return NotifAction::Errno(libc::EINVAL),
};
let parent = confined.parent().unwrap_or(Path::new("/"));
let parent_str = parent.to_str().unwrap_or("/");
let parent_host = if let Some((mt, sub)) = ctx.mount_target(parent) {
match resolve_in_root(mt, &sub) {
Some((hp, _)) => hp,
None => return NotifAction::Errno(libc::EACCES),
}
} else {
match resolve_in_root(ctx.root, parent_str) {
Some((hp, _)) => hp,
None => return NotifAction::Errno(libc::EACCES),
}
};
let host_path = parent_host.join(&file_name);
{
let cs = cow_state.lock().await;
if let Some(cow) = cs.branch.as_ref() {
let host_str = host_path.to_string_lossy();
if cow.matches(&host_str) {
let target = match cow.handle_readlink(&host_str) {
Some(t) => t,
None => return NotifAction::Errno(libc::ENOENT),
};
drop(cs);
return write_target(target.as_bytes());
}
}
}
let target = match std::fs::read_link(&host_path) {
Ok(t) => t,
Err(_) => return NotifAction::Errno(libc::ENOENT),
};
let display = if target.is_absolute() {
ctx.host_to_virtual(&target).unwrap_or(target)
} else {
target
};
write_target(display.to_string_lossy().as_bytes())
}
pub(crate) async fn handle_chroot_getdents(
_notif: &SeccompNotif,
_chroot_state: &Arc<Mutex<ChrootState>>,
_cow_state: &Arc<Mutex<CowState>>,
_notif_fd: RawFd,
_ctx: &ChrootCtx<'_>,
) -> NotifAction {
NotifAction::Continue
}
pub(crate) async fn handle_chroot_chdir(
notif: &SeccompNotif,
_chroot_state: &Arc<Mutex<ChrootState>>,
_cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let path_ptr = notif.data.args[0];
let path = match read_path(notif, path_ptr, notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let full_path = if Path::new(&path).is_absolute() {
path
} else {
match std::fs::read_link(format!("/proc/{}/cwd", notif.pid)) {
Ok(cwd) => match ctx.host_to_virtual(&cwd) {
Some(base) => base.join(&path).to_string_lossy().to_string(),
None => return NotifAction::Errno(libc::EACCES),
},
Err(_) => return NotifAction::Errno(libc::EACCES),
}
};
let confined = confine(&full_path);
let (chdir_root, chdir_path) = if let Some((mt, sub)) = ctx.mount_target(&confined) {
(mt.to_path_buf(), sub)
} else {
(ctx.root.to_path_buf(), full_path.clone())
};
let src_fd = match openat2_in_root(
&chdir_root,
&chdir_path,
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
0,
) {
Ok(fd) => fd,
Err(errno) => return NotifAction::Errno(errno),
};
let addfd = SeccompNotifAddfd {
id: notif.id,
flags: 0,
srcfd: src_fd as u32,
newfd: 0,
newfd_flags: libc::O_CLOEXEC as u32,
};
let child_fd = unsafe {
libc::ioctl(
notif_fd,
SECCOMP_IOCTL_NOTIF_ADDFD as libc::c_ulong,
&addfd as *const _,
)
};
unsafe { libc::close(src_fd) };
if child_fd < 0 {
return NotifAction::Errno(libc::EIO);
}
let fd_path = format!("/proc/self/fd/{}\0", child_fd);
if write_child_mem(notif_fd, notif.id, notif.pid, path_ptr, fd_path.as_bytes()).is_err() {
return NotifAction::Errno(libc::EFAULT);
}
NotifAction::Continue
}
pub(crate) async fn handle_chroot_getcwd(
notif: &SeccompNotif,
_chroot_state: &Arc<Mutex<ChrootState>>,
_cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let buf_addr = notif.data.args[0];
let buf_size = (notif.data.args[1] & 0xFFFFFFFF) as usize;
let cwd = match std::fs::read_link(format!("/proc/{}/cwd", notif.pid)) {
Ok(c) => c,
Err(_) => return NotifAction::Continue,
};
let virtual_cwd = ctx.host_to_virtual(&cwd).unwrap_or_else(|| PathBuf::from("/"));
let cwd_str = virtual_cwd.to_string_lossy();
let cwd_bytes = cwd_str.as_bytes();
if cwd_bytes.len() + 1 > buf_size {
return NotifAction::Errno(libc::ERANGE);
}
let mut write_buf = cwd_bytes.to_vec();
write_buf.push(0);
if write_child_mem(notif_fd, notif.id, notif.pid, buf_addr, &write_buf).is_err() {
return NotifAction::Continue;
}
NotifAction::ReturnValue(write_buf.len() as i64)
}
pub(crate) async fn handle_chroot_statfs(
notif: &SeccompNotif,
_chroot_state: &Arc<Mutex<ChrootState>>,
_cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let path_ptr = notif.data.args[0];
let statfsbuf_addr = notif.data.args[1];
let path = match read_path(notif, path_ptr, notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let (host_path, _) = match resolve_chroot_path_existing(notif, libc::AT_FDCWD as i64, &path, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::ENOENT),
};
let c_path = match path_cstr(&host_path, libc::ENOENT) {
Ok(c) => c,
Err(a) => return a,
};
let mut statfs_buf: libc::statfs = unsafe { std::mem::zeroed() };
if unsafe { libc::statfs(c_path.as_ptr(), &mut statfs_buf) } < 0 {
return NotifAction::Errno(last_errno(libc::ENOENT));
}
let buf_bytes = unsafe {
std::slice::from_raw_parts(
&statfs_buf as *const libc::statfs as *const u8,
std::mem::size_of::<libc::statfs>(),
)
};
if write_child_mem(notif_fd, notif.id, notif.pid, statfsbuf_addr, buf_bytes).is_err() {
return NotifAction::Continue;
}
NotifAction::ReturnValue(0)
}
pub(crate) async fn handle_chroot_utimensat(
notif: &SeccompNotif,
_chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let dirfd = notif.data.args[0] as i64;
let path_ptr = notif.data.args[1];
let times_ptr = notif.data.args[2];
let flags = notif.data.args[3] as i32;
if path_ptr == 0 {
return NotifAction::Continue;
}
let path = match read_path(notif, path_ptr, notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let (host_path, vp) = match resolve_chroot_path(notif, dirfd, &path, ctx) {
Some(r) => r,
None => return NotifAction::Errno(libc::EACCES),
};
if !ctx.can_write(&vp) { return NotifAction::Errno(libc::EACCES); }
let real_path = match cow_resolve(cow_state, &host_path).await {
Ok(p) => p,
Err(a) => return a,
};
let times = if times_ptr != 0 {
match read_child_mem(notif_fd, notif.id, notif.pid, times_ptr, 32) {
Ok(data) => {
let mut ts: [libc::timespec; 2] = unsafe { std::mem::zeroed() };
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), &mut ts as *mut _ as *mut u8, 32);
}
Some(ts)
}
Err(_) => return NotifAction::Errno(libc::EFAULT),
}
} else {
None
};
let c_path = match path_cstr(&real_path, libc::ENOENT) {
Ok(c) => c,
Err(a) => return a,
};
let times_raw = times.as_ref().map(|t| t.as_ptr()).unwrap_or(std::ptr::null());
if unsafe { libc::utimensat(libc::AT_FDCWD, c_path.as_ptr(), times_raw, flags) } < 0 {
return NotifAction::Errno(last_errno(libc::EIO));
}
NotifAction::ReturnValue(0)
}
fn notif_with_args(notif: &SeccompNotif, args: [u64; 6]) -> SeccompNotif {
let mut copy = *notif;
copy.data.args = args;
copy
}
pub(crate) async fn handle_chroot_legacy_open(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], notif.data.args[1], notif.data.args[2], 0, 0,
]);
handle_chroot_open(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_stat(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], notif.data.args[1], 0, 0, 0,
]);
handle_chroot_stat(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_lstat(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], notif.data.args[1], libc::AT_SYMLINK_NOFOLLOW as u64,
0, 0,
]);
handle_chroot_stat(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_access(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let mut synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], 0, 0, 0, 0,
]);
synth.data.nr = libc::SYS_faccessat as i32;
handle_chroot_stat(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_readlink(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], notif.data.args[1], notif.data.args[2], 0, 0,
]);
handle_chroot_readlink(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_unlink(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let mut synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], 0, 0, 0, 0,
]);
synth.data.nr = libc::SYS_unlinkat as i32;
handle_chroot_write(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_rmdir(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let mut synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], libc::AT_REMOVEDIR as u64,
0, 0, 0,
]);
synth.data.nr = libc::SYS_unlinkat as i32;
handle_chroot_write(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_mkdir(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let mut synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], notif.data.args[1], 0, 0, 0,
]);
synth.data.nr = libc::SYS_mkdirat as i32;
handle_chroot_write(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_rename(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let mut synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], libc::AT_FDCWD as u64,
notif.data.args[1], 0, 0,
]);
synth.data.nr = libc::SYS_renameat2 as i32;
handle_chroot_write(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_symlink(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let mut synth = notif_with_args(notif, [
notif.data.args[0], libc::AT_FDCWD as u64,
notif.data.args[1], 0, 0, 0,
]);
synth.data.nr = libc::SYS_symlinkat as i32;
handle_chroot_write(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_link(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let mut synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], libc::AT_FDCWD as u64,
notif.data.args[1], 0, 0,
]);
synth.data.nr = libc::SYS_linkat as i32;
handle_chroot_write(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_chmod(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
) -> NotifAction {
let mut synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], notif.data.args[1], 0, 0, 0,
]);
synth.data.nr = libc::SYS_fchmodat as i32;
handle_chroot_write(&synth, chroot_state, cow_state, notif_fd, ctx).await
}
pub(crate) async fn handle_chroot_legacy_chown(
notif: &SeccompNotif,
chroot_state: &Arc<Mutex<ChrootState>>,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
ctx: &ChrootCtx<'_>,
nofollow: bool,
) -> NotifAction {
let flags = if nofollow { libc::AT_SYMLINK_NOFOLLOW as u64 } else { 0 };
let mut synth = notif_with_args(notif, [
libc::AT_FDCWD as u64,
notif.data.args[0], notif.data.args[1], notif.data.args[2], flags,
0,
]);
synth.data.nr = libc::SYS_fchownat as i32;
handle_chroot_write(&synth, chroot_state, cow_state, notif_fd, ctx).await
}