Skip to content

runtime: Add freestanding functions to get/set/delete UEFI variables #1250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 59 additions & 18 deletions uefi-test-runner/src/runtime/vars.rs
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -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
Expand All @@ -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();
}
6 changes: 6 additions & 0 deletions uefi/src/data_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ pub trait Align {
}
}

impl Align for [u8] {
fn alignment() -> usize {
1
}
}

mod guid;
pub use guid::{Guid, Identify};

Expand Down
146 changes: 144 additions & 2 deletions uefi/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<uefi_raw::table::runtime::RuntimeServices> {
let st = table::system_table_raw_panicking();
Expand Down Expand Up @@ -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<usize>> {
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(), &[])
}