Skip to content
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
145 changes: 145 additions & 0 deletions mythril_core/src/acpi/hpet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use super::rsdt::SDT;
use super::GenericAddressStructure;
use crate::error::{Error, Result};
use byteorder::{ByteOrder, NativeEndian};
use core::fmt;
use core::ops::Range;
use derive_try_from_primitive::TryFromPrimitive;

mod offsets {
use super::*;
pub const EVENT_TIMER_BLOCK_ID: Range<usize> = 0..4;
pub const BASE_ADDRESS: Range<usize> = 4..16;
pub const HPET_NUMBER: usize = 16;
pub const MIN_CLOCK_TICK: Range<usize> = 17..19;
pub const PAGE_PROTECTION: usize = 19;
}

/// Page Protection for HPET register access.
///
/// See Table 3 in the IA-PC HPET specification.
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
pub enum PageProtection {
/// No page protection
NoProtection = 0x00,
/// Register access is protected by a 4KB page
Protected4KB = 0x01,
/// Register access is protected by a 64KB page
Protected64KB = 0x02,
}

/// High Precision Event Timer ACPI entry.
///
/// See `IA-PC HPET § 1.0a`.
pub struct HPET<'a> {
/// System Descriptor Table Header for this structure.
sdt: &'a SDT<'a>,
/// The hardware revision ID.
pub hardware_rev_id: u8,
/// The number of comparators in the first timer block.
pub comparator_count: u8,
/// The size cap of the counter. If false, then only 32 bit mode is allowed.
/// If true, then both 32 bit and 64 bit modes are supported.
pub counter_cap: bool,
/// If true, then the HPET is LegacyReplacement IRQ routing capable.
pub legacy_replacement: bool,
/// The PCI vendor ID of the first timer block.
pub pci_vendor_id: u16,
/// The address of the HPET register stored as an ACPI GAS.
pub address: GenericAddressStructure,
/// The HPET sequence number.
pub hpet_number: u8,
/// The minimum number of ticks that must be used by any counter programmed
/// in periodic mode to avoid lost interrupts.
pub minimum_tick: u16,
/// The type of page protection for HPET register access.
pub page_protection: PageProtection,
}

impl<'a> HPET<'a> {
/// Create a new HPET given a SDT.
pub fn new(sdt: &'a SDT<'a>) -> Result<HPET<'a>> {
let event_timer_block_id =
NativeEndian::read_u32(&sdt.table[offsets::EVENT_TIMER_BLOCK_ID]);
let address =
GenericAddressStructure::new(&sdt.table[offsets::BASE_ADDRESS])?;
let hpet_number = sdt.table[offsets::HPET_NUMBER];
let minimum_tick =
NativeEndian::read_u16(&sdt.table[offsets::MIN_CLOCK_TICK]);

let page_protection =
PageProtection::try_from(sdt.table[offsets::PAGE_PROTECTION] & 0xF)
.ok_or_else(|| {
Error::InvalidValue(format!(
"Invalid HPET Page Protection type: {}",
sdt.table[offsets::PAGE_PROTECTION] & 0xF
))
})?;

let hardware_rev_id = (event_timer_block_id & 0xFF) as u8;
let comparator_count = ((event_timer_block_id >> 8) & 0x1F) as u8;
let counter_cap = ((event_timer_block_id >> 13) & 0x1) as u8;
let legacy_replacement = ((event_timer_block_id >> 15) & 0x1) as u8;
let pci_vendor_id = ((event_timer_block_id >> 16) & 0xFFFF) as u16;

let counter_cap = counter_cap != 0;
let legacy_replacement = legacy_replacement != 0;

Ok(Self {
sdt,
hardware_rev_id,
comparator_count,
counter_cap,
legacy_replacement,
pci_vendor_id,
address,
hpet_number,
minimum_tick,
page_protection,
})
}
}

impl<'a> fmt::Debug for HPET<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.sdt)?;
write!(f, " HPET address=0x{:x}", self.address.address)
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::acpi::{AccessSize, AddressSpaceID};

