From 015e5724fad2f04a4c01f74c325d83a8d5fa7247 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Fri, 15 May 2026 07:24:06 +0200 Subject: [PATCH 1/4] loader_gen: Expose ELF module on non-x86 architectures Signed-off-by: Aelin Reidel --- src/lib.rs | 14 ++++++++++++-- src/loader_gen/{x86_64 => }/elf.rs | 0 src/loader_gen/mod.rs | 12 +++++++++++- src/loader_gen/x86_64/mod.rs | 9 --------- 4 files changed, 23 insertions(+), 12 deletions(-) rename src/loader_gen/{x86_64 => }/elf.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index 7d85390b..05909373 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,9 +127,19 @@ pub mod configurator; pub mod loader; #[allow(clippy::undocumented_unsafe_blocks)] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any( + all(target_arch = "aarch64", feature = "elf"), + all(target_arch = "riscv64", feature = "elf"), + target_arch = "x86", + target_arch = "x86_64" +))] mod loader_gen; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any( + all(target_arch = "aarch64", feature = "elf"), + all(target_arch = "riscv64", feature = "elf"), + target_arch = "x86", + target_arch = "x86_64" +))] pub use loader_gen::*; extern crate vm_memory; diff --git a/src/loader_gen/x86_64/elf.rs b/src/loader_gen/elf.rs similarity index 100% rename from src/loader_gen/x86_64/elf.rs rename to src/loader_gen/elf.rs diff --git a/src/loader_gen/mod.rs b/src/loader_gen/mod.rs index 31010c71..b280dce0 100644 --- a/src/loader_gen/mod.rs +++ b/src/loader_gen/mod.rs @@ -9,7 +9,17 @@ //! Bindgen autogenerated structs for boot parameters. -#![cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86_64; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub use x86_64::*; + +#[cfg(feature = "elf")] +pub mod elf; diff --git a/src/loader_gen/x86_64/mod.rs b/src/loader_gen/x86_64/mod.rs index 0d1a6c57..71d8510f 100644 --- a/src/loader_gen/x86_64/mod.rs +++ b/src/loader_gen/x86_64/mod.rs @@ -10,19 +10,10 @@ //! Bindgen autogenerated structs for `x86_64` boot parameters. #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(missing_docs)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] // Hide the autogenerated documentation for bindgen'ed sources. #[doc(hidden)] pub mod bootparam; -#[cfg(feature = "elf")] -pub mod elf; - #[cfg(feature = "elf")] pub mod start_info; From 638a941112f32fa7ef1650ac45d07c4043b46ba0 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Fri, 15 May 2026 07:25:41 +0200 Subject: [PATCH 2/4] loader_gen: elf: Add ELFDATA2MSB constant Signed-off-by: Aelin Reidel --- src/loader_gen/elf.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/loader_gen/elf.rs b/src/loader_gen/elf.rs index 3932e94f..23326da7 100644 --- a/src/loader_gen/elf.rs +++ b/src/loader_gen/elf.rs @@ -37,6 +37,7 @@ pub const ELFMAG2: u8 = b'L'; pub const ELFMAG3: u8 = b'F'; pub const ELFDATA2LSB: u32 = 1; +pub const ELFDATA2MSB: u32 = 2; pub type __s8 = ::std::os::raw::c_schar; pub type __u8 = ::std::os::raw::c_uchar; From 01051bfa6441ce91182f8a2f4c753148f7bb7ce0 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Fri, 15 May 2026 07:26:20 +0200 Subject: [PATCH 3/4] loader: Add support for loading ELF on aarch64 and riscv64 These lack PVH support, so we conditionally don't compile the code related to that. Since aarch64 can technically also be big-endian, I also extended the code to not check for little endian, but rather native endian. I have however not run the testsuite on aarch64_be, only on aarch64. Signed-off-by: Aelin Reidel --- README.md | 11 ++++---- src/loader/elf/mod.rs | 64 +++++++++++++++++++++++++++++++++++++------ src/loader/mod.rs | 60 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 116 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index f8627ea2..32521f63 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,10 @@ [![crates.io](https://img.shields.io/crates/v/linux-loader)](https://crates.io/crates/linux-loader) [![docs.rs](https://img.shields.io/docsrs/linux-loader)](https://docs.rs/linux-loader/) -The `linux-loader` crate offers support for loading raw ELF (`vmlinux`) and -compressed big zImage (`bzImage`) format kernel images on `x86_64` and PE -(`Image`) kernel images on `aarch64` and `riscv64`. ELF support includes the +The `linux-loader` crate offers support for loading raw ELF (`vmlinux`) on +`aarch64`, `riscv64` and `x86_64`, compressed big zImage (`bzImage`) format +kernel images on `x86_64` and PE (`Image`) kernel images on `aarch64` and +`riscv64`. ELF support includes the [Linux](https://www.kernel.org/doc/Documentation/x86/boot.txt) and [PVH](https://xenbits.xen.org/docs/unstable/misc/pvh.html) boot protocols. @@ -16,8 +17,8 @@ much of the boot process remains the VMM's responsibility. See [Usage] for detai - Parsing and loading kernel images into guest memory. - `x86_64`: `vmlinux` (raw ELF image), `bzImage` - - `aarch64`: `Image` - - `riscv64`: `Image` + - `aarch64`: `vmlinux` (raw ELF image), `Image` + - `riscv64`: `vmlinux` (raw ELF image), `Image` - Parsing and building the kernel command line. - Loading device tree blobs (`aarch64` and `riscv64`). - Configuring boot parameters using the exported primitives. diff --git a/src/loader/elf/mod.rs b/src/loader/elf/mod.rs index d7bf51a8..1723d634 100644 --- a/src/loader/elf/mod.rs +++ b/src/loader/elf/mod.rs @@ -10,11 +10,20 @@ //! Traits and structs for loading elf image kernels into guest memory. -#![cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] +#![cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) +))] use std::fmt; use std::io::{Read, Seek, SeekFrom}; use std::mem; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] use std::result; use vm_memory::{ @@ -23,6 +32,7 @@ use vm_memory::{ use crate::loader::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; use crate::loader_gen::elf; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub use crate::loader_gen::start_info; // SAFETY: The layout of the structure is fixed and can be initialized by @@ -54,6 +64,8 @@ pub enum Error { InvalidProgramHeaderAddress, /// Invalid entry address. InvalidEntryAddress, + /// Loaded little endian binary on a big endian platform. + LittleEndianElfOnBig, /// Overflow occurred during an arithmetic operation. Overflow, /// Unable to read ELF header. @@ -88,6 +100,9 @@ impl fmt::Display for Error { Error::InvalidProgramHeaderOffset => "Invalid program header offset", Error::InvalidProgramHeaderAddress => "Invalid Program Header Address", Error::InvalidEntryAddress => "Invalid entry address", + Error::LittleEndianElfOnBig => { + "Trying to load little-endian binary on big-endian machine" + } Error::Overflow => "Overflow occurred during an arithmetic operation", Error::ReadElfHeader => "Unable to read elf header", Error::ReadKernelImage => "Unable to read kernel image", @@ -148,9 +163,14 @@ impl Elf { { return Err(Error::InvalidElfMagicNumber); } + #[cfg(target_endian = "little")] if ehdr.e_ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 { return Err(Error::BigEndianElfOnLittle); } + #[cfg(target_endian = "big")] + if ehdr.e_ident[elf::EI_DATA as usize] != elf::ELFDATA2MSB as u8 { + return Err(Error::LittleEndianElfOnBig); + } if ehdr.e_phentsize as usize != mem::size_of::() { return Err(Error::InvalidProgramHeaderSize); } @@ -252,6 +272,7 @@ impl KernelLoader for Elf { // Read in each section pointed to by the program headers. for phdr in phdrs { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] if phdr.p_type != elf::PT_LOAD || phdr.p_filesz == 0 { if phdr.p_type == elf::PT_NOTE { // The PVH boot protocol currently requires that the kernel is loaded at @@ -294,13 +315,17 @@ impl KernelLoader for Elf { } // elf image has no setup_header which is defined for bzImage - loader_result.setup_header = None; + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + loader_result.setup_header = None; + } Ok(loader_result) } } // Size of string "Xen", including the terminating NULL. +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] const PVH_NOTE_STR_SZ: usize = 4; /// Examines a supplied elf program header of type `PT_NOTE` to determine if it contains an entry @@ -309,6 +334,7 @@ const PVH_NOTE_STR_SZ: usize = 4; /// with paging disabled, as described by the PVH boot protocol. /// Returns the encoded entry point address, or `None` if no `XEN_ELFNOTE_PHYS32_ENTRY` entries /// are found in the note header. +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn parse_elf_note(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result where F: Read + ReadVolatile + Seek, @@ -415,6 +441,7 @@ where /// /// Returns the smallest x with alignment `align` so that x >= addr if the alignment is a power of /// 2, or an error otherwise. +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn align_up(addr: u64, align: u64) -> result::Result { if !align.is_power_of_two() { return Err(Error::Align); @@ -448,22 +475,27 @@ mod tests { v } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_elfnote() -> Vec { include_bytes!("test_elfnote.bin").to_vec() } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_elfnote_8byte_align() -> Vec { include_bytes!("test_elfnote_8byte_align.bin").to_vec() } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_dummy_elfnote() -> Vec { include_bytes!("test_dummy_note.bin").to_vec() } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_invalid_pvh_note() -> Vec { include_bytes!("test_invalid_pvh_note.bin").to_vec() } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_elfnote_bad_align() -> Vec { include_bytes!("test_bad_align.bin").to_vec() } @@ -522,15 +554,27 @@ mod tests { #[test] fn test_bad_endian() { - // Only little endian is supported. + // Only native endian is supported. let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x0); let mut bad_image = make_elf_bin(); - bad_image[0x5] = 2; - assert_eq!( - Some(KernelLoaderError::Elf(Error::BigEndianElfOnLittle)), - Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err() - ); + + #[cfg(target_endian = "little")] + { + bad_image[0x5] = elf::ELFDATA2MSB as u8; + assert_eq!( + Some(KernelLoaderError::Elf(Error::BigEndianElfOnLittle)), + Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err() + ); + } + #[cfg(target_endian = "big")] + { + bad_image[0x5] = elf::ELFDATA2LSB as u8; + assert_eq!( + Some(KernelLoaderError::Elf(Error::LittleEndianElfOnBig)), + Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err() + ); + } } #[test] @@ -547,6 +591,7 @@ mod tests { } #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn test_load_pvh() { let gm = create_guest_mem(); let pvhnote_image = make_elfnote(); @@ -571,6 +616,7 @@ mod tests { } #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn test_dummy_elfnote() { let gm = create_guest_mem(); let dummynote_image = make_dummy_elfnote(); @@ -582,6 +628,7 @@ mod tests { } #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn test_bad_elfnote() { let gm = create_guest_mem(); let badnote_image = make_invalid_pvh_note(); @@ -592,6 +639,7 @@ mod tests { } #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn test_load_pvh_with_align() { // Alignment of ELF notes is always const value (4-bytes), ELF notes parse should not get Align // error. diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 4f1c5d21..caf98d17 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -36,9 +36,25 @@ pub mod pe; #[cfg(all(any(target_arch = "aarch64", target_arch = "riscv64"), feature = "pe"))] pub use pe::*; -#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))] +#[cfg(all( + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ), + feature = "elf" +))] pub mod elf; -#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))] +#[cfg(all( + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ), + feature = "elf" +))] pub use elf::*; #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "bzimage"))] @@ -54,7 +70,15 @@ pub enum Error { Bzimage(bzimage::Error), /// Failed to load elf image. - #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] + #[cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) + ))] Elf(elf::Error), /// Failed to load PE image. @@ -84,7 +108,15 @@ impl fmt::Display for Error { match self { #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] Error::Bzimage(ref e) => write!(f, "failed to load bzImage kernel image: {e}"), - #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] + #[cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) + ))] Error::Elf(ref e) => write!(f, "failed to load ELF kernel image: {e}"), #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] Error::Pe(ref e) => write!(f, "failed to load PE kernel image: {e}"), @@ -103,7 +135,15 @@ impl std::error::Error for Error { match self { #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] Error::Bzimage(ref e) => Some(e), - #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] + #[cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) + ))] Error::Elf(ref e) => Some(e), #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] Error::Pe(ref e) => Some(e), @@ -117,7 +157,15 @@ impl std::error::Error for Error { } } -#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] +#[cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) +))] impl From for Error { fn from(err: elf::Error) -> Self { Error::Elf(err) From 70cb872c192cc3669b4e1c87075747919a9050f9 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Sun, 17 May 2026 00:07:15 +0200 Subject: [PATCH 4/4] loader: Move load_dtb to top-level module This method can also be used when loading ELF files, hence move it to the loader module level. Signed-off-by: Aelin Reidel --- src/loader/mod.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++ src/loader/pe/mod.rs | 40 ------------------------------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/loader/mod.rs b/src/loader/mod.rs index caf98d17..1a8dd595 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -20,6 +20,8 @@ extern crate vm_memory; use std::fmt; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +use std::io::SeekFrom; use std::io::{Read, Seek}; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] @@ -95,6 +97,19 @@ pub enum Error { InvalidKernelStartAddress, /// Memory to load kernel image is too small. MemoryOverflow, + + /// Unable to seek to DTB start. + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + SeekDtbStart, + /// Unable to seek to DTB end. + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + SeekDtbEnd, + /// Device tree binary too big. + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + DtbTooBig, + /// Unable to read DTB image + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + ReadDtbImage, } /// A specialized [`Result`] type for the kernel loader. @@ -126,6 +141,15 @@ impl fmt::Display for Error { Error::CommandLineOverflow => write!(f, "command line overflowed guest memory"), Error::InvalidKernelStartAddress => write!(f, "invalid kernel start address"), Error::MemoryOverflow => write!(f, "memory to load kernel image is not enough"), + + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::SeekDtbStart => write!(f, "unable to seek DTB start"), + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::SeekDtbEnd => write!(f, "unable to seek DTB end"), + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::DtbTooBig => write!(f, "device tree image too big"), + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::ReadDtbImage => write!(f, "unable to read DTB image"), } } } @@ -153,6 +177,11 @@ impl std::error::Error for Error { Error::CommandLineOverflow => None, Error::InvalidKernelStartAddress => None, Error::MemoryOverflow => None, + + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::SeekDtbStart | Error::SeekDtbEnd | Error::DtbTooBig | Error::ReadDtbImage => { + None + } } } } @@ -297,6 +326,34 @@ pub fn load_cmdline( Ok(()) } +/// Writes the device tree to the given memory slice. +/// +/// # Arguments +/// +/// * `guest_mem` - A u8 slice that will be partially overwritten by the device tree blob. +/// * `guest_addr` - The address in `guest_mem` at which to load the device tree blob. +/// * `dtb_image` - The device tree blob. +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +pub fn load_dtb( + guest_mem: &M, + guest_addr: GuestAddress, + dtb_image: &mut F, +) -> Result<()> +where + F: ReadVolatile + Read + Seek, +{ + let dtb_size = dtb_image + .seek(SeekFrom::End(0)) + .map_err(|_| Error::SeekDtbEnd)? as usize; + if dtb_size > 0x200000 { + return Err(Error::DtbTooBig); + } + dtb_image.rewind().map_err(|_| Error::SeekDtbStart)?; + guest_mem + .read_exact_volatile_from(guest_addr, dtb_image, dtb_size) + .map_err(|_| Error::ReadDtbImage) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/loader/pe/mod.rs b/src/loader/pe/mod.rs index a22dc2fa..0631a942 100644 --- a/src/loader/pe/mod.rs +++ b/src/loader/pe/mod.rs @@ -30,18 +30,10 @@ pub enum Error { SeekImageEnd, /// Unable to seek to Image header. SeekImageHeader, - /// Unable to seek to DTB start. - SeekDtbStart, - /// Unable to seek to DTB end. - SeekDtbEnd, - /// Device tree binary too big. - DtbTooBig, /// Unable to read kernel image. ReadKernelImage, /// Unable to read Image header. ReadImageHeader, - /// Unable to read DTB image - ReadDtbImage, /// Invalid Image binary. InvalidImage, /// Invalid Image magic number. @@ -56,12 +48,8 @@ impl fmt::Display for Error { Error::SeekImageEnd => "unable to seek Image end", Error::SeekImageHeader => "unable to seek Image header", Error::ReadImageHeader => "unable to read Image header", - Error::ReadDtbImage => "unable to read DTB image", - Error::SeekDtbStart => "unable to seek DTB start", - Error::SeekDtbEnd => "unable to seek DTB end", Error::InvalidImage => "invalid Image", Error::InvalidImageMagicNumber => "invalid Image magic number", - Error::DtbTooBig => "device tree image too big", Error::ReadKernelImage => "unable to read kernel image", Error::InvalidBaseAddrAlignment => "base address not aligned to 2 MB", }; @@ -207,34 +195,6 @@ impl KernelLoader for PE { } } -/// Writes the device tree to the given memory slice. -/// -/// # Arguments -/// -/// * `guest_mem` - A u8 slice that will be partially overwritten by the device tree blob. -/// * `guest_addr` - The address in `guest_mem` at which to load the device tree blob. -/// * `dtb_image` - The device tree blob. -#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -pub fn load_dtb( - guest_mem: &M, - guest_addr: GuestAddress, - dtb_image: &mut F, -) -> Result<()> -where - F: ReadVolatile + Read + Seek, -{ - let dtb_size = dtb_image - .seek(SeekFrom::End(0)) - .map_err(|_| Error::SeekDtbEnd)? as usize; - if dtb_size > 0x200000 { - return Err(Error::DtbTooBig.into()); - } - dtb_image.rewind().map_err(|_| Error::SeekDtbStart)?; - guest_mem - .read_exact_volatile_from(guest_addr, dtb_image, dtb_size) - .map_err(|_| Error::ReadDtbImage.into()) -} - #[cfg(test)] mod tests { use super::*;