Skip to content

Commit

Permalink
PVALIDATE and initialize the legacy SMBIOS range (#4982)
Browse files Browse the repository at this point in the history
* PVALIDATE and initialize the legacy SMBIOS range

Linux commit 0f4a1e80989a ("x86/sev: Skip ROM range scans and
validation for SEV-SNP guests") removes Linux's attempt to PVALIDATE
the legacy ROM regions, including the legacy SMBIOS range [0xf0000,
0x100000). However, legacy code may still attempt to scan this range
when using non-EFI firmware such as Stage0.

To avoid a crash during guest boot, PVALIDATE the range in Stage0.

To avoid legacy code reading garbage from this region, initialize the
PVALIDATEd memory to 0.

* FIXUP: address reviewer feedback on legacy smbios range readability

* FIXUP: fix typo on line 590 (should be equals, not NE)

* FIXUP: provide context in panic message per reviewer feedback

* FIXUP: fix typo in panic message (SMBOIS -> SMBIOS)
  • Loading branch information
kevinloughlin authored Apr 5, 2024
1 parent f5898b0 commit 571627b
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 3 deletions.
33 changes: 33 additions & 0 deletions stage0/src/sev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,39 @@ pub fn validate_memory(e820_table: &[BootE820Entry], encrypted: u64) {
}
}

// Locate the legacy SMBIOS range [0xF_0000, 0x10_0000) in the E820 table.
// Unwrap() will panic if entry not found with expected start, size, and type.
let legacy_smbios_range_entry = e820_table
.iter()
.find(|entry| {
entry.addr() == 0xF_0000
&& entry.size() == 0x1_0000
&& entry.entry_type() == Some(E820EntryType::RESERVED)
})
.expect("couldn't find legacy SMBIOS memory range");

// Pvalidate the legacy SMBIOS range since legacy code may scan this range for
// the SMBIOS entry point table, even if the range is marked as reserved.
let range = PhysFrame::<Size4KiB>::range(
PhysFrame::from_start_address(PhysAddr::new(legacy_smbios_range_entry.addr() as u64))
.unwrap(),
PhysFrame::from_start_address(PhysAddr::new(
(legacy_smbios_range_entry.addr() + legacy_smbios_range_entry.size()) as u64,
))
.unwrap(),
);
range.pvalidate(&mut validation_pt, encrypted).expect("failed to validate SMBIOS memory");

// Safety: the E820 table indicates that this is the correct memory segment.
let legacy_smbios_range_bytes = unsafe {
core::slice::from_raw_parts_mut::<u8>(
legacy_smbios_range_entry.addr() as *mut u8,
legacy_smbios_range_entry.size(),
)
};
// Zeroize the legacy SMBIOS range bytes to avoid legacy code reading garbage.
legacy_smbios_range_bytes.zeroize();

page_tables.pd_0[1].set_unused();
page_tables.pdpt[1].set_unused();
tlb::flush_all();
Expand Down
8 changes: 5 additions & 3 deletions stage0/src/zero_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,13 @@ impl ZeroPage {
self.validate_e820_table();

// Carve out a chunk of memory for the ACPI area in the range
// [0x80000-0xA0000). We also remove the region [0xA0000,0x100000)
// [0x80000-0xA0000). We also remove the region [0xA0000,0xF0000)
// since historically this contained hardware-related regions such as
// the VGA bios rom.
// the VGA bios rom. Finally, reserve [0xF0000,0x100000) as Stage0
// initializes this memory to handle legacy scans of the SMBIOS range.
self.insert_e820_entry(BootE820Entry::new(0x8_0000, 0x2_0000, E820EntryType::ACPI));
self.ensure_e820_gap(0xA_0000, 0x6_0000);
self.ensure_e820_gap(0xA_0000, 0x5_0000);
self.insert_e820_entry(BootE820Entry::new(0xF_0000, 0x1_0000, E820EntryType::RESERVED));

for entry in self.inner.e820_table() {
log::debug!(
Expand Down
10 changes: 10 additions & 0 deletions stage0_bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ Stage0 maps the first 1 GiB of physical memory using identity mapping before
handing control over to the kernel. The stage0 binary itself is mapped into
memory just below the end of 4 GiB address space, like any other BIOS ROM.

We explicitly reserve the SMBIOS entry table memory range (0xF0000-0xFFFFF) to
indicate that Stage0 initializes this range.

```text
| ... |
0x1_0000_0000 +------------------------------------------------+ 4GiB
Expand All @@ -144,6 +147,8 @@ memory just below the end of 4 GiB address space, like any other BIOS ROM.
0x20_0000 +------------------------------------------------+ 2MiB
| |
0x10_0000 +------------------------------------------------+ 1MiB
| Reserved SMBIOS entry point table memory |
0xF_0000 +------------------------------------------------+ 960KiB
| BIOS Hole |
0xA_0000 +------------------------------------------------+ 640KiB
| Extended BIOS Data Area (ACPI Tables) |
Expand All @@ -161,6 +166,9 @@ The Linux kernel and the Oak restricted kernel both assume that the firmware
will validate all (or at least most) of the guest-physical memory before jumping
into the kernel (by calling the PVALIDATE instruction).

Because legacy code may scan the SMBIOS region (even if marked as reserved in
the E820 table), we pvalidate this region to prevent crashes.

We don't have to PVALIDATE the Stage 0 ROM image range, since that memory was
already set in the appropriate validated state by the Secure Processor before
launching the guest VM.
Expand All @@ -172,6 +180,8 @@ launching the guest VM.
0xFFFE_0000 +------------------------------------------------+ 4GiB - 128MiB
| PVALIDATEd |
0x10_0000 +------------------------------------------------+ 1MiB
| PVALIDATEd SMBIOS entry point table memory |
0xF_0000 +------------------------------------------------+ 960KiB
| BIOS Hole |
0xA_0000 +------------------------------------------------+ 640KiB
| PVALIDATEd by bootstrap assembly code |
Expand Down

0 comments on commit 571627b

Please sign in to comment.