gatehouse_runtime_protocol/lifecycle_v2/
capture_inputs.rs1use super::{
3 capture_wire::{CaptureExpected, CaptureRequest},
4 protected_context::ProtectedContext,
5};
6use std::{
7 fs::File,
8 os::{
9 fd::AsRawFd,
10 unix::fs::{FileExt, MetadataExt},
11 },
12 path::Path,
13};
14type Result<T> = std::result::Result<T, &'static str>;
15const SEALS: i32 = libc::F_SEAL_WRITE | libc::F_SEAL_GROW | libc::F_SEAL_SHRINK | libc::F_SEAL_SEAL;
16pub struct ProtectedCaptureInputs {
18 request: CaptureRequest,
19 expected: CaptureExpected,
20 context: ProtectedContext,
21}
22impl ProtectedCaptureInputs {
23 pub fn request(&self) -> &CaptureRequest {
24 &self.request
25 }
26 pub fn expected(&self) -> &CaptureExpected {
27 &self.expected
28 }
29 pub fn context(&self) -> Result<&ProtectedContext> {
30 self.context.revalidate()?;
31 Ok(&self.context)
32 }
33}
34pub fn parse(config: File, expected: File, stdin: &[u8]) -> Result<ProtectedCaptureInputs> {
38 let request = CaptureRequest::decode(stdin)?;
39 cloexec(&config)?;
40 cloexec(&expected)?;
41 let bytes = sealed_bytes(&expected)?;
42 let expected = CaptureExpected::decode(&bytes, &request)?;
43 let context = ProtectedContext::from_held_versioned(Path::new(&expected.config_path), config)?;
44 if context.config_sha256()? != expected.config_sha256 {
45 return Err("protected capture config mismatch");
46 }
47 context.revalidate()?;
48 Ok(ProtectedCaptureInputs {
49 request,
50 expected,
51 context,
52 })
53}
54fn cloexec(file: &File) -> Result<()> {
55 let flags = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFD) };
56 if flags < 0
57 || unsafe { libc::fcntl(file.as_raw_fd(), libc::F_SETFD, flags | libc::FD_CLOEXEC) } < 0
58 {
59 return Err("capture descriptor flags rejected");
60 }
61 Ok(())
62}
63fn sealed_bytes(file: &File) -> Result<Vec<u8>> {
64 let m = file.metadata().map_err(|_| "capture expected metadata")?;
65 let seals = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GET_SEALS) };
66 if !m.is_file()
67 || m.nlink() != 0
68 || m.mode() & 0o7777 != 0o600
69 || m.uid() != unsafe { libc::geteuid() }
70 || m.len() == 0
71 || m.len() > 16384
72 || seals < 0
73 || seals & SEALS != SEALS
74 {
75 return Err("capture expected must be bounded private sealed memory");
76 }
77 let mut bytes = vec![0; m.len() as usize];
78 let mut offset = 0;
79 while offset < bytes.len() {
80 match file.read_at(&mut bytes[offset..], offset as u64) {
81 Ok(0) => return Err("capture expected short read"),
82 Ok(n) => offset += n,
83 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
84 Err(_) => return Err("capture expected read failed"),
85 }
86 }
87 Ok(bytes)
88}
89#[cfg(test)]
90pub(super) fn test_memory(bytes: &[u8], seals: i32, mode: u32) -> File {
91 use std::{io::Write, os::fd::FromRawFd};
92 let fd = unsafe {
93 libc::memfd_create(
94 c"podman-capture-test".as_ptr(),
95 libc::MFD_ALLOW_SEALING | libc::MFD_CLOEXEC,
96 )
97 };
98 assert!(fd >= 0);
99 let mut file = unsafe { File::from_raw_fd(fd) };
100 assert_eq!(unsafe { libc::fchmod(fd, mode) }, 0);
101 file.write_all(bytes).unwrap();
102 if seals != 0 {
103 assert_eq!(unsafe { libc::fcntl(fd, libc::F_ADD_SEALS, seals) }, 0);
104 }
105 file
106}
107#[cfg(test)]
108mod tests {
109 use super::*;
110 #[test]
111 fn sealed_expected_shape_and_every_seal_required() {
112 let good = test_memory(b"test", SEALS, 0o600);
113 assert_eq!(sealed_bytes(&good).unwrap(), b"test");
114 for seal in [
115 libc::F_SEAL_WRITE,
116 libc::F_SEAL_GROW,
117 libc::F_SEAL_SHRINK,
118 libc::F_SEAL_SEAL,
119 ] {
120 assert!(sealed_bytes(&test_memory(b"test", SEALS & !seal, 0o600)).is_err());
121 }
122 for (bytes, mode) in [
123 (vec![], 0o600),
124 (vec![b'x'; 16385], 0o600),
125 (b"test".to_vec(), 0o644),
126 ] {
127 assert!(sealed_bytes(&test_memory(&bytes, SEALS, mode)).is_err());
128 }
129 let ordinary = tempfile::tempfile().unwrap();
130 assert!(sealed_bytes(&ordinary).is_err());
131 }
132}