Skip to main content

mediatek_optical/receiver/
reference.rs

1//! Explicit firmware-reference evidence for receivers with restricted code reads.
2//! Code analysis is offline. Physical metadata binds that evidence to a running
3//! application; it does not establish byte-for-byte equality of its live code.
4use 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/// Where the receiver's instruction evidence came from.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Provenance {
16    /// Instructions were read from the live device.
17    LiveCode,
18    /// Instructions came from an explicit firmware reference. Live identity and
19    /// build metadata must match before execution; live code was not verified.
20    CurrentReference,
21}
22
23/// Reference analysis or physical metadata binding failed.
24#[derive(Debug)]
25pub enum Error<E> {
26    /// The firmware reference did not contain a recognized application receiver.
27    Analysis(super::Error<&'static str>),
28    /// Live metadata could not be read or did not match the reference.
29    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    /// Analyze a validated application image for a future stage. Execution must
90    /// still bind its identity, banner and build metadata to the physical drive.
91    /// A reference cannot be used to infer a BOOT receiver.
92    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    /// Source of the cached instruction evidence.
120    pub fn provenance(&self) -> Provenance {
121        if self.reference.is_some() {
122            Provenance::CurrentReference
123        } else {
124            Provenance::LiveCode
125        }
126    }
127
128    /// Canonical image digest when the receiver uses explicit reference code.
129    pub fn reference_sha256(&self) -> Option<&[u8; 32]> {
130        self.reference
131            .as_ref()
132            .map(|reference| &reference.image_hash)
133    }
134}
135
136/// Analyze an explicit Current reference and bind it to physical application
137/// metadata. The resulting receiver revalidates physical metadata before writes;
138/// execution and verification never substitute file bytes for device reads.
139pub 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    // Preserve the actual INQUIRY record for diagnostics, never synthesize live identity.
147    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
156// This adapter exists only during offline analysis and has no physical handle.
157// It cannot escape into execution or verification.
158struct 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}