Skip to content

Commit 2c3bc2a

Browse files
rcercIsaacWoods
authored andcommitted
acpi: Avoid entirely mapping a table if its header is invalid
This also quickens table searches by signature because it requires only the headers of irrelevant tables to be mapped.
1 parent a0db984 commit 2c3bc2a

File tree

2 files changed

+78
-48
lines changed

2 files changed

+78
-48
lines changed

acpi/src/lib.rs

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -359,37 +359,12 @@ unsafe fn read_table<H: AcpiHandler, T: AcpiTable>(
359359
address: usize,
360360
) -> AcpiResult<PhysicalMapping<H, T>> {
361361
// Attempt to peek at the SDT header to correctly enumerate the entire table.
362-
// SAFETY: `address` needs to be valid for the size of `SdtHeader`, or the ACPI tables are malformed (not a software issue).
363-
let header_mapping = unsafe { handler.map_physical_region::<SdtHeader>(address, mem::size_of::<SdtHeader>()) };
364-
365-
// If possible (if the existing mapping covers enough memory), reuse the existing physical mapping.
366-
// This allows allocators/memory managers that map in chunks larger than `size_of::<SdtHeader>()` to be used more efficiently.
367-
let table_length = header_mapping.length as usize;
368-
let table_mapping = if header_mapping.mapped_length() >= table_length {
369-
// Avoid requesting table unmap twice (from both `header_mapping` and `table_mapping`)
370-
let header_mapping = mem::ManuallyDrop::new(header_mapping);
371-
372-
// SAFETY: `header_mapping` maps entire table.
373-
unsafe {
374-
PhysicalMapping::new(
375-
header_mapping.physical_start(),
376-
header_mapping.virtual_start().cast::<T>(),
377-
table_length,
378-
header_mapping.mapped_length(),
379-
handler,
380-
)
381-
}
382-
} else {
383-
// Drop the old mapping here, to ensure it's unmapped in software before requesting an overlapping mapping.
384-
drop(header_mapping);
385362

386-
// SAFETY: Address and length are already known-good.
387-
unsafe { handler.map_physical_region(address, table_length) }
388-
};
389-
390-
table_mapping.validate()?;
363+
// SAFETY: `address` needs to be valid for the size of `SdtHeader`, or the ACPI tables are malformed (not a
364+
// software issue).
365+
let header_mapping = unsafe { handler.map_physical_region::<SdtHeader>(address, mem::size_of::<SdtHeader>()) };
391366

392-
Ok(table_mapping)
367+
SdtHeader::validate_lazy(header_mapping, handler)
393368
}
394369

395370
/// Iterator that steps through all of the tables, and returns only the SSDTs as `AmlTable`s.

acpi/src/sdt.rs

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::{AcpiError,};
2-
use core::{fmt, mem::MaybeUninit, str};
1+
use crate::{AcpiError, AcpiHandler, AcpiResult, AcpiTable, PhysicalMapping};
2+
use core::{fmt, mem::MaybeUninit, str};
33

44
/// Represents a field which may or may not be present within an ACPI structure, depending on the version of ACPI
55
/// that a system supports. If the field is not present, it is not safe to treat the data as initialised.
@@ -110,14 +110,10 @@ pub struct SdtHeader {
110110
}
111111

