Skip to main content

detcore_model/
liteinst_native_exec_state.rs

1//! Exact neutral identity envelope around opaque Tool and backend state.
2//!
3//! These are authenticated transport data, never native-exec authority. The
4//! registry must independently join the cut to its real scheduler/watch owner.
5//! Old descriptor-table identity originates in the trusted live guest receipt;
6//! the host does not reconstruct it from kernel descriptors or decode a Tool.
7
8use serde::Deserialize;
9use serde::Serialize;
10
11use crate::fd::FilesId;
12use crate::futex::MmId;
13use crate::pid::DetPid;
14use crate::pid::DetTid;
15
16/// Maximum complete Tool/runtime frame; the bootstrap control stays 4 KiB.
17pub const MAX_BYTES: usize = 1024 * 1024;
18/// Fixed identity bytes, using the existing legacy positional model encoding.
19pub const BINDING_BYTES: usize = 80;
20/// Magic, version, two exact lengths and the neutral cut identity.
21pub const HEADER_BYTES: usize = 18 + BINDING_BYTES;
22
23/// Actual live producer identities retained by the authenticated coordinator.
24#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
25pub struct CutBinding {
26    /// Actual deterministic main-thread identity.
27    pub dettid: DetTid,
28    /// Continuing deterministic process identity.
29    pub detpid: DetPid,
30    /// Actual watched physical leader/TID.
31    pub physical_tid: i32,
32    /// Old memory incarnation from the live pre-preparation receipt.
33    pub old_mm: MmId,
34    /// Old table incarnation from that same actual live table receipt.
35    pub old_files: FilesId,
36    /// Replacement memory incarnation proved by ordinary exec preparation.
37    pub prepared_mm: MmId,
38    /// Independently admitted exact wire Config digest.
39    pub config_digest: [u8; 32],
40}
41
42impl CutBinding {
43    /// Refuse unsupported identities and overflowing model transitions.
44    pub fn validate(self) -> Result<(), Error> {
45        if self.dettid != self.detpid
46            || self.dettid.as_raw() <= 0
47            || self.physical_tid <= 0
48            || self.old_mm.creator() != self.dettid
49            || self.old_files.creator() != self.dettid
50            || self.old_mm.generation().checked_add(1) != Some(self.prepared_mm.generation())
51            || self.prepared_mm.creator() != self.dettid
52            || self.prepared_mm.generation() == 0
53            || self.old_files.generation() == u64::MAX
54            || self.config_digest == [0; 32]
55        {
56            return Err(Error::Identity);
57        }
58        Ok(())
59    }
60}
61
62/// Structural/data error, distinct from physical launch proof or Linux errno.
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub enum Error {
65    /// Unsupported magic/version, lengths, trailing bytes or finite bound.
66    Bounds,
67    /// Unsupported or inconsistent neutral identities.
68    Identity,
69}
70
71/// Encode the exact version-2 header into preallocated storage, with no heap.
72pub fn encode_header(
73    binding: CutBinding,
74    tool_len: usize,
75    runtime_len: usize,
76    out: &mut [u8; HEADER_BYTES],
77) -> Result<(), Error> {
78    binding.validate()?;
79    let length = HEADER_BYTES
80        .checked_add(tool_len)
81        .and_then(|n| n.checked_add(runtime_len));
82    if tool_len == 0 || runtime_len == 0 || length.is_none_or(|n| n > MAX_BYTES) {
83        return Err(Error::Bounds);
84    }
85    out[..8].copy_from_slice(b"HLXSTATE");
86    out[8..10].copy_from_slice(&2u16.to_le_bytes());
87    out[10..14].copy_from_slice(&(tool_len as u32).to_le_bytes());
88    out[14..18].copy_from_slice(&(runtime_len as u32).to_le_bytes());
89    let n = bincode::serde::encode_into_slice(binding, &mut out[18..], bincode::config::legacy())
90        .map_err(|_| Error::Bounds)?;
91    if n != BINDING_BYTES {
92        return Err(Error::Bounds);
93    }
94    Ok(())
95}
96
97/// Split only a bounded, exact frame. Its entire digest must first be checked
98/// against the independently admitted successor ticket's expected state.
99pub fn decode(bytes: &[u8]) -> Result<(CutBinding, &[u8], &[u8]), Error> {
100    if bytes.len() < HEADER_BYTES
101        || bytes.len() > MAX_BYTES
102        || &bytes[..8] != b"HLXSTATE"
103        || bytes[8..10] != 2u16.to_le_bytes()
104    {
105        return Err(Error::Bounds);
106    }
107    let tool_len = u32::from_le_bytes(bytes[10..14].try_into().unwrap()) as usize;
108    let runtime_len = u32::from_le_bytes(bytes[14..18].try_into().unwrap()) as usize;
109    let end = HEADER_BYTES.checked_add(tool_len).ok_or(Error::Bounds)?;
110    if tool_len == 0 || runtime_len == 0 || end.checked_add(runtime_len) != Some(bytes.len()) {
111        return Err(Error::Bounds);
112    }
113    let (binding, used): (CutBinding, usize) =
114        bincode::serde::decode_from_slice(&bytes[18..HEADER_BYTES], bincode::config::legacy())
115            .map_err(|_| Error::Identity)?;
116    if used != BINDING_BYTES {
117        return Err(Error::Bounds);
118    }
119    binding.validate()?;
120    Ok((binding, &bytes[HEADER_BYTES..end], &bytes[end..]))
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    fn binding() -> CutBinding {
128        let owner = DetTid::from_raw(41);
129        let old_mm = MmId::initial(owner).for_exec(owner);
130        CutBinding {
131            dettid: owner,
132            detpid: owner,
133            physical_tid: 73,
134            old_mm,
135            old_files: FilesId::initial(owner).for_exec(owner),
136            prepared_mm: old_mm.for_exec(owner),
137            config_digest: [1; 32],
138        }
139    }
140
141    #[test]
142    fn neutral_state_exact_identities_lengths_and_version() {
143        let original = binding();
144        let mut header = [0; HEADER_BYTES];
145        encode_header(original, 4, 7, &mut header).unwrap();
146        assert_eq!(&header[..18], b"HLXSTATE\x02\0\x04\0\0\0\x07\0\0\0");
147        let mut frame = header.to_vec();
148        frame.extend_from_slice(b"toolruntime");
149        assert_eq!(
150            decode(&frame),
151            Ok((original, b"tool".as_slice(), b"runtime".as_slice()))
152        );
153        for n in 0..frame.len() {
154            assert!(decode(&frame[..n]).is_err(), "truncation {n}");
155        }
156        for offset in [0, 8, 10, 14] {
157            let mut bad = frame.clone();
158            bad[offset] ^= 1;
159            assert!(decode(&bad).is_err(), "header {offset}");
160        }
161        frame.push(0);
162        assert!(decode(&frame).is_err());
163        assert!(encode_header(original, MAX_BYTES, 1, &mut header).is_err());
164        assert!(encode_header(original, 0, 1, &mut header).is_err());
165        assert!(encode_header(original, 1, 0, &mut header).is_err());
166    }
167
168    #[test]
169    fn neutral_state_rejects_invalid_owner_mm_files_and_generation() {
170        let good = binding();
171        let other = DetTid::from_raw(42);
172        let cases = [
173            CutBinding {
174                dettid: other,
175                ..good
176            },
177            CutBinding {
178                detpid: other,
179                ..good
180            },
181            CutBinding {
182                physical_tid: 0,
183                ..good
184            },
185            CutBinding {
186                old_mm: MmId::initial(other),
187                ..good
188            },
189            CutBinding {
190                prepared_mm: good.old_mm,
191                ..good
192            },
193            CutBinding {
194                prepared_mm: MmId::initial(other).for_exec(other),
195                ..good
196            },
197            CutBinding {
198                old_files: FilesId::initial(other),
199                ..good
200            },
201            CutBinding {
202                config_digest: [0; 32],
203                ..good
204            },
205        ];
206        for bad in cases {
207            assert_eq!(bad.validate(), Err(Error::Identity));
208        }
209        // Exact legacy data decode supplies an overflow value, not authority.
210        let mut wire = bincode::serde::encode_to_vec(good, bincode::config::legacy()).unwrap();
211        wire[16..24].copy_from_slice(&u64::MAX.to_le_bytes());
212        let (bad, used): (CutBinding, usize) =
213            bincode::serde::decode_from_slice(&wire, bincode::config::legacy()).unwrap();
214        assert_eq!(used, BINDING_BYTES);
215        assert_eq!(bad.validate(), Err(Error::Identity));
216        let mut wire = bincode::serde::encode_to_vec(good, bincode::config::legacy()).unwrap();
217        wire[28..36].copy_from_slice(&u64::MAX.to_le_bytes());
218        let (bad, used): (CutBinding, usize) =
219            bincode::serde::decode_from_slice(&wire, bincode::config::legacy()).unwrap();
220        assert_eq!(used, BINDING_BYTES);
221        assert_eq!(bad.validate(), Err(Error::Identity));
222    }
223}