Skip to main content

mediatek_optical/receiver/entry/
application.rs

1//! Application command-table evidence, separated from BOOT decoding.
2//!
3//! A command table is relevant only when its mode-1 target reaches the unique
4//! update-flag clearing site. READ BUFFER tables cannot supply an entry recipe.
5//! This is one proof component, not authorization to write: the entry guard,
6//! complete flag-copy semantics, opcode dispatch and completion still matter.
7use super::Window;
8
9mod prologue;
10pub use prologue::{inspect_prologue, Prologue};
11mod flags;
12pub use flags::{inspect_flags, FlagCopy};
13mod mode_six;
14pub use mode_six::{inspect_mode_six, ModeSix};
15mod registration;
16pub use registration::{inspect_registration, Registration};
17
18/// A command table bound to an update-flag clearing block.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct Candidate {
21    /// Thumb switch sequence or compiler-helper call.
22    pub table: u32,
23    /// The literal load of MMIO 0x04002040 preceding the flag clear.
24    pub entry: u32,
25    /// Mode 1 target before an optional unconditional branch.
26    pub mode1_target: u32,
27    /// Mode 6 target. Its buffer-id guard is a separate proof obligation.
28    pub mode6_target: u32,
29    /// ARM compiler helper requiring an independent read and signature check.
30    pub helper: Option<u32>,
31}
32
33const THUMB_TABLE: &[u8] = b"\x5b\x00\x7b\x44\x9b\x88\x5b\x00\x9f\x44";
34const ARM_HELPER: &[u8] = b"\x01\xc0\x5e\xe5\x0c\x00\x53\xe1\x03\x30\xde\x37\x0c\x30\xde\x27\x83\xc0\x8e\xe0\x1c\xff\x2f\xe1";
35
36/// Verify the complete compiler helper referenced by a candidate. A partial
37/// read or different helper never licenses interpreting its inline table.
38pub fn matches_helper(bytes: &[u8]) -> bool {
39    bytes.get(..ARM_HELPER.len()) == Some(ARM_HELPER)
40}
41
42/// Required bounded read length at [`Candidate::helper`].
43pub const HELPER_BYTES: usize = ARM_HELPER.len();
44
45/// Complete size of the supported mode/buffer exclusion helper.
46pub const MODE_GUARD_BYTES: usize = 40;
47const MODE_GUARD: &[u8; MODE_GUARD_BYTES] = b"\x01\x22\x02\x28\x04\xd0\x05\x28\x05\xd0\x0a\x28\x0a\xd1\x08\xe0\x8f\x29\x07\xd1\x05\xe0\x04\x29\x03\xd0\x05\x29\x01\xd0\x06\x29\x00\xd1\x00\x22\x10\x00\x70\x47";
48
49/// Evidence for the complete mode/buffer exclusion helper. This establishes
50/// the helper's return value only; the caller must separately prove argument
51/// setup and how it uses that return value.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct ModeBufferGuard(());
54
55impl ModeBufferGuard {
56    /// Match every instruction, including conditional branches and return.
57    /// Unknown or truncated helpers never acquire an inferred behavior.
58    pub fn inspect(bytes: &[u8]) -> Option<Self> {
59        (bytes.get(..MODE_GUARD_BYTES) == Some(MODE_GUARD.as_slice())).then_some(Self(()))
60    }
61
62    /// Whether the matched helper returns one for its mode and buffer inputs.
63    /// A one is not itself authorization: interpretation belongs to the caller.
64    pub fn returns_one(self, mode: u8, buffer: u8) -> bool {
65        match mode {
66            2 => buffer != 0x8f,
67            5 => !(4..=6).contains(&buffer),
68            10 => false,
69            _ => true,
70        }
71    }
72}
73
74fn clear_site(w: &Window<'_>, address: u32) -> Option<u32> {
75    let load = w.word(address)?;
76    if load & 0xffc0 != 0x6980 {
77        return None;
78    }
79    let tmp = load & 7;
80    let base = (load >> 3) & 7;
81    if tmp == base {
82        return None;
83    }
84    if w.word(address.checked_add(2)?)? != 0x0900 | (tmp << 3) | tmp
85        || w.word(address.checked_add(4)?)? != 0x0100 | (tmp << 3) | tmp
86        || w.word(address.checked_add(6)?)? != 0x6180 | (base << 3) | tmp
87    {
88        return None;
89    }
90    let entry = address.checked_sub(2)?;
91    if w.literal(entry)? != (base, 0x0400_2040) {
92        return None;
93    }
94    Some(entry)
95}
96
97fn bind(
98    w: &Window<'_>,
99    entry: u32,
100    table: u32,
101    mode1: u32,
102    mode6: u32,
103    helper: Option<u32>,
104) -> Option<Candidate> {
105    if mode1 != entry && w.branch(mode1)? != entry {
106        return None;
107    }
108    // Both targets must be instructions in the supplied code window.
109    w.word(mode6)?;
110    if mode1 & 1 != 0 || mode6 & 1 != 0 {
111        return None;
112    }
113    Some(Candidate {
114        table,
115        entry,
116        mode1_target: mode1,
117        mode6_target: mode6,
118        helper,
119    })
120}
121
122fn thumb(w: &Window<'_>, at: u32, entry: u32) -> Option<Candidate> {
123    if w.bytes(at, THUMB_TABLE.len())? != THUMB_TABLE {
124        return None;
125    }
126    let compare = w.word(at.checked_sub(6)?)?;
127    if compare & 0xff00 != 0x2b00
128        || !(7..=32).contains(&(compare & 255))
129        || w.conditional(at.checked_sub(4)?, 0xd300)? != at
130    {
131        return None;
132    }
133    w.branch(at.checked_sub(2)?)?;
134    let target = |mode: u32| -> Option<u32> {
135        let offset = w.word(at.checked_add(10 + mode * 2)?)?;
136        at.checked_add(12)?.checked_add(u32::from(offset) * 2)
137    };
138    bind(w, entry, at, target(1)?, target(6)?, None)
139}
140
141fn helper(w: &Window<'_>, at: u32, entry: u32) -> Option<Candidate> {
142    let first = w.word(at)?;
143    let second = w.word(at.checked_add(2)?)?;
144    if first & 0xf800 != 0xf000 || second & 0xf800 != 0xe800 {
145        return None;
146    }
147    let encoded = (u32::from(first & 2047) << 12) | (u32::from(second & 2047) << 1);
148    let delta = ((encoded as i32) << 9) >> 9;
149    let helper = u32::try_from(i64::from(at) + 4 + i64::from(delta)).ok()? & !3;
150    if !(0x10000..0x1c0000).contains(&helper) {
151        return None;
152    }
153    let table = at.checked_add(4)?;
154    let maximum = *w.bytes(table, 1)?.first()?;
155    if !(7..32).contains(&maximum) {
156        return None;
157    }
158    w.bytes(table, usize::from(maximum) + 2)?;
159    let target = |mode: u32| -> Option<u32> {
160        let offset = *w.bytes(table.checked_add(1 + mode)?, 1)?.first()?;
161        table.checked_add(u32::from(offset) * 2)
162    };
163    bind(w, entry, at, target(1)?, target(6)?, Some(helper))
164}
165
166/// Locate one relevant command table and one flag-clearing block in a bounded
167/// application window. Unknown, truncated and ambiguous windows return `None`.
168/// A helper candidate remains provisional until [`matches_helper`] succeeds.
169/// This never performs I/O or selects transfer encoding or error policy.
170pub fn inspect(bytes: &[u8], address: u32) -> Option<Candidate> {
171    let w = Window {
172        bytes,
173        base: address,
174    };
175    let mut entry = None;
176    for offset in (0..bytes.len()).step_by(2) {
177        let at = address.checked_add(u32::try_from(offset).ok()?)?;
178        if at & 1 != 0 {
179            continue;
180        }
181        if let Some(found) = clear_site(&w, at) {
182            if entry.is_some() {
183                return None;
184            }
185            entry = Some(found);
186        }
187    }
188    let entry = entry?;
189    let mut found = None;
190    for offset in (0..bytes.len()).step_by(2) {
191        let at = address.checked_add(u32::try_from(offset).ok()?)?;
192        if at & 1 != 0 {
193            continue;
194        }
195        if let Some(candidate) = thumb(&w, at, entry).or_else(|| helper(&w, at, entry)) {
196            if found.is_some() {
197                return None;
198            }
199            found = Some(candidate);
200        }
201    }
202    found
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn complete_guard_signature_preserves_mode_and_buffer_exclusions() {
211        let guard = ModeBufferGuard::inspect(MODE_GUARD).unwrap();
212        assert!(guard.returns_one(1, 0));
213        assert!(guard.returns_one(6, 0x67));
214        for buffer in 0..=255 {
215            assert!(!guard.returns_one(10, buffer));
216            assert_eq!(guard.returns_one(2, buffer), buffer != 0x8f);
217        }
218        for buffer in [4, 5, 6] {
219            assert!(!guard.returns_one(5, buffer));
220        }
221        assert!(guard.returns_one(5, 3));
222        assert!(guard.returns_one(5, 7));
223        for i in 0..MODE_GUARD_BYTES {
224            let mut changed = *MODE_GUARD;
225            changed[i] ^= 1;
226            assert!(ModeBufferGuard::inspect(&changed).is_none());
227            assert!(ModeBufferGuard::inspect(&MODE_GUARD[..i]).is_none());
228        }
229    }
230
231    // Real instruction excerpts, independently disassembled from V1 and INTM.
232    fn fixture(base: usize, length: usize, fragments: &[(usize, &str)]) -> std::vec::Vec<u8> {
233        let mut bytes = std::vec![0xff; length];
234        for &(address, hex) in fragments {
235            for i in (0..hex.len()).step_by(2) {
236                bytes[address - base + i / 2] = u8::from_str_radix(&hex[i..i + 2], 16).unwrap();
237            }
238        }
239        bytes
240    }
241
242    fn v1() -> std::vec::Vec<u8> {
243        fixture(0x9ca00, 0x800, &[
244            (0x9ca2a, "092b00d3f9e35b007b449b885b009f44ac0508002900ac052f001401ee02ac057104574a93691b091b019361"),
245            (0x9cbac, "40200004"),
246            (0x9d018, "8178"),
247        ])
248    }
249
250    #[test]
251    fn real_inline_table_binds_mode_one_to_mmio_clear() {
252        let bytes = v1();
253        assert_eq!(
254            inspect(&bytes, 0x9ca00),
255            Some(Candidate {
256                table: 0x9ca30,
257                entry: 0x9ca4c,
258                mode1_target: 0x9ca4c,
259                mode6_target: 0x9d018,
260                helper: None,
261            })
262        );
263        for offset in [0x2b, 0x2c, 0x30, 0x3c, 0x50, 0x1ac] {
264            let mut corrupt = bytes.clone();
265            corrupt[offset] ^= 1;
266            assert_eq!(inspect(&corrupt, 0x9ca00), None, "offset {offset:x}");
267        }
268        assert_eq!(inspect(&bytes[..0x100], 0x9ca00), None);
269        assert_eq!(inspect(&bytes, u32::MAX - 16), None);
270        assert_eq!(inspect(&bytes, 0x9ca01), None);
271    }
272
273    #[test]
274    fn real_compiler_table_requires_separate_helper_proof() {
275        let bytes = fixture(
276            0x9cd00,
277            0x400,
278            &[
279                (
280                    0x9cd90,
281                    "724c734e00219ff00ce809630627632df5f463f363006f498b691b091b018b61",
282                ),
283                (0x9cf64, "40200004"),
284                (0x9cf82, "e3e1"),
285            ],
286        );
287        assert_eq!(
288            inspect(&bytes, 0x9cd00),
289            Some(Candidate {
290                table: 0x9cd96,
291                entry: 0x9cda6,
292                mode1_target: 0x9cda6,
293                mode6_target: 0x9cf82,
294                helper: Some(0x13bdb0),
295            })
296        );
297        assert!(matches_helper(ARM_HELPER));
298        assert!(!matches_helper(&ARM_HELPER[..HELPER_BYTES - 1]));
299        for offset in 0..HELPER_BYTES {
300            let mut corrupt = ARM_HELPER.to_vec();
301            corrupt[offset] ^= 1;
302            assert!(!matches_helper(&corrupt));
303        }
304    }
305}