hypervisor: tests: add 32-bit pmode segment limit test

BUG=b:354053941

Change-Id: I16008b408379afd59730863587e2ca138c577bdf
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/5730916
Reviewed-by: Oystein Eftevaag <oysteine@chromium.org>
Reviewed-by: Idan Raiter <idanr@google.com>
Commit-Queue: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
Daniel Verkamp 2024-07-22 10:55:47 -07:00 committed by crosvm LUCI
parent 44b2688a3c
commit 034f48e1af

View file

@ -298,30 +298,66 @@ global_asm_data!(
const GDT_OFFSET: u64 = 0x1500;
const IDT_OFFSET: u64 = 0x1528;
pub fn configure_long_mode_memory(vm: &mut dyn Vm) -> Segment {
// Condensed version of the function in x86_64\src\gdt.rs
pub fn segment_from_gdt(entry: u64, table_index: u8) -> Segment {
let g = ((entry & 0x0080000000000000) >> 55) as u8;
let limit =
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32;
let limit_bytes = if g == 0 { limit } else { (limit * 4096) + 4095 };
Segment {
base: (((entry) & 0xFF00000000000000) >> 32)
| (((entry) & 0x000000FF00000000) >> 16)
| (((entry) & 0x00000000FFFF0000) >> 16),
limit_bytes,
selector: (table_index * 8) as u16,
type_: ((entry & 0x00000F0000000000) >> 40) as u8,
present: ((entry & 0x0000800000000000) >> 47) as u8,
dpl: ((entry & 0x0000600000000000) >> 45) as u8,
db: ((entry & 0x0040000000000000) >> 54) as u8,
s: ((entry & 0x0000100000000000) >> 44) as u8,
l: ((entry & 0x0020000000000000) >> 53) as u8,
g,
avl: ((entry & 0x0010000000000000) >> 52) as u8,
}
// Condensed version of the function in x86_64\src\gdt.rs
pub fn segment_from_gdt(entry: u64, table_index: u8) -> Segment {
let g = ((entry & 0x0080000000000000) >> 55) as u8;
let limit = ((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32;
let limit_bytes = if g == 0 { limit } else { (limit * 4096) + 4095 };
Segment {
base: (((entry) & 0xFF00000000000000) >> 32)
| (((entry) & 0x000000FF00000000) >> 16)
| (((entry) & 0x00000000FFFF0000) >> 16),
limit_bytes,
selector: (table_index * 8) as u16,
type_: ((entry & 0x00000F0000000000) >> 40) as u8,
present: ((entry & 0x0000800000000000) >> 47) as u8,
dpl: ((entry & 0x0000600000000000) >> 45) as u8,
db: ((entry & 0x0040000000000000) >> 54) as u8,
s: ((entry & 0x0000100000000000) >> 44) as u8,
l: ((entry & 0x0020000000000000) >> 53) as u8,
g,
avl: ((entry & 0x0010000000000000) >> 52) as u8,
}
}
pub fn null_descriptor() -> [u8; 8] {
[0u8; 8]
}
const DESC_ACCESS_PRESENT: u8 = 1 << 7;
const DESC_ACCESS_NOT_SYS: u8 = 1 << 4;
const DESC_ACCESS_EXEC: u8 = 1 << 3;
const DESC_ACCESS_RW: u8 = 1 << 1;
const DESC_ACCESS_ACCESSED: u8 = 1 << 0;
const DESC_FLAG_GRAN_4K: u8 = 1 << 7;
const DESC_FLAG_DEFAULT_OP_SIZE_32: u8 = 1 << 6;
const DESC_FLAG_LONG_MODE: u8 = 1 << 5;
pub fn segment_descriptor(base: u32, limit: u32, access: u8, flags: u8) -> [u8; 8] {
assert!(limit < (1 << 20)); // limit value must fit in 20 bits
assert!(flags & 0x0F == 0x00); // flags must be in the high 4 bits only
[
limit as u8, // limit [7:0]
(limit >> 8) as u8, // limit [15:8]
base as u8, // base [7:0]
(base >> 8) as u8, // base [15:8]
(base >> 16) as u8, // base [23:16]
access, // type + s + dpl + p
(limit >> 16) as u8 | flags, // limit [19:16] + flags
(base >> 24) as u8, // base [31:24]
]
}
pub fn write_gdt(guest_mem: &GuestMemory, gdt: &[u8]) {
let gdt_addr = GuestAddress(GDT_OFFSET);
guest_mem
.write_at_addr(gdt, gdt_addr)
.expect("Failed to write GDT entry to guest memory");
}
pub fn configure_long_mode_memory(vm: &mut dyn Vm) -> Segment {
let guest_mem = vm.get_memory();
assert!(
@ -329,50 +365,25 @@ pub fn configure_long_mode_memory(vm: &mut dyn Vm) -> Segment {
"Long-mode setup requires 0x1500-0xc000 to be mapped in the guest."
);
const PRESENT: u8 = 1 << 7;
const NOT_SYS: u8 = 1 << 4;
const EXEC: u8 = 1 << 3;
const RW: u8 = 1 << 1;
const ACCESSED: u8 = 1 << 0;
const GRAN_4K: u8 = 1 << 7;
const LONG_MODE: u8 = 1 << 5;
// Setup GDT
let gdt: Vec<u8> = vec![
// Null descriptor
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
// Null descriptor
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
// Code segment descriptor
0xFF, // Limit & Base (low, bits 0-15)
0xFF,
0x00,
0x00,
0x00, // Base (mid, bits 16-23)
PRESENT | NOT_SYS | EXEC | RW | ACCESSED, // Access byte
GRAN_4K | LONG_MODE | 0x0F, // Flags & Limit (high)
0x00, // Base (high)
];
let mut gdt = Vec::new();
// 0x00
gdt.extend_from_slice(&null_descriptor());
// 0x08
gdt.extend_from_slice(&null_descriptor());
// 0x10: code segment descriptor
gdt.extend_from_slice(&segment_descriptor(
0x0,
0xFFFFF,
DESC_ACCESS_PRESENT
| DESC_ACCESS_NOT_SYS
| DESC_ACCESS_EXEC
| DESC_ACCESS_RW
| DESC_ACCESS_ACCESSED,
DESC_FLAG_GRAN_4K | DESC_FLAG_LONG_MODE,
));
let gdt_addr = GuestAddress(GDT_OFFSET);
guest_mem
.write_at_addr(&gdt, gdt_addr)
.expect("Failed to write GDT entry to guest memory");
write_gdt(guest_mem, &gdt);
// Convert the GDT entries to a vector of u64
let gdt_entries: Vec<u64> = gdt
@ -448,6 +459,78 @@ pub fn enter_long_mode(vcpu: &mut dyn VcpuX86_64, vm: &mut dyn Vm) {
vcpu.set_sregs(&sregs).expect("failed to set sregs");
}
pub fn configure_flat_protected_mode_memory(vm: &mut dyn Vm) -> Segment {
let guest_mem = vm.get_memory();
assert!(
guest_mem.range_overlap(GuestAddress(0x1500), GuestAddress(0xc000)),
"Protected-mode setup requires 0x1500-0xc000 to be mapped in the guest."
);
// Setup GDT
let mut gdt = Vec::new();
// 0x00
gdt.extend_from_slice(&null_descriptor());
// 0x08
gdt.extend_from_slice(&null_descriptor());
// 0x10: code segment descriptor
gdt.extend_from_slice(&segment_descriptor(
0x0,
0xFFFFF,
DESC_ACCESS_PRESENT
| DESC_ACCESS_NOT_SYS
| DESC_ACCESS_EXEC
| DESC_ACCESS_RW
| DESC_ACCESS_ACCESSED,
DESC_FLAG_GRAN_4K | DESC_FLAG_DEFAULT_OP_SIZE_32,
));
write_gdt(guest_mem, &gdt);
// Convert the GDT entries to a vector of u64
let gdt_entries: Vec<u64> = gdt
.chunks(8)
.map(|chunk| {
let mut array = [0u8; 8];
array.copy_from_slice(chunk);
u64::from_le_bytes(array)
})
.collect();
let code_seg = segment_from_gdt(gdt_entries[2], 2);
// Setup IDT
let idt_addr = GuestAddress(IDT_OFFSET);
let idt_entry: u64 = 0; // Empty IDT
let idt_entry_bytes = idt_entry.to_le_bytes();
guest_mem
.write_at_addr(&idt_entry_bytes, idt_addr)
.expect("failed to write IDT entry to guest memory");
code_seg
}
pub fn enter_protected_mode(vcpu: &mut dyn VcpuX86_64, vm: &mut dyn Vm) {
let code_seg = configure_flat_protected_mode_memory(vm);
let mut sregs = vcpu.get_sregs().expect("failed to get sregs");
sregs.cs = code_seg;
sregs.gdt.base = GDT_OFFSET;
sregs.gdt.limit = 0xFFFF;
sregs.idt.base = IDT_OFFSET;
sregs.idt.limit = 0xFFF;
// 32-bit protected mode, paging disabled
sregs.cr0 |= 0x1; // PE
sregs.cr0 &= !0x80000000; // ~PG
vcpu.set_sregs(&sregs).expect("failed to set sregs");
}
// This runs a minimal program under virtualization.
// It should require only the ability to execute instructions under virtualization, physical
// memory, the ability to get and set some guest VM registers, and intercepting HLT.
@ -3656,3 +3739,56 @@ fn test_minimal_exception_injection() {
|_, exit, _, _: &mut dyn Vm| matches!(exit, VcpuExit::Hlt)
);
}
#[test]
fn test_pmode_segment_limit() {
// This test configures 32-bit protected mode and verifies that segment limits are converted
// correctly. The test setup configures a segment with the 20-bit limit field set to 0xFFFFF and
// the 4096-byte granularity bit set, which should result in a 4 GB limit (0xFFFFFFFF).
mod assembly {
use super::*;
global_asm_data!(
pub init,
".code32",
// Load the CS segment limit into EAX.
"mov cx, cs",
"lsl eax, cx",
"hlt",
);
}
let mem_size: u64 = 0x20000;
let setup = TestSetup {
initial_regs: Regs {
..Default::default()
},
mem_size,
extra_vm_setup: Some(Box::new(|vcpu: &mut dyn VcpuX86_64, vm: &mut dyn Vm| {
enter_protected_mode(vcpu, vm);
let guest_mem = vm.get_memory();
let mut regs = vcpu.get_regs().expect("Failed to get regs");
regs.rax = 12345;
regs.rip = 0x1000;
vcpu.set_regs(&regs).expect("Failed to set regs");
let init_assembly = assembly::init::data().to_vec();
guest_mem
.write_at_addr(&init_assembly, GuestAddress(0x1000))
.expect("Failed to write init assembly to guest memory");
})),
..Default::default()
};
run_tests!(
setup,
|_, regs, _| {
// The output of the LSL instruction should be 4GB - 1.
assert_eq!(regs.rax, 0xFFFFFFFF);
},
|_, exit, _, _: &mut dyn Vm| matches!(exit, VcpuExit::Hlt)
);
}