1use serde::Deserialize;
9use serde::Serialize;
10
11use crate::fd::FilesId;
12use crate::futex::MmId;
13use crate::pid::DetPid;
14use crate::pid::DetTid;
15
16pub const MAX_BYTES: usize = 1024 * 1024;
18pub const BINDING_BYTES: usize = 80;
20pub const HEADER_BYTES: usize = 18 + BINDING_BYTES;
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
25pub struct CutBinding {
26 pub dettid: DetTid,
28 pub detpid: DetPid,
30 pub physical_tid: i32,
32 pub old_mm: MmId,
34 pub old_files: FilesId,
36 pub prepared_mm: MmId,
38 pub config_digest: [u8; 32],
40}
41
42impl CutBinding {
43 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub enum Error {
65 Bounds,
67 Identity,
69}
70
71pub 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
97pub 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 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}