ia_sandbox/
lib.rs

1#![deny(
2    clippy::clone_on_ref_ptr,
3    clippy::default_trait_access,
4    clippy::doc_markdown,
5    clippy::empty_enum,
6    clippy::empty_line_after_outer_attr,
7    clippy::enum_glob_use,
8    clippy::expl_impl_clone_on_copy,
9    clippy::fallible_impl_from,
10    clippy::float_cmp_const,
11    clippy::items_after_statements,
12    clippy::match_same_arms,
13    clippy::multiple_inherent_impl,
14    clippy::mut_mut,
15    clippy::needless_continue,
16    clippy::print_stdout,
17    clippy::range_plus_one,
18    clippy::single_match_else,
19    clippy::unimplemented,
20    clippy::unnecessary_unwrap,
21    clippy::use_self,
22    clippy::used_underscore_binding,
23    clippy::writeln_empty_string,
24    clippy::wrong_self_convention,
25    missing_copy_implementations,
26    missing_debug_implementations,
27    trivial_casts,
28    trivial_numeric_casts,
29    unreachable_pub,
30    unused_extern_crates,
31    unused_import_braces,
32    unused_qualifications,
33    unused_results,
34    variant_size_differences,
35    warnings
36)]
37
38use std::time::Duration;
39
40use capctl::caps::CapState;
41
42mod cgroups;
43pub mod config;
44pub mod errors;
45mod ffi;
46pub mod run_info;
47pub mod utils;
48
49use config::{Aslr, Config, Interactive, Limits, ShareNet, SpaceUsage, SwapRedirects};
50pub use errors::*;
51use ffi::CloneHandle;
52use run_info::{RunInfo, RunUsage};
53
54pub fn spawn_jail(config: &Config) -> Result<JailHandle> {
55    let user_group_id = ffi::get_user_group_id();
56
57    ffi::set_sig_alarm_handler().map_err(Error::FFIError)?;
58
59    // Start a supervisor process in a different pid namespace
60    // If by any chance the supervisor process dies, by rules of pid namespaces
61    // all its descendant processes will die as well
62    ffi::clone(ShareNet::Share, false, || {
63        ffi::kill_on_parent_death()?;
64        // Mount proc just for security
65        ffi::mount_proc()?;
66        // Without setting uid/gid maps user is not seen so it can not do anything
67        ffi::set_uid_gid_maps(user_group_id)?;
68
69        ffi::clone(config.share_net(), true, || {
70            if config.swap_redirects() == SwapRedirects::Yes {
71                if let Some(stdout) = config.redirect_stdout() {
72                    ffi::redirect_fd(ffi::STDOUT, stdout)?;
73                }
74            }
75
76            if let Some(stdin) = config.redirect_stdin() {
77                ffi::redirect_fd(ffi::STDIN, stdin)?;
78            }
79
80            if config.swap_redirects() == SwapRedirects::No {
81                if let Some(stdout) = config.redirect_stdout() {
82                    ffi::redirect_fd(ffi::STDOUT, stdout)?;
83                }
84            }
85
86            if let Some(stderr) = config.redirect_stderr() {
87                ffi::redirect_fd(ffi::STDERR, stderr)?;
88            }
89
90            ffi::set_stack_rlimit(config.limits().stack())?;
91            ffi::set_pids_rlimit(config.limits().pids())?;
92
93            // Enter cgroup before we pivot root, afterwards it is too late
94            if let Some(hierarchy) = config.hierarchy_path() {
95                cgroups::enter_all_cgroups(
96                    hierarchy,
97                    config.instance_name(),
98                    config.limits(),
99                    config.clear_usage(),
100                )?;
101            } else {
102                // fallback to some setrlimit, can by bypassed unfortunately
103                ffi::set_cpu_rlimit(config.limits().user_time())?;
104                ffi::set_memory_rlimit(config.limits().memory())?;
105            }
106
107            ffi::unshare_cgroup()?;
108
109            // Remount everything privately
110            ffi::remount_private()?;
111
112            if let Some(new_root) = config.new_root() {
113                for mount in config.mounts() {
114                    ffi::mount_inside(new_root, mount)?;
115                }
116
117                ffi::pivot_root(new_root, || {
118                    // Mount proc (since we are in a new pid namespace)
119                    // Must be done after pivot_root so we mount this in the right location
120                    // but also before we unmount the old root because ... I don't know
121                    ffi::mount_proc()
122                })?;
123            } else {
124                ffi::mount_proc()?;
125            }
126
127            // Make sure we are root (we don't really need to,
128            // but this way the child process can do anything it likes
129            // inside its namespace and nothing outside)
130            // Must be done after mount_proc so we can properly read and write
131            // /proc/self/uid_map and /proc/self/gid_map
132            ffi::set_uid_gid_maps((ffi::UserId::ROOT, ffi::GroupId::ROOT))?;
133
134            if config.interactive() == Interactive::No {
135                // Move the process to a different process group (so it can't kill it's own
136                // father by sending signals to the whole process group)
137                // But for interactive applications (mostly to test stuff), leave it there
138                ffi::move_to_different_process_group()?;
139            }
140
141            if config.aslr() == Aslr::NoRandomize {
142                ffi::disable_aslr()?;
143            }
144
145            if !config.privileged {
146                CapState::empty().set_current()?;
147                ffi::remove_future_privileges()?;
148            }
149
150            ffi::exec_command(config.command(), &config.args(), config.environment())?;
151
152            Ok(())
153        })?
154        .wait(config.limits(), |wall_time| {
155            if let Some(hierarchy) = config.hierarchy_path() {
156                Ok(cgroups::get_usage(
157                    hierarchy,
158                    config.instance_name(),
159                    wall_time,
160                )?)
161            } else {
162                Ok(RunUsage::new(
163                    Duration::default(),
164                    wall_time,
165                    SpaceUsage::default(),
166                ))
167            }
168        })
169        .and_then(|run_info| {
170            run_info.and_then(|option| match option {
171                None => Ok(()),
172                Some(result) => result.map_err(Error::ChildError),
173            })
174        })
175    })
176    .map(JailHandle::new)
177    .map_err(Error::from)
178}
179
180#[allow(missing_debug_implementations)]
181pub struct JailHandle {
182    handle: CloneHandle<Result<RunInfo<()>>>,
183}
184
185impl JailHandle {
186    fn new(handle: CloneHandle<Result<RunInfo<()>>>) -> Self {
187        Self { handle }
188    }
189
190    pub fn wait(self) -> Result<RunInfo<()>> {
191        self.handle
192            .wait(Limits::default(), |_| Ok(RunUsage::default()))
193            .and_then(|run_info| {
194                run_info
195                    .success() // we only care if supervisor process successfully finished
196                    .and_then(|x| x) // its an option inside an option, so flatten it
197                    .ok_or(Error::SupervisorProcessDiedError)
198                    .and_then(|x| x) // result in result, flatten it
199            })
200    }
201}