extrasafe_multiarch/builtins/
network.rs

1//! Contains a [`RuleSet`] for allowing networking-related syscalls.
2
3use std::collections::{HashMap, HashSet};
4
5use crate::syscalls::Sysno;
6
7use super::YesReally;
8use crate::{SeccompRule, RuleSet};
9
10// TODO: make bind calls conditional on the DGRAM/UNIX/STREAM flag in each function
11
12// TODO: add io_uring
13const NET_IO_SYSCALLS: &[Sysno] = &[
14    #[cfg(enabled_arch = "x86_64")]
15    Sysno::epoll_create,
16    Sysno::epoll_create1, Sysno::epoll_ctl,
17    #[cfg(enabled_arch = "x86_64")]
18    Sysno::epoll_wait,
19    Sysno::epoll_pwait, Sysno::epoll_pwait2,
20    #[cfg(enabled_arch = "x86_64")]
21    Sysno::select,
22    Sysno::pselect6,
23    #[cfg(enabled_arch = "x86_64")]
24    Sysno::poll,
25    Sysno::ppoll, Sysno::accept, Sysno::accept4,
26    // used in reqwest::blocking I guess to notify when blocking reads finish?
27    #[cfg(enabled_arch = "x86_64")]
28    Sysno::eventfd,
29    Sysno::eventfd2,
30    // Used to set tcp_nodelay
31    Sysno::fcntl, Sysno::ioctl,
32    Sysno::getsockopt,
33    Sysno::setsockopt,
34
35    // Misc socket info
36    Sysno::getpeername,
37    Sysno::getsockname,
38];
39
40// listen is technically not a "read" syscall but you'd never listen and not read.
41const NET_READ_SYSCALLS: &[Sysno] = &[Sysno::listen,
42                                      Sysno::recvfrom, Sysno::recvmsg, Sysno::recvmmsg,
43                                      Sysno::read, Sysno::readv, Sysno::preadv, Sysno::preadv2];
44const NET_WRITE_SYSCALLS: &[Sysno] = &[Sysno::sendto, Sysno::sendmsg, Sysno::sendmmsg,
45                                       Sysno::sendfile,
46                                       Sysno::write, Sysno::writev, Sysno::pwritev, Sysno::pwritev2];
47
48// TODO: refactor Socket rule creation to reduce duplication in the allow_start_*_server functions
49
50/// A [`RuleSet`] representing syscalls that perform network operations - accept/listen/bind/connect etc.
51///
52/// # How to use
53///
54/// 1. Select TCP or UDP (or both) with `enable_tcp()`, `enable_udp()`
55/// 2a. If you are a server of some sort, **strongly** consider first binding to your ports and
56///     then not allowing further binds by using `running_tcp_server()` or `running_udp_server()`.
57///     Otherwise,
58/// 2b. If you are a client, use `tcp_client()` and/or `udp_client()`, which does not allow
59///     `accept` or `listen` syscalls.
60/// The most common use-case: select TCP or UDP (or both) with `.enable_tcp()` or `.enable_udp()`,
61/// and then decide if you're going to allow binding to new ports
62///
63///
64/// # Security considerations
65///
66/// If you enable writing (on either tcp or udp), this enables the `write` syscall which will
67/// therefore also enable writing to stdout/stderr and any open files. Therefore you should take
68/// care to consider whether you can split up your program (e.g. across separate threads) into a
69/// part that opens and writes to files and a part that speaks to the network. This is a good
70/// security practice in general.
71#[must_use]
72pub struct Networking {
73    /// Syscalls that are allowed
74    allowed: HashSet<Sysno>,
75    /// Syscalls that are allowed with custom rules, e.g. only allow to specific fds
76    custom: HashMap<Sysno, Vec<SeccompRule>>,
77}
78
79impl Networking {
80    /// By default, allow no networking syscalls.
81    pub fn nothing() -> Networking {
82        Networking {
83            allowed: HashSet::new(),
84            custom: HashMap::new(),
85        }
86    }
87
88    /// Allow a running TCP server to continue running. Does not allow `socket` or `bind`,
89    /// preventing new sockets from being created.
90    pub fn allow_running_tcp_servers(mut self) -> Networking {
91        self.allowed.extend(NET_IO_SYSCALLS);
92        self.allowed.extend(NET_READ_SYSCALLS);
93        self.allowed.extend(NET_WRITE_SYSCALLS);
94
95        self
96    }
97
98    /// Allow starting new TCP servers.
99    ///
100    /// # Security Notes
101    ///
102    /// You probably don't need to use this. In most cases you can just run your server and then
103    /// use [`allow_running_tcp_servers`](Self::allow_running_tcp_servers). See
104    /// `examples/network_server.rs` for an example with warp.
105    pub fn allow_start_tcp_servers(mut self) -> YesReally<Networking> {
106        const AF_INET: u64 = libc::AF_INET as u64;
107        const AF_INET6: u64 = libc::AF_INET6 as u64;
108        const SOCK_STREAM: u64 = libc::SOCK_STREAM as u64;
109
110        // IPv4
111        let rule = SeccompRule::new(Sysno::socket)
112            .and_condition(seccomp_arg_filter!(arg0 & AF_INET == AF_INET))
113            .and_condition(seccomp_arg_filter!(arg1 & SOCK_STREAM == SOCK_STREAM));
114        self.custom.entry(Sysno::socket)
115            .or_insert_with(Vec::new)
116            .push(rule);
117        // IPv6
118        let rule = SeccompRule::new(Sysno::socket)
119            .and_condition(seccomp_arg_filter!(arg0 & AF_INET6 == AF_INET6))
120            .and_condition(seccomp_arg_filter!(arg1 & SOCK_STREAM == SOCK_STREAM));
121        self.custom.entry(Sysno::socket)
122            .or_insert_with(Vec::new)
123            .push(rule);
124
125        // Bind is unconditional here because besides the socket fd, the other argument is a
126        // struct we can't look into due to seccomp restrictions.
127        self.allowed.extend(&[Sysno::bind]);
128        self.allowed.extend(NET_IO_SYSCALLS);
129        self.allowed.extend(NET_READ_SYSCALLS);
130        self.allowed.extend(NET_WRITE_SYSCALLS);
131
132        YesReally::new(self)
133    }
134
135    /// Allow a running UDP socket to continue running. Does not allow `socket` or `bind`,
136    /// preventing new sockets from being created.
137    pub fn allow_running_udp_sockets(mut self) -> Networking {
138        self.allowed.extend(NET_IO_SYSCALLS);
139        self.allowed.extend(NET_READ_SYSCALLS);
140        self.allowed.extend(NET_WRITE_SYSCALLS);
141
142        self
143    }
144
145    /// Allow starting new UDP sockets.
146    ///
147    /// # Security Notes
148    ///
149    /// You probably don't need to use this. In most cases you can just run your server and then
150    /// use [`allow_running_udp_sockets`](Self::allow_running_udp_sockets).
151    pub fn allow_start_udp_servers(mut self) -> YesReally<Networking> {
152        const AF_INET: u64 = libc::AF_INET as u64;
153        const AF_INET6: u64 = libc::AF_INET6 as u64;
154        const SOCK_DGRAM: u64 = libc::SOCK_DGRAM as u64;
155
156        // IPv4
157        let rule = SeccompRule::new(Sysno::socket)
158            .and_condition(seccomp_arg_filter!(arg0 & AF_INET == AF_INET))
159            .and_condition(seccomp_arg_filter!(arg1 & SOCK_DGRAM == SOCK_DGRAM));
160        self.custom.entry(Sysno::socket)
161            .or_insert_with(Vec::new)
162            .push(rule);
163        // IPv6
164        let rule = SeccompRule::new(Sysno::socket)
165            .and_condition(seccomp_arg_filter!(arg0 & AF_INET6 == AF_INET6))
166            .and_condition(seccomp_arg_filter!(arg1 & SOCK_DGRAM == SOCK_DGRAM));
167        self.custom.entry(Sysno::socket)
168            .or_insert_with(Vec::new)
169            .push(rule);
170
171        self.allowed.extend(&[Sysno::bind]);
172        self.allowed.extend(NET_IO_SYSCALLS);
173        self.allowed.extend(NET_READ_SYSCALLS);
174        self.allowed.extend(NET_WRITE_SYSCALLS);
175
176        YesReally::new(self)
177    }
178
179    /// Allow `connect` syscall
180    ///
181    /// # Security Considerations
182    ///
183    /// This allows connnecting to a potentially dangerous network resource
184    pub fn allow_connect(mut self) -> YesReally<Networking> {
185        self.allowed.extend(&[Sysno::connect]);
186        YesReally::new(self)
187    }
188
189    /// Allow starting new TCP clients.
190    ///
191    /// # Security Notes
192    ///
193    /// In some cases you can create the socket ahead of time, but that isn't possible with e.g.
194    /// reqwest, so we allow socket but not bind here.
195    pub fn allow_start_tcp_clients(mut self) -> Networking {
196        const AF_INET: u64 = libc::AF_INET as u64;
197        const AF_INET6: u64 = libc::AF_INET6 as u64;
198        const SOCK_STREAM: u64 = libc::SOCK_STREAM as u64;
199
200        // IPv4
201        let rule = SeccompRule::new(Sysno::socket)
202            .and_condition(seccomp_arg_filter!(arg0 & AF_INET == AF_INET))
203            .and_condition(seccomp_arg_filter!(arg1 & SOCK_STREAM == SOCK_STREAM));
204        self.custom.entry(Sysno::socket)
205            .or_insert_with(Vec::new)
206            .push(rule);
207        // IPv6
208        let rule = SeccompRule::new(Sysno::socket)
209            .and_condition(seccomp_arg_filter!(arg0 & AF_INET6 == AF_INET6))
210            .and_condition(seccomp_arg_filter!(arg1 & SOCK_STREAM == SOCK_STREAM));
211        self.custom.entry(Sysno::socket)
212            .or_insert_with(Vec::new)
213            .push(rule);
214        
215        self.allowed.extend(&[Sysno::connect]);
216        self.allowed.extend(NET_IO_SYSCALLS);
217        self.allowed.extend(NET_READ_SYSCALLS);
218        self.allowed.extend(NET_WRITE_SYSCALLS);
219
220        self
221    }
222
223    /// Allow a running TCP client to continue running. Does not allow `socket` or `connect`,
224    /// preventing new sockets from being created.
225    ///
226    /// This is technically the same as
227    /// [`allow_running_tcp_servers`](Self::allow_running_tcp_servers).
228    pub fn allow_running_tcp_clients(mut self) -> Networking {
229        self.allowed.extend(NET_IO_SYSCALLS);
230        self.allowed.extend(NET_READ_SYSCALLS);
231        self.allowed.extend(NET_WRITE_SYSCALLS);
232
233        self
234    }
235
236    /// Allow starting new Unix domain servers
237    ///
238    /// # Security Notes
239    ///
240    /// You probably don't need to use this. In most cases you can just run your server and then
241    /// use [`allow_running_unix_servers`](Self::allow_running_unix_servers).
242    pub fn allow_start_unix_servers(mut self) -> YesReally<Networking> {
243        const AF_UNIX: u64 = libc::AF_UNIX as u64;
244        const SOCK_STREAM: u64 = libc::SOCK_STREAM as u64;
245        const SOCK_DGRAM: u64 = libc::SOCK_DGRAM as u64;
246
247        // We allow both stream and dgram unix sockets
248        let rule = SeccompRule::new(Sysno::socket)
249            .and_condition(seccomp_arg_filter!(arg0 & AF_UNIX == AF_UNIX))
250            .and_condition(seccomp_arg_filter!(arg1 & SOCK_STREAM == SOCK_STREAM));
251        self.custom.entry(Sysno::socket)
252            .or_insert_with(Vec::new)
253            .push(rule);
254        // DGRAM
255        let rule = SeccompRule::new(Sysno::socket)
256            .and_condition(seccomp_arg_filter!(arg0 & AF_UNIX == AF_UNIX))
257            .and_condition(seccomp_arg_filter!(arg1 & SOCK_DGRAM == SOCK_DGRAM));
258        self.custom.entry(Sysno::socket)
259            .or_insert_with(Vec::new)
260            .push(rule);
261
262        self.allowed.extend(&[Sysno::bind]);
263        self.allowed.extend(NET_IO_SYSCALLS);
264        self.allowed.extend(NET_READ_SYSCALLS);
265        self.allowed.extend(NET_WRITE_SYSCALLS);
266
267        YesReally::new(self)
268    }
269
270    /// Allow a running Unix server to continue running. Does not allow `socket` or `bind`,
271    /// preventing new sockets from being created.
272    pub fn allow_running_unix_servers(mut self) -> Networking {
273        self.allowed.extend(NET_IO_SYSCALLS);
274        self.allowed.extend(NET_READ_SYSCALLS);
275        self.allowed.extend(NET_WRITE_SYSCALLS);
276
277        self
278    }
279
280    /// Allow a running Unix socket client to continue running. Does not allow `socket` or `connect`,
281    /// preventing new sockets from being created.
282    ///
283    /// This is technically the same as
284    /// [`allow_running_unix_servers`](Self::allow_running_unix_servers).
285    pub fn allow_running_unix_clients(mut self) -> Networking {
286        self.allowed.extend(NET_IO_SYSCALLS);
287        self.allowed.extend(NET_READ_SYSCALLS);
288        self.allowed.extend(NET_WRITE_SYSCALLS);
289
290        self
291    }
292}
293
294impl RuleSet for Networking {
295    fn simple_rules(&self) -> Vec<crate::syscalls::Sysno> {
296        self.allowed.iter().copied().collect()
297    }
298
299    fn conditional_rules(&self) -> HashMap<crate::syscalls::Sysno, Vec<SeccompRule>> {
300        self.custom.clone()
301    }
302
303    fn name(&self) -> &'static str {
304        "Networking"
305    }
306}