gatehouse_runtime_protocol/lifecycle/installation/
sealed.rs1use super::*;
5use sha2::{Digest, Sha256};
6use std::{
7 fs::File,
8 os::{
9 fd::AsRawFd,
10 unix::fs::{FileExt, MetadataExt},
11 },
12};
13const SEALS: i32 = libc::F_SEAL_WRITE | libc::F_SEAL_GROW | libc::F_SEAL_SHRINK | libc::F_SEAL_SEAL;
14fn read(file: &File) -> Result<Vec<u8>> {
15 let m = file
16 .metadata()
17 .map_err(|_| Invalid("invalid lifecycle protected descriptor"))?;
18 let seals = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GET_SEALS) };
20 if !m.is_file()
21 || m.nlink() != 0
22 || m.mode() & 0o7777 != 0o600
23 || m.uid() != unsafe { libc::geteuid() }
24 || m.len() == 0
25 || m.len() > crate::MAX_MESSAGE_BYTES as u64
26 || seals < 0
27 || seals & SEALS != SEALS
28 {
29 return Err(Invalid(
30 "lifecycle input must be bounded private sealed memory",
31 ));
32 }
33 let mut bytes = vec![0; m.len() as usize];
34 let mut offset = 0;
35 while offset < bytes.len() {
36 match file.read_at(&mut bytes[offset..], offset as u64) {
37 Ok(0) => return Err(Invalid("incomplete lifecycle protected input")),
38 Ok(n) => offset += n,
39 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
40 Err(_) => return Err(Invalid("cannot read lifecycle protected input")),
41 }
42 }
43 Ok(bytes)
44}
45pub fn parse_protected_inputs(config: &File, expected: &File, stdin: &[u8]) -> Result<Request> {
48 if stdin.len() > crate::MAX_MESSAGE_BYTES {
49 return Err(Invalid("lifecycle stdin too large"));
50 }
51 let config = read(config)?;
52 let expected = read(expected)?;
53 let request = super::super::wire::parse_protected_request(stdin, &expected)?;
54 let c: Config = parse(&config)?;
55 if c.schema != "gate-runtime/lifecycle-config-v1"
56 || c.engine_binding != request.binding.engine_binding
57 || request.binding.config_sha256 != format!("{:x}", Sha256::digest(&config))
58 || c.engine_binding.principal_uid != unsafe { libc::geteuid() }
59 {
60 return Err(Invalid("lifecycle protected configuration mismatch"));
61 }
62 Ok(request)
63}
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use std::{io::Write, os::fd::FromRawFd};
68 fn memory(bytes: &[u8], seal: bool, mode: u32) -> File {
69 let fd = unsafe {
70 libc::memfd_create(
71 c"lifecycle-test-input".as_ptr(),
72 libc::MFD_CLOEXEC | libc::MFD_ALLOW_SEALING,
73 )
74 };
75 assert!(fd >= 0);
76 let mut f = unsafe { File::from_raw_fd(fd) };
77 assert_eq!(unsafe { libc::fchmod(fd, mode) }, 0);
78 f.write_all(bytes).unwrap();
79 if seal {
80 assert_eq!(unsafe { libc::fcntl(fd, libc::F_ADD_SEALS, SEALS) }, 0);
81 }
82 f
83 }
84 fn fixture() -> (Vec<u8>, Vec<u8>) {
85 let mut r =
86 super::super::super::wire::parse_request(include_bytes!("../wire/status-request.json"))
87 .unwrap();
88 r.binding.engine_binding.principal_uid = unsafe { libc::geteuid() };
89 let c = Config {
90 schema: "gate-runtime/lifecycle-config-v1".into(),
91 engine_binding: r.binding.engine_binding.clone(),
92 };
93 let config = canonical::encode(&serde_json::to_value(c).unwrap()).unwrap();
94 r.binding.config_sha256 = format!("{:x}", Sha256::digest(&config));
95 r.binding.operation_key = r.operation_key().unwrap();
96 (config, r.canonical_bytes().unwrap())
97 }
98 #[test]
99 fn exact_sealed_inputs_ignore_shared_cursor_and_reject_substitution() {
100 let (c, r) = fixture();
101 let config = memory(&c, true, 0o600);
102 let expected = memory(&r, true, 0o600);
103 assert!(parse_protected_inputs(&config, &expected, &r).is_ok());
104 assert!(parse_protected_inputs(&config, &expected, &r).is_ok());
105 assert!(parse_protected_inputs(&config, &expected, b"{}").is_err());
106 let changed = memory(b"{}", true, 0o600);
107 assert!(parse_protected_inputs(&changed, &expected, &r).is_err());
108 }
109 #[test]
110 fn unsealed_public_empty_and_oversized_inputs_rejected() {
111 let (c, r) = fixture();
112 let config = memory(&c, true, 0o600);
113 for expected in [
114 memory(&r, false, 0o600),
115 memory(&r, true, 0o644),
116 memory(b"", true, 0o600),
117 memory(&vec![b'x'; 65537], true, 0o600),
118 ] {
119 assert!(parse_protected_inputs(&config, &expected, &r).is_err());
120 }
121 let regular = tempfile::tempfile().unwrap();
122 assert!(read(®ular).is_err());
123 }
124}