#[test]
fn test_hpet_parse() {
// sample HPET ACPI entry
let buf = [
0x48, 0x50, 0x45, 0x54, 0x38, 0x00, 0x00, 0x00, 0x01, 0xb6, 0x41,
0x4c, 0x41, 0x53, 0x4b, 0x41, 0x41, 0x20, 0x4d, 0x20, 0x49, 0x00,
0x00, 0x00, 0x09, 0x20, 0x07, 0x01, 0x41, 0x4d, 0x49, 0x2e, 0x05,
0x00, 0x00, 0x00, 0x01, 0xa7, 0x86, 0x80, 0x00, 0x40, 0x00, 0x00,
0x00, 0x00, 0xd0, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0xee, 0x37,
0x00,
];

let hpet_sdt = unsafe { SDT::new(buf.as_ptr()).unwrap() };
let hpet = HPET::new(&hpet_sdt).unwrap();

assert_eq!(hpet.hardware_rev_id, 1);
assert_eq!(hpet.comparator_count, 7);
assert_eq!(hpet.counter_cap, true);
assert_eq!(hpet.legacy_replacement, true);
assert_eq!(hpet.pci_vendor_id, 0x8086);
assert_eq!(hpet.address.address_space, AddressSpaceID::SystemMemory);
assert_eq!(hpet.address.bit_width, 64);
assert_eq!(hpet.address.bit_offset, 0);
assert_eq!(hpet.address.access_size, AccessSize::Undefined);
assert_eq!(hpet.address.address, 0xfed00000);
assert_eq!(hpet.hpet_number, 0);
assert_eq!(hpet.minimum_tick, 0x37ee);
assert_eq!(hpet.page_protection, PageProtection::NoProtection);
}
}
148 changes: 148 additions & 0 deletions mythril_core/src/acpi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,29 @@
//! [ACPI 6.3]: https://uefi.org/sites/default/files/resources/ACPI_6_3_May16.pdf

use crate::error::{Error, Result};
use byteorder::{ByteOrder, NativeEndian};
use derive_try_from_primitive::TryFromPrimitive;
use raw_cpuid::CpuId;

/// Support for the High Precision Event Timer (HPET)
pub mod hpet;
/// Support for the Multiple APIC Descriptor Table (MADT).
pub mod madt;
/// Support for the Root System Descriptor Pointer (RSDP).
pub mod rsdp;
/// Support for the Root System Descriptor Table (RSDT).
pub mod rsdt;

mod offsets {
use core::ops::Range;

pub const GAS_ADDRESS_SPACE: usize = 0;
pub const GAS_BIT_WIDTH: usize = 1;
pub const GAS_BIT_OFFSET: usize = 2;
pub const GAS_ACCESS_SIZE: usize = 3;
pub const GAS_ADDRESS: Range<usize> = 4..12;
}

/// Verify a one byte checksum for a given slice and length.
pub(self) fn verify_checksum(bytes: &[u8], cksum_idx: usize) -> Result<()> {
// Sum up the bytes in the buffer.
Expand All @@ -34,3 +49,136 @@ pub(self) fn verify_checksum(bytes: &[u8], cksum_idx: usize) -> Result<()> {
)))
}
}

/// The size of a Generic Address Structure in bytes.
pub const GAS_SIZE: usize = 12;

/// Generic Address Structure (GAS) used by ACPI for position of registers.
///
/// See Table 5-25 in ACPI specification.
#[derive(Debug)]
pub struct GenericAddressStructure {
/// The address space where the associated address exists.
pub address_space: AddressSpaceID,
/// The size in bits of the given register.
pub bit_width: u8,
/// The bit offset of the given register at the given address.
pub bit_offset: u8,
/// The size of the memory access for the given address.
pub access_size: AccessSize,
/// The 64-bit address of the register or data structure.
pub address: u64,
}

