Skip to main content

mediatek_optical/receiver/
entry.rs

1//! Bounded static evidence for BOOT PREPARE handling.
2//!
3//! This recognizes a fixed Thumb instruction grammar, including operands,
4//! literal values and branch destinations. A match establishes flag-copy
5//! semantics only: dispatcher reachability, completion and image acceptance
6//! must be established separately before authorizing a transfer plan.
7
8pub mod application;
9
10/// A mode-1 branch into a proven four-bit CDB-to-MMIO flag copy.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct LegacyCandidate {
13    /// Address of the mode-nibble decoder.
14    pub decoder: u32,
15    /// Address selected by the mode-1 branch.
16    pub entry: u32,
17    /// First instruction after the validated return.
18    pub end: u32,
19}
20
21/// BOOT entry supporting mode 1 and mode 6/buffer 0x67 through the same body.
22/// As with [`LegacyCandidate`], this is evidence, not transfer authorization.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct DualCandidate {
25    /// Start of the validated CDB decoder function.
26    pub decoder: u32,
27    /// Shared entry body.
28    pub entry: u32,
29    /// Start of stream parameter handling after the entry body.
30    pub stream: u32,
31}
32
33struct Window<'a> {
34    bytes: &'a [u8],
35    base: u32,
36}
37impl Window<'_> {
38    fn bytes(&self, address: u32, count: usize) -> Option<&[u8]> {
39        let start = usize::try_from(address.checked_sub(self.base)?).ok()?;
40        self.bytes.get(start..start.checked_add(count)?)
41    }
42    fn word(&self, address: u32) -> Option<u16> {
43        Some(u16::from_le_bytes(self.bytes(address, 2)?.try_into().ok()?))
44    }
45    fn literal(&self, address: u32) -> Option<(u16, u32)> {
46        let op = self.word(address)?;
47        if op & 0xf800 != 0x4800 {
48            return None;
49        }
50        let at = (address.checked_add(4)? & !3).checked_add(u32::from(op & 255) * 4)?;
51        Some((
52            (op >> 8) & 7,
53            u32::from_le_bytes(self.bytes(at, 4)?.try_into().ok()?),
54        ))
55    }
56    fn conditional(&self, address: u32, condition: u16) -> Option<u32> {
57        let op = self.word(address)?;
58        if op & 0xff00 != condition {
59            return None;
60        }
61        u32::try_from(i64::from(address) + 4 + i64::from(op as u8 as i8) * 2).ok()
62    }
63    fn branch(&self, address: u32) -> Option<u32> {
64        let op = self.word(address)?;
65        if op & 0xf800 != 0xe000 {
66            return None;
67        }
68        let delta = ((i32::from(op & 0x7ff) << 21) >> 20) as i64;
69        u32::try_from(i64::from(address) + 4 + delta).ok()
70    }
71}
72
73// Verify the complete local prologue, rather than finding useful bytes in an
74// otherwise unconstrained prefix. This also proves r0 is still the CDB pointer
75// and whether r7 really carries the bit-zero constant.
76fn prologue(w: &Window<'_>, start: u32) -> Option<bool> {
77    for distance in [14, 16, 18] {
78        let Some(mut p) = start.checked_sub(distance) else {
79            continue;
80        };
81        let Some((0, cdb)) = w.literal(p) else {
82            continue;
83        };
84        if !(0x0200_0000..0x0201_0000).contains(&cdb) {
85            continue;
86        }
87        p += 2;
88        if w.word(p) != Some(0xb5f0) || w.word(p + 2) != Some(0x7882) {
89            continue;
90        }
91        p += 4;
92        if !matches!(w.word(p), Some(0xb085 | 0xb089)) {
93            continue;
94        }
95        p += 2;
96        let mut one = false;
97        let mut state = false;
98        while p < start.saturating_sub(4) {
99            if w.word(p) == Some(0x2701) && !one {
100                one = true;
101                p += 2;
102            } else if let Some((1, address)) = w.literal(p) {
103                if state || !(0x0200_0000..0x0201_0000).contains(&address) {
104                    break;
105                }
106                state = true;
107                p += 2;
108            } else if state && w.word(p) == Some(0x390a) {
109                p += 2;
110            } else {
111                break;
112            }
113        }
114        if state
115            && p == start.checked_sub(4)?
116            && w.word(p) == Some(0x2a00)
117            && w.conditional(p + 2, 0xd100).is_some()
118        {
119            return Some(one);
120        }
121    }
122    None
123}
124
125fn candidate(w: &Window<'_>, start: u32) -> Option<LegacyCandidate> {
126    // CDB pointer r0; mode decoder reads/masks/writes byte 1 through r2.
127    if w.bytes(start, 10)? != [0x42, 0x78, 0x12, 0x07, 0x12, 0x0f, 0x42, 0x70, 0x42, 0x78] {
128        return None;
129    }
130    let preloaded_one = prologue(w, start)?;
131    let mut p = start.checked_add(10)?;
132    let mut mmio = w.literal(p);
133    if mmio.is_some() {
134        p = p.checked_add(2)?;
135    }
136    if w.word(p)? != 0x2a01 {
137        return None;
138    }
139    let entry = w.conditional(p.checked_add(2)?, 0xd000)?;
140    p = entry;
141    if let Some(local) = w.literal(p) {
142        mmio = Some(local);
143        p = p.checked_add(2)?;
144    }
145    let (base, address) = mmio?;
146    if address != 0x0400_2040 || base == 0 {
147        return None;
148    }
149    let mut tmp = w.word(p)? & 7;
150    if tmp == 0 || tmp == base {
151        return None;
152    }
153    let load = 0x6800 | (6 << 6) | (base << 3) | tmp;
154    let clear = [
155        load,
156        0x0800 | (4 << 6) | (tmp << 3) | tmp,
157        (4 << 6) | (tmp << 3) | tmp,
158        0x6000 | (6 << 6) | (base << 3) | tmp,
159    ];
160    for op in clear {
161        if w.word(p)? != op {
162            return None;
163        }
164        p = p.checked_add(2)?;
165    }
166    for bit in 0..4u16 {
167        if bit == 3 {
168            tmp = 0;
169        }
170        if w.word(p)? != 0x7800 | (9 << 6) | tmp
171            || w.word(p.checked_add(2)?)? != ((31 - bit) << 6) | (tmp << 3) | tmp
172        {
173            return None;
174        }
175        let skip = w.conditional(p.checked_add(4)?, if bit == 0 { 0xd000 } else { 0xd500 })?;
176        p = p.checked_add(6)?;
177        if w.word(p)? != 0x6800 | (6 << 6) | (base << 3) | tmp {
178            return None;
179        }
180        p = p.checked_add(2)?;
181        let op = w.word(p)?;
182        let source = if op & 0xf800 == 0x2000 {
183            if op & 255 != 1 << bit {
184                return None;
185            }
186            p = p.checked_add(2)?;
187            (op >> 8) & 7
188        } else if bit == 0 && preloaded_one && base != 7 {
189            7
190        } else {
191            return None;
192        };
193        if source == tmp || source == base || (source == 0 && bit != 3) {
194            return None;
195        }
196        if w.word(p)? != 0x4300 | (source << 3) | tmp
197            || w.word(p.checked_add(2)?)? != 0x6000 | (6 << 6) | (base << 3) | tmp
198        {
199            return None;
200        }
201        p = p.checked_add(4)?;
202        if p != skip {
203            return None;
204        }
205    }
206    if w.word(p)? & 0xff80 != 0xb000 || w.word(p.checked_add(2)?)? & 0xff00 != 0xbd00 {
207        return None;
208    }
209    Some(LegacyCandidate {
210        decoder: start,
211        entry,
212        end: p.checked_add(4)?,
213    })
214}
215
216/// Inspect a bounded flash-addressed window for a legacy BOOT entry candidate.
217/// The window must include referenced literals and the complete flag-copy body.
218/// Truncated, unknown and ambiguous windows return `None`. This performs no I/O
219/// and grants no permission to issue PREPARE or ignore its result.
220pub fn inspect_boot_legacy(bytes: &[u8], address: u32) -> Option<LegacyCandidate> {
221    let w = Window {
222        bytes,
223        base: address,
224    };
225    let mut found = None;
226    for index in (0..bytes.len().saturating_sub(9)).step_by(2) {
227        let at = address.checked_add(u32::try_from(index).ok()?)?;
228        if at & 1 != 0 {
229            continue;
230        }
231        if let Some(hit) = candidate(&w, at) {
232            if found.is_some() {
233                return None;
234            }
235            found = Some(hit);
236        }
237    }
238    found
239}
240
241// Two compiler forms of the complete flag-copy body, including out-of-line
242// bit-zero/bit-two blocks and their backward branches. Only the first form's
243// PC-relative MMIO literal load varies; it is resolved and validated below.
244const DUAL_LOCAL: &[u8] = b"\x00\x48\x85\x69\x2d\x09\x2d\x01\x85\x61\x4d\x7a\xed\x07\x10\xd1\x4d\x7a\xad\x07\x02\xd5\x85\x69\x2c\x43\x84\x61\x4c\x7a\x64\x07\x0c\xd4\x49\x7a\x09\x07\x02\xd5\x81\x69\x11\x43\x81\x61\x05\xb0\xf0\xbd\x85\x69\x01\x26\x35\x43\x85\x61\xe9\xe7\x84\x69\x23\x43\x83\x61\xee\xe7";
245const DUAL_PRELOADED: &[u8] = b"\xb0\x69\x00\x09\x00\x01\xb0\x61\x48\x7a\xc0\x07\x10\xd1\x48\x7a\x80\x07\x02\xd5\xb0\x69\x20\x43\xb0\x61\x48\x7a\x40\x07\x0b\xd4\x48\x7a\x00\x07\x02\xd5\xb0\x69\x10\x43\xb0\x61\x09\xb0\xf0\xbd\xb0\x69\x38\x43\xb0\x61\xea\xe7\xb0\x69\x18\x43\xb0\x61\xef\xe7";
246
247fn dual_candidate(w: &Window<'_>, start: u32) -> Option<DualCandidate> {
248    let (register, cdb) = w.literal(start)?;
249    if register != 1 || !(0x0200_0000..0x0201_0000).contains(&cdb) {
250        return None;
251    }
252    let mut p = start.checked_add(2)?;
253    for op in [0xb5f0, 0x7848] {
254        if w.word(p)? != op {
255            return None;
256        }
257        p = p.checked_add(2)?;
258    }
259    let stack = w.word(p)?;
260    if !matches!(stack, 0xb085 | 0xb089) {
261        return None;
262    }
263    p = p.checked_add(2)?;
264    for op in [0x0700, 0x0f00, 0x7048, 0x7848, 0x2208, 0x2304, 0x2402] {
265        if w.word(p)? != op {
266            return None;
267        }
268        p = p.checked_add(2)?;
269    }
270    let preloaded = stack == 0xb089;
271    if preloaded {
272        if w.word(p)? != 0x2701 || w.literal(p.checked_add(2)?)? != (6, 0x0400_2040) {
273            return None;
274        }
275        p = p.checked_add(4)?;
276    }
277    if w.word(p)? != 0x2801 {
278        return None;
279    }
280    let entry = w.conditional(p.checked_add(2)?, 0xd000)?;
281    p = p.checked_add(4)?;
282    let (register, state) = w.literal(p)?;
283    if register != 5 || !(0x0200_0000..0x0201_0000).contains(&state) {
284        return None;
285    }
286    p = p.checked_add(2)?;
287    if w.word(p)? != 0x3d0a || w.word(p.checked_add(2)?)? != 0x2802 {
288        return None;
289    }
290    p = p.checked_add(4)?;
291    if w.conditional(p, 0xd000)? != p.checked_add(6)? {
292        return None;
293    }
294    if w.word(p.checked_add(2)?)? != 0x2806 {
295        return None;
296    }
297    let reject = w.conditional(p.checked_add(4)?, 0xd100)?;
298    p = p.checked_add(6)?;
299    if w.word(p)? != 0x7848 || w.word(p.checked_add(2)?)? != 0x2806 {
300        return None;
301    }
302    let stream = w.conditional(p.checked_add(4)?, 0xd100)?;
303    p = p.checked_add(6)?;
304    if w.word(p)? != 0x7888
305        || w.word(p.checked_add(2)?)? != 0x2867
306        || w.conditional(p.checked_add(4)?, 0xd100)? != stream
307    {
308        return None;
309    }
310    p = p.checked_add(6)?;
311    if p != entry {
312        return None;
313    }
314    let template = if preloaded {
315        DUAL_PRELOADED
316    } else {
317        DUAL_LOCAL
318    };
319    if !preloaded && w.literal(entry)? != (0, 0x0400_2040) {
320        return None;
321    }
322    let body = w.bytes(entry, template.len())?;
323    let skip = if preloaded { 0 } else { 1 };
324    if body[skip..] != template[skip..]
325        || entry.checked_add(u32::try_from(template.len()).ok()?)? != stream
326    {
327        return None;
328    }
329    // The unsupported-mode branch must reach the state error flag and return,
330    // not arbitrary code whose result happened to be ignored by the caller.
331    let mut error = w.branch(reject)?;
332    if preloaded {
333        if w.word(error)? != 0x712f {
334            return None;
335        } // STRB r7,[r5,#4]
336        error = error.checked_add(2)?;
337    } else {
338        if w.word(error)? != 0x2601 || w.word(error.checked_add(2)?)? != 0x712e {
339            return None;
340        }
341        error = error.checked_add(4)?;
342    }
343    let return_at = entry.checked_add(if preloaded { 44 } else { 46 })?;
344    if w.branch(error)? != return_at {
345        return None;
346    }
347    Some(DualCandidate {
348        decoder: start,
349        entry,
350        stream,
351    })
352}
353
354/// Inspect the alternate BOOT entry grammar, validating both mode branches,
355/// buffer 0x67, MMIO destination and the complete flag-copy control flow.
356/// Unknown, truncated or ambiguous windows return `None`; this does no I/O.
357pub fn inspect_boot_dual(bytes: &[u8], address: u32) -> Option<DualCandidate> {
358    let w = Window {
359        bytes,
360        base: address,
361    };
362    let mut found = None;
363    for index in (0..bytes.len()).step_by(2) {
364        let at = address.checked_add(u32::try_from(index).ok()?)?;
365        if at & 1 != 0 {
366            continue;
367        }
368        if let Some(hit) = dual_candidate(&w, at) {
369            if found.is_some() {
370                return None;
371            }
372            found = Some(hit);
373        }
374    }
375    found
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    // Instruction/literal excerpts from WH16NS58 V9 SHA256 97b28707...,
383    // checked against the independent disassembly. No firmware payload needed.
384    fn v9_window() -> std::vec::Vec<u8> {
385        let mut bytes = std::vec![0xff; 0x600];
386        for (address, hex) in [
387            (0x352a, "e548f0b5827889b00127e349002a72d142781207120f42704278e04e012a49d0022a01d0062af2d1"),
388            (0x35de, "b16909090901b161417ac90702d0b1693943b161417a890703d5b16902221143b161417a490703d5b16904221143b161407a000703d5b06908210843b06109b0f0bd"),
389            (0x38c0, "fc0d0002800d000240200004"),
390        ] {
391            for i in (0..hex.len()).step_by(2) {
392                bytes[address - 0x3400 + i / 2] = u8::from_str_radix(&hex[i..i+2], 16).unwrap();
393            }
394        }
395        bytes
396    }
397
398    #[test]
399    fn actual_v9_entry_has_fixed_operands_and_bounded_literals() {
400        let bytes = v9_window();
401        assert_eq!(
402            inspect_boot_legacy(&bytes, 0x3400),
403            Some(LegacyCandidate {
404                decoder: 0x353a,
405                entry: 0x35de,
406                end: 0x3620,
407            })
408        );
409        for length in 0..0x4cc {
410            assert!(inspect_boot_legacy(&bytes[..length], 0x3400).is_none());
411        }
412        for address in [
413            0x352e, 0x3532, 0x3536, 0x3546, 0x3548, 0x35de, 0x35e6, 0x35e8, 0x35ea, 0x35ee, 0x361a,
414            0x38c8,
415        ] {
416            let mut changed = bytes.clone();
417            changed[address - 0x3400] ^= 1;
418            assert!(
419                inspect_boot_legacy(&changed, 0x3400).is_none(),
420                "{address:#x}"
421            );
422        }
423    }
424
425    #[test]
426    fn ambiguity_alignment_and_address_overflow_never_select_a_recipe() {
427        let bytes = v9_window();
428        let mut doubled = bytes.clone();
429        doubled.extend_from_slice(&bytes);
430        assert!(inspect_boot_legacy(&doubled, 0x3400).is_none());
431        assert!(inspect_boot_legacy(&bytes, 0x3401).is_none());
432        assert!(inspect_boot_legacy(&bytes, u32::MAX - 16).is_none());
433    }
434
435    #[test]
436    fn both_real_dual_entry_compiler_forms_bind_modes_and_flag_paths() {
437        for (prefix, literal_at, literals, entry, body) in [
438            ("de49f0b5487885b00007000f4870487808220423022401280bd0d74d0a3d022801d0062879d14878062824d18878672821d1d348", 0x38bc,
439             "8a0d0002fc0d000240200004", 0x3576, DUAL_LOCAL),
440            ("dd49f0b5487889b00007000f487048780822042302240127d84e01280bd0d54d0a3d022801d006286cd14878062822d1887867281fd1", 0x38b8,
441             "8a0d0002fc0d000240200004", 0x357a, DUAL_PRELOADED),
442        ] {
443            let mut bytes = std::vec![0xff; 0x600];
444            for (address, hex) in [(0x3544, prefix), (literal_at, literals)] {
445                for i in (0..hex.len()).step_by(2) {
446                    bytes[address - 0x3400 + i / 2] = u8::from_str_radix(&hex[i..i+2], 16).unwrap();
447                }
448            }
449            for (address, hex) in if entry == 0x3576 {
450                [(0x365e, "7ce0"), (0x375a, "01262e7121e7")]
451            } else {
452                [(0x3648, "dbe0"), (0x3802, "2f71cfe6")]
453            } {
454                for i in (0..hex.len()).step_by(2) {
455                    bytes[address - 0x3400 + i / 2] = u8::from_str_radix(&hex[i..i+2], 16).unwrap();
456                }
457            }
458            bytes[entry-0x3400..entry-0x3400+body.len()].copy_from_slice(body);
459            if entry == 0x3576 { bytes[entry-0x3400] = 0xd3; }
460            assert_eq!(inspect_boot_dual(&bytes, 0x3400), Some(DualCandidate {
461                decoder: 0x3544, entry: entry as u32, stream: 0x35ba,
462            }));
463            assert!(inspect_boot_legacy(&bytes, 0x3400).is_none());
464            for address in 0x3554..0x35ba {
465                // Literal relocation is variable; every other body byte and
466                // mode-routing byte is fixed in these complete compiler forms.
467                if address == entry { continue; }
468                let mut changed = bytes.clone(); changed[address-0x3400] ^= 1;
469                assert!(inspect_boot_dual(&changed, 0x3400).is_none(), "{entry:#x}: {address:#x}");
470            }
471            let mut doubled = bytes.clone(); doubled.extend_from_slice(&bytes);
472            assert!(inspect_boot_dual(&doubled, 0x3400).is_none());
473            assert!(inspect_boot_dual(&bytes, u32::MAX - 16).is_none());
474        }
475    }
476}