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, null_mut}, 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 /// Wrapper type for context parameter used in a fastboot local session. 46 pub struct LocalSessionContext(*mut c_void); 47 48 impl Protocol<'_, GblFastbootProtocol> { 49 /// 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>50 pub fn get_var<'a>( 51 &self, 52 var: &CStr, 53 args: impl Iterator<Item = &'a CStr> + Clone, 54 out: &mut [u8], 55 ) -> Result<usize> { 56 let mut args_arr = [null(); MAX_ARGS]; 57 let num_args = safemath::SafeNum::from(1) + args.clone().count(); 58 let args_arr = args_arr.get_mut(..num_args.try_into()?).ok_or(Error::InvalidInput)?; 59 args_arr[0] = var.as_ptr(); 60 args_arr[1..].iter_mut().zip(args).for_each(|(l, r)| *l = r.as_ptr()); 61 let mut bufsize = out.len(); 62 // SAFETY: 63 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 64 // established by `Protocol::new()`. 65 // No parameters are retained, all parameters outlive the call, and no pointers are Null. 66 unsafe { 67 efi_call!( 68 @bufsize bufsize, 69 self.interface()?.get_var, 70 self.interface, 71 args_arr.as_ptr(), 72 args_arr.len(), 73 out.as_mut_ptr(), 74 &mut bufsize 75 )? 76 }; 77 Ok(bufsize) 78 } 79 80 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.get_var_all` get_var_all(&self, mut cb: impl FnMut(&[&CStr], &CStr)) -> Result<()>81 pub fn get_var_all(&self, mut cb: impl FnMut(&[&CStr], &CStr)) -> Result<()> { 82 struct Callback<'a>(&'a mut dyn FnMut(&[&CStr], &CStr)); 83 84 /// Callback C function to be passed to the `get_var_all` function. 85 /// 86 /// # Safety 87 /// 88 /// * Caller must guarantee that `ctx` points to a valid instance of `Callback`, outlives 89 /// the call, and not being referenced elsewhere. 90 /// * Caller must guarantee that `args` points to an array of NULL-terminated strings with 91 /// size `len` and outlives the call. 92 /// * Caller must guarantee that `val` points to valid NULL-terminated strings and outlives 93 /// the call. 94 unsafe extern "C" fn get_var_all_cb( 95 ctx: *mut c_void, 96 args: *const *const c_char, 97 len: usize, 98 val: *const c_char, 99 ) { 100 // SAFETY: By safety requirement of this function, `args` points to an array of 101 // NULL-terminated strings of length `len`. 102 let args = 103 unsafe { from_raw_parts(args, len) }.iter().map(|v| unsafe { CStr::from_ptr(*v) }); 104 // SAFETY: By requirement of this function, `ctx` points to a `Callback`. 105 let cb = unsafe { (ctx as *mut Callback).as_mut() }.unwrap(); 106 // Checks number of arguments and stores them in an array. 107 let mut args_arr = [c""; MAX_ARGS]; 108 match args_arr.get_mut(..len) { 109 Some(v) => { 110 v.iter_mut().zip(args).for_each(|(l, r)| *l = r); 111 // SAFETY: By safety requirement of this function `val` points to a 112 // NULL-terminated string. 113 (cb.0)(&v, unsafe { CStr::from_ptr(val) }) 114 } 115 _ => (cb.0)(&[c"<Number of arguments exceeds limit>"], c""), 116 } 117 } 118 119 // SAFETY: 120 // *`self.interface()?` guarantees self.interface is non-null and points to a valid object 121 // * established by `Protocol::new()`. 122 // * The `ctx` parameter is a valid `Callback` object, outlives the call and not being 123 // referenced elsewhere(declared inline at the parameter site). 124 // * By UEFI interface requirement, vendor firmware passes array of C strings to 125 // `get_var_all_cb` that remains valid for the call. 126 unsafe { 127 efi_call!( 128 self.interface()?.get_var_all, 129 self.interface, 130 &mut Callback(&mut cb) as *mut _ as _, 131 Some(get_var_all_cb), 132 )? 133 }; 134 135 Ok(()) 136 } 137 138 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.run_oem_function()` run_oem_function(&self, cmd: &str, buffer: &mut [u8]) -> Result<usize>139 pub fn run_oem_function(&self, cmd: &str, buffer: &mut [u8]) -> Result<usize> { 140 let mut bufsize = buffer.len(); 141 if !buffer.is_empty() { 142 buffer[0] = 0; 143 } 144 145 // SAFETY: 146 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 147 // established by `Protocol::new()`. 148 // No parameter is retained, all parameters outlive the call, 149 // and no pointers are Null. 150 unsafe { 151 efi_call!( 152 @bufsize bufsize, 153 self.interface()?.run_oem_function, 154 self.interface, 155 cmd.as_ptr(), 156 cmd.len(), 157 buffer.as_mut_ptr(), 158 &mut bufsize, 159 )? 160 }; 161 Ok(core::cmp::min(bufsize, buffer.iter().position(|c| *c == 0).unwrap_or(buffer.len()))) 162 } 163 164 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.get_policy()` get_policy(&self) -> Result<GblEfiFastbootPolicy>165 pub fn get_policy(&self) -> Result<GblEfiFastbootPolicy> { 166 let mut policy: GblEfiFastbootPolicy = Default::default(); 167 168 // SAFETY: 169 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 170 // established by `Protocol::new()`. 171 // No parameters are retained, all parameters outlive the call, and no pointers are Null. 172 unsafe { efi_call!(self.interface()?.get_policy, self.interface, &mut policy)? }; 173 174 Ok(policy) 175 } 176 177 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.set_lock()` set_lock(&self, flags: u64) -> Result<()>178 pub fn set_lock(&self, flags: u64) -> Result<()> { 179 // SAFETY: 180 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 181 // established by `Protocol::new()`. 182 // `self.interface` is an input parameter and will not be retained. It outlives the call. 183 unsafe { efi_call!(self.interface()?.set_lock, self.interface, flags) } 184 } 185 186 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.clear_lock()` clear_lock(&self, flags: u64) -> Result<()>187 pub fn clear_lock(&self, flags: u64) -> Result<()> { 188 // SAFETY: 189 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 190 // established by `Protocol::new()`. 191 // `self.interface` is an input parameter and will not be retained. It outlives the call. 192 unsafe { efi_call!(self.interface()?.clear_lock, self.interface, flags) } 193 } 194 195 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.get_partition_permissions()` get_partition_permissions(&self, part_name: &str) -> Result<u64>196 pub fn get_partition_permissions(&self, part_name: &str) -> Result<u64> { 197 let mut permissions = 0u64; 198 199 // SAFETY: 200 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 201 // established by `Protocol::new()`. 202 // No parameters are retained, all parameters outlive the call, and no pointers are Null. 203 unsafe { 204 efi_call!( 205 self.interface()?.get_partition_permissions, 206 self.interface, 207 part_name.as_ptr(), 208 part_name.len(), 209 &mut permissions 210 )? 211 }; 212 Ok(permissions) 213 } 214 215 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.start_local_session()` start_local_session(&self) -> Result<LocalSessionContext>216 pub fn start_local_session(&self) -> Result<LocalSessionContext> { 217 let mut ctx = null_mut(); 218 // SAFETY: 219 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 220 // established by `Protocol::new()`. 221 // No parameters are retained, all parameters outlive the call, and no pointers are Null. 222 unsafe { efi_call!(self.interface()?.start_local_session, self.interface, &mut ctx)? }; 223 Ok(LocalSessionContext(ctx)) 224 } 225 226 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.update_local_session()` update_local_session(&self, ctx: &LocalSessionContext, out: &mut [u8]) -> Result<usize>227 pub fn update_local_session(&self, ctx: &LocalSessionContext, out: &mut [u8]) -> Result<usize> { 228 let mut bufsize = out.len(); 229 230 // SAFETY: 231 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 232 // established by `Protocol::new()`. 233 // No parameters are retained, all parameters outlive the call, and no pointers are Null. 234 unsafe { 235 efi_call!( 236 @bufsize bufsize, 237 self.interface()?.update_local_session, 238 self.interface, 239 ctx.0, out.as_mut_ptr(), 240 &mut bufsize)? 241 }; 242 Ok(bufsize) 243 } 244 245 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.close_local_session()` close_local_session(&self, ctx: &LocalSessionContext) -> Result<()>246 pub fn close_local_session(&self, ctx: &LocalSessionContext) -> Result<()> { 247 // SAFETY: 248 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 249 // established by `Protocol::new()`. 250 // No parameters are retained, all parameters outlive the call, and no pointers are Null. 251 unsafe { efi_call!(self.interface()?.close_local_session, self.interface, ctx.0) } 252 } 253 254 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.wipe_user_data()` wipe_user_data(&self) -> Result<()>255 pub fn wipe_user_data(&self) -> Result<()> { 256 // SAFETY: 257 // `self.interface()?` guarantees self.interface is non-null and points to a valid object 258 // established by `Protocol::new()`. 259 // `self.interface` is an input parameter and will not be retained. It outlives the call. 260 unsafe { efi_call!(self.interface()?.wipe_user_data, self.interface) } 261 } 262 263 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.should_stop_in_fastboot()` should_stop_in_fastboot(&self) -> bool264 pub fn should_stop_in_fastboot(&self) -> bool { 265 let Ok(interface) = self.interface() else { return false }; 266 267 let Some(should_stop_in_fastboot) = interface.should_stop_in_fastboot else { return false }; 268 // SAFETY: 269 // `self.interface` is non-null due to check above. 270 // `self.interface` is an input parameter and will not be retained. It outlives the call. 271 // `should_stop_in_fastboot` is non-null due to check above. 272 // `should_stop_in_fastboot` is responsible for validating its input. 273 unsafe { should_stop_in_fastboot(self.interface) } 274 } 275 276 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.serial_number` serial_number(&self) -> Result<&str>277 pub fn serial_number(&self) -> Result<&str> { 278 let serial_number = &self.interface()?.serial_number; 279 let null_idx = serial_number.iter().position(|c| *c == 0).unwrap_or(serial_number.len()); 280 Ok(from_utf8(&serial_number[..null_idx])?) 281 } 282 283 /// Wrapper of `GBL_EFI_FASTBOOT_PROTOCOL.version` version(&self) -> Result<u32>284 pub fn version(&self) -> Result<u32> { 285 Ok(self.interface()?.version) 286 } 287 } 288 289 #[cfg(test)] 290 mod test { 291 use super::*; 292 use crate::{ 293 protocol::GetVarAllCallback, 294 test::{generate_protocol, run_test}, 295 DeviceHandle, EfiEntry, 296 }; 297 use core::{ 298 ffi::{c_void, CStr}, 299 ptr::null_mut, 300 slice::from_raw_parts_mut, 301 }; 302 use efi_types::{EfiStatus, EFI_STATUS_SUCCESS}; 303 304 #[test] test_serial_number()305 fn test_serial_number() { 306 run_test(|image_handle, systab_ptr| { 307 // Serial number is shorter than max length and contains non-ASCII unicode. 308 let austria = "Österreich"; 309 310 let mut fb = GblEfiFastbootProtocol { ..Default::default() }; 311 fb.serial_number.as_mut_slice()[..austria.len()].copy_from_slice(austria.as_bytes()); 312 let efi_entry = EfiEntry { image_handle, systab_ptr }; 313 314 let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb); 315 316 // Don't include trailing Null terminators. 317 assert_eq!(protocol.serial_number().unwrap().len(), 11); 318 assert_eq!(protocol.serial_number().unwrap(), austria); 319 }); 320 } 321 322 #[test] test_serial_number_max_length()323 fn test_serial_number_max_length() { 324 run_test(|image_handle, systab_ptr| { 325 let mut fb = GblEfiFastbootProtocol { serial_number: [71u8; 32], ..Default::default() }; 326 let efi_entry = EfiEntry { image_handle, systab_ptr }; 327 328 let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb); 329 330 assert_eq!(protocol.serial_number().unwrap().len(), 32); 331 assert_eq!(protocol.serial_number().unwrap(), "GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"); 332 }); 333 } 334 335 #[test] test_serial_number_invalid_utf8()336 fn test_serial_number_invalid_utf8() { 337 run_test(|image_handle, systab_ptr| { 338 let mut fb = GblEfiFastbootProtocol { serial_number: [0xF8; 32], ..Default::default() }; 339 let efi_entry = EfiEntry { image_handle, systab_ptr }; 340 341 let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb); 342 343 assert_eq!(protocol.serial_number(), Err(Error::InvalidInput)); 344 }); 345 } 346 347 #[test] test_get_var()348 fn test_get_var() { 349 /// # Safety 350 /// 351 /// * Caller must guarantee that `args` points to an array of NULL-terminated strings with 352 /// size `num_args`. 353 /// * Caller must guarantee that `out` points to a `[u8]` 354 /// * Caller must guarantee that `out_size` points to a `usize` 355 unsafe extern "C" fn get_var_test( 356 _: *mut GblEfiFastbootProtocol, 357 args: *const *const c_char, 358 num_args: usize, 359 out: *mut u8, 360 out_size: *mut usize, 361 ) -> EfiStatus { 362 // SAFETY: By safety requirement of this function, `args` points to an array of 363 // NULL-terminated strings with length `num_args`. 364 let args = unsafe { from_raw_parts(args, num_args) } 365 .iter() 366 .map(|v| unsafe { CStr::from_ptr(*v) }) 367 .collect::<Vec<_>>(); 368 assert_eq!(args, [c"var", c"arg1", c"arg2"]); 369 // SAFETY: By safety requirement of this function, `out_size` points to a `usize`; 370 let out_size = &mut unsafe { *out_size }; 371 // SAFETY: By safety requirement of this function, `out` points to a `[u8]`; 372 let out = unsafe { from_raw_parts_mut(out, *out_size) }; 373 out.clone_from_slice(c"val".to_bytes()); 374 *out_size = c"val".to_bytes().len(); 375 EFI_STATUS_SUCCESS 376 } 377 378 run_test(|image_handle, systab_ptr| { 379 let mut fb = 380 GblEfiFastbootProtocol { get_var: Some(get_var_test), ..Default::default() }; 381 let efi_entry = EfiEntry { image_handle, systab_ptr }; 382 let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb); 383 let mut out = [0u8; 3]; 384 let args = [c"arg1", c"arg2"]; 385 assert_eq!(protocol.get_var(c"var", args.iter().copied(), &mut out[..]), Ok(3)); 386 assert_eq!(&out, b"val"); 387 }); 388 } 389 390 #[test] test_get_var_all()391 fn test_get_var_all() { 392 /// # Safety 393 /// 394 /// * Caller must guarantee that `ctx` points to data needed by function pointer `cb`. 395 unsafe extern "C" fn test_get_var_all( 396 _: *mut GblEfiFastbootProtocol, 397 ctx: *mut c_void, 398 cb: GetVarAllCallback, 399 ) -> EfiStatus { 400 for (args, val) in [ 401 ([c"foo", c"foo_arg1", c"foo_arg2"], c"foo_val"), 402 ([c"bar", c"bar_arg1", c"bar_arg2"], c"bar_val"), 403 ] { 404 let args = args.map(|v| v.as_ptr()); 405 // SAFETY: 406 // * `args` is an array of NULL-terminated strings. `val` is a NULL-terminated 407 // string. 408 // * By safety requirement of this function, `ctx` points to a valid type of data 409 // needed by `cb`. 410 unsafe { (cb.unwrap())(ctx, args.as_ptr(), args.len(), val.as_ptr()) }; 411 } 412 EFI_STATUS_SUCCESS 413 } 414 run_test(|image_handle, systab_ptr| { 415 let mut fb = GblEfiFastbootProtocol { 416 get_var_all: Some(test_get_var_all), 417 ..Default::default() 418 }; 419 let efi_entry = EfiEntry { image_handle, systab_ptr }; 420 let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb); 421 let mut out = vec![]; 422 protocol 423 .get_var_all(|args, val| { 424 let args_str = 425 args.iter().map(|v| v.to_str().unwrap()).collect::<Vec<_>>().join(":"); 426 out.push(format!("{args_str}: {}", val.to_str().unwrap())) 427 }) 428 .unwrap(); 429 assert_eq!(out, ["foo:foo_arg1:foo_arg2: foo_val", "bar:bar_arg1:bar_arg2: bar_val",]) 430 }); 431 } 432 433 #[test] test_get_var_all_exceeds_max_arguments()434 fn test_get_var_all_exceeds_max_arguments() { 435 /// # Safety 436 /// 437 /// * Caller must guarantee that `ctx` points to data needed by function pointer `cb`. 438 unsafe extern "C" fn test_get_var_all( 439 _: *mut GblEfiFastbootProtocol, 440 ctx: *mut c_void, 441 cb: GetVarAllCallback, 442 ) -> EfiStatus { 443 let args = [c"".as_ptr(); MAX_ARGS + 1]; 444 // SAFETY: 445 // * `args` is an array of NULL-terminated strings. `val` is a NULL-terminated 446 // string. 447 // * By safety requirement of this function, `ctx` points to a valid type of data 448 // needed by `cb`. 449 unsafe { (cb.unwrap())(ctx, args.as_ptr(), args.len(), c"".as_ptr()) }; 450 EFI_STATUS_SUCCESS 451 } 452 run_test(|image_handle, systab_ptr| { 453 let mut fb = GblEfiFastbootProtocol { 454 get_var_all: Some(test_get_var_all), 455 ..Default::default() 456 }; 457 let efi_entry = EfiEntry { image_handle, systab_ptr }; 458 let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb); 459 let mut out = vec![]; 460 protocol 461 .get_var_all(|args, val| { 462 let args_str = 463 args.iter().map(|v| v.to_str().unwrap()).collect::<Vec<_>>().join(":"); 464 out.push(format!("{args_str}: {}", val.to_str().unwrap())) 465 }) 466 .unwrap(); 467 assert_eq!(out, ["<Number of arguments exceeds limit>: "]) 468 }); 469 } 470 471 #[test] test_should_stop_in_fastboot()472 fn test_should_stop_in_fastboot() { 473 unsafe extern "C" fn test_should_stop_in_fastboot(_: *mut GblEfiFastbootProtocol) -> bool { 474 true 475 } 476 run_test(|image_handle, systab_ptr| { 477 let mut fb = GblEfiFastbootProtocol { 478 should_stop_in_fastboot: Some(test_should_stop_in_fastboot), 479 ..Default::default() 480 }; 481 let efi_entry = EfiEntry { image_handle, systab_ptr }; 482 let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb); 483 assert!(protocol.should_stop_in_fastboot()); 484 }); 485 } 486 487 #[test] test_should_stop_in_fastboot_no_interface()488 fn test_should_stop_in_fastboot_no_interface() { 489 run_test(|image_handle, systab_ptr| { 490 let efi_entry = EfiEntry { image_handle, systab_ptr }; 491 // SAFETY: `protocol.interface` is explicitly null for testing. 492 let protocol = unsafe { 493 Protocol::<GblFastbootProtocol>::new( 494 DeviceHandle::new(null_mut()), 495 null_mut(), 496 &efi_entry, 497 ) 498 }; 499 assert!(!protocol.should_stop_in_fastboot()); 500 }); 501 } 502 503 #[test] test_should_stop_in_fastboot_no_method()504 fn test_should_stop_in_fastboot_no_method() { 505 run_test(|image_handle, systab_ptr| { 506 let mut fb: GblEfiFastbootProtocol = Default::default(); 507 let efi_entry = EfiEntry { image_handle, systab_ptr }; 508 let protocol = generate_protocol::<GblFastbootProtocol>(&efi_entry, &mut fb); 509 assert!(!protocol.should_stop_in_fastboot()); 510 }); 511 } 512 } 513