Skip to main content

shadow_rs/host/syscall/handler/
clone.rs

1use linux_api::capability::{LINUX_CAPABILITY_VERSION_3, user_cap_data, user_cap_header};
2use linux_api::errno::Errno;
3use linux_api::posix_types::kernel_pid_t;
4use linux_api::sched::CloneFlags;
5use linux_api::signal::Signal;
6use log::{debug, trace, warn};
7use shadow_shim_helper_rs::explicit_drop::ExplicitDropper;
8use shadow_shim_helper_rs::rootedcell::rc::RootedRc;
9use shadow_shim_helper_rs::rootedcell::refcell::RootedRefCell;
10use shadow_shim_helper_rs::syscall_types::ForeignPtr;
11
12use crate::host::descriptor::DropPosixRecordLocks;
13use crate::host::descriptor::descriptor_table::DescriptorTable;
14use crate::host::process::ProcessId;
15use crate::host::thread::Thread;
16
17use super::{SyscallContext, SyscallHandler};
18
19impl SyscallHandler {
20    fn clone_internal(
21        ctx: &mut SyscallContext,
22        flags: CloneFlags,
23        exit_signal: Option<Signal>,
24        child_stack: ForeignPtr<()>,
25        ptid: ForeignPtr<kernel_pid_t>,
26        ctid: ForeignPtr<kernel_pid_t>,
27        newtls: u64,
28    ) -> Result<kernel_pid_t, Errno> {
29        trace!(
30            "flags:{flags:?} exit_signal:{exit_signal:?} child_stack:{child_stack:?} ptid:{ptid:?} ctid:{ctid:?} newtls:{newtls:?}"
31        );
32        // We use this for a consistency check to validate that we've inspected
33        // and emulated all of the provided flags.
34        let mut handled_flags = CloneFlags::empty();
35
36        // The parameters that we'll pass to the native clone call.
37        let mut native_flags = CloneFlags::empty();
38
39        // We emulate the flags that would use these, so we always pass NULL to
40        // the native call.
41        let native_ctid = ForeignPtr::<kernel_pid_t>::null();
42        let native_ptid = ForeignPtr::<kernel_pid_t>::null();
43
44        // We use the managed-code provided stack.
45        let native_child_stack = child_stack;
46
47        // We use the managed-code provided newtls.
48        let native_newtls = newtls;
49
50        if flags.contains(CloneFlags::CLONE_THREAD) {
51            // From clone(2):
52            // > Since Linux 2.5.35, the flags mask must also include
53            // > CLONE_SIGHAND if CLONE_THREAD is specified
54            if !flags.contains(CloneFlags::CLONE_SIGHAND) {
55                debug!("Missing CLONE_SIGHAND");
56                return Err(Errno::EINVAL);
57            }
58            if !flags.contains(CloneFlags::CLONE_SETTLS) {
59                // Legal in Linux, but the shim will be broken and behave unpredictably.
60                warn!("CLONE_THREAD without CLONE_TLS not supported by shadow");
61                return Err(Errno::ENOTSUP);
62            }
63            if exit_signal.is_some() {
64                warn!("Exit signal is unimplemented");
65                return Err(Errno::ENOTSUP);
66            }
67            // The native clone call will:
68            // - create a thread.
69            native_flags.insert(CloneFlags::CLONE_THREAD);
70            // - share signal handlers (mandatory anyway)
71            native_flags.insert(CloneFlags::CLONE_SIGHAND);
72            // - share file system info (mostly N/A for shadow, but conventional for threads)
73            native_flags.insert(CloneFlags::CLONE_FS);
74            // - share file descriptors
75            native_flags.insert(CloneFlags::CLONE_FILES);
76            // - share semaphores (mostly N/A for shadow, but conventional for threads)
77            native_flags.insert(CloneFlags::CLONE_SYSVSEM);
78
79            handled_flags.insert(CloneFlags::CLONE_THREAD);
80        } else {
81            // Make shadow the parent process
82            native_flags.insert(CloneFlags::CLONE_PARENT);
83        }
84
85        if flags.contains(CloneFlags::CLONE_SIGHAND) {
86            // From clone(2):
87            // > Since Linux 2.6.0, the flags mask must also include CLONE_VM if
88            // > CLONE_SIGHAND is specified
89            if !flags.contains(CloneFlags::CLONE_VM) {
90                debug!("Missing CLONE_VM");
91                return Err(Errno::EINVAL);
92            }
93            // Currently a no-op since threads always share signal handlers,
94            // and we don't yet support non-CLONE_THREAD.
95            handled_flags.insert(CloneFlags::CLONE_SIGHAND);
96        }
97
98        if flags.contains(CloneFlags::CLONE_FS) {
99            // Currently a no-op since we don't support the related
100            // metadata and syscalls that this affects (e.g. chroot).
101            handled_flags.insert(CloneFlags::CLONE_FS);
102        }
103
104        let desc_table = if flags.contains(CloneFlags::CLONE_FILES) {
105            // Child gets a reference to the same table.
106            RootedRc::clone(ctx.objs.thread.descriptor_table(), ctx.objs.host.root())
107        } else {
108            // Child gets a *copy* of the table.
109            let root = ctx.objs.host.root();
110            let table: DescriptorTable = ctx
111                .objs
112                .thread
113                .descriptor_table_borrow(ctx.objs.host)
114                .clone();
115            RootedRc::new(root, RootedRefCell::new(root, table))
116        };
117        let desc_table = ExplicitDropper::new(desc_table, |desc_table| {
118            // If we drop the table through this object, it means the clone
119            // failed, and we *don't* drop any posix record locks. (Particularly not the current
120            // process's locks).
121            let d = DropPosixRecordLocks::False;
122            desc_table.explicit_drop_recursive(ctx.objs.host.root(), (ctx.objs.host, d));
123        });
124        handled_flags.insert(CloneFlags::CLONE_FILES);
125
126        if flags.contains(CloneFlags::CLONE_SETTLS) {
127            native_flags.insert(CloneFlags::CLONE_SETTLS);
128            handled_flags.insert(CloneFlags::CLONE_SETTLS);
129        }
130
131        if flags.contains(CloneFlags::CLONE_VFORK) {
132            // *Typically* `CLONE_VFORK|CLONE_VM` is used as a "faster fork", and
133            // ignoring it will still work as intended.
134            //
135            // In principle this might not be true if the managed program
136            // actually uses the shared memory with the parent process as a
137            // "feature" and e.g. writes to non-scratch memory, expecting the
138            // parent process to see those writes when it resumes.
139            warn_once_then_debug!(
140                "Ignoring CLONE_VFORK (and CLONE_VM if set). In *typical* usage this won't \
141                result in incorrect behavior."
142            );
143            handled_flags.insert(CloneFlags::CLONE_VFORK);
144        }
145
146        if flags.contains(CloneFlags::CLONE_VM) {
147            if flags.contains(CloneFlags::CLONE_THREAD) {
148                native_flags.insert(CloneFlags::CLONE_VM);
149            } else if flags.contains(CloneFlags::CLONE_VFORK) {
150                // We already handled (warned) about this above.
151            } else {
152                // Haven't seen this in practice.
153                //
154                // Unclear that it'd be safe to ignore. Lack of CLONE_VFORK
155                // (which normally pauses the parent until the child exec's or
156                // exits) implies that this that the child may exist for more
157                // than a brief window before exec'ing.
158                warn!("CLONE_VM without CLONE_THREAD and without CLONE_VFORK unsupported");
159                return Err(Errno::ENOTSUP);
160            }
161            handled_flags.insert(CloneFlags::CLONE_VM);
162        }
163
164        if flags.contains(CloneFlags::CLONE_SYSVSEM) {
165            // Currently a no-op since we don't support sysv semaphores.
166            handled_flags.insert(CloneFlags::CLONE_SYSVSEM);
167        }
168
169        // Handled after native clone
170        let do_parent_settid = flags.contains(CloneFlags::CLONE_PARENT_SETTID);
171        handled_flags.insert(CloneFlags::CLONE_PARENT_SETTID);
172
173        // Handled after native clone
174        let do_child_settid = flags.contains(CloneFlags::CLONE_CHILD_SETTID);
175        handled_flags.insert(CloneFlags::CLONE_CHILD_SETTID);
176
177        // Handled after native clone
178        let do_child_cleartid = flags.contains(CloneFlags::CLONE_CHILD_CLEARTID);
179        handled_flags.insert(CloneFlags::CLONE_CHILD_CLEARTID);
180
181        let do_copy_sighandlers = if flags.contains(CloneFlags::CLONE_CLEAR_SIGHAND) {
182            // clone(2): Specifying this flag together with CLONE_SIGHAND is
183            // nonsensical and disallowed.
184            if flags.contains(CloneFlags::CLONE_SIGHAND) {
185                return Err(Errno::EINVAL);
186            }
187            false
188        } else {
189            // We only need to copy if they're not shared.
190            !flags.contains(CloneFlags::CLONE_SIGHAND)
191        };
192        handled_flags.insert(CloneFlags::CLONE_CLEAR_SIGHAND);
193
194        if flags.contains(CloneFlags::CLONE_PARENT) {
195            // Handled in `new_forked_process` when creating a new process.
196            // No-op when not creating a new process.
197            handled_flags.insert(CloneFlags::CLONE_PARENT);
198        }
199
200        let unhandled_flags = flags.difference(handled_flags);
201        if !unhandled_flags.is_empty() {
202            warn!("Unhandled clone flags: {unhandled_flags:?}");
203            return Err(Errno::ENOTSUP);
204        }
205
206        let child_mthread = ctx.objs.thread.mthread().native_clone(
207            ctx.objs,
208            native_flags,
209            native_child_stack,
210            native_ptid,
211            native_ctid,
212            native_newtls,
213        )?;
214
215        let child_tid = ctx.objs.host.get_new_thread_id();
216        let child_pid = if flags.contains(CloneFlags::CLONE_THREAD) {
217            ctx.objs.process.id()
218        } else {
219            ProcessId::from(child_tid)
220        };
221
222        let child_thread = Thread::wrap_mthread(
223            ctx.objs.host,
224            child_mthread,
225            desc_table.into_value(),
226            child_pid,
227            child_tid,
228        );
229
230        let childrc = ExplicitDropper::new(
231            RootedRc::new(
232                ctx.objs.host.root(),
233                RootedRefCell::new(ctx.objs.host.root(), child_thread),
234            ),
235            |childrc| {
236                childrc.explicit_drop_recursive(ctx.objs.host.root(), ctx.objs.host);
237            },
238        );
239
240        let child_process_rc;
241        let child_process_borrow;
242        let child_process;
243        if flags.contains(CloneFlags::CLONE_THREAD) {
244            child_process_borrow = None;
245            child_process = ctx.objs.process;
246            ctx.objs
247                .process
248                .add_thread(ctx.objs.host, childrc.into_value());
249        } else {
250            let process = ctx
251                .objs
252                .process
253                .borrow_as_runnable()
254                .unwrap()
255                .new_forked_process(ctx.objs.host, flags, exit_signal, childrc.into_value());
256            child_process_rc = Some(ExplicitDropper::new(
257                process.clone(ctx.objs.host.root()),
258                |x| {
259                    x.explicit_drop_recursive(ctx.objs.host.root(), ctx.objs.host);
260                },
261            ));
262            child_process_borrow = Some(
263                child_process_rc
264                    .as_ref()
265                    .unwrap()
266                    .borrow(ctx.objs.host.root()),
267            );
268            child_process = child_process_borrow.as_ref().unwrap();
269            ctx.objs
270                .host
271                .add_and_schedule_forked_process(ctx.objs.host, process);
272        }
273
274        if do_parent_settid {
275            ctx.objs
276                .process
277                .memory_borrow_mut()
278                .write(ptid, &kernel_pid_t::from(child_tid))?;
279        }
280
281        if do_child_settid {
282            // Set the child thread id in the child's memory.
283            child_process
284                .memory_borrow_mut()
285                .write(ctid, &kernel_pid_t::from(child_tid))?;
286        }
287
288        if do_child_cleartid {
289            let childrc = child_process.thread_borrow(child_tid).unwrap();
290            let child = childrc.borrow(ctx.objs.host.root());
291            child.set_tid_address(ctid);
292        }
293
294        if do_copy_sighandlers {
295            let shmem_lock = ctx.objs.host.shim_shmem_lock_borrow_mut().unwrap();
296
297            let parent_shmem = ctx.objs.process.shmem();
298            let parent_shmem_prot = parent_shmem.protected.borrow(&shmem_lock.root);
299
300            let child_shmem = child_process_borrow.as_ref().unwrap().shmem();
301            let mut child_shmem_prot = child_shmem.protected.borrow_mut(&shmem_lock.root);
302            // Safety: pointers in the parent are valid in the child.
303            unsafe { child_shmem_prot.clone_signal_actions(&parent_shmem_prot) };
304        }
305
306        Ok(kernel_pid_t::from(child_tid))
307    }
308
309    // Note that the syscall args are different than the libc wrapper.
310    // See "C library/kernel differences" in clone(2).
311    log_syscall!(
312        clone,
313        /* rv */ kernel_pid_t,
314        /* flags */ CloneFlags,
315        /* child_stack */ *const std::ffi::c_void,
316        /* ptid */ *const kernel_pid_t,
317        /* ctid */ *const kernel_pid_t,
318        /* newtls */ *const std::ffi::c_void,
319    );
320    pub fn clone(
321        ctx: &mut SyscallContext,
322        flags_and_exit_signal: i32,
323        child_stack: ForeignPtr<()>,
324        ptid: ForeignPtr<kernel_pid_t>,
325        ctid: ForeignPtr<kernel_pid_t>,
326        newtls: u64,
327    ) -> Result<kernel_pid_t, Errno> {
328        let raw_flags = flags_and_exit_signal as u32 & !0xff;
329        let raw_exit_signal = (flags_and_exit_signal as u32 & 0xff) as i32;
330
331        let Some(flags) = CloneFlags::from_bits(raw_flags as u64) else {
332            debug!("Couldn't parse clone flags: {raw_flags:x}");
333            return Err(Errno::EINVAL);
334        };
335
336        let exit_signal = if raw_exit_signal == 0 {
337            None
338        } else {
339            let Ok(exit_signal) = Signal::try_from(raw_exit_signal) else {
340                debug!("Bad exit signal: {raw_exit_signal:?}");
341                return Err(Errno::EINVAL);
342            };
343            Some(exit_signal)
344        };
345
346        Self::clone_internal(ctx, flags, exit_signal, child_stack, ptid, ctid, newtls)
347    }
348
349    log_syscall!(
350        clone3,
351        /* rv */ kernel_pid_t,
352        /* args*/ *const linux_api::sched::clone_args,
353        /* args_size*/ usize,
354    );
355    pub fn clone3(
356        ctx: &mut SyscallContext,
357        args: ForeignPtr<linux_api::sched::clone_args>,
358        args_size: usize,
359    ) -> Result<kernel_pid_t, Errno> {
360        if args_size != std::mem::size_of::<linux_api::sched::clone_args>() {
361            // TODO: allow smaller size, and be careful to only read
362            // as much as the caller specified, and zero-fill the rest.
363            return Err(Errno::EINVAL);
364        }
365        let args = ctx.objs.process.memory_borrow().read(args)?;
366        trace!("clone3 args: {args:?}");
367        let Some(flags) = CloneFlags::from_bits(args.flags) else {
368            debug!("Couldn't parse clone flags: {:x}", args.flags);
369            return Err(Errno::EINVAL);
370        };
371        let exit_signal = if args.exit_signal == 0 {
372            None
373        } else {
374            let Ok(exit_signal) = Signal::try_from(args.exit_signal as i32) else {
375                debug!("Bad signal number: {}", args.exit_signal);
376                return Err(Errno::EINVAL);
377            };
378            Some(exit_signal)
379        };
380        Self::clone_internal(
381            ctx,
382            flags,
383            exit_signal,
384            ForeignPtr::<()>::from(args.stack + args.stack_size),
385            ForeignPtr::<kernel_pid_t>::from_raw_ptr(args.parent_tid as *mut kernel_pid_t),
386            ForeignPtr::<kernel_pid_t>::from_raw_ptr(args.child_tid as *mut kernel_pid_t),
387            args.tls,
388        )
389    }
390
391    log_syscall!(fork, /* rv */ kernel_pid_t);
392    pub fn fork(ctx: &mut SyscallContext) -> Result<kernel_pid_t, Errno> {
393        // This should be the correct call to `clone_internal`, but `clone_internal`
394        // will currently return an error.
395        Self::clone_internal(
396            ctx,
397            CloneFlags::empty(),
398            Some(Signal::SIGCHLD),
399            ForeignPtr::<()>::null(),
400            ForeignPtr::<kernel_pid_t>::null(),
401            ForeignPtr::<kernel_pid_t>::null(),
402            0,
403        )
404    }
405
406    log_syscall!(vfork, /* rv */ kernel_pid_t);
407    pub fn vfork(ctx: &mut SyscallContext) -> Result<kernel_pid_t, Errno> {
408        // This should be the correct call to `clone_internal`, but `clone_internal`
409        // will currently return an error.
410        Self::clone_internal(
411            ctx,
412            CloneFlags::CLONE_VFORK | CloneFlags::CLONE_VM,
413            Some(Signal::SIGCHLD),
414            ForeignPtr::<()>::null(),
415            ForeignPtr::<kernel_pid_t>::null(),
416            ForeignPtr::<kernel_pid_t>::null(),
417            0,
418        )
419    }
420
421    log_syscall!(gettid, /* rv */ kernel_pid_t);
422    pub fn gettid(ctx: &mut SyscallContext) -> Result<kernel_pid_t, Errno> {
423        Ok(kernel_pid_t::from(ctx.objs.thread.id()))
424    }
425
426    log_syscall!(
427        capget,
428        /* rv */ std::ffi::c_int,
429        /* hdrp */ *const std::ffi::c_void,
430        /* datap */ *const std::ffi::c_void,
431    );
432    pub fn capget(
433        ctx: &mut SyscallContext,
434        hdrp: ForeignPtr<user_cap_header>,
435        datap: ForeignPtr<[user_cap_data; 2]>,
436    ) -> Result<(), Errno> {
437        // If the version is not 3, we return the error
438        let hdrp = ctx.objs.process.memory_borrow().read(hdrp)?;
439        if hdrp.version != LINUX_CAPABILITY_VERSION_3 {
440            warn_once_then_debug!(
441                "The version of Linux capabilities is not supported ({})",
442                hdrp.version
443            );
444            return Err(Errno::EINVAL);
445        }
446
447        if !datap.is_null() {
448            // Since we don't provide any capability to the managed plugin, we return zeroes to both
449            // datap[0] and datap[1]
450            let empty = user_cap_data {
451                effective: 0,
452                permitted: 0,
453                inheritable: 0,
454            };
455            ctx.objs
456                .process
457                .memory_borrow_mut()
458                .write(datap, &[empty, empty])?;
459        }
460        Ok(())
461    }
462
463    log_syscall!(
464        capset,
465        /* rv */ std::ffi::c_int,
466        /* hdrp */ *const std::ffi::c_void,
467        /* datap */ *const std::ffi::c_void,
468    );
469    pub fn capset(
470        ctx: &mut SyscallContext,
471        hdrp: ForeignPtr<user_cap_header>,
472        datap: ForeignPtr<[user_cap_data; 2]>,
473    ) -> Result<(), Errno> {
474        // If the version is not 3, we return the error
475        let hdrp = ctx.objs.process.memory_borrow().read(hdrp)?;
476        if hdrp.version != LINUX_CAPABILITY_VERSION_3 {
477            warn_once_then_debug!(
478                "The version of Linux capabilities is not supported ({})",
479                hdrp.version
480            );
481            return Err(Errno::EINVAL);
482        }
483
484        let datap: [_; 2] = ctx.objs.process.memory_borrow().read(datap)?;
485        for data in &datap {
486            // We don't allow the plugin to set any capability
487            if data.effective != 0 || data.permitted != 0 || data.inheritable != 0 {
488                warn_once_then_debug!("Setting Linux capabilities is not supported");
489                return Err(Errno::EINVAL);
490            }
491        }
492        Ok(())
493    }
494}