112112
impl SdtHeader {
113-
/// Check that:
114-
/// a) The signature matches the one given
115-
/// b) The checksum of the SDT is valid
116-
///
117-
/// This assumes that the whole SDT is mapped.
118-
pub fn validate(&self, signature: Signature) -> Result<(), AcpiError> {
113+
/// Whether values of header fields are permitted.
114+
fn validate_header_fields(&self, signature: Signature) -> AcpiResult<()> {
119115
// Check the signature
120-
if self.signature != signature {
116+
if self.signature != signature || str::from_utf8(&self.signature.0).is_err() {
121117
return Err(AcpiError::SdtInvalidSignature(signature));
122118
}
123119

@@ -131,22 +127,81 @@ impl SdtHeader {
131127
return Err(AcpiError::SdtInvalidTableId(signature));
132128
}
133129

134-
// Validate the checksum
135-
let self_ptr = self as *const SdtHeader as *const u8;
136-
let mut sum: u8 = 0;
137-
for offset in 0..self.length {
138-
// SAFETY: Read from pointer is valid for the described table length, and all reads are read safely
139-
// via `core::ptr::read_unaligned`.
140-
sum = sum.wrapping_add(unsafe { self_ptr.offset(offset as isize).read_unaligned() } as u8);
141-
}
130+
Ok(())
131+
}
132+
133+
/// Whether table is valid according to checksum.
134+
fn validate_checksum(&self, signature: Signature) -> AcpiResult<()> {
135+
// SAFETY: Entire table is mapped.
136+
let table_bytes =
137+
unsafe { core::slice::from_raw_parts((self as *const SdtHeader).cast::<u8>(), self.length as usize) };
138+
let sum = table_bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
142139

143-
if sum > 0 {
144-
return Err(AcpiError::SdtInvalidChecksum(signature));
140+
if sum == 0 {
141+
Ok(())
142+
} else {
143+
Err(AcpiError::SdtInvalidChecksum(signature))
145144
}
145+
}
146+
147+
/// Checks that:
148+
///
149+
/// 1. The signature matches the one given.
150+
/// 2. The values of various fields in the header are allowed.
151+
/// 3. The checksum of the SDT is valid.
152+
///
153+
/// This assumes that the whole SDT is mapped.
154+
pub fn validate(&self, signature: Signature) -> AcpiResult<()> {
155+
self.validate_header_fields(signature)?;
156+
self.validate_checksum(signature)?;
146157

147158
Ok(())
148159
}
149160

161+
/// Validates header, proceeding with checking entire table and returning a [`PhysicalMapping`] to it if
162+
/// successful.
163+
///
164+
/// The same checks are performed as [`SdtHeader::validate`], but `header_mapping` does not have to map the
165+
/// entire table when calling. This is useful to avoid completely mapping a table that will be immediately
166+
/// unmapped if it does not have a particular signature or has an invalid header.
167+
pub(crate) fn validate_lazy<H: AcpiHandler, T: AcpiTable>(
168+
header_mapping: PhysicalMapping<H, Self>,
169+
handler: H,
170+
) -> AcpiResult<PhysicalMapping<H, T>> {
171+
header_mapping.validate_header_fields(T::SIGNATURE)?;
172+
173+
// Reuse `header_mapping` to access the rest of the table if the latter is already mapped entirely
174+
let table_length = header_mapping.length as usize;
175+
let table_mapping = if header_mapping.mapped_length() >= table_length {
176+
// Avoid requesting table unmap twice (from both `header_mapping` and `table_mapping`)
177+
let header_mapping = core::mem::ManuallyDrop::new(header_mapping);
178+
179+
// SAFETY: `header_mapping` maps entire table.
180+
unsafe {
181+
PhysicalMapping::new(
182+
header_mapping.physical_start(),
183+
header_mapping.virtual_start().cast::<T>(),
184+
table_length,
185+
header_mapping.mapped_length(),
186+
handler,
187+
)
188+
}
189+
} else {
190+
// Unmap header as soon as possible
191+
let table_phys_start = header_mapping.physical_start();
192+
drop(header_mapping);
193+
194+
// SAFETY: `table_phys_start` is the physical address of the header and the rest of the table.
195+
unsafe { handler.map_physical_region(table_phys_start, table_length) }
196+
};
197+
198+
// This is usually redundant compared to simply calling `validate_checksum` but respects custom
199+
// `AcpiTable::validate` implementations.
200+
table_mapping.validate()?;
201+
202+
Ok(table_mapping)
203+
}
204+
150205
pub fn oem_id(&self) -> &str {
151206
// Safe to unwrap because checked in `validate`
152207
str::from_utf8(&self.oem_id).unwrap()

0 commit comments

Comments
 (0)