From d2de7f2808757fd997308a5b19b2d627557b7e1b Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Thu, 28 Nov 2024 15:48:31 +0100 Subject: [PATCH] rust/(pv|pvimg): Add Secure Execution boot image metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add metadata about the image to the Secure Execution image. This helps to identify where the Secure Execution header is located in the image and therefore it's less prone to errors to locate the header. This patch adds the support for it to 'pvimg' as well as to the 'pvsecret' and 'pvattest' tools. Reviewed-by: Steffen Eiden Signed-off-by: Marc Hartmayer Signed-off-by: Jan Höppner --- rust/pv/src/brcb.rs | 112 +++++++++++++++++++++- rust/pv/src/lib.rs | 2 +- rust/pvimg/README.md | 10 +- rust/pvimg/src/se_img.rs | 22 ++++- rust/pvimg/src/se_img_comps.rs | 37 +++---- rust/pvimg/src/se_img_comps/bootloader.rs | 13 ++- rust/pvimg/src/se_img_comps/metadata.rs | 61 ++++++++++++ 7 files changed, 228 insertions(+), 29 deletions(-) create mode 100644 rust/pvimg/src/se_img_comps/metadata.rs 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 + } +}