Skip to content

Commit

Permalink
Add support for booting a bzImage Linux Kernel (#4189)
Browse files Browse the repository at this point in the history
  • Loading branch information
conradgrobler authored Jul 20, 2023
1 parent c64da1b commit 7dfefbc
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 34 deletions.
22 changes: 18 additions & 4 deletions linux_boot_params/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ impl CCSetupData {
/// For more details see the Linux kernel docs:
/// <https://www.kernel.org/doc/html/latest/x86/boot.html#the-real-mode-kernel-header>
#[repr(C, packed)]
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, FromBytes, AsBytes)]
pub struct SetupHeader {
/// The size of the setup code in 512-byte sectors.
///
Expand Down Expand Up @@ -254,7 +254,7 @@ pub struct SetupHeader {
/// Boot protocol option flags
///
/// Type: modify (obligatory)
pub loadflags: LoadFlags,
pub loadflags: u8,
/// Move to high memory size (used with hooks)
///
/// Type: modify (obligatory)
Expand Down Expand Up @@ -389,7 +389,7 @@ pub struct SetupHeader {
/// Boot protocol option flags
///
/// Type: read
pub xloadflags: XLoadFlags,
pub xloadflags: u16,
/// Maximum size of the kernel command line
///
/// Type: read
Expand Down Expand Up @@ -440,7 +440,7 @@ pub struct SetupHeader {
///
/// The 64-bit physical pointer to NULL terminated single linked list of struct setup_data.
/// This is used to define a more extensible boot parameters passing mechanism.
pub setup_data: *const SetupData,
pub setup_data: u64,
/// Preferred loading address
///
/// Type: read (reloc)
Expand Down Expand Up @@ -478,6 +478,20 @@ pub struct SetupHeader {
}
static_assertions::assert_eq_size!(SetupHeader, [u8; 123usize]);

impl SetupHeader {
pub fn setup_data(&self) -> *const SetupData {
self.setup_data as *const SetupData
}

pub fn load_flags(&self) -> Option<LoadFlags> {
LoadFlags::from_bits(self.loadflags)
}

pub fn x_load_flags(&self) -> Option<XLoadFlags> {
XLoadFlags::from_bits(self.xloadflags)
}
}

#[repr(C, packed)]
#[derive(Debug, Copy, Clone, FromBytes, AsBytes)]
pub struct BootE820Entry {
Expand Down
2 changes: 1 addition & 1 deletion oak_restricted_kernel/src/snp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub fn get_snp_page_addresses(info: &BootParams) -> SnpPageAddresses {
assert_page_in_valid_range(phys);
// The setup data is stored in a null-terminated linked list, so we step through the list until
// we find an entry with the right type or reach the end.
let mut setup_data_ptr = info.hdr.setup_data;
let mut setup_data_ptr = info.hdr.setup_data();
while !setup_data_ptr.is_null() {
assert_pointer_in_valid_range(setup_data_ptr);
// Safety: we have checked that the pointer is not null and at least points to memory within
Expand Down
95 changes: 94 additions & 1 deletion stage0/src/fw_cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ enum FwCfgItems {
CmdlineAddr = 0x0013,
CmdlineSize = 0x0014,
CmdlineData = 0x0015,
SetupAddr = 0x0016,
SetupSize = 0x0017,
SetupData = 0x0018,
FileDir = 0x0019,
E820ReservationTable = 0x8003,
}
Expand Down Expand Up @@ -109,6 +112,14 @@ impl Default for DirEntry {
}

impl DirEntry {
fn new_for_selector(size: u32, selector: FwCfgItems) -> Self {
Self {
size: size.to_be(),
select: (selector as u16).to_be(),
..Default::default()
}
}

pub fn name(&self) -> &CStr {
CStr::from_bytes_until_nul(&self.name).unwrap()
}
Expand Down Expand Up @@ -277,6 +288,89 @@ impl FwCfg {
Ok(len)
}

/// Reads the size of the kernel command-line.
pub fn read_cmdline_size(&mut self) -> Result<u32, &'static str> {
let mut cmdline_size: u32 = 0;
self.write_selector(FwCfgItems::CmdlineSize as u16)?;
self.read(&mut cmdline_size)?;
Ok(cmdline_size)
}

/// Gets an unnamed file representation of the kernel command-line using the dedicated selector.
///
/// Returns `None` if the kernel command-line size is 0.
pub fn get_cmdline_file(&mut self) -> Option<DirEntry> {
let size = self
.read_cmdline_size()
.expect("couldn't read cmdline size");
if size == 0 {
None
} else {
Some(DirEntry::new_for_selector(size, FwCfgItems::CmdlineData))
}
}

/// Reads the size of the initial RAM disk.
pub fn read_initrd_size(&mut self) -> Result<u32, &'static str> {
let mut initrd_size: u32 = 0;
self.write_selector(FwCfgItems::InitrdSize as u16)?;
self.read(&mut initrd_size)?;
Ok(initrd_size)
}

/// Gets an unnamed file representation of the initial RAM disk using the dedicated selector.
///
/// Returns `None` if the initial RAM disk size is 0.
pub fn get_initrd_file(&mut self) -> Option<DirEntry> {
let size = self.read_initrd_size().expect("couldn't read initrd size");
if size == 0 {
None
} else {
Some(DirEntry::new_for_selector(size, FwCfgItems::InitrdData))
}
}

/// Reads the size of the kernel.
pub fn read_kernel_size(&mut self) -> Result<u32, &'static str> {
let mut kernel_size: u32 = 0;
self.write_selector(FwCfgItems::KernelSize as u16)?;
self.read(&mut kernel_size)?;
Ok(kernel_size)
}

/// Gets an unnamed file representation of the kernel using the dedicated selector.
///
/// Returns `None` if the kernel size is 0.
pub fn get_kernel_file(&mut self) -> Option<DirEntry> {
let size = self.read_kernel_size().expect("couldn't read kernel size");
if size == 0 {
None
} else {
Some(DirEntry::new_for_selector(size, FwCfgItems::KernelData))
}
}

/// Reads the size of the setup information for the kernel.
pub fn read_setup_size(&mut self) -> Result<u32, &'static str> {
let mut setup_size: u32 = 0;
self.write_selector(FwCfgItems::SetupSize as u16)?;
self.read(&mut setup_size)?;
Ok(setup_size)
}

/// Gets an unnamed file representation of the kernel's setup information using the dedicated
/// selector.
///
/// Returns `None` if the kernel size is 0.
pub fn get_setup_file(&mut self) -> Option<DirEntry> {
let size = self.read_setup_size().expect("couldn't read setup size");
if size == 0 {
None
} else {
Some(DirEntry::new_for_selector(size, FwCfgItems::SetupData))
}
}

fn write_selector(&mut self, selector: u16) -> Result<(), &'static str> {
// Safety: we make sure the device is available when initializing FwCfg.
unsafe { self.selector.try_write(selector) }
Expand All @@ -297,7 +391,6 @@ impl FwCfg {

fn read_buf_dma(&mut self, buf: &mut [u8]) -> Result<(), &'static str> {
let mut chunks_mut = buf.chunks_mut(Size4KiB::SIZE as usize);

for chunk in chunks_mut.by_ref() {
self.read_chunk_dma(chunk)?;
}
Expand Down
6 changes: 4 additions & 2 deletions stage0/src/initramfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ pub fn try_load_initial_ram_disk(
e820_table: &[BootE820Entry],
kernel_info: &KernelInfo,
) -> Option<&'static [u8]> {
let path = CStr::from_bytes_with_nul(INITIAL_RAM_DISK_FILE_PATH).expect("invalid c-string");
let file = fw_cfg.find(path)?;
let file = fw_cfg.get_initrd_file().or_else(|| {
let path = CStr::from_bytes_with_nul(INITIAL_RAM_DISK_FILE_PATH).expect("invalid c-string");
fw_cfg.find(path)
})?;
let size = file.size();
let initrd_address =
find_suitable_dma_address(size, e820_table).expect("no suitable DMA address available");
Expand Down
79 changes: 58 additions & 21 deletions stage0/src/kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use oak_linux_boot_params::BootE820Entry;
use x86_64::{PhysAddr, VirtAddr};

/// The default start location and entry point for the kernel if a kernel wasn't supplied via the
/// QEMU fw_cfg device.
/// QEMU fw_cfg device. This is also used for the default location when loading a compressed kernel.
const DEFAULT_KERNEL_START: u64 = 0x200000;

/// The default size for the kernel if a kernel wasn't supplied via the QEMU fw_cfg device.
Expand Down Expand Up @@ -55,26 +55,37 @@ impl Default for KernelInfo {
}
}

/// Tries to load the kernel command-line from the fw_cfg device.
///
/// We first try to read it using the traditional selector. If it is not available there we try to
/// read it using a custom file path.
pub fn try_load_cmdline(fw_cfg: &mut FwCfg) -> Option<&'static CStr> {
let cmdline_path = CStr::from_bytes_with_nul(CMDLINE_FILE_PATH).expect("invalid c-string");
let cmdline_file = fw_cfg.find(cmdline_path)?;
let cmdline_size = cmdline_file.size();
// Make the buffer one byte longer so that the kernel command-line is null-terminated.
let (cmdline_file, buffer_size) = if let Some(cmdline_file) = fw_cfg.get_cmdline_file() {
// The provided value is already null-terminated.
let size = cmdline_file.size();
(cmdline_file, size)
} else {
let cmdline_path = CStr::from_bytes_with_nul(CMDLINE_FILE_PATH).expect("invalid c-string");
let cmdline_file = fw_cfg.find(cmdline_path)?;
// Make the buffer one byte longer so that the kernel command-line is null-terminated.
let size = cmdline_file.size() + 1;
(cmdline_file, size)
};
// Safety: len will always be at least 1 byte, and we don't care about alignment. If the
// allocation fails, we won't try coercing it into a slice.
let buf = unsafe {
let len = cmdline_size + 1;
NonNull::slice_from_raw_parts(
BOOT_ALLOC.allocate(Layout::from_size_align(len, 1).unwrap())?,
len,
BOOT_ALLOC.allocate(Layout::from_size_align(buffer_size, 1).unwrap())?,
buffer_size,
)
.as_mut()
};
let actual_size = fw_cfg
.read_file(&cmdline_file, buf)
.expect("could not read cmdline");
assert_eq!(
actual_size, cmdline_size,
actual_size,
cmdline_file.size(),
"cmdline size did not match expected size"
);

Expand All @@ -88,22 +99,33 @@ pub fn try_load_cmdline(fw_cfg: &mut FwCfg) -> Option<&'static CStr> {

/// Tries to load a kernel image from the QEMU fw_cfg device.
///
/// We expect the kernel to be available with a filename of "opt/stage0/elf_kernel". We only support
/// uncompressed ELF kernels at the moment.
/// We assume that a kernel file provided via the traditional seletor is a compressed kernel using
/// the bzImage format. We assume that a kernel file provided via the custom filename of
/// "opt/stage0/elf_kernel" is an uncompressed ELF file.
///
/// If it finds a RAM disk it returns the information about the kernel, otherwise `None`.
/// If it finds a kernel it returns the information about the kernel, otherwise `None`.
pub fn try_load_kernel_image(
fw_cfg: &mut FwCfg,
e820_table: &[BootE820Entry],
) -> Option<KernelInfo> {
let path = CStr::from_bytes_with_nul(KERNEL_FILE_PATH).expect("invalid c-string");
let file = fw_cfg.find(path)?;
let (file, bzimage) = if let Some(file) = fw_cfg.get_kernel_file() {
(file, true)
} else {
let path = CStr::from_bytes_with_nul(KERNEL_FILE_PATH).expect("invalid c-string");
let file = fw_cfg.find(path)?;
(file, false)
};
let size = file.size();

// We copy the kernel image to a temporary location where we can parse it.
let dma_address =
find_suitable_dma_address(size, e820_table).expect("no suitable DMA address available");
let start_address = crate::phys_to_virt(PhysAddr::new(dma_address.as_u64()));
let dma_address = if bzimage {
// For a compressed kernel we use the default start address.
PhysAddr::new(DEFAULT_KERNEL_START)
} else {
// For an Elf kernel we copy the kernel image to a temporary location at the end of
// available mapped virtual memory where we can parse it.
find_suitable_dma_address(size, e820_table).expect("no suitable DMA address available")
};
let start_address = crate::phys_to_virt(dma_address);
log::debug!("Kernel image size {}", size);
log::debug!(
"Kernel image start address {:#018x}",
Expand All @@ -117,9 +139,24 @@ pub fn try_load_kernel_image(
.expect("could not read kernel file");
assert_eq!(actual_size, size, "kernel size did not match expected size");

if bzimage {
// For a bzImage the 64-bit entry point is at offset 0x200 from the start of the 64-bit
// kernel. See <https://www.kernel.org/doc/html/v6.3/x86/boot.html>.
let entry = start_address + 0x200usize;
log::debug!("Kernel entry point {:#018x}", entry.as_u64());
Some(KernelInfo {
start_address,
size,
entry,
})
} else {
Some(parse_elf_file(buf, e820_table))
}
}

fn parse_elf_file(buf: &[u8], e820_table: &[BootE820Entry]) -> KernelInfo {
let mut kernel_start = VirtAddr::new(crate::TOP_OF_VIRTUAL_MEMORY);
let mut kernel_end = VirtAddr::new(0);

// We expect an uncompressed ELF kernel, so we parse it and lay it out in memory.
let file = ElfBytes::<AnyEndian>::minimal_parse(buf).expect("couldn't parse kernel header");

Expand Down Expand Up @@ -147,11 +184,11 @@ pub fn try_load_kernel_image(
log::debug!("Kernel start address {:#018x}", kernel_start.as_u64());
log::debug!("Kernel entry point {:#018x}", entry.as_u64());

Some(KernelInfo {
KernelInfo {
start_address: kernel_start,
size: kernel_size,
entry,
})
}
}

/// Loads a segment from an ELF file into memory.
Expand Down
2 changes: 2 additions & 0 deletions stage0/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ pub fn rust64_start(encrypted: u64) -> ! {

paging::map_additional_memory(encrypted);

zero_page.try_fill_hdr_from_setup_data(&mut fwcfg);

if snp {
let cc_blob = BOOT_ALLOC
.leak(oak_linux_boot_params::CCBlobSevInfo::new(
Expand Down
Loading

0 comments on commit 7dfefbc

Please sign in to comment.