From 19b797f1a29e9dcf8c61a65b2afae90358b7a28b Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Tue, 9 Jan 2024 22:39:32 -0800 Subject: [PATCH] implement elf loader --- xencall/Cargo.toml | 4 + xencall/examples/version_capabilities.rs | 8 + xencall/src/domctl.rs | 33 +++- xencall/src/lib.rs | 25 ++- xencall/src/sys.rs | 82 ++++++--- xenclient/Cargo.toml | 10 ++ xenclient/examples/boot.rs | 10 ++ xenclient/examples/simple.rs | 2 +- xenclient/src/boot.rs | 213 +++++++++++++++++++++++ xenclient/src/lib.rs | 2 + xenclient/src/sys.rs | 19 ++ 11 files changed, 383 insertions(+), 25 deletions(-) create mode 100644 xencall/examples/version_capabilities.rs create mode 100644 xenclient/examples/boot.rs create mode 100644 xenclient/src/boot.rs create mode 100644 xenclient/src/sys.rs diff --git a/xencall/Cargo.toml b/xencall/Cargo.toml index 163d157..c5328b5 100644 --- a/xencall/Cargo.toml +++ b/xencall/Cargo.toml @@ -22,3 +22,7 @@ path = "examples/domain_info.rs" [[example]] name = "xencall-domain-create" path = "examples/domain_create.rs" + +[[example]] +name = "xencall-version-capabilities" +path = "examples/version_capabilities.rs" diff --git a/xencall/examples/version_capabilities.rs b/xencall/examples/version_capabilities.rs new file mode 100644 index 0000000..a306997 --- /dev/null +++ b/xencall/examples/version_capabilities.rs @@ -0,0 +1,8 @@ +use xencall::{XenCall, XenCallError}; + +fn main() -> Result<(), XenCallError> { + let mut call = XenCall::open()?; + let info = call.get_version_capabilities()?; + println!("{:?}", info); + Ok(()) +} diff --git a/xencall/src/domctl.rs b/xencall/src/domctl.rs index dafb0e2..e5e5bd8 100644 --- a/xencall/src/domctl.rs +++ b/xencall/src/domctl.rs @@ -1,6 +1,7 @@ use crate::sys::{ - ArchDomainConfig, CreateDomain, DomCtl, DomCtlValue, GetDomainInfo, HYPERVISOR_DOMCTL, - XEN_DOMCTL_CREATEDOMAIN, XEN_DOMCTL_GETDOMAININFO, XEN_DOMCTL_INTERFACE_VERSION, + ArchDomainConfig, CreateDomain, DomCtl, DomCtlValue, GetDomainInfo, MaxMem, MaxVcpus, + HYPERVISOR_DOMCTL, XEN_DOMCTL_CREATEDOMAIN, XEN_DOMCTL_GETDOMAININFO, + XEN_DOMCTL_INTERFACE_VERSION, XEN_DOMCTL_MAX_MEM, XEN_DOMCTL_MAX_VCPUS, }; use crate::{XenCall, XenCallError}; use std::ffi::c_ulong; @@ -70,4 +71,32 @@ impl DomainControl<'_> { domid: domctl.domid, }) } + + pub fn set_max_mem(&mut self, domid: u32, memkb: u64) -> Result<(), XenCallError> { + let domctl = DomCtl { + cmd: XEN_DOMCTL_MAX_MEM, + interface_version: XEN_DOMCTL_INTERFACE_VERSION, + domid, + value: DomCtlValue { + max_mem: MaxMem { max_memkb: memkb }, + }, + }; + self.call + .hypercall1(HYPERVISOR_DOMCTL, addr_of!(domctl) as c_ulong)?; + Ok(()) + } + + pub fn set_max_vcpus(&mut self, domid: u32, max_vcpus: u32) -> Result<(), XenCallError> { + let domctl = DomCtl { + cmd: XEN_DOMCTL_MAX_VCPUS, + interface_version: XEN_DOMCTL_INTERFACE_VERSION, + domid, + value: DomCtlValue { + max_cpus: MaxVcpus { max_vcpus }, + }, + }; + self.call + .hypercall1(HYPERVISOR_DOMCTL, addr_of!(domctl) as c_ulong)?; + Ok(()) + } } diff --git a/xencall/src/lib.rs b/xencall/src/lib.rs index 13ac01a..834a365 100644 --- a/xencall/src/lib.rs +++ b/xencall/src/lib.rs @@ -1,13 +1,16 @@ pub mod domctl; pub mod sys; -use crate::sys::Hypercall; +use crate::sys::{ + Hypercall, Mmap, XenCapabilitiesInfo, HYPERVISOR_XEN_VERSION, XENVER_CAPABILITIES, +}; use nix::errno::Errno; use std::error::Error; use std::ffi::{c_long, c_ulong}; use std::fmt::{Display, Formatter}; use std::fs::{File, OpenOptions}; use std::os::fd::AsRawFd; +use std::ptr::addr_of; pub struct XenCall { pub handle: File, @@ -116,4 +119,24 @@ impl XenCall { ) -> Result { self.hypercall(op, [arg1, arg2, arg3, arg4, arg5]) } + + pub fn mmap(&mut self, mmap: Mmap) -> Result { + unsafe { + let mut mmap = mmap.clone(); + let result = sys::mmap(self.handle.as_raw_fd(), &mut mmap)?; + Ok(result as c_long) + } + } + + pub fn get_version_capabilities(&mut self) -> Result { + let info = XenCapabilitiesInfo { + capabilities: [0; 1024], + }; + self.hypercall2( + HYPERVISOR_XEN_VERSION, + XENVER_CAPABILITIES, + addr_of!(info) as c_ulong, + )?; + Ok(info) + } } diff --git a/xencall/src/sys.rs b/xencall/src/sys.rs index 2362a70..1514247 100644 --- a/xencall/src/sys.rs +++ b/xencall/src/sys.rs @@ -1,6 +1,6 @@ /// Handwritten hypercall bindings. use nix::ioctl_readwrite_bad; -use std::ffi::c_ulong; +use std::ffi::{c_char, c_int, c_ulong}; use uuid::Uuid; #[repr(C)] @@ -10,9 +10,27 @@ pub struct Hypercall { pub arg: [c_ulong; 5], } +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct MmapEntry { + pub va: u64, + pub mfn: u64, + pub npages: u64, +} + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct Mmap { + pub num: c_int, + pub dom: u16, + pub entry: *mut MmapEntry, +} + const IOCTL_PRIVCMD_HYPERCALL: u64 = 0x305000; +const IOCTL_PRIVCMD_MMAP: u64 = 0x105002; ioctl_readwrite_bad!(hypercall, IOCTL_PRIVCMD_HYPERCALL, Hypercall); +ioctl_readwrite_bad!(mmap, IOCTL_PRIVCMD_MMAP, Mmap); pub const HYPERVISOR_SET_TRAP_TABLE: c_ulong = 0; pub const HYPERVISOR_MMU_UPDATE: c_ulong = 1; @@ -175,6 +193,8 @@ pub struct DomCtl { pub union DomCtlValue { pub create_domain: CreateDomain, pub get_domain_info: GetDomainInfo, + pub max_mem: MaxMem, + pub max_cpus: MaxVcpus, } #[repr(C)] @@ -194,6 +214,28 @@ pub struct CreateDomain { pub arch_domain_config: ArchDomainConfig, } +impl Default for CreateDomain { + fn default() -> Self { + CreateDomain { + ssidref: SECINITSID_DOMU, + handle: Uuid::new_v4().into_bytes(), + flags: 0, + iommu_opts: 0, + max_vcpus: 1, + max_evtchn_port: 1023, + max_grant_frames: -1, + max_maptrack_frames: -1, + grant_opts: 2, + vmtrace_size: 0, + cpupool_id: 0, + arch_domain_config: ArchDomainConfig { + emulation_flags: 0, + misc_flags: 0, + }, + } + } +} + #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct GetDomainInfo { @@ -223,27 +265,25 @@ pub struct ArchDomainConfig { pub misc_flags: u32, } +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct MaxMem { + pub max_memkb: u64, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct MaxVcpus { + pub max_vcpus: u32, +} + pub const XEN_DOMCTL_INTERFACE_VERSION: u32 = 0x00000015; pub const SECINITSID_DOMU: u32 = 13; -impl Default for CreateDomain { - fn default() -> Self { - CreateDomain { - ssidref: SECINITSID_DOMU, - handle: Uuid::new_v4().into_bytes(), - flags: 0, - iommu_opts: 0, - max_vcpus: 1, - max_evtchn_port: 1023, - max_grant_frames: -1, - max_maptrack_frames: -1, - grant_opts: 2, - vmtrace_size: 0, - cpupool_id: 0, - arch_domain_config: ArchDomainConfig { - emulation_flags: 0, - misc_flags: 0, - }, - } - } +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct XenCapabilitiesInfo { + pub capabilities: [c_char; 1024], } + +pub const XENVER_CAPABILITIES: u64 = 3; diff --git a/xenclient/Cargo.toml b/xenclient/Cargo.toml index bc82c36..3b51b54 100644 --- a/xenclient/Cargo.toml +++ b/xenclient/Cargo.toml @@ -4,6 +4,12 @@ version = "0.0.1" edition = "2021" resolver = "2" +[dependencies] +elf = "0.7.4" +flate2 = "1.0" +xz2 = "0.1" +memchr = "2" + [dependencies.xencall] path = "../xencall" @@ -20,3 +26,7 @@ path = "src/lib.rs" [[example]] name = "xenclient-simple" path = "examples/simple.rs" + +[[example]] +name = "xenclient-boot" +path = "examples/boot.rs" diff --git a/xenclient/examples/boot.rs b/xenclient/examples/boot.rs new file mode 100644 index 0000000..1b4c85d --- /dev/null +++ b/xenclient/examples/boot.rs @@ -0,0 +1,10 @@ +use std::alloc::Layout; +use xenclient::boot::ElfLoader; +use xenclient::XenClientError; + +fn main() -> Result<(), XenClientError> { + let boot = ElfLoader::load_file_kernel("/boot/vmlinuz-6.1.0-17-amd64")?; + let ptr = unsafe { std::alloc::alloc(Layout::from_size_align(128 * 1024 * 1024, 16).unwrap()) }; + boot.load(ptr)?; + Ok(()) +} diff --git a/xenclient/examples/simple.rs b/xenclient/examples/simple.rs index 145b5dc..b767190 100644 --- a/xenclient/examples/simple.rs +++ b/xenclient/examples/simple.rs @@ -11,6 +11,6 @@ fn main() -> Result<(), XenClientError> { None, None, )); - client.create(2, config)?; + client.create(config)?; Ok(()) } diff --git a/xenclient/src/boot.rs b/xenclient/src/boot.rs new file mode 100644 index 0000000..d5b7ce4 --- /dev/null +++ b/xenclient/src/boot.rs @@ -0,0 +1,213 @@ +use crate::sys::{XEN_ELFNOTE_ENTRY, XEN_ELFNOTE_HV_START_LOW, XEN_ELFNOTE_VIRT_BASE}; +use crate::XenClientError; +use elf::abi::{PF_R, PF_W, PF_X, PT_LOAD, SHT_NOTE}; +use elf::endian::AnyEndian; +use elf::note::Note; +use elf::{ElfBytes, ParseError}; +use flate2::bufread::GzDecoder; +use memchr::memmem::find_iter; +use std::collections::HashMap; +use std::io::{BufReader, Read}; +use std::mem::size_of; +use xz2::bufread::XzDecoder; + +impl From for XenClientError { + fn from(value: ParseError) -> Self { + XenClientError::new(value.to_string().as_str()) + } +} + +pub struct ElfLoader { + data: Vec, +} + +fn xen_note_value_u64(endian: AnyEndian, notes: &HashMap>, key: u64) -> Option { + let value = notes.get(&key); + value?; + let value = value.unwrap(); + let bytes: Option<[u8; size_of::()]> = value.clone().try_into().ok(); + bytes?; + + Some(match endian { + AnyEndian::Little => u64::from_le_bytes(bytes.unwrap()), + AnyEndian::Big => u64::from_be_bytes(bytes.unwrap()), + }) +} + +impl ElfLoader { + pub fn new(data: Vec) -> ElfLoader { + ElfLoader { data } + } + + pub fn load_file(path: &str) -> Result { + let data = std::fs::read(path)?; + Ok(ElfLoader::new(data)) + } + + pub fn load_gz(data: &[u8]) -> Result { + let buff = BufReader::new(data); + let image = ElfLoader::read_one_stream(&mut GzDecoder::new(buff))?; + Ok(ElfLoader::new(image)) + } + + pub fn load_xz(data: &[u8]) -> Result { + let buff = BufReader::new(data); + let image = ElfLoader::read_one_stream(&mut XzDecoder::new(buff))?; + Ok(ElfLoader::new(image)) + } + + fn read_one_stream(read: &mut dyn Read) -> Result, XenClientError> { + let mut result: Vec = Vec::new(); + let mut buffer = [0u8; 8192]; + + loop { + match read.read(&mut buffer) { + Ok(size) => { + if size == 0 { + break; + } + result.extend_from_slice(&buffer[0..size]) + } + Err(error) => { + if !result.is_empty() { + break; + } + return Err(XenClientError::from(error)); + } + } + } + Ok(result) + } + + pub fn load_file_gz(path: &str) -> Result { + let file = std::fs::read(path)?; + ElfLoader::load_gz(file.as_slice()) + } + + pub fn load_file_xz(path: &str) -> Result { + let file = std::fs::read(path)?; + ElfLoader::load_xz(file.as_slice()) + } + + pub fn load_file_kernel(path: &str) -> Result { + let file = std::fs::read(path)?; + + for start in find_iter(file.as_slice(), &[0x1f, 0x8b]) { + if let Ok(elf) = ElfLoader::load_gz(&file[start..]) { + return Ok(elf); + } + } + + for start in find_iter(file.as_slice(), &[0xfd, 0x37, 0x7a, 0x58]) { + match ElfLoader::load_xz(&file[start..]) { + Ok(elf) => return Ok(elf), + Err(error) => { + println!("{}", error); + } + } + } + + Err(XenClientError::new( + "Unable to parse kernel image: unknown compression type", + )) + } + + pub fn load(&self, dst: *mut u8) -> Result<(), XenClientError> { + let elf = ElfBytes::::minimal_parse(self.data.as_slice())?; + let headers = elf.section_headers().ok_or(XenClientError::new( + "Unable to parse kernel image: section headers not found.", + ))?; + let mut linux_notes: HashMap> = HashMap::new(); + let mut xen_notes: HashMap> = HashMap::new(); + + for header in headers { + if header.sh_type != SHT_NOTE { + continue; + } + + let notes = elf.section_data_as_notes(&header)?; + for note in notes { + if let Note::Unknown(note) = note { + if note.name == "Linux" { + linux_notes.insert(note.n_type, note.desc.to_vec()); + } + + if note.name == "Xen" { + xen_notes.insert(note.n_type, note.desc.to_vec()); + continue; + } + } + } + } + + if linux_notes.is_empty() { + return Err(XenClientError::new( + "Provided kernel does not appear to be a Linux kernel image.", + )); + } + + if xen_notes.is_empty() { + return Err(XenClientError::new( + "Provided kernel does not have Xen support.", + )); + } + + let virt_base = xen_note_value_u64(elf.ehdr.endianness, &xen_notes, XEN_ELFNOTE_VIRT_BASE) + .ok_or(XenClientError::new( + "Unable to find virt_base note in kernel.", + ))?; + let entry = xen_note_value_u64(elf.ehdr.endianness, &xen_notes, XEN_ELFNOTE_ENTRY) + .ok_or(XenClientError::new("Unable to find entry note in kernel."))?; + let hv_start_low = + xen_note_value_u64(elf.ehdr.endianness, &xen_notes, XEN_ELFNOTE_HV_START_LOW).ok_or( + XenClientError::new("Unable to find hv_start_low note in kernel."), + )?; + + let mut start: u64 = u64::MAX; + let mut end: u64 = 0; + + let segments = elf.segments().ok_or(XenClientError::new( + "Unable to parse kernel image: segments not found.", + ))?; + for segment in segments { + if (segment.p_type != PT_LOAD) || (segment.p_flags & (PF_R | PF_W | PF_X)) == 0 { + continue; + } + let paddr = segment.p_paddr; + let memsz = segment.p_memsz; + if start > paddr { + start = paddr; + } + + if end < paddr + memsz { + end = paddr + memsz; + } + } + + let base_dst_addr = dst as u64; + for header in segments { + let paddr = header.p_paddr; + let filesz = header.p_filesz; + let memsz = header.p_memsz; + let dest = base_dst_addr + paddr - start; + let data = elf.segment_data(&header)?; + + unsafe { + std::ptr::copy(data.as_ptr(), dest as *mut u8, filesz as usize); + std::ptr::write_bytes((dest + filesz) as *mut u8, 0, (memsz - filesz) as usize); + } + } + + let virt_base = if virt_base == u64::MAX { 0 } else { virt_base }; + + let virt_kstart = start + virt_base; + let virt_kend = end + virt_base; + + println!("virt_kstart: {}", virt_kstart); + println!("virt_kend: {}", virt_kend); + println!("entry: {}", entry); + println!("hv_start_low: {}", hv_start_low); + + Ok(()) + } +} diff --git a/xenclient/src/lib.rs b/xenclient/src/lib.rs index ba02d76..6591451 100644 --- a/xenclient/src/lib.rs +++ b/xenclient/src/lib.rs @@ -1,4 +1,6 @@ +pub mod boot; pub mod create; +pub mod sys; use crate::create::DomainConfig; use std::error::Error; diff --git a/xenclient/src/sys.rs b/xenclient/src/sys.rs new file mode 100644 index 0000000..58c8b7e --- /dev/null +++ b/xenclient/src/sys.rs @@ -0,0 +1,19 @@ +pub const XEN_ELFNOTE_INFO: u64 = 0; +pub const XEN_ELFNOTE_ENTRY: u64 = 1; +pub const XEN_ELFNOTE_HYPERCALL_PAGE: u64 = 2; +pub const XEN_ELFNOTE_VIRT_BASE: u64 = 3; +pub const XEN_ELFNOTE_PADDR_OFFSET: u64 = 4; +pub const XEN_ELFNOTE_XEN_VERSION: u64 = 5; +pub const XEN_ELFNOTE_GUEST_OS: u64 = 6; +pub const XEN_ELFNOTE_GUEST_VERSION: u64 = 7; +pub const XEN_ELFNOTE_LOADER: u64 = 8; +pub const XEN_ELFNOTE_PAE_MODE: u64 = 9; +pub const XEN_ELFNOTE_FEATURES: u64 = 10; +pub const XEN_ELFNOTE_BSD_SYMTAB: u64 = 11; +pub const XEN_ELFNOTE_HV_START_LOW: u64 = 12; +pub const XEN_ELFNOTE_L1_MFN_VALID: u64 = 13; +pub const XEN_ELFNOTE_SUSPEND_CANCEL: u64 = 14; +pub const XEN_ELFNOTE_INIT_P2M: u64 = 15; +pub const XEN_ELFNOTE_MOD_START_PFN: u64 = 16; +pub const XEN_ELFNOTE_SUPPORTED_FEATURES: u64 = 17; +pub const XEN_ELFNOTE_PHYS32_ENTRY: u64 = 18;