mediatek_optical/receiver/entry/
application.rs1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct Candidate {
21 pub table: u32,
23 pub entry: u32,
25 pub mode1_target: u32,
27 pub mode6_target: u32,
29 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
36pub fn matches_helper(bytes: &[u8]) -> bool {
39 bytes.get(..ARM_HELPER.len()) == Some(ARM_HELPER)
40}
41
42pub const HELPER_BYTES: usize = ARM_HELPER.len();
44
45pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct ModeBufferGuard(());
54
55impl ModeBufferGuard {
56 pub fn inspect(bytes: &[u8]) -> Option<Self> {
59 (bytes.get(..MODE_GUARD_BYTES) == Some(MODE_GUARD.as_slice())).then_some(Self(()))
60 }
61
62 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 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
166pub 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 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}