Files
s390-tools/rust/pvimg/examples/tamper_pvimg/main.rs
T
Gautam Gala 2d86117167 rust/pvimg: SE header tampering tool for use with tests
Adding a new tool (tamper_pvimg) for helping to test if the expected
RC and RRC values are received for several different reasons when
DIAG 308 subcode does not complete successfully. The reasons can include
issues related to host key hash, SE header, ALD/PDL/TLD mistmatch, etc.

This tool can read the SE image and manipulate one of the SE header
fields so that the SE image will fail to enter secure mode during the
stage3a booting process. The stage3a bootloader should receive UVC
command code id, RC and RRC values corresponding to the SE header
field that was manipulated. These values will be checked for
consistency in the tests for correctness of DIAG 308 subcode 10 RCs
and RRCs.

[hoeppner@linux.ibm.com: shortened commit subject]
Signed-off-by: Gautam Gala <ggala@linux.ibm.com>
Signed-off-by: Steffen Eiden <sediden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-06-11 12:58:03 +02:00

155 lines
5.0 KiB
Rust

#![allow(missing_docs)]
use anyhow::Context;
use clap::{Parser, ValueEnum, ValueHint};
use log::info;
use pv::{
misc::{open_file, parse_hex, read_file},
request::SymKey,
};
use pvimg::{
error::Result,
uvdata::{
KeyExchangeTrait, SeHdr, SeHdrBinV1, SeHdrData, SeHdrDataV1, SeHdrVersioned,
UvDataPlainTrait, UvDataTrait,
},
};
use std::{fs::File, io::Write, path::PathBuf};
use utils::{PvLogger, VerbosityOptions};
#[derive(Parser, Debug)]
struct Cli {
/// Use INPUT as the Secure Execution image.
#[arg(short, long, value_name = "INPUT", value_hint = ValueHint::FilePath,)]
infile: PathBuf,
/// Use INPUT as the Secure Execution image.
#[arg(short, long, value_name = "OUTPUT", value_hint = ValueHint::FilePath,)]
outfile: PathBuf,
/// Use the key in FILE to decrypt the Secure Execution header.
/// It is the key that was specified with the command line option
/// '--hdr-key' at the Secure Execution image creation.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, alias = "key")]
hdr_key: PathBuf,
#[clap(flatten)]
verbosity: VerbosityOptions,
/// Hdr Value to tamper with
#[arg(long, value_enum)]
tamp: TampVal,
}
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum TampVal {
/// ALD.
AddressListDigest,
/// PLD.
PageListDigest,
/// TLD.
TweakListDigest,
/// Change lenght of secure header
HdrSize,
/// customer ECDH key
CustomerPublicKey,
/// host key hash
HostKey,
/// Secret control flags
SecretControlFlag,
/// Plaintext control flags
PlaintextControlFlag,
/// Number of keyslots
NumKeySlots,
/// Size encrypted area
SizeEncArea,
/// Number of encrypted pages
NumEncPages,
}
static LOGGER: PvLogger = PvLogger;
fn main() -> anyhow::Result<()> {
let opt = Cli::parse();
LOGGER
.start(opt.verbosity.to_level_filter())
.with_context(|| "Failed to set-up logger")?;
info!("Reading Secure Execution header {}", opt.infile.display());
let mut input = open_file(&opt.infile)?;
SeHdr::seek_sehdr(&mut input, None)?;
let hdr = SeHdr::try_from_io(&mut input)?;
let mut hdr_encr_v1: SeHdrBinV1 =
<SeHdrVersioned as TryInto<SeHdrBinV1>>::try_into(hdr.clone().data).expect("SE-header V1");
let mut decryption_required = false;
let mut hdr_encr: SeHdr;
// Tamper with parts of SE header that doesn't require decryption
match &opt.tamp {
TampVal::HdrSize => hdr_encr_v1.aad.sehs += 1,
TampVal::HostKey => hdr_encr_v1.aad.keyslots[0].phkh[..32].copy_from_slice(&[0; 32]),
TampVal::PlaintextControlFlag => {
let hex_str = String::from_utf8(
read_file("/sys/firmware/uv/query/supp_se_hdr_pcf", "input file").unwrap(),
)
.expect("PCF support not found");
let hex = parse_hex(&hex_str);
let mut hex_pad = [0u8; 8];
hex_pad[(8 - hex.len())..].copy_from_slice(&hex);
let supported_pcf: u64 = u64::from_be_bytes(hex_pad);
hdr_encr_v1.aad.pcf = 0xFFFF_FFFF_FFFF_D5FF - supported_pcf;
}
TampVal::NumKeySlots => hdr_encr_v1.aad.nks = 0,
TampVal::SizeEncArea => hdr_encr_v1.aad.sea = 0,
_ => decryption_required = true,
}
if decryption_required {
let key = SymKey::try_from_data(
hdr.key_type(),
read_file(&opt.hdr_key, "Reading key")?.into(),
)?;
let mut hdr_plain = hdr.decrypt(&key)?;
let mut hdr_v1: SeHdrDataV1 =
<SeHdrData as TryInto<SeHdrDataV1>>::try_into(hdr_plain.clone().data)
.expect("SE-header V1");
// Tamper with the SE header data
match &opt.tamp {
TampVal::AddressListDigest => hdr_v1.aad.ald[..8].copy_from_slice(&[0; 8]),
TampVal::PageListDigest => hdr_v1.aad.pld[..8].copy_from_slice(&[0; 8]),
TampVal::TweakListDigest => hdr_v1.aad.tld[..8].copy_from_slice(&[0; 8]),
TampVal::CustomerPublicKey => {
hdr_v1.aad.cust_pub_key.coord[..160].copy_from_slice(&[0; 160])
}
TampVal::SecretControlFlag => hdr_v1.data.value_mut().scf = 0xFFFF_FFFF_FFFF_FFFF,
TampVal::NumEncPages => hdr_v1.aad.nep = 0,
_ => {}
}
hdr_plain.data = hdr_v1.into();
hdr_encr = hdr_plain.encrypt(&key)?;
} else {
hdr_encr = hdr.clone();
hdr_encr.data = hdr_encr_v1.clone().into();
}
std::fs::copy(&opt.infile, &opt.outfile)?;
match tamper_image_file(&opt.outfile, hdr_encr) {
Ok(_) => (),
Err(err) => {
std::fs::remove_file(&opt.outfile)?;
panic!("Could not seek SE header: {}", err);
}
};
Ok(())
}
fn tamper_image_file(outfile: &PathBuf, hdr_encr: SeHdr) -> Result<()> {
let mut output = File::options().read(true).write(true).open(outfile)?;
SeHdr::seek_sehdr(&mut output, None)?;
output.write_all(&hdr_encr.as_bytes()?)?;
Ok(())
}