1 | //! Shim lock protocol. |
2 | |
3 | #![cfg (any( |
4 | target_arch = "i386" , |
5 | target_arch = "x86_64" , |
6 | target_arch = "arm" , |
7 | target_arch = "aarch64" |
8 | ))] |
9 | |
10 | use crate::proto::unsafe_protocol ; |
11 | use crate::result::Error; |
12 | use crate::{Result, Status}; |
13 | use core::ffi::c_void; |
14 | use core::mem::MaybeUninit; |
15 | |
16 | // The `PE_COFF_LOADER_IMAGE_CONTEXT` type. None of our methods need to inspect |
17 | // the fields of this struct, we just need to make sure it is the right size. |
18 | #[repr (C)] |
19 | struct Context { |
20 | _image_address: u64, |
21 | _image_size: u64, |
22 | _entry_point: u64, |
23 | _size_of_headers: usize, |
24 | _image_type: u16, |
25 | _number_of_sections: u16, |
26 | _section_alignment: u32, |
27 | _first_section: *const c_void, |
28 | _reloc_dir: *const c_void, |
29 | _sec_dir: *const c_void, |
30 | _number_of_rva_and_sizes: u64, |
31 | _pe_hdr: *const c_void, |
32 | } |
33 | |
34 | const SHA1_DIGEST_SIZE: usize = 20; |
35 | const SHA256_DIGEST_SIZE: usize = 32; |
36 | |
37 | /// Authenticode hashes of some UEFI application |
38 | pub struct Hashes { |
39 | /// SHA256 Authenticode Digest |
40 | pub sha256: [u8; SHA256_DIGEST_SIZE], |
41 | /// SHA1 Authenticode Digest |
42 | pub sha1: [u8; SHA1_DIGEST_SIZE], |
43 | } |
44 | |
45 | // These macros set the correct calling convention for the Shim protocol methods. |
46 | |
47 | #[cfg (any(target_arch = "i386" , target_arch = "x86_64" ))] |
48 | macro_rules! shim_function { |
49 | (fn $args:tt -> $return_type:ty) => (extern "sysv64" fn $args -> $return_type) |
50 | } |
51 | |
52 | #[cfg (any(target_arch = "arm" , target_arch = "aarch64" ))] |
53 | macro_rules! shim_function { |
54 | (fn $args:tt -> $return_type:ty) => (extern "C" fn $args -> $return_type) |
55 | } |
56 | |
57 | /// The Shim lock protocol. |
58 | /// |
59 | /// This protocol is not part of the UEFI specification, but is |
60 | /// installed by the [Shim bootloader](https://github.com/rhboot/shim) |
61 | /// which is commonly used by Linux distributions to support UEFI |
62 | /// Secure Boot. Shim is built with an embedded certificate that is |
63 | /// used to validate another EFI application before running it. That |
64 | /// application may itself be a bootloader that needs to validate |
65 | /// another EFI application before running it, and the shim lock |
66 | /// protocol exists to support that. |
67 | #[repr (C)] |
68 | #[unsafe_protocol ("605dab50-e046-4300-abb6-3dd810dd8b23" )] |
69 | pub struct ShimLock { |
70 | verify: shim_function! { fn(buffer: *const u8, size: u32) -> Status }, |
71 | hash: shim_function! { |
72 | fn( |
73 | buffer: *const u8, |
74 | size: u32, |
75 | context: *mut Context, |
76 | sha256: *mut [u8; SHA256_DIGEST_SIZE], |
77 | sha1: *mut [u8; SHA1_DIGEST_SIZE] |
78 | ) -> Status |
79 | }, |
80 | context: shim_function! { fn(buffer: *const u8, size: u32, context: *mut Context) -> Status }, |
81 | } |
82 | |
83 | impl ShimLock { |
84 | /// Verify that an EFI application is signed by the certificate |
85 | /// embedded in shim. |
86 | /// |
87 | /// The buffer's size must fit in a `u32`; if that condition is not |
88 | /// met then a `BAD_BUFFER_SIZE` error will be returned and the shim |
89 | /// lock protocol will not be called. |
90 | pub fn verify(&self, buffer: &[u8]) -> Result { |
91 | let size: u32 = buffer |
92 | .len() |
93 | .try_into() |
94 | .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?; |
95 | (self.verify)(buffer.as_ptr(), size).into() |
96 | } |
97 | /// Compute the Authenticode Hash of the provided EFI application. |
98 | /// |
99 | /// The buffer's size must fit in a `u32`; if that condition is not |
100 | /// met then a `BAD_BUFFER_SIZE` error will be returned and the shim |
101 | /// lock protocol will not be called. |
102 | pub fn hash(&self, buffer: &[u8], hashes: &mut Hashes) -> Result { |
103 | let ptr: *const u8 = buffer.as_ptr(); |
104 | let size: u32 = buffer |
105 | .len() |
106 | .try_into() |
107 | .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?; |
108 | |
109 | let mut context = MaybeUninit::<Context>::uninit(); |
110 | Result::from((self.context)(ptr, size, context.as_mut_ptr()))?; |
111 | (self.hash)( |
112 | ptr, |
113 | size, |
114 | context.as_mut_ptr(), |
115 | &mut hashes.sha256, |
116 | &mut hashes.sha1, |
117 | ) |
118 | .into() |
119 | } |
120 | } |
121 | |