From 034f48e1afe54b015e586ad4edd190e1264fbd0a Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Mon, 22 Jul 2024 10:55:47 -0700 Subject: [PATCH] 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 Reviewed-by: Idan Raiter Commit-Queue: Daniel Verkamp --- hypervisor/tests/hypervisor_virtualization.rs | 264 +++++++++++++----- 1 file changed, 200 insertions(+), 64 deletions(-) diff --git a/hypervisor/tests/hypervisor_virtualization.rs b/hypervisor/tests/hypervisor_virtualization.rs index 7e6536651e..55ae88560d 100644 --- a/hypervisor/tests/hypervisor_virtualization.rs +++ b/hypervisor/tests/hypervisor_virtualization.rs @@ -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 = 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 = 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 = 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(®s).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) + ); +}