xref: /aosp_15_r20/bootable/libbootloader/gbl/libefi/src/protocol/gbl_efi_fastboot.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Rust wrapper for `GBL_EFI_FASTBOOT_PROTOCOL`
16 
17 use crate::{
18     efi_call,
19     protocol::{Protocol, ProtocolInfo},
20 };
21 use core::{
22     ffi::{c_char, c_void, CStr},
23     ptr::null,
24     slice::from_raw_parts,
25     str::from_utf8,
26 };
27 use efi_types::{EfiGuid, GblEfiFastbootPolicy, GblEfiFastbootProtocol};
28 use liberror::{Error, Result};
29 
30 /// GBL_EFI_FASTBOOT_PROTOCOL
31 pub struct GblFastbootProtocol;
32 
33 // Note: this is an internal limitation due to the need to allocate
34 // fixed sized buffers for storing args in the iterator
35 // and in the wrapper for `GblEfiFastbootProtocol.get_var`.
36 const MAX_ARGS: usize = 16;
37 
38 impl ProtocolInfo for GblFastbootProtocol {
39     type InterfaceType = GblEfiFastbootProtocol;
40 
41     const GUID: EfiGuid =
42         EfiGuid::new(0xc67e48a0, 0x5eb8, 0x4127, [0xbe, 0x89, 0xdf, 0x2e, 0xd9, 0x3d, 0x8a, 0x9a]);
43 }
44 
45 impl Protocol<'_, GblFastbootProtocol> {
46     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.get_var`
get_var<'a>( &self, var: &CStr, args: impl Iterator<Item = &'a CStr> + Clone, out: &mut [u8], ) -> Result<usize>47     pub fn get_var<'a>(
48         &self,
49         var: &CStr,
50         args: impl Iterator<Item = &'a CStr> + Clone,
51         out: &mut [u8],
52     ) -> Result<usize> {
53         let mut args_arr = [null(); MAX_ARGS];
54         let num_args = safemath::SafeNum::from(1) + args.clone().count();
55         let args_arr = args_arr.get_mut(..num_args.try_into()?).ok_or(Error::InvalidInput)?;
56         args_arr[0] = var.as_ptr();
57         args_arr[1..].iter_mut().zip(args).for_each(|(l, r)| *l = r.as_ptr());
58         let mut bufsize = out.len();
59         // SAFETY:
60         // `self.interface()?` guarantees self.interface is non-null and points to a valid object
61         // established by `Protocol::new()`.
62         // No parameters are retained, all parameters outlive the call, and no pointers are Null.
63         unsafe {
64             efi_call!(
65                 @bufsize bufsize,
66                 self.interface()?.get_var,
67                 self.interface,
68                 args_arr.as_ptr(),
69                 args_arr.len(),
70                 out.as_mut_ptr(),
71                 &mut bufsize
72             )?
73         };
74         Ok(bufsize)
75     }
76 
77     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.get_var_all`
get_var_all(&self, mut cb: impl FnMut(&[&CStr], &CStr)) -> Result<()>78     pub fn get_var_all(&self, mut cb: impl FnMut(&[&CStr], &CStr)) -> Result<()> {
79         struct Callback<'a>(&'a mut dyn FnMut(&[&CStr], &CStr));
80 
81         /// Callback C function to be passed to the `get_var_all` function.
82         ///
83         /// # Safety
84         ///
85         /// * Caller must guarantee that `ctx` points to a valid instance of `Callback`, outlives
86         ///   the call, and not being referenced elsewhere.
87         /// * Caller must guarantee that `args` points to an array of NULL-terminated strings with
88         ///   size `len` and outlives the call.
89         /// * Caller must guarantee that `val` points to valid NULL-terminated strings and outlives
90         ///   the call.
91         unsafe extern "C" fn get_var_all_cb(
92             ctx: *mut c_void,
93             args: *const *const c_char,
94             len: usize,
95             val: *const c_char,
96         ) {
97             // SAFETY: By safety requirement of this function, `args` points to an array of
98             // NULL-terminated strings of length `len`.
99             let args =
100                 unsafe { from_raw_parts(args, len) }.iter().map(|v| unsafe { CStr::from_ptr(*v) });
101             // SAFETY: By requirement of this function, `ctx` points to a `Callback`.
102             let cb = unsafe { (ctx as *mut Callback).as_mut() }.unwrap();
103             // Checks number of arguments and stores them in an array.
104             let mut args_arr = [c""; MAX_ARGS];
105             match args_arr.get_mut(..len) {
106                 Some(v) => {
107                     v.iter_mut().zip(args).for_each(|(l, r)| *l = r);
108                     // SAFETY: By safety requirement of this function `val` points to a
109                     // NULL-terminated string.
110                     (cb.0)(&v, unsafe { CStr::from_ptr(val) })
111                 }
112                 _ => (cb.0)(&[c"<Number of arguments exceeds limit>"], c""),
113             }
114         }
115 
116         // SAFETY:
117         // *`self.interface()?` guarantees self.interface is non-null and points to a valid object
118         // * established by `Protocol::new()`.
119         // * The `ctx` parameter is a valid `Callback` object, outlives the call and not being
120         //   referenced elsewhere(declared inline at the parameter site).
121         // * By UEFI interface requirement, vendor firmware passes array of C strings to
122         //   `get_var_all_cb` that remains valid for the call.
123         unsafe {
124             efi_call!(
125                 self.interface()?.get_var_all,
126                 self.interface,
127                 &mut Callback(&mut cb) as *mut _ as _,
128                 Some(get_var_all_cb),
129             )?
130         };
131 
132         Ok(())
133     }
134 
135     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.run_oem_function()`
run_oem_function(&self, cmd: &str, buffer: &mut [u8]) -> Result<usize>136     pub fn run_oem_function(&self, cmd: &str, buffer: &mut [u8]) -> Result<usize> {
137         let mut bufsize = buffer.len();
138         if !buffer.is_empty() {
139             buffer[0] = 0;
140         }
141 
142         // SAFETY:
143         // `self.interface()?` guarantees self.interface is non-null and points to a valid object
144         // established by `Protocol::new()`.
145         // No parameter is retained, all parameters outlive the call,
146         // and no pointers are Null.
147         unsafe {
148             efi_call!(
149                 @bufsize bufsize,
150                 self.interface()?.run_oem_function,
151                 self.interface,
152                 cmd.as_ptr(),
153                 cmd.len(),
154                 buffer.as_mut_ptr(),
155                 &mut bufsize,
156             )?
157         };
158         Ok(core::cmp::min(bufsize, buffer.iter().position(|c| *c == 0).unwrap_or(buffer.len())))
159     }
160 
161     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.get_policy()`
get_policy(&self) -> Result<GblEfiFastbootPolicy>162     pub fn get_policy(&self) -> Result<GblEfiFastbootPolicy> {
163         let mut policy: GblEfiFastbootPolicy = Default::default();
164 
165         // SAFETY:
166         // `self.interface()?` guarantees self.interface is non-null and points to a valid object
167         // established by `Protocol::new()`.
168         // No parameters are retained, all parameters outlive the call, and no pointers are Null.
169         unsafe { efi_call!(self.interface()?.get_policy, self.interface, &mut policy)? };
170 
171         Ok(policy)
172     }
173 
174     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.set_lock()`
set_lock(&self, flags: u64) -> Result<()>175     pub fn set_lock(&self, flags: u64) -> Result<()> {
176         // SAFETY:
177         // `self.interface()?` guarantees self.interface is non-null and points to a valid object
178         // established by `Protocol::new()`.
179         // `self.interface` is an input parameter and will not be retained. It outlives the call.
180         unsafe { efi_call!(self.interface()?.set_lock, self.interface, flags) }
181     }
182 
183     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.clear_lock()`
clear_lock(&self, flags: u64) -> Result<()>184     pub fn clear_lock(&self, flags: u64) -> Result<()> {
185         // SAFETY:
186         // `self.interface()?` guarantees self.interface is non-null and points to a valid object
187         // established by `Protocol::new()`.
188         // `self.interface` is an input parameter and will not be retained. It outlives the call.
189         unsafe { efi_call!(self.interface()?.clear_lock, self.interface, flags) }
190     }
191 
192     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.get_partition_permissions()`
get_partition_permissions(&self, part_name: &str) -> Result<u64>193     pub fn get_partition_permissions(&self, part_name: &str) -> Result<u64> {
194         let mut permissions = 0u64;
195 
196         // SAFETY:
197         // `self.interface()?` guarantees self.interface is non-null and points to a valid object
198         // established by `Protocol::new()`.
199         // No parameters are retained, all parameters outlive the call, and no pointers are Null.
200         unsafe {
201             efi_call!(
202                 self.interface()?.get_partition_permissions,
203                 self.interface,
204                 part_name.as_ptr(),
205                 part_name.len(),
206                 &mut permissions
207             )?
208         };
209         Ok(permissions)
210     }
211 
212     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.wipe_user_data()`
wipe_user_data(&self) -> Result<()>213     pub fn wipe_user_data(&self) -> Result<()> {
214         // SAFETY:
215         // `self.interface()?` guarantees self.interface is non-null and points to a valid object
216         // established by `Protocol::new()`.
217         // `self.interface` is an input parameter and will not be retained. It outlives the call.
218         unsafe { efi_call!(self.interface()?.wipe_user_data, self.interface) }
219     }
220 
221     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.serial_number`
serial_number(&self) -> Result<&str>222     pub fn serial_number(&self) -> Result<&str> {
223         let serial_number = &self.interface()?.serial_number;
224         let null_idx = serial_number.iter().position(|c| *c == 0).unwrap_or(serial_number.len());
225         Ok(from_utf8(&serial_number[..null_idx])?)
226     }
227 
228     /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.version`
version(&self) -> Result<u32>229     pub fn version(&self) -> Result<u32> {
230         Ok(self.interface()?.version)
231     }
232 }
233 
234 #[cfg(test)]
235 mod test {
236     use super::*;
237     use crate::{
238         protocol::GetVarAllCallback,
239         test::{generate_protocol, run_test},
240         EfiEntry,
241     };
242     use core::{
243         ffi::{c_void, CStr},
244         slice::from_raw_parts_mut,
245     };
246     use efi_types::{EfiStatus, EFI_STATUS_SUCCESS};
247 
248     #[test]
test_serial_number()249     fn test_serial_number() {
250         run_test(|image_handle, systab_ptr| {
251             // Serial number is shorter than max length and contains non-ASCII unicode.
252             let austria = "Österreich";
253 
254             let mut fb = GblEfiFastbootProtocol { ..Default::default() };
255             fb.serial_number.as_mut_slice()[..austria.len()].copy_from_slice(austria.as_bytes());
256             let efi_entry = EfiEntry { image_handle, systab_ptr };
257 
258             let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb);
259 
260             // Don't include trailing Null terminators.
261             assert_eq!(protocol.serial_number().unwrap().len(), 11);
262             assert_eq!(protocol.serial_number().unwrap(), austria);
263         });
264     }
265 
266     #[test]
test_serial_number_max_length()267     fn test_serial_number_max_length() {
268         run_test(|image_handle, systab_ptr| {
269             let mut fb = GblEfiFastbootProtocol { serial_number: [71u8; 32], ..Default::default() };
270             let efi_entry = EfiEntry { image_handle, systab_ptr };
271 
272             let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb);
273 
274             assert_eq!(protocol.serial_number().unwrap().len(), 32);
275             assert_eq!(protocol.serial_number().unwrap(), "GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG");
276         });
277     }
278 
279     #[test]
test_serial_number_invalid_utf8()280     fn test_serial_number_invalid_utf8() {
281         run_test(|image_handle, systab_ptr| {
282             let mut fb = GblEfiFastbootProtocol { serial_number: [0xF8; 32], ..Default::default() };
283             let efi_entry = EfiEntry { image_handle, systab_ptr };
284 
285             let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb);
286 
287             assert_eq!(protocol.serial_number(), Err(Error::InvalidInput));
288         });
289     }
290 
291     #[test]
test_get_var()292     fn test_get_var() {
293         /// # Safety
294         ///
295         /// * Caller must guarantee that `args` points to an array of NULL-terminated strings with
296         ///   size `num_args`.
297         /// * Caller must guarantee that `out` points to a `[u8]`
298         /// * Caller must guarantee that `out_size` points to a `usize`
299         unsafe extern "C" fn get_var_test(
300             _: *mut GblEfiFastbootProtocol,
301             args: *const *const c_char,
302             num_args: usize,
303             out: *mut u8,
304             out_size: *mut usize,
305         ) -> EfiStatus {
306             // SAFETY: By safety requirement of this function, `args` points to an array of
307             // NULL-terminated strings with length `num_args`.
308             let args = unsafe { from_raw_parts(args, num_args) }
309                 .iter()
310                 .map(|v| unsafe { CStr::from_ptr(*v) })
311                 .collect::<Vec<_>>();
312             assert_eq!(args, [c"var", c"arg1", c"arg2"]);
313             // SAFETY: By safety requirement of this function, `out_size` points to a `usize`;
314             let out_size = &mut unsafe { *out_size };
315             // SAFETY: By safety requirement of this function, `out` points to a `[u8]`;
316             let out = unsafe { from_raw_parts_mut(out, *out_size) };
317             out.clone_from_slice(c"val".to_bytes());
318             *out_size = c"val".to_bytes().len();
319             EFI_STATUS_SUCCESS
320         }
321 
322         run_test(|image_handle, systab_ptr| {
323             let mut fb =
324                 GblEfiFastbootProtocol { get_var: Some(get_var_test), ..Default::default() };
325             let efi_entry = EfiEntry { image_handle, systab_ptr };
326             let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb);
327             let mut out = [0u8; 3];
328             let args = [c"arg1", c"arg2"];
329             assert_eq!(protocol.get_var(c"var", args.iter().copied(), &mut out[..]), Ok(3));
330             assert_eq!(&out, b"val");
331         });
332     }
333 
334     #[test]
test_get_var_all()335     fn test_get_var_all() {
336         /// # Safety
337         ///
338         /// * Caller must guarantee that `ctx` points to data needed by function pointer `cb`.
339         unsafe extern "C" fn test_get_var_all(
340             _: *mut GblEfiFastbootProtocol,
341             ctx: *mut c_void,
342             cb: GetVarAllCallback,
343         ) -> EfiStatus {
344             for (args, val) in [
345                 ([c"foo", c"foo_arg1", c"foo_arg2"], c"foo_val"),
346                 ([c"bar", c"bar_arg1", c"bar_arg2"], c"bar_val"),
347             ] {
348                 let args = args.map(|v| v.as_ptr());
349                 // SAFETY:
350                 // * `args` is an array of NULL-terminated strings. `val` is a NULL-terminated
351                 //   string.
352                 // * By safety requirement of this function, `ctx` points to a valid type of data
353                 //   needed by `cb`.
354                 unsafe { (cb.unwrap())(ctx, args.as_ptr(), args.len(), val.as_ptr()) };
355             }
356             EFI_STATUS_SUCCESS
357         }
358         run_test(|image_handle, systab_ptr| {
359             let mut fb = GblEfiFastbootProtocol {
360                 get_var_all: Some(test_get_var_all),
361                 ..Default::default()
362             };
363             let efi_entry = EfiEntry { image_handle, systab_ptr };
364             let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb);
365             let mut out = vec![];
366             protocol
367                 .get_var_all(|args, val| {
368                     let args_str =
369                         args.iter().map(|v| v.to_str().unwrap()).collect::<Vec<_>>().join(":");
370                     out.push(format!("{args_str}: {}", val.to_str().unwrap()))
371                 })
372                 .unwrap();
373             assert_eq!(out, ["foo:foo_arg1:foo_arg2: foo_val", "bar:bar_arg1:bar_arg2: bar_val",])
374         });
375     }
376 
377     #[test]
test_get_var_all_exceeds_max_arguments()378     fn test_get_var_all_exceeds_max_arguments() {
379         /// # Safety
380         ///
381         /// * Caller must guarantee that `ctx` points to data needed by function pointer `cb`.
382         unsafe extern "C" fn test_get_var_all(
383             _: *mut GblEfiFastbootProtocol,
384             ctx: *mut c_void,
385             cb: GetVarAllCallback,
386         ) -> EfiStatus {
387             let args = [c"".as_ptr(); MAX_ARGS + 1];
388             // SAFETY:
389             // * `args` is an array of NULL-terminated strings. `val` is a NULL-terminated
390             //   string.
391             // * By safety requirement of this function, `ctx` points to a valid type of data
392             //   needed by `cb`.
393             unsafe { (cb.unwrap())(ctx, args.as_ptr(), args.len(), c"".as_ptr()) };
394             EFI_STATUS_SUCCESS
395         }
396         run_test(|image_handle, systab_ptr| {
397             let mut fb = GblEfiFastbootProtocol {
398                 get_var_all: Some(test_get_var_all),
399                 ..Default::default()
400             };
401             let efi_entry = EfiEntry { image_handle, systab_ptr };
402             let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb);
403             let mut out = vec![];
404             protocol
405                 .get_var_all(|args, val| {
406                     let args_str =
407                         args.iter().map(|v| v.to_str().unwrap()).collect::<Vec<_>>().join(":");
408                     out.push(format!("{args_str}: {}", val.to_str().unwrap()))
409                 })
410                 .unwrap();
411             assert_eq!(out, ["<Number of arguments exceeds limit>: "])
412         });
413     }
414 }
415