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
|
||||
- pvsecret:
|
||||
Manage secrets for IBM Secure Execution guests
|
||||
- pvimg:
|
||||
Create and inspect IBM Secure Execution images
|
||||
|
||||
* dasdfmt:
|
||||
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
|
||||
build options:
|
||||
|
||||
| __LIBRARY__ | __BUILD OPTION__ | __TOOLS__ |
|
||||
|----------------|:------------------:|:-------------------------------------:|
|
||||
| fuse3 | `HAVE_FUSE` | cmsfs-fuse, zdsfs, hmcdrvfs, zgetdump,|
|
||||
| | | hsavmcore |
|
||||
| zlib | `HAVE_ZLIB` | zgetdump, dump2tar |
|
||||
| ncurses | `HAVE_NCURSES` | hyptop |
|
||||
| net-snmp | `HAVE_SNMP` | osasnmpd |
|
||||
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
|
||||
| openssl | `HAVE_OPENSSL` | genprotimg, zkey, libekmfweb, |
|
||||
| | | libkmipclient, zgetdump, |
|
||||
| | | rust/pvattest, rust/pvsecret, |
|
||||
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
|
||||
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
|
||||
| | | libkmipclient |
|
||||
| glib2 | `HAVE_GLIB2` | genprotimg, zgetdump |
|
||||
| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient,|
|
||||
| | | rust/pvattest, rust/pvsecret, |
|
||||
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
|
||||
| systemd | `HAVE_SYSTEMD` | hsavmcore |
|
||||
| libudev | `HAVE_LIBUDEV` | cpacfstatsd |
|
||||
| __LIBRARY__ | __BUILD OPTION__ | __TOOLS__ |
|
||||
|--------------|:------------------:|:--------------------------------------:|
|
||||
| fuse3 | `HAVE_FUSE` | cmsfs-fuse, zdsfs, hmcdrvfs, zgetdump, |
|
||||
| | | hsavmcore |
|
||||
| zlib | `HAVE_ZLIB` | zgetdump, dump2tar |
|
||||
| ncurses | `HAVE_NCURSES` | hyptop |
|
||||
| net-snmp | `HAVE_SNMP` | osasnmpd |
|
||||
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
|
||||
| openssl | `HAVE_OPENSSL` | genprotimg, zkey, libekmfweb, |
|
||||
| | | libkmipclient, zgetdump, |
|
||||
| | | rust/pvattest, rust/pvsecret, |
|
||||
| | | rust/pvimg |
|
||||
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
|
||||
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
|
||||
| | | libkmipclient |
|
||||
| glib2 | `HAVE_GLIB2` | genprotimg, zgetdump |
|
||||
| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient, |
|
||||
| | | rust/pvattest, rust/pvsecret, |
|
||||
| | | rust/pvimg |
|
||||
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
|
||||
| systemd | `HAVE_SYSTEMD` | hsavmcore |
|
||||
| libudev | `HAVE_LIBUDEV` | cpacfstatsd |
|
||||
|
||||
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.
|
||||
|
||||
* 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:
|
||||
For building pvattest you need OpenSSL version 1.1.1 or newer
|
||||
installed (openssl-devel.rpm). Also required is cargo and libcurl.
|
||||
|
||||
@@ -25,7 +25,7 @@ endif #HOSTARCH
|
||||
|
||||
ifneq (${HAVE_OPENSSL},0)
|
||||
ifneq (${HAVE_LIBCURL},0)
|
||||
PV_TARGETS := pvsecret pvattest
|
||||
PV_TARGETS := pvsecret pvattest pvimg
|
||||
|
||||
ifeq ($(HOST_ARCH),s390x)
|
||||
PV_TARGETS += pvapconfig
|
||||
@@ -88,6 +88,7 @@ install: $(INSTALL_TARGETS)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(PVIMG_PKGDATADIR)
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvimg/tools/check_hostkeydoc $(DESTDIR)$(PVIMG_PKGDATADIR)
|
||||
$(MAKE) -C pvimg/boot install
|
||||
ln -sf pvimg $(DESTDIR)$(USRBINDIR)/genprotimg
|
||||
|
||||
print-rust-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;
|
||||
|
||||
/// 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(
|
||||
group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])),
|
||||
)]
|
||||
|
||||
Reference in New Issue
Block a user