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}