capsicum_net/lib.rs
1// vim: tw=80
2//! Rust bindings to FreeBSD's
3//! [cap_net(3)](https://man.freebsd.org/cgi/man.cgi?query=cap_net) library.
4//!
5//! cap_net allows access to several network APIs that are forbidden in
6//! capability mode by delegating them to an unsandboxed process, the Casper
7//! daemon.
8//!
9//! The main entry point for this library is [`CapNetAgent`]. The agent may be
10//! created at any time, whether in capability mode or not, as long as the
11//! Casper daemon was started prior to entering capability mode. After creating
12//! the agent, this library has three interfaces:
13//!
14//! * Low-level methods directly on the `CapNetAgent` object. These work well
15//! with the [nix](https://docs.rs/nix/0.27.1/nix/) crate.
16//! * Extension traits that work on the standard socket types, like
17//! [`UdpSocketExt`](crate::std::UdpSocketExt).
18//! * Extension traits that work with tokio types, like
19//! [`TcpSocketExt`](tokio::TcpSocketExt).
20//!
21//! # Example
22//! In this example, we create a new UdpSocket and bind it to a port. Such a
23//! thing is normally not allowed in capability mode, but `cap_bind` lets us do
24//! it.
25//!
26//! ```
27//! use std::{io, str::FromStr, net::UdpSocket };
28//!
29//! use capsicum::casper::Casper;
30//! use capsicum_net::{CasperExt, std::UdpSocketExt};
31//!
32//! // Safe because we are single-threaded
33//! let mut casper = unsafe { Casper::new().unwrap() };
34//! let mut cap_net = casper.net().unwrap();
35//!
36//! capsicum::enter();
37//!
38//! // At this point regular bind(2) will fail because we're in capability mode.
39//! UdpSocket::bind("127.0.0.1:8086").unwrap_err();
40//!
41//! // But cap_bind will still succeed.
42//! let socket = UdpSocket::cap_bind(&mut cap_net, "127.0.0.1:8086")
43//! .unwrap();
44//! ```
45#![cfg_attr(docsrs, feature(doc_cfg))]
46#![warn(missing_docs)]
47use ::std::{
48 io,
49 marker::PhantomData,
50 net::ToSocketAddrs,
51 os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd},
52 path::Path,
53};
54use bitflags::bitflags;
55use capsicum::casper;
56use nix::{
57 errno::Errno,
58 sys::socket::{
59 AddressFamily,
60 SockFlag,
61 SockType,
62 SockaddrIn,
63 SockaddrIn6,
64 SockaddrLike,
65 },
66 Result,
67};
68
69mod ffi;
70
71pub mod std;
72#[cfg(feature = "tokio")]
73pub mod tokio;
74
75casper::service_connection! {
76 /// A connection to the Casper
77 /// [cap_net(3)](https://man.freebsd.org/cgi/man.cgi?query=cap_net) service.
78 #[derive(Debug)]
79 pub CapNetAgent,
80 c"system.net",
81 net
82}
83
84impl CapNetAgent {
85 /// A low-level bind(2) workalike, but in capability mode.
86 ///
87 /// # Examples
88 ///
89 /// ```
90 /// use std::{
91 /// os::fd::AsRawFd,
92 /// str::FromStr
93 /// };
94 /// use capsicum::casper::Casper;
95 /// use capsicum_net::CasperExt;
96 /// use nix::sys::socket::{
97 /// AddressFamily, SockaddrIn, SockaddrLike, SockFlag,
98 /// SockType, socket
99 /// };
100 ///
101 /// // Safe if we are single-threaded
102 /// let mut casper = unsafe { Casper::new().unwrap() };
103 /// let mut cap_net = casper.net().unwrap();
104 /// let s = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(),
105 /// None).unwrap();
106 /// let addr = SockaddrIn::from_str("127.0.0.1:8081").unwrap();
107 /// cap_net.bind(&s, &addr).unwrap();
108 /// ```
109 pub fn bind<F>(&mut self, sock: &F, addr: &dyn SockaddrLike) -> Result<()>
110 where
111 F: AsFd,
112 {
113 let fd = sock.as_fd().as_raw_fd();
114 let res = unsafe {
115 ffi::cap_bind(self.0.as_mut_ptr(), fd, addr.as_ptr(), addr.len())
116 };
117 Errno::result(res).map(drop)
118 }
119
120 /// Helper that binds a raw socket to a std sockaddr
121 fn bind_std_fd(
122 &mut self,
123 sock: BorrowedFd,
124 addr: ::std::net::SocketAddr,
125 ) -> io::Result<()> {
126 let ap = self.0.as_mut_ptr();
127 let fd = sock.as_raw_fd();
128 let res = match addr {
129 // Even though std::net::SocketAddrV4 is probably stored identically
130 // to libc::sockaddr_in, that isn't guaranteed, so we must convert
131 // it. Nix's representation _is_ guaranteed. Ditto for
132 // SocketAddrV6.
133 // XXX ffi::cap_bind is technically a blocking operation. It blocks
134 // within the C library. But the communication is always local, and
135 // in cursory testing is < 0.2 ms, so we'll do it in an ordinary
136 // tokio thread.
137 ::std::net::SocketAddr::V4(addr) => {
138 let sin = SockaddrIn::from(addr);
139 unsafe { ffi::cap_bind(ap, fd, sin.as_ptr(), sin.len()) }
140 }
141 ::std::net::SocketAddr::V6(addr) => {
142 let sin6 = SockaddrIn6::from(addr);
143 unsafe { ffi::cap_bind(ap, fd, sin6.as_ptr(), sin6.len()) }
144 }
145 };
146 if res == 0 {
147 Ok(())
148 } else {
149 Err(io::Error::last_os_error())
150 }
151 }
152
153 /// Private helper used by the std extension traits
154 fn bind_std_to_addrs<A, S>(&mut self, addrs: A) -> io::Result<S>
155 where
156 A: ToSocketAddrs,
157 S: From<OwnedFd>,
158 {
159 let mut last_err = None;
160 for addr in addrs.to_socket_addrs()? {
161 let family = if addr.is_ipv4() {
162 AddressFamily::Inet
163 } else {
164 AddressFamily::Inet6
165 };
166 let sock = nix::sys::socket::socket(
167 family,
168 SockType::Stream,
169 SockFlag::empty(),
170 None,
171 )
172 .map_err(io::Error::from)?;
173 match self.bind_std_fd(sock.as_fd(), addr) {
174 Ok(()) => return Ok(S::from(sock)),
175 Err(e) => {
176 last_err = Some(e);
177 }
178 }
179 }
180 Err(last_err.unwrap_or_else(|| {
181 io::Error::new(
182 io::ErrorKind::InvalidInput,
183 "could not resolve to any addresses",
184 )
185 }))
186 }
187
188 /// Helper that creates a new std socket and binds it to a unix path
189 fn bind_std_unix<P>(
190 &mut self,
191 sock_type: SockType,
192 path: P,
193 ) -> io::Result<OwnedFd>
194 where
195 P: AsRef<Path>,
196 {
197 let s = nix::sys::socket::socket(
198 AddressFamily::Unix,
199 sock_type,
200 SockFlag::empty(),
201 None,
202 )
203 .unwrap();
204 let want = nix::sys::socket::UnixAddr::new(path.as_ref()).unwrap();
205 self.bind(&s, &want)?;
206 Ok(s)
207 }
208
209 /// A low-level connect(2) workalike, but in capability mode.
210 ///
211 /// # Examples
212 ///
213 /// ```no_run
214 /// use std::{
215 /// os::fd::AsRawFd,
216 /// str::FromStr
217 /// };
218 /// use capsicum::casper::Casper;
219 /// use capsicum_net::CasperExt;
220 /// use nix::sys::socket::{
221 /// AddressFamily, SockaddrIn, SockaddrLike, SockFlag,
222 /// SockType, socket
223 /// };
224 ///
225 /// // Safe if we are single-threaded
226 /// let mut casper = unsafe { Casper::new().unwrap() };
227 /// let mut cap_net = casper.net().unwrap();
228 /// let s = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(),
229 /// None).unwrap();
230 /// let addr = SockaddrIn::from_str("8.8.8.8:53").unwrap();
231 /// cap_net.connect(&s, &addr).unwrap();
232 /// ```
233 pub fn connect<F>(
234 &mut self,
235 sock: &F,
236 addr: &dyn SockaddrLike,
237 ) -> Result<()>
238 where
239 F: AsFd,
240 {
241 let fd = sock.as_fd().as_raw_fd();
242 let res = unsafe {
243 ffi::cap_connect(self.0.as_mut_ptr(), fd, addr.as_ptr(), addr.len())
244 };
245 Errno::result(res).map(drop)
246 }
247
248 /// Helper that connects a raw socket to a std sockaddr
249 fn connect_std_fd(
250 &mut self,
251 sock: BorrowedFd,
252 addr: ::std::net::SocketAddr,
253 ) -> io::Result<()> {
254 let ap = self.0.as_mut_ptr();
255 let fd = sock.as_raw_fd();
256 let res = match addr {
257 // Even though std::net::SocketAddrV4 is probably stored identically
258 // to libc::sockaddr_in, that isn't guaranteed, so we must convert
259 // it. Nix's representation _is_ guaranteed. Ditto for
260 // SocketAddrV6.
261 // XXX ffi::cap_connect is technically a blocking operation. It
262 // blocks within the C library.
263 // TODO: determine if Tokio should be using a thread for this.
264 ::std::net::SocketAddr::V4(addr) => {
265 let sin = SockaddrIn::from(addr);
266 unsafe { ffi::cap_connect(ap, fd, sin.as_ptr(), sin.len()) }
267 }
268 ::std::net::SocketAddr::V6(addr) => {
269 let sin6 = SockaddrIn6::from(addr);
270 unsafe { ffi::cap_connect(ap, fd, sin6.as_ptr(), sin6.len()) }
271 }
272 };
273 if res == 0 {
274 Ok(())
275 } else {
276 Err(io::Error::last_os_error())
277 }
278 }
279
280 /// Private helper used by the std extension traits
281 fn connect_std_to_addrs<A>(
282 &mut self,
283 sock: BorrowedFd,
284 addrs: A,
285 ) -> io::Result<()>
286 where
287 A: ToSocketAddrs,
288 {
289 let mut last_err = None;
290 for addr in addrs.to_socket_addrs()? {
291 match self.connect_std_fd(sock, addr) {
292 Ok(()) => return Ok(()),
293 Err(e) => {
294 last_err = Some(e);
295 }
296 }
297 }
298 Err(last_err.unwrap_or_else(|| {
299 io::Error::new(
300 io::ErrorKind::InvalidInput,
301 "could not resolve to any addresses",
302 )
303 }))
304 }
305
306 /// Return an opaque handle used to further limit the capabilities of the
307 /// `cap_net` service.
308 ///
309 /// Each time a [`Limit`] is constructed and applied it can reduce, but
310 /// never enlarge, the service's capabilities.
311 ///
312 /// # Example
313 /// ```
314 /// use std::{
315 /// os::fd::AsRawFd,
316 /// str::FromStr
317 /// };
318 /// use capsicum::casper::Casper;
319 /// use capsicum_net::{CasperExt, LimitFlags};
320 /// use nix::sys::socket::{SockaddrIn, SockaddrLike};
321 ///
322 /// let mut casper = unsafe { Casper::new().unwrap() };
323 /// let mut cap_net = casper.net().unwrap();
324 /// let mut limit = cap_net.limit(LimitFlags::BIND);
325 /// let addr = SockaddrIn::from_str("127.0.0.1:8083").unwrap();
326 /// limit.bind(&addr);
327 /// limit.limit();
328 /// // Now the service will refuse attempts to bind to any other address or
329 /// // port.
330 /// ```
331 pub fn limit(&mut self, flags: LimitFlags) -> Limit {
332 let limit = unsafe {
333 ffi::cap_net_limit_init(self.0.as_mut_ptr(), flags.bits())
334 };
335 assert!(!limit.is_null());
336 Limit {
337 limit,
338 phantom: PhantomData,
339 }
340 }
341}
342
343/// Used to limit which operations will be allowed by the [`CapNetAgent`].
344#[repr(transparent)]
345pub struct Limit<'a> {
346 limit: *mut ffi::cap_net_limit_t,
347 // Because cap_net_limit_t stores a pointer to cap_channel_t
348 phantom: PhantomData<&'a mut CapNetAgent>,
349}
350
351bitflags! {
352 /// Used by [`CapNetAgent::limit`] to restrict which functions are permitted.
353 pub struct LimitFlags: u64 {
354 /// Allow any of the `cap_bind` methods
355 const BIND = ffi::CAPNET_BIND as u64;
356 /// Allow any of the `cap_connect` methods
357 const CONNECT = ffi::CAPNET_CONNECT as u64;
358 }
359}
360
361impl<'a> Limit<'a> {
362 /// Limit the `cap_net` service to only allow binding to the given address.
363 ///
364 /// May be called multiple times to allow binding to multiple addresses.
365 pub fn bind(&mut self, sa: &dyn SockaddrLike) -> &mut Self {
366 let newlimit = unsafe {
367 ffi::cap_net_limit_bind(self.limit, sa.as_ptr(), sa.len())
368 };
369 assert_eq!(newlimit, self.limit);
370 self
371 }
372
373 /// Limit the `cap_net` service to only allow connecting to the given
374 /// address.
375 ///
376 /// May be called multiple times to allow connecting to multiple addresses.
377 pub fn connect(&mut self, sa: &dyn SockaddrLike) -> &mut Self {
378 let newlimit = unsafe {
379 ffi::cap_net_limit_connect(self.limit, sa.as_ptr(), sa.len())
380 };
381 assert_eq!(newlimit, self.limit);
382 self
383 }
384
385 /// Actually apply the limits
386 pub fn limit(self) -> io::Result<()> {
387 let res = unsafe { ffi::cap_net_limit(self.limit) };
388 if res == 0 {
389 Ok(())
390 } else {
391 Err(io::Error::last_os_error())
392 }
393 }
394}