rust/(pv|pvimg): Add Secure Execution boot image metadata

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 <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Marc Hartmayer
2024-11-28 15:48:31 +01:00
committed by Jan Höppner
parent f4cf4ae6eb
commit d2de7f2808
7 changed files with 228 additions and 29 deletions

View File

@@ -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<Vec<u8>> 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<BigEndian>,
/// Version
version: U32<BigEndian>,
/// IPIB offset in the image
ipib_off: U64<BigEndian>,
}
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<R>(img: &mut R) -> Result<bool>
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] {
<Self as AsBytes>::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<R>(img: &mut R) -> Result<bool>
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::<SeImgMetaData>()];
// 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<BootHdrTags> = 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);
}
}

View File

@@ -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,

View File

@@ -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 |

View File

@@ -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<W: Write + Seek> SeImgBuilder<W> {
.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<W: Write + Seek> SeImgBuilder<W> {
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<Rc<ImgComponent>> {
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<Rc<ImgComponent>> {
// Prepare stage3b - for this we must prepare the arguments for it. Since we

View File

@@ -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<ComponentKind> 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<ComponentKind> 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<Value = ComponentKind> {
prop_oneof![
Just(ComponentKind::ShortPSW),
Just(ComponentKind::ImgMetaData),
Just(ComponentKind::Stage3a),
Just(ComponentKind::Kernel),
Just(ComponentKind::Ramdisk),

View File

@@ -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);

View File

@@ -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<Self> {
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<usize> {
self.0.read(buf)
}
}
impl Seek for ImgMetaData {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
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<ComponentKind> for ImgMetaData {
fn kind(&self) -> ComponentKind {
ComponentKind::ImgMetaData
}
fn secure_mode(&self) -> bool {
false
}
}