kernel/
process_checker.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Traits and types for application credentials checkers, used to decide
6//! whether an application can be loaded.
7//!
8//! See the [AppID TRD](../../doc/reference/trd-appid.md).
9
10use core::cell::Cell;
11use core::fmt;
12
13use crate::config;
14use crate::debug;
15use crate::process::Process;
16use crate::process::ShortId;
17use crate::process_binary::ProcessBinary;
18use crate::utilities::cells::{NumericCellExt, OptionalCell};
19use crate::ErrorCode;
20use tock_tbf::types::TbfFooterV2Credentials;
21use tock_tbf::types::TbfParseError;
22
23/// Error from checking process credentials.
24pub enum ProcessCheckError {
25    /// The application checker requires credentials, but the TBF did not
26    /// include a credentials that meets the checker's requirements. This can be
27    /// either because the TBF has no credentials or the checker policy did not
28    /// accept any of the credentials it has.
29    CredentialsNotAccepted,
30
31    /// The process contained a credentials which was rejected by the verifier.
32    /// The `u32` indicates which credentials was rejected: the first
33    /// credentials after the application binary is 0, and each subsequent
34    /// credentials increments this counter.
35    CredentialsRejected(u32),
36
37    /// Error in the kernel implementation.
38    InternalError,
39}
40
41impl fmt::Debug for ProcessCheckError {
42    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43        match self {
44            ProcessCheckError::CredentialsNotAccepted => {
45                write!(f, "No credentials accepted")
46            }
47
48            ProcessCheckError::CredentialsRejected(index) => {
49                write!(f, "Credential {} rejected", index)
50            }
51
52            ProcessCheckError::InternalError => write!(f, "Error in kernel. Likely a bug."),
53        }
54    }
55}
56
57/// What a AppCredentialsChecker decided a particular application's credential
58/// indicates about the runnability of an application binary.
59#[derive(Debug)]
60pub enum CheckResult {
61    /// Accept the credential and run the binary.
62    ///
63    /// The associated value is an optional opaque usize the credential
64    /// checker can return to communication some information about the accepted
65    /// credential.
66    Accept(Option<CheckResultAcceptMetadata>),
67    /// Go to the next credential or in the case of the last one fall
68    /// back to the default policy.
69    Pass,
70    /// Reject the credential and do not run the binary.
71    Reject,
72}
73
74/// Optional metadata the credential checker can attach to an accepted
75/// credential.
76///
77/// This metadata can be used to provide context for why or how the accepted
78/// credential was accepted. For example, this could be set to the index of a
79/// public key that was used to verify a cryptographic signature. This value can
80/// then be used by the AppId assigner to assign the correct AppId and
81/// [`ShortId`].
82#[derive(Debug, Copy, Clone)]
83pub struct CheckResultAcceptMetadata {
84    /// The metadata stored with the accepted credential is a usize that has an
85    /// application-specific meaning.
86    pub metadata: usize,
87}
88
89/// Receives callbacks on whether a credential was accepted or not.
90pub trait AppCredentialsPolicyClient<'a> {
91    /// The check for a particular credential is complete. Result of the check
92    /// is in `result`.
93    fn check_done(
94        &self,
95        result: Result<CheckResult, ErrorCode>,
96        credentials: TbfFooterV2Credentials,
97        integrity_region: &'a [u8],
98    );
99}
100
101/// The accepted credential from the credential checker.
102///
103/// This combines both the credential as stored in the TBF footer with an
104/// optional opaque value provided by the checker when it accepted the
105/// credential. This value can be used when assigning an AppID to the
106/// application based on the how the credential was approved. For example, if
107/// the credential checker has a list of valid public keys used to verify
108/// signatures, it might set the optional value to the index of the public key
109/// in this list.
110#[derive(Copy, Clone)]
111pub struct AcceptedCredential {
112    /// The credential stored in the footer that the credential checker
113    /// accepted.
114    pub credential: TbfFooterV2Credentials,
115    /// An optional opaque value set by the credential checker to store metadata
116    /// about the accepted credential. This is credential checker specific.
117    pub metadata: Option<CheckResultAcceptMetadata>,
118}
119
120/// Implements a Credentials Checking Policy.
121pub trait AppCredentialsPolicy<'a> {
122    /// Set the client which gets notified after the credential check completes.
123    fn set_client(&self, client: &'a dyn AppCredentialsPolicyClient<'a>);
124
125    /// Whether credentials are required or not.
126    ///
127    /// If this returns `true`, then a process will only be executed if one
128    /// credential was accepted. If this returns `false` then a process will be
129    /// executed even if no credentials are accepted.
130    fn require_credentials(&self) -> bool;
131
132    /// Check a particular credential.
133    ///
134    /// If credential checking started successfully then this returns `Ok()`.
135    fn check_credentials(
136        &self,
137        credentials: TbfFooterV2Credentials,
138        integrity_region: &'a [u8],
139    ) -> Result<(), (ErrorCode, TbfFooterV2Credentials, &'a [u8])>;
140}
141
142/// Whether two processes have the same Application Identifier; two
143/// processes with the same Application Identifier cannot run concurrently.
144pub trait AppUniqueness {
145    /// Returns whether `process_a` and `process_b` have a different identifier,
146    /// and so can run concurrently. If this returns `false`, the kernel
147    /// will not run `process_a` and `process_b` at the same time.
148    fn different_identifier(&self, process_a: &ProcessBinary, process_b: &ProcessBinary) -> bool;
149
150    /// Returns whether `process_a` and `process_b` have a different identifier,
151    /// and so can run concurrently. If this returns `false`, the kernel
152    /// will not run `process_a` and `process_b` at the same time.
153    fn different_identifier_process(
154        &self,
155        process_a: &ProcessBinary,
156        process_b: &dyn Process,
157    ) -> bool;
158
159    /// Returns whether `process_a` and `process_b` have a different identifier,
160    /// and so can run concurrently. If this returns `false`, the kernel
161    /// will not run `process_a` and `process_b` at the same time.
162    fn different_identifier_processes(
163        &self,
164        process_a: &dyn Process,
165        process_b: &dyn Process,
166    ) -> bool;
167}
168
169/// Default implementation.
170impl AppUniqueness for () {
171    fn different_identifier(&self, _process_a: &ProcessBinary, _process_b: &ProcessBinary) -> bool {
172        true
173    }
174
175    fn different_identifier_process(
176        &self,
177        _process_a: &ProcessBinary,
178        _process_b: &dyn Process,
179    ) -> bool {
180        true
181    }
182
183    fn different_identifier_processes(
184        &self,
185        _process_a: &dyn Process,
186        _process_b: &dyn Process,
187    ) -> bool {
188        true
189    }
190}
191
192/// Transforms Application Credentials into a corresponding ShortId.
193pub trait Compress {
194    /// Create a `ShortId` for `process`.
195    ///
196    /// If the process was approved to run because of a specific credential, the
197    /// `ProcessBinary will have its `credential` filed set to `Some()` with
198    /// that credential.
199    fn to_short_id(&self, process: &ProcessBinary) -> ShortId;
200}
201
202impl Compress for () {
203    fn to_short_id(&self, _process: &ProcessBinary) -> ShortId {
204        ShortId::LocallyUnique
205    }
206}
207
208pub trait AppIdPolicy: AppUniqueness + Compress {}
209impl<T: AppUniqueness + Compress> AppIdPolicy for T {}
210
211/// Client interface for the outcome of a process credential check.
212pub trait ProcessCheckerMachineClient {
213    /// Check is finished, and the check result is in `result`.0
214    ///
215    /// If `result` is `Ok(Option<Credentials>)`, the credentials were either
216    /// accepted and the accepted credential is provided, or no credentials were
217    /// accepted but none is required.
218    ///
219    /// If `result` is `Err`, the credentials were not accepted and the policy
220    /// denied approving the app.
221    fn done(
222        &self,
223        process_binary: ProcessBinary,
224        result: Result<Option<AcceptedCredential>, ProcessCheckError>,
225    );
226}
227
228/// Outcome from checking a single footer credential.
229#[derive(Debug)]
230enum FooterCheckResult {
231    /// A check has started
232    Checking,
233    /// There are no more footers, no check started
234    PastLastFooter,
235    /// The footer isn't a credential, no check started
236    FooterNotCheckable,
237    /// The footer is invalid, no check started
238    BadFooter,
239    /// An internal error occurred, no check started
240    Error,
241}
242
243/// Checks the footers for a `ProcessBinary` and decides whether to continue
244/// loading the process based on the checking policy in `checker`.
245pub struct ProcessCheckerMachine {
246    /// Client for receiving the outcome of the check.
247    client: OptionalCell<&'static dyn ProcessCheckerMachineClient>,
248    /// Policy for checking credentials.
249    policy: OptionalCell<&'static dyn AppCredentialsPolicy<'static>>,
250    /// Hold the process binary during checking.
251    process_binary: OptionalCell<ProcessBinary>,
252    /// Keep track of which footer is being parsed.
253    footer_index: Cell<usize>,
254}
255
256impl ProcessCheckerMachine {
257    pub fn new(policy: &'static dyn AppCredentialsPolicy<'static>) -> Self {
258        Self {
259            footer_index: Cell::new(0),
260            policy: OptionalCell::new(policy),
261            process_binary: OptionalCell::empty(),
262            client: OptionalCell::empty(),
263        }
264    }
265
266    pub fn set_client(&self, client: &'static dyn ProcessCheckerMachineClient) {
267        self.client.set(client);
268    }
269
270    pub fn set_policy(&self, policy: &'static dyn AppCredentialsPolicy<'static>) {
271        self.policy.replace(policy);
272    }
273
274    /// Check this `process_binary` to see if its credentials are valid.
275    ///
276    /// This must be called from a interrupt callback chain.
277    pub fn check(&self, process_binary: ProcessBinary) -> Result<(), ProcessCheckError> {
278        self.footer_index.set(0);
279        self.process_binary.set(process_binary);
280        self.next()
281    }
282
283    /// Must be called from a callback context.
284    fn next(&self) -> Result<(), ProcessCheckError> {
285        let policy = self.policy.get().ok_or(ProcessCheckError::InternalError)?;
286        let pb = self
287            .process_binary
288            .take()
289            .ok_or(ProcessCheckError::InternalError)?;
290        let pb_name = pb.header.get_package_name().unwrap_or("");
291
292        // Loop over all footers in the footer region. We don't know how many
293        // footers there are, so we use `loop {}`.
294        loop {
295            let footer_index = self.footer_index.get();
296
297            let check_result = ProcessCheckerMachine::check_footer(&pb, policy, footer_index);
298
299            if config::CONFIG.debug_process_credentials {
300                debug!(
301                    "Checking: Check status for process {}, footer {}: {:?}",
302                    pb_name, footer_index, check_result
303                );
304            }
305            match check_result {
306                FooterCheckResult::Checking => {
307                    self.process_binary.set(pb);
308                    break;
309                }
310                FooterCheckResult::PastLastFooter | FooterCheckResult::BadFooter => {
311                    // We reached the end of the footers without any
312                    // credentials or all credentials were Pass: apply
313                    // the checker policy to see if the process
314                    // should be allowed to run.
315                    self.policy.map(|policy| {
316                        let requires = policy.require_credentials();
317
318                        let result = if requires {
319                            Err(ProcessCheckError::CredentialsNotAccepted)
320                        } else {
321                            Ok(None)
322                        };
323
324                        self.client.map(|client| client.done(pb, result));
325                    });
326                    break;
327                }
328                FooterCheckResult::FooterNotCheckable => {
329                    // Go to next footer
330                    self.footer_index.increment();
331                }
332                FooterCheckResult::Error => {
333                    self.client
334                        .map(|client| client.done(pb, Err(ProcessCheckError::InternalError)));
335                    break;
336                }
337            }
338        }
339        Ok(())
340    }
341
342    // Returns whether a footer is being checked or not, and if not, why.
343    // Iterates through the footer list until if finds `next_footer` or
344    // it reached the end of the footer region.
345    fn check_footer(
346        process_binary: &ProcessBinary,
347        policy: &'static dyn AppCredentialsPolicy<'static>,
348        next_footer: usize,
349    ) -> FooterCheckResult {
350        if config::CONFIG.debug_process_credentials {
351            debug!(
352                "Checking: Checking {:?} footer {}",
353                process_binary.header.get_package_name(),
354                next_footer
355            );
356        }
357
358        let integrity_slice = process_binary.get_integrity_region_slice();
359        let mut footer_slice = process_binary.footers;
360
361        if config::CONFIG.debug_process_credentials {
362            debug!(
363                "Checking: Integrity region is {:x}-{:x}; footers at {:x}-{:x}",
364                integrity_slice.as_ptr() as usize,
365                integrity_slice.as_ptr() as usize + integrity_slice.len(),
366                footer_slice.as_ptr() as usize,
367                footer_slice.as_ptr() as usize + footer_slice.len(),
368            );
369        }
370
371        let mut current_footer = 0;
372        while current_footer <= next_footer {
373            if config::CONFIG.debug_process_credentials {
374                debug!(
375                    "Checking: Current footer slice {:x}-{:x}",
376                    footer_slice.as_ptr() as usize,
377                    footer_slice.as_ptr() as usize + footer_slice.len(),
378                );
379            }
380
381            let parse_result = tock_tbf::parse::parse_tbf_footer(footer_slice);
382            match parse_result {
383                Err(TbfParseError::NotEnoughFlash) => {
384                    if config::CONFIG.debug_process_credentials {
385                        debug!("Checking: Not enough flash for a footer");
386                    }
387                    return FooterCheckResult::PastLastFooter;
388                }
389                Err(TbfParseError::BadTlvEntry(t)) => {
390                    if config::CONFIG.debug_process_credentials {
391                        debug!("Checking: Bad TLV entry, type: {:?}", t);
392                    }
393                    return FooterCheckResult::BadFooter;
394                }
395                Err(e) => {
396                    if config::CONFIG.debug_process_credentials {
397                        debug!("Checking: Error parsing footer: {:?}", e);
398                    }
399                    return FooterCheckResult::BadFooter;
400                }
401                Ok((footer, len)) => {
402                    let slice_result = footer_slice.get(len as usize + 4..);
403                    if config::CONFIG.debug_process_credentials {
404                        debug!(
405                            "ProcessCheck: @{:x} found a len {} footer: {:?}",
406                            footer_slice.as_ptr() as usize,
407                            len,
408                            footer.format()
409                        );
410                    }
411                    match slice_result {
412                        None => {
413                            return FooterCheckResult::BadFooter;
414                        }
415                        Some(slice) => {
416                            footer_slice = slice;
417                            if current_footer == next_footer {
418                                match policy.check_credentials(footer, integrity_slice) {
419                                    Ok(()) => {
420                                        if config::CONFIG.debug_process_credentials {
421                                            debug!("Checking: Found {}, checking", current_footer);
422                                        }
423                                        return FooterCheckResult::Checking;
424                                    }
425                                    Err((ErrorCode::NOSUPPORT, _, _)) => {
426                                        if config::CONFIG.debug_process_credentials {
427                                            debug!(
428                                                "Checking: Found {}, not supported",
429                                                current_footer
430                                            );
431                                        }
432                                        return FooterCheckResult::FooterNotCheckable;
433                                    }
434                                    Err((ErrorCode::ALREADY, _, _)) => {
435                                        if config::CONFIG.debug_process_credentials {
436                                            debug!("Checking: Found {}, already", current_footer);
437                                        }
438                                        return FooterCheckResult::FooterNotCheckable;
439                                    }
440                                    Err(e) => {
441                                        if config::CONFIG.debug_process_credentials {
442                                            debug!(
443                                                "Checking: Found {}, error {:?}",
444                                                current_footer, e
445                                            );
446                                        }
447                                        return FooterCheckResult::Error;
448                                    }
449                                }
450                            }
451                        }
452                    }
453                }
454            }
455            current_footer += 1;
456        }
457        FooterCheckResult::PastLastFooter
458    }
459}
460
461impl AppCredentialsPolicyClient<'static> for ProcessCheckerMachine {
462    fn check_done(
463        &self,
464        result: Result<CheckResult, ErrorCode>,
465        credentials: TbfFooterV2Credentials,
466        _integrity_region: &'static [u8],
467    ) {
468        if config::CONFIG.debug_process_credentials {
469            debug!("Checking: check_done gave result {:?}", result);
470        }
471        let cont = match result {
472            Ok(CheckResult::Accept(opaque)) => {
473                self.client.map(|client| {
474                    if let Some(pb) = self.process_binary.take() {
475                        client.done(
476                            pb,
477                            Ok(Some(AcceptedCredential {
478                                credential: credentials,
479                                metadata: opaque,
480                            })),
481                        )
482                    }
483                });
484                false
485            }
486            Ok(CheckResult::Pass) => {
487                // Checker ignored the credential, so we try the next one.
488                self.footer_index.increment();
489                true
490            }
491            Ok(CheckResult::Reject) => {
492                self.client.map(|client| {
493                    if let Some(pb) = self.process_binary.take() {
494                        client.done(
495                            pb,
496                            Err(ProcessCheckError::CredentialsRejected(
497                                self.footer_index.get() as u32,
498                            )),
499                        )
500                    }
501                });
502                false
503            }
504            Err(e) => {
505                if config::CONFIG.debug_process_credentials {
506                    debug!("Checking: error checking footer {:?}", e);
507                }
508                self.footer_index.increment();
509                true
510            }
511        };
512        if cont {
513            // If this errors it is an internal error. We don't have a
514            // `process_binary` to signal the `client::done()` callback, so we
515            // cannot signal the error.
516            let _ = self.next();
517        }
518    }
519}