]>
Commit | Line | Data |
---|---|---|
9cffeac4 WB |
1 | //! Module for LXC specific related seccomp handling. |
2 | ||
3 | use std::convert::TryFrom; | |
c95be5f6 WB |
4 | use std::ffi::CString; |
5 | use std::os::unix::fs::FileExt; | |
6 | use std::os::unix::io::{FromRawFd, IntoRawFd}; | |
7 | use std::{io, mem}; | |
9cffeac4 WB |
8 | |
9 | use failure::{bail, Error}; | |
41ff6d28 | 10 | use lazy_static::lazy_static; |
9cffeac4 WB |
11 | use libc::pid_t; |
12 | ||
c95be5f6 | 13 | use crate::pidfd::PidFd; |
e420f6f9 | 14 | use crate::seccomp::{SeccompNotif, SeccompNotifResp, SeccompNotifSizes}; |
0e2d0fa2 | 15 | use crate::socket::AsyncSeqPacketSocket; |
c95be5f6 | 16 | use crate::tools::{IoVec, IoVecMut}; |
9cffeac4 WB |
17 | |
18 | /// Seccomp notification proxy message sent by the lxc monitor. | |
19 | /// | |
20 | /// Whenever a process in a container triggers a seccomp notification, and lxc has a seccomp | |
21 | /// notification proxy configured, this is sent over to the proxy, together with a `SeccompNotif`, | |
22 | /// `SeccompNotifResp` and a cookie. | |
23 | /// | |
24 | /// Using this struct may be inconvenient. See the [`ProxyMessageBuffer`] for a convenient helper | |
25 | /// for communcation. | |
26 | #[repr(C)] | |
27 | pub struct SeccompNotifyProxyMsg { | |
28 | /// Reserved data must be zero. | |
29 | reserved0: u64, | |
30 | ||
31 | /// The lxc monitor pid. | |
32 | /// | |
33 | /// Unless some other proxy forwards proxy messages, this should be the same pid as the peer | |
34 | /// we receive this message from. | |
35 | monitor_pid: pid_t, | |
36 | ||
37 | /// The container's init pid. | |
38 | /// | |
39 | /// If supported by the kernel, the lxc monitor should keep a pidfd open to this process, so | |
40 | /// this pid should be valid as long as `monitor_pid` is valid. | |
41 | init_pid: pid_t, | |
42 | ||
43 | /// Information about the seccomp structure sizes. | |
44 | /// | |
45 | /// This must be equal to `SeccompNotifSizes::get()`, otherwise the proxy and lxc monitor have | |
46 | /// inconsistent views of the kernel's seccomp API. | |
47 | sizes: SeccompNotifSizes, | |
48 | ||
49 | /// The length of the container's configured `lxc.seccomp.notify.cookie` value. | |
50 | cookie_len: u64, | |
51 | } | |
52 | ||
53 | /// Helper to receive and verify proxy notification messages. | |
9cffeac4 | 54 | pub struct ProxyMessageBuffer { |
571dbe03 WB |
55 | proxy_msg: SeccompNotifyProxyMsg, |
56 | seccomp_notif: SeccompNotif, | |
57 | seccomp_resp: SeccompNotifResp, | |
58 | cookie_buf: Vec<u8>, | |
59 | ||
9cffeac4 WB |
60 | sizes: SeccompNotifSizes, |
61 | seccomp_packet_size: usize, | |
41214ae2 | 62 | |
c95be5f6 WB |
63 | pid_fd: Option<PidFd>, |
64 | mem_fd: Option<std::fs::File>, | |
9cffeac4 WB |
65 | } |
66 | ||
571dbe03 WB |
67 | unsafe fn io_vec_mut<T>(value: &mut T) -> IoVecMut { |
68 | IoVecMut::new(std::slice::from_raw_parts_mut( | |
69 | value as *mut T as *mut u8, | |
70 | mem::size_of::<T>(), | |
71 | )) | |
72 | } | |
73 | ||
74 | unsafe fn io_vec<T>(value: &T) -> IoVec { | |
75 | IoVec::new(std::slice::from_raw_parts( | |
76 | value as *const T as *const u8, | |
77 | mem::size_of::<T>(), | |
78 | )) | |
79 | } | |
80 | ||
41ff6d28 | 81 | lazy_static! { |
e420f6f9 WB |
82 | static ref SECCOMP_SIZES: SeccompNotifSizes = SeccompNotifSizes::get_checked() |
83 | .map_err(|e| panic!("{}\nrefusing to run", e)) | |
84 | .unwrap(); | |
41ff6d28 WB |
85 | } |
86 | ||
9cffeac4 WB |
87 | impl ProxyMessageBuffer { |
88 | /// Allocate a new proxy message buffer with a specific maximum cookie size. | |
e420f6f9 | 89 | pub fn new(max_cookie: usize) -> Self { |
41ff6d28 | 90 | let sizes = SECCOMP_SIZES.clone(); |
571dbe03 | 91 | |
9cffeac4 WB |
92 | let seccomp_packet_size = mem::size_of::<SeccompNotifyProxyMsg>() |
93 | + sizes.notif as usize | |
94 | + sizes.notif_resp as usize; | |
571dbe03 | 95 | |
e420f6f9 | 96 | Self { |
571dbe03 WB |
97 | proxy_msg: unsafe { mem::zeroed() }, |
98 | seccomp_notif: unsafe { mem::zeroed() }, | |
99 | seccomp_resp: unsafe { mem::zeroed() }, | |
100 | cookie_buf: unsafe { super::tools::vec::uninitialized(max_cookie) }, | |
9cffeac4 WB |
101 | sizes, |
102 | seccomp_packet_size, | |
41214ae2 WB |
103 | pid_fd: None, |
104 | mem_fd: None, | |
e420f6f9 | 105 | } |
9cffeac4 WB |
106 | } |
107 | ||
0e2d0fa2 | 108 | /// Returns None on EOF. |
c95be5f6 | 109 | pub async fn recv(&mut self, socket: &AsyncSeqPacketSocket) -> Result<bool, Error> { |
571dbe03 WB |
110 | self.proxy_msg.cookie_len = 0; |
111 | ||
112 | unsafe { | |
113 | self.cookie_buf.set_len(self.cookie_buf.capacity()); | |
114 | } | |
115 | ||
0e2d0fa2 | 116 | let mut iovec = [ |
571dbe03 WB |
117 | unsafe { io_vec_mut(&mut self.proxy_msg) }, |
118 | unsafe { io_vec_mut(&mut self.seccomp_notif) }, | |
119 | unsafe { io_vec_mut(&mut self.seccomp_resp) }, | |
120 | IoVecMut::new(self.cookie_buf.as_mut_slice()), | |
121 | ]; | |
9cffeac4 | 122 | |
9cffeac4 | 123 | unsafe { |
571dbe03 | 124 | self.cookie_buf.set_len(0); |
9cffeac4 | 125 | } |
571dbe03 | 126 | |
41214ae2 WB |
127 | let (size, fds) = socket.recv_fds_vectored(&mut iovec, 2).await?; |
128 | if size == 0 { | |
129 | return Ok(false); | |
130 | } | |
131 | ||
0e2d0fa2 WB |
132 | self.set_len(size)?; |
133 | ||
41214ae2 | 134 | let mut fds = fds.into_iter(); |
c95be5f6 WB |
135 | self.pid_fd = fds |
136 | .next() | |
137 | .map(|fd| unsafe { PidFd::from_raw_fd(fd.into_raw_fd()) }); | |
138 | self.mem_fd = fds | |
139 | .next() | |
140 | .map(|fd| unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) }); | |
41214ae2 WB |
141 | if self.mem_fd.is_none() { |
142 | self.drop_fds(); | |
143 | bail!("missing file descriptors with proxied seccomp message"); | |
144 | } | |
145 | ||
146 | Ok(true) | |
147 | } | |
148 | ||
c95be5f6 WB |
149 | /// Get the process' pidfd. |
150 | /// | |
151 | /// Note that the message must be valid, otherwise this panics! | |
152 | pub fn pid_fd(&self) -> &PidFd { | |
153 | self.pid_fd.as_ref().unwrap() | |
154 | } | |
155 | ||
156 | /// Get the process' mem fd. | |
157 | /// | |
158 | /// Note that this returns a non-mut trait object. This is because positional I/O does not need | |
159 | /// mutable self and the standard library correctly represents this in its `FileExt` trait! | |
160 | /// | |
161 | /// Note that the message must be valid, otherwise this panics! | |
162 | pub fn mem_fd(&self) -> &dyn FileExt { | |
163 | self.mem_fd.as_ref().unwrap() | |
164 | } | |
165 | ||
41214ae2 WB |
166 | pub fn drop_fds(&mut self) { |
167 | self.pid_fd = None; | |
168 | self.mem_fd = None; | |
9cffeac4 WB |
169 | } |
170 | ||
0e2d0fa2 | 171 | /// Send the current data as response. |
c95be5f6 | 172 | pub async fn respond(&mut self, socket: &AsyncSeqPacketSocket) -> io::Result<()> { |
0e2d0fa2 | 173 | let iov = [ |
571dbe03 WB |
174 | unsafe { io_vec(&self.proxy_msg) }, |
175 | unsafe { io_vec(&self.seccomp_notif) }, | |
176 | unsafe { io_vec(&self.seccomp_resp) }, | |
0e2d0fa2 WB |
177 | ]; |
178 | socket.sendmsg_vectored(&iov).await | |
9cffeac4 WB |
179 | } |
180 | ||
181 | #[inline] | |
182 | fn prepare_response(&mut self) { | |
183 | let id = self.request().id; | |
184 | let resp = self.response_mut(); | |
185 | resp.id = id; | |
186 | resp.val = -1; | |
187 | resp.error = -libc::ENOSYS; | |
188 | resp.flags = 0; | |
189 | } | |
190 | ||
571dbe03 WB |
191 | /// Called by with_io_slice after the callback returned the new size. This verifies that |
192 | /// there's enough data available. | |
9cffeac4 | 193 | pub fn set_len(&mut self, len: usize) -> Result<(), Error> { |
571dbe03 WB |
194 | if len < self.seccomp_packet_size { |
195 | bail!("seccomp proxy message too short"); | |
9cffeac4 WB |
196 | } |
197 | ||
571dbe03 WB |
198 | if self.proxy_msg.reserved0 != 0 { |
199 | bail!("reserved data wasn't 0, liblxc secocmp notify protocol mismatch"); | |
200 | } | |
201 | ||
202 | if !self.check_sizes() { | |
9cffeac4 WB |
203 | bail!("seccomp proxy message content size validation failed"); |
204 | } | |
205 | ||
571dbe03 WB |
206 | if len - self.seccomp_packet_size > self.cookie_buf.capacity() { |
207 | bail!("seccomp proxy message too long"); | |
208 | } | |
209 | ||
210 | let cookie_len = match usize::try_from(self.proxy_msg.cookie_len) { | |
211 | Ok(cl) => cl, | |
212 | Err(_) => { | |
213 | self.proxy_msg.cookie_len = 0; | |
214 | bail!("cookie length exceeds our size type!"); | |
215 | } | |
216 | }; | |
217 | ||
218 | if len != self.seccomp_packet_size + cookie_len { | |
52f50bd4 WB |
219 | bail!( |
220 | "seccomp proxy packet contains unexpected cookie length {} + {} != {}", | |
221 | self.seccomp_packet_size, | |
571dbe03 | 222 | cookie_len, |
52f50bd4 WB |
223 | len |
224 | ); | |
9cffeac4 WB |
225 | } |
226 | ||
227 | unsafe { | |
571dbe03 | 228 | self.cookie_buf.set_len(cookie_len); |
9cffeac4 WB |
229 | } |
230 | ||
231 | self.prepare_response(); | |
232 | ||
233 | Ok(()) | |
234 | } | |
235 | ||
571dbe03 WB |
236 | fn check_sizes(&self) -> bool { |
237 | let got = self.proxy_msg.sizes.clone(); | |
9cffeac4 WB |
238 | got.notif == self.sizes.notif |
239 | && got.notif_resp == self.sizes.notif_resp | |
240 | && got.data == self.sizes.data | |
241 | } | |
242 | ||
9cffeac4 WB |
243 | /// Get the monitor pid from the current message. |
244 | /// | |
245 | /// There's no guarantee that the pid is valid. | |
246 | pub fn monitor_pid(&self) -> pid_t { | |
571dbe03 | 247 | self.proxy_msg.monitor_pid |
9cffeac4 WB |
248 | } |
249 | ||
250 | /// Get the container's init pid from the current message. | |
251 | /// | |
252 | /// There's no guarantee that the pid is valid. | |
253 | pub fn init_pid(&self) -> pid_t { | |
571dbe03 | 254 | self.proxy_msg.init_pid |
9cffeac4 WB |
255 | } |
256 | ||
257 | /// Get the syscall request structure of this message. | |
258 | pub fn request(&self) -> &SeccompNotif { | |
571dbe03 | 259 | &self.seccomp_notif |
9cffeac4 WB |
260 | } |
261 | ||
262 | /// Access the response buffer of this message. | |
263 | pub fn response_mut(&mut self) -> &mut SeccompNotifResp { | |
571dbe03 | 264 | &mut self.seccomp_resp |
9cffeac4 WB |
265 | } |
266 | ||
267 | /// Get the cookie's length. | |
268 | pub fn cookie_len(&self) -> usize { | |
571dbe03 | 269 | usize::try_from(self.proxy_msg.cookie_len).expect("cookie size should fit in an usize") |
9cffeac4 WB |
270 | } |
271 | ||
272 | /// Get the cookie sent along with this message. | |
273 | pub fn cookie(&self) -> &[u8] { | |
571dbe03 | 274 | &self.cookie_buf |
9cffeac4 | 275 | } |
c95be5f6 WB |
276 | |
277 | #[inline] | |
278 | fn checked_arg(&self, arg: u32) -> nix::Result<u64> { | |
279 | self.request() | |
280 | .data | |
281 | .args | |
282 | .get(arg as usize) | |
283 | .map(|x| *x) | |
284 | .ok_or_else(|| nix::errno::Errno::ERANGE.into()) | |
285 | } | |
286 | ||
287 | /// Get a parameter as C String. | |
288 | /// | |
289 | /// Strings are limited to 4k bytes currently. | |
290 | pub fn arg_c_string(&self, arg: u32) -> Result<CString, Error> { | |
291 | crate::syscall::get_c_string(self, self.checked_arg(arg)?) | |
292 | } | |
9cffeac4 | 293 | } |