mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
+ Sort and group the imports + Normalize and format comments (100 characters width) Command used: $ cargo +nightly fmt -- Acked-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Marc Hartmayer <marc@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
509 lines
18 KiB
Rust
509 lines
18 KiB
Rust
// SPDX-License-Identifier: MIT
|
|
//
|
|
// Copyright IBM Corp. 2024
|
|
|
|
use std::fmt::Display;
|
|
use std::io::{Cursor, Seek, SeekFrom, Write};
|
|
use std::path::PathBuf;
|
|
use std::rc::Rc;
|
|
|
|
use anyhow::{anyhow, Context, Result};
|
|
use deku::DekuContainerRead;
|
|
use log::debug;
|
|
use openssl::pkey::{PKey, Public};
|
|
use pv::misc::read_file;
|
|
use pv::request::Confidential;
|
|
use pvimg::error::Error;
|
|
use pvimg::misc::{round_up, serialize_to_bytes, ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA};
|
|
use pvimg::secured_comp::{
|
|
ComponentTrait, Interval, Layout, SecuredComponent, SecuredComponentBuilder,
|
|
};
|
|
use pvimg::uvdata::{
|
|
BuilderTrait, PlaintextControlFlagsV1, SeHdrBuilder, SeHdrVersion, SecretControlFlagsV1,
|
|
};
|
|
|
|
use crate::se_img_comps::ipib::Ipib;
|
|
use crate::se_img_comps::kernel::S390Kernel;
|
|
use crate::se_img_comps::metadata::ImgMetaData;
|
|
use crate::se_img_comps::sehdr::SeHdrComp;
|
|
use crate::se_img_comps::shortpsw::ShortPSWComp;
|
|
use crate::se_img_comps::{
|
|
create_ipib, render_stage3a, render_stage3b, stage3a_path, stage3b_path, CompTweakV1,
|
|
Component, ComponentKind, STAGE3A_ENTRY, STAGE3A_INIT_ENTRY, STAGE3A_LOAD_ADDRESS,
|
|
};
|
|
|
|
pub struct SeHdrArgs<'a> {
|
|
pub keys: &'a [PKey<Public>],
|
|
pub pcf: &'a PlaintextControlFlagsV1,
|
|
pub scf: &'a SecretControlFlagsV1,
|
|
pub cck: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
|
|
pub hdr_aead_key: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
|
|
pub psw_addr: &'a Option<u64>,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct ImgComponent {
|
|
kind: ComponentKind,
|
|
pub(crate) src: Rc<Interval>,
|
|
pub(crate) secure_mode: Option<SecuredComponent>,
|
|
}
|
|
|
|
impl ImgComponent {
|
|
pub fn kind(&self) -> ComponentKind {
|
|
self.kind.clone()
|
|
}
|
|
}
|
|
|
|
impl Ord for ImgComponent {
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
self.src.cmp(&other.src)
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for ImgComponent {
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl Display for ImgComponent {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "| {:23} | ", self.kind.to_string())?;
|
|
self.src.to_string().fmt(f)?;
|
|
write!(f, " |")
|
|
}
|
|
}
|
|
|
|
pub struct SeImgBuilder<W> {
|
|
/// Expert mode (components encryption key and Secure Execution header
|
|
/// protection key can be set). By default disabled.
|
|
expert_mode: bool,
|
|
writer: W,
|
|
layout: Layout,
|
|
comps: Vec<Rc<ImgComponent>>,
|
|
builder: SecuredComponentBuilder,
|
|
stage3a: Vec<u8>,
|
|
stage3b: Vec<u8>,
|
|
/// The legacy Secure Execution header address (directly after stage3a)
|
|
legacy_se_hdr_addr: Option<u64>,
|
|
finalized: bool,
|
|
}
|
|
|
|
impl<W: Write + Seek> SeImgBuilder<W> {
|
|
const COMPONENT_ALIGNMENT_V1: u64 = SecuredComponentBuilder::COMPONENT_ALIGNMENT_V1;
|
|
const DEFAULT_INITIAL_PSW_MASK: u64 = PSW_MASK_BA | PSW_MASK_EA;
|
|
|
|
/// Create a Secure Execution boot image builder
|
|
#[allow(clippy::similar_names)]
|
|
pub(crate) fn new_v1(
|
|
mut writer: W,
|
|
encryption: bool,
|
|
legacy_expected_se_hdr_size: Option<usize>,
|
|
bootloader_dir: Option<&PathBuf>,
|
|
) -> Result<Self> {
|
|
let stage3a = read_file(stage3a_path(bootloader_dir), "stage3a")?;
|
|
let stage3b = read_file(stage3b_path(bootloader_dir), "stage3b")?;
|
|
let mut legacy_se_hdr_addr = None;
|
|
|
|
// Reserve memory space for the stage3a loader that will be written
|
|
// later.
|
|
let mut next_comp_addr: u64 = round_up(
|
|
STAGE3A_LOAD_ADDRESS
|
|
.checked_add(stage3a.len().try_into()?)
|
|
.ok_or(Error::UnexpectedOverflow)?,
|
|
Self::COMPONENT_ALIGNMENT_V1,
|
|
)?;
|
|
|
|
// Reserve memory space for the Secure Execution header in case of
|
|
// legacy mode.
|
|
if let Some(expected_se_hdr_size) = legacy_expected_se_hdr_size {
|
|
// Place the Secure Execution header next to the stage3a and use as
|
|
// the minimum address 0x14000. 0x14000 is used as the starting
|
|
// point for searching the Secure Execution header in the
|
|
// `pvextract-hdr` utility and we can therefore not use e.g. 0x13000
|
|
// even if it would be possible in regard to the memory layout.
|
|
const PV_EXTRACT_SE_HDR_SEARCH_ADDR: u64 = 0x14000;
|
|
let se_hdr_addr = std::cmp::max(next_comp_addr, PV_EXTRACT_SE_HDR_SEARCH_ADDR);
|
|
next_comp_addr = round_up(
|
|
se_hdr_addr
|
|
.checked_add(expected_se_hdr_size.try_into()?)
|
|
.ok_or(Error::UnexpectedOverflow)?,
|
|
Self::COMPONENT_ALIGNMENT_V1,
|
|
)?;
|
|
legacy_se_hdr_addr = Some(se_hdr_addr);
|
|
}
|
|
|
|
if next_comp_addr % Self::COMPONENT_ALIGNMENT_V1 != 0 {
|
|
return Err(Error::UnalignedAddress {
|
|
addr: next_comp_addr,
|
|
alignment: Self::COMPONENT_ALIGNMENT_V1,
|
|
}
|
|
.into());
|
|
}
|
|
|
|
// Secure Execution expects, that the component addresses are aligned to
|
|
// 4096.
|
|
let layout = Layout::new(next_comp_addr, Self::COMPONENT_ALIGNMENT_V1)?;
|
|
let builder = SecuredComponentBuilder::new_v1(encryption)?;
|
|
|
|
// The layout of the boot image matches with the memory layout as it
|
|
// it's loaded at location 0x0. Therefore let's seek to the
|
|
// `next_comp_addr`.
|
|
writer.seek(SeekFrom::Start(next_comp_addr))?;
|
|
|
|
Ok(Self {
|
|
layout,
|
|
expert_mode: false,
|
|
comps: vec![],
|
|
writer,
|
|
builder,
|
|
legacy_se_hdr_addr,
|
|
stage3a,
|
|
stage3b,
|
|
finalized: false,
|
|
})
|
|
}
|
|
|
|
/// Enable expert mode - this is required for specifying component tweaks by
|
|
/// hand etc...
|
|
pub(crate) fn i_know_what_i_am_doing(&mut self) {
|
|
self.builder.i_know_what_i_am_doing();
|
|
self.expert_mode = true;
|
|
}
|
|
|
|
/// Prepare the given component as secured component, append it to the layout
|
|
/// and write it to the output.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// This function will return an error if:
|
|
/// + stage3b has already been added
|
|
/// + problem with the preparation of the secured component
|
|
/// + serialization problem of the component tweak (very unlikely)
|
|
/// + a tweak was given, but the expert mode not enabled
|
|
pub(crate) fn prepare_and_append_as_secure_component<T>(
|
|
&mut self,
|
|
component: &mut T,
|
|
tweak: Option<Vec<u8>>,
|
|
) -> Result<Rc<ImgComponent>>
|
|
where
|
|
T: ComponentTrait<ComponentKind>,
|
|
{
|
|
if self.finalized {
|
|
return Err(Error::ImgAlreadyFinalized.into());
|
|
}
|
|
|
|
if !component.secure_mode() {
|
|
unreachable!("Bug")
|
|
}
|
|
|
|
if tweak.is_some() && !self.expert_mode {
|
|
return Err(Error::NonExpertModeTweakGiven.into());
|
|
}
|
|
|
|
debug!("Preparing {} as secured component", component.kind());
|
|
let tweak = tweak.unwrap_or(serialize_to_bytes(&CompTweakV1::new(component.kind())?)?);
|
|
// No reason to seek as there are no holes between components (addr
|
|
// alignment == alignment of the component size). If that changes we have to seek beforehand
|
|
// to `self.layout.next_addr` self.writer.seek(SeekFrom::Start(self.layout.
|
|
// next_addr))?;
|
|
|
|
let secured_comp = self.builder.prepare_and_append_as_secure_component(
|
|
&mut self.writer,
|
|
&mut self.layout,
|
|
component,
|
|
tweak,
|
|
)?;
|
|
|
|
let img_comp = Rc::new(ImgComponent {
|
|
kind: component.kind(),
|
|
src: secured_comp.src.clone(),
|
|
secure_mode: Some(secured_comp),
|
|
});
|
|
self.comps.push(img_comp.clone());
|
|
Ok(img_comp)
|
|
}
|
|
|
|
/// Insert and write the given non-secured component at the given address.
|
|
fn insert_nonsecure_component<T: ComponentTrait<ComponentKind>>(
|
|
&mut self,
|
|
component: &mut T,
|
|
addr: u64,
|
|
) -> Result<Rc<ImgComponent>> {
|
|
// FIXME Guarantee this during compile time using a "SecureMode" trait.
|
|
if component.secure_mode() {
|
|
unreachable!("Programming bug!")
|
|
};
|
|
|
|
let max_component_size = self.layout.max_size_of_chunk_at_addr(addr)?;
|
|
let mut buf = vec![0_u8; self.builder.chunk_size()];
|
|
let mut total_written_count: usize = 0;
|
|
|
|
assert_ne!(buf.len(), 0);
|
|
|
|
self.writer.seek(SeekFrom::Start(addr))?;
|
|
loop {
|
|
let read_count = component.read(&mut buf)?;
|
|
// The end of file has reached as it's guaranteed that the buffer
|
|
// [`buf`] has a length != 0. See
|
|
// https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read
|
|
if read_count == 0 {
|
|
break;
|
|
}
|
|
|
|
if let Some(max_component_size) = max_component_size {
|
|
if total_written_count
|
|
.checked_add(read_count)
|
|
.ok_or(Error::UnexpectedOverflow)?
|
|
> max_component_size
|
|
{
|
|
return Err(anyhow!(
|
|
"BUG: Component is too large for this location in the image: {} > {}",
|
|
total_written_count + read_count,
|
|
max_component_size
|
|
));
|
|
}
|
|
}
|
|
|
|
self.writer.write_all(&buf[0..read_count])?;
|
|
total_written_count = total_written_count
|
|
.checked_add(read_count)
|
|
.ok_or(Error::UnexpectedOverflow)?;
|
|
}
|
|
|
|
let src = self
|
|
.layout
|
|
.insert_interval(addr, total_written_count.try_into()?)?;
|
|
let img_comp = Rc::new(ImgComponent {
|
|
src,
|
|
kind: component.kind(),
|
|
secure_mode: None,
|
|
});
|
|
|
|
match self.comps.binary_search(&img_comp) {
|
|
Ok(_pos) => {
|
|
return Err(anyhow!(
|
|
"BUG: There is already another component at this location"
|
|
))
|
|
}
|
|
Err(pos) => self.comps.insert(pos, img_comp.clone()),
|
|
}
|
|
Ok(img_comp)
|
|
}
|
|
|
|
fn append_component<T: ComponentTrait<ComponentKind>>(
|
|
&mut self,
|
|
component: &mut T,
|
|
) -> Result<Rc<ImgComponent>> {
|
|
let next_addr = self.layout.next_addr;
|
|
self.insert_nonsecure_component(component, next_addr)
|
|
}
|
|
|
|
/// Prepare IPIB and write it to file
|
|
fn add_ipib(&mut self, sehdr_src: &Interval) -> Result<Rc<ImgComponent>> {
|
|
let img_comps_tweak_and_src: Result<Vec<_>> = self
|
|
.comps
|
|
.iter()
|
|
.filter(|comp| comp.secure_mode.is_some())
|
|
.map(|comp| {
|
|
// Safety: We checked in the filter for `comp.secure_mode.is_some()`.
|
|
let secure_mode_data = comp.secure_mode.as_ref().unwrap();
|
|
let src = &comp.src;
|
|
let (_, tweak) = CompTweakV1::from_bytes((secure_mode_data.tweak(), 0))?;
|
|
Ok((tweak.pref, src.clone()))
|
|
})
|
|
.collect();
|
|
let ipib = create_ipib(sehdr_src, img_comps_tweak_and_src?)?;
|
|
let mut ipib_comp = Ipib::new(Box::new(Cursor::new(serialize_to_bytes(&ipib)?)));
|
|
self.append_component(&mut ipib_comp)
|
|
}
|
|
|
|
/// Prepare Secure Execution header and write it to the output
|
|
fn add_sehdr(&mut self, stage3b_entry: u64, sehdr_args: SeHdrArgs) -> Result<Rc<ImgComponent>> {
|
|
let meta = self.builder.finish()?;
|
|
|
|
let mut se_hdr_builder = SeHdrBuilder::new(
|
|
SeHdrVersion::V1,
|
|
PSW {
|
|
addr: sehdr_args.psw_addr.unwrap_or(stage3b_entry),
|
|
mask: Self::DEFAULT_INITIAL_PSW_MASK,
|
|
},
|
|
meta,
|
|
)?;
|
|
se_hdr_builder
|
|
.add_hostkeys(sehdr_args.keys)?
|
|
.with_pcf(sehdr_args.pcf)?
|
|
.with_scf(sehdr_args.scf)?;
|
|
|
|
if self.expert_mode {
|
|
se_hdr_builder.i_know_what_i_am_doing();
|
|
}
|
|
|
|
if let Some((path, cck)) = &sehdr_args.cck {
|
|
se_hdr_builder
|
|
.with_cck(cck.clone())
|
|
.with_context(|| format!("Failed to use '{}' as the CCK", path.display()))?;
|
|
}
|
|
|
|
if let Some((path, prot_key)) = sehdr_args.hdr_aead_key {
|
|
se_hdr_builder
|
|
.with_aead_key(prot_key.clone())
|
|
.with_context(|| {
|
|
format!(
|
|
"Failed to use '{}' as the Secure Execution header protection key",
|
|
path.display()
|
|
)
|
|
})?;
|
|
}
|
|
|
|
let se_hdr_bin = se_hdr_builder.build()?;
|
|
let mut comp: Component =
|
|
SeHdrComp::new(Box::new(Cursor::new(se_hdr_bin.as_bytes()?))).into();
|
|
|
|
if let Some(se_hdr_addr) = self.legacy_se_hdr_addr {
|
|
self.insert_nonsecure_component(&mut comp, se_hdr_addr)
|
|
} else {
|
|
self.append_component(&mut comp)
|
|
}
|
|
}
|
|
|
|
/// Finish the Secure Execution image - e.g. create Stage3a, Stage3b, Secure
|
|
/// Execution header and so on.
|
|
#[allow(clippy::similar_names)]
|
|
pub fn finish(mut self, sehdr_args: SeHdrArgs) -> Result<Vec<Rc<ImgComponent>>> {
|
|
if (sehdr_args.hdr_aead_key.is_some() || sehdr_args.psw_addr.is_some()) && !self.expert_mode
|
|
{
|
|
return Err(Error::NonExpertMode.into());
|
|
}
|
|
|
|
// Create stage3b and write it to the output file
|
|
let psw = PSW {
|
|
addr: S390Kernel::KERNEL_ENTRY,
|
|
mask: Self::DEFAULT_INITIAL_PSW_MASK,
|
|
};
|
|
let stage3b_img_comp = self
|
|
.add_stage3b(psw)
|
|
.context("Failed to prepare stage3b component")?;
|
|
|
|
// Create Secure Execution header and write it to the output file
|
|
let sehdr_img_comp = self
|
|
.add_sehdr(stage3b_img_comp.src.start, sehdr_args)
|
|
.context("Failed to prepare Secure Execution header")?;
|
|
|
|
// Create and write IPIB to the output file
|
|
let ipib_img_comp = self
|
|
.add_ipib(&sehdr_img_comp.src)
|
|
.context("Failed to prepare IPIB")?;
|
|
|
|
// Create and write stage3a to the output file
|
|
let stage3a_img_comp = self
|
|
.add_stage3a(&sehdr_img_comp.src, &ipib_img_comp.src)
|
|
.context("Failed to prepare Stage3a")?;
|
|
assert_eq!(stage3a_img_comp.src.start, STAGE3A_INIT_ENTRY);
|
|
assert_eq!(stage3a_img_comp.src.start + 0x1000, STAGE3A_ENTRY);
|
|
|
|
// Create and write short PSW at the beginning of the file
|
|
let _short_psw_img_comp = self.add_short_psw(
|
|
stage3a_img_comp
|
|
.src
|
|
.start
|
|
.checked_add(0x1000)
|
|
.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)
|
|
}
|
|
|
|
/// Prepare stage3a and write it to file
|
|
fn add_stage3a(
|
|
&mut self,
|
|
se_hdr_src: &Interval,
|
|
ipib_src: &Interval,
|
|
) -> Result<Rc<ImgComponent>> {
|
|
let stage3a_load_addr = STAGE3A_LOAD_ADDRESS;
|
|
let mut stage3a_comp = render_stage3a(
|
|
self.stage3a.clone(),
|
|
stage3a_load_addr,
|
|
se_hdr_src,
|
|
ipib_src,
|
|
)?;
|
|
self.insert_nonsecure_component(&mut stage3a_comp, stage3a_load_addr)
|
|
}
|
|
|
|
/// Prepare short PSW and write it to file
|
|
fn add_short_psw(&mut self, stage3a_entry: u64) -> Result<Rc<ImgComponent>> {
|
|
let short_psw: ShortPsw = PSW {
|
|
addr: stage3a_entry,
|
|
mask: Self::DEFAULT_INITIAL_PSW_MASK,
|
|
}
|
|
.try_into()?;
|
|
|
|
let mut short_psw_comp =
|
|
ShortPSWComp::new(Box::new(Cursor::new(serialize_to_bytes(&short_psw)?)));
|
|
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
|
|
// have the memory layout for the movable components (kernel, cmdline, and
|
|
// initrd) we can do this now.
|
|
let mut stage3b_comp = render_stage3b(self.stage3b.clone(), psw, &self.comps)?;
|
|
|
|
let result = self.prepare_and_append_as_secure_component(&mut stage3b_comp, None);
|
|
// No other "regular components can be added now
|
|
self.finalized = true;
|
|
result
|
|
}
|
|
|
|
pub(crate) fn set_components_key(
|
|
&mut self,
|
|
key_data: Confidential<Vec<u8>>,
|
|
) -> pvimg::error::Result<()> {
|
|
self.builder.set_components_key(key_data)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::io::Cursor;
|
|
|
|
use super::SeImgBuilder;
|
|
use crate::se_img::stage3a_path;
|
|
use crate::se_img_comps::stage3b_path;
|
|
|
|
#[test]
|
|
fn test_comp_ctx_new() {
|
|
// If the bootloader does not exist, we cannot test.
|
|
if !stage3a_path(None).exists() || !stage3b_path(None).exists() {
|
|
return;
|
|
}
|
|
|
|
let encryption = true;
|
|
let mut writer = Cursor::new(Vec::new());
|
|
let ctx_res = SeImgBuilder::new_v1(&mut writer, encryption, None, None);
|
|
assert!(ctx_res.is_ok());
|
|
let ctx = ctx_res.unwrap();
|
|
|
|
assert_eq!(ctx.layout.next_addr, 0x13000);
|
|
assert!(ctx.builder.encryption_enabled());
|
|
assert_eq!(ctx.comps, vec![]);
|
|
}
|
|
}
|