mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
rust: Add a new tool called 'pvimg'
Add a new tool called 'pvimg' that can be used to create and inspect
Secure Execution images. It has several subcommands:
+ create: create an IBM Secure Execution image (genprotimg compatible
sytnax) and C-'genprotimg' is going to be replaced by a
symlink to this subcommand.
+ test: test various aspects of an existing Secure Execution image
+ info: print information about an existing Secure Execution
image (experimental API!)
+ version: print version and exit
As mentioned above, the 'genprotimg' tool is now a symbolic link to the
'pvimg create' subcommand and the CLI is backward compatible with the
original genprotimg CLI, with the following exceptions:
- '-v' increases the verbosity instead of showing the version
- '-V' is now deprecated in favor of '-v'
- an existing output file is no longer silently overwritten, but there
is a new flag '--overwrite' to get the original behavior
- experimental options are no longer described in the help
- the commands '--cert ...' and '--root-ca' are now mutually exclusive
- to '--no-verify'
- there is now a component check, e.g. it checks if the specified
Linux kernel looks like a raw binary s390x kernel. These checks can be
disabled by using the new command line flag '--no-component-check'
Acked-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:
committed by
Jan Höppner
parent
f524b0b8dc
commit
f4cf4ae6eb
52
README.md
52
README.md
@@ -25,6 +25,8 @@ Package contents
|
|||||||
Automatic configure APQNs within an SE KVM guest
|
Automatic configure APQNs within an SE KVM guest
|
||||||
- pvsecret:
|
- pvsecret:
|
||||||
Manage secrets for IBM Secure Execution guests
|
Manage secrets for IBM Secure Execution guests
|
||||||
|
- pvimg:
|
||||||
|
Create and inspect IBM Secure Execution images
|
||||||
|
|
||||||
* dasdfmt:
|
* dasdfmt:
|
||||||
Low-level format ECKD DASDs with the classical Linux disk layout or the new
|
Low-level format ECKD DASDs with the classical Linux disk layout or the new
|
||||||
@@ -304,26 +306,28 @@ HAVE_FUSE=0`".
|
|||||||
The following table provides an overview of the used libraries and
|
The following table provides an overview of the used libraries and
|
||||||
build options:
|
build options:
|
||||||
|
|
||||||
| __LIBRARY__ | __BUILD OPTION__ | __TOOLS__ |
|
| __LIBRARY__ | __BUILD OPTION__ | __TOOLS__ |
|
||||||
|----------------|:------------------:|:-------------------------------------:|
|
|--------------|:------------------:|:--------------------------------------:|
|
||||||
| fuse3 | `HAVE_FUSE` | cmsfs-fuse, zdsfs, hmcdrvfs, zgetdump,|
|
| fuse3 | `HAVE_FUSE` | cmsfs-fuse, zdsfs, hmcdrvfs, zgetdump, |
|
||||||
| | | hsavmcore |
|
| | | hsavmcore |
|
||||||
| zlib | `HAVE_ZLIB` | zgetdump, dump2tar |
|
| zlib | `HAVE_ZLIB` | zgetdump, dump2tar |
|
||||||
| ncurses | `HAVE_NCURSES` | hyptop |
|
| ncurses | `HAVE_NCURSES` | hyptop |
|
||||||
| net-snmp | `HAVE_SNMP` | osasnmpd |
|
| net-snmp | `HAVE_SNMP` | osasnmpd |
|
||||||
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
|
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
|
||||||
| openssl | `HAVE_OPENSSL` | genprotimg, zkey, libekmfweb, |
|
| openssl | `HAVE_OPENSSL` | genprotimg, zkey, libekmfweb, |
|
||||||
| | | libkmipclient, zgetdump, |
|
| | | libkmipclient, zgetdump, |
|
||||||
| | | rust/pvattest, rust/pvsecret, |
|
| | | rust/pvattest, rust/pvsecret, |
|
||||||
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
|
| | | rust/pvimg |
|
||||||
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
|
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
|
||||||
| | | libkmipclient |
|
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
|
||||||
| glib2 | `HAVE_GLIB2` | genprotimg, zgetdump |
|
| | | libkmipclient |
|
||||||
| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient,|
|
| glib2 | `HAVE_GLIB2` | genprotimg, zgetdump |
|
||||||
| | | rust/pvattest, rust/pvsecret, |
|
| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient, |
|
||||||
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
|
| | | rust/pvattest, rust/pvsecret, |
|
||||||
| systemd | `HAVE_SYSTEMD` | hsavmcore |
|
| | | rust/pvimg |
|
||||||
| libudev | `HAVE_LIBUDEV` | cpacfstatsd |
|
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
|
||||||
|
| systemd | `HAVE_SYSTEMD` | hsavmcore |
|
||||||
|
| libudev | `HAVE_LIBUDEV` | cpacfstatsd |
|
||||||
|
|
||||||
This table lists additional build or install options:
|
This table lists additional build or install options:
|
||||||
|
|
||||||
@@ -363,6 +367,14 @@ the different tools are provided:
|
|||||||
|
|
||||||
The runtime requirements are: openssl-libs (>= 1.1.0) and glib2.
|
The runtime requirements are: openssl-libs (>= 1.1.0) and glib2.
|
||||||
|
|
||||||
|
* rust/pvimg:
|
||||||
|
For building pvimg you need OpenSSL version 1.1.1 or newer
|
||||||
|
installed (openssl-devel.rpm). Also required is cargo and libcurl.
|
||||||
|
Tip: you may skip the pvimg build by adding
|
||||||
|
`HAVE_OPENSSL=0`, `HAVE_LIBCURL=0`, or `HAVE_CARGO=0`.
|
||||||
|
|
||||||
|
The runtime requirements are: openssl-libs (>= 1.1.1) and libcurl.
|
||||||
|
|
||||||
* rust/pvattest:
|
* rust/pvattest:
|
||||||
For building pvattest you need OpenSSL version 1.1.1 or newer
|
For building pvattest you need OpenSSL version 1.1.1 or newer
|
||||||
installed (openssl-devel.rpm). Also required is cargo and libcurl.
|
installed (openssl-devel.rpm). Also required is cargo and libcurl.
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ endif #HOSTARCH
|
|||||||
|
|
||||||
ifneq (${HAVE_OPENSSL},0)
|
ifneq (${HAVE_OPENSSL},0)
|
||||||
ifneq (${HAVE_LIBCURL},0)
|
ifneq (${HAVE_LIBCURL},0)
|
||||||
PV_TARGETS := pvsecret pvattest
|
PV_TARGETS := pvsecret pvattest pvimg
|
||||||
|
|
||||||
ifeq ($(HOST_ARCH),s390x)
|
ifeq ($(HOST_ARCH),s390x)
|
||||||
PV_TARGETS += pvapconfig
|
PV_TARGETS += pvapconfig
|
||||||
@@ -88,6 +88,7 @@ install: $(INSTALL_TARGETS)
|
|||||||
$(INSTALL) -d -m 755 $(DESTDIR)$(PVIMG_PKGDATADIR)
|
$(INSTALL) -d -m 755 $(DESTDIR)$(PVIMG_PKGDATADIR)
|
||||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvimg/tools/check_hostkeydoc $(DESTDIR)$(PVIMG_PKGDATADIR)
|
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvimg/tools/check_hostkeydoc $(DESTDIR)$(PVIMG_PKGDATADIR)
|
||||||
$(MAKE) -C pvimg/boot install
|
$(MAKE) -C pvimg/boot install
|
||||||
|
ln -sf pvimg $(DESTDIR)$(USRBINDIR)/genprotimg
|
||||||
|
|
||||||
print-rust-targets:
|
print-rust-targets:
|
||||||
echo $(BUILD_TARGETS)
|
echo $(BUILD_TARGETS)
|
||||||
|
|||||||
84
rust/pvimg/README.md
Normal file
84
rust/pvimg/README.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# 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,
|
||||||
|
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
|
||||||
|
the metadata necessary for running the guest in protected mode. See [Memory
|
||||||
|
Layout](#memory-layout) for details about the internal structure of the created
|
||||||
|
image.
|
||||||
|
|
||||||
|
It is possible to use the generated image as a kernel for zipl or for a direct
|
||||||
|
kernel boot using QEMU.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
If all dependencies are met a simple `make` call in the source tree should be
|
||||||
|
enough for building `pvimg`.
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
The main idea of `pvimg create` is:
|
||||||
|
|
||||||
|
1. Generate all keys, IVs, and other information needed for the encryption of
|
||||||
|
the components and the generation of the PV header
|
||||||
|
2. add stub stage3a (so we can calculate the memory addresses)
|
||||||
|
3. add components: prepare the components (alignment and encryption) and add
|
||||||
|
them to the memory layout
|
||||||
|
4. build and add stage3b: generate the stage3b and add it to the memory layout
|
||||||
|
5. generate the Secure Execution header: generate the hashes (pld, ald, and tld)
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Boot Loader
|
||||||
|
|
||||||
|
The boot loader consists of two parts:
|
||||||
|
|
||||||
|
1. stage3a boot loader (cleartext), this loader is responsible for the
|
||||||
|
transition into the protected mode by doing `diag308` subcode 8 and 10 calls.
|
||||||
|
2. stage3b boot loader (encrypted), this loader is very similar to the normal
|
||||||
|
zipl stage3 boot loader. It will be loaded by the Ultravisor after the
|
||||||
|
successful transition into protected mode. Like the zipl stage3 boot loader
|
||||||
|
it moves the kernel and patches in the values for initrd and kernel command
|
||||||
|
line.
|
||||||
|
|
||||||
|
The loaders have the following constraints:
|
||||||
|
|
||||||
|
1. It must be possible to place stage3a and stage3b at a location greater than
|
||||||
|
0x10000 because the zipl stage3 loader zeroes out everything at addresses
|
||||||
|
lower than 0x10000 of the image.
|
||||||
|
2. As the stage3 loader of zipl assumes that the passed kernel image looks like
|
||||||
|
a normal kernel image, the zipl stage3 loader modifies the content at the
|
||||||
|
memory area 0x10400 - 0x10800, therefore we leave this area unused in our
|
||||||
|
stage3a loader.
|
||||||
|
3. The default entry address used by the zipl stage3 loader is 0x10000 so we add
|
||||||
|
a simple branch to 0x11000 at 0x10000 so the zipl stage3 loader can modify
|
||||||
|
the area 0x10400 - 0x10800 without affecting the stage3a loader.
|
||||||
|
|
||||||
|
#### Stage3b
|
||||||
|
|
||||||
|
The `stage3b.bin` is linked at address 0x9000, therefore it will not work at
|
||||||
|
another address. The relocation support for the stage3b loader, so that it can
|
||||||
|
be loaded at addresses != 0x9000, is added in the loader with the name
|
||||||
|
`stage3b_reloc.bin`. By default, if we're talking about stage3b we refer to
|
||||||
|
`stage3b_reloc.bin.`
|
||||||
|
|
||||||
|
### Memory Layout
|
||||||
|
|
||||||
|
The memory layout of the bootable file looks like:
|
||||||
|
|
||||||
|
| Start | End | Use |
|
||||||
|
|--------------------------|------------|-----------------------------------------------------------------------|
|
||||||
|
| 0 | 0x7 | Short PSW, starting instruction at 0x11000 |
|
||||||
|
| 0x10000 | 0x10012 | Branch to 0x11000 |
|
||||||
|
| 0x10013 | 0x10fff | Left intentionally unused |
|
||||||
|
| 0x11000 | 0x12fff | Stage3a |
|
||||||
|
| 0x14000 | 0x1[45]fff | SE-header used for the diag308 call (size can be either 1 or 2 pages) |
|
||||||
|
| `NEXT_PAGE_ALIGNED_ADDR` | | Encrypted kernel |
|
||||||
|
| `NEXT_PAGE_ALIGNED_ADDR` | | Encrypted kernel parameters |
|
||||||
|
| `NEXT_PAGE_ALIGNED_ADDR` | | Encrypted initrd |
|
||||||
|
| `NEXT_PAGE_ALIGNED_ADDR` | | Encrypted stage3b_reloc |
|
||||||
|
| `NEXT_PAGE_ALIGNED_ADDR` | | IPIB used as argument for the diag308 call |
|
||||||
29
rust/pvimg/build.rs
Normal file
29
rust/pvimg/build.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
// it under the terms of the MIT license. See LICENSE for details.
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
use clap_complete::{generate_to, Shell};
|
||||||
|
|
||||||
|
include!("src/cli.rs");
|
||||||
|
|
||||||
|
fn main() -> Result<(), Error> {
|
||||||
|
let outdir = env::var_os("OUT_DIR").unwrap();
|
||||||
|
let crate_name = env!("CARGO_PKG_NAME");
|
||||||
|
for &shell in Shell::value_variants() {
|
||||||
|
for (name, mut cmd) in [
|
||||||
|
(crate_name, CliOptions::command()),
|
||||||
|
("genprotimg", GenprotimgCliOptions::command()),
|
||||||
|
] {
|
||||||
|
generate_to(shell, &mut cmd, name, &outdir)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
|
println!("cargo:rerun-if-changed=src/cli.rs");
|
||||||
|
println!("cargo:rerun-if-changed=../utils/src/cli.rs");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
752
rust/pvimg/src/cli.rs
Normal file
752
rust/pvimg/src/cli.rs
Normal file
@@ -0,0 +1,752 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::{env, fmt::Display, path::PathBuf};
|
||||||
|
|
||||||
|
use clap::{ArgGroup, Args, Command, CommandFactory, Parser, ValueEnum, ValueHint};
|
||||||
|
use log::warn;
|
||||||
|
use utils::{CertificateOptions, DeprecatedVerbosityOptions};
|
||||||
|
|
||||||
|
/// Create and inspect IBM Secure Execution images.
|
||||||
|
///
|
||||||
|
/// Use pvimg to create an IBM Secure Execution image, which can be loaded using
|
||||||
|
/// zipl or QEMU. pvimg can also be used to inspect existing Secure Execution
|
||||||
|
/// images.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command()]
|
||||||
|
pub struct CliOptions {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub verbose: DeprecatedVerbosityOptions,
|
||||||
|
|
||||||
|
/// Print version information and exit.
|
||||||
|
// Implemented for the help message only. Actual parsing happens in the
|
||||||
|
// version command.
|
||||||
|
#[arg(long)]
|
||||||
|
pub version: bool,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub cmd: SubCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GenprotimgCliOptions> for CliOptions {
|
||||||
|
fn from(value: GenprotimgCliOptions) -> Self {
|
||||||
|
Self {
|
||||||
|
verbose: value.verbose,
|
||||||
|
version: false,
|
||||||
|
cmd: SubCommands::Create(value.args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliOptions {
|
||||||
|
pub fn new_version_cmd_opts() -> Self {
|
||||||
|
Self {
|
||||||
|
verbose: DeprecatedVerbosityOptions::default(),
|
||||||
|
version: true,
|
||||||
|
cmd: SubCommands::Version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the given command line options.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if an argument is missing.
|
||||||
|
pub fn validate_cli(opts: &CliOptions) -> Result<(), clap::error::Error> {
|
||||||
|
match &opts.cmd {
|
||||||
|
SubCommands::Create(create_opts) => {
|
||||||
|
if let Some(dir) = create_opts
|
||||||
|
.experimental_args
|
||||||
|
.x_bootloader_directory
|
||||||
|
.as_ref()
|
||||||
|
{
|
||||||
|
warn!("Use bootloader directory: {}", dir.display());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CLI Argument collection for handling input components.
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ComponentPaths {
|
||||||
|
/// Use the content of FILE as a raw binary Linux kernel.
|
||||||
|
///
|
||||||
|
/// The Linux kernel must be a raw binary s390x Linux kernel. The ELF format
|
||||||
|
/// is not supported.
|
||||||
|
#[arg(short='i', long = "kernel", value_name = "FILE", value_hint = ValueHint::FilePath, visible_alias = "image")]
|
||||||
|
pub kernel: PathBuf,
|
||||||
|
|
||||||
|
/// Use the content of FILE as the Linux initial RAM disk.
|
||||||
|
#[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath)]
|
||||||
|
pub ramdisk: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Use the content of FILE as the Linux kernel command line.
|
||||||
|
///
|
||||||
|
/// The Linux kernel command line must be shorter than the maximum kernel
|
||||||
|
/// command line size supported by the given Linux kernel.
|
||||||
|
#[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath)]
|
||||||
|
pub parmfile: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
#[command(group(ArgGroup::new("header-flags").multiple(true).conflicts_with_all(["x_pcf", "x_scf"])))]
|
||||||
|
pub struct CreateBootImageLegacyFlags {
|
||||||
|
/// Enable Secure Execution guest dump support. This option requires the
|
||||||
|
/// '--comm-key' option.
|
||||||
|
#[arg(long, action = clap::ArgAction::SetTrue, requires="comm_key", group="header-flags")]
|
||||||
|
pub enable_dump: Option<bool>,
|
||||||
|
|
||||||
|
/// Disable Secure Execution guest dump support (default).
|
||||||
|
#[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_dump", group="header-flags")]
|
||||||
|
pub disable_dump: Option<bool>,
|
||||||
|
|
||||||
|
/// Add-secret requests must provide an extension secret that matches the
|
||||||
|
/// CCK-derived extension secret. This option requires the '--comm-key'
|
||||||
|
/// option.
|
||||||
|
#[arg(long, action = clap::ArgAction::SetTrue, requires="comm_key", group="header-flags")]
|
||||||
|
pub enable_cck_extension_secret: Option<bool>,
|
||||||
|
|
||||||
|
/// Add-secret requests don't have to provide the CCK-derived extension
|
||||||
|
/// secret (default).
|
||||||
|
#[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_cck_extension_secret", group="header-flags")]
|
||||||
|
pub disable_cck_extension_secret: Option<bool>,
|
||||||
|
|
||||||
|
/// Enable the support for the DEA, TDEA, AES, and ECC PCKMO key encryption
|
||||||
|
/// functions (default).
|
||||||
|
#[arg(long, action = clap::ArgAction::SetTrue, group="header-flags")]
|
||||||
|
pub enable_pckmo: Option<bool>,
|
||||||
|
|
||||||
|
/// Disable the support for the DEA, TDEA, AES, and ECC PCKMO key encryption
|
||||||
|
/// functions.
|
||||||
|
#[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_pckmo", group="header-flags")]
|
||||||
|
pub disable_pckmo: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
|
||||||
|
pub enum OutputFormat {
|
||||||
|
/// JSON format.
|
||||||
|
Json,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for OutputFormat {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::Json => "JSON",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct SeImgInputArgs {
|
||||||
|
/// Use INPUT as the Secure Execution image.
|
||||||
|
#[arg(value_name = "INPUT", value_hint = ValueHint::FilePath,)]
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct InfoArgs {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub input: SeImgInputArgs,
|
||||||
|
|
||||||
|
/// The output format
|
||||||
|
#[arg(long, value_enum)]
|
||||||
|
pub format: OutputFormat,
|
||||||
|
|
||||||
|
/// Use the key in FILE to decrypt the Secure Execution header.
|
||||||
|
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||||
|
pub key: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
#[command(group(ArgGroup::new("test-args").multiple(true).required(true)))]
|
||||||
|
pub struct TestArgs {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub input: SeImgInputArgs,
|
||||||
|
|
||||||
|
/// Use FILE to check for a host key document.
|
||||||
|
///
|
||||||
|
/// Verifies that the image contains the host key hash of one of the
|
||||||
|
/// specified host keys. The check fails if none of the host keys match the
|
||||||
|
/// hash in the image. This parameter can be specified multiple times.
|
||||||
|
/// Mutually exclusive with '--key-hashes'.
|
||||||
|
#[arg(
|
||||||
|
short = 'k',
|
||||||
|
long = "host-key-document",
|
||||||
|
value_name = "FILE",
|
||||||
|
value_hint = ValueHint::FilePath,
|
||||||
|
use_value_delimiter = true,
|
||||||
|
value_delimiter = ',',
|
||||||
|
group = "test-args",
|
||||||
|
)]
|
||||||
|
pub host_key_documents: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// Use FILE to check for the host key hashes provided by the ultravisor. If
|
||||||
|
/// no FILE is specified, FILE defaults to '/sys/firmware/uv/keys/all'.
|
||||||
|
///
|
||||||
|
/// The default file is only available if the local system supports the
|
||||||
|
/// Query Ultravisor Keys UVC. Verifies that the image contains the host key
|
||||||
|
/// hash of one of the specified hashes in FILE. The check fails if none of
|
||||||
|
/// the host keys match a hash in the response. Mutually exclusive with
|
||||||
|
/// '--host-key-document'.
|
||||||
|
#[arg(
|
||||||
|
long = "key-hashes",
|
||||||
|
value_name = "FILE",
|
||||||
|
value_hint = ValueHint::FilePath,
|
||||||
|
num_args = 0..=1,
|
||||||
|
require_equals = true,
|
||||||
|
default_missing_value = "/sys/firmware/uv/keys/all",
|
||||||
|
conflicts_with="host_key_documents",
|
||||||
|
group = "test-args",
|
||||||
|
)]
|
||||||
|
pub key_hashes: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an IBM Secure Execution image.
|
||||||
|
///
|
||||||
|
/// Create a new IBM Secure Execution image. Only create these images in a
|
||||||
|
/// trusted environment, such as your workstation. The 'genprotimg' command
|
||||||
|
/// creates randomly generated keys to protect the image. The generated image
|
||||||
|
/// can then be booted on an IBM Secure Execution system as a KVM guest.
|
||||||
|
///
|
||||||
|
/// Note: The 'genprotimg' command is a symbolic link to the 'pvimg create'
|
||||||
|
/// command.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct GenprotimgCliOptions {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub args: Box<CreateBootImageArgs>,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub verbose: DeprecatedVerbosityOptions,
|
||||||
|
|
||||||
|
/// Print version information and exit.
|
||||||
|
// Implemented for the help message only. Actual parsing happens in the
|
||||||
|
// version command.
|
||||||
|
#[arg(long, action = clap::ArgAction::SetTrue )]
|
||||||
|
pub version: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenprotimgCliOptions {
|
||||||
|
pub fn command() -> Command {
|
||||||
|
let cmd = <Self as CommandFactory>::command();
|
||||||
|
// Make sure that the correct binary is shown in the clap error
|
||||||
|
// messages.
|
||||||
|
cmd.bin_name("genprotimg")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn own_parse() -> CliOptions {
|
||||||
|
let args = env::args_os();
|
||||||
|
let args_len = args.len();
|
||||||
|
let version_count = args.filter(|value| value == "--version").count();
|
||||||
|
if version_count > 1 || version_count == 1 && (args_len != version_count + 1) {
|
||||||
|
Self::command()
|
||||||
|
.error(
|
||||||
|
clap::error::ErrorKind::UnknownArgument,
|
||||||
|
"unexpected argument",
|
||||||
|
)
|
||||||
|
.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
if version_count == 1 {
|
||||||
|
CliOptions::new_version_cmd_opts()
|
||||||
|
} else {
|
||||||
|
let genprotimg_opts = Self::parse();
|
||||||
|
genprotimg_opts.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct CreateBootImageArgs {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub component_paths: ComponentPaths,
|
||||||
|
|
||||||
|
/// Write the generated Secure Execution boot image to FILE.
|
||||||
|
#[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||||
|
pub output: PathBuf,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub certificate_args: CertificateOptions,
|
||||||
|
|
||||||
|
/// Disable all input component checks.
|
||||||
|
///
|
||||||
|
/// For example, for the Linux kernel, it tests if the given kernel looks
|
||||||
|
/// like a raw binary s390x kernel.
|
||||||
|
#[arg(long)]
|
||||||
|
pub no_component_check: bool,
|
||||||
|
|
||||||
|
/// Overwrite an existing Secure Execution boot image.
|
||||||
|
#[arg(long)]
|
||||||
|
pub overwrite: bool,
|
||||||
|
|
||||||
|
/// Use the content of FILE as the customer-communication key (CCK).
|
||||||
|
///
|
||||||
|
/// The file must contain exactly 32 bytes of data.
|
||||||
|
#[arg(long, value_name = "FILE")]
|
||||||
|
pub comm_key: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub legacy_flags: CreateBootImageLegacyFlags,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub experimental_args: CreateBootImageExperimentalArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Experimental options
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct CreateBootImageExperimentalArgs {
|
||||||
|
/// Manually set the directory used to load the Secure Execution bootloaders
|
||||||
|
/// (stage3a and stage3b) (experimental option).
|
||||||
|
// Hidden in user documentation.
|
||||||
|
#[arg(long, value_name = "DIR", hide(true))]
|
||||||
|
pub x_bootloader_directory: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Manually set the image components encryption key (experimental option).
|
||||||
|
// Hidden in user documentation.
|
||||||
|
#[arg(long, value_name = "FILE", hide(true))]
|
||||||
|
pub x_comp_key: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Manually set the Secure Execution header protection key (experimental option).
|
||||||
|
// Hidden in user documentation.
|
||||||
|
#[arg(long, value_name = "FILE", hide(true))]
|
||||||
|
pub x_header_key: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Manually set the PSW address used for the Secure Execution header (experimental option).
|
||||||
|
// Hidden in user documentation.
|
||||||
|
#[arg(long, value_name = "ADDRESS", hide(true))]
|
||||||
|
pub x_psw: Option<String>,
|
||||||
|
|
||||||
|
/// Manually set the plaintext control flags (experimental option).
|
||||||
|
// No validity checks made. Hidden in user documentation.
|
||||||
|
#[arg(long, value_name = "PCF", hide(true))]
|
||||||
|
pub x_pcf: Option<String>,
|
||||||
|
|
||||||
|
/// Manually set the secret control flags (experimental option).
|
||||||
|
// No validity checks made. Hidden in user documentation.
|
||||||
|
#[arg(long, value_name = "SCF", hide(true))]
|
||||||
|
pub x_scf: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Subcommand)]
|
||||||
|
pub enum SubCommands {
|
||||||
|
/// Create an IBM Secure Execution image.
|
||||||
|
///
|
||||||
|
/// Create a new IBM Secure Execution image. Only create these images in a
|
||||||
|
/// trusted environment, such as your workstation. The 'pvimg create'
|
||||||
|
/// command creates randomly generated keys to protect the image. The
|
||||||
|
/// generated image can then be booted on an IBM Secure Execution system as
|
||||||
|
/// a KVM guest.
|
||||||
|
Create(Box<CreateBootImageArgs>),
|
||||||
|
|
||||||
|
/// Print information about the IBM Secure Execution image.
|
||||||
|
///
|
||||||
|
/// Note that the API and output format is experimental and subject to
|
||||||
|
/// change.
|
||||||
|
Info(InfoArgs),
|
||||||
|
|
||||||
|
/// Test different aspects of an existing IBM Secure Execution image.
|
||||||
|
Test(Box<TestArgs>),
|
||||||
|
|
||||||
|
/// Print version information and exit.
|
||||||
|
#[command(aliases(["--version"]), hide(true))]
|
||||||
|
Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::shadow_unrelated)]
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Hash, Eq, PartialEq, Debug, Clone)]
|
||||||
|
struct CliOption {
|
||||||
|
name: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliOption {
|
||||||
|
fn new<S: AsRef<str>, T: AsRef<str>, P: AsRef<[S]>>(name: T, args: P) -> Self {
|
||||||
|
let name = name.as_ref().to_owned();
|
||||||
|
let args = args
|
||||||
|
.as_ref()
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.as_ref().to_owned())
|
||||||
|
.collect();
|
||||||
|
Self { name, args }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CliOption> for Vec<String> {
|
||||||
|
fn from(val: CliOption) -> Self {
|
||||||
|
let CliOption { args, .. } = val;
|
||||||
|
args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flat_map_collect(map: BTreeMap<String, CliOption>) -> Vec<String> {
|
||||||
|
map.into_values().flat_map(|v| v.args).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(
|
||||||
|
mut map: BTreeMap<String, CliOption>,
|
||||||
|
values: Vec<CliOption>,
|
||||||
|
) -> BTreeMap<String, CliOption> {
|
||||||
|
for value in values {
|
||||||
|
map.insert(value.name.to_owned(), value);
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove<S: AsRef<str>>(
|
||||||
|
mut map: BTreeMap<String, CliOption>,
|
||||||
|
key: S,
|
||||||
|
) -> BTreeMap<String, CliOption> {
|
||||||
|
map.remove(key.as_ref());
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn genprotimg_and_pvimg_create_args() {
|
||||||
|
// Minimal valid create arguments using no-verify
|
||||||
|
let mut mvcanv = BTreeMap::new();
|
||||||
|
mvcanv = insert(mvcanv, vec![CliOption::new("image", ["--image", "/dev/null"])]);
|
||||||
|
mvcanv = insert(mvcanv, vec![CliOption::new("hkd", ["--host-key-document", "/dev/null"])]);
|
||||||
|
mvcanv = insert(mvcanv, vec![CliOption::new("output", ["--output", "/dev/null"])]);
|
||||||
|
mvcanv = insert(mvcanv, vec![CliOption::new("no-verify", ["--no-verify"])]);
|
||||||
|
|
||||||
|
// Minimal valid create arguments using --cert
|
||||||
|
let mut mvca = mvcanv.clone();
|
||||||
|
mvca.remove("no-verify");
|
||||||
|
mvca = insert(mvca, vec![CliOption::new("cert", ["--cert", "/dev/null"])]);
|
||||||
|
|
||||||
|
let valid_create_args = [
|
||||||
|
flat_map_collect(mvcanv.clone()),
|
||||||
|
flat_map_collect(insert(remove(mvcanv.clone(), "image"), vec![CliOption::new("kernel", ["--kernel", "/dev/kernel"])])),
|
||||||
|
flat_map_collect(insert(mvcanv.clone(), vec![CliOption::new("root-ca", ["--root-ca", "/dev/null"])])),
|
||||||
|
flat_map_collect(mvca.clone()),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("quiet", ["-q"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("verbose", ["-vvv"])])),
|
||||||
|
// Verify the old verbosity is still working.
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("verbose", ["-VVV"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("offline", ["--offline"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("ramdisk", ["--ramdisk", "/dev/null"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("parmfile", ["--parmfile", "/dev/null"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"]),
|
||||||
|
CliOption::new("comm-key", ["--comm-key", "/dev/null"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"]),
|
||||||
|
CliOption::new("comm-key", ["--comm-key", "/dev/null"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-pcf", ["--x-pcf", "0x0"]),
|
||||||
|
CliOption::new("x-scf", ["--x-scf", "0x0"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-psw", ["--x-psw", "0x0"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("no-component-check", ["--no-component-check"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-pckmo", ["--enable-pckmo"])])),
|
||||||
|
];
|
||||||
|
let invalid_create_args = [
|
||||||
|
flat_map_collect(remove(mvcanv.clone(), "no-verify")),
|
||||||
|
flat_map_collect(remove(mvcanv.clone(), "image")),
|
||||||
|
flat_map_collect(remove(mvcanv.clone(), "hkd")),
|
||||||
|
flat_map_collect(remove(mvcanv, "output")),
|
||||||
|
|
||||||
|
// missing `--comm-key`
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"])])),
|
||||||
|
|
||||||
|
// -v and -q cannot be combined
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("verbose", ["-v"]),
|
||||||
|
CliOption::new("quiet", ["-q"])])),
|
||||||
|
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("image2", ["--image", "/dev/null"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("output2", ["--output", "/dev/null"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("ramdisk", ["--ramdisk", "/dev/null"]),
|
||||||
|
CliOption::new("ramdisk2", ["--ramdisk", "/dev/null"]) ])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("parmfile", ["--parmfile", "/dev/null"]),
|
||||||
|
CliOption::new("parmfile2", ["--parmfile", "/dev/null"]) ])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-pcf", ["--x-pcf", "0x0"]),
|
||||||
|
CliOption::new("x-pcf2", ["--x-pcf", "0x0"])])),
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-pckmo", ["--enable-pckmo"]),
|
||||||
|
CliOption::new("disable-pckmo", ["--disable-pckmo"])])),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut genprotimg_valid_args = vec![
|
||||||
|
// See workaround `parse_version` in `pvimg/main.rs`.
|
||||||
|
// vec!["genprotimg", "--version"],
|
||||||
|
];
|
||||||
|
let mut pvimg_valid_args = vec![
|
||||||
|
vec!["pvimg", "--version"],
|
||||||
|
vec!["pvimg", "version"],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test for invalid combinations
|
||||||
|
let mut genprotimg_invalid_args = vec![
|
||||||
|
vec!["genprotimg"],
|
||||||
|
];
|
||||||
|
let mut pvimg_invalid_args = vec![
|
||||||
|
vec!["pvimg"],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test that `genprotimg` and `pvimg create` behave equally.
|
||||||
|
for create_args in &valid_create_args {
|
||||||
|
genprotimg_valid_args.push([["genprotimg"].to_vec(), Vec::from_iter(create_args.iter().map(String::as_str))].concat());
|
||||||
|
pvimg_valid_args.push([["pvimg", "create"].to_vec(), Vec::from_iter(create_args.iter().map(String::as_str))].concat());
|
||||||
|
}
|
||||||
|
|
||||||
|
for invalid_create_args in &invalid_create_args {
|
||||||
|
genprotimg_invalid_args.push([["genprotimg"].to_vec(), Vec::from_iter(invalid_create_args.iter().map(String::as_str))].concat());
|
||||||
|
pvimg_invalid_args.push([["pvimg", "create"].to_vec(), Vec::from_iter(invalid_create_args.iter().map(String::as_str))].concat());
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in pvimg_valid_args {
|
||||||
|
let res = CliOptions::try_parse_from(&arg);
|
||||||
|
#[allow(clippy::use_debug, clippy::print_stdout)]
|
||||||
|
if let Err(e) = &res {
|
||||||
|
println!("arg: {arg:?}");
|
||||||
|
println!("{e}");
|
||||||
|
}
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in pvimg_invalid_args {
|
||||||
|
let res = CliOptions::try_parse_from(&arg);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in genprotimg_valid_args {
|
||||||
|
let res = GenprotimgCliOptions::try_parse_from(&arg);
|
||||||
|
#[allow(clippy::use_debug, clippy::print_stdout)]
|
||||||
|
if let Err(e) = &res {
|
||||||
|
println!("arg: {arg:?}");
|
||||||
|
println!("{e}");
|
||||||
|
}
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in genprotimg_invalid_args {
|
||||||
|
let res = GenprotimgCliOptions::try_parse_from(&arg);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pvimg_test_cli() {
|
||||||
|
let args = BTreeMap::new();
|
||||||
|
let valid_test_args = [
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("host-key-hashes", ["--key-hashes"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("host-key-hashes2", ["--key-hashes=/dev/null"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("host-key-hashes2", ["--key-hashes=/dev/null"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
// global works
|
||||||
|
CliOption::new("quiet", ["-q"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
// separation between keyword and positional args works
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("host-key-hashes2", ["--key-hashes=/dev/null"]),
|
||||||
|
CliOption::new("image", ["--", "/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
// Verify that the old verbosity is still working.
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("host-key-hashes2", ["--key-hashes=/dev/null"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
CliOption::new("verbose", ["-VVV"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let invalid_test_args = [
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![CliOption::new("image", ["/dev/null"])],
|
||||||
|
)),
|
||||||
|
// the argument '--key-hashes[=<FILE>]' cannot be used with '--host-key-document
|
||||||
|
// <FILE>'
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("host-key-hashes2", ["--key-hashes=/dev/null"]),
|
||||||
|
CliOption::new("host-key-document", ["--host-key-document", "/dev/null"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args,
|
||||||
|
vec![
|
||||||
|
CliOption::new("host-key-hashes2", ["--key-hashes", "/sys/null"]),
|
||||||
|
CliOption::new("image", ["--", "/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut pvimg_valid_args = vec![];
|
||||||
|
|
||||||
|
// Test for invalid combinations
|
||||||
|
// Input is missing
|
||||||
|
let mut pvimg_invalid_args = vec![vec!["pvimg", "test"]];
|
||||||
|
|
||||||
|
for create_args in &valid_test_args {
|
||||||
|
pvimg_valid_args.push(
|
||||||
|
[
|
||||||
|
["pvimg", "test"].to_vec(),
|
||||||
|
Vec::from_iter(create_args.iter().map(String::as_str)),
|
||||||
|
]
|
||||||
|
.concat(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for invalid_test_arg in &invalid_test_args {
|
||||||
|
pvimg_invalid_args.push(
|
||||||
|
[
|
||||||
|
["pvimg", "test"].to_vec(),
|
||||||
|
Vec::from_iter(invalid_test_arg.iter().map(String::as_str)),
|
||||||
|
]
|
||||||
|
.concat(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in pvimg_valid_args {
|
||||||
|
let res = CliOptions::try_parse_from(&arg);
|
||||||
|
#[allow(clippy::use_debug, clippy::print_stdout)]
|
||||||
|
if let Err(e) = &res {
|
||||||
|
println!("arg: {arg:?}");
|
||||||
|
println!("{e}");
|
||||||
|
}
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in pvimg_invalid_args {
|
||||||
|
let res = CliOptions::try_parse_from(&arg);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pvimg_info_cli() {
|
||||||
|
let args = BTreeMap::new();
|
||||||
|
let valid_test_args = [
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("format", ["--format", "json"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("format", ["--format=json"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
// separation between keyword and positional args works
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("format", ["--format=json"]),
|
||||||
|
CliOption::new("image", ["--", "/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
// Verify that the old verbosity is still working.
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("format", ["--format=json"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
CliOption::new("verbose", ["-VVV"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let invalid_test_args = [
|
||||||
|
// the argument '--key-hashes[=<FILE>]' cannot be used with '--host-key-document
|
||||||
|
// <FILE>'
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![CliOption::new("image", ["/dev/null"])],
|
||||||
|
)),
|
||||||
|
// No default defined for --format
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args,
|
||||||
|
vec![
|
||||||
|
CliOption::new("format", ["--format"]),
|
||||||
|
CliOption::new("image", ["--", "/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut pvimg_valid_args = vec![];
|
||||||
|
|
||||||
|
// Test for invalid combinations
|
||||||
|
// Input is missing
|
||||||
|
let mut pvimg_invalid_args = vec![vec!["pvimg", "test"]];
|
||||||
|
|
||||||
|
for create_args in &valid_test_args {
|
||||||
|
pvimg_valid_args.push(
|
||||||
|
[
|
||||||
|
["pvimg", "info"].to_vec(),
|
||||||
|
Vec::from_iter(create_args.iter().map(String::as_str)),
|
||||||
|
]
|
||||||
|
.concat(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for invalid_test_arg in &invalid_test_args {
|
||||||
|
pvimg_invalid_args.push(
|
||||||
|
[
|
||||||
|
["pvimg", "info"].to_vec(),
|
||||||
|
Vec::from_iter(invalid_test_arg.iter().map(String::as_str)),
|
||||||
|
]
|
||||||
|
.concat(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in pvimg_valid_args {
|
||||||
|
let res = CliOptions::try_parse_from(&arg);
|
||||||
|
#[allow(clippy::use_debug, clippy::print_stdout)]
|
||||||
|
if let Err(e) = &res {
|
||||||
|
println!("arg: {arg:?}");
|
||||||
|
println!("{e}");
|
||||||
|
}
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in pvimg_invalid_args {
|
||||||
|
let res = CliOptions::try_parse_from(&arg);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_cli() {
|
||||||
|
use clap::CommandFactory;
|
||||||
|
CliOptions::command().debug_assert();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
rust/pvimg/src/cmd.rs
Normal file
16
rust/pvimg/src/cmd.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
mod create;
|
||||||
|
mod info;
|
||||||
|
mod test;
|
||||||
|
mod version;
|
||||||
|
|
||||||
|
pub const CMD_FN: &[&str] = &["+create", "+test", "+info"];
|
||||||
|
|
||||||
|
pub use create::create;
|
||||||
|
pub use info::info;
|
||||||
|
pub use test::test;
|
||||||
|
pub use version::version;
|
||||||
85
rust/pvimg/src/cmd/common.rs
Normal file
85
rust/pvimg/src/cmd/common.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::info;
|
||||||
|
use pv::{misc::read_file, request::Confidential};
|
||||||
|
|
||||||
|
use crate::cli::CreateBootImageExperimentalArgs;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
/// Makes it easier to
|
||||||
|
macro_rules! log_println {
|
||||||
|
($($arg:tt)+) => { warn!($($arg)+) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UserProvidedKeys {
|
||||||
|
pub(crate) cck: Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||||
|
pub(crate) components_key: Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||||
|
pub(crate) aead_key: Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads all user provided keys.
|
||||||
|
pub fn read_user_provided_keys(
|
||||||
|
cck_path: Option<&Path>,
|
||||||
|
experimental_args: &CreateBootImageExperimentalArgs,
|
||||||
|
) -> Result<UserProvidedKeys> {
|
||||||
|
let components_key = {
|
||||||
|
match &experimental_args.x_comp_key {
|
||||||
|
Some(key_path) => {
|
||||||
|
info!(
|
||||||
|
"Use file '{}' as the image components protection key",
|
||||||
|
key_path.display()
|
||||||
|
);
|
||||||
|
Some((
|
||||||
|
key_path.to_owned(),
|
||||||
|
Confidential::new(read_file(key_path, "image components key")?),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let aead_key = {
|
||||||
|
match &experimental_args.x_header_key {
|
||||||
|
Some(key_path) => {
|
||||||
|
info!(
|
||||||
|
"Use file '{}' as the Secure Execution header protection",
|
||||||
|
key_path.display()
|
||||||
|
);
|
||||||
|
Some((
|
||||||
|
key_path.to_owned(),
|
||||||
|
Confidential::new(read_file(
|
||||||
|
key_path,
|
||||||
|
"Secure Execution header protection key",
|
||||||
|
)?),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let cck = {
|
||||||
|
match cck_path {
|
||||||
|
Some(key_path) => {
|
||||||
|
info!(
|
||||||
|
"Use file '{}' as the customer communication key (CCK)",
|
||||||
|
key_path.display()
|
||||||
|
);
|
||||||
|
Some((
|
||||||
|
key_path.to_owned(),
|
||||||
|
(Confidential::new(read_file(key_path, "customer communication key (CCK)")?)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(UserProvidedKeys {
|
||||||
|
cck,
|
||||||
|
components_key,
|
||||||
|
aead_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
196
rust/pvimg/src/cmd/create.rs
Normal file
196
rust/pvimg/src/cmd/create.rs
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::{fs::OpenOptions, io::BufReader};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use log::{debug, warn};
|
||||||
|
use pv::misc::{open_file, try_parse_u64};
|
||||||
|
use pvimg::{
|
||||||
|
error::OwnExitCode,
|
||||||
|
secured_comp::ComponentTrait,
|
||||||
|
uvdata::{
|
||||||
|
ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, PlaintextControlFlagsV1, ScfV1,
|
||||||
|
SeHdrDataV1, SecretControlFlagsV1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use utils::{AtomicFile, AtomicFileOperation};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cli::{ComponentPaths, CreateBootImageArgs},
|
||||||
|
cmd::common::read_user_provided_keys,
|
||||||
|
se_img::{SeHdrArgs, SeImgBuilder},
|
||||||
|
se_img_comps::{
|
||||||
|
check_components, cmdline::Cmdline, kernel::S390Kernel, ramdisk::Ramdisk, Component,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The returned vector is sorted by the occurrence in the memory layout:
|
||||||
|
/// First the kernel, then the ramdisk and then the kernel cmdline.
|
||||||
|
///
|
||||||
|
/// Keep this ordering in sync with the ordering of [`ComponentKind`]!
|
||||||
|
fn components(component_args: &ComponentPaths) -> Result<Vec<Component>> {
|
||||||
|
// IMPORTANT: Don't change the order of the components: kernel, ramdisk, and
|
||||||
|
// then parmline! This is important since ALD, PLD and TLD is sorted by the
|
||||||
|
// component address.
|
||||||
|
let mut components: Vec<Component> =
|
||||||
|
vec![S390Kernel::new(Box::new(BufReader::new(open_file(&component_args.kernel)?))).into()];
|
||||||
|
if let Some(path) = &component_args.ramdisk {
|
||||||
|
components.push(Ramdisk::new(Box::new(BufReader::new(open_file(path)?))).into());
|
||||||
|
}
|
||||||
|
if let Some(path) = &component_args.parmfile {
|
||||||
|
components.push(Cmdline::new(Box::new(BufReader::new(open_file(path)?))).into());
|
||||||
|
}
|
||||||
|
Ok(components)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_flags(
|
||||||
|
args: &CreateBootImageArgs,
|
||||||
|
) -> Result<(PlaintextControlFlagsV1, SecretControlFlagsV1)> {
|
||||||
|
let lf = &args.legacy_flags;
|
||||||
|
let plaintext_flags: Vec<FlagData<PcfV1>> = [
|
||||||
|
lf.disable_dump
|
||||||
|
.filter(|x| *x)
|
||||||
|
.and(Some(PcfV1::all_disabled([PcfV1::AllowDumping]))),
|
||||||
|
lf.enable_dump
|
||||||
|
.filter(|x| *x)
|
||||||
|
.and(Some(PcfV1::all_disabled([PcfV1::AllowDumping]))),
|
||||||
|
lf.disable_pckmo
|
||||||
|
.filter(|x| *x)
|
||||||
|
.and(Some(PcfV1::all_disabled([
|
||||||
|
PcfV1::PckmoAes,
|
||||||
|
PcfV1::PckmoDeaTdea,
|
||||||
|
PcfV1::PckmoEcc,
|
||||||
|
]))),
|
||||||
|
lf.enable_pckmo.filter(|x| *x).and(Some(PcfV1::all_enabled([
|
||||||
|
PcfV1::PckmoAes,
|
||||||
|
PcfV1::PckmoDeaTdea,
|
||||||
|
PcfV1::PckmoEcc,
|
||||||
|
]))),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
// This is ensured by Clap's `conflicts_with`.
|
||||||
|
assert!(PlaintextControlFlagsV1::no_duplicates(&plaintext_flags));
|
||||||
|
|
||||||
|
let secret_flags: Vec<FlagData<ScfV1>> = [
|
||||||
|
lf.disable_cck_extension_secret
|
||||||
|
.filter(|x| *x)
|
||||||
|
.and(Some(ScfV1::all_disabled([
|
||||||
|
ScfV1::CCKExtensionSecretEnforcment,
|
||||||
|
]))),
|
||||||
|
lf.enable_cck_extension_secret
|
||||||
|
.filter(|x| *x)
|
||||||
|
.and(Some(ScfV1::all_enabled([
|
||||||
|
ScfV1::CCKExtensionSecretEnforcment,
|
||||||
|
]))),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
// This is ensured by Clap's `conflicts_with`.
|
||||||
|
assert!(SecretControlFlagsV1::no_duplicates(&secret_flags));
|
||||||
|
|
||||||
|
let mut pcf: PlaintextControlFlagsV1 = match &args.experimental_args.x_pcf {
|
||||||
|
Some(v) => try_parse_u64(v, "x-pcf")?.into(),
|
||||||
|
None => PlaintextControlFlagsV1::default(),
|
||||||
|
};
|
||||||
|
pcf.parse_flags(&plaintext_flags);
|
||||||
|
debug!("Using plaintext flags: {pcf}");
|
||||||
|
|
||||||
|
let mut scf: SecretControlFlagsV1 = match &args.experimental_args.x_scf {
|
||||||
|
Some(v) => try_parse_u64(v, "x-scf")?.into(),
|
||||||
|
None => SecretControlFlagsV1::default(),
|
||||||
|
};
|
||||||
|
scf.parse_flags(&secret_flags);
|
||||||
|
debug!("Using secret flags: {scf}");
|
||||||
|
|
||||||
|
Ok((pcf, scf))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a Secure Execution boot image
|
||||||
|
pub fn create(opt: &CreateBootImageArgs) -> Result<OwnExitCode> {
|
||||||
|
// Verify host key documents first, because if they are not valid there is
|
||||||
|
// no reason to continue.
|
||||||
|
let verified_host_keys = opt
|
||||||
|
.certificate_args
|
||||||
|
.get_verified_hkds("Secure Execution image")?;
|
||||||
|
let user_provided_keys =
|
||||||
|
read_user_provided_keys(opt.comm_key.as_deref(), &opt.experimental_args)?;
|
||||||
|
let (plaintext_flags, secret_flags) = parse_flags(opt)?;
|
||||||
|
|
||||||
|
let mut components = components(&opt.component_paths)?;
|
||||||
|
if opt.no_component_check {
|
||||||
|
warn!("The component check is turned off!");
|
||||||
|
} else {
|
||||||
|
check_components(&mut components)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME get rid of the legacy mode. But that's only possible as soon as all
|
||||||
|
// available tools are updated.
|
||||||
|
let expected_se_hdr_size = SeHdrDataV1::expected_size(verified_host_keys.len())?;
|
||||||
|
let mut writer = AtomicFile::with_extension(&opt.output, "part", &mut OpenOptions::new())?;
|
||||||
|
let mut seimg_ctx = SeImgBuilder::new_v1(
|
||||||
|
&mut writer,
|
||||||
|
plaintext_flags.is_unset(PcfV1::NoComponentEncryption),
|
||||||
|
Some(expected_se_hdr_size),
|
||||||
|
opt.experimental_args.x_bootloader_directory.as_ref(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Enable expert mode
|
||||||
|
seimg_ctx.i_know_what_i_am_doing();
|
||||||
|
if let Some((path, key)) = user_provided_keys.components_key {
|
||||||
|
seimg_ctx.set_components_key(key).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to use '{}' as the image components key",
|
||||||
|
path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let psw_addr: Option<u64> = match &opt.experimental_args.x_psw {
|
||||||
|
Some(v) => try_parse_u64(v, "x-psw")?.into(),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
for mut component in components.into_iter() {
|
||||||
|
seimg_ctx
|
||||||
|
.prepare_and_append_as_secure_component(&mut component, None)
|
||||||
|
.with_context(|| format!("Failed to prepare {} component", component.kind()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let img_comps = seimg_ctx.finish(SeHdrArgs {
|
||||||
|
keys: verified_host_keys.as_slice(),
|
||||||
|
pcf: &plaintext_flags,
|
||||||
|
scf: &secret_flags,
|
||||||
|
cck: &user_provided_keys.cck,
|
||||||
|
hdr_aead_key: &user_provided_keys.aead_key,
|
||||||
|
psw_addr: &psw_addr,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
debug!("");
|
||||||
|
debug!("----------------------------------------------------------------");
|
||||||
|
debug!("| {:^60} |", "Secure Execution image layout");
|
||||||
|
debug!("|--------------------------------------------------------------|");
|
||||||
|
debug!("| {:<23} | {:<34} |", "Component type", "Component address");
|
||||||
|
debug!("|-------------------------|------------------------------------|");
|
||||||
|
img_comps
|
||||||
|
.iter()
|
||||||
|
.for_each(|img_comp| debug!("{img_comp:<33}"));
|
||||||
|
debug!("----------------------------------------------------------------");
|
||||||
|
|
||||||
|
// Rename the file `$OUTPUT.part` to `$OUTPUT` for achieving atomic file
|
||||||
|
// creation.
|
||||||
|
let op = match opt.overwrite {
|
||||||
|
true => AtomicFileOperation::Replace,
|
||||||
|
false => AtomicFileOperation::NoReplace,
|
||||||
|
};
|
||||||
|
writer.finish(op)?;
|
||||||
|
|
||||||
|
warn!("Successfully generated the Secure Execution image.");
|
||||||
|
Ok(OwnExitCode::Success)
|
||||||
|
}
|
||||||
40
rust/pvimg/src/cmd/info.rs
Normal file
40
rust/pvimg/src/cmd/info.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::info;
|
||||||
|
use pv::{
|
||||||
|
misc::{open_file, read_file},
|
||||||
|
request::SymKey,
|
||||||
|
};
|
||||||
|
use pvimg::{
|
||||||
|
error::OwnExitCode,
|
||||||
|
uvdata::{KeyExchangeTrait, SeHdr, UvDataTrait},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::cli::InfoArgs;
|
||||||
|
|
||||||
|
pub fn info(opt: &InfoArgs) -> Result<OwnExitCode> {
|
||||||
|
info!(
|
||||||
|
"Reading Secure Execution header {}",
|
||||||
|
opt.input.path.display()
|
||||||
|
);
|
||||||
|
let mut input = open_file(&opt.input.path)?;
|
||||||
|
let mut output = std::io::stdout();
|
||||||
|
|
||||||
|
SeHdr::seek_sehdr(&mut input, None)?;
|
||||||
|
let hdr = SeHdr::try_from_io(input)?;
|
||||||
|
if let Some(key_path) = &opt.key {
|
||||||
|
let key =
|
||||||
|
SymKey::try_from_data(hdr.key_type(), read_file(key_path, "Reading key")?.into())?;
|
||||||
|
serde_json::to_writer_pretty(&mut output, &hdr.decrypt(&key)?)?;
|
||||||
|
} else {
|
||||||
|
serde_json::to_writer_pretty(&mut output, &hdr)?;
|
||||||
|
}
|
||||||
|
writeln!(output)?;
|
||||||
|
|
||||||
|
Ok(OwnExitCode::Success)
|
||||||
|
}
|
||||||
122
rust/pvimg/src/cmd/test.rs
Normal file
122
rust/pvimg/src/cmd/test.rs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::{info, warn};
|
||||||
|
use pv::{
|
||||||
|
misc::{open_file, read_certs, read_file},
|
||||||
|
FileAccessErrorType, PvCoreError,
|
||||||
|
};
|
||||||
|
use pvimg::{
|
||||||
|
error::{Error, OwnExitCode, PvError},
|
||||||
|
uvdata::{KeyExchangeTrait, SeHdr, UvKeyHashesV1},
|
||||||
|
};
|
||||||
|
use utils::HexSlice;
|
||||||
|
|
||||||
|
use crate::{cli::TestArgs, log_println};
|
||||||
|
|
||||||
|
/// Returns `Ok(true)` if at least one of the hashes is included.
|
||||||
|
fn hdr_test_target_hashes(hdr: &SeHdr, key_hashes: &Path) -> Result<bool> {
|
||||||
|
let file = open_file(key_hashes).map_err(|err| match err {
|
||||||
|
PvCoreError::FileAccess {
|
||||||
|
ref ty,
|
||||||
|
ref path,
|
||||||
|
ref source,
|
||||||
|
} if matches!(ty, FileAccessErrorType::Open)
|
||||||
|
&& source.kind() == std::io::ErrorKind::NotFound
|
||||||
|
&& *path == PathBuf::from(UvKeyHashesV1::SYS_UV_KEYS_ALL) =>
|
||||||
|
{
|
||||||
|
Error::UnavailableQueryUvKeyHashesSupport { source: err }
|
||||||
|
}
|
||||||
|
err => Error::PvCore(err),
|
||||||
|
})?;
|
||||||
|
let hashes = UvKeyHashesV1::read_from_io(file)?;
|
||||||
|
let mut contains = hdr.contains_hash(&hashes.pchkh);
|
||||||
|
if contains {
|
||||||
|
log_println!(
|
||||||
|
" ✓ Host key hash {:#} is included",
|
||||||
|
HexSlice::from(&hashes.pchkh)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if hdr.contains_hash(&hashes.pbhkh) {
|
||||||
|
log_println!(
|
||||||
|
" ✓ Backup host key hash {:#} is included",
|
||||||
|
HexSlice::from(&hashes.pbhkh)
|
||||||
|
);
|
||||||
|
contains = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for hash in hashes.res {
|
||||||
|
if hdr.contains_hash(&hash) {
|
||||||
|
log_println!(" ✓ Key hash {:#} is included", HexSlice::from(&hash));
|
||||||
|
contains = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !contains {
|
||||||
|
warn!(" ✘ None of the key hashes is included");
|
||||||
|
}
|
||||||
|
Ok(contains)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `Ok(true)` if at least one of the given public key of the host key
|
||||||
|
/// documents was used for the image creation or if no host key document was
|
||||||
|
/// specified.
|
||||||
|
fn hdr_test_hkd<P>(hdr: &SeHdr, host_key_documents: &[P]) -> Result<bool>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
if host_key_documents.is_empty() {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = false;
|
||||||
|
for path in host_key_documents {
|
||||||
|
let hkd_path = path.as_ref();
|
||||||
|
let hkd_data = read_file(hkd_path, "host key document")?;
|
||||||
|
let certs = read_certs(&hkd_data)?;
|
||||||
|
if certs.is_empty() {
|
||||||
|
return Err(PvError::NoHkdInFile(hkd_path.display().to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if certs.len() != 1 {
|
||||||
|
warn!("The host key document in '{}' contains more than one certificate! Only the first certificate will be used.",
|
||||||
|
hkd_path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic: len is == 1 -> unwrap will succeed/not panic
|
||||||
|
let cert = certs.first().unwrap();
|
||||||
|
if hdr.contains(cert.public_key()?)? {
|
||||||
|
result = true;
|
||||||
|
log_println!(" ✓ Host key document '{}' is included", hkd_path.display());
|
||||||
|
} else {
|
||||||
|
log_println!(
|
||||||
|
" ✘ Host key document '{}' is not included",
|
||||||
|
hkd_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test(opt: &TestArgs) -> Result<OwnExitCode> {
|
||||||
|
info!("Testing a Secure Execution image");
|
||||||
|
|
||||||
|
let mut input = open_file(&opt.input.path)?;
|
||||||
|
SeHdr::seek_sehdr(&mut input, None)?;
|
||||||
|
let hdr = SeHdr::try_from_io(input)?;
|
||||||
|
|
||||||
|
let mut success = hdr_test_hkd(&hdr, &opt.host_key_documents)?;
|
||||||
|
if let Some(path) = &opt.key_hashes {
|
||||||
|
success = hdr_test_target_hashes(&hdr, path)? && success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(if success {
|
||||||
|
OwnExitCode::Success
|
||||||
|
} else {
|
||||||
|
OwnExitCode::GenericError
|
||||||
|
})
|
||||||
|
}
|
||||||
18
rust/pvimg/src/cmd/version.rs
Normal file
18
rust/pvimg/src/cmd/version.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::LevelFilter;
|
||||||
|
use pvimg::error::OwnExitCode;
|
||||||
|
use utils::print_version;
|
||||||
|
|
||||||
|
use crate::cmd;
|
||||||
|
|
||||||
|
const FEATURES: &[&[&str]] = &[cmd::CMD_FN];
|
||||||
|
|
||||||
|
/// Print the version
|
||||||
|
pub fn version(filter: LevelFilter) -> Result<OwnExitCode> {
|
||||||
|
print_version!("2024", filter; FEATURES.concat());
|
||||||
|
Ok(OwnExitCode::Success)
|
||||||
|
}
|
||||||
71
rust/pvimg/src/main.rs
Normal file
71
rust/pvimg/src/main.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
//! # pvimg
|
||||||
|
//!
|
||||||
|
//! `pvimg` is a command line utility to create and inspect IBM Secure
|
||||||
|
//! Execution boot images.
|
||||||
|
//!
|
||||||
|
//! Use `pvimg` to create a IBM Secure Execution boot image file, which can
|
||||||
|
//! be loaded using `zipl` or `QEMU`. The tool can also be used to inspect
|
||||||
|
//! existing Secure Execution boot images.
|
||||||
|
|
||||||
|
mod cli;
|
||||||
|
mod cmd;
|
||||||
|
mod se_img;
|
||||||
|
mod se_img_comps;
|
||||||
|
|
||||||
|
use std::{env, process::ExitCode};
|
||||||
|
|
||||||
|
use clap::{Command, CommandFactory, Parser};
|
||||||
|
use cli::{validate_cli, CliOptions, SubCommands};
|
||||||
|
use log::trace;
|
||||||
|
use pvimg::error::OwnExitCode;
|
||||||
|
use utils::{print_cli_error, print_error, PvLogger};
|
||||||
|
|
||||||
|
use crate::cli::GenprotimgCliOptions;
|
||||||
|
|
||||||
|
static LOGGER: PvLogger = PvLogger;
|
||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
let exe = env::args_os().next().unwrap();
|
||||||
|
let (opts, cmd): (CliOptions, Command) = match exe.to_str() {
|
||||||
|
// Test if the symlink executable 'genprotimg' was used. If so use the
|
||||||
|
// `pvimg create` command directly.
|
||||||
|
Some(val) if val.ends_with("genprotimg") => (
|
||||||
|
GenprotimgCliOptions::own_parse(),
|
||||||
|
GenprotimgCliOptions::command(),
|
||||||
|
),
|
||||||
|
_ => (CliOptions::parse(), CliOptions::command()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let verbosity = opts.verbose.to_level_filter();
|
||||||
|
if let Err(e) = LOGGER.start(verbosity) {
|
||||||
|
unreachable!("Logger error: {e:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
match validate_cli(&opts) {
|
||||||
|
Ok(opts) => opts,
|
||||||
|
Err(e) => {
|
||||||
|
let _ = print_cli_error(e, cmd);
|
||||||
|
return OwnExitCode::UsageError.into();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE trace verbosity is disabled in release builds
|
||||||
|
trace!("Trace verbosity, may leak secrets to command-line");
|
||||||
|
trace!("Options {opts:?}");
|
||||||
|
|
||||||
|
let res = match &opts.cmd {
|
||||||
|
SubCommands::Create(opt) => cmd::create(opt),
|
||||||
|
SubCommands::Info(opt) => cmd::info(opt),
|
||||||
|
SubCommands::Test(opt) => cmd::test(opt),
|
||||||
|
SubCommands::Version => cmd::version(verbosity),
|
||||||
|
};
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(own_exit_code) => own_exit_code.into(),
|
||||||
|
Err(e) => print_error(&e, verbosity),
|
||||||
|
}
|
||||||
|
}
|
||||||
488
rust/pvimg/src/se_img.rs
Normal file
488
rust/pvimg/src/se_img.rs
Normal file
@@ -0,0 +1,488 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt::Display,
|
||||||
|
io::{Cursor, Seek, SeekFrom, Write},
|
||||||
|
path::PathBuf,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use deku::DekuContainerRead;
|
||||||
|
use log::debug;
|
||||||
|
use openssl::pkey::{PKey, Public};
|
||||||
|
use pv::{misc::read_file, request::Confidential};
|
||||||
|
use pvimg::{
|
||||||
|
error::Error,
|
||||||
|
misc::{round_up, serialize_to_bytes, ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA},
|
||||||
|
secured_comp::{ComponentTrait, Interval, Layout, SecuredComponent, SecuredComponentBuilder},
|
||||||
|
uvdata::{
|
||||||
|
BuilderTrait, PlaintextControlFlagsV1, SeHdrBuilder, SeHdrVersion, SecretControlFlagsV1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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 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, 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![]);
|
||||||
|
}
|
||||||
|
}
|
||||||
374
rust/pvimg/src/se_img_comps.rs
Normal file
374
rust/pvimg/src/se_img_comps.rs
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
io::{Read, Seek, SeekFrom},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use deku::{ctx::Endian, DekuRead, DekuWrite};
|
||||||
|
use enum_dispatch::enum_dispatch;
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
pub use crate::se_img_comps::bootloader::{
|
||||||
|
create_ipib, render_stage3a, render_stage3b, stage3a_path, stage3b_path, STAGE3A_ENTRY,
|
||||||
|
STAGE3A_INIT_ENTRY, STAGE3A_LOAD_ADDRESS,
|
||||||
|
};
|
||||||
|
use crate::se_img_comps::ipib::Ipib;
|
||||||
|
|
||||||
|
mod bootloader;
|
||||||
|
|
||||||
|
pub mod cmdline;
|
||||||
|
pub mod ipib;
|
||||||
|
pub mod kernel;
|
||||||
|
pub mod ramdisk;
|
||||||
|
pub mod sehdr;
|
||||||
|
pub mod shortpsw;
|
||||||
|
pub mod stage3a;
|
||||||
|
pub mod stage3b;
|
||||||
|
|
||||||
|
/// A trait for checking a component.
|
||||||
|
#[enum_dispatch]
|
||||||
|
trait ComponentCheckTrait: ComponentTrait<ComponentKind> {
|
||||||
|
/// Check the component
|
||||||
|
///
|
||||||
|
/// Note: The implementer does not have to care about resetting the file position
|
||||||
|
/// as this is done by [`ComponentCheckCtx`].
|
||||||
|
fn check(&mut self, ctx: &ComponentCheckCtx) -> Result<()>;
|
||||||
|
|
||||||
|
/// Initialize [`ComponentCheckCtx`], e.g. it reads what max kernel command
|
||||||
|
/// line is supported by the given Linux kernel.
|
||||||
|
///
|
||||||
|
/// Note: The implementer does not have to care about resetting the file
|
||||||
|
/// position as this is done by the [`ComponentCheckCtx`]
|
||||||
|
fn init_ctx(&mut self, ctx: &mut ComponentCheckCtx) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ComponentCheckCtx {
|
||||||
|
max_kernel_cmdline_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ComponentCheckCtx {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
max_kernel_cmdline_size: S390Kernel::LEGACY_MAX_COMMAND_LINE_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCheckCtx {
|
||||||
|
fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize component context.
|
||||||
|
fn init(&mut self, component: &mut Component) -> Result<()> {
|
||||||
|
let old_pos = component.stream_position()?;
|
||||||
|
let result = component.init_ctx(self);
|
||||||
|
component.seek(SeekFrom::Start(old_pos))?;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check component.
|
||||||
|
fn check_comp(&self, component: &mut Component) -> Result<()> {
|
||||||
|
let old_pos = component.stream_position()?;
|
||||||
|
let result = component.check(self);
|
||||||
|
component.seek(SeekFrom::Start(old_pos))?;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the given components.
|
||||||
|
///
|
||||||
|
/// The original stream position of the components remains as it was before
|
||||||
|
/// calling this function.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if there was an IO error or the component
|
||||||
|
/// check has failed.
|
||||||
|
pub fn check_components(components: &mut [Component]) -> Result<(), anyhow::Error> {
|
||||||
|
let mut components_ctx = ComponentCheckCtx::new();
|
||||||
|
for component in components.iter_mut() {
|
||||||
|
components_ctx
|
||||||
|
.init(component)
|
||||||
|
.with_context(|| format!("Check for {} component has failed", component.kind()))?;
|
||||||
|
}
|
||||||
|
for component in components.iter_mut() {
|
||||||
|
components_ctx
|
||||||
|
.check_comp(component)
|
||||||
|
.with_context(|| format!("Check for {} component has failed", component.kind()))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[enum_dispatch(ComponentCheckTrait)]
|
||||||
|
pub enum Component {
|
||||||
|
ShortPSW(ShortPSWComp),
|
||||||
|
Stage3a(Stage3a),
|
||||||
|
Kernel(S390Kernel),
|
||||||
|
Ramdisk(Ramdisk),
|
||||||
|
Cmdline(Cmdline),
|
||||||
|
Stage3b(Stage3b),
|
||||||
|
SeHdr(SeHdrComp),
|
||||||
|
Ipib(Ipib),
|
||||||
|
}
|
||||||
|
|
||||||
|
// No `enum_dispatch` can be used since the trait is implemented in another
|
||||||
|
// crate.
|
||||||
|
impl Seek for Component {
|
||||||
|
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||||
|
match self {
|
||||||
|
Self::ShortPSW(obj) => obj.seek(pos),
|
||||||
|
Self::Stage3a(obj) => obj.seek(pos),
|
||||||
|
Self::Kernel(obj) => obj.seek(pos),
|
||||||
|
Self::Ramdisk(obj) => obj.seek(pos),
|
||||||
|
Self::Cmdline(obj) => obj.seek(pos),
|
||||||
|
Self::Stage3b(obj) => obj.seek(pos),
|
||||||
|
Self::SeHdr(obj) => obj.seek(pos),
|
||||||
|
Self::Ipib(obj) => obj.seek(pos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No `enum_dispatch` can be used since the trait is implemented in another
|
||||||
|
// crate.
|
||||||
|
impl Read for Component {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
match self {
|
||||||
|
Self::ShortPSW(obj) => obj.read(buf),
|
||||||
|
Self::Stage3a(obj) => obj.read(buf),
|
||||||
|
Self::Kernel(obj) => obj.read(buf),
|
||||||
|
Self::Ramdisk(obj) => obj.read(buf),
|
||||||
|
Self::Cmdline(obj) => obj.read(buf),
|
||||||
|
Self::Stage3b(obj) => obj.read(buf),
|
||||||
|
Self::SeHdr(obj) => obj.read(buf),
|
||||||
|
Self::Ipib(obj) => obj.read(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No `enum_dispatch` can be used since the trait is implemented in another
|
||||||
|
// crate.
|
||||||
|
impl ComponentTrait<ComponentKind> for Component {
|
||||||
|
fn secure_mode(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::ShortPSW(obj) => obj.secure_mode(),
|
||||||
|
Self::Stage3a(obj) => obj.secure_mode(),
|
||||||
|
Self::Kernel(obj) => obj.secure_mode(),
|
||||||
|
Self::Ramdisk(obj) => obj.secure_mode(),
|
||||||
|
Self::Cmdline(obj) => obj.secure_mode(),
|
||||||
|
Self::Stage3b(obj) => obj.secure_mode(),
|
||||||
|
Self::SeHdr(obj) => obj.secure_mode(),
|
||||||
|
Self::Ipib(obj) => obj.secure_mode(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind(&self) -> ComponentKind {
|
||||||
|
match self {
|
||||||
|
Self::ShortPSW(obj) => obj.kind(),
|
||||||
|
Self::Stage3a(obj) => obj.kind(),
|
||||||
|
Self::Kernel(obj) => obj.kind(),
|
||||||
|
Self::Ramdisk(obj) => obj.kind(),
|
||||||
|
Self::Cmdline(obj) => obj.kind(),
|
||||||
|
Self::Stage3b(obj) => obj.kind(),
|
||||||
|
Self::SeHdr(obj) => obj.kind(),
|
||||||
|
Self::Ipib(obj) => obj.kind(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trick to be able to pass it as `&dyn ReadSeekDebug`
|
||||||
|
pub trait ReadSeekDebug: Read + Seek + Debug {}
|
||||||
|
impl<T: Read + Seek + Debug> ReadSeekDebug for T {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CompReader {
|
||||||
|
reader: Box<dyn ReadSeekDebug>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompReader {
|
||||||
|
pub fn new(reader: Box<dyn ReadSeekDebug>) -> Self {
|
||||||
|
Self { reader }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for CompReader {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.reader.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for CompReader {
|
||||||
|
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||||
|
self.reader.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The order of enum variants implicitly defines the order of the secured
|
||||||
|
/// components within the Secure Execution image!
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq)]
|
||||||
|
pub enum ComponentKind {
|
||||||
|
ShortPSW = 10,
|
||||||
|
Stage3a = 30,
|
||||||
|
Kernel = 40,
|
||||||
|
Ramdisk = 50,
|
||||||
|
Cmdline = 60,
|
||||||
|
Stage3b = 70,
|
||||||
|
SeHdr = 80,
|
||||||
|
Ipib = 90,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentKind {
|
||||||
|
pub fn tweak_prefix(&self) -> u16 {
|
||||||
|
self.clone() as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_tweak_prefix(value: u16) -> Self {
|
||||||
|
// Safety: `value` must correspond to a discriminant value of `Self`
|
||||||
|
unsafe { std::mem::transmute(value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq, Clone, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct CompTweakPrefV1 {
|
||||||
|
pub comp_prefix: u16,
|
||||||
|
pub rand: [u8; 6],
|
||||||
|
}
|
||||||
|
impl CompTweakPrefV1 {
|
||||||
|
fn to_u64(&self) -> u64 {
|
||||||
|
let mut bytes_be = self.comp_prefix.to_be_bytes().to_vec();
|
||||||
|
bytes_be.extend_from_slice(self.rand.as_slice());
|
||||||
|
assert_eq!(bytes_be.len(), 8);
|
||||||
|
// Safety: `bytes_be ` is guaranteed to be 8 bytes long.
|
||||||
|
u64::from_be_bytes(bytes_be.try_into().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq, Clone, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct CompTweakV1 {
|
||||||
|
pub pref: CompTweakPrefV1,
|
||||||
|
pub pg_idx: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompTweakV1 {
|
||||||
|
pub fn new(kind: ComponentKind) -> Result<Self> {
|
||||||
|
let pref = CompTweakPrefV1 {
|
||||||
|
comp_prefix: kind.tweak_prefix(),
|
||||||
|
rand: random_array()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self { pref, pg_idx: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn comp_prefix(&self) -> u16 {
|
||||||
|
self.pref.comp_prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::shadow_unrelated)]
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use deku::{DekuContainerRead, DekuContainerWrite};
|
||||||
|
use proptest::{
|
||||||
|
prelude::{Just, Strategy},
|
||||||
|
prop_assert_eq, prop_oneof, proptest,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{ComponentCheckCtx, ComponentKind};
|
||||||
|
use crate::se_img_comps::{check_components, kernel::S390Kernel, CompTweakPrefV1, CompTweakV1};
|
||||||
|
|
||||||
|
fn component_kind_strategy() -> impl Strategy<Value = ComponentKind> {
|
||||||
|
prop_oneof![
|
||||||
|
Just(ComponentKind::ShortPSW),
|
||||||
|
Just(ComponentKind::Stage3a),
|
||||||
|
Just(ComponentKind::Kernel),
|
||||||
|
Just(ComponentKind::Ramdisk),
|
||||||
|
Just(ComponentKind::Cmdline),
|
||||||
|
Just(ComponentKind::Stage3b),
|
||||||
|
Just(ComponentKind::SeHdr),
|
||||||
|
Just(ComponentKind::Ipib),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn tweak_prefix_back_to_original(kind in component_kind_strategy()) {
|
||||||
|
let prefix = kind.tweak_prefix();
|
||||||
|
prop_assert_eq!(kind, ComponentKind::from_tweak_prefix(prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compctx() {
|
||||||
|
let ctx = ComponentCheckCtx::new();
|
||||||
|
assert_eq!(
|
||||||
|
ctx.max_kernel_cmdline_size,
|
||||||
|
S390Kernel::LEGACY_MAX_COMMAND_LINE_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_components() {
|
||||||
|
check_components(&mut []).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn comptweak_v1() {
|
||||||
|
let tweak = CompTweakV1 {
|
||||||
|
pref: CompTweakPrefV1 {
|
||||||
|
comp_prefix: 3,
|
||||||
|
rand: [157, 239, 44, 103, 219, 118],
|
||||||
|
},
|
||||||
|
pg_idx: 0,
|
||||||
|
};
|
||||||
|
let bytes = [0, 3, 157, 239, 44, 103, 219, 118, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
assert_eq!(tweak.pref.to_u64(), 1018075497880438);
|
||||||
|
assert_eq!(tweak.to_bytes().unwrap(), bytes,);
|
||||||
|
assert_eq!(CompTweakV1::from_bytes((&bytes, 0)).unwrap().1, tweak);
|
||||||
|
|
||||||
|
let tweak = CompTweakV1 {
|
||||||
|
pref: CompTweakPrefV1 {
|
||||||
|
comp_prefix: 0,
|
||||||
|
rand: [0; 6],
|
||||||
|
},
|
||||||
|
pg_idx: 0,
|
||||||
|
};
|
||||||
|
let bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
assert_eq!(tweak.pref.to_u64(), 0);
|
||||||
|
assert_eq!(tweak.to_bytes().unwrap(), bytes,);
|
||||||
|
assert_eq!(CompTweakV1::from_bytes((&bytes, 0)).unwrap().1, tweak);
|
||||||
|
}
|
||||||
|
}
|
||||||
216
rust/pvimg/src/se_img_comps/bootloader.rs
Normal file
216
rust/pvimg/src/se_img_comps/bootloader.rs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::{io::Cursor, path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
|
pub mod ipl;
|
||||||
|
mod stage3a_defs;
|
||||||
|
mod stage3b_defs;
|
||||||
|
use ipl::IPL_PARM_BLOCK_PV_VERSION;
|
||||||
|
use log::trace;
|
||||||
|
use pvimg::{
|
||||||
|
error::{Error, Result},
|
||||||
|
misc::{serialize_to_bytes, PSW},
|
||||||
|
secured_comp::Interval,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use self::stage3a_defs::{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,
|
||||||
|
IPL_PARM_BLOCK_VERSION,
|
||||||
|
},
|
||||||
|
stage3b_defs::{memblob, stage3b_args},
|
||||||
|
};
|
||||||
|
use super::CompTweakPrefV1;
|
||||||
|
use crate::{
|
||||||
|
se_img::ImgComponent,
|
||||||
|
se_img_comps::{
|
||||||
|
bootloader::stage3a_defs::stage3a_args, stage3a::Stage3a, stage3b::Stage3b, ComponentKind,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Get the `PVIMG_PKGDATADIR` used for `pvimg`
|
||||||
|
///
|
||||||
|
/// Provides the package data directory for `pvimg`.
|
||||||
|
/// For release builds this requires the environment variable
|
||||||
|
/// `PVIMG_PKGDATADIR` to be present at compile time.
|
||||||
|
/// For debug builds this value defaults to `CARGO_MANIFEST_DIR/boot/`
|
||||||
|
/// if that variable is not present.
|
||||||
|
/// Should only be used by binary targets!!
|
||||||
|
///
|
||||||
|
/// Collapses to a compile time constant, that is likely to be inlined by the
|
||||||
|
/// compiler in release builds.
|
||||||
|
macro_rules! pvimg_pkg_data {
|
||||||
|
() => {{
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
match option_env!("PVIMG_PKGDATADIR") {
|
||||||
|
Some(data) => data,
|
||||||
|
None => concat!(env!("CARGO_MANIFEST_DIR"), "/boot/"),
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
env!("PVIMG_PKGDATADIR", "env 'PVIMG_PKGDATADIR' must be set for release builds. Trigger build using the s390-tools build system or export the variable yourself")
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bootloader_dir(path: Option<&PathBuf>) -> PathBuf {
|
||||||
|
path.map_or_else(|| PathBuf::from(pvimg_pkg_data!()), |v| v.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path to `stage3a.bin`.
|
||||||
|
pub fn stage3a_path(dir: Option<&PathBuf>) -> PathBuf {
|
||||||
|
bootloader_dir(dir).join("stage3a.bin")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path to `stage3b_reloc.bin`.
|
||||||
|
pub fn stage3b_path(dir: Option<&PathBuf>) -> PathBuf {
|
||||||
|
bootloader_dir(dir).join("stage3b_reloc.bin")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render stage3b "template"
|
||||||
|
pub fn render_stage3a(
|
||||||
|
mut stage3a: Vec<u8>,
|
||||||
|
stage3a_addr: u64,
|
||||||
|
se_hdr_src: &Interval,
|
||||||
|
ipib_src: &Interval,
|
||||||
|
) -> Result<Stage3a> {
|
||||||
|
let stage3a_size = stage3a.len();
|
||||||
|
let stage3a_size_u64: u64 = stage3a_size.try_into()?;
|
||||||
|
|
||||||
|
if stage3a_size < 24 {
|
||||||
|
unreachable!("Bug!");
|
||||||
|
}
|
||||||
|
let stage3a_data_addr = stage3a_addr
|
||||||
|
.checked_add(stage3a_size_u64)
|
||||||
|
.ok_or(Error::UnexpectedOverflow)?
|
||||||
|
- 24;
|
||||||
|
assert!(
|
||||||
|
se_hdr_src.start
|
||||||
|
> stage3a_addr
|
||||||
|
.checked_add(stage3a_size_u64)
|
||||||
|
.ok_or(Error::UnexpectedOverflow)?
|
||||||
|
);
|
||||||
|
// IMPORTANT: Secure Execution header must be located AFTER the stage3a
|
||||||
|
// loader.
|
||||||
|
let hdr_offs = se_hdr_src
|
||||||
|
.start
|
||||||
|
.checked_sub(stage3a_data_addr)
|
||||||
|
.ok_or(Error::UnexpectedUnderflow)?;
|
||||||
|
assert!(
|
||||||
|
ipib_src.start
|
||||||
|
> stage3a_addr
|
||||||
|
.checked_add(stage3a_size_u64)
|
||||||
|
.ok_or(Error::UnexpectedOverflow)?
|
||||||
|
);
|
||||||
|
// IMPORTANT: IPIB must be located AFTER the stage3a loader.
|
||||||
|
let ipib_offs = ipib_src
|
||||||
|
.start
|
||||||
|
.checked_sub(stage3a_data_addr)
|
||||||
|
.ok_or(Error::UnexpectedUnderflow)?;
|
||||||
|
let args = stage3a_args {
|
||||||
|
hdr_offs,
|
||||||
|
hdr_size: se_hdr_src.size(),
|
||||||
|
ipib_offs,
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("stage3a arguments: {args:#x?}");
|
||||||
|
|
||||||
|
let stage3a_args_bin = serialize_to_bytes(&args)?;
|
||||||
|
assert_eq!(stage3a_args_bin.len(), 24);
|
||||||
|
|
||||||
|
// Insert the stage3a arguments
|
||||||
|
assert!(stage3a_size > stage3a_args_bin.len());
|
||||||
|
stage3a.splice(stage3a_size - stage3a_args_bin.len().., stage3a_args_bin);
|
||||||
|
Ok(Stage3a::new(Box::new(Cursor::new(stage3a))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render stage3b "template"
|
||||||
|
pub fn render_stage3b(
|
||||||
|
mut stage3b: Vec<u8>,
|
||||||
|
psw: PSW,
|
||||||
|
prepared_comps: &[Rc<ImgComponent>],
|
||||||
|
) -> Result<Stage3b> {
|
||||||
|
let mut args = stage3b_args {
|
||||||
|
psw,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
prepared_comps
|
||||||
|
.iter()
|
||||||
|
.filter(|comp| comp.secure_mode.is_some() && comp.kind() != ComponentKind::Stage3b)
|
||||||
|
.map(|comp| {
|
||||||
|
// Safety: Safe because of the filtering.
|
||||||
|
let secure_mode_data = comp.secure_mode.as_ref().unwrap();
|
||||||
|
let src = comp.src.start;
|
||||||
|
let size = secure_mode_data.original_size.try_into()?;
|
||||||
|
match comp.kind() {
|
||||||
|
ComponentKind::Cmdline => args.cmdline = memblob { src, size },
|
||||||
|
ComponentKind::Kernel => args.kernel = memblob { src, size },
|
||||||
|
ComponentKind::Ramdisk => args.initrd = memblob { src, size },
|
||||||
|
ComponentKind::Stage3a
|
||||||
|
| ComponentKind::Ipib
|
||||||
|
| ComponentKind::SeHdr
|
||||||
|
| ComponentKind::ShortPSW
|
||||||
|
| ComponentKind::Stage3b => unreachable!(),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
if prepared_comps.len() > 3 {
|
||||||
|
// That would mean there is a bug somewhere.
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("stage3b arguments: {args:#x?}");
|
||||||
|
|
||||||
|
let stage3b_args_bin = serialize_to_bytes(&args)?;
|
||||||
|
assert_eq!(stage3b_args_bin.len(), 64);
|
||||||
|
|
||||||
|
let stage3b_len = stage3b.len();
|
||||||
|
let stage3b_args_bin_len = stage3b_args_bin.len();
|
||||||
|
|
||||||
|
// Insert the stage3b arguments
|
||||||
|
assert!(stage3b_len > stage3b_args_bin_len);
|
||||||
|
let stage3b_parms_off = stage3b_len - stage3b_args_bin_len;
|
||||||
|
stage3b.splice(stage3b_parms_off.., stage3b_args_bin);
|
||||||
|
|
||||||
|
Ok(Stage3b::new(Box::new(Cursor::new(stage3b))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_ipib(
|
||||||
|
hdr: &Interval,
|
||||||
|
img_comps: Vec<(CompTweakPrefV1, Rc<Interval>)>,
|
||||||
|
) -> Result<ipl_parameter_block> {
|
||||||
|
let mut components = vec![];
|
||||||
|
for (tweak_pref, src) in img_comps {
|
||||||
|
components.push(ipl_pb0_pv_comp {
|
||||||
|
tweak_pref: tweak_pref.to_u64(),
|
||||||
|
addr: src.start,
|
||||||
|
len: src.size(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let comps_len = components.len();
|
||||||
|
let ipip_len = ipl_parameter_block::size(comps_len)?.try_into()?;
|
||||||
|
let ipip_pv_len = ipl_pb0_pv::size(comps_len)?.try_into()?;
|
||||||
|
let ipib = ipl_parameter_block {
|
||||||
|
hdr: ipl_pl_hdr {
|
||||||
|
len: ipip_len,
|
||||||
|
flags: 0,
|
||||||
|
version: IPL_PARM_BLOCK_VERSION,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
pv: ipl_pb0_pv {
|
||||||
|
len: ipip_pv_len,
|
||||||
|
pbt: ipl_pbt_IPL_PBT_PV,
|
||||||
|
version: IPL_PARM_BLOCK_PV_VERSION,
|
||||||
|
num_comp: comps_len.try_into()?,
|
||||||
|
pv_hdr_addr: hdr.start,
|
||||||
|
pv_hdr_size: hdr.size(),
|
||||||
|
components,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(ipib)
|
||||||
|
}
|
||||||
118
rust/pvimg/src/se_img_comps/bootloader/ipl.rs
Normal file
118
rust/pvimg/src/se_img_comps/bootloader/ipl.rs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
// Based on the output of rust-bindgen 0.69.1
|
||||||
|
#![allow(nonstandard_style, unused)]
|
||||||
|
|
||||||
|
use deku::{ctx::Endian, prelude::*};
|
||||||
|
use pvimg::{error::Result, misc::bytesize};
|
||||||
|
|
||||||
|
pub const IPL_FLAG_SECURE: u32 = 64;
|
||||||
|
pub const IPL_RB_COMPONENT_FLAG_SIGNED: u32 = 128;
|
||||||
|
pub const IPL_RB_COMPONENT_FLAG_VERIFIED: u32 = 64;
|
||||||
|
pub const IPL_MAX_SUPPORTED_VERSION: u32 = 0;
|
||||||
|
pub const IPL_PARM_BLOCK_VERSION: u8 = 1;
|
||||||
|
pub const IPL_PARM_BLOCK_PV_VERSION: u8 = 1;
|
||||||
|
pub const ipl_pbt_IPL_PBT_FCP: ipl_pbt = 0;
|
||||||
|
pub const ipl_pbt_IPL_PBT_SCP_DATA: ipl_pbt = 1;
|
||||||
|
pub const ipl_pbt_IPL_PBT_CCW: ipl_pbt = 2;
|
||||||
|
pub const ipl_pbt_IPL_PBT_ECKD: ipl_pbt = 3;
|
||||||
|
pub const ipl_pbt_IPL_PBT_NVME: ipl_pbt = 4;
|
||||||
|
pub const ipl_pbt_IPL_PBT_PV: ipl_pbt = 5;
|
||||||
|
pub type ipl_pbt = u8;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Clone, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct ipl_pl_hdr {
|
||||||
|
pub len: u32,
|
||||||
|
pub flags: u8,
|
||||||
|
pub reserved1: [u8; 2_usize],
|
||||||
|
pub version: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Clone, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct ipl_pb0_pv_comp {
|
||||||
|
pub tweak_pref: u64,
|
||||||
|
pub addr: u64,
|
||||||
|
pub len: u64,
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct ipl_pb0_pv {
|
||||||
|
pub len: u32,
|
||||||
|
pub pbt: u8,
|
||||||
|
pub reserved1: [u8; 3_usize],
|
||||||
|
pub loadparm: [u8; 8_usize],
|
||||||
|
pub reserved2: [u8; 84_usize],
|
||||||
|
pub reserved3: [u8; 3_usize],
|
||||||
|
pub version: u8,
|
||||||
|
pub reserved4: [u8; 4_usize],
|
||||||
|
pub num_comp: u32,
|
||||||
|
pub pv_hdr_addr: u64,
|
||||||
|
pub pv_hdr_size: u64,
|
||||||
|
#[deku(count = "num_comp")]
|
||||||
|
pub components: Vec<ipl_pb0_pv_comp>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ipl_pb0_pv {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
len: Default::default(),
|
||||||
|
pbt: Default::default(),
|
||||||
|
reserved1: Default::default(),
|
||||||
|
loadparm: Default::default(),
|
||||||
|
reserved2: [0; 84],
|
||||||
|
reserved3: Default::default(),
|
||||||
|
version: Default::default(),
|
||||||
|
reserved4: Default::default(),
|
||||||
|
num_comp: Default::default(),
|
||||||
|
pv_hdr_addr: Default::default(),
|
||||||
|
pv_hdr_size: Default::default(),
|
||||||
|
components: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Clone, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct ipl_parameter_block {
|
||||||
|
pub hdr: ipl_pl_hdr,
|
||||||
|
pub pv: ipl_pb0_pv,
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
impl ipl_parameter_block {
|
||||||
|
pub fn size(num_comp: usize) -> Result<usize> {
|
||||||
|
let comps = iter::repeat(ipl_pb0_pv_comp::default())
|
||||||
|
.take(num_comp)
|
||||||
|
.collect();
|
||||||
|
let ipib = Self {
|
||||||
|
pv: ipl_pb0_pv {
|
||||||
|
components: comps,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
bytesize(&ipib)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ipl_pb0_pv {
|
||||||
|
pub fn size(num_comp: usize) -> Result<usize> {
|
||||||
|
let comp = ipl_pb0_pv_comp::default();
|
||||||
|
let comps = iter::repeat(comp).take(num_comp).collect();
|
||||||
|
let ipl = Self {
|
||||||
|
components: comps,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
bytesize(&ipl)
|
||||||
|
}
|
||||||
|
}
|
||||||
68
rust/pvimg/src/se_img_comps/bootloader/stage3a_defs.rs
Normal file
68
rust/pvimg/src/se_img_comps/bootloader/stage3a_defs.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
// Based on the output of rust-bindgen 0.69.1
|
||||||
|
#![allow(nonstandard_style)]
|
||||||
|
use deku::{ctx::Endian, prelude::*};
|
||||||
|
|
||||||
|
pub const IMAGE_ENTRY: u64 = 0x10000;
|
||||||
|
pub const STAGE3A_INIT_ENTRY: u64 = IMAGE_ENTRY;
|
||||||
|
pub const STAGE3A_ENTRY: u64 = STAGE3A_INIT_ENTRY + 0x1000;
|
||||||
|
pub const STAGE3A_LOAD_ADDRESS: u64 = STAGE3A_INIT_ENTRY;
|
||||||
|
pub const STAGE3A_BSS_ADDRESS: u64 = 0xc000;
|
||||||
|
pub const STAGE3A_BSS_SIZE: u64 = 0x1000;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct stage3a_args {
|
||||||
|
pub hdr_offs: u64,
|
||||||
|
pub hdr_size: u64,
|
||||||
|
pub ipib_offs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_stage3a_args() {
|
||||||
|
const UNINIT: ::std::mem::MaybeUninit<stage3a_args> = ::std::mem::MaybeUninit::uninit();
|
||||||
|
let ptr = UNINIT.as_ptr();
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<stage3a_args>(),
|
||||||
|
24_usize,
|
||||||
|
concat!("Size of: ", stringify!(stage3a_args))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<stage3a_args>(),
|
||||||
|
8_usize,
|
||||||
|
concat!("Alignment of ", stringify!(stage3a_args))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { ::std::ptr::addr_of!((*ptr).hdr_offs) as usize - ptr as usize },
|
||||||
|
0_usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(stage3a_args),
|
||||||
|
"::",
|
||||||
|
stringify!(hdr_offs)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { ::std::ptr::addr_of!((*ptr).hdr_size) as usize - ptr as usize },
|
||||||
|
8_usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(stage3a_args),
|
||||||
|
"::",
|
||||||
|
stringify!(hdr_size)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { ::std::ptr::addr_of!((*ptr).ipib_offs) as usize - ptr as usize },
|
||||||
|
16_usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(stage3a_args),
|
||||||
|
"::",
|
||||||
|
stringify!(ipib_offs)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
116
rust/pvimg/src/se_img_comps/bootloader/stage3b_defs.rs
Normal file
116
rust/pvimg/src/se_img_comps/bootloader/stage3b_defs.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
// Based on the output of rust-bindgen 0.69.1
|
||||||
|
#![allow(non_camel_case_types, non_snake_case, nonstandard_style)]
|
||||||
|
use deku::{ctx::Endian, prelude::*};
|
||||||
|
use pvimg::misc::PSW;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct memblob {
|
||||||
|
pub src: u64,
|
||||||
|
pub size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_memblob() {
|
||||||
|
const UNINIT: ::std::mem::MaybeUninit<memblob> = ::std::mem::MaybeUninit::uninit();
|
||||||
|
let ptr = UNINIT.as_ptr();
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<memblob>(),
|
||||||
|
16_usize,
|
||||||
|
concat!("Size of: ", stringify!(memblob))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<memblob>(),
|
||||||
|
8_usize,
|
||||||
|
concat!("Alignment of ", stringify!(memblob))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { ::std::ptr::addr_of!((*ptr).src) as usize - ptr as usize },
|
||||||
|
0_usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(memblob),
|
||||||
|
"::",
|
||||||
|
stringify!(src)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { ::std::ptr::addr_of!((*ptr).size) as usize - ptr as usize },
|
||||||
|
8_usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(memblob),
|
||||||
|
"::",
|
||||||
|
stringify!(size)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct stage3b_args {
|
||||||
|
pub kernel: memblob,
|
||||||
|
pub cmdline: memblob,
|
||||||
|
pub initrd: memblob,
|
||||||
|
pub psw: PSW,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_stage3b_args() {
|
||||||
|
const UNINIT: ::std::mem::MaybeUninit<stage3b_args> = ::std::mem::MaybeUninit::uninit();
|
||||||
|
let ptr = UNINIT.as_ptr();
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<stage3b_args>(),
|
||||||
|
64_usize,
|
||||||
|
concat!("Size of: ", stringify!(stage3b_args))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<stage3b_args>(),
|
||||||
|
8_usize,
|
||||||
|
concat!("Alignment of ", stringify!(stage3b_args))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { ::std::ptr::addr_of!((*ptr).kernel) as usize - ptr as usize },
|
||||||
|
0_usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(stage3b_args),
|
||||||
|
"::",
|
||||||
|
stringify!(kernel)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { ::std::ptr::addr_of!((*ptr).cmdline) as usize - ptr as usize },
|
||||||
|
16_usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(stage3b_args),
|
||||||
|
"::",
|
||||||
|
stringify!(cmdline)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { ::std::ptr::addr_of!((*ptr).initrd) as usize - ptr as usize },
|
||||||
|
32_usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(stage3b_args),
|
||||||
|
"::",
|
||||||
|
stringify!(initrd)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { ::std::ptr::addr_of!((*ptr).psw) as usize - ptr as usize },
|
||||||
|
48_usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(stage3b_args),
|
||||||
|
"::",
|
||||||
|
stringify!(psw)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
91
rust/pvimg/src/se_img_comps/cmdline.rs
Normal file
91
rust/pvimg/src/se_img_comps/cmdline.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
|
use pvimg::error::{Error, Result};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
CompReader, ComponentCheckCtx, ComponentCheckTrait, ComponentKind, ComponentTrait,
|
||||||
|
ReadSeekDebug,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Cmdline {
|
||||||
|
comp: CompReader,
|
||||||
|
last_value: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cmdline {
|
||||||
|
pub fn new(reader: Box<dyn ReadSeekDebug>) -> Self {
|
||||||
|
Self {
|
||||||
|
comp: CompReader { reader },
|
||||||
|
last_value: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Cmdline {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
// Make sure that the kernel cmdline always is C NUL-terminated.
|
||||||
|
let size = self.comp.read(buf)?;
|
||||||
|
// Store last value
|
||||||
|
if size > 0 {
|
||||||
|
self.last_value = Some(buf[size - 1]);
|
||||||
|
return Ok(size);
|
||||||
|
}
|
||||||
|
if buf.is_empty() {
|
||||||
|
return Ok(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EOF has been reached, check for NUL-Terminator
|
||||||
|
assert!(size == 0);
|
||||||
|
|
||||||
|
// Was the last value a NUL-Terminator?
|
||||||
|
if self.last_value.is_some_and(|x| x == b'\0') {
|
||||||
|
return Ok(size);
|
||||||
|
}
|
||||||
|
// Store a NUL-Terminator in buf so the next `read(...)` call will stop.
|
||||||
|
buf[0] = b'\0';
|
||||||
|
self.last_value = Some(buf[0]);
|
||||||
|
Ok(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for Cmdline {
|
||||||
|
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||||
|
// Invalidate last value after seeking
|
||||||
|
self.last_value = None;
|
||||||
|
self.comp.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCheckTrait for Cmdline {
|
||||||
|
fn check(&mut self, ctx: &ComponentCheckCtx) -> Result<()> {
|
||||||
|
let mut buf = vec![];
|
||||||
|
let size = self.read_to_end(&mut buf)?;
|
||||||
|
if size > ctx.max_kernel_cmdline_size {
|
||||||
|
return Err(Error::KernelCmdlineTooLarge {
|
||||||
|
size,
|
||||||
|
max_size: ctx.max_kernel_cmdline_size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_ctx(&mut self, _ctx: &mut ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTrait<ComponentKind> for Cmdline {
|
||||||
|
fn kind(&self) -> ComponentKind {
|
||||||
|
ComponentKind::Cmdline
|
||||||
|
}
|
||||||
|
|
||||||
|
fn secure_mode(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
52
rust/pvimg/src/se_img_comps/ipib.rs
Normal file
52
rust/pvimg/src/se_img_comps/ipib.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
use pvimg::error::Result;
|
||||||
|
use pvimg::secured_comp::ComponentTrait;
|
||||||
|
|
||||||
|
use super::ComponentKind;
|
||||||
|
use super::{CompReader, ComponentCheckCtx, ComponentCheckTrait, ReadSeekDebug};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Ipib(CompReader);
|
||||||
|
|
||||||
|
impl Ipib {
|
||||||
|
pub fn new(reader: Box<dyn ReadSeekDebug>) -> Self {
|
||||||
|
Self(CompReader { reader })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Ipib {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.0.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for Ipib {
|
||||||
|
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||||
|
self.0.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCheckTrait for Ipib {
|
||||||
|
fn check(&mut self, _ctx: &ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_ctx(&mut self, _ctx: &mut ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTrait<ComponentKind> for Ipib {
|
||||||
|
fn secure_mode(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind(&self) -> ComponentKind {
|
||||||
|
ComponentKind::Ipib
|
||||||
|
}
|
||||||
|
}
|
||||||
100
rust/pvimg/src/se_img_comps/kernel.rs
Normal file
100
rust/pvimg/src/se_img_comps/kernel.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
|
use pvimg::error::{Error, Result};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
CompReader, ComponentCheckCtx, ComponentCheckTrait, ComponentKind, ComponentTrait,
|
||||||
|
ReadSeekDebug,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct S390Kernel(CompReader);
|
||||||
|
|
||||||
|
impl S390Kernel {
|
||||||
|
const ELF_MAGIC: [u8; Self::ELF_MAGIC_SIZE] = [0x7f, 0x45, 0x4c, 0x46];
|
||||||
|
const ELF_MAGIC_OFF: u64 = 0x0;
|
||||||
|
const ELF_MAGIC_SIZE: usize = 4;
|
||||||
|
const KERNEL_COMMAND_LINE_SIZE_ADDR: u64 = 0x10430;
|
||||||
|
const KERNEL_COMMAND_LINE_SIZE_LEN: usize = 8;
|
||||||
|
pub const KERNEL_ENTRY: u64 = 0x10000;
|
||||||
|
pub const LEGACY_MAX_COMMAND_LINE_SIZE: usize = 896;
|
||||||
|
const S390EP: [u8; Self::S390EP_SIZE] = [0x53, 0x33, 0x39, 0x30, 0x45, 0x50];
|
||||||
|
// Location of "S390EP" in a Linux binary (see arch/s390/boot/head.S)
|
||||||
|
const S390EP_OFFS: u64 = 0x10008;
|
||||||
|
const S390EP_SIZE: usize = 6;
|
||||||
|
|
||||||
|
pub fn new(reader: Box<dyn ReadSeekDebug>) -> Self {
|
||||||
|
Self(CompReader { reader })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_elf_file(&mut self) -> Result<bool> {
|
||||||
|
self.seek(SeekFrom::Start(Self::ELF_MAGIC_OFF))?;
|
||||||
|
let mut buf = [0x0_u8; Self::ELF_MAGIC_SIZE];
|
||||||
|
self.read_exact(&mut buf)?;
|
||||||
|
Ok(buf == Self::ELF_MAGIC)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_s390x_kernel(&mut self) -> Result<bool> {
|
||||||
|
self.seek(SeekFrom::Start(Self::S390EP_OFFS))?;
|
||||||
|
let mut buf = [0_u8; Self::S390EP_SIZE];
|
||||||
|
self.read_exact(&mut buf)?;
|
||||||
|
Ok(buf == Self::S390EP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_max_kernel_cmdline_size(&mut self) -> Result<usize> {
|
||||||
|
self.seek(SeekFrom::Start(Self::KERNEL_COMMAND_LINE_SIZE_ADDR))?;
|
||||||
|
let mut buf = [0x0_u8; Self::KERNEL_COMMAND_LINE_SIZE_LEN];
|
||||||
|
self.read_exact(&mut buf).map_err(|e| match e.kind() {
|
||||||
|
std::io::ErrorKind::UnexpectedEof => Error::NoS390Kernel,
|
||||||
|
_ => e.into(),
|
||||||
|
})?;
|
||||||
|
let mut max_size = u64::from_be_bytes(buf).try_into()?;
|
||||||
|
if max_size == 0 {
|
||||||
|
max_size = Self::LEGACY_MAX_COMMAND_LINE_SIZE;
|
||||||
|
}
|
||||||
|
Ok(max_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for S390Kernel {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.0.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for S390Kernel {
|
||||||
|
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||||
|
self.0.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCheckTrait for S390Kernel {
|
||||||
|
fn check(&mut self, _ctx: &ComponentCheckCtx) -> Result<()> {
|
||||||
|
if self.is_elf_file()? {
|
||||||
|
return Err(Error::UnexpectedElfFile);
|
||||||
|
}
|
||||||
|
if !self.is_s390x_kernel()? {
|
||||||
|
return Err(Error::NoS390Kernel);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_ctx(&mut self, ctx: &mut ComponentCheckCtx) -> Result<()> {
|
||||||
|
ctx.max_kernel_cmdline_size = self.read_max_kernel_cmdline_size()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTrait<ComponentKind> for S390Kernel {
|
||||||
|
fn secure_mode(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind(&self) -> ComponentKind {
|
||||||
|
ComponentKind::Kernel
|
||||||
|
}
|
||||||
|
}
|
||||||
51
rust/pvimg/src/se_img_comps/ramdisk.rs
Normal file
51
rust/pvimg/src/se_img_comps/ramdisk.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
use pvimg::error::Result;
|
||||||
|
|
||||||
|
use super::ComponentKind;
|
||||||
|
use super::{CompReader, ComponentCheckCtx, ComponentCheckTrait, ComponentTrait, ReadSeekDebug};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Ramdisk(CompReader);
|
||||||
|
|
||||||
|
impl Ramdisk {
|
||||||
|
pub fn new(reader: Box<dyn ReadSeekDebug>) -> Self {
|
||||||
|
Self(CompReader { reader })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Ramdisk {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.0.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for Ramdisk {
|
||||||
|
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||||
|
self.0.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCheckTrait for Ramdisk {
|
||||||
|
fn check(&mut self, _ctx: &ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_ctx(&mut self, _ctx: &mut ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTrait<ComponentKind> for Ramdisk {
|
||||||
|
fn kind(&self) -> ComponentKind {
|
||||||
|
ComponentKind::Ramdisk
|
||||||
|
}
|
||||||
|
|
||||||
|
fn secure_mode(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
52
rust/pvimg/src/se_img_comps/sehdr.rs
Normal file
52
rust/pvimg/src/se_img_comps/sehdr.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
use pvimg::error::Result;
|
||||||
|
use pvimg::secured_comp::ComponentTrait;
|
||||||
|
|
||||||
|
use super::ComponentKind;
|
||||||
|
use super::{CompReader, ComponentCheckCtx, ComponentCheckTrait, ReadSeekDebug};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SeHdrComp(pub CompReader);
|
||||||
|
|
||||||
|
impl SeHdrComp {
|
||||||
|
pub fn new(reader: Box<dyn ReadSeekDebug>) -> Self {
|
||||||
|
Self(CompReader { reader })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for SeHdrComp {
|
||||||
|
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||||
|
self.0.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for SeHdrComp {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.0.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCheckTrait for SeHdrComp {
|
||||||
|
fn check(&mut self, _ctx: &ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_ctx(&mut self, _ctx: &mut ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTrait<ComponentKind> for SeHdrComp {
|
||||||
|
fn kind(&self) -> ComponentKind {
|
||||||
|
ComponentKind::SeHdr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn secure_mode(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
55
rust/pvimg/src/se_img_comps/shortpsw.rs
Normal file
55
rust/pvimg/src/se_img_comps/shortpsw.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
use pvimg::error::Result;
|
||||||
|
use pvimg::secured_comp::ComponentTrait;
|
||||||
|
|
||||||
|
use super::ComponentKind;
|
||||||
|
use super::{CompReader, ComponentCheckCtx, ComponentCheckTrait, ReadSeekDebug};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ShortPSWComp(CompReader);
|
||||||
|
|
||||||
|
impl ShortPSWComp {
|
||||||
|
/// Offset in the Secure Execution image
|
||||||
|
pub const OFFSET: u64 = 0x0;
|
||||||
|
|
||||||
|
pub fn new(reader: Box<dyn ReadSeekDebug>) -> Self {
|
||||||
|
Self(CompReader { reader })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for ShortPSWComp {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.0.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for ShortPSWComp {
|
||||||
|
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||||
|
self.0.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCheckTrait for ShortPSWComp {
|
||||||
|
fn check(&mut self, _ctx: &ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_ctx(&mut self, _ctx: &mut ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTrait<ComponentKind> for ShortPSWComp {
|
||||||
|
fn kind(&self) -> ComponentKind {
|
||||||
|
ComponentKind::ShortPSW
|
||||||
|
}
|
||||||
|
|
||||||
|
fn secure_mode(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
51
rust/pvimg/src/se_img_comps/stage3a.rs
Normal file
51
rust/pvimg/src/se_img_comps/stage3a.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
use pvimg::error::Result;
|
||||||
|
use pvimg::secured_comp::ComponentTrait;
|
||||||
|
|
||||||
|
use super::ComponentKind;
|
||||||
|
use super::{CompReader, ComponentCheckCtx, ComponentCheckTrait, ReadSeekDebug};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Stage3a(CompReader);
|
||||||
|
|
||||||
|
impl Stage3a {
|
||||||
|
pub fn new(reader: Box<dyn ReadSeekDebug>) -> Self {
|
||||||
|
Self(CompReader { reader })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Stage3a {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.0.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Seek for Stage3a {
|
||||||
|
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||||
|
self.0.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCheckTrait for Stage3a {
|
||||||
|
fn check(&mut self, _ctx: &ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_ctx(&mut self, _ctx: &mut ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTrait<ComponentKind> for Stage3a {
|
||||||
|
fn kind(&self) -> ComponentKind {
|
||||||
|
ComponentKind::Stage3a
|
||||||
|
}
|
||||||
|
|
||||||
|
fn secure_mode(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
52
rust/pvimg/src/se_img_comps/stage3b.rs
Normal file
52
rust/pvimg/src/se_img_comps/stage3b.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
use pvimg::error::Result;
|
||||||
|
use pvimg::secured_comp::ComponentTrait;
|
||||||
|
|
||||||
|
use super::ComponentKind;
|
||||||
|
use super::{CompReader, ComponentCheckCtx, ComponentCheckTrait, ReadSeekDebug};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Stage3b(CompReader);
|
||||||
|
|
||||||
|
impl Stage3b {
|
||||||
|
pub fn new(reader: Box<dyn ReadSeekDebug>) -> Self {
|
||||||
|
Self(CompReader::new(reader))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Stage3b {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.0.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for Stage3b {
|
||||||
|
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||||
|
self.0.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCheckTrait for Stage3b {
|
||||||
|
fn check(&mut self, _ctx: &ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_ctx(&mut self, _ctx: &mut ComponentCheckCtx) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTrait<ComponentKind> for Stage3b {
|
||||||
|
fn kind(&self) -> ComponentKind {
|
||||||
|
ComponentKind::Stage3b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn secure_mode(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
/// CLI Argument collection for handling host-keys, IBM signing keys, and certificates.
|
/// CLI Argument collection for handling host-keys, IBM signing keys, and certificates.
|
||||||
#[derive(Args, Debug, PartialEq, Eq, Default)]
|
#[derive(Args, Debug, Clone, PartialEq, Eq, Default)]
|
||||||
#[command(
|
#[command(
|
||||||
group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])),
|
group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])),
|
||||||
)]
|
)]
|
||||||
|
|||||||
Reference in New Issue
Block a user