Skip to main content

detcore/
in_guest_launch.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! What Hermit tells the in-guest LiteInst runtime at launch, carried in the
10//! sealed preload bootstrap's tool data (`reverie_inguest::bootstrap`) rather
11//! than in the guest's environment.
12//!
13//! The kernel copies a launch environment onto the guest's initial stack, so a
14//! value there shifts every stack address and stays readable in
15//! `/proc/self/environ` even after the runtime removes it. The bootstrap is a
16//! sealed memfd the runtime reads and closes before the guest's code runs.
17
18/// Identifies this record, and its version, at the start of the tool data.
19const MAGIC: &[u8] = b"HERMIT-LITEINST-LAUNCH-V1\0";
20
21const FORWARD: &str = "forward";
22const POLICY: &str = "policy";
23const FINGERPRINT: &str = "fingerprint";
24
25/// Hermit's launch settings for one in-guest LiteInst run.
26#[derive(Clone, Debug, Default, PartialEq, Eq)]
27pub struct InGuestLaunch {
28    /// The DETLOG forwarding socket, as
29    /// [`crate::detlog::tool_output_env_value`] writes it.
30    pub forward_request: Option<String>,
31    /// The forwarding policy, [`crate::detlog::ForwardPolicy::encode`].
32    pub forward_policy: Option<String>,
33    /// This build's [`crate::config_wire_fingerprint`].
34    pub coordinator_fingerprint: Option<String>,
35}
36
37impl InGuestLaunch {
38    /// The tool-data bytes: the magic, then `name=value` entries, each ended
39    /// by a NUL byte. Values never contain NUL.
40    pub fn encode(&self) -> Vec<u8> {
41        let mut bytes = MAGIC.to_vec();
42        for (name, value) in [
43            (FORWARD, &self.forward_request),
44            (POLICY, &self.forward_policy),
45            (FINGERPRINT, &self.coordinator_fingerprint),
46        ] {
47            if let Some(value) = value {
48                assert!(!value.contains('\0'), "{name} contains a NUL byte");
49                bytes.extend_from_slice(name.as_bytes());
50                bytes.push(b'=');
51                bytes.extend_from_slice(value.as_bytes());
52                bytes.push(0);
53            }
54        }
55        bytes
56    }
57
58    /// Reads [`Self::encode`]'s bytes. Refuses another record, an unknown or
59    /// repeated name, and an unterminated entry: a runtime that cannot read
60    /// what Hermit sent must say so rather than run with less.
61    pub fn decode(bytes: &[u8]) -> Result<Self, String> {
62        let entries = bytes
63            .strip_prefix(MAGIC)
64            .ok_or("the tool data is not a Hermit LiteInst launch record")?;
65        let mut launch = Self::default();
66        let mut rest = entries;
67        while !rest.is_empty() {
68            let end = rest
69                .iter()
70                .position(|byte| *byte == 0)
71                .ok_or("a launch setting is not NUL-terminated")?;
72            let entry = std::str::from_utf8(&rest[..end])
73                .map_err(|_| "a launch setting is not UTF-8".to_owned())?;
74            rest = &rest[end + 1..];
75            let (name, value) = entry
76                .split_once('=')
77                .ok_or_else(|| format!("launch setting {entry:?} is not name=value"))?;
78            let slot = match name {
79                FORWARD => &mut launch.forward_request,
80                POLICY => &mut launch.forward_policy,
81                FINGERPRINT => &mut launch.coordinator_fingerprint,
82                _ => return Err(format!("unknown launch setting {name:?}")),
83            };
84            if slot.replace(value.to_owned()).is_some() {
85                return Err(format!("launch setting {name:?} is repeated"));
86            }
87        }
88        Ok(launch)
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn a_launch_record_round_trips() {
98        for launch in [
99            InGuestLaunch::default(),
100            InGuestLaunch {
101                forward_request: Some("7:12345:67890".to_owned()),
102                forward_policy: Some("info,detcore::random=warn".to_owned()),
103                coordinator_fingerprint: Some("9349c85b2c48fc92".to_owned()),
104            },
105            InGuestLaunch {
106                coordinator_fingerprint: Some("0123456789abcdef".to_owned()),
107                ..Default::default()
108            },
109        ] {
110            assert_eq!(InGuestLaunch::decode(&launch.encode()), Ok(launch));
111        }
112    }
113
114    #[test]
115    fn a_launch_record_refuses_what_it_cannot_read() {
116        let mut repeated = MAGIC.to_vec();
117        repeated.extend_from_slice(b"policy=a\0policy=b\0");
118        let mut unknown = MAGIC.to_vec();
119        unknown.extend_from_slice(b"colour=blue\0");
120        let mut unterminated = MAGIC.to_vec();
121        unterminated.extend_from_slice(b"policy=a");
122        for bytes in [&b"REVERIE-LI-V1"[..], &repeated, &unknown, &unterminated] {
123            assert!(InGuestLaunch::decode(bytes).is_err(), "{bytes:?}");
124        }
125    }
126}