Skip to main content

libcontainer/
namespaces.rs

1//! Namespaces provide isolation of resources for processes at a kernel level.
2//! The namespaces are: Mount (filesystem),
3//! Process (processes in a namespace have two PIDs, one for the global PID,
4//! which is used by the main system and the second one is for the child within the process tree),
5//! Interprocess Communication (Control or communication between processes),
6//! Network (which network devices can be seen by the processes in the namespace), User (User configs),
7//! UTS (hostname and domain information, processes will think they're running on servers with different names),
8//! Cgroup (Resource limits, execution priority etc.)
9
10use std::collections;
11
12use nix::sched::CloneFlags;
13use nix::sys::stat;
14use nix::{fcntl, unistd};
15use oci_spec::runtime::{LinuxNamespace, LinuxNamespaceType};
16
17use crate::syscall::Syscall;
18use crate::syscall::syscall::create_syscall;
19
20type Result<T> = std::result::Result<T, NamespaceError>;
21
22#[derive(Debug, thiserror::Error)]
23pub enum NamespaceError {
24    #[error(transparent)]
25    Nix(#[from] nix::Error),
26    #[error(transparent)]
27    IO(#[from] std::io::Error),
28    #[error(transparent)]
29    Syscall(#[from] crate::syscall::SyscallError),
30    #[error("Namespace type not supported: {0}")]
31    NotSupported(String),
32}
33
34static ORDERED_NAMESPACES: &[CloneFlags] = &[
35    CloneFlags::CLONE_NEWUSER,
36    CloneFlags::CLONE_NEWPID,
37    CloneFlags::CLONE_NEWUTS,
38    CloneFlags::CLONE_NEWIPC,
39    CloneFlags::CLONE_NEWNET,
40    CloneFlags::CLONE_NEWCGROUP,
41    CloneFlags::CLONE_NEWNS,
42];
43
44/// Holds information about namespaces
45pub struct Namespaces {
46    command: Box<dyn Syscall>,
47    namespace_map: collections::HashMap<CloneFlags, LinuxNamespace>,
48}
49
50fn get_clone_flag(namespace_type: LinuxNamespaceType) -> Result<CloneFlags> {
51    let flag = match namespace_type {
52        LinuxNamespaceType::User => CloneFlags::CLONE_NEWUSER,
53        LinuxNamespaceType::Pid => CloneFlags::CLONE_NEWPID,
54        LinuxNamespaceType::Uts => CloneFlags::CLONE_NEWUTS,
55        LinuxNamespaceType::Ipc => CloneFlags::CLONE_NEWIPC,
56        LinuxNamespaceType::Network => CloneFlags::CLONE_NEWNET,
57        LinuxNamespaceType::Cgroup => CloneFlags::CLONE_NEWCGROUP,
58        LinuxNamespaceType::Mount => CloneFlags::CLONE_NEWNS,
59        LinuxNamespaceType::Time => return Err(NamespaceError::NotSupported("time".to_string())),
60    };
61
62    Ok(flag)
63}
64
65impl TryFrom<Option<&Vec<LinuxNamespace>>> for Namespaces {
66    type Error = NamespaceError;
67
68    fn try_from(namespaces: Option<&Vec<LinuxNamespace>>) -> Result<Self> {
69        let command: Box<dyn Syscall> = create_syscall();
70        let namespace_map: collections::HashMap<CloneFlags, LinuxNamespace> = namespaces
71            .unwrap_or(&vec![])
72            .iter()
73            .map(|ns| match get_clone_flag(ns.typ()) {
74                Ok(flag) => Ok((flag, ns.clone())),
75                Err(err) => Err(err),
76            })
77            .collect::<Result<Vec<(CloneFlags, LinuxNamespace)>>>()?
78            .into_iter()
79            .collect();
80
81        Ok(Namespaces {
82            command,
83            namespace_map,
84        })
85    }
86}
87
88impl Namespaces {
89    pub fn apply_namespaces<F: Fn(CloneFlags) -> bool>(&self, filter: F) -> Result<()> {
90        let to_enter: Vec<(&CloneFlags, &LinuxNamespace)> = ORDERED_NAMESPACES
91            .iter()
92            .filter(|c| filter(**c))
93            .filter_map(|c| self.namespace_map.get_key_value(c))
94            .collect();
95
96        for (_, ns) in to_enter {
97            self.unshare_or_setns(ns)?;
98        }
99        Ok(())
100    }
101
102    pub fn unshare_or_setns(&self, namespace: &LinuxNamespace) -> Result<()> {
103        tracing::debug!("unshare or setns: {:?}", namespace);
104        match namespace.path() {
105            Some(path) => {
106                let fd = fcntl::open(path, fcntl::OFlag::empty(), stat::Mode::empty())
107                    .inspect_err(|err| {
108                        tracing::error!(?err, ?namespace, "failed to open namespace file");
109                    })?;
110                self.command
111                    .set_ns(fd, get_clone_flag(namespace.typ())?)
112                    .map_err(|err| {
113                        tracing::error!(?err, ?namespace, "failed to set namespace");
114                        err
115                    })?;
116                unistd::close(fd).inspect_err(|err| {
117                    tracing::error!(?err, ?namespace, "failed to close namespace file");
118                })?;
119            }
120            None => {
121                self.command
122                    .unshare(get_clone_flag(namespace.typ())?)
123                    .map_err(|err| {
124                        tracing::error!(?err, ?namespace, "failed to unshare namespace");
125                        err
126                    })?;
127            }
128        }
129
130        Ok(())
131    }
132
133    pub fn get(&self, k: LinuxNamespaceType) -> Result<Option<&LinuxNamespace>> {
134        Ok(self.namespace_map.get(&get_clone_flag(k)?))
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use oci_spec::runtime::{LinuxNamespaceBuilder, LinuxNamespaceType};
141    use serial_test::serial;
142
143    use super::*;
144    use crate::syscall::test::TestHelperSyscall;
145
146    fn gen_sample_linux_namespaces() -> Vec<LinuxNamespace> {
147        vec![
148            LinuxNamespaceBuilder::default()
149                .typ(LinuxNamespaceType::Mount)
150                .path("/dev/null")
151                .build()
152                .unwrap(),
153            LinuxNamespaceBuilder::default()
154                .typ(LinuxNamespaceType::Network)
155                .path("/dev/null")
156                .build()
157                .unwrap(),
158            LinuxNamespaceBuilder::default()
159                .typ(LinuxNamespaceType::Pid)
160                .build()
161                .unwrap(),
162            LinuxNamespaceBuilder::default()
163                .typ(LinuxNamespaceType::User)
164                .build()
165                .unwrap(),
166            LinuxNamespaceBuilder::default()
167                .typ(LinuxNamespaceType::Ipc)
168                .build()
169                .unwrap(),
170        ]
171    }
172
173    #[test]
174    #[serial]
175    fn test_apply_namespaces() {
176        let sample_linux_namespaces = gen_sample_linux_namespaces();
177        let namespaces = Namespaces::try_from(Some(&sample_linux_namespaces))
178            .expect("create namespace struct should be good");
179        let test_command: &TestHelperSyscall = namespaces.command.as_any().downcast_ref().unwrap();
180        assert!(
181            namespaces
182                .apply_namespaces(|ns_type| { ns_type != CloneFlags::CLONE_NEWIPC })
183                .is_ok()
184        );
185
186        let mut setns_args: Vec<_> = test_command
187            .get_setns_args()
188            .into_iter()
189            .map(|(_fd, cf)| cf)
190            .collect();
191        setns_args.sort();
192        let mut expect = vec![CloneFlags::CLONE_NEWNS, CloneFlags::CLONE_NEWNET];
193        expect.sort();
194        assert_eq!(setns_args, expect);
195
196        let mut unshare_args = test_command.get_unshare_args();
197        unshare_args.sort();
198        let mut expect = vec![CloneFlags::CLONE_NEWUSER, CloneFlags::CLONE_NEWPID];
199        expect.sort();
200        assert_eq!(unshare_args, expect)
201    }
202}