Skip to main content

libcontainer/syscall/
test.rs

1use std::any::Any;
2use std::cell::{Ref, RefCell, RefMut};
3use std::collections::HashMap;
4use std::ffi::{OsStr, OsString};
5use std::fs::{File, read_link};
6use std::os::fd::{AsRawFd, BorrowedFd, RawFd};
7use std::os::unix::io::OwnedFd;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10
11use caps::{CapSet, CapsHashSet};
12use nix::mount::{MntFlags, MsFlags};
13use nix::sched::CloneFlags;
14use nix::sys::stat::{Mode, SFlag};
15use nix::unistd::{Gid, Uid};
16use oci_spec::runtime::PosixRlimit;
17
18use super::super::config::PersonalityDomain;
19use super::{Result, Syscall, linux};
20
21#[derive(Clone, PartialEq, Eq, Debug)]
22pub struct MountArgs {
23    pub source: Option<PathBuf>,
24    pub target: PathBuf,
25    pub fstype: Option<String>,
26    pub flags: MsFlags,
27    pub data: Option<String>,
28}
29
30#[derive(Clone, PartialEq, Eq, Debug)]
31pub struct MountFromFdArgs {
32    pub fd: i32,
33    pub target: PathBuf,
34}
35
36#[derive(Clone, PartialEq, Eq, Debug)]
37pub struct MoveMountArgs {
38    pub from_dirfd: i32,
39    pub from_path: Option<OsString>,
40    pub to_dirfd: i32,
41    pub to_path: Option<OsString>,
42    pub to_dirfd_path: Option<PathBuf>,
43    pub flags: u32,
44}
45
46#[derive(Clone, PartialEq, Eq, Debug)]
47pub struct FsopenArgs {
48    pub fsname: Option<String>,
49    pub flags: u32,
50}
51
52#[derive(Clone, PartialEq, Eq, Debug)]
53pub struct FsconfigArgs {
54    pub cmd: u32,
55    pub key: Option<String>,
56    pub val: Option<String>,
57    pub aux: libc::c_int,
58}
59
60#[derive(Clone, PartialEq, Eq, Debug)]
61pub struct FsmountArgs {
62    pub flags: u32,
63    pub attr_flags: Option<u64>,
64}
65
66#[derive(Clone, PartialEq, Eq, Debug)]
67pub struct OpenTreeArgs {
68    pub dirfd: i32,
69    pub path: Option<String>,
70    pub flags: u32,
71}
72
73#[derive(Clone, PartialEq, Eq, Debug)]
74pub struct MountSetattrArgs {
75    pub dirfd: i32,
76    pub pathname: PathBuf,
77    pub flags: u32,
78    pub attr_set: u64,
79    pub attr_clr: u64,
80    pub propagation: u64,
81    pub userns_fd: u64,
82    pub size: libc::size_t,
83}
84
85#[derive(Clone, PartialEq, Eq, Debug)]
86pub struct MknodArgs {
87    pub path: PathBuf,
88    pub kind: SFlag,
89    pub perm: Mode,
90    pub dev: u64,
91}
92
93#[derive(Clone, PartialEq, Eq, Debug)]
94pub struct ChownArgs {
95    pub path: PathBuf,
96    pub owner: Option<Uid>,
97    pub group: Option<Gid>,
98}
99
100#[derive(Clone, PartialEq, Eq, Debug)]
101pub struct IoPriorityArgs {
102    pub class: i64,
103    pub priority: i64,
104}
105
106#[derive(Clone, PartialEq, Eq, Debug)]
107pub struct MemPolicyArgs {
108    pub mode: i32,
109    pub nodemask: Vec<libc::c_ulong>, // Store the nodemask vector for testing
110    pub maxnode: u64,
111}
112
113#[derive(Clone, PartialEq, Eq, Debug)]
114pub struct UMount2Args {
115    pub target: PathBuf,
116    pub flags: MntFlags,
117}
118
119#[derive(Default)]
120struct Mock {
121    values: Vec<Box<dyn Any>>,
122    ret_err: Option<fn() -> Result<()>>,
123    ret_err_times: usize,
124}
125
126#[derive(PartialEq, Eq, Hash, Copy, Clone)]
127pub enum ArgName {
128    Namespace,
129    Unshare,
130    Mount,
131    MountFromFd,
132    Symlink,
133    Mknod,
134    Chown,
135    Hostname,
136    Domainname,
137    Groups,
138    Capability,
139    IoPriority,
140    MemPolicy,
141    UMount2,
142    MoveMount,
143    Fsopen,
144    Fsconfig,
145    Fsmount,
146    OpenTree,
147    MountSetattr,
148}
149
150impl ArgName {
151    fn iterator() -> impl Iterator<Item = ArgName> {
152        [
153            ArgName::Namespace,
154            ArgName::Unshare,
155            ArgName::Mount,
156            ArgName::MountFromFd,
157            ArgName::Symlink,
158            ArgName::Mknod,
159            ArgName::Chown,
160            ArgName::Hostname,
161            ArgName::Domainname,
162            ArgName::Groups,
163            ArgName::Capability,
164            ArgName::IoPriority,
165            ArgName::MemPolicy,
166            ArgName::MoveMount,
167            ArgName::Fsopen,
168            ArgName::Fsconfig,
169            ArgName::Fsmount,
170            ArgName::OpenTree,
171            ArgName::MountSetattr,
172        ]
173        .iter()
174        .copied()
175    }
176}
177
178struct MockCalls {
179    args: HashMap<ArgName, RefCell<Mock>>,
180}
181
182impl Default for MockCalls {
183    fn default() -> Self {
184        let mut m = MockCalls {
185            args: HashMap::new(),
186        };
187
188        for name in ArgName::iterator() {
189            m.args.insert(name, RefCell::new(Mock::default()));
190        }
191
192        m
193    }
194}
195
196impl MockCalls {
197    fn act(&self, name: ArgName, value: Box<dyn Any>) -> Result<()> {
198        if self.args.get(&name).unwrap().borrow().ret_err_times > 0 {
199            self.args.get(&name).unwrap().borrow_mut().ret_err_times -= 1;
200            if let Some(e) = &self.args.get(&name).unwrap().borrow().ret_err {
201                return e();
202            }
203        }
204
205        self.args
206            .get(&name)
207            .unwrap()
208            .borrow_mut()
209            .values
210            .push(value);
211        Ok(())
212    }
213
214    fn fetch(&self, name: ArgName) -> Ref<'_, Mock> {
215        self.args.get(&name).unwrap().borrow()
216    }
217
218    fn fetch_mut(&self, name: ArgName) -> RefMut<'_, Mock> {
219        self.args.get(&name).unwrap().borrow_mut()
220    }
221}
222
223/// Open a fresh, valid file descriptor for use as a placeholder return value
224/// from fd-returning syscalls (`fsopen`/`fsmount`/`open_tree`). Tests don't
225/// dereference it; it only needs to be a real, owned fd.
226fn dummy_owned_fd() -> OwnedFd {
227    OwnedFd::from(File::open("/dev/null").expect("failed to open /dev/null for a test fd"))
228}
229
230#[derive(Default)]
231pub struct TestHelperSyscall {
232    mock_id: RefCell<MockId>,
233    mocks: MockCalls,
234}
235
236pub struct MockId {
237    uid: Uid,
238    gid: Gid,
239    euid: Uid,
240    egid: Gid,
241}
242
243impl Default for MockId {
244    fn default() -> Self {
245        Self {
246            uid: nix::unistd::getuid(),
247            gid: nix::unistd::getgid(),
248            euid: nix::unistd::geteuid(),
249            egid: nix::unistd::getegid(),
250        }
251    }
252}
253
254impl Syscall for TestHelperSyscall {
255    fn as_any(&self) -> &dyn Any {
256        self
257    }
258
259    fn pivot_rootfs(&self, _path: &Path) -> Result<()> {
260        unimplemented!()
261    }
262
263    fn set_ns(&self, rawfd: i32, nstype: CloneFlags) -> Result<()> {
264        self.mocks
265            .act(ArgName::Namespace, Box::new((rawfd, nstype)))
266    }
267
268    fn set_id(&self, _uid: Uid, _gid: Gid) -> Result<()> {
269        self.mock_id.borrow_mut().uid = _uid;
270        self.mock_id.borrow_mut().gid = _gid;
271        self.mock_id.borrow_mut().euid = _uid;
272        self.mock_id.borrow_mut().egid = _gid;
273        Ok(())
274    }
275
276    fn unshare(&self, flags: CloneFlags) -> Result<()> {
277        self.mocks.act(ArgName::Unshare, Box::new(flags))
278    }
279
280    fn set_capability(&self, cset: CapSet, value: &CapsHashSet) -> Result<()> {
281        self.mocks
282            .act(ArgName::Capability, Box::new((cset, value.clone())))
283    }
284
285    fn set_hostname(&self, hostname: &str) -> Result<()> {
286        self.mocks
287            .act(ArgName::Hostname, Box::new(hostname.to_owned()))
288    }
289
290    fn set_domainname(&self, domainname: &str) -> Result<()> {
291        self.mocks
292            .act(ArgName::Domainname, Box::new(domainname.to_owned()))
293    }
294
295    fn set_rlimit(&self, _rlimit: &PosixRlimit) -> Result<()> {
296        todo!()
297    }
298
299    fn get_pwuid(&self, _: u32) -> Option<Arc<OsStr>> {
300        Some(OsString::from("youki").into())
301    }
302
303    fn chroot(&self, _: &Path) -> Result<()> {
304        todo!()
305    }
306
307    fn mount(
308        &self,
309        source: Option<&Path>,
310        target: &Path,
311        fstype: Option<&str>,
312        flags: MsFlags,
313        data: Option<&str>,
314    ) -> Result<()> {
315        // For tests: resolve /proc/self/fd/<n> to the real path before recording.
316        let target_owned = if target.starts_with(Path::new("/proc/self/fd")) {
317            read_link(target).unwrap_or_else(|_| target.to_owned())
318        } else {
319            target.to_owned()
320        };
321
322        self.mocks.act(
323            ArgName::Mount,
324            Box::new(MountArgs {
325                source: source.map(|x| x.to_owned()),
326                target: target_owned,
327                fstype: fstype.map(|x| x.to_owned()),
328                flags,
329                data: data.map(|x| x.to_owned()),
330            }),
331        )
332    }
333
334    fn mount_from_fd(&self, source_fd: &OwnedFd, target: &Path) -> Result<()> {
335        self.mocks.act(
336            ArgName::MountFromFd,
337            Box::new(MountFromFdArgs {
338                fd: source_fd.as_raw_fd(),
339                target: target.to_owned(),
340            }),
341        )
342    }
343
344    fn move_mount(
345        &self,
346        from_dirfd: BorrowedFd<'_>,
347        from_path: Option<&str>,
348        to_dirfd: BorrowedFd<'_>,
349        to_path: Option<&str>,
350        flags: u32,
351    ) -> Result<()> {
352        // The destination is usually passed by fd (MOVE_MOUNT_T_EMPTY_PATH), so
353        // resolve the real path it points at via /proc/self/fd while the fd is
354        // still open, for later assertions.
355        let to_dirfd_path = if to_path.is_none() {
356            read_link(format!("/proc/self/fd/{}", to_dirfd.as_raw_fd())).ok()
357        } else {
358            None
359        };
360
361        self.mocks.act(
362            ArgName::MoveMount,
363            Box::new(MoveMountArgs {
364                from_dirfd: from_dirfd.as_raw_fd(),
365                from_path: from_path.map(OsString::from),
366                to_dirfd: to_dirfd.as_raw_fd(),
367                to_path: to_path.map(OsString::from),
368                to_dirfd_path,
369                flags,
370            }),
371        )
372    }
373
374    fn fsopen(&self, fstype: Option<&str>, flags: u32) -> Result<OwnedFd> {
375        self.mocks.act(
376            ArgName::Fsopen,
377            Box::new(FsopenArgs {
378                fsname: fstype.map(|s| s.to_owned()),
379                flags,
380            }),
381        )?;
382        Ok(dummy_owned_fd())
383    }
384
385    fn fsconfig(
386        &self,
387        _fsfd: BorrowedFd<'_>,
388        cmd: u32,
389        key: Option<&str>,
390        val: Option<&str>,
391        aux: libc::c_int,
392    ) -> Result<()> {
393        self.mocks.act(
394            ArgName::Fsconfig,
395            Box::new(FsconfigArgs {
396                cmd,
397                key: key.map(|s| s.to_owned()),
398                val: val.map(|s| s.to_owned()),
399                aux,
400            }),
401        )
402    }
403
404    fn fsmount(
405        &self,
406        _fsfd: BorrowedFd<'_>,
407        flags: u32,
408        attr_flags: Option<u64>,
409    ) -> Result<OwnedFd> {
410        self.mocks.act(
411            ArgName::Fsmount,
412            Box::new(FsmountArgs { flags, attr_flags }),
413        )?;
414        Ok(dummy_owned_fd())
415    }
416
417    fn open_tree(&self, dirfd: RawFd, path: Option<&str>, flags: u32) -> Result<OwnedFd> {
418        self.mocks.act(
419            ArgName::OpenTree,
420            Box::new(OpenTreeArgs {
421                dirfd,
422                path: path.map(|s| s.to_owned()),
423                flags,
424            }),
425        )?;
426        Ok(dummy_owned_fd())
427    }
428
429    fn symlink(&self, original: &Path, link: &Path) -> Result<()> {
430        self.mocks.act(
431            ArgName::Symlink,
432            Box::new((original.to_path_buf(), link.to_path_buf())),
433        )
434    }
435
436    fn mknod(&self, path: &Path, kind: SFlag, perm: Mode, dev: u64) -> Result<()> {
437        self.mocks.act(
438            ArgName::Mknod,
439            Box::new(MknodArgs {
440                path: path.to_path_buf(),
441                kind,
442                perm,
443                dev,
444            }),
445        )
446    }
447    fn chown(&self, path: &Path, owner: Option<Uid>, group: Option<Gid>) -> Result<()> {
448        self.mocks.act(
449            ArgName::Chown,
450            Box::new(ChownArgs {
451                path: path.to_path_buf(),
452                owner,
453                group,
454            }),
455        )
456    }
457
458    fn set_groups(&self, groups: &[Gid]) -> Result<()> {
459        self.mocks.act(ArgName::Groups, Box::new(groups.to_vec()))
460    }
461
462    fn close_range(&self, _: i32) -> Result<()> {
463        todo!()
464    }
465
466    fn mount_setattr(
467        &self,
468        dirfd: BorrowedFd<'_>,
469        pathname: &Path,
470        flags: u32,
471        mount_attr: &linux::MountAttr,
472        size: libc::size_t,
473    ) -> Result<()> {
474        self.mocks.act(
475            ArgName::MountSetattr,
476            Box::new(MountSetattrArgs {
477                dirfd: dirfd.as_raw_fd(),
478                pathname: pathname.to_owned(),
479                flags,
480                attr_set: mount_attr.attr_set,
481                attr_clr: mount_attr.attr_clr,
482                propagation: mount_attr.propagation,
483                userns_fd: mount_attr.userns_fd,
484                size,
485            }),
486        )
487    }
488
489    fn set_io_priority(&self, class: i64, priority: i64) -> Result<()> {
490        self.mocks.act(
491            ArgName::IoPriority,
492            Box::new(IoPriorityArgs { class, priority }),
493        )
494    }
495
496    fn set_mempolicy(&self, mode: i32, nodemask: &[libc::c_ulong], maxnode: u64) -> Result<()> {
497        self.mocks.act(
498            ArgName::MemPolicy,
499            Box::new(MemPolicyArgs {
500                mode,
501                nodemask: nodemask.to_vec(),
502                maxnode,
503            }),
504        )
505    }
506
507    fn umount2(&self, target: &Path, flags: MntFlags) -> Result<()> {
508        self.mocks.act(
509            ArgName::UMount2,
510            Box::new(UMount2Args {
511                target: target.to_owned(),
512                flags,
513            }),
514        )
515    }
516
517    fn get_uid(&self) -> Uid {
518        self.mock_id.borrow().uid
519    }
520
521    fn get_gid(&self) -> Gid {
522        self.mock_id.borrow().gid
523    }
524
525    fn get_euid(&self) -> Uid {
526        self.mock_id.borrow().euid
527    }
528
529    fn get_egid(&self) -> Gid {
530        self.mock_id.borrow().egid
531    }
532
533    fn personality(&self, _: PersonalityDomain) -> Result<()> {
534        todo!()
535    }
536}
537
538impl TestHelperSyscall {
539    pub fn set_ret_err(&self, name: ArgName, err: fn() -> Result<()>) {
540        self.mocks.fetch_mut(name).ret_err = Some(err);
541        self.set_ret_err_times(name, 1);
542    }
543
544    pub fn set_ret_err_times(&self, name: ArgName, times: usize) {
545        self.mocks.fetch_mut(name).ret_err_times = times;
546    }
547
548    pub fn get_setns_args(&self) -> Vec<(i32, CloneFlags)> {
549        self.mocks
550            .fetch(ArgName::Namespace)
551            .values
552            .iter()
553            .map(|x| *x.downcast_ref::<(i32, CloneFlags)>().unwrap())
554            .collect::<Vec<(i32, CloneFlags)>>()
555    }
556
557    pub fn get_unshare_args(&self) -> Vec<CloneFlags> {
558        self.mocks
559            .fetch(ArgName::Unshare)
560            .values
561            .iter()
562            .map(|x| *x.downcast_ref::<CloneFlags>().unwrap())
563            .collect::<Vec<CloneFlags>>()
564    }
565
566    pub fn get_set_capability_args(&self) -> Vec<(CapSet, CapsHashSet)> {
567        self.mocks
568            .fetch(ArgName::Capability)
569            .values
570            .iter()
571            .map(|x| x.downcast_ref::<(CapSet, CapsHashSet)>().unwrap().clone())
572            .collect::<Vec<(CapSet, CapsHashSet)>>()
573    }
574
575    pub fn get_mount_args(&self) -> Vec<MountArgs> {
576        self.mocks
577            .fetch(ArgName::Mount)
578            .values
579            .iter()
580            .map(|x| x.downcast_ref::<MountArgs>().unwrap().clone())
581            .collect::<Vec<MountArgs>>()
582    }
583
584    pub fn get_fsopen_args(&self) -> Vec<FsopenArgs> {
585        self.mocks
586            .fetch(ArgName::Fsopen)
587            .values
588            .iter()
589            .map(|x| x.downcast_ref::<FsopenArgs>().unwrap().clone())
590            .collect::<Vec<FsopenArgs>>()
591    }
592
593    pub fn get_fsconfig_args(&self) -> Vec<FsconfigArgs> {
594        self.mocks
595            .fetch(ArgName::Fsconfig)
596            .values
597            .iter()
598            .map(|x| x.downcast_ref::<FsconfigArgs>().unwrap().clone())
599            .collect::<Vec<FsconfigArgs>>()
600    }
601
602    pub fn get_fsmount_args(&self) -> Vec<FsmountArgs> {
603        self.mocks
604            .fetch(ArgName::Fsmount)
605            .values
606            .iter()
607            .map(|x| x.downcast_ref::<FsmountArgs>().unwrap().clone())
608            .collect::<Vec<FsmountArgs>>()
609    }
610
611    pub fn get_open_tree_args(&self) -> Vec<OpenTreeArgs> {
612        self.mocks
613            .fetch(ArgName::OpenTree)
614            .values
615            .iter()
616            .map(|x| x.downcast_ref::<OpenTreeArgs>().unwrap().clone())
617            .collect::<Vec<OpenTreeArgs>>()
618    }
619
620    pub fn get_mount_setattr_args(&self) -> Vec<MountSetattrArgs> {
621        self.mocks
622            .fetch(ArgName::MountSetattr)
623            .values
624            .iter()
625            .map(|x| x.downcast_ref::<MountSetattrArgs>().unwrap().clone())
626            .collect::<Vec<MountSetattrArgs>>()
627    }
628
629    pub fn get_move_mount_args(&self) -> Vec<MoveMountArgs> {
630        self.mocks
631            .fetch(ArgName::MoveMount)
632            .values
633            .iter()
634            .map(|x| x.downcast_ref::<MoveMountArgs>().unwrap().clone())
635            .collect::<Vec<MoveMountArgs>>()
636    }
637
638    pub fn get_mount_from_fd_args(&self) -> Vec<MountFromFdArgs> {
639        self.mocks
640            .fetch(ArgName::MountFromFd)
641            .values
642            .iter()
643            .map(|x| x.downcast_ref::<MountFromFdArgs>().unwrap().clone())
644            .collect::<Vec<MountFromFdArgs>>()
645    }
646
647    pub fn get_symlink_args(&self) -> Vec<(PathBuf, PathBuf)> {
648        self.mocks
649            .fetch(ArgName::Symlink)
650            .values
651            .iter()
652            .map(|x| x.downcast_ref::<(PathBuf, PathBuf)>().unwrap().clone())
653            .collect::<Vec<(PathBuf, PathBuf)>>()
654    }
655
656    pub fn get_mknod_args(&self) -> Vec<MknodArgs> {
657        self.mocks
658            .fetch(ArgName::Mknod)
659            .values
660            .iter()
661            .map(|x| x.downcast_ref::<MknodArgs>().unwrap().clone())
662            .collect::<Vec<MknodArgs>>()
663    }
664
665    pub fn get_chown_args(&self) -> Vec<ChownArgs> {
666        self.mocks
667            .fetch(ArgName::Chown)
668            .values
669            .iter()
670            .map(|x| x.downcast_ref::<ChownArgs>().unwrap().clone())
671            .collect::<Vec<ChownArgs>>()
672    }
673
674    pub fn get_hostname_args(&self) -> Vec<String> {
675        self.mocks
676            .fetch(ArgName::Hostname)
677            .values
678            .iter()
679            .map(|x| x.downcast_ref::<String>().unwrap().clone())
680            .collect::<Vec<String>>()
681    }
682
683    pub fn get_domainname_args(&self) -> Vec<String> {
684        self.mocks
685            .fetch(ArgName::Domainname)
686            .values
687            .iter()
688            .map(|x| x.downcast_ref::<String>().unwrap().clone())
689            .collect::<Vec<String>>()
690    }
691
692    pub fn get_groups_args(&self) -> Vec<Gid> {
693        self.mocks
694            .fetch(ArgName::Groups)
695            .values
696            .iter()
697            .flat_map(|x| x.downcast_ref::<Vec<Gid>>().unwrap().clone())
698            .collect::<Vec<Gid>>()
699    }
700
701    pub fn get_io_priority_args(&self) -> Vec<IoPriorityArgs> {
702        self.mocks
703            .fetch(ArgName::IoPriority)
704            .values
705            .iter()
706            .map(|x| x.downcast_ref::<IoPriorityArgs>().unwrap().clone())
707            .collect::<Vec<IoPriorityArgs>>()
708    }
709
710    pub fn get_mempolicy_args(&self) -> Vec<MemPolicyArgs> {
711        self.mocks
712            .fetch(ArgName::MemPolicy)
713            .values
714            .iter()
715            .map(|x| x.downcast_ref::<MemPolicyArgs>().unwrap().clone())
716            .collect::<Vec<MemPolicyArgs>>()
717    }
718
719    pub fn get_umount_args(&self) -> Vec<UMount2Args> {
720        self.mocks
721            .fetch(ArgName::UMount2)
722            .values
723            .iter()
724            .map(|x| x.downcast_ref::<UMount2Args>().unwrap().clone())
725            .collect::<Vec<UMount2Args>>()
726    }
727}