diff --git a/uefi-test-runner/src/runtime/vars.rs b/uefi-test-runner/src/runtime/vars.rs index a66f7f0de..48a3dbc07 100644 --- a/uefi-test-runner/src/runtime/vars.rs +++ b/uefi-test-runner/src/runtime/vars.rs @@ -1,40 +1,46 @@ use log::info; -use uefi::guid; use uefi::prelude::*; use uefi::table::runtime::{VariableAttributes, VariableVendor}; +use uefi::{guid, runtime, CStr16, Error}; -fn test_variables(rt: &RuntimeServices) { - let name = cstr16!("UefiRsTestVar"); - let test_value = b"TestValue"; - let test_attrs = VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS; +/// Test variable name. +const NAME: &CStr16 = cstr16!("UefiRsTestVar"); + +/// Test variable vendor. +const VENDOR: &VariableVendor = &VariableVendor(guid!("9baf21cf-e187-497e-ae77-5bd8b0e09703")); + +/// Test variable value. +const VALUE: &[u8] = b"TestValue"; - // Arbitrary GUID generated for this test. - let vendor = VariableVendor(guid!("9baf21cf-e187-497e-ae77-5bd8b0e09703")); +/// Test variable attributes. +const ATTRS: VariableAttributes = + VariableAttributes::BOOTSERVICE_ACCESS.union(VariableAttributes::RUNTIME_ACCESS); +fn test_variables(rt: &RuntimeServices) { info!("Testing set_variable"); - rt.set_variable(name, &vendor, test_attrs, test_value) + rt.set_variable(NAME, VENDOR, ATTRS, VALUE) .expect("failed to set variable"); info!("Testing get_variable_size"); let size = rt - .get_variable_size(name, &vendor) + .get_variable_size(NAME, VENDOR) .expect("failed to get variable size"); - assert_eq!(size, test_value.len()); + assert_eq!(size, VALUE.len()); info!("Testing get_variable"); let mut buf = [0u8; 9]; let (data, attrs) = rt - .get_variable(name, &vendor, &mut buf) + .get_variable(NAME, VENDOR, &mut buf) .expect("failed to get variable"); - assert_eq!(data, test_value); - assert_eq!(attrs, test_attrs); + assert_eq!(data, VALUE); + assert_eq!(attrs, ATTRS); info!("Testing get_variable_boxed"); let (data, attrs) = rt - .get_variable_boxed(name, &vendor) + .get_variable_boxed(NAME, VENDOR) .expect("failed to get variable"); - assert_eq!(&*data, test_value); - assert_eq!(attrs, test_attrs); + assert_eq!(&*data, VALUE); + assert_eq!(attrs, ATTRS); info!("Testing variable_keys"); let variable_keys = rt.variable_keys().expect("failed to get variable keys"); @@ -46,10 +52,44 @@ fn test_variables(rt: &RuntimeServices) { } info!("Testing delete_variable()"); - rt.delete_variable(name, &vendor) + rt.delete_variable(NAME, VENDOR) .expect("failed to delete variable"); assert_eq!( - rt.get_variable(name, &vendor, &mut buf) + rt.get_variable(NAME, VENDOR, &mut buf) + .unwrap_err() + .status(), + Status::NOT_FOUND + ); +} + +/// Test the variable functions in `uefi::runtime`. +fn test_variables_freestanding() { + // Create the test variable. + runtime::set_variable(NAME, VENDOR, ATTRS, VALUE).expect("failed to set variable"); + + // Test `get_variable` with too small of a buffer. + let mut buf = [0u8; 0]; + assert_eq!( + runtime::get_variable(NAME, VENDOR, &mut buf).unwrap_err(), + Error::new(Status::BUFFER_TOO_SMALL, Some(9)) + ); + + // Test `get_variable`. + let mut buf = [0u8; 9]; + let (data, attrs) = + runtime::get_variable(NAME, VENDOR, &mut buf).expect("failed to get variable"); + assert_eq!(data, VALUE); + assert_eq!(attrs, ATTRS); + + // Test `get_variable_boxed`. + let (data, attrs) = runtime::get_variable_boxed(NAME, VENDOR).expect("failed to get variable"); + assert_eq!(&*data, VALUE); + assert_eq!(attrs, ATTRS); + + // Delete the variable and verify it can no longer be read. + runtime::delete_variable(NAME, VENDOR).expect("failed to delete variable"); + assert_eq!( + runtime::get_variable(NAME, VENDOR, &mut buf) .unwrap_err() .status(), Status::NOT_FOUND @@ -76,4 +116,5 @@ fn test_variable_info(rt: &RuntimeServices) { pub fn test(rt: &RuntimeServices) { test_variables(rt); test_variable_info(rt); + test_variables_freestanding(); } diff --git a/uefi/src/data_types/mod.rs b/uefi/src/data_types/mod.rs index 74239c025..e35beba1a 100644 --- a/uefi/src/data_types/mod.rs +++ b/uefi/src/data_types/mod.rs @@ -137,6 +137,12 @@ pub trait Align { } } +impl Align for [u8] { + fn alignment() -> usize { + 1 + } +} + mod guid; pub use guid::{Guid, Identify}; diff --git a/uefi/src/runtime.rs b/uefi/src/runtime.rs index 56015bc60..0143cf164 100644 --- a/uefi/src/runtime.rs +++ b/uefi/src/runtime.rs @@ -5,11 +5,18 @@ //! functions after exiting boot services; see the "Calling Convention" section //! of the UEFI specification for details. -use crate::table::{self}; -use crate::{Result, StatusExt}; +use crate::{table, CStr16, Error, Result, Status, StatusExt}; use core::ptr::{self, NonNull}; +#[cfg(feature = "alloc")] +use {crate::mem::make_boxed, alloc::boxed::Box}; + +#[cfg(all(feature = "unstable", feature = "alloc"))] +use alloc::alloc::Global; + pub use crate::table::runtime::{Daylight, Time, TimeCapabilities, TimeError, TimeParams}; +pub use uefi_raw::capsule::{CapsuleBlockDescriptor, CapsuleFlags, CapsuleHeader}; +pub use uefi_raw::table::runtime::{ResetType, VariableAttributes, VariableVendor}; fn runtime_services_raw_panicking() -> NonNull { let st = table::system_table_raw_panicking(); @@ -55,3 +62,138 @@ pub unsafe fn set_time(time: &Time) -> Result { let time: *const Time = time; (rt.set_time)(time.cast()).to_result() } + +/// Gets the contents and attributes of a variable. The size of `buf` must be at +/// least as big as the variable's size, although it can be larger. +/// +/// On success, returns a tuple containing the variable's value (a slice of +/// `buf`) and the variable's attributes. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: variable was not found. +/// * [`Status::BUFFER_TOO_SMALL`]: `buf` is not large enough. The required size +/// will be returned in the error data. +/// * [`Status::DEVICE_ERROR`]: variable could not be read due to a hardware error. +/// * [`Status::SECURITY_VIOLATION`]: variable could not be read due to an +/// authentication error. +/// * [`Status::UNSUPPORTED`]: this platform does not support variable storage +/// after exiting boot services. +pub fn get_variable<'buf>( + name: &CStr16, + vendor: &VariableVendor, + buf: &'buf mut [u8], +) -> Result<(&'buf mut [u8], VariableAttributes), Option> { + let rt = runtime_services_raw_panicking(); + let rt = unsafe { rt.as_ref() }; + + let mut attributes = VariableAttributes::empty(); + let mut data_size = buf.len(); + let status = unsafe { + (rt.get_variable)( + name.as_ptr().cast(), + &vendor.0, + &mut attributes, + &mut data_size, + buf.as_mut_ptr(), + ) + }; + + match status { + Status::SUCCESS => Ok((&mut buf[..data_size], attributes)), + Status::BUFFER_TOO_SMALL => Err(Error::new(status, Some(data_size))), + _ => Err(Error::new(status, None)), + } +} + +/// Gets the contents and attributes of a variable. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: variable was not found. +/// * [`Status::DEVICE_ERROR`]: variable could not be read due to a hardware error. +/// * [`Status::SECURITY_VIOLATION`]: variable could not be read due to an +/// authentication error. +/// * [`Status::UNSUPPORTED`]: this platform does not support variable storage +/// after exiting boot services. +#[cfg(feature = "alloc")] +pub fn get_variable_boxed( + name: &CStr16, + vendor: &VariableVendor, +) -> Result<(Box<[u8]>, VariableAttributes)> { + let mut out_attr = VariableAttributes::empty(); + let get_var = |buf| { + get_variable(name, vendor, buf).map(|(val, attr)| { + // `make_boxed` expects only a DST value to be returned (`val` in + // this case), so smuggle the `attr` value out via a separate + // variable. + out_attr = attr; + val + }) + }; + #[cfg(not(feature = "unstable"))] + { + make_boxed(get_var).map(|val| (val, out_attr)) + } + #[cfg(feature = "unstable")] + { + make_boxed(get_var, Global).map(|val| (val, out_attr)) + } +} + +/// Sets the value of a variable. This can be used to create a new variable, +/// update an existing variable, or (when the size of `data` is zero) +/// delete a variable. +/// +/// # Warnings +/// +/// The [`Status::WARN_RESET_REQUIRED`] warning will be returned when using +/// this function to transition the Secure Boot mode to setup mode or audit +/// mode if the firmware requires a reboot for that operation. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: invalid attributes, name, or vendor. +/// * [`Status::OUT_OF_RESOURCES`]: not enough storage is available to hold +/// the variable. +/// * [`Status::WRITE_PROTECTED`]: variable is read-only. +/// * [`Status::SECURITY_VIOLATION`]: variable could not be written due to an +/// authentication error. +/// * [`Status::NOT_FOUND`]: attempted to update a non-existent variable. +/// * [`Status::UNSUPPORTED`]: this platform does not support variable storage +/// after exiting boot services. +pub fn set_variable( + name: &CStr16, + vendor: &VariableVendor, + attributes: VariableAttributes, + data: &[u8], +) -> Result { + let rt = runtime_services_raw_panicking(); + let rt = unsafe { rt.as_ref() }; + + unsafe { + (rt.set_variable)( + name.as_ptr().cast(), + &vendor.0, + attributes, + data.len(), + data.as_ptr(), + ) + .to_result() + } +} + +/// Deletes a UEFI variable. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: invalid name or vendor. +/// * [`Status::WRITE_PROTECTED`]: variable is read-only. +/// * [`Status::SECURITY_VIOLATION`]: variable could not be deleted due to an +/// authentication error. +/// * [`Status::NOT_FOUND`]: attempted to delete a non-existent variable. +/// * [`Status::UNSUPPORTED`]: this platform does not support variable storage +/// after exiting boot services. +pub fn delete_variable(name: &CStr16, vendor: &VariableVendor) -> Result { + set_variable(name, vendor, VariableAttributes::empty(), &[]) +}