diff --git a/mythril_core/src/acpi/hpet.rs b/mythril_core/src/acpi/hpet.rs new file mode 100644 index 0000000..3045c7f --- /dev/null +++ b/mythril_core/src/acpi/hpet.rs @@ -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 = 0..4; + pub const BASE_ADDRESS: Range = 4..16; + pub const HPET_NUMBER: usize = 16; + pub const MIN_CLOCK_TICK: Range = 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> { + 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); + } +} diff --git a/mythril_core/src/acpi/mod.rs b/mythril_core/src/acpi/mod.rs index 367de59..14aded3 100644 --- a/mythril_core/src/acpi/mod.rs +++ b/mythril_core/src/acpi/mod.rs @@ -9,7 +9,12 @@ //! [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). @@ -17,6 +22,16 @@ 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 = 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. @@ -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 { + 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, + }) + } +} diff --git a/mythril_multiboot2/src/main.rs b/mythril_multiboot2/src/main.rs index 1c43eee..e42eea2 100644 --- a/mythril_multiboot2/src/main.rs +++ b/mythril_multiboot2/src/main.rs @@ -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 {