xref: /aosp_15_r20/bootable/libbootloader/gbl/libefi/src/protocol/gbl_efi_os_configuration.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_OS_CONFIGURATION_PROTOCOL`.
16 
17 use crate::efi_call;
18 use crate::protocol::{Protocol, ProtocolInfo};
19 use core::ffi::CStr;
20 use efi_types::{EfiGuid, GblEfiOsConfigurationProtocol, GblEfiVerifiedDeviceTree};
21 use liberror::{Error, Result};
22 
23 /// `GBL_EFI_OS_CONFIGURATION_PROTOCOL` implementation.
24 pub struct GblOsConfigurationProtocol;
25 
26 impl ProtocolInfo for GblOsConfigurationProtocol {
27     type InterfaceType = GblEfiOsConfigurationProtocol;
28 
29     const GUID: EfiGuid =
30         EfiGuid::new(0xdda0d135, 0xaa5b, 0x42ff, [0x85, 0xac, 0xe3, 0xad, 0x6e, 0xfb, 0x46, 0x19]);
31 }
32 
33 // Protocol interface wrappers.
34 impl Protocol<'_, GblOsConfigurationProtocol> {
35     /// Wraps `GBL_EFI_OS_CONFIGURATION_PROTOCOL.fixup_kernel_commandline()`.
fixup_kernel_commandline(&self, commandline: &CStr, fixup: &mut [u8]) -> Result<()>36     pub fn fixup_kernel_commandline(&self, commandline: &CStr, fixup: &mut [u8]) -> Result<()> {
37         if fixup.is_empty() {
38             return Err(Error::InvalidInput);
39         }
40 
41         let mut fixup_size = fixup.len();
42         fixup[0] = 0;
43         // SAFETY:
44         // * `self.interface()?` guarantees self.interface is non-null and points to a valid object
45         //   established by `Protocol::new()`.
46         // * `commandline` is a valid pointer to null-terminated string used only within the call.
47         // * `fixup` is non-null buffer available for write, used only within the call.
48         // * `fixup_size` is non-null buffer available for write, used only within the call.
49         unsafe {
50             efi_call!(
51                 @bufsize fixup_size,
52                 self.interface()?.fixup_kernel_commandline,
53                 self.interface,
54                 commandline.as_ptr() as _,
55                 fixup.as_mut_ptr(),
56                 &mut fixup_size
57             )?;
58         }
59 
60         Ok(())
61     }
62 
63     /// Wraps `GBL_EFI_OS_CONFIGURATION_PROTOCOL.fixup_bootconfig()`.
fixup_bootconfig(&self, bootconfig: &[u8], fixup: &mut [u8]) -> Result<usize>64     pub fn fixup_bootconfig(&self, bootconfig: &[u8], fixup: &mut [u8]) -> Result<usize> {
65         if fixup.is_empty() {
66             return Err(Error::InvalidInput);
67         }
68 
69         let mut fixup_size = fixup.len();
70         // SAFETY:
71         // * `self.interface()?` guarantees self.interface is non-null and points to a valid object
72         //   established by `Protocol::new()`.
73         // * `bootconfig` is non-null buffer used only within the call.
74         // * `fixup` is non-null buffer available for write, used only within the call.
75         // * `fixup_size` is non-null usize buffer available for write, used only within the call.
76         unsafe {
77             efi_call!(
78                 @bufsize fixup_size,
79                 self.interface()?.fixup_bootconfig,
80                 self.interface,
81                 bootconfig.as_ptr(),
82                 bootconfig.len(),
83                 fixup.as_mut_ptr(),
84                 &mut fixup_size
85             )?;
86         }
87 
88         Ok(fixup_size)
89     }
90 
91     /// Wraps `GBL_EFI_OS_CONFIGURATION_PROTOCOL.select_device_trees()`.
select_device_trees(&self, components: &mut [GblEfiVerifiedDeviceTree]) -> Result<()>92     pub fn select_device_trees(&self, components: &mut [GblEfiVerifiedDeviceTree]) -> Result<()> {
93         // SAFETY:
94         // * `self.interface()?` guarantees self.interface is non-null and points to a valid object
95         //   established by `Protocol::new()`.
96         // * `components` is non-null buffer available for write, used only within the call.
97         // * `components_len` is non-null usize buffer, used only within the call.
98         unsafe {
99             efi_call!(
100                 self.interface()?.select_device_trees,
101                 self.interface,
102                 components.as_mut_ptr() as _,
103                 components.len(),
104             )?;
105         }
106 
107         Ok(())
108     }
109 }
110 
111 #[cfg(test)]
112 mod test {
113     use super::*;
114 
115     use crate::test::run_test_with_mock_protocol;
116     use efi_types::{
117         EfiStatus, EFI_STATUS_BUFFER_TOO_SMALL, EFI_STATUS_INVALID_PARAMETER, EFI_STATUS_SUCCESS,
118     };
119     use std::{ffi::CStr, slice};
120 
121     #[test]
fixup_kernel_commandline_no_op()122     fn fixup_kernel_commandline_no_op() {
123         // No-op C callback implementation.
124         unsafe extern "C" fn c_return_success(
125             _: *mut GblEfiOsConfigurationProtocol,
126             _: *const u8,
127             _: *mut u8,
128             _: *mut usize,
129         ) -> EfiStatus {
130             EFI_STATUS_SUCCESS
131         }
132 
133         let c_interface = GblEfiOsConfigurationProtocol {
134             fixup_kernel_commandline: Some(c_return_success),
135             ..Default::default()
136         };
137 
138         run_test_with_mock_protocol(c_interface, |os_config_protocol| {
139             let mut fixup_buffer = [0x0; 128];
140             let commandline = c"foo=bar baz";
141 
142             assert!(os_config_protocol
143                 .fixup_kernel_commandline(commandline, &mut fixup_buffer)
144                 .is_ok());
145             assert_eq!(
146                 CStr::from_bytes_until_nul(&fixup_buffer[..]).unwrap().to_str().unwrap(),
147                 ""
148             );
149         });
150     }
151 
152     #[test]
fixup_kernel_commandline_provided()153     fn fixup_kernel_commandline_provided() {
154         const EXPECTED_COMMANDLINE: &CStr = c"a=b";
155         const EXPECTED_FIXUP: &[u8] = b"hello=world\0";
156         const EXPECTED_FIXUP_STR: &str = "hello=world";
157 
158         // C callback implementation to add "hello=world" to the given command line.
159         unsafe extern "C" fn c_add_hello_world(
160             _: *mut GblEfiOsConfigurationProtocol,
161             command_line: *const u8,
162             fixup: *mut u8,
163             _: *mut usize,
164         ) -> EfiStatus {
165             assert_eq!(
166                 // SAFETY:
167                 // * `command_line` is valid pointer to null terminated string.
168                 unsafe { CStr::from_ptr(command_line as _) },
169                 EXPECTED_COMMANDLINE
170             );
171 
172             // SAFETY:
173             // * `fixup` is valid writtable buffer with enough space for test data.
174             let fixup_buffer = unsafe { slice::from_raw_parts_mut(fixup, EXPECTED_FIXUP.len()) };
175             fixup_buffer.copy_from_slice(EXPECTED_FIXUP);
176 
177             EFI_STATUS_SUCCESS
178         }
179 
180         let c_interface = GblEfiOsConfigurationProtocol {
181             fixup_kernel_commandline: Some(c_add_hello_world),
182             ..Default::default()
183         };
184 
185         run_test_with_mock_protocol(c_interface, |os_config_protocol| {
186             let mut fixup_buffer = [0x0; 128];
187 
188             assert!(os_config_protocol
189                 .fixup_kernel_commandline(EXPECTED_COMMANDLINE, &mut fixup_buffer)
190                 .is_ok());
191             assert_eq!(
192                 CStr::from_bytes_until_nul(&fixup_buffer[..]).unwrap().to_str().unwrap(),
193                 EXPECTED_FIXUP_STR,
194             );
195         });
196     }
197 
198     #[test]
fixup_kernel_commandline_error()199     fn fixup_kernel_commandline_error() {
200         // C callback implementation to return an error.
201         unsafe extern "C" fn c_error(
202             _: *mut GblEfiOsConfigurationProtocol,
203             _: *const u8,
204             _: *mut u8,
205             _: *mut usize,
206         ) -> EfiStatus {
207             EFI_STATUS_INVALID_PARAMETER
208         }
209 
210         let c_interface = GblEfiOsConfigurationProtocol {
211             fixup_kernel_commandline: Some(c_error),
212             ..Default::default()
213         };
214 
215         run_test_with_mock_protocol(c_interface, |os_config_protocol| {
216             let mut fixup_buffer = [0x0; 128];
217             let commandline = c"foo=bar baz";
218 
219             assert_eq!(
220                 os_config_protocol.fixup_kernel_commandline(commandline, &mut fixup_buffer),
221                 Err(Error::InvalidInput),
222             );
223         });
224     }
225 
226     #[test]
fixup_kernel_commandline_buffer_too_small()227     fn fixup_kernel_commandline_buffer_too_small() {
228         const EXPECTED_REQUESTED_FIXUP_SIZE: usize = 256;
229         // C callback implementation to return an error.
230         unsafe extern "C" fn c_error(
231             _: *mut GblEfiOsConfigurationProtocol,
232             _: *const u8,
233             _: *mut u8,
234             fixup_size: *mut usize,
235         ) -> EfiStatus {
236             // SAFETY:
237             // * `fixup_size` is a valid pointer to writtable usize buffer.
238             unsafe {
239                 *fixup_size = EXPECTED_REQUESTED_FIXUP_SIZE;
240             }
241             EFI_STATUS_BUFFER_TOO_SMALL
242         }
243 
244         let c_interface = GblEfiOsConfigurationProtocol {
245             fixup_kernel_commandline: Some(c_error),
246             ..Default::default()
247         };
248 
249         run_test_with_mock_protocol(c_interface, |os_config_protocol| {
250             let mut fixup_buffer = [0x0; 128];
251             let commandline = c"foo=bar baz";
252 
253             assert_eq!(
254                 os_config_protocol.fixup_kernel_commandline(commandline, &mut fixup_buffer),
255                 Err(Error::BufferTooSmall(Some(EXPECTED_REQUESTED_FIXUP_SIZE))),
256             );
257         });
258     }
259 
260     #[test]
fixup_bootconfig_no_op()261     fn fixup_bootconfig_no_op() {
262         // No-op C callback implementation.
263         unsafe extern "C" fn c_return_success(
264             _: *mut GblEfiOsConfigurationProtocol,
265             _: *const u8,
266             _: usize,
267             _: *mut u8,
268             fixup_size: *mut usize,
269         ) -> EfiStatus {
270             // SAFETY:
271             // * `fixup_size` is a valid pointer to writtable usize buffer.
272             unsafe {
273                 *fixup_size = 0;
274             }
275             EFI_STATUS_SUCCESS
276         }
277 
278         let c_interface = GblEfiOsConfigurationProtocol {
279             fixup_bootconfig: Some(c_return_success),
280             ..Default::default()
281         };
282 
283         run_test_with_mock_protocol(c_interface, |os_config_protocol| {
284             let mut fixup_buffer = [0x0; 128];
285             let bootconfig = c"foo=bar\nbaz".to_bytes_with_nul();
286 
287             assert_eq!(
288                 os_config_protocol.fixup_bootconfig(&bootconfig[..], &mut fixup_buffer),
289                 Ok(0)
290             );
291         });
292     }
293 
294     #[test]
fixup_bootconfig_provided()295     fn fixup_bootconfig_provided() {
296         // no trailer for simplicity
297         const EXPECTED_BOOTCONFIG: &[u8] = b"a=b\nc=d\n";
298         const EXPECTED_LEN: usize = 4;
299         const EXPECTED_FIXUP: &[u8] = b"e=f\n";
300 
301         // C callback implementation to add "e=f" to the given bootconfig.
302         unsafe extern "C" fn c_add_ef(
303             _: *mut GblEfiOsConfigurationProtocol,
304             bootconfig: *const u8,
305             bootconfig_size: usize,
306             fixup: *mut u8,
307             fixup_size: *mut usize,
308         ) -> EfiStatus {
309             // SAFETY:
310             // * `bootconfig` is a valid pointer to the buffer at least `bootconfig_size` size.
311             let bootconfig_buffer = unsafe { slice::from_raw_parts(bootconfig, bootconfig_size) };
312 
313             assert_eq!(bootconfig_buffer, EXPECTED_BOOTCONFIG);
314 
315             // SAFETY:
316             // * `fixup` is a valid writtable buffer with enough space for test data.
317             // * `fixup_size` is a valid pointer to writtable usize buffer.
318             let fixup_buffer = unsafe {
319                 *fixup_size = EXPECTED_FIXUP.len();
320                 slice::from_raw_parts_mut(fixup, *fixup_size)
321             };
322             fixup_buffer.copy_from_slice(EXPECTED_FIXUP);
323 
324             EFI_STATUS_SUCCESS
325         }
326 
327         let c_interface = GblEfiOsConfigurationProtocol {
328             fixup_bootconfig: Some(c_add_ef),
329             ..Default::default()
330         };
331 
332         run_test_with_mock_protocol(c_interface, |os_config_protocol| {
333             let mut fixup_buffer = [0x0; 128];
334 
335             assert_eq!(
336                 os_config_protocol.fixup_bootconfig(&EXPECTED_BOOTCONFIG[..], &mut fixup_buffer),
337                 Ok(EXPECTED_LEN),
338             );
339             assert_eq!(&fixup_buffer[..EXPECTED_LEN], &EXPECTED_FIXUP[..],);
340         });
341     }
342 
343     #[test]
fixup_bootconfig_error()344     fn fixup_bootconfig_error() {
345         // C callback implementation to return an error.
346         unsafe extern "C" fn c_error(
347             _: *mut GblEfiOsConfigurationProtocol,
348             _: *const u8,
349             _: usize,
350             _: *mut u8,
351             _: *mut usize,
352         ) -> EfiStatus {
353             EFI_STATUS_INVALID_PARAMETER
354         }
355 
356         let c_interface =
357             GblEfiOsConfigurationProtocol { fixup_bootconfig: Some(c_error), ..Default::default() };
358 
359         run_test_with_mock_protocol(c_interface, |os_config_protocol| {
360             let mut fixup_buffer = [0x0; 128];
361             let bootconfig = c"foo=bar\nbaz".to_bytes_with_nul();
362 
363             assert_eq!(
364                 os_config_protocol.fixup_bootconfig(&bootconfig[..], &mut fixup_buffer),
365                 Err(Error::InvalidInput)
366             );
367         });
368     }
369 
370     #[test]
fixup_bootconfig_fixup_buffer_too_small()371     fn fixup_bootconfig_fixup_buffer_too_small() {
372         const EXPECTED_REQUESTED_FIXUP_SIZE: usize = 256;
373         // C callback implementation to return an error.
374         unsafe extern "C" fn c_error(
375             _: *mut GblEfiOsConfigurationProtocol,
376             _: *const u8,
377             _: usize,
378             _: *mut u8,
379             fixup_size: *mut usize,
380         ) -> EfiStatus {
381             // SAFETY:
382             // * `fixup_size` is a valid pointer to writtable usize buffer.
383             unsafe {
384                 *fixup_size = EXPECTED_REQUESTED_FIXUP_SIZE;
385             }
386             EFI_STATUS_BUFFER_TOO_SMALL
387         }
388 
389         let c_interface =
390             GblEfiOsConfigurationProtocol { fixup_bootconfig: Some(c_error), ..Default::default() };
391 
392         run_test_with_mock_protocol(c_interface, |os_config_protocol| {
393             let mut fixup_buffer = [0x0; 128];
394             let bootconfig = c"foo=bar\nbaz".to_bytes_with_nul();
395 
396             assert_eq!(
397                 os_config_protocol.fixup_bootconfig(&bootconfig[..], &mut fixup_buffer),
398                 Err(Error::BufferTooSmall(Some(EXPECTED_REQUESTED_FIXUP_SIZE))),
399             );
400         });
401     }
402 
403     #[test]
select_device_trees_selected()404     fn select_device_trees_selected() {
405         // C callback implementation to select first component.
406         unsafe extern "C" fn c_select_first(
407             _: *mut GblEfiOsConfigurationProtocol,
408             device_trees: *mut GblEfiVerifiedDeviceTree,
409             num: usize,
410         ) -> EfiStatus {
411             assert_eq!(num, 1);
412 
413             // SAFETY:
414             // * device_trees is non-null buffer available for write.
415             unsafe {
416                 (*device_trees).selected = true;
417             }
418 
419             EFI_STATUS_SUCCESS
420         }
421 
422         let c_interface = GblEfiOsConfigurationProtocol {
423             select_device_trees: Some(c_select_first),
424             ..Default::default()
425         };
426 
427         run_test_with_mock_protocol(c_interface, |os_config_protocol| {
428             let device_trees = &mut [GblEfiVerifiedDeviceTree::default()];
429 
430             assert!(os_config_protocol.select_device_trees(device_trees).is_ok());
431             assert!(device_trees[0].selected);
432         });
433     }
434 }
435