mediatek_optical/receiver/
reference.rs1use super::{Mode, Receiver};
5use crate::{
6 cdb,
7 drive::{self, Data, Transport},
8 envelope::Envelope,
9 layout, INQUIRY_LEN,
10};
11use core::convert::Infallible;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Provenance {
16 LiveCode,
18 CurrentReference,
21}
22
23#[derive(Debug)]
25pub enum Error<E> {
26 Analysis(super::Error<&'static str>),
28 Binding(super::Error<E>),
30}
31impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
32 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33 match self {
34 Self::Analysis(e) => write!(f, "Current firmware receiver analysis failed: {e}"),
35 Self::Binding(e) => write!(
36 f,
37 "Current firmware does not match the running receiver: {e}"
38 ),
39 }
40 }
41}
42impl<E: core::fmt::Debug + 'static> std::error::Error for Error<E> {
43 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
44 match self {
45 Self::Analysis(source) => Some(source),
46 Self::Binding(source) => Some(source),
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub(super) struct Binding {
53 identity: [u8; 28],
54 banner: [u8; layout::BANNER.len],
55 descriptor: [u8; 0x58],
56 live_banner: Option<[u8; layout::BANNER.len]>,
57 image_hash: [u8; 32],
58}
59impl Binding {
60 fn read_banner<T: Transport>(
61 &self,
62 t: &mut T,
63 ) -> Result<[u8; layout::BANNER.len], super::Error<T::Error>> {
64 let mode = super::detect_mode(t)?;
65 if mode.mode() != Mode::Firmware || mode.inquiry()[8..36] != self.identity {
66 return Err(super::Error::Changed);
67 }
68 let mut banner = [0; layout::BANNER.len];
69 drive::read_memory(t, layout::BANNER.start as u32, &mut banner)
70 .map_err(super::Error::Read)?;
71 let mut descriptor = [0; layout::DESCRIPTOR.len];
72 drive::read_memory(t, layout::DESCRIPTOR.start as u32, &mut descriptor)
73 .map_err(super::Error::Read)?;
74 if banner[..16] != self.banner[..16]
75 || descriptor[..0x58] != self.descriptor
76 || self.live_banner.is_some_and(|expected| banner != expected)
77 {
78 return Err(super::Error::Changed);
79 }
80 Ok(banner)
81 }
82
83 pub(super) fn revalidate<T: Transport>(&self, t: &mut T) -> Result<(), super::Error<T::Error>> {
84 self.read_banner(t).map(|_| ())
85 }
86}
87
88impl Receiver {
89 pub fn from_firmware(envelope: &Envelope) -> Result<Self, Error<Infallible>> {
93 let image = envelope.image();
94 let descriptor = layout::DESCRIPTOR.of(image).expect("validated envelope");
95 let mut inquiry = [0; INQUIRY_LEN];
96 inquiry[0] = 5;
97 inquiry[4] = 31;
98 inquiry[8..36].copy_from_slice(&descriptor[..28]);
99 let mut source = ImageSource { image, inquiry };
100 let mode = super::detect_mode(&mut source).map_err(Error::Analysis)?;
101 if mode.mode() != Mode::Firmware {
102 return Err(Error::Analysis(super::Error::InvalidIdentity));
103 }
104 let mut receiver = super::detect(&mut source, &mode).map_err(Error::Analysis)?;
105 receiver.reference = Some(Binding {
106 identity: descriptor[..28].try_into().expect("descriptor identity"),
107 banner: layout::BANNER
108 .of(image)
109 .expect("validated banner")
110 .try_into()
111 .unwrap(),
112 descriptor: descriptor[..0x58].try_into().expect("descriptor metadata"),
113 live_banner: None,
114 image_hash: *envelope.image_sha256(),
115 });
116 Ok(receiver)
117 }
118
119 pub fn provenance(&self) -> Provenance {
121 if self.reference.is_some() {
122 Provenance::CurrentReference
123 } else {
124 Provenance::LiveCode
125 }
126 }
127
128 pub fn reference_sha256(&self) -> Option<&[u8; 32]> {
130 self.reference
131 .as_ref()
132 .map(|reference| &reference.image_hash)
133 }
134}
135
136pub fn detect<T: Transport>(t: &mut T, envelope: &Envelope) -> Result<Receiver, Error<T::Error>> {
140 let mut receiver = Receiver::from_firmware(envelope).map_err(|error| match error {
141 Error::Analysis(error) => Error::Analysis(error),
142 Error::Binding(_) => unreachable!("offline analysis performs no physical binding"),
143 })?;
144 let binding = receiver.reference.as_mut().expect("reference receiver");
145 binding.live_banner = Some(binding.read_banner(t).map_err(Error::Binding)?);
146 let identity = super::detect_mode(t).map_err(Error::Binding)?;
148 if identity.mode() != Mode::Firmware || identity.inquiry()[8..36] != binding.identity {
149 return Err(Error::Binding(super::Error::Changed));
150 }
151 receiver.identity = identity;
152 receiver.revalidate(t).map_err(Error::Binding)?;
153 Ok(receiver)
154}
155
156struct ImageSource<'a> {
159 image: &'a [u8],
160 inquiry: [u8; INQUIRY_LEN],
161}
162impl Transport for ImageSource<'_> {
163 type Error = &'static str;
164 fn sense(&self) -> Option<(u8, u8, u8)> {
165 None
166 }
167 fn exec(&mut self, command: &[u8], data: Data<'_>) -> Result<usize, Self::Error> {
168 let Data::In(bytes) = data else {
169 return Err("offline source cannot execute commands");
170 };
171 if command == cdb::inquiry(INQUIRY_LEN as u16) {
172 bytes.copy_from_slice(&self.inquiry);
173 } else if command.len() == 10 && command[..3] == cdb::read_memory(0, 0)[..3] {
174 let address =
175 ((command[3] as usize) << 16) | ((command[4] as usize) << 8) | command[5] as usize;
176 let source = self
177 .image
178 .get(address..address + bytes.len())
179 .ok_or("offline read outside image")?;
180 bytes.copy_from_slice(source);
181 } else {
182 return Err("unsupported offline analysis read");
183 }
184 Ok(bytes.len())
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 struct Restricted {
193 image: Vec<u8>,
194 reads: usize,
195 }
196 impl Transport for Restricted {
197 type Error = &'static str;
198 fn sense(&self) -> Option<(u8, u8, u8)> {
199 None
200 }
201 fn exec(&mut self, command: &[u8], data: Data<'_>) -> Result<usize, Self::Error> {
202 self.reads += 1;
203 let Data::In(bytes) = data else {
204 panic!("reference binding must not write")
205 };
206 if command == cdb::inquiry(INQUIRY_LEN as u16) {
207 bytes.fill(0);
208 bytes[0] = 5;
209 bytes[4] = 31;
210 bytes[8..36].copy_from_slice(
211 &self.image[layout::DESCRIPTOR.start..layout::DESCRIPTOR.start + 28],
212 );
213 } else {
214 let address = ((command[3] as usize) << 16)
215 | ((command[4] as usize) << 8)
216 | command[5] as usize;
217 if ![layout::BANNER, layout::DESCRIPTOR]
218 .iter()
219 .any(|range| address == range.start && bytes.len() == range.len)
220 {
221 return Err("physical code reads unavailable");
222 }
223 bytes.copy_from_slice(&self.image[address..address + bytes.len()]);
224 }
225 Ok(bytes.len())
226 }
227 }
228
229 fn fixture() -> (Envelope, Restricted) {
230 let mut image = crate::fixtures::image(&crate::fixtures::BU3, 7);
231 for (i, &byte) in super::super::PLAIN_APP.iter().enumerate() {
232 image[0x9a300 + i] = if byte == super::super::ANY {
233 0
234 } else {
235 byte as u8
236 };
237 }
238 let image = crate::fixtures::sign(image);
239 let envelope = Envelope::load(&image).unwrap();
240 (envelope, Restricted { image, reads: 0 })
241 }
242
243 #[test]
244 fn explicit_reference_binds_without_physical_code_reads() {
245 let (envelope, mut live) = fixture();
246 let receiver = detect(&mut live, &envelope).unwrap();
247 assert_eq!(receiver.provenance(), Provenance::CurrentReference);
248 assert_eq!(receiver.reference_sha256(), Some(envelope.image_sha256()));
249 assert!(receiver.prepare_command().is_some());
250 receiver.revalidate(&mut live).unwrap();
251 assert!(live.reads > 0);
252 }
253
254 #[test]
255 fn offline_stage_requires_actual_matching_application_at_handoff() {
256 let (envelope, mut live) = fixture();
257 let receiver = Receiver::from_firmware(&envelope).unwrap();
258 assert_eq!(live.reads, 0);
259 receiver.revalidate(&mut live).unwrap();
260 live.image[layout::DESCRIPTOR.start + 24..layout::DESCRIPTOR.start + 28]
261 .copy_from_slice(b"INTM");
262 assert!(matches!(
263 receiver.revalidate(&mut live),
264 Err(super::super::Error::Changed)
265 ));
266 live.image[layout::DESCRIPTOR.start + 24..layout::DESCRIPTOR.start + 28]
267 .copy_from_slice(b"BOOT");
268 assert!(detect(&mut live, &envelope).is_err());
269 }
270
271 #[test]
272 fn reference_binding_checks_build_metadata_and_preserves_live_boot_snapshot() {
273 let (envelope, mut live) = fixture();
274 live.image[layout::BANNER.start + 20] ^= 1;
275 let receiver = detect(&mut live, &envelope).unwrap();
276 receiver.revalidate(&mut live).unwrap();
277 live.image[layout::BANNER.start + 20] ^= 2;
278 assert!(receiver.revalidate(&mut live).is_err());
279 let receiver = Receiver::from_firmware(&envelope).unwrap();
280 receiver.revalidate(&mut live).unwrap();
281 live.image[layout::DESCRIPTOR.start + 0x40] ^= 1;
282 assert!(receiver.revalidate(&mut live).is_err());
283 }
284
285 #[test]
286 fn unknown_reference_code_does_not_gain_a_receiver_from_matching_metadata() {
287 let (envelope, mut live) = fixture();
288 let mut image = envelope.image().to_vec();
289 image[0x98000..0xb4000].fill(0);
290 let envelope = Envelope::load(&crate::fixtures::sign(image)).unwrap();
291 assert!(matches!(
292 detect(&mut live, &envelope),
293 Err(Error::Analysis(_))
294 ));
295 assert_eq!(live.reads, 0);
296 }
297
298 #[test]
299 fn reference_is_not_a_source_of_live_readback_bytes() {
300 let (envelope, mut live) = fixture();
301 let receiver = detect(&mut live, &envelope).unwrap();
302 receiver.revalidate(&mut live).unwrap();
303 let mut code = [0; 32];
304 assert!(drive::read_memory(&mut live, 0x9a300, &mut code).is_err());
305 }
306}