blob: 32333671120ea20c63925a8213235d4ecfffdce5 [file]
// Copyright 2021 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
/// EFI service tests.
#[cfg(ktest)]
#[unittest::suite(name = "efi")]
mod tests {
use crate::kernel::thread;
use crate::platform_rs::efi::{
EfiServicesActivation, for_each_memory_attribute_entry_safe, try_activate_efi_services,
};
use crate::platform_rs::efi_ffi::is_efi_expected;
use core::mem::size_of;
use core::ptr;
use efi::{
EFI_INVALID_PARAMETER, EFI_RUNTIME_SERVICES_CODE, EFI_RUNTIME_SERVICES_DATA, EFI_SUCCESS,
EfiGuid, EfiMemoryAttributesTableHeader, EfiMemoryDescriptor, EfiTime,
};
use kprint::kprint;
use unittest::{
expect_eq, expect_err, expect_false, expect_gt, expect_lt, expect_ok, expect_true,
};
use zerocopy::IntoBytes;
use zx_status::Status;
/// Ensure EFI is present on platforms we know should have it.
#[test]
#[sanitize(address = "off")]
fn test_efi_present() {
// This test aims to prevent EFI support from being silently dropped.
// Grab our current aspace.
// SAFETY: The reference is only compared by address below, after the guard restores it.
let old_aspace = unsafe { thread::current_active_aspace() };
// Attempt to fetch EFI services.
// SAFETY: `services` is scoped to this stack frame (strictly LIFO), the thread's previous
// `VmAspace` is not switched or destroyed before `services` is reset, and this function is
// tagged `#[sanitize(address = "off")]`.
let mut services = unsafe { try_activate_efi_services() };
// Ensure we got back a valid result if EFI meant to be present.
// SAFETY: Takes no arguments and reads platform state set up during boot.
if unsafe { is_efi_expected() } {
expect_true!(services.valid());
// This should switch back to the old aspace.
// SAFETY: No inner `EfiServicesActivation` guard is active, and `services` is not
// accessed again after `reset`.
unsafe { services.reset() };
// Make sure it actually did.
// SAFETY: The reference is only compared by address.
expect_eq!(
old_aspace.map_or(ptr::null(), ptr::from_ref),
unsafe { thread::current_active_aspace() }.map_or(ptr::null(), ptr::from_ref)
);
} else {
kprint!("Unknown if EFI is expected to be supported on platform. Skipping test.\n");
}
}
/// Tests that `GetTime` returns a reasonable year when EFI services are available.
#[test]
#[sanitize(address = "off", cfi = "off")]
fn test_efi_services() {
// Fetch EFI services.
// SAFETY: `services` is scoped to this stack frame (strictly LIFO), the thread's previous
// `VmAspace` is not switched or destroyed before `services` is dropped, and this function
// is tagged `#[sanitize(address = "off")]`.
let services = unsafe { try_activate_efi_services() };
// We may not have EFI services.
if let Some(rt) = services.get() {
// Ensure we can call `GetTime` and get a reasonable year (between 2000 and 2100).
let mut time = EfiTime::default();
// SAFETY: `rt` points at the firmware's runtime services table, mapped 1:1 in the
// aspace the guard activated.
if let Some(get_time) = unsafe { (*rt.as_ptr()).GetTime } {
// SAFETY: `get_time` is the firmware's `GetTime`, and `time` is a live local.
let result = unsafe { get_time(&mut time, ptr::null_mut()) };
expect_eq!(result, EFI_SUCCESS);
expect_gt!(time.Year, 2000);
expect_lt!(time.Year, 2100);
} else {
expect_true!(false, "EFI RuntimeServices->GetTime is null");
}
}
}
/// Tests that every descriptor of a well-formed table is visited.
#[test]
fn test_memory_attributes_table_valid() {
const VALID_DATA: [u32; 24] = [
0x2, // header.version
0x2, // header.number_of_entries
size_of::<EfiMemoryDescriptor>() as u32, // header.descriptor_size
0, // header.reserved
// descriptor 0
EFI_RUNTIME_SERVICES_CODE, // descriptor.Type
0, // descriptor.Padding
0,
0x1000, // descriptor.PhysicalStart (64-bit)
0,
0, // descriptor.VirtualStart (64-bit)
1,
0, // descriptor.NumberOfPages (64-bit)
0,
0, // descriptor.Attribute (64-bit)
// descriptor 1
EFI_RUNTIME_SERVICES_CODE, // descriptor.Type
0, // descriptor.Padding
0,
0x1000, // descriptor.PhysicalStart (64-bit)
0,
0, // descriptor.VirtualStart (64-bit)
1,
0, // descriptor.NumberOfPages (64-bit)
0,
0, // descriptor.Attribute (64-bit)
];
let bytes: &[u8] = VALID_DATA.as_bytes();
let mut callback_count: usize = 0;
expect_ok!(for_each_memory_attribute_entry_safe(bytes, |_| {
// The first descriptor is valid so this should be called at least once.
callback_count += 1;
Ok(())
}));
expect_eq!(2, callback_count);
}
/// Tests that a table shorter than the memory attributes header is rejected.
#[test]
fn test_memory_attributes_table_invalid() {
const SHORT_DATA: [u8; 4] = [0xab, 0xab, 0xab, 0xab];
let bytes: &[u8] = &SHORT_DATA;
const _: () = assert!(
SHORT_DATA.len() < size_of::<EfiMemoryAttributesTableHeader>(),
"test data is not small enough"
);
expect_err!(for_each_memory_attribute_entry_safe(bytes, |_| Ok(())), Status::INVALID_ARGS);
}
/// Tests that a table truncated before its declared entry count is rejected.
#[test]
fn test_memory_attributes_table_truncated() {
const TRUNCATED_DATA: [u32; 15] = [
0x2, // header.version
0x10, // header.number_of_entries
size_of::<EfiMemoryDescriptor>() as u32, // header.descriptor_size
0, // header.reserved
// descriptor 0
EFI_RUNTIME_SERVICES_CODE, // descriptor.Type
0, // descriptor.Padding
0,
0x1000, // descriptor.PhysicalStart (64-bit)
0,
0, // descriptor.VirtualStart (64-bit)
1,
0, // descriptor.NumberOfPages (64-bit)
0,
0, // descriptor.Attribute (64-bit)
// descriptor 1
EFI_RUNTIME_SERVICES_DATA,
];
let bytes: &[u8] = TRUNCATED_DATA.as_bytes();
let mut did_callback = false;
expect_err!(
for_each_memory_attribute_entry_safe(bytes, |_| {
// The first descriptor is valid so this should be called at least once.
did_callback = true;
Ok(())
}),
Status::INVALID_ARGS
);
expect_true!(did_callback);
}
/// Tests that a header whose descriptor size is smaller than a descriptor is rejected.
#[test]
fn test_memory_attributes_table_short_descriptor() {
const SHORT_DESCRIPTOR_DATA: [u32; 15] = [
0x2, // header.version
0x10, // header.number_of_entries
size_of::<EfiMemoryDescriptor>() as u32 - 3, // header.descriptor_size
0, // header.reserved
// descriptor 0
EFI_RUNTIME_SERVICES_CODE, // descriptor.Type
0, // descriptor.Padding
0,
0x1000, // descriptor.PhysicalStart (64-bit)
0,
0, // descriptor.VirtualStart (64-bit)
1,
0, // descriptor.NumberOfPages (64-bit)
0,
0, // descriptor.Attribute (64-bit)
// descriptor 1
EFI_RUNTIME_SERVICES_DATA,
];
let bytes: &[u8] = SHORT_DESCRIPTOR_DATA.as_bytes();
let mut did_callback = false;
expect_err!(
for_each_memory_attribute_entry_safe(bytes, |_| {
// The descriptor size is too small, so this should never be called.
did_callback = true;
Ok(())
}),
Status::INVALID_ARGS
);
expect_false!(did_callback);
}
/// Tests that descriptors are visited with a stride larger than `EfiMemoryDescriptor`.
#[test]
fn test_memory_attributes_table_large_descriptor_size() {
const LARGE_DESCRIPTOR_DATA: [u32; 28] = [
0x2, // header.version
0x2, // header.number_of_entries
size_of::<EfiMemoryDescriptor>() as u32 + 8, // header.descriptor_size
0, // header.reserved
// descriptor 0
EFI_RUNTIME_SERVICES_CODE, // descriptor.Type
0, // descriptor.Padding
0,
0x1000, // descriptor.PhysicalStart (64-bit)
0,
0, // descriptor.VirtualStart (64-bit)
1,
0, // descriptor.NumberOfPages (64-bit)
0,
0, // descriptor.Attribute (64-bit)
0xdeadbeef,
0xdeadbeef, // bytes past EfiMemoryDescriptor
// descriptor 1
EFI_RUNTIME_SERVICES_DATA, // descriptor.Type
0, // descriptor.Padding
0,
0x1000, // descriptor.PhysicalStart (64-bit)
0,
0, // descriptor.VirtualStart (64-bit)
1,
0, // descriptor.NumberOfPages (64-bit)
0,
0, // descriptor.Attribute (64-bit)
0xdeadbeef,
0xdeadbeef, // bytes past EfiMemoryDescriptor
];
let bytes: &[u8] = LARGE_DESCRIPTOR_DATA.as_bytes();
let mut types = [0u32; 2];
let mut callback_count: usize = 0;
expect_ok!(for_each_memory_attribute_entry_safe(bytes, |desc| {
if let Some(ty) = types.get_mut(callback_count) {
*ty = desc.Type;
}
callback_count += 1;
Ok(())
}));
expect_eq!(2, callback_count);
expect_eq!(EFI_RUNTIME_SERVICES_CODE, types[0]);
expect_eq!(EFI_RUNTIME_SERVICES_DATA, types[1]);
}
/// Tests that `set_variable` on an inactive guard returns `EFI_INVALID_PARAMETER`.
#[test]
fn test_set_variable_invalid_guard() {
let services = EfiServicesActivation::default();
expect_false!(services.valid());
expect_eq!(EFI_INVALID_PARAMETER, services.set_variable(&[0], &EfiGuid::default(), 0, &[]));
}
/// Tests that `set_variable` rejects a `var_name` that is not NUL-terminated.
#[test]
#[sanitize(address = "off")]
fn test_set_variable_invalid_var_name() {
// SAFETY: `services` is scoped to this stack frame (strictly LIFO), the thread's previous
// `VmAspace` is not switched or destroyed before `services` is dropped, and this function
// is tagged `#[sanitize(address = "off")]`.
let services = unsafe { try_activate_efi_services() };
// We may not have EFI services.
if services.valid() {
expect_eq!(
EFI_INVALID_PARAMETER,
services.set_variable(&[], &EfiGuid::default(), 0, &[])
);
expect_eq!(
EFI_INVALID_PARAMETER,
services.set_variable(&[0x41], &EfiGuid::default(), 0, &[])
);
}
}
}