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}