1mod acceptance;
8pub use acceptance::{inspect_acceptance_tail, AcceptanceTail};
9
10mod reserved;
11pub use reserved::{inspect_reserved_skips, ReservedSkips};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct Selection {
16 pub address: u32,
18 pub end: u32,
20 pub offset_cell: u32,
22 pub start_cell: u32,
24 pub length_cell: u32,
26 pub total_pointer_register: u8,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct IntegritySelection {
34 pub address: u32,
36 pub boot_check: u32,
38 pub application_check: u32,
40 pub success: u32,
42 pub failure: u32,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct PlainSignature {
52 pub staging_cell: u32,
54 pub offset: u32,
56 pub length: u32,
58 pub success: u32,
60 pub mismatch: u32,
62 pub outside_range: u32,
64}
65
66pub fn inspect_plain_signature(
69 bytes: &[u8],
70 base: u32,
71 selection: &Selection,
72) -> Option<PlainSignature> {
73 if inspect_selection(bytes, base, selection.address)? != *selection {
74 return None;
75 }
76 let start = selection.end;
77 let at = |index: u32| start.checked_add(index.checked_mul(2)?);
78 let op = |index| word(bytes, base, at(index)?);
79 for (index, expected) in [
80 (0, 0x2201),
81 (1, 0x0692),
82 (2, 0x6810),
83 (3, 0x2110),
84 (4, 0x4388),
85 (5, 0x6010),
86 (7, 0x6820),
87 (8, 0x2283),
88 (9, 0x0252),
89 (10, 0x4290),
90 (12, 0x6820),
91 (14, 0x1840),
92 (16, 0x4288),
93 (19, 0x2100),
94 (20, 0x6800),
95 (21, 0x0213),
96 (23, 0x0a1b),
97 (24, 0x181b),
98 (25, 0x781b),
99 (29, 0x1c49),
100 (30, 0x0609),
101 (31, 0x0e09),
102 (32, 0x1c52),
103 (33, 0x2908),
104 (34, 0xd3f1),
105 ] {
106 if op(index)? != expected {
107 return None;
108 }
109 }
110 if literal(bytes, base, at(6)?, 4)? != selection.offset_cell
111 || literal(bytes, base, at(15)?, 1)? != 0x10700
112 {
113 return None;
114 }
115 let last_load = word(bytes, base, selection.end.checked_sub(4)?)?;
118 let length_register = (last_load >> 8) & 7;
119 if op(13)? != 0x6801 | length_register << 3 {
120 return None;
121 }
122 let signature_register = (op(22)? >> 8) & 7;
123 if signature_register < 4
124 || literal(bytes, base, at(22)?, signature_register)? != 0x10600
125 || op(26)? != 0x5c40 | signature_register << 3 | signature_register
126 || op(27)? != 0x4283 | signature_register << 3
127 {
128 return None;
129 }
130 let branch = |index, condition| {
131 let instruction = op(index)?;
132 if instruction & 0xff00 != condition {
133 return None;
134 }
135 let target =
136 u32::try_from(i64::from(at(index)?) + 4 + i64::from(instruction as u8 as i8) * 2)
137 .ok()?;
138 word(bytes, base, target)?;
139 Some(target)
140 };
141 let outside_range = branch(11, 0xd800)?;
142 if branch(17, 0xd900)? != outside_range {
143 return None;
144 }
145 let mismatch = branch(28, 0xd100)?;
146 let success = at(35)?;
147 if outside_range < success || mismatch < success {
148 return None;
149 }
150 let staging_cell = literal(bytes, base, at(18)?, 0)?;
151 if !ram(staging_cell)
152 || [
153 selection.offset_cell,
154 selection.start_cell,
155 selection.length_cell,
156 ]
157 .contains(&staging_cell)
158 {
159 return None;
160 }
161 Some(PlainSignature {
162 staging_cell,
163 offset: 0x10600,
164 length: 8,
165 success,
166 mismatch,
167 outside_range,
168 })
169}
170
171fn word(bytes: &[u8], base: u32, address: u32) -> Option<u16> {
172 let offset = usize::try_from(address.checked_sub(base)?).ok()?;
173 Some(u16::from_le_bytes(
174 bytes.get(offset..offset.checked_add(2)?)?.try_into().ok()?,
175 ))
176}
177
178fn literal(bytes: &[u8], base: u32, address: u32, register: u16) -> Option<u32> {
179 let op = word(bytes, base, address)?;
180 if op & 0xff00 != 0x4800 | register << 8 {
181 return None;
182 }
183 let at = (address.checked_add(4)? & !3).checked_add(u32::from(op & 255) * 4)?;
184 Some(
185 u32::from(word(bytes, base, at)?) | u32::from(word(bytes, base, at.checked_add(2)?)?) << 16,
186 )
187}
188
189fn ram(address: u32) -> bool {
190 address & 3 == 0 && (0x02000000..0x02010000).contains(&address)
191}
192
193pub fn inspect_selection(bytes: &[u8], base: u32, start: u32) -> Option<Selection> {
195 match (
196 inspect_legacy_selection(bytes, base, start),
197 inspect_shared_selection(bytes, base, start),
198 ) {
199 (Some(proof), None) | (None, Some(proof)) => Some(proof),
200 _ => None,
201 }
202}
203
204fn inspect_shared_selection(bytes: &[u8], base: u32, start: u32) -> Option<Selection> {
205 if start & 1 != 0 || base & 1 != 0 {
206 return None;
207 }
208 let branch_target = |address: u32, conditional: bool| {
211 let instruction = word(bytes, base, address)?;
212 let delta = if conditional {
213 if instruction & 0xff00 != 0xd000 {
214 return None;
215 }
216 i64::from(instruction as u8 as i8) * 2
217 } else {
218 if instruction & 0xf800 != 0xe000 {
219 return None;
220 }
221 i64::from((i32::from(instruction & 0x7ff) << 21) >> 20)
222 };
223 u32::try_from(i64::from(address) + 4 + delta).ok()
224 };
225 let alternate = branch_target(start.checked_add(8)?, true)?;
226 let join = branch_target(start.checked_add(20)?, false)?;
227 if alternate < start.checked_add(22)? || join != alternate.checked_add(14)? {
228 return None;
229 }
230 let at = |index: u32| {
231 if index < 11 {
232 start.checked_add(index.checked_mul(2)?)
233 } else {
234 alternate.checked_add(index.checked_sub(11)?.checked_mul(2)?)
235 }
236 };
237 let op = |index| word(bytes, base, at(index)?);
238 if literal(bytes, base, start, 0)? != 0x04002040 {
239 return None;
240 }
241 for (index, expected) in [
242 (1, 0x6980),
243 (2, 0x07c1),
244 (6, 0x2100),
245 (7, 0x6011),
246 (8, 0x6001),
247 (12, 0x2101),
248 (13, 0x0409),
249 (14, 0x6011),
250 (15, 0x6001),
251 (17, 0x1a51),
252 ] {
253 if op(index)? != expected {
254 return None;
255 }
256 }
257 let start_cell = literal(bytes, base, at(3)?, 0)?;
258 let offset_cell = literal(bytes, base, at(5)?, 2)?;
259 if literal(bytes, base, at(11)?, 2)? != offset_cell {
260 return None;
261 }
262 let total = op(9)?;
263 if total & 0xffc7 != 0x6801 || op(16)? != (total & !7) | 2 {
264 return None;
265 }
266 let pointer = ((total >> 3) & 7) as u8;
267 if pointer < 3 {
268 return None;
269 }
270 let length_register = (op(18)? >> 8) & 7;
271 let length_cell = literal(bytes, base, at(18)?, length_register)?;
272 if length_register == 1 || op(19)? != 0x6001 | length_register << 3 {
273 return None;
274 }
275 if ![offset_cell, start_cell, length_cell].into_iter().all(ram)
276 || offset_cell == start_cell
277 || offset_cell == length_cell
278 || start_cell == length_cell
279 {
280 return None;
281 }
282 Some(Selection {
283 address: start,
284 end: at(20)?,
285 offset_cell,
286 start_cell,
287 length_cell,
288 total_pointer_register: pointer,
289 })
290}
291
292pub fn inspect_legacy_integrity(bytes: &[u8], base: u32, start: u32) -> Option<IntegritySelection> {
295 use super::decoding::branch_link;
296 if base & 1 != 0 || start & 1 != 0 || literal(bytes, base, start, 0)? != 0x04002040 {
297 return None;
298 }
299 let at = |delta: u32| start.checked_add(delta);
300 for (offset, expected) in [
301 (2, 0x6980),
302 (4, 0x07c0),
303 (6, 0xd003),
304 (12, 0x2801),
305 (14, 0xd102),
306 (16, 0x2001),
307 (22, 0x2800),
308 ] {
309 if word(bytes, base, at(offset)?)? != expected {
310 return None;
311 }
312 }
313 let branch = word(bytes, base, at(24)?)?;
314 if branch & 0xff00 != 0xd100 {
315 return None;
316 }
317 let success = u32::try_from(i64::from(at(24)?) + 4 + i64::from(branch as u8 as i8) * 2).ok()?;
318 let failure = at(26)?;
319 word(bytes, base, success)?;
320 word(bytes, base, failure)?;
321 Some(IntegritySelection {
322 address: start,
323 boot_check: branch_link(bytes, base, at(8)?)?,
324 application_check: branch_link(bytes, base, at(18)?)?,
325 success,
326 failure,
327 })
328}
329
330pub fn inspect_legacy_selection(bytes: &[u8], base: u32, start: u32) -> Option<Selection> {
334 if start & 1 != 0 || base & 1 != 0 {
335 return None;
336 }
337 let at = |index: u32| start.checked_add(index.checked_mul(2)?);
338 let op = |index| word(bytes, base, at(index)?);
339 if literal(bytes, base, start, 0)? != 0x04002040 {
340 return None;
341 }
342 for (index, expected) in [
344 (1, 0x6980),
345 (2, 0x07c0),
346 (3, 0xd006),
347 (5, 0x2000),
348 (6, 0x6008),
349 (8, 0x6008),
350 (10, 0xe007),
351 (12, 0x2101),
352 (13, 0x0409),
353 (14, 0x6001),
354 (16, 0x6001),
355 (18, 0x1a40),
356 ] {
357 if op(index)? != expected {
358 return None;
359 }
360 }
361 let offset_cell = literal(bytes, base, at(4)?, 1)?;
362 let start_cell = literal(bytes, base, at(7)?, 1)?;
363 if literal(bytes, base, at(11)?, 0)? != offset_cell
364 || literal(bytes, base, at(15)?, 0)? != start_cell
365 {
366 return None;
367 }
368 let total = op(9)?;
369 if total & 0xffc7 != 0x6800 || op(17)? != total {
370 return None;
371 }
372 let pointer = ((total >> 3) & 7) as u8;
373 if pointer < 2 {
375 return None;
376 }
377 let load = op(19)?;
378 let length_register = (load >> 8) & 7;
379 let length_cell = literal(bytes, base, at(19)?, length_register)?;
380 if length_register == 0 || op(20)? != 0x6000 | length_register << 3 {
381 return None;
382 }
383 if ![offset_cell, start_cell, length_cell].into_iter().all(ram)
384 || offset_cell == start_cell
385 || offset_cell == length_cell
386 || start_cell == length_cell
387 {
388 return None;
389 }
390 Some(Selection {
391 address: start,
392 end: at(21)?,
393 offset_cell,
394 start_cell,
395 length_cell,
396 total_pointer_register: pointer,
397 })
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 fn fixture() -> Vec<u8> {
405 let words = [
406 0x48b5u16, 0x6980, 0x07c0, 0xd006, 0x49a3, 0x2000, 0x6008, 0x49b6, 0x6008, 0x6820,
407 0xe007, 0x489f, 0x2101, 0x0409, 0x6001, 0x48b2, 0x6001, 0x6820, 0x1a40, 0x4ea1, 0x6030,
408 ];
409 let mut bytes = vec![0; 0x300];
410 for (i, op) in words.into_iter().enumerate() {
411 bytes[2 * i..2 * i + 2].copy_from_slice(&op.to_le_bytes());
412 }
413 for (address, value) in [
414 (0x9d7f8u32, 0x04002040u32),
415 (0x9d7b8, 0x02000c60),
416 (0x9d80c, 0x02000c68),
417 (0x9d7d0, 0x02000c6c),
418 ] {
419 let at = (address - 0x9d522) as usize;
420 bytes[at..at + 4].copy_from_slice(&value.to_le_bytes());
421 }
422 bytes
423 }
424
425 fn shared_fixture(island: usize) -> Vec<u8> {
428 let mut bytes = vec![0; 0x300];
429 let alternate = 22 + island;
430 let join = alternate + 14;
431 let mut put = |at: usize, value: u16| {
432 bytes[at..at + 2].copy_from_slice(&value.to_le_bytes());
433 };
434 for (at, op) in [
435 (2, 0x6980),
436 (4, 0x07c1),
437 (12, 0x2100),
438 (14, 0x6011),
439 (16, 0x6001),
440 (18, 0x6821),
441 ] {
442 put(at, op);
443 }
444 put(8, 0xd000 | ((alternate - 12) / 2) as u16);
445 put(20, 0xe000 | ((join - 24) / 2) as u16);
446 for (offset, op) in [
447 (2, 0x2101),
448 (4, 0x0409),
449 (6, 0x6011),
450 (8, 0x6001),
451 (10, 0x6822),
452 (12, 0x1a51),
453 (16, 0x6031),
454 ] {
455 put(alternate + offset, op);
456 }
457 for (at, reg, literal_at, value) in [
458 (0, 0, 0x200, 0x04002040u32),
459 (6, 0, 0x204, 0x02000c68),
460 (10, 2, 0x208, 0x02000c60),
461 (alternate, 2, 0x208, 0x02000c60),
462 (join, 6, 0x20c, 0x02000c6c),
463 ] {
464 let op = 0x4800u16 | reg << 8 | ((literal_at - ((at + 4) & !3)) / 4) as u16;
465 bytes[at..at + 2].copy_from_slice(&op.to_le_bytes());
466 bytes[literal_at..literal_at + 4].copy_from_slice(&value.to_le_bytes());
467 }
468 bytes
469 }
470
471 #[test]
472 fn shared_selection_follows_branches_over_literal_islands() {
473 for island in [0, 0x76] {
474 let bytes = shared_fixture(island);
475 let proof = inspect_selection(&bytes, 0, 0).unwrap();
476 assert_eq!(proof.end, (40 + island) as u32);
477 assert_eq!(proof.start_cell, 0x02000c68);
478 assert_eq!(proof.total_pointer_register, 4);
479 for at in [8, 20, 22 + island + 4, 22 + island + 12, 22 + island + 16] {
481 let mut changed = bytes.clone();
482 changed[at] ^= 1;
483 assert!(inspect_selection(&changed, 0, 0).is_none(), "at {at:#x}");
484 }
485 if island != 0 {
487 let mut changed = bytes.clone();
488 changed[22..22 + island].fill(0xff);
489 assert_eq!(inspect_selection(&changed, 0, 0), Some(proof));
490 }
491 }
492 }
493
494 #[test]
495 fn intm_selection_binds_both_arms_and_distinct_cells() {
496 let evidence = inspect_legacy_selection(&fixture(), 0x9d522, 0x9d522).unwrap();
497 assert_eq!(evidence.offset_cell, 0x02000c60);
498 assert_eq!(evidence.start_cell, 0x02000c68);
499 assert_eq!(evidence.length_cell, 0x02000c6c);
500 assert_eq!(evidence.total_pointer_register, 4);
501 assert_eq!(evidence.end, 0x9d54c);
502 }
503
504 #[test]
505 fn changed_guards_stores_subtraction_and_joins_are_rejected() {
506 let bytes = fixture();
507 for index in [1, 2, 3, 5, 6, 8, 10, 12, 13, 14, 16, 17, 18, 20] {
508 let mut changed = bytes.clone();
509 changed[index * 2] ^= 1;
510 assert!(
511 inspect_legacy_selection(&changed, 0x9d522, 0x9d522).is_none(),
512 "instruction {index}"
513 );
514 }
515 assert!(inspect_legacy_selection(&bytes[..42], 0x9d522, 0x9d522).is_none());
516 assert!(inspect_legacy_selection(&bytes, u32::MAX - 1, u32::MAX - 1).is_none());
517 }
518}