/// Where a given address pointed to by a GAS resides.
///
/// See Table 5-25 of the ACPI specification.
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
pub enum AddressSpaceID {
/// The associated address exists in the System Memory space.
SystemMemory = 0x00,
/// The associated address exists in the System I/O space.
SystemIO = 0x01,
/// The associated address exists in the PCI Configuration space.
PCIConfiguration = 0x02,
/// The associated address exists in an Embedded Controller.
EmbeddedController = 0x03,
/// The associated address exists in the SMBus.
SMBus = 0x04,
/// The associated address exists in the SystemCMOS.
SystemCMOS = 0x05,
/// The associated address exists in a PCI Bar Target.
PciBarTarget = 0x06,
/// The associated address exists in an IPMI.
IPMI = 0x07,
/// The associated address exists in General Purpose I/O.
GPIO = 0x08,
/// The associated address exists in a Generic Serial Bus.
GenericSerialBus = 0x09,
/// The associated address exists in the Platform Communications Channel (PCC).
PlatformCommunicationsChannel = 0x0A,
/// The associated address exists in Functional Fixed Hardware.
FunctionalFixedHardware = 0x7F,
}

/// Specifies access size of an address in a GAS.
///
/// See Table 5-25 in the ACPI specification.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
pub enum AccessSize {
/// Undefined (legacy reasons).
Undefined = 0x00,
/// Byte access.
Byte = 0x01,
/// Word access.
Word = 0x02,
/// DWord access.
DWord = 0x03,
/// QWord access.
QWord = 0x04,
}

impl GenericAddressStructure {
/// Create a new GAS from a slice of bytes.
pub fn new(bytes: &[u8]) -> Result<GenericAddressStructure> {
if bytes.len() != GAS_SIZE {
return Err(Error::InvalidValue(format!(
"Invalid number of bytes for GAS: {} != {}",
bytes.len(),
GAS_SIZE
)));
}

let address_space =
AddressSpaceID::try_from(bytes[offsets::GAS_ADDRESS_SPACE])
.ok_or_else(|| {
Error::InvalidValue(format!(
"Invalid Address Space ID: {}",
bytes[offsets::GAS_ADDRESS_SPACE]
))
})?;

let bit_width = bytes[offsets::GAS_BIT_WIDTH];
let bit_offset = bytes[offsets::GAS_BIT_OFFSET];

let access_size = AccessSize::try_from(bytes[offsets::GAS_ACCESS_SIZE])
.ok_or_else(|| {
Error::InvalidValue(format!(
"Invalid Access Size: {}",
bytes[offsets::GAS_ACCESS_SIZE]
))
})?;

let address = NativeEndian::read_u64(&bytes[offsets::GAS_ADDRESS]);

if address_space == AddressSpaceID::SystemMemory
|| address_space == AddressSpaceID::SystemIO
{
// call CPUID to determine if we need to verify the address. If the
// call to CPUID fails, the check is not performed.
let cpuid = CpuId::new();
let is_64bit = cpuid
.get_extended_function_info()
.and_then(|x| Some(x.has_64bit_mode()))
.ok_or_else(|| Error::NotSupported)?;

// verify that the address is only 32 bits for 32-bit platforms.
if !is_64bit && ((address >> 32) & 0xFFFFFFFF) != 0 {
return Err(Error::InvalidValue(format!(
"Invalid address for a 32-bit system: {:x}",
address
)));
}
}

Ok(Self {
address_space,
bit_width,
bit_offset,
access_size,
address,
})
}
}
6 changes: 6 additions & 0 deletions mythril_multiboot2/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ pub extern "C" fn kmain(multiboot_info_addr: usize) -> ! {
let madt_sdt = rsdt.find_entry(b"APIC").expect("No MADT found");
let madt = acpi::madt::MADT::new(&madt_sdt);

let hpet_sdt = rsdt.find_entry(b"HPET").expect("No HPET found");
let hpet = acpi::hpet::HPET::new(&hpet_sdt)
.unwrap_or_else(|e| panic!("Failed to create the HPET: {:?}", e));

info!("{:?}", hpet);

let apic_ids = madt
.structures()
.filter_map(|ics| match ics {
Expand Down