diff --git a/rust/pv/src/brcb.rs b/rust/pv/src/brcb.rs index bc370d90..ac481fce 100644 --- a/rust/pv/src/brcb.rs +++ b/rust/pv/src/brcb.rs @@ -7,7 +7,7 @@ use std::{ mem::size_of, }; -use log::debug; +use log::{debug, warn}; use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32, U64}; // (SE) boot request control block aka SE header @@ -46,6 +46,81 @@ impl TryFrom> for BootHdrTags { } } +/// Struct representing the Secure Execution boot image metadata +#[allow(unused)] +#[repr(packed)] +#[derive(Debug, Clone, FromBytes, FromZeroes, AsBytes, PartialEq, Eq)] +pub struct SeImgMetaData { + /// Magic value + magic: [u8; 8], + /// Secure Execution header offset in the image + hdr_off: U64, + /// Version + version: U32, + /// IPIB offset in the image + ipib_off: U64, +} +assert_size!(SeImgMetaData, 28); + +impl SeImgMetaData { + /// Address in the Secure Execution boot image + pub const OFFSET: u64 = 0xc000; + /// V1 of the Secure Execution boot image metadata + const V1: u32 = 0x1; + + /// Create v1 Secure Execution image metadata. + pub fn new_v1(hdr_off: u64, ipib_off: u64) -> Self { + Self { + magic: Self::MAGIC, + version: Self::V1.into(), + hdr_off: hdr_off.into(), + ipib_off: ipib_off.into(), + } + } + + fn seek_start(img: &mut R) -> Result + where + R: Read + Seek, + { + const BUF_SIZE: i64 = 8; + static_assert!(SeImgMetaData::MAGIC.len() == BUF_SIZE as usize); + + let mut buf = [0; BUF_SIZE as usize]; + match img.seek(std::io::SeekFrom::Start(Self::OFFSET)) { + Ok(it) => it, + Err(_) => return Ok(false), + }; + match img.read_exact(&mut buf) { + Ok(it) => it, + Err(_) => return Ok(false), + } + + if Self::starts_with_magic(&buf) { + // go back to the beginning of the metadata + img.seek(Current(-BUF_SIZE))?; + return Ok(true); + } + Ok(false) + } + + /// Gets the bytes of this value. + #[inline(always)] + pub fn as_bytes(&self) -> &[u8] { + ::as_bytes(self) + } + + /// Returns the version of this [`SeImgMetaData`]. + pub fn version(&self) -> u32 { + self.version.into() + } +} + +/// Magic value for the metadata of a Secure Execution boot image +impl MagicValue<8> for SeImgMetaData { + // ASCII `SeImgLnx` + const MAGIC: [u8; 8] = [0x53, 0x65, 0x49, 0x6d, 0x67, 0x4c, 0x6e, 0x78]; +} + /// Magic value for a SE-(boot)header #[derive(Debug)] pub struct BootHdrMagic; @@ -65,10 +140,30 @@ pub fn seek_se_hdr_start(img: &mut R) -> Result where R: Read + Seek, { - let max_iter: usize = 0x15; + let max_iter: usize; const BUF_SIZE: i64 = 8; static_assert!(BootHdrMagic::MAGIC.len() == BUF_SIZE as usize); + let old_position = img.stream_position()?; + if !SeImgMetaData::seek_start(img)? { + // Search from the previous position. + img.seek(std::io::SeekFrom::Start(old_position))?; + max_iter = 0x15; + } else { + let mut img_metadata_bytes = vec![0u8; size_of::()]; + // read in the header + img.read_exact(&mut img_metadata_bytes)?; + // Cannot fail because the buffer has the same size as SeImgMetaData. + let img_metadata = SeImgMetaData::ref_from(&img_metadata_bytes).unwrap(); + let img_metadata_version = img_metadata.version(); + if img_metadata_version != SeImgMetaData::V1 { + warn!("Unknown Secure Execution boot image version {img_metadata_version}"); + } + + img.seek(std::io::SeekFrom::Start(img_metadata.hdr_off.into()))?; + max_iter = 1; + } + let mut buf = [0; BUF_SIZE as usize]; for _ in 0..max_iter { match img.read_exact(&mut buf) { @@ -284,4 +379,17 @@ mod tests { let der: Result = ser.clone().try_into(); assert!(matches!(der, Err(Error::InvBootHdrSize(_)))); } + + #[test] + fn se_img_metadata() { + let metadata = SeImgMetaData::new_v1(0x14000, 0x16000); + let data = [ + 83, 101, 73, 109, 103, 76, 110, 120, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 1, 96, 0, + ]; + assert_eq!(metadata.as_bytes(), &data); + assert_eq!(SeImgMetaData::ref_from(&data), Some(&metadata)); + + assert_eq!(metadata.version(), SeImgMetaData::V1); + } } diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs index 9c98f551..7a33210c 100644 --- a/rust/pv/src/lib.rs +++ b/rust/pv/src/lib.rs @@ -86,7 +86,7 @@ pub use crate::error::HkdVerifyErrorType; /// Functionalities to build UV requests pub mod request { pub use crate::{ - brcb::{seek_se_hdr_start, BootHdrTags}, + brcb::{seek_se_hdr_start, BootHdrTags, SeImgMetaData}, crypto::{ decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array, AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey, diff --git a/rust/pvimg/README.md b/rust/pvimg/README.md index 9ed52028..c28135a3 100644 --- a/rust/pvimg/README.md +++ b/rust/pvimg/README.md @@ -1,7 +1,7 @@ # pvimg -`pvimg create` takes a kernel, key files, optionally an initrd image, optionally a -file containing the kernel command line parameters, and generates a single, +`pvimg create` takes a kernel, key files, optionally an initrd image, optionally +a file containing the kernel command line parameters, and generates a single, bootable image file. The generated image file consists of a concatenation of a plain text boot loader, the encrypted components for kernel, initrd, kernel command line, and the integrity-protected Secure Execution header, containing @@ -31,7 +31,10 @@ The main idea of `pvimg create` is: of the components and create the header and IPIB 6. parameterize the stub stage3a: uses the address of the IPIB and Secure Execution header -8. write the final image to the specified output path. +8. write the final image to the specified output path and generate the boot + image metadata at address `0xc000`. The address `0xc000` is chosen as this is + the `BSS` section of the stage3a loader and will therefore zeroed out as soon + as the stage3a is executed and has therefore no leftovers in the memory. ### Boot Loader @@ -73,6 +76,7 @@ The memory layout of the bootable file looks like: | Start | End | Use | |--------------------------|------------|-----------------------------------------------------------------------| | 0 | 0x7 | Short PSW, starting instruction at 0x11000 | +| 0x0c000 | 0x0cfff | Image metadata, e.g. it includes the file offset of the SE-header | | 0x10000 | 0x10012 | Branch to 0x11000 | | 0x10013 | 0x10fff | Left intentionally unused | | 0x11000 | 0x12fff | Stage3a | diff --git a/rust/pvimg/src/se_img.rs b/rust/pvimg/src/se_img.rs index 9f2b3dc6..ef8722d8 100644 --- a/rust/pvimg/src/se_img.rs +++ b/rust/pvimg/src/se_img.rs @@ -24,9 +24,9 @@ use pvimg::{ }; use crate::se_img_comps::{ - create_ipib, ipib::Ipib, kernel::S390Kernel, render_stage3a, render_stage3b, sehdr::SeHdrComp, - shortpsw::ShortPSWComp, stage3a_path, stage3b_path, CompTweakV1, Component, ComponentKind, - STAGE3A_ENTRY, STAGE3A_INIT_ENTRY, STAGE3A_LOAD_ADDRESS, + create_ipib, ipib::Ipib, kernel::S390Kernel, metadata::ImgMetaData, render_stage3a, + render_stage3b, sehdr::SeHdrComp, shortpsw::ShortPSWComp, stage3a_path, stage3b_path, + CompTweakV1, Component, ComponentKind, STAGE3A_ENTRY, STAGE3A_INIT_ENTRY, STAGE3A_LOAD_ADDRESS, }; pub struct SeHdrArgs<'a> { @@ -408,6 +408,10 @@ impl SeImgBuilder { .ok_or(Error::UnexpectedOverflow)?, )?; + // Create and write Secure Execution boot image meta data right after the short PSW + let _metadata_img_comp = + self.add_metadata(ipib_img_comp.src.start, sehdr_img_comp.src.start)?; + Ok(self.comps) } @@ -440,6 +444,18 @@ impl SeImgBuilder { self.insert_nonsecure_component(&mut short_psw_comp, ShortPSWComp::OFFSET) } + /// Prepare Secure Execution image metadata and write it to the file + fn add_metadata(&mut self, ipib_off: u64, hdr_off: u64) -> Result> { + let mut metadata_comp = ImgMetaData::new(ipib_off, hdr_off)?; + + let metadata_img_comp = + self.insert_nonsecure_component(&mut metadata_comp, ImgMetaData::OFFSET)?; + if metadata_img_comp.src.size() > ImgMetaData::MAX_SIZE { + unreachable!("The metadata should never be larger than the BSS size of stage3a"); + } + Ok(metadata_img_comp) + } + /// Prepare stage3b and write it to file fn add_stage3b(&mut self, psw: PSW) -> Result> { // Prepare stage3b - for this we must prepare the arguments for it. Since we diff --git a/rust/pvimg/src/se_img_comps.rs b/rust/pvimg/src/se_img_comps.rs index dd04cd00..6175cc74 100644 --- a/rust/pvimg/src/se_img_comps.rs +++ b/rust/pvimg/src/se_img_comps.rs @@ -14,8 +14,8 @@ use pv::request::random_array; use pvimg::{error::Result, secured_comp::ComponentTrait}; use self::{ - cmdline::Cmdline, kernel::S390Kernel, ramdisk::Ramdisk, sehdr::SeHdrComp, - shortpsw::ShortPSWComp, stage3a::Stage3a, stage3b::Stage3b, + cmdline::Cmdline, kernel::S390Kernel, metadata::ImgMetaData, ramdisk::Ramdisk, + sehdr::SeHdrComp, shortpsw::ShortPSWComp, stage3a::Stage3a, stage3b::Stage3b, }; pub use crate::se_img_comps::bootloader::{ create_ipib, render_stage3a, render_stage3b, stage3a_path, stage3b_path, STAGE3A_ENTRY, @@ -28,6 +28,7 @@ mod bootloader; pub mod cmdline; pub mod ipib; pub mod kernel; +pub mod metadata; pub mod ramdisk; pub mod sehdr; pub mod shortpsw; @@ -115,6 +116,7 @@ pub fn check_components(components: &mut [Component]) -> Result<(), anyhow::Erro #[enum_dispatch(ComponentCheckTrait)] pub enum Component { ShortPSW(ShortPSWComp), + ImgMetaData(ImgMetaData), Stage3a(Stage3a), Kernel(S390Kernel), Ramdisk(Ramdisk), @@ -137,6 +139,7 @@ impl Seek for Component { Self::Stage3b(obj) => obj.seek(pos), Self::SeHdr(obj) => obj.seek(pos), Self::Ipib(obj) => obj.seek(pos), + Self::ImgMetaData(obj) => obj.seek(pos), } } } @@ -154,6 +157,7 @@ impl Read for Component { Self::Stage3b(obj) => obj.read(buf), Self::SeHdr(obj) => obj.read(buf), Self::Ipib(obj) => obj.read(buf), + Self::ImgMetaData(obj) => obj.read(buf), } } } @@ -171,6 +175,7 @@ impl ComponentTrait for Component { Self::Stage3b(obj) => obj.secure_mode(), Self::SeHdr(obj) => obj.secure_mode(), Self::Ipib(obj) => obj.secure_mode(), + Self::ImgMetaData(obj) => obj.secure_mode(), } } @@ -184,6 +189,7 @@ impl ComponentTrait for Component { Self::Stage3b(obj) => obj.kind(), Self::SeHdr(obj) => obj.kind(), Self::Ipib(obj) => obj.kind(), + Self::ImgMetaData(obj) => obj.kind(), } } } @@ -221,6 +227,7 @@ impl Seek for CompReader { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq)] pub enum ComponentKind { ShortPSW = 10, + ImgMetaData = 20, Stage3a = 30, Kernel = 40, Ramdisk = 50, @@ -243,20 +250,17 @@ impl ComponentKind { impl Display for ComponentKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt( - &match self { - Self::Kernel => "Linux kernel", - Self::Ramdisk => "ramdisk", - Self::Cmdline => "kernel cmdline", - Self::Stage3a => "stage3a", - Self::Stage3b => "stage3b", - Self::SeHdr => "Secure Execution header", - Self::Ipib => "IPIB", - Self::ShortPSW => "short PSW", - } - .to_string(), - f, - ) + match self { + Self::Kernel => write!(f, "Linux kernel"), + Self::Ramdisk => write!(f, "ramdisk"), + Self::Cmdline => write!(f, "kernel cmdline"), + Self::Stage3a => write!(f, "stage3a"), + Self::Stage3b => write!(f, "stage3b"), + Self::SeHdr => write!(f, "Secure Execution header"), + Self::Ipib => write!(f, "IPIB"), + Self::ShortPSW => write!(f, "short PSW"), + Self::ImgMetaData => write!(f, "Image metadata"), + } } } @@ -313,6 +317,7 @@ mod tests { fn component_kind_strategy() -> impl Strategy { prop_oneof![ Just(ComponentKind::ShortPSW), + Just(ComponentKind::ImgMetaData), Just(ComponentKind::Stage3a), Just(ComponentKind::Kernel), Just(ComponentKind::Ramdisk), diff --git a/rust/pvimg/src/se_img_comps/bootloader.rs b/rust/pvimg/src/se_img_comps/bootloader.rs index 3afc8198..23910ba6 100644 --- a/rust/pvimg/src/se_img_comps/bootloader.rs +++ b/rust/pvimg/src/se_img_comps/bootloader.rs @@ -15,7 +15,9 @@ use pvimg::{ secured_comp::Interval, }; -pub use self::stage3a_defs::{STAGE3A_ENTRY, STAGE3A_INIT_ENTRY, STAGE3A_LOAD_ADDRESS}; +pub use self::stage3a_defs::{ + STAGE3A_BSS_ADDRESS, STAGE3A_BSS_SIZE, STAGE3A_ENTRY, STAGE3A_INIT_ENTRY, STAGE3A_LOAD_ADDRESS, +}; use self::{ ipl::{ ipl_parameter_block, ipl_pb0_pv, ipl_pb0_pv_comp, ipl_pbt_IPL_PBT_PV, ipl_pl_hdr, @@ -78,8 +80,8 @@ pub fn render_stage3a( let stage3a_size = stage3a.len(); let stage3a_size_u64: u64 = stage3a_size.try_into()?; - if stage3a_size < 24 { - unreachable!("Bug!"); + if stage3a_size <= 24 { + return Err(Error::InvalidStage3a); } let stage3a_data_addr = stage3a_addr .checked_add(stage3a_size_u64) @@ -151,6 +153,7 @@ pub fn render_stage3b( | ComponentKind::Ipib | ComponentKind::SeHdr | ComponentKind::ShortPSW + | ComponentKind::ImgMetaData | ComponentKind::Stage3b => unreachable!(), } Ok(()) @@ -171,7 +174,9 @@ pub fn render_stage3b( let stage3b_args_bin_len = stage3b_args_bin.len(); // Insert the stage3b arguments - assert!(stage3b_len > stage3b_args_bin_len); + if stage3b_len <= stage3b_args_bin_len { + return Err(Error::InvalidStage3b); + } let stage3b_parms_off = stage3b_len - stage3b_args_bin_len; stage3b.splice(stage3b_parms_off.., stage3b_args_bin); diff --git a/rust/pvimg/src/se_img_comps/metadata.rs b/rust/pvimg/src/se_img_comps/metadata.rs new file mode 100644 index 00000000..ecea4f52 --- /dev/null +++ b/rust/pvimg/src/se_img_comps/metadata.rs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use std::io::{Cursor, Read, Seek}; + +use pv::{request::SeImgMetaData, static_assert}; +use pvimg::error::Result; + +use super::{ + bootloader::{STAGE3A_BSS_ADDRESS, STAGE3A_BSS_SIZE}, + CompReader, ComponentCheckCtx, ComponentCheckTrait, ComponentKind, ComponentTrait, +}; + +#[derive(Debug)] +pub struct ImgMetaData(CompReader); +static_assert!(ImgMetaData::OFFSET == SeImgMetaData::OFFSET); + +impl ImgMetaData { + pub const MAX_SIZE: u64 = STAGE3A_BSS_SIZE; + pub const OFFSET: u64 = STAGE3A_BSS_ADDRESS; + + pub fn new(ipib_off: u64, hdr_off: u64) -> Result { + let data = SeImgMetaData::new_v1(hdr_off, ipib_off); + + let reader = Box::new(Cursor::new(data.as_bytes().to_owned())); + Ok(Self(CompReader { reader })) + } +} + +impl Read for ImgMetaData { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.0.read(buf) + } +} + +impl Seek for ImgMetaData { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.0.seek(pos) + } +} + +impl ComponentCheckTrait for ImgMetaData { + fn check(&mut self, _ctx: &ComponentCheckCtx) -> Result<()> { + Ok(()) + } + + fn init_ctx(&mut self, _ctx: &mut ComponentCheckCtx) -> Result<()> { + Ok(()) + } +} + +impl ComponentTrait for ImgMetaData { + fn kind(&self) -> ComponentKind { + ComponentKind::ImgMetaData + } + + fn secure_mode(&self) -> bool { + false + } +}