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