use std::os::unix::io::{FromRawFd, OwnedFd, RawFd};
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::procfs::{build_dirent64, DT_DIR, DT_LNK, DT_REG};
use crate::seccomp::notif::{read_child_mem, write_child_mem, NotifAction};
use crate::seccomp::state::CowState;
use crate::sys::structs::SeccompNotif;
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 resolve_at_path(notif: &SeccompNotif, dirfd: i64, path: &str) -> String {
if std::path::Path::new(path).is_absolute() {
return path.to_string();
}
let dirfd32 = dirfd as i32;
if dirfd32 == libc::AT_FDCWD {
if let Ok(cwd) = std::fs::read_link(format!("/proc/{}/cwd", notif.pid)) {
return format!("{}/{}", cwd.display(), path);
}
return path.to_string();
}
if let Ok(base) = std::fs::read_link(format!("/proc/{}/fd/{}", notif.pid, dirfd)) {
format!("{}/{}", base.display(), path)
} else {
path.to_string()
}
}
pub(crate) async fn handle_cow_open(
notif: &SeccompNotif,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
) -> NotifAction {
use crate::cow::seccomp::CowOpenPlan;
let nr = notif.data.nr as i64;
let (path_ptr, dirfd, flags) = if nr == libc::SYS_open as i64 {
(notif.data.args[0], libc::AT_FDCWD as i64, notif.data.args[1])
} else {
(notif.data.args[1], notif.data.args[0] as i64, notif.data.args[2])
};
let rel_path = match read_path(notif, path_ptr, notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
let path = resolve_at_path(notif, dirfd, &rel_path);
let plan = {
let mut st = cow_state.lock().await;
let cow = match st.branch.as_mut() {
Some(c) => c,
None => return NotifAction::Continue,
};
if !cow.matches(&path) {
return NotifAction::Continue;
}
const WRITE_FLAGS: u64 = 0o1 | 0o2 | 0o100 | 0o1000 | 0o2000;
let is_write = flags & WRITE_FLAGS != 0;
if !is_write && !cow.needs_read_intercept(&path) {
return NotifAction::Continue;
}
match cow.prepare_open(&path, flags) {
Ok(plan) => plan,
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
Err(crate::error::BranchError::Exists) => return NotifAction::Errno(libc::EEXIST),
Err(_) => return NotifAction::Continue,
}
};
let real_path = match plan {
CowOpenPlan::Skip => return NotifAction::Continue,
CowOpenPlan::Resolved(p) | CowOpenPlan::UpperReady { upper: p } => p,
CowOpenPlan::NeedsCopy { upper, lower, file_size, rel_path: _rel } => {
let upper_clone = upper.clone();
let copy_result = tokio::task::spawn_blocking(move || {
crate::cow::seccomp::SeccompCowBranch::execute_copy(&upper_clone, &lower)
}).await;
match copy_result {
Ok(Ok(())) => upper,
Ok(Err(_)) | Err(_) => {
let mut st = cow_state.lock().await;
if let Some(cow) = st.branch.as_mut() {
cow.rollback_copy(file_size);
}
return NotifAction::Continue;
}
}
}
};
let open_flags = (flags & !(libc::O_EXCL as u64)) as i32;
let c_path = match std::ffi::CString::new(real_path.to_str().unwrap_or("")) {
Ok(c) => c,
Err(_) => return NotifAction::Continue,
};
let fd = unsafe { libc::open(c_path.as_ptr(), open_flags, 0o666) };
if fd < 0 {
return NotifAction::Continue;
}
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 }
}
enum CowWriteOp {
Unlink { path: String, is_dir: bool },
Mkdir { path: String },
Rename { old_path: String, new_path: String },
Symlink { target: String, linkpath: String },
Link { old_path: String, new_path: String },
Chmod { path: String, mode: u32 },
Chown { path: String, uid: u32, gid: u32 },
Truncate { path: String, length: i64 },
}
fn read_resolved(
notif: &SeccompNotif,
path_arg: usize,
dirfd_arg: Option<usize>,
notif_fd: RawFd,
) -> Option<String> {
let raw = read_path(notif, notif.data.args[path_arg], notif_fd)?;
match dirfd_arg {
Some(i) => Some(resolve_at_path(notif, notif.data.args[i] as i64, &raw)),
None => Some(raw),
}
}
fn parse_cow_write(notif: &SeccompNotif, notif_fd: RawFd) -> Option<CowWriteOp> {
let nr = notif.data.nr as i64;
if nr == libc::SYS_unlinkat {
let path = read_resolved(notif, 1, Some(0), notif_fd)?;
let is_dir = (notif.data.args[2] & libc::AT_REMOVEDIR as u64) != 0;
return Some(CowWriteOp::Unlink { path, is_dir });
}
if nr == libc::SYS_mkdirat {
return Some(CowWriteOp::Mkdir { path: read_resolved(notif, 1, Some(0), notif_fd)? });
}
if nr == libc::SYS_renameat2 {
let old_path = read_resolved(notif, 1, Some(0), notif_fd)?;
let new_path = read_resolved(notif, 3, Some(2), notif_fd)?;
return Some(CowWriteOp::Rename { old_path, new_path });
}
if nr == libc::SYS_symlinkat {
let target = read_path(notif, notif.data.args[0], notif_fd)?;
let linkpath = read_resolved(notif, 2, Some(1), notif_fd)?;
return Some(CowWriteOp::Symlink { target, linkpath });
}
if nr == libc::SYS_linkat {
let old_path = read_resolved(notif, 1, Some(0), notif_fd)?;
let new_path = read_resolved(notif, 3, Some(2), notif_fd)?;
return Some(CowWriteOp::Link { old_path, new_path });
}
if nr == libc::SYS_fchmodat {
let path = read_resolved(notif, 1, Some(0), notif_fd)?;
return Some(CowWriteOp::Chmod { path, mode: (notif.data.args[2] & 0o7777) as u32 });
}
if nr == libc::SYS_fchownat {
let path = read_resolved(notif, 1, Some(0), notif_fd)?;
return Some(CowWriteOp::Chown { path, uid: notif.data.args[2] as u32, gid: notif.data.args[3] as u32 });
}
if nr == libc::SYS_unlink as i64 {
return Some(CowWriteOp::Unlink { path: read_resolved(notif, 0, None, notif_fd)?, is_dir: false });
}
if nr == libc::SYS_rmdir as i64 {
return Some(CowWriteOp::Unlink { path: read_resolved(notif, 0, None, notif_fd)?, is_dir: true });
}
if nr == libc::SYS_mkdir as i64 {
return Some(CowWriteOp::Mkdir { path: read_resolved(notif, 0, None, notif_fd)? });
}
if nr == libc::SYS_rename as i64 {
let old_path = read_resolved(notif, 0, None, notif_fd)?;
let new_path = read_resolved(notif, 1, None, notif_fd)?;
return Some(CowWriteOp::Rename { old_path, new_path });
}
if nr == libc::SYS_symlink as i64 {
let target = read_path(notif, notif.data.args[0], notif_fd)?;
let linkpath = read_resolved(notif, 1, None, notif_fd)?;
return Some(CowWriteOp::Symlink { target, linkpath });
}
if nr == libc::SYS_link as i64 {
let old_path = read_resolved(notif, 0, None, notif_fd)?;
let new_path = read_resolved(notif, 1, None, notif_fd)?;
return Some(CowWriteOp::Link { old_path, new_path });
}
if nr == libc::SYS_chmod as i64 {
let path = read_resolved(notif, 0, None, notif_fd)?;
return Some(CowWriteOp::Chmod { path, mode: (notif.data.args[1] & 0o7777) as u32 });
}
if nr == libc::SYS_chown as i64 || nr == libc::SYS_lchown as i64 {
let path = read_resolved(notif, 0, None, notif_fd)?;
return Some(CowWriteOp::Chown { path, uid: notif.data.args[1] as u32, gid: notif.data.args[2] as u32 });
}
if nr == libc::SYS_truncate {
let path = read_resolved(notif, 0, None, notif_fd)?;
return Some(CowWriteOp::Truncate { path, length: notif.data.args[1] as i64 });
}
None
}
fn cow_result(r: Result<bool, crate::error::BranchError>) -> NotifAction {
match r {
Ok(true) => NotifAction::ReturnValue(0),
Err(crate::error::BranchError::QuotaExceeded) => NotifAction::Errno(libc::ENOSPC),
_ => NotifAction::Continue,
}
}
fn unlink_result(r: Result<bool, i32>) -> NotifAction {
match r {
Ok(true) => NotifAction::ReturnValue(0),
Err(errno) => NotifAction::Errno(errno),
_ => NotifAction::Continue,
}
}
fn cow_copy_rel<'a>(
op: &'a CowWriteOp,
cow: &crate::cow::seccomp::SeccompCowBranch,
) -> Option<(&'a str, String)> {
let (match_path, copy_path) = match op {
CowWriteOp::Chmod { ref path, .. }
| CowWriteOp::Chown { ref path, .. }
| CowWriteOp::Truncate { ref path, .. } => (path.as_str(), path.as_str()),
CowWriteOp::Rename { ref old_path, .. } => (old_path.as_str(), old_path.as_str()),
CowWriteOp::Link { ref old_path, ref new_path, .. } => (new_path.as_str(), old_path.as_str()),
_ => return None,
};
if !cow.matches(match_path) {
return None;
}
cow.safe_rel(copy_path)
.map(|rel| (match_path, rel))
}
async fn execute_deferred_copy(
cow_state: &Arc<Mutex<CowState>>,
upper: std::path::PathBuf,
lower: std::path::PathBuf,
file_size: u64,
) -> Option<std::path::PathBuf> {
let upper_clone = upper.clone();
let copy_result = tokio::task::spawn_blocking(move || {
crate::cow::seccomp::SeccompCowBranch::execute_copy(&upper_clone, &lower)
}).await;
match copy_result {
Ok(Ok(())) => Some(upper),
_ => {
let mut st = cow_state.lock().await;
if let Some(cow) = st.branch.as_mut() {
cow.rollback_copy(file_size);
}
None
}
}
}
pub(crate) async fn handle_cow_write(
notif: &SeccompNotif,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
) -> NotifAction {
let op = match parse_cow_write(notif, notif_fd) {
Some(op) => op,
None => return NotifAction::Continue,
};
let copy_plan = {
let mut st = cow_state.lock().await;
let cow = match st.branch.as_mut() {
Some(c) => c,
None => return NotifAction::Continue,
};
match cow_copy_rel(&op, cow) {
Some((_match_path, ref rel)) => {
match cow.prepare_copy(rel) {
Ok(plan) => Some(plan),
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
Err(_) => return NotifAction::Continue,
}
}
None => None,
}
};
if let Some(crate::cow::seccomp::CowCopyPlan::NeedsCopy { upper, lower, file_size }) = copy_plan {
if execute_deferred_copy(cow_state, upper, lower, file_size).await.is_none() {
return NotifAction::Continue;
}
}
let mut st = cow_state.lock().await;
let cow = match st.branch.as_mut() {
Some(c) => c,
None => return NotifAction::Continue,
};
match op {
CowWriteOp::Unlink { ref path, is_dir } => {
if !cow.matches(path) { return NotifAction::Continue; }
unlink_result(cow.handle_unlink(path, is_dir))
}
CowWriteOp::Mkdir { ref path } => {
if !cow.matches(path) { return NotifAction::Continue; }
cow_result(cow.handle_mkdir(path))
}
CowWriteOp::Rename { ref old_path, ref new_path } => {
if !cow.matches(old_path) { return NotifAction::Continue; }
cow_result(cow.handle_rename(old_path, new_path))
}
CowWriteOp::Symlink { ref target, ref linkpath } => {
if !cow.matches(linkpath) { return NotifAction::Continue; }
cow_result(cow.handle_symlink(target, linkpath))
}
CowWriteOp::Link { ref old_path, ref new_path } => {
if !cow.matches(new_path) { return NotifAction::Continue; }
cow_result(cow.handle_link(old_path, new_path))
}
CowWriteOp::Chmod { ref path, mode } => {
if !cow.matches(path) { return NotifAction::Continue; }
cow_result(cow.handle_chmod(path, mode))
}
CowWriteOp::Chown { ref path, uid, gid } => {
if !cow.matches(path) { return NotifAction::Continue; }
cow_result(cow.handle_chown(path, uid, gid))
}
CowWriteOp::Truncate { ref path, length } => {
if !cow.matches(path) { return NotifAction::Continue; }
cow_result(cow.handle_truncate(path, length))
}
}
}
pub(crate) const SYS_FACCESSAT2: i64 = 439;
pub(crate) async fn handle_cow_access(
notif: &SeccompNotif,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
) -> NotifAction {
let nr = notif.data.nr as i64;
let (path, mode) = if nr == libc::SYS_access as i64 {
let p = match read_path(notif, notif.data.args[0], notif_fd) {
Some(p) => p,
None => return NotifAction::Continue,
};
(p, notif.data.args[1] as i32)
} else {
let dirfd = notif.data.args[0] as i64;
let p = match read_path(notif, notif.data.args[1], notif_fd) {
Some(p) => resolve_at_path(notif, dirfd, &p),
None => return NotifAction::Continue,
};
(p, notif.data.args[2] as i32)
};
if mode & libc::W_OK == 0 {
return NotifAction::Continue;
}
let st = cow_state.lock().await;
let cow = match st.branch.as_ref() {
Some(c) => c,
None => return NotifAction::Continue,
};
if !cow.matches(&path) {
return NotifAction::Continue;
}
if std::path::Path::new(&path).exists() {
return NotifAction::ReturnValue(0);
}
NotifAction::Continue
}
pub(crate) async fn handle_cow_utimensat(
notif: &SeccompNotif,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
) -> 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) => resolve_at_path(notif, dirfd, &p),
None => return NotifAction::Continue,
};
let upper_path = {
let mut st = cow_state.lock().await;
let cow = match st.branch.as_mut() {
Some(c) => c,
None => return NotifAction::Continue,
};
if !cow.matches(&path) {
return NotifAction::Continue;
}
match cow.handle_utimensat(&path) {
Ok(Some(p)) => p,
Ok(None) => return NotifAction::Continue,
Err(crate::error::BranchError::QuotaExceeded) => return NotifAction::Errno(libc::ENOSPC),
Err(_) => return NotifAction::Continue,
}
};
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 std::ffi::CString::new(upper_path.to_str().unwrap_or("")) {
Ok(c) => c,
Err(_) => return NotifAction::Continue,
};
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(libc::EIO);
}
NotifAction::ReturnValue(0)
}
pub(crate) async fn handle_cow_stat(
notif: &SeccompNotif,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
) -> NotifAction {
let nr = notif.data.nr as i64;
let dirfd = notif.data.args[0] as i64;
let path = match read_path(notif, notif.data.args[1], notif_fd) {
Some(p) => resolve_at_path(notif, dirfd, &p),
None => return NotifAction::Continue,
};
let st = cow_state.lock().await;
let cow = match st.branch.as_ref() {
Some(c) => c,
None => return NotifAction::Continue,
};
if !cow.has_changes() || !cow.matches(&path) {
return NotifAction::Continue;
}
let real_path = match cow.handle_stat(&path) {
Some(p) => p,
None => {
return NotifAction::Errno(libc::ENOENT);
}
};
drop(st);
if nr == libc::SYS_faccessat || nr == SYS_FACCESSAT2 {
if real_path.exists() || real_path.is_symlink() {
return NotifAction::ReturnValue(0);
}
return NotifAction::Errno(libc::ENOENT);
}
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(&real_path)
} else {
std::fs::symlink_metadata(&real_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; 144];
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_cow_statx(
notif: &SeccompNotif,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
) -> NotifAction {
let dirfd = notif.data.args[0] as i64;
let path = match read_path(notif, notif.data.args[1], notif_fd) {
Some(p) => resolve_at_path(notif, dirfd, &p),
None => return NotifAction::Continue,
};
let st = cow_state.lock().await;
let cow = match st.branch.as_ref() {
Some(c) => c,
None => return NotifAction::Continue,
};
if !cow.has_changes() || !cow.matches(&path) {
return NotifAction::Continue;
}
match cow.handle_stat(&path) {
Some(_) => NotifAction::Continue, None => NotifAction::Errno(libc::ENOENT), }
}
pub(crate) async fn handle_cow_readlink(
notif: &SeccompNotif,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
) -> NotifAction {
let dirfd = notif.data.args[0] as i64;
let path = match read_path(notif, notif.data.args[1], notif_fd) {
Some(p) => resolve_at_path(notif, dirfd, &p),
None => return NotifAction::Continue,
};
let buf_addr = notif.data.args[2];
let bufsiz = (notif.data.args[3] & 0xFFFFFFFF) as usize;
let st = cow_state.lock().await;
let cow = match st.branch.as_ref() {
Some(c) => c,
None => return NotifAction::Continue,
};
if !cow.has_changes() || !cow.matches(&path) {
return NotifAction::Continue;
}
let target = match cow.handle_readlink(&path) {
Some(t) => t,
None => return NotifAction::Errno(libc::ENOENT),
};
drop(st);
let target_bytes = target.as_bytes();
let write_len = target_bytes.len().min(bufsiz);
if write_child_mem(notif_fd, notif.id, notif.pid, buf_addr, &target_bytes[..write_len]).is_err()
{
return NotifAction::Continue;
}
NotifAction::ReturnValue(write_len as i64)
}
pub(crate) async fn handle_cow_getdents(
notif: &SeccompNotif,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
) -> NotifAction {
let pid = notif.pid;
let child_fd = (notif.data.args[0] & 0xFFFFFFFF) as u32;
let buf_addr = notif.data.args[1];
let buf_size = (notif.data.args[2] & 0xFFFFFFFF) as usize;
let link_path = format!("/proc/{}/fd/{}", pid, child_fd);
let target = match std::fs::read_link(&link_path) {
Ok(t) => t.to_string_lossy().into_owned(),
Err(_) => return NotifAction::Continue,
};
let mut st = cow_state.lock().await;
let cow = match st.branch.as_ref() {
Some(c) => c,
None => return NotifAction::Continue,
};
if !cow.has_changes() || !cow.matches(&target) {
return NotifAction::Continue;
}
let cache_key = (pid as i32, child_fd);
if let Some((cached_target, entries)) = st.dir_cache.get(&cache_key) {
if *cached_target != target {
st.dir_cache.remove(&cache_key);
} else if entries.is_empty() {
st.dir_cache.remove(&cache_key);
return NotifAction::ReturnValue(0);
}
}
if !st.dir_cache.contains_key(&cache_key) {
let cow = st.branch.as_ref().unwrap();
let rel_path = cow.safe_rel(&target).unwrap_or_else(|| ".".to_string());
let merged = cow.list_merged_dir(&rel_path);
let upper_dir = cow.upper_dir().join(&rel_path);
let lower_dir = cow.workdir().join(&rel_path);
let mut entries = Vec::new();
let mut d_off: i64 = 0;
for name in &merged {
d_off += 1;
let upper_p = upper_dir.join(name);
let lower_p = lower_dir.join(name);
let check = if upper_p.exists() || upper_p.is_symlink() {
&upper_p
} else {
&lower_p
};
let d_type = if check.is_dir() {
DT_DIR
} else if check.is_symlink() {
DT_LNK
} else {
DT_REG
};
use std::os::unix::fs::MetadataExt;
let d_ino = std::fs::symlink_metadata(check)
.map(|m| m.ino())
.unwrap_or(0);
entries.push(build_dirent64(d_ino, d_off, d_type, name));
}
st.dir_cache.insert(cache_key, (target.clone(), entries));
}
let entries = match st.dir_cache.get_mut(&cache_key) {
Some((_, e)) => e,
None => return NotifAction::Continue,
};
let mut result = Vec::new();
let mut consumed = 0;
for entry in entries.iter() {
if result.len() + entry.len() > buf_size {
break;
}
result.extend_from_slice(entry);
consumed += 1;
}
if consumed > 0 {
entries.drain(..consumed);
}
if entries.is_empty() {
}
drop(st);
if !result.is_empty() {
if write_child_mem(notif_fd, notif.id, pid, buf_addr, &result).is_err() {
return NotifAction::Continue;
}
}
NotifAction::ReturnValue(result.len() as i64)
}
pub(crate) async fn handle_cow_chdir(
notif: &SeccompNotif,
cow_state: &Arc<Mutex<CowState>>,
notif_fd: RawFd,
) -> 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 orig_path_buf_len = path.len() + 1;
let abs_path = if std::path::Path::new(&path).is_absolute() {
path
} else {
match std::fs::read_link(format!("/proc/{}/cwd", notif.pid)) {
Ok(cwd) => cwd.join(&path).to_string_lossy().into_owned(),
Err(_) => return NotifAction::Continue,
}
};
let st = cow_state.lock().await;
let cow = match st.branch.as_ref() {
Some(c) => c,
None => return NotifAction::Continue,
};
if !cow.matches(&abs_path) {
return NotifAction::Continue;
}
let rel = match cow.safe_rel(&abs_path) {
Some(r) => r,
None => return NotifAction::Continue,
};
let upper_path = cow.upper_dir().join(&rel);
drop(st);
if std::path::Path::new(&abs_path).is_dir() {
return NotifAction::Continue;
}
if !upper_path.is_dir() {
return NotifAction::Continue;
}
let c_path = match std::ffi::CString::new(upper_path.to_str().unwrap_or("")) {
Ok(c) => c,
Err(_) => return NotifAction::Continue,
};
let src_fd = unsafe {
libc::open(c_path.as_ptr(), libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC)
};
if src_fd < 0 {
return NotifAction::Errno(libc::ENOENT);
}
let addfd = crate::sys::structs::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,
crate::sys::structs::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 orig_path_buf_len < fd_path.len() {
return NotifAction::Errno(libc::ENOENT);
}
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
}