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