shadow_rs/host/syscall/handler/
clone.rsuse linux_api::capability::{user_cap_data, user_cap_header, LINUX_CAPABILITY_VERSION_3};
use linux_api::errno::Errno;
use linux_api::posix_types::kernel_pid_t;
use linux_api::sched::CloneFlags;
use linux_api::signal::Signal;
use log::{debug, trace, warn};
use shadow_shim_helper_rs::explicit_drop::ExplicitDropper;
use shadow_shim_helper_rs::rootedcell::rc::RootedRc;
use shadow_shim_helper_rs::rootedcell::refcell::RootedRefCell;
use shadow_shim_helper_rs::syscall_types::ForeignPtr;
use crate::host::descriptor::descriptor_table::DescriptorTable;
use crate::host::process::ProcessId;
use crate::host::thread::Thread;
use super::{SyscallContext, SyscallHandler};
impl SyscallHandler {
fn clone_internal(
ctx: &mut SyscallContext,
flags: CloneFlags,
exit_signal: Option<Signal>,
child_stack: ForeignPtr<()>,
ptid: ForeignPtr<kernel_pid_t>,
ctid: ForeignPtr<kernel_pid_t>,
newtls: u64,
) -> Result<kernel_pid_t, Errno> {
let mut handled_flags = CloneFlags::empty();
let mut native_flags = CloneFlags::empty();
let native_ctid = ForeignPtr::<kernel_pid_t>::null();
let native_ptid = ForeignPtr::<kernel_pid_t>::null();
let native_child_stack = child_stack;
let native_newtls = newtls;
if flags.contains(CloneFlags::CLONE_THREAD) {
if !flags.contains(CloneFlags::CLONE_SIGHAND) {
debug!("Missing CLONE_SIGHAND");
return Err(Errno::EINVAL);
}
if !flags.contains(CloneFlags::CLONE_SETTLS) {
warn!("CLONE_THREAD without CLONE_TLS not supported by shadow");
return Err(Errno::ENOTSUP);
}
if exit_signal.is_some() {
warn!("Exit signal is unimplemented");
return Err(Errno::ENOTSUP);
}
native_flags.insert(CloneFlags::CLONE_THREAD);
native_flags.insert(CloneFlags::CLONE_SIGHAND);
native_flags.insert(CloneFlags::CLONE_FS);
native_flags.insert(CloneFlags::CLONE_FILES);
native_flags.insert(CloneFlags::CLONE_SYSVSEM);
handled_flags.insert(CloneFlags::CLONE_THREAD);
} else {
if ctx.objs.process.memory_borrow().has_mapper() {
warn!("Fork with memory mapper unimplemented");
return Err(Errno::ENOTSUP);
}
native_flags.insert(CloneFlags::CLONE_PARENT);
}
if flags.contains(CloneFlags::CLONE_SIGHAND) {
if !flags.contains(CloneFlags::CLONE_VM) {
debug!("Missing CLONE_VM");
return Err(Errno::EINVAL);
}
handled_flags.insert(CloneFlags::CLONE_SIGHAND);
}
if flags.contains(CloneFlags::CLONE_FS) {
handled_flags.insert(CloneFlags::CLONE_FS);
}
let desc_table = if flags.contains(CloneFlags::CLONE_FILES) {
RootedRc::clone(ctx.objs.thread.descriptor_table(), ctx.objs.host.root())
} else {
let root = ctx.objs.host.root();
let table: DescriptorTable = ctx
.objs
.thread
.descriptor_table_borrow(ctx.objs.host)
.clone();
RootedRc::new(root, RootedRefCell::new(root, table))
};
let desc_table = ExplicitDropper::new(desc_table, |desc_table| {
desc_table.explicit_drop_recursive(ctx.objs.host.root(), ctx.objs.host);
});
handled_flags.insert(CloneFlags::CLONE_FILES);
if flags.contains(CloneFlags::CLONE_SETTLS) {
native_flags.insert(CloneFlags::CLONE_SETTLS);
handled_flags.insert(CloneFlags::CLONE_SETTLS);
}
if flags.contains(CloneFlags::CLONE_VFORK) {
warn_once_then_debug!(
"Ignoring CLONE_VFORK (and CLONE_VM if set). In *typical* usage this won't \
result in incorrect behavior."
);
handled_flags.insert(CloneFlags::CLONE_VFORK);
}
if flags.contains(CloneFlags::CLONE_VM) {
if flags.contains(CloneFlags::CLONE_THREAD) {
native_flags.insert(CloneFlags::CLONE_VM);
} else if flags.contains(CloneFlags::CLONE_VFORK) {
} else {
warn!("CLONE_VM without CLONE_THREAD and without CLONE_VFORK unsupported");
return Err(Errno::ENOTSUP);
}
handled_flags.insert(CloneFlags::CLONE_VM);
}
if flags.contains(CloneFlags::CLONE_SYSVSEM) {
handled_flags.insert(CloneFlags::CLONE_SYSVSEM);
}
let do_parent_settid = flags.contains(CloneFlags::CLONE_PARENT_SETTID);
handled_flags.insert(CloneFlags::CLONE_PARENT_SETTID);
let do_child_settid = flags.contains(CloneFlags::CLONE_CHILD_SETTID);
handled_flags.insert(CloneFlags::CLONE_CHILD_SETTID);
let do_child_cleartid = flags.contains(CloneFlags::CLONE_CHILD_CLEARTID);
handled_flags.insert(CloneFlags::CLONE_CHILD_CLEARTID);
let do_copy_sighandlers = if flags.contains(CloneFlags::CLONE_CLEAR_SIGHAND) {
if flags.contains(CloneFlags::CLONE_SIGHAND) {
return Err(Errno::EINVAL);
}
false
} else {
!flags.contains(CloneFlags::CLONE_SIGHAND)
};
handled_flags.insert(CloneFlags::CLONE_CLEAR_SIGHAND);
if flags.contains(CloneFlags::CLONE_PARENT) {
handled_flags.insert(CloneFlags::CLONE_PARENT);
}
let unhandled_flags = flags.difference(handled_flags);
if !unhandled_flags.is_empty() {
warn!("Unhandled clone flags: {unhandled_flags:?}");
return Err(Errno::ENOTSUP);
}
let child_mthread = ctx.objs.thread.mthread().native_clone(
ctx.objs,
native_flags,
native_child_stack,
native_ptid,
native_ctid,
native_newtls,
)?;
let child_tid = ctx.objs.host.get_new_thread_id();
let child_pid = if flags.contains(CloneFlags::CLONE_THREAD) {
ctx.objs.process.id()
} else {
ProcessId::from(child_tid)
};
let child_thread = Thread::wrap_mthread(
ctx.objs.host,
child_mthread,
desc_table.into_value(),
child_pid,
child_tid,
)?;
let childrc = ExplicitDropper::new(
RootedRc::new(
ctx.objs.host.root(),
RootedRefCell::new(ctx.objs.host.root(), child_thread),
),
|childrc| {
childrc.explicit_drop_recursive(ctx.objs.host.root(), ctx.objs.host);
},
);
let child_process_rc;
let child_process_borrow;
let child_process;
if flags.contains(CloneFlags::CLONE_THREAD) {
child_process_borrow = None;
child_process = ctx.objs.process;
ctx.objs
.process
.add_thread(ctx.objs.host, childrc.into_value());
} else {
let process = ctx
.objs
.process
.borrow_as_runnable()
.unwrap()
.new_forked_process(ctx.objs.host, flags, exit_signal, childrc.into_value());
child_process_rc = Some(ExplicitDropper::new(
process.clone(ctx.objs.host.root()),
|x| {
x.explicit_drop_recursive(ctx.objs.host.root(), ctx.objs.host);
},
));
child_process_borrow = Some(
child_process_rc
.as_ref()
.unwrap()
.borrow(ctx.objs.host.root()),
);
child_process = child_process_borrow.as_ref().unwrap();
ctx.objs
.host
.add_and_schedule_forked_process(ctx.objs.host, process);
}
if do_parent_settid {
ctx.objs
.process
.memory_borrow_mut()
.write(ptid, &kernel_pid_t::from(child_tid))?;
}
if do_child_settid {
child_process
.memory_borrow_mut()
.write(ctid, &kernel_pid_t::from(child_tid))?;
}
if do_child_cleartid {
let childrc = child_process.thread_borrow(child_tid).unwrap();
let child = childrc.borrow(ctx.objs.host.root());
child.set_tid_address(ctid);
}
if do_copy_sighandlers {
let shmem_lock = ctx.objs.host.shim_shmem_lock_borrow_mut().unwrap();
let parent_shmem = ctx.objs.process.shmem();
let parent_shmem_prot = parent_shmem.protected.borrow(&shmem_lock.root);
let child_shmem = child_process_borrow.as_ref().unwrap().shmem();
let mut child_shmem_prot = child_shmem.protected.borrow_mut(&shmem_lock.root);
unsafe { child_shmem_prot.clone_signal_actions(&parent_shmem_prot) };
}
Ok(kernel_pid_t::from(child_tid))
}
log_syscall!(
clone,
kernel_pid_t,
CloneFlags,
*const std::ffi::c_void,
*const kernel_pid_t,
*const kernel_pid_t,
*const std::ffi::c_void,
);
pub fn clone(
ctx: &mut SyscallContext,
flags_and_exit_signal: i32,
child_stack: ForeignPtr<()>,
ptid: ForeignPtr<kernel_pid_t>,
ctid: ForeignPtr<kernel_pid_t>,
newtls: u64,
) -> Result<kernel_pid_t, Errno> {
let raw_flags = flags_and_exit_signal as u32 & !0xff;
let raw_exit_signal = (flags_and_exit_signal as u32 & 0xff) as i32;
let Some(flags) = CloneFlags::from_bits(raw_flags as u64) else {
debug!("Couldn't parse clone flags: {raw_flags:x}");
return Err(Errno::EINVAL);
};
let exit_signal = if raw_exit_signal == 0 {
None
} else {
let Ok(exit_signal) = Signal::try_from(raw_exit_signal) else {
debug!("Bad exit signal: {raw_exit_signal:?}");
return Err(Errno::EINVAL);
};
Some(exit_signal)
};
Self::clone_internal(ctx, flags, exit_signal, child_stack, ptid, ctid, newtls)
}
log_syscall!(
clone3,
kernel_pid_t,
*const linux_api::sched::clone_args,
usize,
);
pub fn clone3(
ctx: &mut SyscallContext,
args: ForeignPtr<linux_api::sched::clone_args>,
args_size: usize,
) -> Result<kernel_pid_t, Errno> {
if args_size != std::mem::size_of::<linux_api::sched::clone_args>() {
return Err(Errno::EINVAL);
}
let args = ctx.objs.process.memory_borrow().read(args)?;
trace!("clone3 args: {args:?}");
let Some(flags) = CloneFlags::from_bits(args.flags) else {
debug!("Couldn't parse clone flags: {:x}", args.flags);
return Err(Errno::EINVAL);
};
let exit_signal = if args.exit_signal == 0 {
None
} else {
let Ok(exit_signal) = Signal::try_from(args.exit_signal as i32) else {
debug!("Bad signal number: {}", args.exit_signal);
return Err(Errno::EINVAL);
};
Some(exit_signal)
};
Self::clone_internal(
ctx,
flags,
exit_signal,
ForeignPtr::<()>::from(args.stack + args.stack_size),
ForeignPtr::<kernel_pid_t>::from_raw_ptr(args.parent_tid as *mut kernel_pid_t),
ForeignPtr::<kernel_pid_t>::from_raw_ptr(args.child_tid as *mut kernel_pid_t),
args.tls,
)
}
log_syscall!(fork, kernel_pid_t);
pub fn fork(ctx: &mut SyscallContext) -> Result<kernel_pid_t, Errno> {
Self::clone_internal(
ctx,
CloneFlags::empty(),
Some(Signal::SIGCHLD),
ForeignPtr::<()>::null(),
ForeignPtr::<kernel_pid_t>::null(),
ForeignPtr::<kernel_pid_t>::null(),
0,
)
}
log_syscall!(vfork, kernel_pid_t);
pub fn vfork(ctx: &mut SyscallContext) -> Result<kernel_pid_t, Errno> {
Self::clone_internal(
ctx,
CloneFlags::CLONE_VFORK | CloneFlags::CLONE_VM,
Some(Signal::SIGCHLD),
ForeignPtr::<()>::null(),
ForeignPtr::<kernel_pid_t>::null(),
ForeignPtr::<kernel_pid_t>::null(),
0,
)
}
log_syscall!(gettid, kernel_pid_t);
pub fn gettid(ctx: &mut SyscallContext) -> Result<kernel_pid_t, Errno> {
Ok(kernel_pid_t::from(ctx.objs.thread.id()))
}
log_syscall!(
capget,
std::ffi::c_int,
*const std::ffi::c_void,
*const std::ffi::c_void,
);
pub fn capget(
ctx: &mut SyscallContext,
hdrp: ForeignPtr<user_cap_header>,
datap: ForeignPtr<[user_cap_data; 2]>,
) -> Result<(), Errno> {
let hdrp = ctx.objs.process.memory_borrow().read(hdrp)?;
if hdrp.version != LINUX_CAPABILITY_VERSION_3 {
warn_once_then_debug!(
"The version of Linux capabilities is not supported ({})",
hdrp.version
);
return Err(Errno::EINVAL);
}
if !datap.is_null() {
let empty = user_cap_data {
effective: 0,
permitted: 0,
inheritable: 0,
};
ctx.objs
.process
.memory_borrow_mut()
.write(datap, &[empty, empty])?;
}
Ok(())
}
log_syscall!(
capset,
std::ffi::c_int,
*const std::ffi::c_void,
*const std::ffi::c_void,
);
pub fn capset(
ctx: &mut SyscallContext,
hdrp: ForeignPtr<user_cap_header>,
datap: ForeignPtr<[user_cap_data; 2]>,
) -> Result<(), Errno> {
let hdrp = ctx.objs.process.memory_borrow().read(hdrp)?;
if hdrp.version != LINUX_CAPABILITY_VERSION_3 {
warn_once_then_debug!(
"The version of Linux capabilities is not supported ({})",
hdrp.version
);
return Err(Errno::EINVAL);
}
let datap: [_; 2] = ctx.objs.process.memory_borrow().read(datap)?;
for data in &datap {
if data.effective != 0 || data.permitted != 0 || data.inheritable != 0 {
warn_once_then_debug!("Setting Linux capabilities is not supported");
return Err(Errno::EINVAL);
}
}
Ok(())
}
}