#![allow(clippy::mutex_atomic)] #[macro_use]
extern crate log;
pub mod constants;
pub mod errors;
mod libkvmi;
use enum_primitive_derive::Primitive;
pub use kvmi_sys::{kvm_dtable, kvm_msrs, kvm_regs, kvm_segment, kvm_sregs, kvmi_dom_event};
use kvmi_sys::{
kvm_msr_entry, kvmi_event_cr_reply, kvmi_event_msr_reply, kvmi_event_pf_reply,
kvmi_event_reply, kvmi_introspector2qemu, kvmi_qemu2introspector, kvmi_vcpu_hdr,
KVMI_EVENT_BREAKPOINT, KVMI_EVENT_CR, KVMI_EVENT_MSR, KVMI_EVENT_PAUSE_VCPU, KVMI_EVENT_PF,
KVMI_PAGE_ACCESS_R, KVMI_PAGE_ACCESS_W, KVMI_PAGE_ACCESS_X,
};
use libc::free;
use nix::errno::Errno;
use num_traits::{FromPrimitive, ToPrimitive};
use std::alloc::{alloc_zeroed, Layout};
use std::cmp;
use std::cmp::PartialEq;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::ffi::CString;
use std::io::Error;
use std::mem;
use std::os::raw::{c_int, c_uchar, c_uint, c_ushort, c_void};
use std::ptr::null_mut;
use std::slice;
use std::sync::{Condvar, Mutex};
use constants::PAGE_SHIFT;
use errors::KVMiError;
use libkvmi::Libkvmi;
#[derive(Debug)]
struct KVMiCon {
dom: *mut c_void,
guard: Mutex<bool>,
condvar: Condvar,
}
#[derive(Debug, Copy, Clone, Primitive)]
pub enum KVMiInterceptType {
PauseVCPU = KVMI_EVENT_PAUSE_VCPU as isize,
Cr = KVMI_EVENT_CR as isize,
Msr = KVMI_EVENT_MSR as isize,
Breakpoint = KVMI_EVENT_BREAKPOINT as isize,
Pagefault = KVMI_EVENT_PF as isize,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum KVMiPageAccess {
NIL = 0,
R = KVMI_PAGE_ACCESS_R as u8,
W = KVMI_PAGE_ACCESS_W as u8,
RW = KVMI_PAGE_ACCESS_R as u8 | KVMI_PAGE_ACCESS_W as u8,
X = KVMI_PAGE_ACCESS_X as u8,
RX = KVMI_PAGE_ACCESS_R as u8 | KVMI_PAGE_ACCESS_X as u8,
WX = KVMI_PAGE_ACCESS_W as u8 | KVMI_PAGE_ACCESS_X as u8,
RWX = KVMI_PAGE_ACCESS_R as u8 | KVMI_PAGE_ACCESS_W as u8 | KVMI_PAGE_ACCESS_X as u8,
}
impl TryFrom<u8> for KVMiPageAccess {
type Error = &'static str;
fn try_from(access: u8) -> Result<Self, Self::Error> {
match access as u32 {
0 => Ok(KVMiPageAccess::NIL),
KVMI_PAGE_ACCESS_R => Ok(KVMiPageAccess::R),
KVMI_PAGE_ACCESS_W => Ok(KVMiPageAccess::W),
a if a == KVMI_PAGE_ACCESS_R | KVMI_PAGE_ACCESS_W => Ok(KVMiPageAccess::RW),
KVMI_PAGE_ACCESS_X => Ok(KVMiPageAccess::X),
a if a == KVMI_PAGE_ACCESS_R | KVMI_PAGE_ACCESS_X => Ok(KVMiPageAccess::RX),
a if a == KVMI_PAGE_ACCESS_W | KVMI_PAGE_ACCESS_X => Ok(KVMiPageAccess::WX),
a if a == KVMI_PAGE_ACCESS_R | KVMI_PAGE_ACCESS_W | KVMI_PAGE_ACCESS_X => {
Ok(KVMiPageAccess::RWX)
}
_ => Err("invalid access value"),
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum KVMiEventType {
PauseVCPU,
Cr {
cr_type: KVMiCr,
new: u64,
old: u64,
},
Msr {
msr_type: u32,
new: u64,
old: u64,
},
Breakpoint {
gpa: u64,
insn_len: u8,
},
Pagefault {
gva: u64,
gpa: u64,
access: KVMiPageAccess,
view: u16,
},
}
#[derive(Primitive, Debug, Copy, Clone)]
pub enum KVMiEventReply {
Continue = kvmi_sys::KVMI_EVENT_ACTION_CONTINUE as isize,
Retry = kvmi_sys::KVMI_EVENT_ACTION_RETRY as isize,
Crash = kvmi_sys::KVMI_EVENT_ACTION_CRASH as isize,
}
#[derive(Primitive, Debug, Copy, Clone, PartialEq)]
pub enum KVMiCr {
Cr0 = 0,
Cr3 = 3,
Cr4 = 4,
}
#[derive(Primitive, Debug, Copy, Clone, PartialEq)]
#[repr(u32)]
pub enum KVMiMsrIndices {
SysenterCs = 0x174,
SysenterEsp = 0x175,
SysenterEip = 0x176,
MsrEfer = 0xc0000080,
MsrStar = 0xc0000081,
MsrLstar = 0xc0000082,
}
pub enum SocketType {
UnixSocket(String),
VSock(u32),
}
#[derive(Debug)]
pub struct KVMiEvent {
pub vcpu: u16,
pub ev_type: KVMiEventType,
pub ffi_event: *mut kvmi_dom_event,
}
unsafe extern "C" fn new_guest_cb(
dom: *mut c_void,
_uuid: *mut [c_uchar; 16usize],
cb_ctx: *mut c_void,
) -> c_int {
debug!("KVMi new guest");
if cb_ctx.is_null() {
panic!("Unexpected null context");
}
let kvmi_con = &mut *(cb_ctx as *mut KVMiCon);
let mut connected = kvmi_con
.guard
.lock()
.expect("Failed to acquire connexion mutex");
kvmi_con.dom = dom;
*connected = true;
kvmi_con.condvar.notify_one();
0
}
unsafe extern "C" fn handshake_cb(
_arg1: *const kvmi_qemu2introspector,
_arg2: *mut kvmi_introspector2qemu,
_cb_ctx: *mut c_void,
) -> c_int {
debug!("KVMi handshake");
0
}
#[derive(Default)]
pub struct KvmMsrs {
internal_msrs: Box<kvm_msrs>,
}
impl KvmMsrs {
pub fn new() -> KvmMsrs {
let size = 6;
let layout = Layout::from_size_align(
2 * mem::size_of::<u32>() + size * mem::size_of::<kvm_msr_entry>(),
cmp::max(mem::align_of::<u32>(), mem::align_of::<kvm_msr_entry>()),
)
.unwrap();
#[allow(clippy::cast_ptr_alignment)]
let internal_msrs = unsafe { alloc_zeroed(layout) as *mut kvm_msrs };
unsafe { (*internal_msrs).nmsrs = size as _ };
let mut msrs = KvmMsrs {
internal_msrs: unsafe { Box::from_raw(internal_msrs) },
};
let mut msrs_as_slice = msrs.as_slice_mut();
msrs_as_slice[0].index = KVMiMsrIndices::SysenterCs as u32;
msrs_as_slice[1].index = KVMiMsrIndices::SysenterEsp as u32;
msrs_as_slice[2].index = KVMiMsrIndices::SysenterEip as u32;
msrs_as_slice[3].index = KVMiMsrIndices::MsrEfer as u32;
msrs_as_slice[4].index = KVMiMsrIndices::MsrStar as u32;
msrs_as_slice[5].index = KVMiMsrIndices::MsrLstar as u32;
msrs
}
fn get_internal_msrs_mut(&mut self) -> *mut kvm_msrs {
self.internal_msrs.as_mut()
}
pub fn as_slice_mut(&mut self) -> &mut [kvm_msr_entry] {
unsafe {
slice::from_raw_parts_mut(
self.internal_msrs.entries.as_mut_ptr() as *mut _,
self.internal_msrs.nmsrs.try_into().unwrap(),
)
}
}
pub fn as_slice(&self) -> &[kvm_msr_entry] {
unsafe {
slice::from_raw_parts(
self.internal_msrs.entries.as_ptr() as *mut _,
self.internal_msrs.nmsrs.try_into().unwrap(),
)
}
}
}
pub trait KVMIntrospectable: std::fmt::Debug {
fn init(&mut self, socket_type: SocketType) -> Result<(), Error>;
fn control_events(
&self,
vcpu: u16,
intercept_type: KVMiInterceptType,
enabled: bool,
) -> Result<(), Error>;
fn control_cr(&self, vcpu: u16, reg: KVMiCr, enabled: bool) -> Result<(), Error>;
fn control_msr(&self, vcpu: u16, reg: u32, enabled: bool) -> Result<(), Error>;
fn read_physical(&self, gpa: u64, buffer: &mut [u8]) -> Result<(), Error>;
fn write_physical(&self, gpa: u64, buffer: &[u8]) -> Result<(), Error>;
fn set_page_access(&self, gpa: u64, access: KVMiPageAccess, view: u16) -> Result<(), Error>;
fn pause(&self) -> Result<(), Error>;
fn resume(&mut self) -> Result<(), KVMiError>;
fn get_vcpu_count(&self) -> Result<u32, Error>;
fn get_registers(&self, vcpu: u16) -> Result<(kvm_regs, kvm_sregs, KvmMsrs), Error>;
fn set_registers(&self, vcpu: u16, regs: &kvm_regs) -> Result<(), Error>;
fn wait_and_pop_event(&self, ms: i32) -> Result<Option<KVMiEvent>, Error>;
fn reply(&self, event: &KVMiEvent, reply_type: KVMiEventReply) -> Result<(), Error>;
fn get_maximum_gfn(&self) -> Result<u64, Error>;
fn get_maximum_paddr(&self) -> Result<u64, KVMiError>;
}
pub fn create_kvmi() -> Result<KVMi, Box<dyn std::error::Error>> {
Ok(KVMi::new(unsafe { Libkvmi::new()? }))
}
#[derive(Debug)]
pub struct KVMi {
ctx: *mut c_void,
dom: *mut c_void,
libkvmi: Libkvmi,
expect_pause_ev: u32,
}
impl KVMi {
fn new(libkvmi: Libkvmi) -> KVMi {
KVMi {
ctx: null_mut(),
dom: null_mut(),
libkvmi,
expect_pause_ev: 0,
}
}
}
impl KVMIntrospectable for KVMi {
fn init(&mut self, socket_type: SocketType) -> Result<(), Error> {
let accept_db = Some(
new_guest_cb
as unsafe extern "C" fn(*mut c_void, *mut [c_uchar; 16usize], *mut c_void) -> c_int,
);
let hsk_cb = Some(
handshake_cb
as unsafe extern "C" fn(
*const kvmi_qemu2introspector,
*mut kvmi_introspector2qemu,
*mut c_void,
) -> c_int,
);
let mut kvmi_con = KVMiCon {
dom: null_mut(),
guard: Mutex::new(false),
condvar: Condvar::new(),
};
let cb_ctx: *mut c_void = &mut kvmi_con as *mut _ as *mut _;
let mut connected = kvmi_con
.guard
.lock()
.expect("Failed to acquire connection mutex");
self.ctx = match socket_type {
SocketType::UnixSocket(socket_path) => {
let socket_path = CString::new(socket_path).unwrap();
(self.libkvmi.init_unix_socket)(socket_path.as_ptr(), accept_db, hsk_cb, cb_ctx)
}
SocketType::VSock(socket_port) => {
(self.libkvmi.init_vsock)(socket_port, accept_db, hsk_cb, cb_ctx)
}
};
if !self.ctx.is_null() {
debug!("Waiting for connection...");
while !*connected {
connected = kvmi_con.condvar.wait(connected).unwrap();
}
}
self.dom = kvmi_con.dom;
Ok(())
}
fn control_events(
&self,
vcpu: u16,
intercept_type: KVMiInterceptType,
enabled: bool,
) -> Result<(), Error> {
let res = (self.libkvmi.control_events)(
self.dom,
vcpu,
intercept_type.to_i32().unwrap(),
enabled,
);
if res != 0 {
return Err(Error::last_os_error());
}
Ok(())
}
fn control_cr(&self, vcpu: u16, reg: KVMiCr, enabled: bool) -> Result<(), Error> {
let res = (self.libkvmi.control_cr)(self.dom, vcpu, reg.to_u32().unwrap(), enabled);
if res != 0 {
return Err(Error::last_os_error());
}
Ok(())
}
fn control_msr(&self, vcpu: u16, reg: u32, enabled: bool) -> Result<(), Error> {
let res = (self.libkvmi.control_msr)(self.dom, vcpu, reg, enabled);
if res != 0 {
return Err(Error::last_os_error());
}
Ok(())
}
fn read_physical(&self, gpa: u64, buffer: &mut [u8]) -> Result<(), Error> {
let buf_ptr = buffer.as_mut_ptr() as *mut c_void;
let res = (self.libkvmi.read_physical)(self.dom, gpa, buf_ptr, buffer.len());
if res != 0 {
return Err(Error::last_os_error());
}
Ok(())
}
fn write_physical(&self, gpa: u64, buffer: &[u8]) -> Result<(), Error> {
let buf_ptr = buffer.as_ptr() as *mut c_void;
let res = (self.libkvmi.write_physical)(self.dom, gpa, buf_ptr, buffer.len());
if res != 0 {
return Err(Error::last_os_error());
}
Ok(())
}
fn set_page_access(
&self,
mut gpa: u64,
access: KVMiPageAccess,
view: u16,
) -> Result<(), Error> {
let count: c_ushort = 1;
let res =
(self.libkvmi.set_page_access)(self.dom, &mut gpa, &mut (access as u8), count, view);
if res != 0 {
return Err(Error::last_os_error());
}
Ok(())
}
fn pause(&self) -> Result<(), Error> {
let vcpu_count = self.get_vcpu_count()?;
let res = (self.libkvmi.pause_all_vcpus)(self.dom, vcpu_count);
if res != 0 {
return Err(Error::last_os_error());
}
Ok(())
}
fn resume(&mut self) -> Result<(), KVMiError> {
if self.expect_pause_ev == 0 {
return Ok(());
}
while self.expect_pause_ev > 0 {
let kvmi_event = self
.wait_and_pop_event(1000)?
.ok_or(KVMiError::NoPauseEventAvailable)?;
match kvmi_event.ev_type {
KVMiEventType::PauseVCPU => {
debug!("VCPU {} - Received Pause Event", kvmi_event.vcpu);
self.expect_pause_ev -= 1;
self.reply(&kvmi_event, KVMiEventReply::Continue)?;
}
_ => return Err(KVMiError::UnexpectedEventWhileResuming(kvmi_event.ev_type)),
}
}
Ok(())
}
fn get_vcpu_count(&self) -> Result<u32, Error> {
let mut vcpu_count: c_uint = 0;
let res = (self.libkvmi.get_vcpu_count)(self.dom, &mut vcpu_count);
if res != 0 {
return Err(Error::last_os_error());
}
Ok(vcpu_count)
}
fn get_registers(&self, vcpu: u16) -> Result<(kvm_regs, kvm_sregs, KvmMsrs), Error> {
let mut regs: kvm_regs = unsafe { mem::MaybeUninit::<kvm_regs>::zeroed().assume_init() };
let mut sregs: kvm_sregs = unsafe { mem::MaybeUninit::<kvm_sregs>::zeroed().assume_init() };
let mut msrs = KvmMsrs::new();
let mut mode: c_uint = 0;
let res = (self.libkvmi.get_registers)(
self.dom,
vcpu,
&mut regs,
&mut sregs,
msrs.get_internal_msrs_mut(),
&mut mode,
);
if res != 0 {
return Err(Error::last_os_error());
}
Ok((regs, sregs, msrs))
}
fn set_registers(&self, vcpu: u16, regs: &kvm_regs) -> Result<(), Error> {
let res = (self.libkvmi.set_registers)(self.dom, vcpu, regs);
if res != 0 {
return Err(Error::last_os_error());
}
Ok(())
}
fn wait_and_pop_event(&self, ms: i32) -> Result<Option<KVMiEvent>, Error> {
let res = (self.libkvmi.wait_event)(self.dom, ms);
if res != 0 {
if Errno::last() == Errno::ETIMEDOUT {
return Ok(None);
}
return Err(Error::last_os_error());
}
let mut ev_ptr: *mut kvmi_dom_event = null_mut();
let ev_ptr_ptr = &mut ev_ptr as *mut _;
let res = (self.libkvmi.pop_event)(self.dom, ev_ptr_ptr);
if res != 0 {
return Err(Error::last_os_error());
}
let ev_type = unsafe {
let ev_u8 = (*ev_ptr).event.common.event.try_into().unwrap();
match KVMiInterceptType::from_u32(ev_u8).unwrap() {
KVMiInterceptType::PauseVCPU => KVMiEventType::PauseVCPU,
KVMiInterceptType::Breakpoint => KVMiEventType::Breakpoint {
gpa: (*ev_ptr).event.__bindgen_anon_1.breakpoint.gpa,
insn_len: (*ev_ptr).event.__bindgen_anon_1.breakpoint.insn_len,
},
KVMiInterceptType::Pagefault => KVMiEventType::Pagefault {
gpa: (*ev_ptr).event.__bindgen_anon_1.page_fault.gpa,
gva: (*ev_ptr).event.__bindgen_anon_1.page_fault.gva,
access: (*ev_ptr)
.event
.__bindgen_anon_1
.page_fault
.access
.try_into()
.unwrap(),
view: (*ev_ptr).event.__bindgen_anon_1.page_fault.view,
},
KVMiInterceptType::Cr => KVMiEventType::Cr {
cr_type: KVMiCr::from_i32(
(*ev_ptr).event.__bindgen_anon_1.cr.cr.try_into().unwrap(),
)
.unwrap(),
new: (*ev_ptr).event.__bindgen_anon_1.cr.new_value,
old: (*ev_ptr).event.__bindgen_anon_1.cr.old_value,
},
KVMiInterceptType::Msr => KVMiEventType::Msr {
msr_type: (*ev_ptr).event.__bindgen_anon_1.msr.msr,
new: (*ev_ptr).event.__bindgen_anon_1.msr.new_value,
old: (*ev_ptr).event.__bindgen_anon_1.msr.old_value,
},
}
};
let kvmi_event = KVMiEvent {
vcpu: unsafe { (*ev_ptr).event.common.vcpu },
ev_type,
ffi_event: ev_ptr,
};
Ok(Some(kvmi_event))
}
fn reply(&self, event: &KVMiEvent, reply_type: KVMiEventReply) -> Result<(), Error> {
#[repr(C)]
struct EventReplyCommon {
hdr: kvmi_vcpu_hdr,
common: kvmi_event_reply,
}
let mut reply_common =
unsafe { mem::MaybeUninit::<EventReplyCommon>::zeroed().assume_init() };
unsafe {
reply_common.hdr.vcpu = (*event.ffi_event).event.common.vcpu;
reply_common.common.event = (*event.ffi_event).event.common.event;
}
reply_common.common.action = reply_type.to_i32().unwrap().try_into().unwrap();
let seq = unsafe { (*event.ffi_event).seq };
let res = match event.ev_type {
KVMiEventType::PauseVCPU => {
let size = mem::size_of::<EventReplyCommon>();
let rpl_ptr: *const c_void = &reply_common as *const _ as *const c_void;
(self.libkvmi.reply_event)(self.dom, seq, rpl_ptr, size as usize)
}
KVMiEventType::Breakpoint {
gpa: _,
insn_len: _,
} => {
let size = mem::size_of::<EventReplyCommon>();
let rpl_ptr: *const c_void = &reply_common as *const _ as *const c_void;
(self.libkvmi.reply_event)(self.dom, seq, rpl_ptr, size as usize)
}
KVMiEventType::Pagefault {
gpa: _,
gva: _,
access: _,
view: _,
} => {
#[repr(C)]
struct EventReplyPagefault {
common: EventReplyCommon,
pf: kvmi_event_pf_reply,
}
let mut reply =
unsafe { mem::MaybeUninit::<EventReplyPagefault>::zeroed().assume_init() };
reply.common = reply_common;
reply.pf.ctx_addr = 0;
reply.pf.ctx_size = 0;
reply.pf.singlestep = 0;
reply.pf.rep_complete = 0;
reply.pf.padding = 0;
reply.pf.ctx_data = [0; 256];
let size = mem::size_of::<EventReplyPagefault>();
let rpl_ptr: *const c_void = &reply as *const _ as *const c_void;
(self.libkvmi.reply_event)(self.dom, seq, rpl_ptr, size as usize)
}
KVMiEventType::Cr {
cr_type: _,
new,
old: _,
} => {
#[repr(C)]
struct EventReplyCr {
common: EventReplyCommon,
cr: kvmi_event_cr_reply,
}
let mut reply = unsafe { mem::MaybeUninit::<EventReplyCr>::zeroed().assume_init() };
reply.common = reply_common;
reply.cr.new_val = new;
let size = mem::size_of::<EventReplyCr>();
let rpl_ptr: *const c_void = &reply as *const _ as *const c_void;
(self.libkvmi.reply_event)(self.dom, seq, rpl_ptr, size as usize)
}
KVMiEventType::Msr {
msr_type: _,
new,
old: _,
} => {
#[repr(C)]
struct EventReplyMsr {
common: EventReplyCommon,
msr: kvmi_event_msr_reply,
}
let mut reply =
unsafe { mem::MaybeUninit::<EventReplyMsr>::zeroed().assume_init() };
reply.common = reply_common;
reply.msr.new_val = new;
let size = mem::size_of::<EventReplyMsr>();
let rpl_ptr: *const c_void = &reply as *const _ as *const c_void;
(self.libkvmi.reply_event)(self.dom, seq, rpl_ptr, size as usize)
}
};
if res != 0 {
return Err(Error::last_os_error());
}
Ok(())
}
fn get_maximum_gfn(&self) -> Result<u64, Error> {
let mut max_gfn: u64 = 0;
let res = (self.libkvmi.get_maximum_gfn)(self.dom, &mut max_gfn);
if res > 0 {
return Err(Error::last_os_error());
}
Ok(max_gfn)
}
fn get_maximum_paddr(&self) -> Result<u64, KVMiError> {
let max_gfn = self.get_maximum_gfn()?;
Ok(max_gfn << PAGE_SHIFT)
}
}
impl Drop for KVMi {
fn drop(&mut self) {
if !self.ctx.is_null() {
(self.libkvmi.uninit)(self.ctx);
self.ctx = null_mut();
}
if !self.dom.is_null() {
(self.libkvmi.domain_close)(self.dom, true);
self.dom = null_mut();
}
}
}
impl Drop for KVMiEvent {
fn drop(&mut self) {
unsafe { free(self.ffi_event as *mut c_void) };
}
}