Skip to content

Commit 37a9cd6

Browse files
authored
Merge pull request #93 from mythril-hypervisor/basic-guest-apic-support
Add support for guest booting with APIC enabled
2 parents 77a6e44 + 674b2c5 commit 37a9cd6

File tree

10 files changed

+140
-45
lines changed

10 files changed

+140
-45
lines changed

mythril/src/emulate/cpuid.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ pub fn emulate_cpuid(
2020

2121
// Hide hypervisor feature
2222
res.ecx &= !(1 << 31);
23+
24+
// Hide TSC deadline timer
25+
res.ecx &= !(1 << 24);
2326
}
2427

2528
guest_cpu.rax = res.eax as u64 | (guest_cpu.rax & 0xffffffff00000000);

mythril/src/kmain.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ fn build_vm(
6363
);
6464

6565
let mut madt = acpi::madt::MADTBuilder::<[_; 8]>::new();
66-
madt.set_ica(0xfee00000);
66+
madt.set_ica(vm::GUEST_LOCAL_APIC_ADDR.as_u64() as u32);
6767
madt.add_ics(acpi::madt::Ics::LocalApic {
6868
apic_id: 0,
6969
apic_uid: 0,
@@ -117,10 +117,8 @@ fn build_vm(
117117
device_map
118118
.register_device(virtdev::rtc::CmosRtc::new(cfg.memory))
119119
.unwrap();
120-
121-
//TODO: this should actually be per-vcpu
122120
device_map
123-
.register_device(virtdev::lapic::LocalApic::new())
121+
.register_device(virtdev::ioapic::IoApic::new())
124122
.unwrap();
125123

126124
let mut fw_cfg_builder = virtdev::qemu_fw_cfg::QemuFwCfgBuilder::new();

mythril/src/memory.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ use x86::controlregs::Cr0;
1515

1616
#[repr(align(4096))]
1717
pub struct Raw4kPage(pub [u8; BASE_PAGE_SIZE]);
18+
impl Raw4kPage {
19+
pub fn as_ptr(&self) -> *const u8 {
20+
self.0.as_ptr()
21+
}
22+
}
23+
1824
impl Default for Raw4kPage {
1925
fn default() -> Self {
2026
Raw4kPage([0u8; BASE_PAGE_SIZE])
@@ -148,7 +154,7 @@ impl Add<usize> for Guest4LevelPagingAddr {
148154
pub struct GuestPhysAddr(u64);
149155

150156
impl GuestPhysAddr {
151-
pub fn new(addr: u64) -> Self {
157+
pub const fn new(addr: u64) -> Self {
152158
Self(addr)
153159
}
154160

mythril/src/vcpu.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ pub enum InjectedInterruptType {
6565
pub struct VCpu {
6666
pub vm: Arc<RwLock<VirtualMachine>>,
6767
pub vmcs: vmcs::ActiveVmcs,
68+
_local_apic: virtdev::lapic::LocalApic,
6869
pending_interrupts: BTreeMap<u8, InjectedInterruptType>,
6970
stack: Vec<u8>,
7071
}
@@ -85,15 +86,22 @@ impl VCpu {
8586
let mut vcpu = Box::pin(Self {
8687
vm: vm,
8788
vmcs: vmcs,
89+
_local_apic: virtdev::lapic::LocalApic::new(),
8890
stack: stack,
8991
pending_interrupts: BTreeMap::new(),
9092
});
9193

92-
// All VCpus in a VM must share the same address space (except for the
93-
// local apic)
94+
// All VCpus in a VM must share the same address space
9495
let eptp = vcpu.vm.read().guest_space.eptp();
9596
vcpu.vmcs.write_field(vmcs::VmcsField::EptPointer, eptp)?;
9697

98+
// Setup access for our local apic
99+
let apic_access_addr = vcpu.vm.read().apic_access_page.as_ptr() as u64;
100+
vcpu.vmcs
101+
.write_field(vmcs::VmcsField::ApicAccessAddr, apic_access_addr)?;
102+
103+
//TODO: set a per-core virtual apic page
104+
97105
let stack_base = vcpu.stack.as_ptr() as u64 + vcpu.stack.len() as u64
98106
- mem::size_of::<*const Self>() as u64;
99107

@@ -272,6 +280,7 @@ impl VCpu {
272280
vmcs.write_with_fixed(
273281
vmcs::VmcsField::CpuBasedVmExecControl,
274282
(vmcs::CpuBasedCtrlFlags::UNCOND_IO_EXITING
283+
| vmcs::CpuBasedCtrlFlags::TPR_SHADOW
275284
| vmcs::CpuBasedCtrlFlags::ACTIVATE_MSR_BITMAP
276285
| vmcs::CpuBasedCtrlFlags::ACTIVATE_SECONDARY_CONTROLS)
277286
.bits(),
@@ -310,7 +319,8 @@ impl VCpu {
310319
vmcs.write_with_fixed(
311320
vmcs::VmcsField::VmExitControls,
312321
(vmcs::VmExitCtrlFlags::IA32E_MODE
313-
| vmcs::VmExitCtrlFlags::ACK_INTR_ON_EXIT)
322+
| vmcs::VmExitCtrlFlags::ACK_INTR_ON_EXIT
323+
| vmcs::VmExitCtrlFlags::SAVE_GUEST_EFER)
314324
.bits(),
315325
msr::IA32_VMX_EXIT_CTLS,
316326
)?;
@@ -501,6 +511,9 @@ impl VCpu {
501511
}
502512
self.skip_emulated_instruction()?;
503513
}
514+
vmexit::ExitInformation::ApicAccess(_info) => {
515+
self.skip_emulated_instruction()?;
516+
}
504517
vmexit::ExitInformation::CrAccess(info) => {
505518
emulate::controlreg::emulate_access(self, guest_cpu, info)?;
506519
self.skip_emulated_instruction()?;

mythril/src/virtdev/ioapic.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use crate::error::Result;
2+
use crate::memory::GuestPhysAddr;
3+
use crate::virtdev::{DeviceRegion, EmulatedDevice, Event};
4+
use alloc::sync::Arc;
5+
use alloc::vec::Vec;
6+
use spin::RwLock;
7+
8+
#[derive(Default)]
9+
pub struct IoApic;
10+
11+
impl IoApic {
12+
pub fn new() -> Arc<RwLock<Self>> {
13+
Arc::new(RwLock::new(IoApic {}))
14+
}
15+
}
16+
17+
impl EmulatedDevice for IoApic {
18+
fn services(&self) -> Vec<DeviceRegion> {
19+
vec![
20+
DeviceRegion::MemIo(
21+
GuestPhysAddr::new(0xfec00000)..=GuestPhysAddr::new(0xfec010f0),
22+
),
23+
//FIXME: this is actually the 1st HPET
24+
DeviceRegion::MemIo(
25+
GuestPhysAddr::new(0xfed00000)..=GuestPhysAddr::new(0xfed010f0),
26+
),
27+
]
28+
}
29+
30+
fn on_event(&mut self, _event: Event) -> Result<()> {
31+
Ok(())
32+
}
33+
}

mythril/src/virtdev/lapic.rs

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,8 @@
1-
use crate::error::Result;
2-
use crate::memory::GuestPhysAddr;
3-
use crate::virtdev::{DeviceRegion, EmulatedDevice, Event};
4-
use alloc::sync::Arc;
5-
use alloc::vec::Vec;
6-
use spin::RwLock;
7-
81
#[derive(Default)]
92
pub struct LocalApic;
103

114
impl LocalApic {
12-
pub fn new() -> Arc<RwLock<Self>> {
13-
Arc::new(RwLock::new(LocalApic::default()))
14-
}
15-
}
16-
17-
impl EmulatedDevice for LocalApic {
18-
fn services(&self) -> Vec<DeviceRegion> {
19-
vec![
20-
DeviceRegion::MemIo(
21-
GuestPhysAddr::new(0xfee00000)..=GuestPhysAddr::new(0xfee010f0),
22-
),
23-
//FIXME: this is actually the 1st HPET
24-
DeviceRegion::MemIo(
25-
GuestPhysAddr::new(0xfed00000)..=GuestPhysAddr::new(0xfed010f0),
26-
),
27-
//FIXME: this is actually the io apic
28-
DeviceRegion::MemIo(
29-
GuestPhysAddr::new(0xfec00000)..=GuestPhysAddr::new(0xfec010f0),
30-
),
31-
]
32-
}
33-
34-
fn on_event(&mut self, _event: Event) -> Result<()> {
35-
Ok(())
5+
pub fn new() -> Self {
6+
LocalApic {}
367
}
378
}

mythril/src/virtdev/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod com;
1616
pub mod debug;
1717
pub mod dma;
1818
pub mod ignore;
19+
pub mod ioapic;
1920
pub mod keyboard;
2021
pub mod lapic;
2122
pub mod pci;

mythril/src/vm.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ use spin::RwLock;
2424

2525
static BIOS_BLOB: &'static [u8] = include_bytes!("blob/bios.bin");
2626

27+
//TODO(alschwalm): this should always be reported by the relevant MSR
28+
/// The location of the local apic in the guest address space
29+
pub const GUEST_LOCAL_APIC_ADDR: GuestPhysAddr = GuestPhysAddr::new(0xfee00000);
30+
2731
static VIRTUAL_MACHINES: RoAfterInit<VirtualMachines> =
2832
RoAfterInit::uninitialized();
2933

@@ -295,6 +299,11 @@ pub struct VirtualMachine {
295299
///
296300
/// This will be shared by all `VCpu`s associated with this VM.
297301
pub guest_space: GuestAddressSpace,
302+
303+
/// The APIC access page
304+
///
305+
/// See section 29.4 of the Intel software developer's manual
306+
pub apic_access_page: Raw4kPage,
298307
}
299308

300309
impl VirtualMachine {
@@ -309,11 +318,27 @@ impl VirtualMachine {
309318
) -> Result<Arc<RwLock<Self>>> {
310319
let guest_space = Self::setup_ept(&config, info)?;
311320

312-
Ok(Arc::new(RwLock::new(Self {
321+
let vm = Arc::new(RwLock::new(Self {
313322
id: id,
314323
config: config,
315324
guest_space: guest_space,
316-
})))
325+
apic_access_page: Raw4kPage([0u8; 4096]),
326+
}));
327+
328+
// Map the guest local apic addr to the access page. This will be set in each
329+
// core's vmcs
330+
let apic_frame = memory::HostPhysFrame::from_start_address(
331+
memory::HostPhysAddr::new(
332+
vm.read().apic_access_page.as_ptr() as u64
333+
),
334+
)?;
335+
vm.write().guest_space.map_frame(
336+
GUEST_LOCAL_APIC_ADDR,
337+
apic_frame,
338+
false,
339+
)?;
340+
341+
Ok(vm)
317342
}
318343

319344
pub fn dispatch_event(

mythril/src/vmexit.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub enum ExitInformation {
116116
Pause,
117117
VmEntryMachineCheck,
118118
TprBelowThreshold,
119-
ApicAccess,
119+
ApicAccess(ApicAccessInformation),
120120
VirtualEio,
121121
AccessGdtridtr,
122122
AccessLdtrTr,
@@ -196,7 +196,9 @@ impl ExitReason {
196196
40 => ExitInformation::Pause,
197197
41 => ExitInformation::VmEntryMachineCheck,
198198
43 => ExitInformation::TprBelowThreshold,
199-
44 => ExitInformation::ApicAccess,
199+
44 => ExitInformation::ApicAccess(
200+
ApicAccessInformation::from_active_vmcs(vmcs)?,
201+
),
200202
45 => ExitInformation::VirtualEio,
201203
46 => ExitInformation::AccessGdtridtr,
202204
47 => ExitInformation::AccessLdtrTr,
@@ -233,6 +235,49 @@ impl ExitReason {
233235
}
234236
}
235237

238+
#[derive(Clone, Debug, TryFromPrimitive, PartialEq)]
239+
#[repr(u8)]
240+
pub enum ApicAccessKind {
241+
LinearRead = 0,
242+
LinearWrite = 1,
243+
LinearFetch = 2,
244+
LinearEventDelivery = 3,
245+
PhysicalAccessDuringEvent = 10,
246+
PhysicalAccessDuringFetch = 15,
247+
}
248+
249+
#[derive(Clone, Debug)]
250+
pub struct ApicAccessInformation {
251+
pub offset: Option<u16>,
252+
pub kind: ApicAccessKind,
253+
pub async_instr_exec: bool,
254+
}
255+
256+
impl ExtendedExitInformation for ApicAccessInformation {
257+
fn from_active_vmcs(vmcs: &vmcs::ActiveVmcs) -> Result<Self> {
258+
let qualifier = vmcs.read_field(vmcs::VmcsField::ExitQualification)?;
259+
260+
let kind = ((qualifier >> 12) & 0b1111) as u8;
261+
let kind = ApicAccessKind::try_from(kind)?;
262+
263+
let offset = if kind == ApicAccessKind::LinearRead
264+
|| kind == ApicAccessKind::LinearWrite
265+
|| kind == ApicAccessKind::LinearFetch
266+
|| kind == ApicAccessKind::LinearEventDelivery
267+
{
268+
Some((qualifier & 0xfff) as u16)
269+
} else {
270+
None
271+
};
272+
273+
Ok(ApicAccessInformation {
274+
kind: kind,
275+
async_instr_exec: qualifier & (1 << 16) != 0,
276+
offset: offset,
277+
})
278+
}
279+
}
280+
236281
#[derive(Clone, Debug)]
237282
pub struct IoInstructionInformation {
238283
pub size: u8,

scripts/mythril.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
"cpus": [0],
77
"kernel": "kernel",
88
"initramfs": "initramfs",
9-
"cmdline": "disableapic earlyprintk=serial,0x3f8,115200 console=ttyS0 debug nokaslr noapic root=/dev/ram0"
9+
"cmdline": "earlyprintk=serial,0x3f8,115200 console=ttyS0 debug nokaslr root=/dev/ram0"
1010
},
1111
{
1212
"memory": 256,
1313
"cpus": [1],
1414
"kernel": "kernel",
1515
"initramfs": "initramfs",
16-
"cmdline": "disableapic earlyprintk=serial,0x3f8,115200 console=ttyS0 debug nokaslr noapic root=/dev/ram0"
16+
"cmdline": "earlyprintk=serial,0x3f8,115200 console=ttyS0 debug nokaslr root=/dev/ram0"
1717
}
1818
]
1919
}

0 commit comments

Comments
 (0)