Compare commits

..

136 Commits

Author SHA1 Message Date
Jan Höppner
6f15ed3264 New release s390-tools-2.31.0
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 17:49:44 +01:00
Joern Siglen
0d2b5af007 dbginfo.sh: check for DPM mode
check if the LPAR is running in DPM mode and reflect this in the summary.

Suggested-by: Mike Storzer <MSTORZER@de.ibm.com>
Acked-by: Eberhard Pasch <epasch@de.ibm.com>
Reviewed-by: Mario Held <mario.held@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Jan Höppner
9e7a8f48e8 common.mak: Set default C/C++ standard to gnu11/gnu++11
s390-tools doesn't define a common C/C++ standard at the moment.
Set the standard to gnu11/gnu++11, which is also used by the Kernel, and
establish a common baseline for all tools.

The -std flag is added to ALL_CFLAGS and ALL_CXXFLAGS to avoid losing it
in case CFLAGS are set by an outside entity.
It is also added to CLAGS_FOR_BUILD for this one special cross build
case.

The -std flag is removed from all tools that set it manually until now.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
98f7a0569c rust/pvsecret: User defined signatures and verifications
Introduces the ability to `pvsecret` to add a signature (ecdsa or rsa)
to the program-reserved space (user-data) of an add-secret request
during the request creation. Additionally, some arbitrary data may be
inserted.

The new command `verify` checks if add-secret requests are sane (e.g.
start with the correct magic value). If the request contains a
user-signature `verify` will also verify this signature.

Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
551f66282e rust/pvsecret: Streamline man and README
Makes the source for the manfiles&README more readable. Fixes some nits
like double spaces, unnessecary indentations, line breaks, ...
Removes pvsecret-version.1 as this command maps to the --version
option.

Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
3d2ba5aaed rust/pvsecret: Sync man and help
The output of --help and man pages got out of sync.
Apply changes to the help output to fix this.

Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
94942a48ab rust/pv: Add function to read a private key
Add a function to read a private key in PEM or DER format.

While at it, fix some documentation issues in read_{certs, crls}.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
ab8984a7a3 rust/pv: Remove unused functions
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Marc Hartmayer
4990f643c1 rust/pv: Add-Secret: Add user-data and user-defined signing keys
User-data can be added to add-secret requests. User-data can be unsigned
or containing a signature of the request using a custom private key.

The user-data is placed at offset 0x218 and 0x200 bytes long.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
34bef977e8 rust/pv: User-data signing and verifying
Add the ability to generate signed user-data and to verify the
signature.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
f36c34038b rust/pv: Add-Secret: Add user-data types
Add four new user-data types an add-secret request could have:
Unsigned, Signed(EcSECP521R1, Rsa(2048, 3072))

As the user-data enum was not marked as non-exaustive this might be a
breaking change for users. (Not for any crate in this repo though).

The addition of such user-data is provided by following patches.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
bfd0e12d22 rust/pv: Signing messages
Adds the ability to sign and verify messages using ECDH or RSA keys.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
1450f85ada rust/pv: Fix documentation issue
Add the missing plaintext flags to the request description.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
2a0f1e6977 pvsecret: improve warning if host key document contains multiple certificates
Improve the warning for the case where a host key document contains
multiple certificates (only possible for a PEM file). In case there are
multiple host key document only the first certificate is used.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
0f433b1142 pvsecret: fix panic if empty file is used as host key document
Fix a panic in `pvsecret` when a empty file is used as a host key
document.

$ touch hkd
$ pvsecret create --no-verify -k hkd --output req.bin --hdr sehdr
...
The host key document in 'bla' contains more than one certificate!
thread 'main' panicked at pvsecret/src/cmd/create.rs:192:31:

Fixes: dd82c26f87 ("rust: Add tool to manage UV-secrets")
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
ab6bcad263 pv/error.rs: fix typo
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
e40a3e0621 pvapconfig/tests: use a temporary directory for tests
Use the `TemporaryDirectory` type to create a temporary directory. This
type has the advantage that the directory is automatically removed once
it goes out of scope. The old implementation in test_sysfs_write_i32
leaks a directory if a previous subtest failed. Also, using a temporary
directory for the LockFile test fixes the following error:

  $ RUST_BACKTRACE=1 cargo test -- helper::tests::test_lockfile
  ...
  running 1 test
  test helper::tests::test_lockfile ... FAILED

  failures:

  ---- helper::tests::test_lockfile stdout ----
  thread 'helper::tests::test_lockfile' panicked at pvapconfig/src/helper.rs:265:9:
  assertion failed: r1.is_ok()
  stack backtrace:
     0: rust_begin_unwind
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/panicking.rs:597:5
     1: core::panicking::panic_fmt
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/panicking.rs:72:14
     2: core::panicking::panic
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/panicking.rs:127:5
     3: pvapconfig::helper::tests::test_lockfile
	       at ./src/helper.rs:265:9
     4: pvapconfig::helper::tests::test_lockfile::{{closure}}
	       at ./src/helper.rs:263:24
     5: core::ops::function::FnOnce::call_once
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/ops/function.rs:250:5
     6: core::ops::function::FnOnce::call_once
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/ops/function.rs:250:5
  note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-By: Harald Freudenberger <freude@de.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
e56acf4f14 pv_core: add TemporaryDirectory
Add the type `TemporaryDirectory` that creates a temporary directory
that is automatically removed when it goes out of scope.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-By: Harald Freudenberger <freude@de.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Finn Callies
17977eda30 cpacfstats: Fix typo PCMKO to PCKMO
This commit fixes a typo in the cpacfstats.c client application and its
respective manpage cpacfstats.1.

Signed-off-by: Finn Callies <fcallies@linux.ibm.com>
Reviewed-by: Harald Freudenberger <freude@linux.ibm.com>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
459a257568 rust/pvapconfig: Fix typos and clippy findings
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
fb65b53b9b rust/pvapconfig: use pv_base instead of pv crate
Replace the pv crate with the pv_core crate.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
c8d4062f73 rust/pv: bump up crate version to 1.0.0
Recent changes makes it reasonable to update the version string of pv
and pv_base to 1.0.0. Recent changes introduced some non-backwards
compatible changes, like move some Error definition from pv to the new
pv_core crate. Also, the pv crates seem to be in a good shape to be
considered released.

Therefore, move up the Semver to 1.0.0.
See also:
https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html

NOTE: As these crates are not published to `crates.io` as of now, the
version number is meaningless, as cargo will not pull them from
anywhere and just use the files provided by this repository.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
c8e0992814 rust/Cargo.lock: Update zerocopy crate
Update the zerocopy dependency from v0.6.* to the non forward compatible
v0.7.*. Incompatible changes are the requirment of the FromZeroes trait
for FromBytes trait and the non-default derive feature which this
patches handles as well.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
c0a12b29d0 rust/pv: Reorganize some tests
Use a static Mockito server instead of creating multiple on-the-fly
during testing. Add the `mockito_server_available` test to quickly
verify that the mockito server could be constructed.  Relocate the
verifier API test to the test folder as it tests the public API. Also,
make the Debug impl for CertVerifier public. The test relocation
requires this. Before, there could be a test local implementation of the
Debug impl as it was in the same crate.

While at it, get rid of the lazy_static crate in favor of the once_cell
crate. As the new std lib implementation is very near to the one from
once cell. The project will switch to the std impl when v1.70 is
available in all supported distros. See:
https://doc.rust-lang.org/std/sync/struct.OnceLock.html

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
02dded11a5 rust/pv: Replace libc wherever possible
Replace all libc references to rust-std references if available.
This eliminates the need to include libc in the pv crate.
However, pv_base still refers to libc::ioctl and libc::ENOTTY.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
b71279cda5 rust/pv: Remove memeq function
The rust std lib already provides functionality to compare two slies.
Replace all `memeq` invocations with == and remove the `memeq` function.
As a side effect this eliminates some unsafe code in this crate.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
c70477f8c6 rust/pv: Fix 'elided_lifetimes_in_associated_constant' warning
Fixes "warning: `&` without an explicit lifetime name cannot be used
here". This warning will become a hard error in "the future". For more
information, see issue #115010 <https://github.com/rust-lang/rust/issues/115010>

Closes: https://github.com/ibm-s390-linux/s390-tools/issues/162
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
9b51b8b882 rust/pv: Refactor pv crate
Big refactoring patch of the pv crate. The main reason behind this
refactoring is to simplify testing and maintaining the pv crate while
keeping OpenSSL/libcurl dependencies optional. Using crate features
increases the number of targets that have to be tested. This refactoring
eliminates the use of features by splitting the functionality of pv into
a use OpenSSL and no-use-OpenSSL crate.

Split off some code from the pv crate into a pv_core crate. pv requires
pv_core and reexports all symbols. pv_base contains all code from former
pv that does not use OpenSSL or libcurl functionalities. The refactored
pv crate contains functionalities to generate requests and validate host
key documents. All features from pv are dropped as they are not needed
anymore and to streamline the codebase for easier use and testing. While
at it fix some documentation issues.

Users (pvsecret & pvapconfig) have next to no code change, besides the
different import of the crate.

Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
48539596ef rust/pv: Use constants for invalid secret types
Add local constants for the reserved, and Null secret type and use
them.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
cafa99774c rust/pv: Replace file-macros with functions
A function does the job as well. This improves code readability and
useability. While at it remove the implicit Buffer wrapper. Users are
currently not use the benefits of a buffered write. Also, streamline the
write_out helper function.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Tobias Huschle
b5f7ac95d8 cpuplugd: adjust to CPU 0 being no longer hotpluggable
With kernel commit 496bb034f4ff ("s390/smp: disallow CPU hotplug
of CPU 0") it is no longer possible to hotplug CPU 0.

As a side effect, the sysfs handle /sys/devices/system/cpu/cpu0/online
does no longer exist. Since cpuplugd relies on checking all online
handles of all CPUs, this change causes cpuplugd to fail as it
interprets a non existing online file as an indicator that no other CPUs
with higher IDs exist as well. This leads to cpuplugd assuming that
there are no CPUs available.

Instead of checking for the online file, it is preferable to check for
the existence of the parent folder to verify the existence of a CPU.
As a consequence, all other checks for non-existing online files must
now imply that the CPU is online, but not hotpluggable.

Signed-off-by: Tobias Huschle <huschle@linux.ibm.com>
Tested-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Gerald Schaefer <gerald.schaefer@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Thomas Richter
e984b97db0 s390-tools/cpumf: set exit code on pai data collection error
When pai records data it may fail in select() system call.
This error is not reported and the pai program exits with success.
Change this and exit with proper exit code.

Fixes: d7b1cbad8b ("cpumf/pai: Add Processor Activity Instrumentation tool")
Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Acked-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Thomas Richter
64d4e02b4f cpumf/pai: Handle perf records type PERF_RECORD_SWITCH
Running command pai -r <file> might encounter entries of type
PERF_RECORD_SWITCH. Entries of that type are currently not handled
and cause an error message:

 # pai -r ~/paicrypto.000
 unknown header-type 14 unknown header-type 14 \
 unknown header-type 14 ...

The error message is not terminated by a newline.

Handle entries of this type PERF_RECORD_SWITCH. These records
do not carry any payload at all, just a bit is set in the
header::misc member. This bit set determines context switch out.

Output after:
 # ./pai -r ~/paicrypto.000
 0x4b814018f4f3 6 cs-out
 0x4b817bc3c936 6 cs-in
 0x4b817bc5246c 6 cs-out
 0x4b817bd90e9a 6 cs-in
 ....
 #

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Acked-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
90ddef5a41 pre-commit: Provide pre-commit configuration
Add pre-commit (https://pre-commit.com/) configuration to allow
developers to run certain checks on their commits automatically.

Currently enabled checks and hooks are:
- Generic checks (e.g. merge-conflicts, trailing whitespace, etc.)
- git clang-format
- Codespell
- Shellcheck (https://www.shellcheck.net/)

Note: For some hooks Rust is excluded as a lot of false positives are
produced at the moment.

Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Joern Siglen
58ef99f76b dbginfo.sh: handle relative and absolute calling path
include the dbginfo.sh script used - enabled for relative paths

Reviewed-by: Michael Storzer <mstorzer@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-01-12 14:10:52 +01:00
Joern Siglen
43c34956fb dbginfo.sh: adding new step to man page
step "initrd config files" was added as 19th step to our data collection
updating for newer date and distro version in the sample output

Reviewed-by: Michael Storzer <mstorzer@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-01-12 14:10:52 +01:00
Joern Siglen
d7dee1b9d3 dbginfo.sh: change copyright for 2024
prepare for the next updates

Reviewed-by: Michael Storzer <mstorzer@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-01-12 14:10:52 +01:00
Harald Freudenberger
94a38ebc3a rust/pvapconfig: Introduce new tool pvapconfig
pvapconfig is a new tool for automatically configuring the APQNs
within an Secure Execution KVM guest with AP pass-through support.
Based on a given AP configuration it tries to find a matching
APQN and bind and associate it with the correct secret.

Signed-off-by: Harald Freudenberger <freude@linux.ibm.com>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Holger Dengler <dengler@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-01-12 14:10:52 +01:00
Steffen Eiden
0764460eaf rust/pv: Provide access for SecretList members
Adds getter for SecretList and SecretEntry.
Adds enum to represent secret types.
Add Iterator functionality for SecretList.

While at it, make the datatype of the capacity of the list transparent
for users.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-01-12 14:10:52 +01:00
Steffen Eiden
6fd02279da rust/Makefile: Fix CC/AR variables for TEST_TARGETS
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-01-12 14:10:52 +01:00
Yaakov Selkowitz
6274294bc5 libkmipclient: Fix build with libxml2-2.12.0
https://gitlab.gnome.org/GNOME/libxml2/-/releases/v2.12.0

"Several cyclic dependencies in public header files were fixed. As a
result, certain headers won't include other headers as before."

Closes: https://github.com/ibm-s390-linux/s390-tools/pull/160
Signed-off-by: Yaakov Selkowitz <yselkowi@redhat.com>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-20 18:32:34 +01:00
Steffen Maier
27708026d4 zdev/dracut: state requirement for sed during kdump initrd build
v2.27.0 commit 73c46a3056 ("zdev/dracut: fix kdump by only activating
required devices") started using `sed` during kdump initrd build time.
It works, but explicitly add the tool as a requirement.

Fixes: 73c46a3056 ("zdev/dracut: fix kdump by only activating required devices")
Closes: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
849aa5b105 zdev/dracut: put temporary files under the subdir ${DRACUT_TMPDIR}
This way, dracut cleans up everything just in case the "rm" here fails.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
0be83bfbba dbginfo.sh: collect migration backups & logs from old persistent config
Complements commits
("zdev/dracut: add rd.zfcp cmdline option handling")
("zdev/dracut: add rd.dasd cmdline option handling")
and s390utils commits
("zfcp: migrate to consolidated persistent device config with
zdev (#1937046,#1937048)")
("dasd: migrate to consolidated persistent device config with
zdev (#1937046,#1937048)")
.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Fedor Loshakov <loshakov@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
f8592be43d dbginfo.sh: collect file list and config files from initrd-s
For each initrd, collect verbose content listing as well as
the content of files matching the file glob patterns in $CONFIGFILES.
For dracut-squash such as RHEL kdump, this includes squashfs content.

Code is intentionally compatible with dash, so no bashisms.

Ubuntu initramfs has zdev persistent device configuration udev rules under
/lib/udev/rules.d/ (as opposed to /etc/udev/rules.d/).
Ubuntu initramfs stores configuration under /conf/.
Ubuntu kdump-tools have initrds under /var/lib/kdump/.

SLES auxiliary boot stage with grub2-s390x-emu has initrds under /boot/zipl/.

Dracut initramfs stores configuration under /etc/cmdline.d/ and /etc/conf.d/.
Dracut kdump have initrds under /boot/.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Fedor Loshakov <loshakov@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
647ad51423 dbginfo.sh: collect other config data from zdev in initrd
Complements commit ("zdev/dracut: retain early persistent config over
switch root").

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Fedor Loshakov <loshakov@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
73f51e45a8 zdev: add helper to convert from zdev config to rd.znet
Converts zdev configuration into the syntax of the dracut cmdline option
rd.znet. Only znet options with non-default values are emitted. The result
string occurs on stdout. It represents one device-specification for the
given network interface name.

Example:
/lib/s390-tools/zdev-to-rd.znet persistent encbdf0
qeth,0.0.bdf0,0.0.bdf1,0.0.bdf2,layer2=1

User:
https://github.com/rhinstaller/anaconda/pull/5250
to generate dracut cmdline entries such as
rd.znet=qeth,0.0.bdf0,0.0.bdf1,0.0.bdf2,layer2=1

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
bc4f455151 zdev/dracut: retain early persistent config over switch root
Useful for debugging (what happened during early boot).

Also useful for distro installers, which can import the early config into
their own environment early after starting:
chzdev --import /run/zdev.initrd.config --persistent --yes \
       --no-root-update --force --verbose

After that, distro installers can modify/add the device config based
on interactive or unattended installation choices using
chzdev --enable --active --persistent ...

Finally, distro installers can likewise transfer the entire device config
to the installed system mounted under $SYSROOT:
chzdev --export /tmp/zdev.config --all --type --persistent --verbose

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
b4b5e0b6aa zdev/dracut: produce helpful user information on parsing rd.zfcp
It builds on the previously added helper function in parse-dasd.sh, which
gets sourced first by dracut so the function is also available to the later
parsing hook parse-zfcp.sh.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
9927023680 zdev/dracut: add rd.dasd cmdline option handling
Add parsing of dracut cmdline option "rd.dasd=..." at initrd runtime.
It delegates configuration to chzdev.

Implement `dracut --print-cmdline` and `dracut --hostonly-cmdline` for
initrd build time. Emit an rd.dasd option for each DASD disk in dracut's
device dependency graph (to mount the root-fs, or to access the kdump
target).

This allows a distribution independent device configuration.
Configuration is consistent by using chzdev as backend.
It also prevents duplicate activations of the same device.

Copy the udev rule for unique DASD device nodes under /dev/disk/
[59-dasd.rules] into initrd for the same functionality like 95dasd or
95dasd_rules.

Along with the existing functionality of zdev/dracut, it makes the
following dracut modules superfluous:
https://github.com/dracutdevs/dracut/tree/master/modules.d/95dasd
https://github.com/dracutdevs/dracut/tree/master/modules.d/95dasd_mod
https://github.com/dracutdevs/dracut/tree/master/modules.d/95dasd_rules

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
9b2fb1d4d2 zdev: add helper to convert from dasd_mod.dasd to zdev config
Shell library, which can be sourced by other POSIX compatible shell
scripts. Provide helper function parsing its stdin based on the syntax of
kernel device driver parameter dasd_mod.dasd= and invoking chzdev to
produce corresponding persistent device configurations. The helper function
takes one argument, which is either "globals" or "ranges". For a complete
configuration, call the function twice, first with "globals" and then with
"ranges".

The new script library file should be packaged in a core (sub)package of
s390-tools so the script is available for initrd environments.

Users with examples:

Subsequent commit ("zdev/dracut: add rd.dasd parsing") introduces
zdev/dracut/95zdev/parse-dasd.sh performing:
zdev_parse_rd_dasd | zdev_parse_dasd_list globals 2>&1 | zdev_vinfo
zdev_parse_rd_dasd | zdev_parse_dasd_list ranges 2>&1 | zdev_vinfo

https://github.com/dracutdevs/dracut/pull/2534 updates
modules.d/80cms/cmssetup.sh performing:
echo "$DASD" | zdev_parse_dasd_list globals 2>&1 | vinfo
echo "$DASD" | zdev_parse_dasd_list ranges 2>&1 | vinfo

The parsing code for rd.dasd using the same syntax as dasd_mod.dasd=
is inspired by the own implementation that used to be in linuxrc.s390
of https://github.com/rhinstaller/anaconda.
https://web.archive.org/web/20190721154444/https://www.redhat.com/archives/anaconda-devel-list/2009-February/msg00392.html
https://www.spinics.net/linux/fedora/anaconda-devel/msg08316.html
("Re: Improved linuxrc.s390 (third try)")
=> 9249e40f42ff ("IBM improvements to linuxrc.s390 (#475350)")
https://web.archive.org/web/20190721150254/https://www.redhat.com/archives/anaconda-devel-list/2009-July/msg00310.html
=> 5f0fcf6688d0 ("Update linuxrc.s390 and friends to reflect review comments.")
https://web.archive.org/web/20190721125255/https://www.redhat.com/archives/anaconda-devel-list/2009-August/msg00158.html
=> 523095c86876 ("Handle activation of DASDs in linuxrc.s390 since loader no longer works")

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
689b894506 zdev: add helper to convert from zdev config to dasd_mod.dasd
Converts zdev configuration into the syntax of the kernel module parameter
dasd_mod.dasd. Only DASD options with non-default values are emitted. The
result string occurs on stdout. It represents one device-specification for
the given DASD device bus-ID.

Example:
/lib/s390-tools/zdev-to-dasd_mod.dasd persistent 0.0.da5d
0.0.da5d(erplog)

User:
https://github.com/storaged-project/blivet/pull/1162
to generate dracut cmdline entries such as rd.dasd=0.0.da5d(erplog)

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
06a30ae529 zdev/dracut: add rd.zfcp cmdline option handling
Add parsing of dracut cmdline option "rd.zfcp=..." at initrd runtime.
It delegates configuration to chzdev.

Implement `dracut --print-cmdline` and `dracut --hostonly-cmdline` for
initrd build time. Emit an rd.zfcp option for each zfcp-attached SCSI disk
in dracut's device dependency graph (to mount the root-fs, or to access the
kdump target).

This allows a distribution independent device configuration.
Configuration is consistent by using chzdev as backend.
It also prevents duplicate activations of the same device.

Along with the existing functionality of zdev/dracut, it makes the
following dracut modules superfluous:
https://github.com/dracutdevs/dracut/tree/master/modules.d/95zfcp
[rd.zfcp.conf is no longer needed and thus ignored here; the preceding
 ("zdev/dracut: fix marking hostonly files so delete option works")
 makes rd.hostonly=0 work as a generic replacement]
https://github.com/dracutdevs/dracut/tree/master/modules.d/95zfcp_rules

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
ed106d7f28 zdev/dracut: add site support for early devices to the kdump case
The existing code can already determine the required devices for the
actively running system, but this does not work for execution environments
in different (disaster recovery) sites.

Kdump likely does not notice when running in a different site and does not
re-generate the kdump initrd (because the kdump config itself does not
change).

The new code allows users to explicitly specify devices required for early
boot in initrd on different sites. Assuming this is a small number of
devices and could even overlap between the root-fs and the kdump case, do
not further distinguish those two sets of devices, but simply use the same
marker attribute "zdev:early=1" for both cases. With all this site
information available at the (initial) kdump initrd generation, the
resulting kdump initrd can work on any site without having to re-generate.

Complements commit 3c7adcc3c81d ("zdev: dracut: modifiy the
module-setup.sh").

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
9d08fd8c7e zdev/dracut: import persistent config of early devices into kdump initrd
The existing code can already determine the required devices for the
actively running system. In case users would have a need to configure
additional devices for early boot in kdump initrd, import those devices
marked with "zdev:early=1" for completeness. Assuming this is a small
number of devices and could even overlap between the root-fs and the kdump
case, do not further distinguish those two sets of devices.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
7e8126704b zdev/dracut: fix to not depend on existing chzdev persistent config
Dracut knows exactly what dependencies it needs. Do not break it by
accidentally missing parts in the chzdev persistent configuration
(/etc/udev/rules.d/41-*.rules). Depicted as mathematical sets:

+-------------------------------+
| active                        |
|            +---------------------------------+
|            |                  |   persistent |
|   +------------------------------------+     |
|   |      root / kdump dependencies     |     |
|   | was    |                  |        |     |
|   | missing|   zdev so far    |   DR   |     |
|   +------------------------------------+     |
|            |                  |              |
+-------------------------------+              |
             |                                 |
             +---------------------------------+

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
d96767ee45 zdev/dracut: fix marking hostonly files so delete option works
Chzdev import generates persistent device configuration which are hostonly
files. Dracut cmdline rd.hostonly=0 should be able to delete those.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
63f31bf73e zdev/dracut: add field debugging capabilities with logging
Additional debugging output can be generated with e.g. dracut option
"--stdlog 5" (or short -L5). It shows the chzdev export result, the output
of chzdev imports, and an overview of the resulting persistent config
within the initrd.
On systems, which default to using dracut option "--quiet", you might need
an additional "--verbose" to counter "--quiet" so -L5 has effect.
Typically combined with "--debug" to get a shell trace from building an
initrd (Note: --debug does not increase the log levels).

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
7ecfe2353f zdev/dracut: respect hostonly mode choice
Otherwise it risks adding arbitrary persistent configuration of the build
host into generic dracut initrds such as those for distro installers.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
7bec672c7e zdev/dracut: fix kernel module install even for hostonly mode
Otherwise, dracut skips modules that are "not hostonly", i.e. not used /
loaded on the system when running dracut to build an initrd.
Without the fix, zdev device auto configuration only works for device
types for which a device driver happened to be loaded when building an
initrd. Likewise for specifying rd.* boot options.

Hostonly mode is often the default in Linux distributions.
Make zdev device auto configuration work nonetheless without users
having to know about and tweak dracut modes for initrd generation.

On the typical sloppy hostonly mode, the code disables hostonly for
installing kernel modules. This covers building regular initrds, where zdev
device auto configuration should even work for device types, for which
device drivers were not loaded when an initrd was generated. This can
happen when new devices of new types are configured for a DPM logical
partition. Also, users could want to start using dracut cmdline options
rd.{dasd,zfcp,znet} for a device type that was not used when the initrd was
built.

The special strict hostonly mode is used by some kdump implementations. In
that case, hostonly remains in effect intentionally because only support
for the really required devices as determined by dracut module 95zdev-kdump
should be included in a kdump initrd due to the memory-constrained kdump
environment. Cf. commit 73c46a3056 ("zdev/dracut: fix kdump by only
activating required devices"), which also provides more references on
strict hostonly mode. Even for non-kdump cases, let strict hostonly mode be
effective here in 95zdev in case some future use case appears for this
special mode beyond kdump.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
5aac5deb75 zdev/dracut/module-setup.sh: add comments for dracut called functions
Add a short explaining comment to those functions, which are just callbacks
invoked by the dracut core infrastructure. So nobody wonders how these
functions get called, especially if there are no local callers here in the
code.

Cf. dracut commit
https://github.com/dracutdevs/dracut/commit/8bcfd683bd0c
("*/module-setup.sh: add comments for dracut called functions")

For more information about the dracut module callback functions:
https://github.com/dracutdevs/dracut/blob/master/man/dracut.modules.7.asc#writing-a-module
https://github.com/dracutdevs/dracut/blob/master/docs/HACKING.md#writing-modules

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
54e016ae71 zdev/dracut: use namespace prefix in functions of zdev-lib.sh
Use namespace prefix in functions of zdev-lib.sh to avoid collisions with
different modules being sourced in the shell.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
6b53378839 zdev/dracut/95zdev: non-functional changes and annotations for shellcheck
Clears all shellcheck reports in old existing code so newly added code can
use shellcheck to find newly introduced suboptimal shell code.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
1266f86444 zdev/dracut: add shellcheck annotations for recently added code
The annotations and the one non-functional code change in
zdev/dracut/95zdev-kdump/module-setup.sh check() clear all shellcheck
reports.

complements
73c46a3056 ("zdev/dracut: fix kdump by only activating required devices")

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
231c02cdeb zdev: fix typo for example in chzdev man page
Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Marc Hartmayer
454a8d9d7b systemd/cpi.service: add missing Requires=sysinit.target
`After=sysinit.target` configures only the order but not that the
systemd unit is required and must be activated. Therefore add the
missing `Requires=sysinit.target`. It probably does not fix anything
because sysinit.target should always be activated anyway but it's
cleaner to have it. See
https://www.freedesktop.org/software/systemd/man/systemd.unit.html for
details.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Marc Hartmayer
bbe92b9cd3 systemd/cpi.service: fix race condition between stopping unit and shutdown
There is a race condition between stopping the cpi systemd service and
systemd getting a request to exit the main loop, so it may proceed with
shutdown before all pending stop jobs have been processed. Therefore,
add `Before=shutdown.target` as it would have been added by
the (deactivated) default dependencies. [1]

While at it, sort the directives.

[1] https://www.freedesktop.org/software/systemd/man/systemd.service.html#Default%20Dependencies

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Jan Höppner
7bb41732fb Prepare for next release
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-01 15:03:39 +01:00
Jan Höppner
c217f6be6a New release s390-tools-2.30.0
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-01 15:03:39 +01:00
Steffen Eiden
21662d38e6 rust/pv: Update mockito to version 1
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Niklas Schnelle
19f3842292 libutil: fix util_file_read_*() using wrong format specifiers
The sscanf() format specifiers for signed and unsigned int mistakenly
used "%d"/"%u" prefix analogous to "%l" for long but those do not exist.

Fixes: 37348ef662 ("libutil: add util_file_read_i()/util_file_read_ui()")
Acked-by: Sven Schnelle <svens@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Steffen Eiden
ae0cbf00b1 rust: Use default panic behaviour
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Steffen Eiden
9019c6864a rust: Sanitize minimal dependencies
The crate dependencies were a bit to slack. Due to the rust dependency
resolver's strategy of always selecting the latest version this never
lead to any issues.

This has no impact on the workspaces Cargo.lock

Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Steffen Eiden
d1b61c37fa rust: Update dependency files
With the last patch introducing the rust workspace the location of
Cargo.lock has changed. Therefore, remove all crate level lock-files and
add rust/Cargo.lock as the only lock-file.

Steps to reproduce:
```
cd rust
mv pvsecret/Cargo.lock .
cargo build
cargo update -p openssl
cargo update -p curl-sys
cargo update -p rustix

```

While at it update some dependencies to get fixes for security issues.

Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Steffen Eiden
32b68a5fad rust: Create workspace
A workspaces simplifies the build and packaging process significantly.
All build artifacts and binaries are now built in a single location
(e.g., rust/target/release/*), and a unified dependency resolution is
used. Hence one Cargo.lock for all crates at rust/Cargo.lock.

Closes: https://github.com/ibm-s390-linux/s390-tools/issues/156
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Matthew Rosato
55fdb17b18 ap_tools/ap-check: handle get-attributes between pre and post event
Since mdevctl commit acf78c1ff6c9 it is now possible for the
get-attributes event to occur between a pre-define and post-define.
This is done in order to obtain the active attributes for the device
before writing them to the config file, and implies that the
get-attributes cannot re-obtain the file lock.  For other cases
where mdevctl calls get-attributes, the file lock is not already
held and must be obtained by ap-check before reading attributes from
active devices.
To solve this, let's use the knowledge that mdevctl is a single-threaded
tool and add a test to detect this scenario.  If the file lock is
already held by the parent during a get-attributes, don't attempt to
re-acquire it.

Reported-by: Boris Fiuczynski <fiuczy@linux.ibm.com>
Reviewed-by: Boris Fiuczynski <fiuczy@linux.ibm.com>
Signed-off-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Matthew Rosato
af730c79a6 libutil/util_lockfile: add routine to return owning pid of file lock
Provide a mechanism via which a caller can query the pid of the process
currently holding the file lock.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Boris Fiuczynski <fiuczy@linux.ibm.com>
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Marc Hartmayer
041e6131d1 genprotimg/boot: stage3b: check cmdline for null-termination
Add a check to the stage3b that the kernel cmdline is always
null-terminated. While at it, ensure the coding style is consistent.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Marc Hartmayer
5a7d7e05b8 genprotimg: make sure the kernel command line is always null-terminated
Make sure that the kernel command line used for the Secure Execution
boot image is always null-terminated. Before this change, users had to
ensure that the provided kernel cmdline was null-terminated, which was
error-prone. But since the default s390x Linux kernel command line is
set to `root=/dev/ram0 ro` the remaining reserved memory for the kernel
command line is zeroed out. Therefore, the problem only shows up if the
used kernel command line is shorter than the default kernel command
line.

Fixes: 65b9fc442c ("genprotimg: introduce new tool for the creation of PV images")
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:48 +01:00
Steffen Eiden
71b93d55ef rust/pv: fix Invalid write of size 1
Fix a valgrind finding. Fix an invalid read/write of one byte after the
actual struct to clear. Not fixing this may result in a illegal write or
memory corruption of the program. Fortunately, for the actual only user,
pvsecret this is not the case.

Fixes: c6f621d0 ("rust: Add library for pv tools")
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 10:24:40 +01:00
Thomas Richter
d2b5e1e2d6 cpumf/pai: Add command line option for realtime scheduling
Pai collects data from per CPU ring buffers and stores them in the
memory mapped output file. When data is collected from many CPUs at
the same time, writing data to output file can be slow.
Improve this and allow the pai recording to run with higher
real time priority. This is the same approach as done by the perf tool.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Acked-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-12-01 09:58:47 +01:00
Mikhail Zaslonko
a3cb877c54 README: Add info about bundled zlib
Update Dependencies section for zipl with the information of zlib
compression support for CCW-type standalone dump.

Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/157
Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Joern Siglen
6895a71cc4 dbginfo.sh: remove brakets on lsqeth device list
in customer situation we found lsqeth listing devices like:
Device name                      : (unnamed net_device)
Device name                      : enc2000
Device name                      : enc3000

- the braket around the "unnamed" device is braking the function call and
leads to stop the dbginfo.sh script.
- this patch removes brakets > the functions call works and call of osaoat
will report an unknown device instead of braking the dbginfo.sh script

Reviewed-by: Mario Held <mario.held@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Joern Siglen
d9034b01f1 dbginfo.sh: enhance ethtool collection for ROCE
collect module-info for new ROCE cards via ethtool

Suggested-by: Niklas Schnelle <schnelle@linux.ibm.com>
Reviewed-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Joern Siglen
488ac8c3f2 dbginfo.sh: fix shellcheck errors in double quoting
change use of double quote to fit the rules

Reviewed-by: Mario Held <mario.held@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Jan Höppner
ecf36d53c8 dasdfmt: Update -k/--keep_volser description
Make the description of the --keep_volser option more generic and avoid
mentioning specific tooling.

Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Jan Höppner
893ad920c5 dasdfmt: Fix trailing whitespace in man page
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Jan Höppner
0695c79f4e fdasd: Improve -k/--keep_volser description slightly
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Jan Höppner
8837ea24cb fdasd: Fix trailing whitespace in man page
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Marc Hartmayer
65222d03b9 zipl/boot: compile the bootloaders only if HOST_ARCH is s390x
The zipl bootloaders are s390x specific, so only build them if the
`HOST_ARCH' is set to s390x.

While at it, rename `INC_FILES` to `EMBEDDED_BOOTLOADERS`. Also
introduce `EXTERNAL_BOOTLOADERS` variable and use it in the `install`
Makefile target.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Ingo Franzki
54937495e2 zkey: Also check for deconfigured and check-stopped cards
When checking if a card or an APQN is online, not only check the 'online'
sysfs attribute, but also check the 'config' and 'chkstop' attribute.
Cards and APQNs in check-stopped or deconfigured state can still be reported
as online via the sysfs attribute, although they are not available to be
used for zkey.

In case the 2 additional sysfs attributes are not available in sysfs, then
don't fail, but rely on the 'online' attribute only.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Harald Freudenberger <freude@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Marc Hartmayer
8a783b81a4 Provide a ShellCheck configuration
This patch adds a ShellCheck configuration for s390-tools. See
https://www.shellcheck.net/wiki/Directive for details.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Marc Hartmayer
093da2a5a7 zipl: move responsibility for the stage3.bin installation to boot/Makefile
Move the code responsible for installing stage3.bin to the
boot/Makefile. In addition, remove the stage3.bin from the Makefile
`all` target prerequisites in src/Makefile, as zipl can be built without
it. While at it, use $(INSTALL) instead of $(CP) for the bootloader
installation.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Ingo Franzki
b68ea5fc7d zkey: Fix typos in man page
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Eric Farman
90c587408f cpictl: Limit kernel sublevel to one byte
Linux stable kernels can reach greater than 256 sublevels [1],
which can cause the cpi tooling to generate an invalid string
that gets passed to the firmware and causes unusual responses:

  $ uname -r
  5.4.255
  $ cat /sys/firmware/cpi/system_level
  0x04260000000504ff

  --reboot--

  $ uname -r
  5.4.256
  $ cat /sys/firmware/cpi/system_level
  0x4260000000504100

The first sublevel field is defined as one byte, so ensure that
a value larger than that isn't included.

[1] https://lore.kernel.org/lkml/1612534196241236@kroah.com/

Signed-off-by: Eric Farman <farman@linux.ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Thorsten Winkler
90475fbaa5 common.mak: use eval only once for defining variables
Using eval only once at the top most function "define_toolchain_variables",
makes the other subsequent eval calls redundant.

“The result of the eval function is always the empty string; thus, it
can be placed virtually anywhere in a makefile without causing syntax
errors.” [1]

So this patch also prevents potential syntax errors using GNU Make <v4.2.
Since version 4.2 GNU Make introduced a less errorness function calling and
variable expanding with commit
e971597 ("[SV 46995] Strip leading/trailing space from variable names")

Reference: https://git.savannah.gnu.org/cgit/make.git/commit/?h=4.2&id=e97159745d3359285cef535af780cd8e2b6b0791

[1] https://www.gnu.org/software/make/manual/html_node/Eval-Function.html

Signed-off-by: Thorsten Winkler <twinkler@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Vineeth Vijayan
07ff9e1da0 zdev: limit the derivation of ZDEV_SITE_ID
Currently ZDEV_SITE_ID is derived with the help of an additional
udev-rule, 40-zdev-id.rules. The sole purpose of this rule is to
determine the ZDEV_SITE_ID environment value with the help of zdev_id
binary. This solution is minimal, but this has some unwanted side-
effects. The zdev_id logic get executed for all the events, even
those completely unrelated to zdev/or site, and imports the unneeded
envionment values to the udev-db.

Instead of having an additional rule file, add this logic as part of
the udev-rule of those devices which are configured with site-support.
The logic will then be available on all those rules with the
site-supported devices only.

Signed-off-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Vineeth Vijayan
5637799c92 zdev: introduce dev_site_configured macro
Introduce dev_site_configured macro,which can be used to find the
availability of site configurations for the device during udev rule
creation.

Signed-off-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Vineeth Vijayan
73c82441e7 zdev: move all site-related definitions to one file
Previously SITE_FALLBACK and other site-specific configuration support
macros were defined in device.h. Instead, move them to a relatively
smaller header file which is exclusive for site-related definitions.
This way, light-weight zdev_id also can use the same header file.

Reported-by: Steffen Maier<maier@linux.ibm.com>
Signed-off-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Alexandra Winter
6d06921276 zdev:qeth: adapt performance_stats attribute semantics
Behaviour of the qeth performance_stats sysfs attribute has changed
with kernel commit
b0abc4f5df76 ("s390/qeth: overhaul ethtool statistics")
that went into kernel v5.1.

Before the kernel commit
- collection of statistics was turned on and off by writing 1 or 0
- default after device activation was 0
- statistics were reset by writing 0

After the kernel commit:
- collection is always on
- attribute always reads 1
- statistics is reset by writing 1; writing 0 is a no-op

Problems of chzdev on new kernels:
chzdev cannot reset statistics ('performance_stats=1' does nothing).
'chzdev --export' always lists performance_stats.
'chzdev qeth --help-attribute performance_stats' reflects old behaviour.

This patch will do the following:
'chzdev qeth --help-attribute performance_stats' reflects new behaviour.
'chzdev --export' does not list performance_stats on new kernels.
'chzdev performance_stats=1' resets statistics on new kernels.
'chzdev performance_stats=0' still resets statistics on old kernels,
    does nothing on new kernels.

Suggested-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Alexandra Winter <wintera@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-11-07 14:13:38 +01:00
Steffen Maier
2996b34ddf dbginfo.sh: collect debug data for zdev site support
Complements v2.24.0 commit c8ad5f57d0 ("zdev: modify zdev_id to read the
site_id from loadparm") and commit 2e89722ef0 ("zdev: make site specific
udev-rule for ccw").

Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:07 +02:00
Steffen Maier
e5821301f6 dbginfo.sh: exclude (empty) cpu subdirs under zfcp sdev block mq sysfs
With many CPUs, such as triple digit counts, the by default many empty
sysfs directories are prohibitive to collect, especially if the number
of SCSI disk devices is also large, such as 4-digit counts.

Excluding them from being collected from sysfs saves hundreds times
thousands of archive entries and inodes on expansion.

Since the number device-mapper devices (multipath and other target types
such as LVM) is smaller and can include devices not backed by zfcp, keep
collecting
/sys/devices/virtual/block/dm-[0-9]*/mq/0/cpu[0-9]*/

Definitely keep collecting
/sys/kernel/debug/block/{sd,dm-}*/hctx0/cpu[0-9]*/
as it contains actual statistics files:
completed  default_rq_list  dispatched  merged  poll_rq_list  read_rq_list

Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:07 +02:00
Steffen Maier
f4d1874ac5 dbginfo.sh: collect text export of udev data base
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:07 +02:00
Steffen Maier
f3428929a2 dbginfo.sh: collect potential kdump config under subdir /etc/kdump
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:07 +02:00
Mikhail Zaslonko
263d6950a1 zipl/boot/zlib: Replace static_assert() in zlib code
Replace static_assert() with STATIC_ASSERT macro from zt_common.h in order
to get rid of glibc dependencies in zipl/boot and comply with older
C standards.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Suggested-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:07 +02:00
Marc Hartmayer
8024f8e31a editorconfig: add settings for EDIT_DESCRIPTION
Set the maximum line length for branch description messages (`git branch
--edit-description`) to 72 characters.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:07 +02:00
Marc Hartmayer
3849b29594 rust/**/*.rs: fix cargo clippy findings
Automatically fixed by the command `cargo clippy --fix` and `cargo fmt`.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:07 +02:00
Mikhail Zaslonko
0e4d4da0e5 zdump: Update zgetdump man page
Update zgetdump man page with the information of compressed DASD dumps
support as well as new verbose 'zgetdump -i' output entries.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:07 +02:00
Mikhail Zaslonko
ca3cd51f91 zdump/dt_s390: Support new dumper version by 'zgetdump -d'
Add new dumper version support to 'zgetdump -d' command.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
fda1e0d33d zdump: Move and rename DF_S390_DUMPER_SIZE constants
Move DF_S390_DUMPER_SIZE_* constants from zdump/df_s390.h to
boot/loaders_layout.h since ccw dumper size depends on the zipl boot
loader layout (to keep it all in one place).

Rename DF_S390_DUMPER_SIZE_* constants to STAGE2_DUMPER_SIZE_*

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
1a850392bc zdump/df_s390: Update 'zgetdump -i' output with zlib info
Update verbose 'zgetdump -i' output with zlib info (internal zlib version
and zlib compression unit size).

The following new entriees are to be dispalyed:
	Zlib version.......: 1
	Zlib compression unit: 1 MB

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
14a79eb142 zdump: Increase output buffer from 8 pages to 1 Mb
Increase the auxiliary buffer size from 8 pages to 1 megabyte in order to
significantly increase compressed dump processing speed.
For uncompressed dumps, the effect is minor.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
271b809495 zdump/dfi_s390: Support reading compressed s390_ext dumps
Update dfi_s390.c to support reading of compressed dump segments.
For this, introduce a callback function for reading memory chunks
associated with compressed dump segments. Apart from the segment location
on disk this function requires the entry_offset array from the dump segment
header in order to process each compressed entry separately, thus allowing
fast seek processing for zgetdump (no need to decompress a big dump segment
to extract a single piece of data).

In addition, split mem_chunks_add_ext() in several functions.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
2363269c1c zipl/boot: Set the new version in the dumper and in the dump header
Since we are using the existing s390 extended dump format for compressed
dumps as well, set the version of the s390_ext dumper with compression
support and also dump header of the compressed dump to '2' (in order for
zgetdump to distinguish).

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Acked-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
f1db473d11 zipl/boot: Fix progress_print to correctly display 'Dump file size'
- Adjust progress_print() calls to pass updated address after the set of
  blocks has been written to disk.
- Currently total_dump_size value is updated only after the entire dump
  segment is written to disk what leads to ambiguos Dump file size values
  displayed by progress_print(). Change write_addr_range() to re-calculate
  total_dump_size after each set of blocks has been written to disk thus
  printing the correct value at the end of each log entry.
- Avoid final log entry duplication.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Acked-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
e35d05a5e3 zipl/boot: Add print_progress parameter to write_addr_range()
With current implementation, printing progress while writing a compressed
data chunk might be very inaccurate. Thus, for compressed dump segments
skip progress_print() in write_addr_range() and call it after each
compressed memory chunk is written to disk. For that change
write_addr_range() to call progress_print() conditionally based on the new
print_progress parameter.

For non-compressed dump segments, call progress_print() from
write_addr_range() just as before.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Acked-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
c61783546b zipl: Add --no-compress option to zipl command
Add --no-compress option to explicitly omit compression for single-volume
DASD dumper. Used primarily for test purposes.

Since only the lowest byte of mvdump_force field (struct
stage2dump_parm_tail) has been used, split it in two byte fields and use
one for the new no_compress attribute.

Update zipl help and zipl man page with the new parameter info.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
c08794bdfb zipl/src: Pass stage2dump_parm_tail struct to install_dump_ functions
Move struct stage2dump_parm_tail from stage2dump.h in to
include/dump/s390_dump.h

Pass the entire stage2dump_parm_tail structure to install_dump_* functions
instead of individual parameters.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
4905975f81 zipl/boot: Adjust Makefile, loaders layout and a linker script
Use a separate linker script eckd2dump_sv.lds for single volume dumper with
compression support.

The new dump tool with zlib compression support now has a size of 8 pages.
Since DASD stand alone dump requires a block size of 4K, we are not
affected by the stage 2 size limitations and can load the dumper to
stage 2 as before. We just need to move the HEAP section for ECKD dumper
in the layout definitions up to 0xb000 address. Also expand the stack by
unused 0x400 bytes.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
d53bfb9201 zipl/boot: Integrate zlib compression to single volume DASD dumper
Integrate zlib DFLTCC deflate compression to single volume dasd dumper
using the existing s390 extended dump format. Compression takes place
only if DFLTCC facility is available, otherwise dump is written
uncompressed as before.

First megabyte of memory is always written uncompressed and afterwards
this area is used for zlib workspace and for the compression output buffer.
The compression takes place in chunks of data of equal size (currently 1MB)
and the offset of each compressed chunk is stored in the dump segment
header. Since existing dump segment headers of 1 page size are used, we
need to limit the maximum size of compressed dump segments.
Chunk is written uncompressed in case of compression error or if
deflate compression only makes it bigger.

Thus every chunk of data is compressed separately and can be decompressed
independently. The main reason for that is to enable zgetdump to make fast
read seeks. Otherwise, zgetdump would need to decompress a big dump segment
in the worst case to extract a single piece of data.

Put compression related functions and structures to eckd2dump_zlib.c
and eckd2dump_zlib.h

Update zipl man page with the general information of zlib compression
support.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
0dac47cb62 zipl/boot: Introduce write_addr_range() helper function
Move code from write_dump_segment() to write_addr_range() function to use
it later for writing compressed dump segments as well.

Verify that passed address range is a multiple of dasd block size.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
7b68552359 zdump: Use global header s390_dump.h
Instead of using its own DF_S390_ constants and df_s390_ structs
in df_s390.h, include those from "dump/s390_dump.h" in order to minimize
duplicates. Adjust the code, where required, to use <stdint> types
instead of those defined in zt_common.h (e.g. use uint64_t instead of u64).

Adjust zdump include statements.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
775495c7e7 include/dump: Create a global header s390_dump.h
Move common dump related structures and constants to the global header
"dump/s390_dump.h" in order to get rid of many duplicates in zgetdump code.

Adjust zipl include statements and update Copyright statements.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
1057f13cdc zipl/zlib: Adjust zlib parts for zipl needs
Mainly zlib code remains unchanged for the sake of further maintenance.
Only minor adjustments of zlib deflate parts for build purposes:
- Make is_dfltcc_enabled() always return true
- Define CONFIG_ZLIB_DFLTCC in zlib.h to build zlib code with DFLTCC support
- Remove inflate related prototypes from zlib.h
- Adjust oesc_msg() to use snprintf from libc.h
- Remove BUG_ON from zlib_deflate_workspacesize()
- Replace bitrev32() with bi_reverse() from defutil.h
- Include <assert.h> to dfltcc.h header because of static_assert() calls
- Fix other include statements
- Adjust the text in zipl.h following Zlib License requirements

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Mikhail Zaslonko
ce59a299cb zipl/zlib: Copy required zlib_deflate parts
Add required zlib_deflate parts based on kernel zlib code in preparation
to DASD dumper DFLTCC deflate exploitation. Omit inflate modules in
order to minimize the dumper size (no decompression is required for the
dumping).

Adjust include statements leaving other code as is.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Harald Freudenberger
8235e025d4 zcrypt/lszcrypt: Improve lszcrypt output on SE guests
The AP queue states within a SE guest may have a so called asynchronous
error pending. When that's the case, the sysfs read of some AP queue
attributes fails with EIO. lszcrypt was not really prepared for this
and instead showed some incorrect output.

This patch fixes this oddity and now lszcrypt -c shows "error" in case
of ap_bound or ap_associate read errors and lszcrypt -V shows also
"error" if the BS bits could not get fetched.

Signed-off-by: Harald Freudenberger <freude@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Holger Dengler <dengler@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Marc Hartmayer
b301381f90 (genprotimg|zipl)/boot: remove executable bit
The bootloader binaries cannot be executed via `exec()` therefore remove
the executable bit.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Jakub Čajka
c62f930634 osasnmpd: Fix missing semicolon
5.9.4 net-snmp started to require semicolon on the config_require there
are no docs covering this change.

Link: https://bugzilla.redhat.com/show_bug.cgi?id=2235734
Closes: https://github.com/ibm-s390-linux/s390-tools/pull/155
Signed-off-by: Jakub Čajka <jcajka@redhat.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Marc Hartmayer
85eb44ac95 lib(ekmfweb|kmipclient): use pkg-config instead of (curl|xml2)-config
`pkg-config` is mandatory for compiling s390-tools anyway therefore
let's replace `curl-config` and `xml2-config` calls whenever possible.
In addition, `pkg-config` has the advantage that cross-compilation is
supported. While at it, use `pkg-config` for libcrypto, json-c, and
libssl as well.

Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Steffen Eiden
d5f8063900 rust/README.md: Fix some typos
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Steffen Eiden
ee66929465 rust/Makefile: Fix use of Cargoflags for 'make clean'
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00
Ingo Franzki
1b044b8a40 zkey: Support EP11 AES keys with prepended header to retain EP11 session
The pkey kernel module supports two key blob formats for EP11 AES keys.
The first one (PKEY_TYPE_EP11) contains a 16 bytes header that overlays
the first 32 bytes of the key blob which usually contain the ID of the
EP11 session to which the key is bound. For zkey/dm-crypt that session
ID used to be all zeros. The second blob format (PKEY_TYPE_EP11_AES)
prepends the 16 bytes header to the blob, an thus does not overlay the
blob. This format can be used for key blobs that are session-bound, i.e.
have a non-zero session ID in the first 32 bytes.

Change zkey to generate EP11 keys using the new format (i.e. pkey type
PKEY_TYPE_EP11_AES), but existing key blobs using the old format can
still be used.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Joerg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-08-21 17:09:26 +02:00
Jan Höppner
f46f6d34d3 gitignore: Add cpumf/lspai
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-08-21 17:09:26 +02:00
Thomas Richter
3a96e8826f cpumf: Add lspai program and man page to display PAI counter sets
Add lspai program and man page to display Processor Activity
Information (PAI) facility counter sets in the same way as
lscpumf for the CPU Measurement Facility counter sets.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-08-21 17:09:23 +02:00
Mete Durlu
84738668ca hyptop/helper: fix smt utilization calculation
When calculating smt utiliziation field, subresults are capped to a
minimum value of zero to prevent wrap around while converting values
from signed to unsigned integers. The capping of subresults cause slight
inaccuracies therefore capping has been moved from intermediate steps
and done at the end.

Fixes: 0209c11bc1 ("hyptop: Add real SMT utilization field")

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-08-21 17:07:44 +02:00
Jan Höppner
dbea311aa8 Prepare for next release
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 14:51:41 +02:00
201 changed files with 13837 additions and 2498 deletions

View File

@@ -1,5 +1,3 @@
parm
parms
crate
ser
deriver

View File

@@ -29,5 +29,5 @@ indent_size = 4
[{Makefile,*.mak}]
indent_style = tab
[COMMIT_EDITMSG]
[{COMMIT_EDITMSG,EDIT_DESCRIPTION}]
max_line_length = 72

1
.gitignore vendored
View File

@@ -32,6 +32,7 @@ cpacfstats/cpacfstatsd
cpumf/chcpumf
cpumf/lscpumf
cpumf/lshwc
cpumf/lspai
cpumf/pai
cpuplugd/cpuplugd
dasdfmt/dasdfmt

35
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,35 @@
---
exclude: \.(crt|crl)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: check-merge-conflict
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
exclude_types: ['rust']
- repo: local
hooks:
- id: git-clang-format
name: git-clang-format
description: Run git-clang-format
entry: git
args: [clang-format, --staged, --]
pass_filenames: true
language: system
require_serial: true
minimum_pre_commit_version: "2.9.0"
types_or: [c++, c]
- repo: https://github.com/codespell-project/codespell
rev: v2.2.1
hooks:
- id: codespell
exclude_types: ['rust']
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
args: ["--external-sources"]

5
.shellcheckrc Normal file
View File

@@ -0,0 +1,5 @@
# Search in the current script's directory by default (since 0.7.0)
source-path=SCRIPTDIR
# Allow external-sources (since 0.8.0)
external-sources=true

View File

@@ -27,6 +27,7 @@ List of all individuals having contributed content to s390-tools
- Eberhard Pasch
- Eduard Shishkin
- Einar Lueck
- Eric Farman
- Eric Sandeen
- Erwin Vicari
- Eugene Crosser
@@ -35,6 +36,7 @@ List of all individuals having contributed content to s390-tools
- Farhan Ali
- Fedor Loshakov
- Felix Beck
- Finn Callies
- Frank Blaschka
- Frank Heimes
- Frank Munzert
@@ -56,6 +58,7 @@ List of all individuals having contributed content to s390-tools
- Horst Hummel
- Ingo Franzki
- Ingo Tuchscherer
- Jakub Čajka
- Jan Glauber
- Jan Höppner
- Jan Willeke
@@ -124,6 +127,8 @@ List of all individuals having contributed content to s390-tools
- Thomas Richter
- Thomas Spatzier
- Thomas Weber
- Thorsten Winkler
- Tobias Huschle
- Tuan Hoang
- Ursula Braun
- Utz Bacher
@@ -134,3 +139,4 @@ List of all individuals having contributed content to s390-tools
- Volker Sameske
- Wenjia Zhang
- Wolfgang Taphorn
- Yaakov Selkowitz

View File

@@ -1,6 +1,59 @@
Release history for s390-tools (MIT version)
--------------------------------------------
* __v2.31.0 (2024-02-02)__
For Linux kernel version: 6.7
General:
- common.mak: Set default C/C++ standard to gnu11/gnu++11
Add new tools / libraries:
- pvapconfig: Tool to automatically configure APQNs in SE KVM guests
- s390-tools: Provide pre-commit configuration
Changes of existing tools:
- cpuplugd: Adjust to CPU 0 being no longer hotpluggable
- dbginfo.sh: Check for Dynamic Partition Mode
- dbginfo.sh: Update man page and copyright
- rust/pv: Add user-data signing and verifying
- rust/pvsecret: Add user defined signatures and verifications
- zdev/dracut: Consolidate device configuration
Bug Fixes:
- dbginfo.sh: Fix relative path on script copy
- libkmipclient: Fix build with libxml2-2.12.0
- pvsecret: Fix panic if empty file is used as host key document
- rust/pv: Fix 'elided_lifetimes_in_associated_constant' warning
* __v2.30.0 (2023-12-01)__
For Linux kernel version: 6.6
Add new tools / libraries:
- lspai: Tool to display PAI counter sets
- s390-tools: Provide a ShellCheck configuration
Changes of existing tools / libraries:
- cpumf/pai: Add command line option for realtime scheduling
- dbginfo.sh: enhance ethtool collection for ROCE
- libutil/util_lockfile: add routine to return owning pid of file lock
- lszcrypt: Improve lszcrypt output on SE guests
- rust: Use a single workspace for all rust tools
- zdev: limit the derivation of ZDEV_SITE_ID
- zdump/df_s390: Update 'zgetdump -i' output with zlib info
- zdump/dfi_s390: Support reading compressed s390_ext dumps
- zipl/boot: Integrate zlib compression to single volume DASD dumper
- zipl/boot: compile the bootloaders only if HOST_ARCH is s390x
- zipl: Add --no-compress option to zipl command
- zkey: Also check for deconfigured and check-stopped cards
- dbginfo.sh: fix relative path on script copy
Bug Fixes:
- ap_tools/ap-check: handle get-attributes between pre and post event
- libutil: fix util_file_read_*() using wrong format specifiers
- rust/pv: fix Invalid write of size 1
* __v2.29.0 (2023-08-04)__
For Linux kernel version: 6.5

View File

@@ -386,6 +386,24 @@ the different tools are provided:
- Packages: blktrace, multipath-tools, sg3-utils
- Tools: rsync, tar, lsscsi
* zipl
For CCW-type DASD dump, zlib compression can be used to compress the dump
data before writing it to the DASD partition. It can benefit from
s390 on-chip compression accelerator (DFLTCC) and provide a faster dumping
process, hence lower system downtime.
The zlib version integrated with zipl (zipl/boot/zlib) is based on the Linux
kernel zlib (kernel version 6.3) which represents zlib version 1.1.3 with a
limited number of functions and a number of updates on top including s390
hardware compression (DFLTCC) support. Also, all memory allocations are
performed in advance, which aligns with zipl requirements.
The CCW-type standalone dumper is built as a single binary and must be
loaded to stage2 during boot. Hence, all required zlib functions must be
integrated into it, and its size is restricted. To limit the size, only
deflate-related parts are integrated (no decompression is required during
dumping).
Removing the inflate modules and function prototypes are the only major
modifications made to the kernel version of zlib.
* zgetdump
For building zgetdump you need OpenSSL version 1.1.0 or newer
installed (openssl-devel.rpm). Also required is glib2

View File

@@ -798,12 +798,34 @@ static int ap_check_handle_get_attributes(struct ap_check_anchor *anc)
FILE *f;
int rc;
rc = ap_get_lock_callout();
if (rc) {
fprintf(stderr, "Failed to acquire configuration lock %d\n", rc);
return -1;
/*
* For the get-attributes callout, we are typically called without the
* callout lock held. However, there is a particular scenario (define
* of an active mdev) where we may or may not be called with the lock
* already held on behalf of mdevctl, depending on the mdevctl version.
* Let's test for lock ownership first and, if already owned by the
* parent (mdevctl) proceed rather than waiting on the file lock.
*/
rc = ap_try_lock_callout();
switch (rc) {
case 0:
/* Lock acquired */
anc->cleanup_lock = true;
break;
case 1:
/* Lock held by parent -- trust the lock will remain held */
break;
default:
/* Lock not acquired or held by parent -- do a normal obtain */
rc = ap_get_lock_callout();
if (rc) {
fprintf(stderr,
"Failed to acquire configuration lock %d\n",
rc);
return -1;
}
anc->cleanup_lock = true;
}
anc->cleanup_lock = true;
/*
* Read the 'matrix' and 'control_domains' attributes to get the

View File

@@ -32,7 +32,7 @@ endif
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
VERSION = 2
RELEASE = 29
RELEASE = 31
PATCHLEVEL = 0
DISTRELEASE = build-$(shell date +%Y%m%d)
S390_TOOLS_RELEASE = $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
@@ -93,19 +93,19 @@ define cmd_define_and_export
endef
define define_toolchain_variables
$(eval $(call cmd_define_and_export, AS$(1)," AS$(1) ",$(2)as))
$(eval $(call cmd_define_and_export, CC$(1)," CC$(1) ",$(2)gcc))
$(eval $(call cmd_define_and_export, LINK$(1)," LINK$(1) ",$$(CC$(1))))
$(eval $(call cmd_define_and_export, CXX$(1)," CXX$(1) ",$(2)g++))
$(eval $(call cmd_define_and_export, LINKXX$(1)," LINKXX$(1) ",$$(CXX$(1))))
$(eval $(call cmd_define_and_export, CPP$(1)," CPP$(1) ",$(2)gcc -E))
$(eval $(call cmd_define_and_export, AR$(1)," AR$(1) ",$(2)ar))
$(eval $(call cmd_define_and_export, NM$(1)," NM$(1) ",$(2)nm))
$(eval $(call cmd_define_and_export, STRIP$(1)," STRIP$(1) ",$(2)strip))
$(eval $(call cmd_define_and_export,OBJCOPY$(1)," OBJCOPY$(1) ",$(2)objcopy))
$(eval $(call cmd_define_and_export,OBJDUMP$(1)," OBJDUMP$(1) ",$(2)objdump))
$(eval PKG_CONFIG$(1) = pkg-config)
$(eval export PKG_CONFIG$(1))
$(call cmd_define_and_export, AS$(1)," AS$(1) ",$(2)as)
$(call cmd_define_and_export, CC$(1)," CC$(1) ",$(2)gcc)
$(call cmd_define_and_export, LINK$(1)," LINK$(1) ",$$(CC$(1)))
$(call cmd_define_and_export, CXX$(1)," CXX$(1) ",$(2)g++)
$(call cmd_define_and_export, LINKXX$(1)," LINKXX$(1) ",$$(CXX$(1)))
$(call cmd_define_and_export, CPP$(1)," CPP$(1) ",$(2)gcc -E)
$(call cmd_define_and_export, AR$(1)," AR$(1) ",$(2)ar)
$(call cmd_define_and_export, NM$(1)," NM$(1) ",$(2)nm)
$(call cmd_define_and_export, STRIP$(1)," STRIP$(1) ",$(2)strip)
$(call cmd_define_and_export,OBJCOPY$(1)," OBJCOPY$(1) ",$(2)objcopy)
$(call cmd_define_and_export,OBJDUMP$(1)," OBJDUMP$(1) ",$(2)objdump)
PKG_CONFIG$(1) = pkg-config
export PKG_CONFIG$(1)
endef
# If the host architecture is not the same as the build architecture
@@ -119,8 +119,8 @@ ifneq ($(HOST_ARCH),$(BUILD_ARCH))
endif
endif
$(call define_toolchain_variables,_FOR_BUILD,)
$(call define_toolchain_variables,,$(CROSS_COMPILE))
$(eval $(call define_toolchain_variables,_FOR_BUILD,))
$(eval $(call define_toolchain_variables,,$(CROSS_COMPILE)))
$(eval $(call cmd_define, RUNTEST," RUNTEST ",$(S390_TEST_LIB_PATH)/s390_runtest))
@@ -304,7 +304,7 @@ ZFCPDUMP_FLAVOR = zfcpdump
export ZFCPDUMP_DIR ZFCPDUMP_IMAGE ZFCPDUMP_INITRD ZFCPDUMP_FLAVOR
CFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
CFLAGS_FOR_BUILD ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
CFLAGS_FOR_BUILD ?= -std=gnu11 $(DEFAULT_CFLAGS) $(OPT_FLAGS)
CPPFLAGS ?= $(DEFAULT_CPPFLAGS)
LDFLAGS ?= $(DEFAULT_LDFLAGS)
@@ -313,14 +313,14 @@ ALL_CFLAGS = -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \
-DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \
-DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
-DS390_TOOLS_BINDIR=$(BINDIR) \
$(CFLAGS)
-std=gnu11 $(CFLAGS)
CXXFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
ALL_CXXFLAGS = -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \
-DS390_TOOLS_LIBDIR=$(TOOLS_LIBDIR) \
-DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \
-DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
-DS390_TOOLS_BINDIR=$(BINDIR) \
$(CXXFLAGS)
-std=gnu++11 $(CXXFLAGS)
ALL_CPPFLAGS = -I $(rootdir)include $(CPPFLAGS)
ALL_LDFLAGS = $(LDFLAGS)

View File

@@ -355,13 +355,13 @@ PCKMO DES,
.IP \(bu
PCKMO 2key TDES,
.IP \(bu
PCMKO TDES,
PCKMO TDES,
.IP \(bu
PCKMO AES 128bit,
.IP \(bu
PCKMO AES 192bit,
.IP \(bu
PCMKO AES 256bit,
PCKMO AES 256bit,
.IP \(bu
PCKMO ECC P256,
.IP \(bu

View File

@@ -204,10 +204,10 @@ static const char *const pai_str[] = {
[142] = "KDSA EdDSA Sign Ed448 protected key",
[143] = "PCKMO DES",
[144] = "PCKMO 2key TDES",
[145] = "PCMKO TDES",
[145] = "PCKMO TDES",
[146] = "PCKMO AES 128bit",
[147] = "PCKMO AES 192bit",
[148] = "PCMKO AES 256bit",
[148] = "PCKMO AES 256bit",
[149] = "PCKMO ECC P256",
[150] = "PCKMO ECC P384",
[151] = "PCKMO ECC P521",

View File

@@ -1,7 +1,7 @@
include ../common.mak
BIN_FILES = lscpumf chcpumf lshwc pai
MAN_FILES = lscpumf.8 chcpumf.8 lshwc.8 pai.8
BIN_FILES = lscpumf chcpumf lshwc pai lspai
MAN_FILES = lscpumf.8 chcpumf.8 lshwc.8 pai.8 lspai.8
all: $(BIN_FILES)
@@ -11,6 +11,7 @@ lscpumf: lscpumf.o $(libs)
chcpumf: chcpumf.o $(libs)
lshwc: lshwc.o $(libs)
pai: pai.o $(libs)
lspai: lspai.o $(libs)
install: all install-man
$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8

352
cpumf/lspai.c Normal file
View File

@@ -0,0 +1,352 @@
/* Copyright IBM Corp. 2023
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
/* List available Processor Assist Instrumentation (PAI) counters. */
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "lib/util_opt.h"
#include "lib/util_prg.h"
#include "lib/util_base.h"
#include "lib/util_path.h"
#include "lib/util_scandir.h"
#include "lib/util_libc.h"
#include "lib/util_file.h"
#include "lib/util_list.h"
#include "lib/libcpumf.h"
static struct util_opt opt_vec[] = {
UTIL_OPT_SECTION("OPTIONS"),
{
.option = { "numeric", no_argument, NULL, 'n' },
.desc = "Sort PAI counters by counter number"
},
{
.option = { "type", required_argument, NULL, 't' },
.argument = "TYPE",
.desc = "Type of PAI counters to show: crypto, nnpa"
},
UTIL_OPT_HELP,
UTIL_OPT_VERSION,
UTIL_OPT_END
};
static const struct util_prg prg = {
.desc = "List Processor Assist Information counter sets",
.copyright_vec = {
{
.owner = "IBM Corp.",
.pub_first = 2023,
.pub_last = 2023,
},
UTIL_PRG_COPYRIGHT_END
}
};
static bool numsort; /* If true sort counter numerically */
#define PAI_PATH "/bus/event_source/devices/%s"
enum pai_types { /* Bit mask for supported PAI counters */
pai_type_crypto = 0, /* PAI Crypto Counters */
pai_type_nnpa = 1, /* PAI NNPA Counters */
pai_type_max = 2, /* PAI maximum value, must be last */
};
static int pai_types_show;
struct pai_ctrname { /* List of defined counters */
char *name; /* Counter name */
unsigned long nr; /* Counter number */
};
struct pai_node { /* Head for PAI counter sets */
struct util_list_node node; /* Successor in PAI counter set list */
enum pai_types type; /* PAI type */
int pmu; /* Assigned PMU type number */
const char *name; /* Counter set name */
char *name_uc; /* Counter set name upper case */
const char *sysfs_name; /* Counter set name in /sysfs tree */
const char *filter_name; /* Counter set name for scandir filter */
struct pai_ctrname *ctrlist; /* List of counter names & numbers */
size_t ctrsize; /* Total size in bytes of ctrlist */
int ctridx; /* Index of last entry used in ctrlist */
unsigned long base; /* Base number for counter set */
};
static struct util_list pai_list;
/* Return base of counter set, this is the first counter of this set. */
static unsigned long pai_type_base(enum pai_types t)
{
switch (t) {
case pai_type_crypto:
return 0x1000;
case pai_type_nnpa:
return 0x1800;
case pai_type_max:
break;
}
return 0;
}
/* Test PAI counter name from command line option. */
static const char *pai_type_name(enum pai_types t)
{
switch (t) {
case pai_type_crypto:
return "crypto";
case pai_type_nnpa:
return "nnpa";
case pai_type_max:
break;
}
return "unknown";
}
/* Convert PAI counter type to sysfs directory name. Only validated
* input at this time.
*/
static const char *pai_type_sysfs(enum pai_types t)
{
if (t == pai_type_crypto)
return "pai_crypto";
return "pai_ext";
}
/* Convert PAI counter type to sysfs directory name filter for scandir(). */
static const char *pai_type_filter(enum pai_types t)
{
if (t == pai_type_nnpa)
return "^NNPA";
return "[^.]"; /* Matches anything but . and .. in sysfs */
}
/* Sort PAI counter names by assigned counter number. */
static int pai_ctrcmp(const void *p1, const void *p2)
{
struct pai_ctrname *l = (struct pai_ctrname *)p1;
struct pai_ctrname *r = (struct pai_ctrname *)p2;
return l->nr > r->nr ? 1 : -1;
}
/* Convert string to upper case. */
static char *str2uc(const char *s)
{
char *uc = util_strdup(s), *old_uc = uc;
for (; *uc; ++uc)
*uc = toupper(*uc);
return old_uc;
}
/* Read counter names and assigned event number from sysfs file tree.
* Exit when sysfs directory can not be scanned.
*/
static void read_counternames(struct pai_node *node)
{
int i, more = 0, ctr = 0, count = 0;
struct dirent **namelist = NULL;
char *path, *ctrpath;
/* Read counter names and assigned event number. */
path = util_path_sysfs(PAI_PATH "/events", node->sysfs_name);
count = util_scandir(&namelist, alphasort, path, node->filter_name);
if (count <= 0)
errx(EXIT_FAILURE, "Cannot open %s", path);
node->ctrsize = count * sizeof(*node->ctrlist);
node->ctrlist = util_malloc(node->ctrsize);
for (i = 0; i < count && ctr >= 0; i++) {
util_asprintf(&ctrpath, "%s/%s", path, namelist[i]->d_name);
if (util_file_read_va(ctrpath, "event=%x", &ctr) == 1) {
node->ctrlist[node->ctridx].name = util_strdup(namelist[i]->d_name);
node->ctrlist[node->ctridx++].nr = ctr;
more++;
} else {
warnx("Cannot parse %s", ctrpath);
}
free(ctrpath);
}
util_scandir_free(namelist, count);
free(path);
if (numsort && more > 1)
qsort(node->ctrlist, more, sizeof(*node->ctrlist), pai_ctrcmp);
}
static void show_painode(void)
{
struct pai_node *node;
int indent = 0;
int offset = 0;
util_list_iterate(&pai_list, node) {
for (int i = 0; i < node->ctridx; ++i)
indent = MAX((size_t)indent, strlen(node->ctrlist[i].name));
}
printf("RAW %*s NAME %*s DESCRIPTION\n", 3, "", indent - 5, "");
util_list_iterate(&pai_list, node) {
for (int i = 0; i < node->ctridx; ++i) {
printf("%d:%ld %s", node->pmu,
node->ctrlist[i].nr, node->ctrlist[i].name);
offset = indent - strlen(node->ctrlist[i].name) + 1;
printf("%*s", offset, "");
printf("Counter %ld / PAI %s counter set\n",
node->ctrlist[i].nr - node->base, node->name_uc);
}
}
}
/* Release all memory allocated at make_painode(). */
static void free_painode(void)
{
struct pai_node *next, *node;
util_list_iterate_safe(&pai_list, node, next) {
free(node->name_uc);
for (int i = 0; i < node->ctridx; ++i)
free(node->ctrlist[i].name);
free(node->ctrlist);
free(node);
}
}
static void make_painode(enum pai_types t)
{
struct pai_node *node = util_zalloc(sizeof(*node));
char *path;
node->type = t;
node->sysfs_name = pai_type_sysfs(t);
node->name = pai_type_name(t);
node->name_uc = str2uc(node->name);
node->filter_name = pai_type_filter(t);
node->base = pai_type_base(t);
/* Read PMU type number. */
path = util_path_sysfs(PAI_PATH, node->sysfs_name);
node->pmu = libcpumf_pmutype(path);
if (node->pmu < 0)
errx(EXIT_FAILURE, "Cannot open %s", path);
free(path);
read_counternames(node);
util_list_add_tail(&pai_list, node);
}
static int painode_cmp(void *a, void *b, void *UNUSED(data))
{
struct pai_node *n1 = (struct pai_node *)a;
struct pai_node *n2 = (struct pai_node *)b;
return n1->pmu < n2->pmu ? -1 : 1;
}
static void sort_painode(void)
{
util_list_sort(&pai_list, painode_cmp, NULL);
}
/* Check for hardware support and return false if not available. */
static bool have_support(enum pai_types t)
{
const char *sysfn = pai_type_sysfs(t);
char *path = util_path_sysfs(PAI_PATH, sysfn);
bool rc = true;
if (!util_path_is_dir(path)) {
warnx("No support for PAI %s facility", pai_type_name(t));
rc = false;
}
free(path);
return rc;
}
/*
* Check the argument for option -t. It must be a valid PAI counter set.
* Exit when an invalid PAI counter set name has been specified.
*/
static void check_type_name(const char *type)
{
bool no_match = true;
enum pai_types i;
const char *fn;
for (i = pai_type_crypto; i < pai_type_max; ++i) {
fn = pai_type_name(i);
if (!strcasecmp(fn, type)) {
pai_types_show |= (1 << i);
no_match = false;
}
}
if (no_match)
errx(EXIT_FAILURE, "Invalid argument for -t %s", type);
}
int main(int argc, char **argv)
{
int ch;
util_list_init(&pai_list, struct pai_node, node);
util_prg_init(&prg);
util_opt_init(opt_vec, NULL);
while ((ch = util_opt_getopt_long(argc, argv)) != -1) {
switch (ch) {
default:
util_opt_print_parse_error(ch, argv);
return EXIT_FAILURE;
case 'h':
util_prg_print_help();
util_opt_print_help();
return EXIT_SUCCESS;
case 'v':
util_prg_print_version();
return EXIT_SUCCESS;
case 'n':
numsort = true;
break;
case 't':
check_type_name(optarg);
break;
}
}
/* Nothing specified, show all PAI counters */
if (!pai_types_show)
pai_types_show = (1 << pai_type_crypto) | (1 << pai_type_nnpa);
/* Check for hardware support */
for (enum pai_types i = pai_type_crypto; i < pai_type_max; ++i) {
if ((pai_types_show & (1 << i))) {
if (!have_support(i))
pai_types_show &= ~(1 << i);
else
make_painode(i);
}
}
sort_painode();
show_painode();
free_painode();
return ch;
}

80
cpumf/man/lspai.8 Normal file
View File

@@ -0,0 +1,80 @@
.\" lspai.8
.\"
.\"
.\" Copyright IBM Corp. 2021
.\" s390-tools is free software; you can redistribute it and/or modify
.\" it under the terms of the MIT license. See LICENSE for details.
.\" ----------------------------------------------------------------------
.ds c \fBlspai\fP
.
.TH \*c "8" "August 2023" "s390-tools" "CPU-MF management programs"
.
.SH NAME
\*c \- list Processor Activity Instrumentation (PAI) counters
.
.SH SYNOPSIS
\*c
.RB [ \-n ]
.RB [ \-t
.IR "\ TYPE" ]
.br
\*c
.BR \-h | \-\-help
.br
\*c
.BR \-v | \-\-version
.
.
.SH DESCRIPTION
\*c displays the Processor Activity Instrumentation (PAI) counters
for Linux on IBM Z.
The output is a human-readable list of available PAI counter
names and numbers.
.SH OPTIONS
.TP
.BR \-h ", " \-\-help
Displays help information, then exits.
.
.TP
.BR \-v ", " \-\-version
Displays version information, then exits.
.
.TP
.BR \-t ", " \-\-type "\ TYPE"
Specifies the PAI counter set to list.
Valid counter set values are
.I crypto
and
.IR nnpa .
By default, the command lists all available PAI counter sets.
NNPA refers to the Neural Network Processing Assist facility counter set.
Crypto refers to the Cryptografic Processing Assist facility counter set.
.
.TP
.BR \-n ", " \-\-numeric
Shows the PAI counter sets sorted by counter number.
Default sort order is PAI counter name.
.
.SH "EXAMPLE"
The \*c invocation lists all PAI Neural Network Processing Assist Facility
(NNPA) counters in numeric order:
.nf
# lspai -t nnpa -n
RAW NAME DESCRIPTION
13:6144 NNPA_ALL Counter 0 / PAI NNPA counter set
13:6145 NNPA_ADD Counter 1 / PAI NNPA counter set
13:6146 NNPA_SUB Counter 2 / PAI NNPA counter set
13:6147 NNPA_MUL Counter 3 / PAI NNPA counter set
\&...
.fi
The first column shows the raw event number suitable for
.IR perf "(8)"
raw event specification.
The second column shows the PAI NNPA counter name,
suitable for
.IR perf "(8)"
event specification by name.
The third gives a short explanation, if available.
.SH "SEE ALSO"
.BR pai (8)
.BR lscpumf (8)

View File

@@ -18,6 +18,8 @@
.IR size ]
.RB [ \-i | \-\-interval
.IR ms ]
.RB [ \-R | \-\-realtime
.IR prio ]
.BR \-c | \-\-crypto [ \fIcpulist ][: \fIdata\fR "] [" \fIloops\fP ]
.br
\*c
@@ -25,6 +27,8 @@
.IR size ]
.RB [ \-i | \-\-interval
.IR ms ]
.RB [ \-R | \-\-realtime
.IR prio ]
.BR \-n | \-\-nnpa [ \fIcpulist ][: \fIdata\fR "] [" \fIloops\fP ]
.br
\*c
@@ -191,6 +195,14 @@ The ring buffer is created with the
.IR mmap (2)
system call.
.
.TP
.BR \-R ", " \-\-realtime "\ prio"
Collect data using the RT SCHED_FIFO priority specified by
.BR prio .
Valid values are integers in the range 1 (low) to 99 (high).
Use this option when gathering data from multiple CPUs
to prevent data loss.
.
.SH ARGUMENT
The command line options determine how command line
arguments are interpreted.

View File

@@ -320,7 +320,7 @@ static void readmap(int fd)
* ring buffer per event, sleep some short time and always read all
* ring buffer for new contents.
*/
static void collect(unsigned long cnt)
static int collect(unsigned long cnt)
{
fd_set r_fds, e_fds, a_fds;
struct pai_event *p;
@@ -328,6 +328,7 @@ static void collect(unsigned long cnt)
int rc, max_fd;
do {
rc = -1;
max_fd = -1;
tv.tv_sec = read_interval / 1000;
tv.tv_usec = (1000 * read_interval) % 1000000;
@@ -357,6 +358,7 @@ static void collect(unsigned long cnt)
}
}
} while (rc != -1 && --cnt > 0);
return rc;
}
static void lookup_event(__u64 evtnum, __u16 ctr, __u64 value)
@@ -449,6 +451,11 @@ static void evt_show(__u64 evtnum, const char *evtsel, struct pai_event_out *ev)
ev->u.s_comm.tid);
break;
case PERF_RECORD_SWITCH:
printf("cs-%s",
(ev->misc & PERF_RECORD_MISC_SWITCH_OUT) ? "out" : "in");
break;
case PERF_RECORD_SWITCH_CPU_WIDE:
if (ev->misc & PERF_RECORD_MISC_SWITCH_OUT) {
short p = PERF_RECORD_MISC_SWITCH_OUT_PREEMPT;
@@ -549,6 +556,9 @@ static int evt_scan(char *fn, unsigned char *buf, size_t len,
offset -= sizeof(__u64);
break;
case PERF_RECORD_SWITCH:
break;
case PERF_RECORD_SWITCH_CPU_WIDE:
memcpy(&ev.u, buf + offset, sizeof(ev.u.s_cs));
offset += sizeof(ev.u.s_cs);
@@ -583,7 +593,7 @@ static int evt_scan(char *fn, unsigned char *buf, size_t len,
break;
default:
printf("unknown header-type %d ", hdr->type);
printf("unknown header-type %d\n", hdr->type);
offset += hdr->size - sizeof(*hdr);
goto bypass;
}
@@ -944,6 +954,11 @@ static struct util_opt opt_vec[] = {
.option = { "report", no_argument, NULL, 'r' },
.desc = "Report file contents"
},
{
.option = { "realtime", required_argument, NULL, 'R' },
.argument = "PRIO",
.desc = "Collect data with this RT SCHED_FIFO priority"
},
{
.option = { "interval", required_argument, NULL, 'i' },
.argument = "NUMBER",
@@ -1007,6 +1022,19 @@ static unsigned long check_mapsize(unsigned long n)
return cnt == 1 ? n : 0;
}
static void setprio(const char *prio)
{
struct sched_param param;
char *endstr;
memset(&param, 0, sizeof(param));
param.sched_priority = strtoul(prio, &endstr, 0);
if (*endstr)
errno = EINVAL;
if (*endstr || sched_setscheduler(0, SCHED_FIFO, &param))
err(EXIT_FAILURE, "Could not set realtime priority");
}
int main(int argc, char **argv)
{
bool crypto_record = false, report = false;
@@ -1061,6 +1089,9 @@ int main(int argc, char **argv)
record_cpus_nnpa(optarg);
nnpa_record = true;
break;
case 'R':
setprio(optarg);
break;
case 'r':
report = true;
break;
@@ -1094,12 +1125,12 @@ int main(int argc, char **argv)
ev_install(group);
ev_enable();
collect(loop_count);
ch = collect(loop_count);
ev_disable();
ev_deinstall();
ev_dealloc();
return EXIT_SUCCESS;
return ch < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
/* Must be reporting */

View File

@@ -25,7 +25,7 @@ int get_numcpus()
for (i = 0; ; i++) {
/* check whether file exists and is readable */
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i);
sprintf(path, "/sys/devices/system/cpu/cpu%d", i);
if (access(path, R_OK) == 0)
number++;
else
@@ -45,11 +45,13 @@ int get_num_online_cpus()
int status = 0;
int value_of_onlinefile, rc;
for (i = 0; i <= get_numcpus(); i++) {
for (i = 0; i < get_numcpus(); i++) {
/* check wether file exists and is readable */
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i);
if (access(path, R_OK) != 0)
if (access(path, R_OK) != 0) {
status++;
continue;
}
filp = fopen(path, "r");
if (!filp)
cpuplugd_exit("Cannot open cpu online file: "
@@ -101,10 +103,8 @@ int hotplug(int cpuid)
cpuid);
return -1;
}
} else {
cpuplugd_error("hotplugging cpu with id %d failed\n", cpuid);
return -1;
}
cpuplugd_debug("cpu with id %d cannot be hotplugged\n", cpuid);
return -1;
}
@@ -135,9 +135,8 @@ int hotunplug(int cpuid)
fclose(filp);
if (state == 0)
return 1;
} else {
cpuplugd_error("unplugging cpu with id %d failed\n", cpuid);
}
cpuplugd_debug("cpu with id %d cannot be hotunplugged\n", cpuid);
return retval;
}
@@ -163,6 +162,8 @@ int is_online(int cpuid)
retval = 0;
}
fclose(filp);
} else {
retval = 1;
}
return retval;
}

View File

@@ -15,13 +15,13 @@ dasdfmt \- formatting of DASD (ECKD) disk drives.
.SH DESCRIPTION
\fBdasdfmt\fR formats a DASD (ECKD) disk drive to prepare it
for usage with Linux for S/390.
for usage with Linux for S/390.
The \fIdevice\fR is the node of the device (e.g. '/dev/dasda').
Any device node created by udev for kernel 2.6 can be used
Any device node created by udev for kernel 2.6 can be used
(e.g. '/dev/dasd/0.0.b100/disc').
.br
\fBWARNING\fR: Careless usage of \fBdasdfmt\fR can result in
\fBWARNING\fR: Careless usage of \fBdasdfmt\fR can result in
\fBLOSS OF DATA\fR.
.SH OPTIONS
@@ -31,7 +31,7 @@ Print usage and exit.
.TP
\fB-t\fR or \fB--test\fR
Disables any modification of the disk drive.
Disables any modification of the disk drive.
.br
\fBdasdfmt\fR just prints
out, what it \fBwould\fR do.
@@ -41,7 +41,7 @@ out, what it \fBwould\fR do.
Increases verbosity.
.TP
\fB-y\fR
\fB-y\fR
Start formatting without further user-confirmation.
.TP
@@ -59,7 +59,7 @@ Omit the writing of a disk label after formatting.
.br
This makes only sense for the 'ldl' disk layout.
.br
The '-L' option has to be specified after the '-d ldl' option.
The '-L' option has to be specified after the '-d ldl' option.
.br
e.g. dasdfmt -d ldl -L /dev/...
@@ -84,13 +84,13 @@ Formats the device with compatible disk layout or linux disk layout.
\fIlayout\fR is either \fIcdl\fR for the compatible disk layout
(default) or \fIldl\fR for the linux disk layout.
.br
Compatible disk layout means a special handling of the
first two tracks of the volume. This enables other S/390 or zSeries
Compatible disk layout means a special handling of the
first two tracks of the volume. This enables other S/390 or zSeries
operating systems to access this device (e.g. for backup purposes).
.TP
\fB-p\fR or \fB--progressbar\fR
Print a progress bar while formatting.
Print a progress bar while formatting.
Do not use this option if you are using a 3270 console,
running in background or redirecting the output to a file.
@@ -164,30 +164,30 @@ and always be a power of two. The recommended blocksize is 4096 bytes.
.TP
\fB-l\fR \fIvolser\fR or \fB--label\fR=\fIvolser\fR
Specify the volume serial number or volume identifier to be written
to disk after formatting. If no label is specified, a sensible default
is used. \fIvolser\fR is interpreted as ASCII string and is automatically
Specify the volume serial number or volume identifier to be written
to disk after formatting. If no label is specified, a sensible default
is used. \fIvolser\fR is interpreted as ASCII string and is automatically
converted to uppercase and then to EBCDIC.
.br
e.g. -l LNX001 or --label=DASD01
.br
The \fIvolser\fR identifies by serial number the volume. A volume serial
The \fIvolser\fR identifies by serial number the volume. A volume serial
number is 1 through 6 alphanumeric or one of the following special
characters: $, #, @, %. Enclose a serial number that contains special
characters in apostrophes. If the number is shorter than six
characters: $, #, @, %. Enclose a serial number that contains special
characters in apostrophes. If the number is shorter than six
characters, it is padded with trailing blanks.
.br
.br
Do not code a volume serial number as SCRTCH, PRIVAT, or Lnnnnn (L with
five numbers); these are used in OS/390 messages to ask the operator to
mount a volume. Do not code a volume serial number as MIGRAT, which is
used by the OS/390 Hierarchical Storage Manager DFSMShsm for migrated
Do not code a volume serial number as SCRTCH, PRIVAT, or Lnnnnn (L with
five numbers); these are used in OS/390 messages to ask the operator to
mount a volume. Do not code a volume serial number as MIGRAT, which is
used by the OS/390 Hierarchical Storage Manager DFSMShsm for migrated
data sets.
.br
NOTE: Try to avoid using special characters in the volume serial. This may cause problems accessing a disk by volser.
NOTE: Try to avoid using special characters in the volume serial. This may cause problems accessing a disk by volser.
.br
In case you really have to use special characters, make sure you are using quotes. In addition there is a special handling for the '$' sign. Please specify it using '\\$' if necessary.
.br
@@ -197,9 +197,8 @@ e.g. -l 'a@b\\$c#' to get A@B$C#
.TP
\fB-k\fR or \fB--keep_volser\fR
Keeps the Volume Serial Number, when writing the Volume Label. This is
useful, if the Serial Number has been written with a VM Tool and should not
be overwritten.
Keeps the Volume Serial Number when writing the Volume Label. This is useful if
the volume already has a Serial Number that should not be overwritten.
.br
.SH SEE ALSO

View File

@@ -1,7 +1,7 @@
# Common definitions
include ../../common.mak
ALL_CPPFLAGS += -I../include -std=gnu99 -Wno-unused-parameter
ALL_CPPFLAGS += -I../include -Wno-unused-parameter
LDLIBS += -lpthread -lrt
ifneq ($(HAVE_ZLIB),0)
ALL_CPPFLAGS += -DHAVE_ZLIB

View File

@@ -19,10 +19,10 @@ help:
.br
\fBfdasd\fR {-h|-v}
.SH DESCRIPTION
\fBfdasd\fR writes a partition table to a cdl (compatible disk layout)
\fBfdasd\fR writes a partition table to a cdl (compatible disk layout)
formatted DASD, in the form of
a VTOC (volume table of contents) for usage with Linux for S/390
or zSeries. If fdasd detects a valid \fBVOL1\fR volume label, it
or zSeries. If fdasd detects a valid \fBVOL1\fR volume label, it
will use it, otherwise it asks to write a new one.
.br
@@ -34,51 +34,51 @@ will use it, otherwise it asks to write a new one.
Print usage information, then exit.
.TP
\fB-v\fR or \fB--version\fR
\fB-v\fR or \fB--version\fR
Print version information, then exit.
.TP
\fB-s\fR or \fB--silent\fR
\fB-s\fR or \fB--silent\fR
Suppress messages in non-interactive mode.
.TP
\fB-r\fR or \fB--verbose\fR
\fB-r\fR or \fB--verbose\fR
Provide more verbose output.
.TP
\fB-a\fR or \fB--auto\fR
Automatically create a partition using the entire disk in non-interactive
\fB-a\fR or \fB--auto\fR
Automatically create a partition using the entire disk in non-interactive
mode.
.TP
\fB-k\fR or \fB--keep_volser\fR
Keeps the volume serial when writing the volume label.
Keeps the Volume Serial Number when writing the Volume Label.
.br
This is useful, if the volume serial has been written before and should not
be overwritten. This option is only applicable in non-interactive mode.
This is useful if the volume already has a Serial Number that should not be
overwritten. This option is only applicable in non-interactive mode.
.TP
\fB-l\fR \fIvolser\fR or \fB--label\fR \fIvolser\fR
Specify the volume serial.
.br
\fIvolser\fR is interpreted as ASCII string and is automatically converted to
\fIvolser\fR is interpreted as ASCII string and is automatically converted to
uppercase, padded with blanks and finally converted to EBCDIC to be written
to disk. This option is only applicable in non-interactive mode.
.br
Do not use the following reserved volume serial: SCRTCH, PRIVAT, MIGRAT,
or Lnnnnn (L with five digit number); These are used as keywords by
Do not use the following reserved volume serial: SCRTCH, PRIVAT, MIGRAT,
or Lnnnnn (L with five digit number); These are used as keywords by
other operating systems (OS/390).
.br
A volume serial is 1 through 6 alphanumeric characters or one of the
following special characters: $, #, @, %. All other characters are simply
ignored.
A volume serial is 1 through 6 alphanumeric characters or one of the
following special characters: $, #, @, %. All other characters are simply
ignored.
.br
Try to avoid using special characters in the volume serial.
This may cause problems accessing a disk by volser.
In case you really have to use special characters, make sure you are using
quotes. In addition there is a special handling for the '$' sign.
Try to avoid using special characters in the volume serial.
This may cause problems accessing a disk by volser.
In case you really have to use special characters, make sure you are using
quotes. In addition there is a special handling for the '$' sign.
Please specify it using '\\$' if necessary.
.br
@@ -124,14 +124,14 @@ partitions that use the entire disk:
.br
.TP
\fB-i\fR or \fB--volser\fR
\fB-i\fR or \fB--volser\fR
Print the volume serial, then exit.
.TP
\fB-p\fR or \fB--table\fR
Print partition table, then exit.
\fB-p\fR or \fB--table\fR
Print partition table, then exit.
.br
In combination with the -s option fdasd will display a short version of the
In combination with the -s option fdasd will display a short version of the
partition table.
.TP
@@ -179,7 +179,7 @@ In case your are not using the device file system, please specify:
.br
where \fIx\fR is one or more lowercase letter(s) or any other device
node specification configured by udev for kernel 2.6 or higher.
node specification configured by udev for kernel 2.6 or higher.
.SH SEE ALSO
.BR dasdfmt (8)

View File

@@ -79,6 +79,7 @@ stage3b.elf: head.o $(ZIPL_OBJS)
%.bin.debug: %.elf
$(OBJCOPY) --only-keep-debug $< $@
@chmod a-x $@
%.bin: %.elf
$(OBJCOPY) -O binary $< $@

View File

@@ -61,13 +61,17 @@ void __noreturn start(void)
if (cmdline->size > get_kernel_cmdline_size())
panic(EINTERNAL, "Command line is too large\n");
/* move the kernel cmdline */
memmove((void *)COMMAND_LINE,
(void *)cmdline->src,
cmdline->size);
if (cmdline->size > 0) {
/* make sure the cmdline is a null-terminated string */
if (((char *)cmdline->src)[cmdline->size - 1] != '\0')
panic(EINTERNAL, "Command line needs to be null-terminated\n");
/* move the kernel cmdline */
memmove((void *)COMMAND_LINE, (void *)cmdline->src, cmdline->size);
}
/* the initrd does not need to be moved */
if (initrd->size != 0) {
if (initrd->size > 0) {
/* copy initrd start address and size into new kernel space */
*(unsigned long long *)INITRD_START = initrd->src;
*(unsigned long long *)INITRD_SIZE = initrd->size;

View File

@@ -27,7 +27,7 @@ $(bin_PROGRAM)_SRCS := $(bin_PROGRAM).c pv/pv_stage3.c pv/pv_image.c \
$(NULL)
$(bin_PROGRAM)_OBJS := $($(bin_PROGRAM)_SRCS:.c=.o)
ALL_CFLAGS += -std=gnu11 -DPKGDATADIR=$(PKGDATADIR) \
ALL_CFLAGS += -DPKGDATADIR=$(PKGDATADIR) \
$(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) $(LIBCURL_CFLAGS) \
-DOPENSSL_API_COMPAT=0x10100000L \
$(WARNINGS) \

View File

@@ -684,7 +684,26 @@ gint pv_img_add_component(PvImage *img, const PvArg *arg, GError **err)
{
g_autoptr(PvComponent) comp = NULL;
comp = pv_component_new_file(arg->type, arg->path, err);
switch (arg->type) {
case PV_COMP_TYPE_INITRD:
case PV_COMP_TYPE_KERNEL:
case PV_COMP_TYPE_STAGE3B:
comp = pv_component_new_file(arg->type, arg->path, err);
break;
case PV_COMP_TYPE_CMDLINE: {
g_autoptr(PvBuffer) buf = NULL;
g_autofree char *data = NULL;
gsize length;
if (!g_file_get_contents(arg->path, &data, &length, err))
return -1;
/* Add one for the null terminator */
buf = pv_buffer_take(g_steal_pointer(&data), length + 1);
comp = pv_component_new_buf(arg->type, buf, err);
} break;
}
if (!comp)
return -1;

View File

@@ -26,6 +26,15 @@ PvBuffer *pv_buffer_alloc(gsize size)
return ret;
}
PvBuffer *pv_buffer_take(char *data, gsize size)
{
PvBuffer *ret = g_new0(PvBuffer, 1);
ret->data = data;
ret->size = size;
return ret;
}
PvBuffer *pv_buffer_dup(const PvBuffer *buf, gboolean page_aligned)
{
PvBuffer *ret;

View File

@@ -21,6 +21,10 @@ typedef struct PvBuffer {
} PvBuffer;
PvBuffer *pv_buffer_alloc(gsize size);
/* After this call @data belongs to the PvBuffer and must no longer be modified
* by the caller.
*/
PvBuffer *pv_buffer_take(char *data, gsize size);
void pv_buffer_free(PvBuffer *buf);
void pv_buffer_clear(PvBuffer **buf);
gint pv_buffer_write(const PvBuffer *buf, FILE *file, GError **err);

View File

@@ -404,11 +404,11 @@ s64 ht_calculate_smt_util(u64 core_us, u64 thr_us, u64 mgm_us, int thread_per_co
s64 component1, component2, smt_us;
double smt_factor = g.o.smt_factor;
component1 = G0(thread_per_core * core_us - thr_us);
component1 = thread_per_core * core_us - thr_us;
if (thread_per_core > 1)
component1 /= smt_factor;
component2 = G0(thr_us - core_us);
smt_us = component1 + component2 + mgm_us;
component2 = thr_us - core_us;
smt_us = G0(component1 + component2 + mgm_us);
return smt_us;
}

View File

@@ -22,11 +22,19 @@
#define STAGE2_DESC _AC(0x78, UL)
#define STAGE2_ENTRY _AC(0x2018, UL)
#define STAGE2_HEAP_ADDRESS _AC(0x6000, UL)
#define ECKD2DUMP_SV_HEAP_ADDRESS _AC(0xb000, UL)
#define STAGE2_HEAP_SIZE _AC(0x3000, UL)
#define STAGE2_STACK_ADDRESS _AC(0xe400, UL)
#define STAGE2_STACK_SIZE _AC(0x1c00, UL)
#define ECKD2DUMP_SV_STACK_ADDRESS _AC(0xe000, UL)
#define ECKD2DUMP_SV_STACK_SIZE _AC(0x2000, UL)
#define STAGE2_MAX_SIZE _AC(0x3000, UL)
#define STAGE2_DUMPER_SIZE_V1 _AC(0x1000, UL)
#define STAGE2_DUMPER_SIZE_V2 _AC(0x2000, UL)
#define STAGE2_DUMPER_SIZE_V3 _AC(0x3000, UL)
#define STAGE2_DUMPER_SIZE_ZLIB _AC(0x8000, UL)
#define STAGE3_ENTRY _AC(0xa000, UL)
#define STAGE2_LOAD_ADDRESS _AC(0x2000, UL)

View File

@@ -25,6 +25,7 @@
#define STACK_FRAME_OVERHEAD _AC(160, U)
/* Facilities */
#define DFLTCC_FACILITY _AC(151, U)
#define UNPACK_FACILITY _AC(161, U)
#ifndef __ASSEMBLER__

134
include/dump/s390_dump.h Normal file
View File

@@ -0,0 +1,134 @@
/*
* s390 related definitions and functions.
*
* Copyright IBM Corp. 2013, 2023
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#ifndef S390_DUMP_H
#define S390_DUMP_H
#include <stdint.h>
#include "boot/page.h"
#include "lib/zt_common.h"
/*
* S390 dump format defines
*/
#define DF_S390_MAGIC 0xa8190173618f23fdULL
#define DF_S390_MAGIC_EXT 0xa8190173618f23feULL
#define DF_S390_HDR_SIZE 0x1000
#define DF_S390_EM_SIZE 16
#define DF_S390_EM_MAGIC 0x44554d505f454e44ULL
#define DF_S390_EM_STR "DUMP_END"
#define DF_S390_CPU_MAX 512
#define DF_S390_MAGIC_BLK_ECKD 3
/*
* Architecture of dumped system
*/
enum df_s390_arch {
DF_S390_ARCH_32 = 1,
DF_S390_ARCH_64 = 2,
};
/*
* zipl parameters passed at tail of dump tools
*/
struct stage2dump_parm_tail {
char reserved[6];
uint8_t no_compress;
uint8_t mvdump_force;
uint64_t mem_upper_limit;
} __packed;
/*
* s390 dump header format
*/
struct df_s390_hdr {
uint64_t magic; /* 0x000 */
uint32_t version; /* 0x008 */
uint32_t hdr_size; /* 0x00c */
uint32_t dump_level; /* 0x010 */
uint32_t page_size; /* 0x014 */
uint64_t mem_size; /* 0x018 */
uint64_t mem_start; /* 0x020 */
uint64_t mem_end; /* 0x028 */
uint32_t num_pages; /* 0x030 */
uint32_t pad; /* 0x034 */
uint64_t tod; /* 0x038 */
uint64_t cpu_id; /* 0x040 */
uint32_t arch; /* 0x048 */
uint32_t volnr; /* 0x04c */
uint32_t build_arch; /* 0x050 */
uint64_t mem_size_real; /* 0x054 */
uint8_t mvdump; /* 0x05c */
uint16_t cpu_cnt; /* 0x05d */
uint16_t real_cpu_cnt; /* 0x05f */
uint8_t zlib_version_s390; /* 0x061 */
uint32_t zlib_entry_size; /* 0x062 */
uint8_t end_pad1[0x200 - 0x066]; /* 0x066 */
uint64_t mvdump_sign; /* 0x200 */
uint64_t mvdump_zipl_time; /* 0x208 */
uint8_t end_pad2[0x800 - 0x210]; /* 0x210 */
uint32_t lc_vec[DF_S390_CPU_MAX]; /* 0x800 */
} __packed __aligned(16);
/*
* End marker: Should be at the end of every valid s390 crash dump
*/
struct df_s390_em {
union {
uint64_t magic;
char str[8];
};
uint64_t tod;
} __packed __aligned(16);
/*
* Dump segment header
*/
struct df_s390_dump_segm_hdr {
union {
struct {
uint64_t start; /* 0x000 */
uint64_t len; /* 0x008 */
uint64_t stop_marker; /* 0x010 */
/* Size in blocks of compressed dump segment written to disk */
uint32_t size_on_disk; /* 0x018 */
uint8_t reserved_pad[0x30 - 0x1c]; /* 0x01c */
/*
* Number of compressed entries in this dump segment (up to
* 1011 entries)
*/
uint32_t entry_count; /* 0x030 */
/*
* Offsets in blocks to compressed entries written to disk
* from the start of the dump segment.
* High-order bit is set if the entry has been written
* uncompressed.
*/
uint32_t entry_offset[]; /* 0x034 */
} __packed;
uint8_t padding[PAGE_SIZE];
};
};
/* Data compression granularity (size of input data chunk for zlib deflate) */
#define DUMP_SEGM_ZLIB_ENTSIZE (1 * MIB)
/* Maximum number of compressed entries in one dump segment */
#define DUMP_SEGM_ZLIB_MAXENTS ((sizeof(struct df_s390_dump_segm_hdr) \
- offsetof(struct df_s390_dump_segm_hdr, entry_offset)) \
/ sizeof(uint32_t))
/*
* Maximum length of compressed dump segment considering the size of
* a single input chunk
*/
#define DUMP_SEGM_ZLIB_MAXLEN (DUMP_SEGM_ZLIB_MAXENTS * DUMP_SEGM_ZLIB_ENTSIZE)
/* Bitmask to mark uncompressed chunks */
#define DUMP_SEGM_ENTRY_UNCOMPRESSED 0x80000000
#endif /* S390_DUMP_H */

View File

@@ -89,6 +89,7 @@ void ap_list_remove_all(struct util_list *list);
/* Lock Functions */
int ap_get_lock(void);
int ap_get_lock_callout(void);
int ap_try_lock_callout(void);
int ap_release_lock(void);
int ap_release_lock_callout(void);

View File

@@ -23,4 +23,6 @@ int util_lockfile_parent_lock(char *lockfile, int retries);
int util_lockfile_release(char *lockfile);
int util_lockfile_parent_release(char *lockfile);
int util_lockfile_peek_owner(char *lockfile, int *pid);
#endif /** LIB_UTIL_LOCKFILE_H @} */

View File

@@ -722,6 +722,34 @@ int ap_get_lock_callout(void)
return util_lockfile_parent_lock(AP_LOCKFILE, AP_LOCK_RETRIES);
}
/**
* Attempt to acquire the ap config lock using the Parent Process ID without
* waiting/retries. Detect if the attempt was rejected because the lock is
* already held by the Parent Process ID.
*
* @retval 0 Lock acquired on behalf of parent process
* @retval 1 Lock not obtained, already held by parent
* @retval != 0 Lock was not obtained, other error
*/
int ap_try_lock_callout(void)
{
int pid, ppid, rc;
if (util_lockfile_parent_lock(AP_LOCKFILE, 0)) {
/* Lock is already held, let's peek at the owner */
ppid = getppid();
rc = util_lockfile_peek_owner(AP_LOCKFILE, &pid);
if (rc || pid != ppid) {
/* We didn't get the lock, unknown or other owner */
return 2;
}
/* Signify that the lock is already held by the caller */
return 1;
}
return 0;
}
/**
* Release the ap config lock
*

View File

@@ -55,7 +55,7 @@ check-dep-libekmfweb: detect-openssl-version.dep
"detect-openssl-version.dep", \
"openssl-devel version >= 1.1.1", \
"HAVE_OPENSSL=0", \
-I. -lcrypto -DOPENSSL_SUPPRESS_DEPRECATED)
-I. `$(PKG_CONFIG) --cflags --libs libcrypto` -DOPENSSL_SUPPRESS_DEPRECATED)
$(call check_dep, \
"libekmfweb", \
"json-c/json.h", \
@@ -66,7 +66,7 @@ check-dep-libekmfweb: detect-openssl-version.dep
"curl/curl.h", \
"libcurl-devel", \
"HAVE_LIBCURL=0" \
`$(CURL_CONFIG) --cflags` `$(CURL_CONFIG) --libs`)
`$(PKG_CONFIG) --cflags --libs libcurl`)
$(CURL_CONFIG) --ssl-backends | grep OpenSSL >/dev/null 2>&1 || { echo "Error: libcurl is not built with the OpenSSL backend"; exit 1; }
touch check-dep-libekmfweb
@@ -85,8 +85,8 @@ ekmfweb.o: check-dep-libekmfweb ekmfweb.c utilities.h cca.h $(rootdir)include/ek
utilities.o: check-dep-libekmfweb utilities.c utilities.h $(rootdir)include/ekmfweb/ekmfweb.h
cca.o: check-dep-libekmfweb cca.c cca.h utilities.h $(rootdir)include/ekmfweb/ekmfweb.h
libekmfweb.so.$(VERSION): ALL_CFLAGS += -fPIC `$(CURL_CONFIG) --cflags`
libekmfweb.so.$(VERSION): LDLIBS = -ljson-c -lcrypto -lssl `$(CURL_CONFIG) --libs` -ldl
libekmfweb.so.$(VERSION): ALL_CFLAGS += -fPIC `$(PKG_CONFIG) --cflags json-c libcurl libcrypto libssl`
libekmfweb.so.$(VERSION): LDLIBS = `$(PKG_CONFIG) --libs json-c libcurl libcrypto libssl` -ldl
libekmfweb.so.$(VERSION): ALL_LDFLAGS += -shared -Wl,--version-script=libekmfweb.map \
-Wl,-z,defs,-Bsymbolic -Wl,-soname,libekmfweb.so.$(VERM)
libekmfweb.so.$(VERSION): ekmfweb.o utilities.o cca.o $(libs)

View File

@@ -51,7 +51,6 @@ detect-openssl-version.dep:
mv $(TMPFILE) $@
CURL_CONFIG ?= curl-config
XML2_CONFIG ?= xml2-config
check-dep-libkmipclient: detect-openssl-version.dep
$(call check_dep, \
@@ -59,7 +58,7 @@ check-dep-libkmipclient: detect-openssl-version.dep
"detect-openssl-version.dep", \
"openssl-devel version >= 1.1.1", \
"HAVE_OPENSSL=0", \
-I. -lcrypto -DOPENSSL_SUPPRESS_DEPRECATED)
-I. `$(PKG_CONFIG) --cflags --libs libcrypto` -DOPENSSL_SUPPRESS_DEPRECATED)
$(call check_dep, \
"libkmipclient", \
"json-c/json.h", \
@@ -70,13 +69,13 @@ check-dep-libkmipclient: detect-openssl-version.dep
"libxml/tree.h", \
"libxml2-devel", \
"HAVE_LIBXML2=0", \
`$(XML2_CONFIG) --cflags` `$(XML2_CONFIG) --libs`)
`$(PKG_CONFIG) --cflags --libs libxml-2.0`)
$(call check_dep, \
"libkmipclient", \
"curl/curl.h", \
"libcurl-devel", \
"HAVE_LIBCURL=0" \
`$(CURL_CONFIG) --cflags` `$(CURL_CONFIG) --libs`)
`$(PKG_CONFIG) --cflags --libs libcurl`)
$(CURL_CONFIG) --ssl-backends | grep OpenSSL >/dev/null 2>&1 || { echo "Error: libcurl is not built with the OpenSSL backend"; exit 1; }
touch check-dep-libkmipclient
@@ -107,8 +106,8 @@ tls.o: check-dep-libkmipclient tls.c kmip.h utils.h $(rootdir)include/kmipclient
names.o: check-dep-libkmipclient names.c names.h utils.h $(rootdir)include/kmipclient/kmipclient.h
utils.o: check-dep-libkmipclient utils.c names.h utils.h $(rootdir)include/kmipclient/kmipclient.h
libkmipclient.so.$(VERSION): ALL_CFLAGS += -fPIC `$(XML2_CONFIG) --cflags` `$(CURL_CONFIG) --cflags`
libkmipclient.so.$(VERSION): LDLIBS = -ljson-c -lcrypto -lssl `$(XML2_CONFIG) --libs` `$(CURL_CONFIG) --libs`
libkmipclient.so.$(VERSION): ALL_CFLAGS += -fPIC `$(PKG_CONFIG) --cflags json-c libcrypto libssl libxml-2.0 libcurl`
libkmipclient.so.$(VERSION): LDLIBS = `$(PKG_CONFIG) --libs json-c libcrypto libssl libxml-2.0 libcurl`
libkmipclient.so.$(VERSION): ALL_LDFLAGS += -shared -Wl,--version-script=libkmipclient.map \
-Wl,-z,defs,-Bsymbolic -Wl,-soname,libkmipclient.so.$(VERM)
libkmipclient.so.$(VERSION): kmip.o request.o response.o attribute.o key.o ttlv.o json.o \

View File

@@ -16,6 +16,7 @@
#include <openssl/ssl.h>
#include <json-c/json.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <curl/curl.h>

View File

@@ -21,8 +21,7 @@ WARNINGS := -Wall -Wextra -Wshadow \
-Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \
$(NULL)
ALL_CFLAGS += -std=gnu11 \
-DOPENSSL_API_COMPAT=0x10101000L \
ALL_CFLAGS += -DOPENSSL_API_COMPAT=0x10101000L \
$(GLIB2_CFLAGS) \
$(LIBCRYPTO_CFLAGS) \
$(LIBCURL_CFLAGS) \

View File

@@ -311,13 +311,13 @@ int util_file_read_i(int *val, int base, const char *fmt, ...)
return -1;
switch (base) {
case 8:
count = sscanf(buf, "%do", val);
count = sscanf(buf, "%o", val);
break;
case 10:
count = sscanf(buf, "%dd", val);
count = sscanf(buf, "%d", val);
break;
case 16:
count = sscanf(buf, "%dx", val);
count = sscanf(buf, "%x", val);
break;
default:
util_panic("Invalid base: %d\n", base);
@@ -425,13 +425,13 @@ int util_file_read_ui(unsigned int *val, int base, const char *fmt, ...)
return -1;
switch (base) {
case 8:
count = sscanf(buf, "%uo", val);
count = sscanf(buf, "%o", val);
break;
case 10:
count = sscanf(buf, "%uu", val);
count = sscanf(buf, "%u", val);
break;
case 16:
count = sscanf(buf, "%ux", val);
count = sscanf(buf, "%x", val);
break;
default:
util_panic("Invalid base: %d\n", base);

View File

@@ -299,3 +299,35 @@ int util_lockfile_parent_release(char *lockfile)
{
return do_lockfile_release(lockfile, getppid());
}
/**
* Return the pid that owns the specified lockfile.
*
* @param[in] lockfile Path to the lock file
* @param[in,out] pid Buffer to place owning pid
*
* @retval 0 pid provided in buffer
* @retval !=0 Error, no pid provided
*/
int util_lockfile_peek_owner(char *lockfile, int *pid)
{
char buf[BUFSIZE];
int fd, len;
if (!lockfile || !pid)
return UTIL_LOCKFILE_ERR;
/* Open lockfile, read the owning pid if it exists */
fd = open(lockfile, O_RDONLY);
if (fd < 0)
return UTIL_LOCKFILE_ERR;
len = read(fd, buf, sizeof(buf));
close(fd);
if (len <= 0)
return UTIL_LOCKFILE_ERR;
buf[len] = 0;
*pid = atoi(buf);
return 0;
}

View File

@@ -16,7 +16,7 @@
/* we may use header_generic and header_simple_table from the util_funcs module */
config_require(util_funcs)
config_require(util_funcs);
/* function prototypes */

View File

@@ -35,8 +35,7 @@ LIBCRYPTO_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs libcrypto)
LIBCURL_CFLAGS := $(shell $(PKG_CONFIG) --silence-errors --cflags libcurl)
LIBCURL_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs libcurl)
ALL_CFLAGS += -std=gnu11 \
-DPKGDATADIR=$(PKGDATADIR) \
ALL_CFLAGS += -DPKGDATADIR=$(PKGDATADIR) \
-DOPENSSL_API_COMPAT=0x10101000L \
$(GLIB2_CFLAGS) \
$(LIBCRYPTO_CFLAGS) \

3
rust/.gitignore vendored
View File

@@ -9,6 +9,3 @@ target/
# Generated during make build can be removed at any point
.check-dep-pvtools
.check-cargo
# Ignore lock files by default
Cargo.lock

File diff suppressed because it is too large Load Diff

16
rust/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[workspace]
members = [
"pv",
"pv_core",
"pvapconfig",
"pvsecret",
"utils",
]
resolver = "2"
[workspace.package]
edition = "2021"
license = "MIT"
[profile.release]
lto = true

View File

@@ -15,16 +15,17 @@ ifneq (${HAVE_CARGO},0)
BUILD_TARGETS = $(CARGO_TARGETS)
INSTALL_TARGETS := install-rust-tools install-man
CARGO_TEST_TARGETS = $(addsuffix, _test, $(CARGO_TARGETS))
CARGO_TEST_TARGETS = $(addsuffix .test, $(CARGO_TARGETS))
ifneq (${HAVE_OPENSSL},0)
ifneq (${HAVE_LIBCURL},0)
PV_TARGETS := pvsecret
PV_TARGETS := pvsecret pvapconfig
PV_BUILD_TARGETS = $(PV_TARGETS)
CARGO_TEST_TARGETS += $(addsuffix, _test, $(PV_TARGETS)) pv
PV_BUILD_TARGETS := $(PV_TARGETS)
CARGO_TEST_TARGETS += $(addsuffix .test,pv $(PV_TARGETS))
endif #LIBCURL
endif #OPENSSL
TEST_TARGETS := $(addsuffix _build,$(CARGO_TEST_TARGETS))
endif #CARGO
BUILD_TARGETS += $(PV_BUILD_TARGETS)
@@ -36,21 +37,18 @@ endif
# the cc crate uses these variables to compile c code. It does not open a shell
# to call the compiler, so no echo etc. allowed here, just a path to a program
$(BUILD_TARGETS) rust-test: CC = $(CC_SILENT)
$(BUILD_TARGETS) rust-test: AR = $(AR_SILENT)
$(BUILD_TARGETS) $(TEST_TARGETS) rust-test: CC = $(CC_SILENT)
$(BUILD_TARGETS) $(TEST_TARGETS) rust-test: AR = $(AR_SILENT)
$(CARGO_TARGETS): .check-cargo .no-cross-compile
$(CARGO_BUILD) --manifest-path=$@/Cargo.toml $(ALL_CARGOFLAGS)
.PHONY: $(CARGO_TARGETS)
$(PV_TARGETS): .check-dep-pvtools
$(PV_TARGETS) $(CARGO_TARGETS): .check-cargo .no-cross-compile
$(CARGO_BUILD) --bin $@ $(ALL_CARGOFLAGS)
.PHONY: $(PV_TARGETS) $(CARGO_TARGETS)
$(CARGO_TEST_TARGETS): .check-cargo .no-cross-compile
$(CARGO_TEST) --manifest-path=$@/Cargo.toml --all-features $(CARGOFLAGS)
.PHONY: $(CARGO_TEST_TARGETS)
$(PV_TARGETS): .check-cargo .no-cross-compile .check-dep-pvtools
$(CARGO_BUILD) --manifest-path=$@/Cargo.toml $(ALL_CARGOFLAGS)
.PHONY: $(PV_TARGETS)
$(TEST_TARGETS): ALL_CARGOFLAGS += --no-run
$(CARGO_TEST_TARGETS) $(TEST_TARGETS): .check-cargo .no-cross-compile
$(CARGO_TEST) --package $(basename $@) --all-features $(ALL_CARGOFLAGS)
.PHONY: $(TEST_TARGETS) $(CARGO_TEST_TARGETS)
skip-build:
echo " SKIP rust-tools due to unresolved dependencies"
@@ -65,22 +63,17 @@ print-rust-targets:
echo $(BUILD_TARGETS)
clean:
$(foreach target,$(CARGO_TARGETS),\
$(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${ALL_CARGOFLAGS} ;)
$(foreach target,$(PV_TARGETS),\
$(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${CARGOFLAGS} ;)
$(CARGO_CLEAN) ${ALL_CARGOFLAGS}
$(RM) -- .check-dep-pvtools .detect-openssl.dep.c .check-cargo
rust-test: .check-cargo .no-cross-compile
$(foreach target,$(CARGO_TEST_TARGETS),\
$(CARGO_TEST) --manifest-path=$(target)/Cargo.toml --all-features ${ALL_CARGOFLAGS} ;)
rust-test: $(CARGO_TEST_TARGETS)
install-rust-tools: $(BUILD_TARGETS)
$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
$(foreach target,$(CARGO_TARGETS),\
$(INSTALL) $(target)/target/release/$(target) $(DESTDIR)$(USRBINDIR);)
$(INSTALL) target/release/$(target) $(DESTDIR)$(USRBINDIR);)
$(foreach target,$(PV_TARGETS),\
$(INSTALL) $(target)/target/release/$(target) $(DESTDIR)$(USRBINDIR);)
$(INSTALL) target/release/$(target) $(DESTDIR)$(USRBINDIR);)
install-man:
$(foreach target,$(CARGO_TARGETS),\
@@ -118,13 +111,13 @@ endif
.check-dep-pvtools: .detect-openssl.dep.c
$(call check_dep, \
"$(BIN_PROGRAM)", \
"Rust-pv", \
$^, \
"openssl-devel / libssl-dev version >= 1.1.1", \
"HAVE_OPENSSL=0", \
"-I.")
$(call check_dep, \
"$(BIN_PROGRAM)", \
"Rust-pv", \
"curl/curl.h", \
"libcurl-devel", \
"HAVE_LIBCURL=0")

View File

@@ -7,34 +7,36 @@ https://www.rust-lang.org/learn/get-started
## Building rust code
### s390-tools build system
If `cargo` is installed a simple `make` should do the job. Note that,
compiling rust programs take significaltly longer than C code. To closely
monitor the prgress use `make V=1` By default release builds are made.
compiling rust programs take significantly longer than C code. To closely
monitor the progress use `make V=1` By default release builds are made.
With `make CARGOFLAGS=<flags>` one can pass additional flags to cargo.
With `make HAVE_CARGO=0` one can turn of any compilation that requires cargo.
With `make CARGO=<...>` one can set the cargo binary
### cargo
If you need to run cargo directly, cd to each project you want to build and
If you need to run cargo directly, `cd` to each project you want to build and
issue your cargo commands. Do **NOT** forget to specify `--release` if you are
building tools for a release. The s390-tools expect the environment variable
`S390_TOOLS_RELEASE` to be present at build time. This is the version string the
rust tools provide.
Tpp: You can use `make version` to get the version string.
Tip: You can use `make version` to get the version string.
## Internal Libraries
* __utils__ _Library for rust tools that bundles common stuff for the 390-tools_
* currently only provides a macro to get the `S390_TOOLS_RELEASE` string
* provides a macro to get the `S390_TOOLS_RELEASE` string
* provides macros for compile time assertions
* __pv_core__ _Library for pv tools, providing uvdevice access and utilities to send, receive and interpret various UV-calls._
* __pv__ _Library for pv tools, providing uvdevice access, encryption utilities, and utilities for generating UV-request_
* requires openssl and libcurl for the feature `request`; use `HAVE_<OPENSSL|CURL>=0` to
disable build that use pv with the request feature.
* requires openssl and libcurl
* reexports ann symbols from __pv_core__
* if no encryption utilities required, use __pv_core__
## Tools
* __pvsecret__ _Manage secrets for IBM Secure Execution guests_
* requires pv with the `request` feature
## Writing new tools
We encourage to use Rust for new tools. However, for some use cases it makes
@@ -43,7 +45,7 @@ Exiting tools may be rewritten in Rust.
### What (third-party) crates can be used for s390-tools?
A huge list of libraries are made available through Rusts' ecosystem and is one
of many upsides. However, just like with Coding Style Guidlines, it is
of many upsides. However, just like with Coding Style Guidelines, it is
important to limit the usage of those libraries so that within a project,
everyone is on the same page and that code written in Rust uses similar
approaches. It makes it easier for code review and maintainability in general.
@@ -81,11 +83,27 @@ is a start, but can change over time.
Dependencies used by the crates listed above can be used, too.
### Add new tool
To add a new tool issue `cargo new <TOOLNAME>` in the `rust` directory.
To add a new tool issue `cargo new $TOOLNAME` in the `rust` directory.
Add the tool to the _s390-tools_ build system:
```Makefile
CARGO_TARGETS := TOOLNAME
CARGO_TARGETS := $TOOLNAME
```
Add the library to the _s390-tools_ test list:
```Makefile
CARGO_TEST_TARGETS := $LIBNAME
```
Add the tool/library to the cargo workspace:
```toml
[workspace]
members = [
"pv",
"pvsecret",
"$TOOLNAME",
"$LIBNAME"
"utils",
]
```
### Versions
@@ -98,8 +116,8 @@ use utils::release_string;
fn print_version() {
println!(
"{} version {}\nCopyright IBM Corp. 2023",
env!("CARGO_PKG_NAME"), // collapes into the crates name
release_string!() // this (very likely) collapes into a compile time constant
env!("CARGO_PKG_NAME"), // collapses into the crates name
release_string!() // this (very likely) collapses into a compile time constant
);
}
```

View File

@@ -1,36 +1,24 @@
[package]
name = "pv"
version = "0.9.0"
edition = "2021"
license = "MIT"
version = "1.0.0"
edition.workspace = true
license.workspace = true
[dependencies]
libc = "0.2"
log = { version = "0.4", features = ["std", "release_max_level_debug"] }
thiserror = "1"
zerocopy = "0.6"
cfg-if = "1.0.0"
byteorder = "1.3"
clap = { version ="4", features = ["derive", "wrap_help"] }
curl = "0.4.7"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
openssl = "0.10.49"
serde = { version = "1.0.139", features = ["derive"] }
thiserror = "1.0.33"
utils = {path = "../utils"}
zerocopy = { version="0.7", features = ["derive"] }
# dependencies for request feature
clap = { version ="4", features = ["derive", "wrap_help"], optional = true }
curl = { version ="0.4", optional = true }
openssl = {version = "0.10", optional = true }
openssl_extensions = { path = "openssl_extensions", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
# misc optional dependencies
byteorder = {version = "1", optional = true }
openssl_extensions = { path = "openssl_extensions" }
pv_core = { path = "../pv_core" }
[dev-dependencies]
mockito = {version = "0.31", default-features = false }
mockito = {version = "1", default-features = false }
once_cell = "1.19"
serde_test = "1"
lazy_static = "1"
[features]
default = []
request = ["dep:openssl", "dep:curl", "dep:openssl_extensions", "dep:serde", "dep:clap"]
uvsecret = ["dep:byteorder", "dep:serde"]
[profile.release]
lto = true
panic = "abort" # release builds now do not clean up stack after panics. .1 Mb

View File

@@ -1,12 +1,12 @@
[package]
name = "openssl_extensions"
version = "0.1.0"
edition = "2021"
license = "MIT"
edition.workspace = true
license.workspace = true
[dependencies]
foreign-types = "0.3"
libc = {version = "0.2", features = [ "extra_traits"] }
log = { version = "0.4", features = ["std", "release_max_level_debug"] }
openssl = "0.10"
openssl-sys = "0.9"
foreign-types = "0.3.1"
libc = {version = "0.2.49", features = [ "extra_traits"] }
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
openssl = "0.10.49"
openssl-sys = "0.9.85"

View File

@@ -5,15 +5,15 @@
use std::fmt;
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
use libc::c_int;
use openssl::x509::{X509CrlRef, X509Ref};
use std::ffi::c_int;
mod ffi {
extern "C" {
pub fn X509_check_akid(
issuer: *const openssl_sys::X509,
akid: *const openssl_sys::AUTHORITY_KEYID,
) -> ::libc::c_int;
) -> super::c_int;
}
}

View File

@@ -30,7 +30,7 @@ mod ffi {
pub fn X509_STORE_add_crl(
xs: *mut openssl_sys::X509_STORE,
x: *mut openssl_sys::X509_CRL,
) -> libc::c_int;
) -> std::ffi::c_int;
}
}

View File

@@ -5,13 +5,13 @@
use std::{marker::PhantomData, ptr};
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::c_int;
use openssl::{
error::ErrorStack,
stack::Stackable,
x509::{X509Crl, X509CrlRef},
};
use openssl_sys::BIO_new_mem_buf;
use std::ffi::c_int;
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);

View File

@@ -8,11 +8,10 @@ use std::{
};
// (SE) boot request control block aka SE header
use crate::{
assert_size, request::MagicValue, requires_feat, static_assert, Error, Result, PAGESIZE,
};
use crate::{assert_size, static_assert, Error, Result, PAGESIZE};
use log::debug;
use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64};
use pv_core::request::MagicValue;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32, U64};
/// Struct containing all SE-header tags.
///
@@ -22,7 +21,6 @@ use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64};
/// Tweak List Digest (tld)
/// SE Header Tag (seht)
///
#[doc = requires_feat!(request)]
#[repr(C)]
#[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq)]
pub struct BootHdrTags {
@@ -144,7 +142,7 @@ impl BootHdrTags {
}
#[repr(C)]
#[derive(Debug, Clone, FromBytes)]
#[derive(Debug, Clone, FromBytes, FromZeroes)]
struct BootHdrHead {
magic: U64<BigEndian>,
version: U32<BigEndian>,
@@ -205,7 +203,7 @@ mod tests {
#[test]
fn from_se_image_hdr() {
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(bin_hdr.clone())).unwrap();
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(*bin_hdr)).unwrap();
assert_eq!(hdr_tags, EXP_HDR);
}
@@ -220,7 +218,7 @@ mod tests {
));
// mess up magic
let mut bin_hdr_copy = bin_hdr.clone();
let mut bin_hdr_copy = *bin_hdr;
bin_hdr_copy.swap(0, 1);
assert!(matches!(
BootHdrTags::from_se_image(&mut Cursor::new(bin_hdr_copy)),

View File

@@ -2,14 +2,12 @@
//
// Copyright IBM Corp. 2023
use crate::misc::{create_file, open_file};
use crate::Result;
use crate::{create_buffered_file, open_buffered_file};
use clap::{ArgGroup, Args, ValueHint};
use std::io::{Read, Write};
/// CLI Argument collection for handling certificates.
///
#[doc = requires_feat!(request)]
#[derive(Args, Debug, PartialEq, Eq, Default)]
#[command(
group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])),
@@ -32,15 +30,15 @@ pub struct CertificateOptions {
/// Disable the host-key document verification.
///
/// Does not require the host-key documents to be valid.
/// Do not use for a production request unless you verified the host-key document before.
/// Do not use for a production request unless you verified the host-key document beforehand.
#[arg(long)]
pub no_verify: bool,
/// Use FILE as a certificate to verify the host-key(s).
/// Use FILE as a certificate to verify the host key or keys.
///
/// The certificates are used to establish a chain of trust for the verification
/// of the host-key documents. Specify this option twice to specify the IBM Z signing key and
/// the intermediate CA certificate (signed by the rootCA).
/// the intermediate CA certificate (signed by the root CA).
#[arg(
short= 'C',
long = "cert",
@@ -54,7 +52,7 @@ pub struct CertificateOptions {
/// Use FILE as a certificate revocation list.
///
/// That list is used to check whether a certificate of the chain of
/// The list is used to check whether a certificate of the chain of
/// trust is revoked. Specify this option multiple times to use multiple CRLs.
#[arg(
long = "crl",
@@ -72,7 +70,7 @@ pub struct CertificateOptions {
/// Use FILE as the root-CA certificate for the verification.
///
/// If omitted, the system wide root CAs installed on the system are used.
/// If omitted, the system wide-root CAs installed on the system are used.
/// Use this only if you trust the specified certificate.
#[arg(long, requires("certs"))]
pub root_ca: Option<String>,
@@ -104,37 +102,25 @@ impl CertificateOptions {
}
/// stdout
#[cfg(feature = "request")]
pub const STDOUT: &str = "-";
/// stdin
#[cfg(feature = "request")]
pub const STDIN: &str = "-";
/// Converts an argument value into a Writer.
///
/// # Errors
/// No Error will occur but function must match a signature
///
#[cfg(feature = "request")]
pub fn get_writer_from_cli_file_arg(path: &str) -> Result<Box<dyn Write>> {
if path == STDOUT {
Ok(Box::new(std::io::stdout()))
} else {
Ok(Box::new(create_buffered_file!(path)))
Ok(Box::new(create_file(path)?))
}
}
/// Converts an argument value into a Reader.
///
/// # Errors
/// No Error will occur but function must match a signature
///
#[cfg(feature = "request")]
pub fn get_reader_from_cli_file_arg(path: &str) -> Result<Box<dyn Read>> {
if path == STDIN {
Ok(Box::new(std::io::stdin()))
} else {
Ok(Box::new(open_buffered_file!(path)))
Ok(Box::new(open_file(path)?))
}
}

View File

@@ -2,29 +2,29 @@
//
// Copyright IBM Corp. 2023
use crate::requires_feat;
use crate::{error::Result, secret::Secret, Error};
use openssl::rand::rand_bytes;
use openssl::{
derive::Deriver,
ec::{EcGroup, EcKey},
hash::{DigestBytes, MessageDigest},
md::MdRef,
nid::Nid,
pkey::{Id, PKey, Private, Public},
pkey::{HasPublic, Id, PKey, PKeyRef, Private, Public},
pkey_ctx::{HkdfMode, PkeyCtx},
symm::{encrypt, encrypt_aead, Cipher},
rand::rand_bytes,
rsa::Padding,
sign::{Signer, Verifier},
symm::{encrypt_aead, Cipher},
};
use std::convert::TryInto;
use std::{convert::TryInto, ops::Range};
/// An AES256-key that will purge itself out of the memory when going out of scope
///
#[doc = requires_feat!(request)]
pub type Aes256Key = Secret<[u8; 32]>;
pub(crate) const AES_256_GCM_TAG_SIZE: usize = 16;
/// Types of symmetric keys, to specify during construction.
///
#[doc = requires_feat!(request)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymKeyType {
@@ -34,7 +34,6 @@ pub enum SymKeyType {
/// Types of symmetric keys
///
#[doc = requires_feat!(request)]
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SymKey {
@@ -88,7 +87,6 @@ impl From<Aes256Key> for SymKey {
/// Performs an hkdf according to RFC 5869.
/// See [`OpenSSL HKDF`]()
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an OpenSSL error if the key could not be generated.
@@ -113,7 +111,6 @@ pub fn hkdf_rfc_5869<const COUNT: usize>(
/// Derive a symmetric key from a private and a public key.
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if something went bad in OpenSSL.
@@ -132,7 +129,6 @@ pub fn derive_key(k1: &PKey<Private>, k2: &PKey<Public>) -> Result<Aes256Key> {
/// Generate a random array.
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if the entropy source fails or is not available.
@@ -144,7 +140,6 @@ pub fn random_array<const COUNT: usize>() -> Result<[u8; COUNT]> {
/// Generate a new random EC-SECP521R1 key.
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if the key could not be generated by OpenSSL.
@@ -154,24 +149,6 @@ pub fn gen_ec_key() -> Result<PKey<Private>> {
PKey::from_ec_key(key).map_err(Error::Crypto)
}
/// Encrypt confidential Data with a symmetric key.
///
/// * `key` - symmetric key used for encryption
/// * `iv` - initialisation vector
/// * `conf` - data to be encrypted
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if the data could not be encrypted by OpenSSL.
pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
match key {
SymKey::Aes256(key) => {
encrypt(Cipher::aes_256_gcm(), key.value(), Some(iv), conf).map_err(Error::Crypto)
}
}
}
/// Encrypt confidential Data with a symmetric key and provida a gcm tag.
///
/// * `key` - symmetric key used for encryption
@@ -179,7 +156,6 @@ pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
/// * `aad` - additional authentic data
/// * `conf` - data to be encrypted
///
#[doc = requires_feat!(request)]
/// # Returns
/// [`Vec<u8>`] with the following content:
/// 1. `aad`
@@ -189,8 +165,13 @@ pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
/// # Errors
///
/// This function will return an error if the data could not be encrypted by OpenSSL.
pub fn encrypt_aes_gcm(key: &SymKey, iv: &[u8], aad: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
let mut tag = vec![0xff; 16];
pub fn encrypt_aes_gcm(
key: &SymKey,
iv: &[u8],
aad: &[u8],
conf: &[u8],
) -> Result<(Vec<u8>, Range<usize>, Range<usize>, Range<usize>)> {
let mut tag = vec![0xff; AES_256_GCM_TAG_SIZE];
let encr = match key {
SymKey::Aes256(key) => encrypt_aead(
Cipher::aes_256_gcm(),
@@ -202,17 +183,28 @@ pub fn encrypt_aes_gcm(key: &SymKey, iv: &[u8], aad: &[u8], conf: &[u8]) -> Resu
)?,
};
let mut res = vec![0; aad.len() + encr.len() + 16];
res[0..aad.len()].copy_from_slice(aad);
res[aad.len()..aad.len() + encr.len()].copy_from_slice(&encr);
res[aad.len() + encr.len()..aad.len() + encr.len() + 16].copy_from_slice(&tag);
let mut res = vec![0; aad.len() + encr.len() + tag.len()];
let aad_range = Range {
start: 0,
end: aad.len(),
};
let encr_range = Range {
start: aad.len(),
end: aad.len() + encr.len(),
};
let tag_range = Range {
start: aad.len() + encr.len(),
end: aad.len() + encr.len() + tag.len(),
};
Ok(res)
res[aad_range.clone()].copy_from_slice(aad);
res[encr_range.clone()].copy_from_slice(&encr);
res[tag_range.clone()].copy_from_slice(&tag);
Ok((res, aad_range, encr_range, tag_range))
}
/// Calculate the hash of a slice.
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if OpenSSL could not compute the hash.
@@ -220,10 +212,100 @@ pub fn hash(t: MessageDigest, data: &[u8]) -> Result<DigestBytes> {
openssl::hash::hash(t, data).map_err(Error::Crypto)
}
/// Calculate a digital signature scheme.
///
/// Calculates the digital signature of the provided message using the signing key. [`Id::EC`],
/// and [`Id::RSA`] keys are supported. For [`Id::RSA`] [`Padding::PKCS1_PSS`] is used.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not compute the signature.
pub fn sign_msg(skey: &PKeyRef<Private>, dgst: MessageDigest, msg: &[u8]) -> Result<Vec<u8>> {
match skey.id() {
Id::EC => {
let mut sgn = Signer::new(dgst, skey)?;
sgn.sign_oneshot_to_vec(msg).map_err(Error::Crypto)
}
Id::RSA => {
let mut sgn = Signer::new(dgst, skey)?;
sgn.set_rsa_padding(Padding::PKCS1_PSS)?;
sgn.sign_oneshot_to_vec(msg).map_err(Error::Crypto)
}
_ => Err(Error::UnsupportedSigningKey),
}
}
/// Verify the digital signature of a message.
///
/// Verifies the digital signature of the provided message using the signing key.
/// [`Id::EC`] and [`Id::RSA`] keys are supported. For [`Id::RSA`] [`Padding::PKCS1_PSS`] is used.
///
/// # Returns
/// true if signature could be verified, false otherwise
///
/// # Errors
///
/// This function will return an error if OpenSSL could not compute the signature.
pub fn verify_signature<T: HasPublic>(
skey: &PKeyRef<T>,
dgst: MessageDigest,
msg: &[u8],
sign: &[u8],
) -> Result<bool> {
match skey.id() {
Id::EC => {
let mut ctx = Verifier::new(dgst, skey)?;
ctx.update(msg)?;
ctx.verify(sign).map_err(Error::Crypto)
}
Id::RSA => {
let mut ctx = Verifier::new(dgst, skey)?;
ctx.set_rsa_padding(Padding::PKCS1_PSS)?;
ctx.verify_oneshot(sign, msg).map_err(Error::Crypto)
}
_ => Err(Error::UnsupportedVerificationKey),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
use crate::{get_test_asset, test_utils::*};
#[test]
fn sign_ec() {
let (ec_key, _) = get_test_keys();
let data = "sample".as_bytes();
let sign = sign_msg(&ec_key, MessageDigest::sha512(), data).unwrap();
assert!(sign.len() <= 139 && sign.len() >= 137);
assert!(verify_signature(&ec_key, MessageDigest::sha512(), data, &sign).unwrap());
}
#[test]
fn sign_rsa_2048() {
let keypair = get_test_asset!("keys/rsa2048key.pem");
let keypair = PKey::private_key_from_pem(keypair).unwrap();
let data = "sample".as_bytes();
let sign = sign_msg(&keypair, MessageDigest::sha512(), data).unwrap();
assert_eq!(256, sign.len());
assert!(verify_signature(&keypair, MessageDigest::sha512(), data, &sign).unwrap());
}
#[test]
fn sign_rsa_3072() {
let keypair = get_test_asset!("keys/rsa3072key.pem");
let keypair = PKey::private_key_from_pem(keypair).unwrap();
let data = "sample".as_bytes();
let sign = sign_msg(&keypair, MessageDigest::sha512(), data).unwrap();
assert_eq!(384, sign.len());
assert!(verify_signature(&keypair, MessageDigest::sha512(), data, &sign).unwrap());
}
#[test]
fn derive_key() {
@@ -285,7 +367,7 @@ mod tests {
0xee, 0x62, 0x98, 0xf7, 0x7e, 0x0c,
];
let res = encrypt_aes_gcm(
let (res, ..) = encrypt_aes_gcm(
&SymKey::Aes256(aes_gcm_key.into()),
&aes_gcm_iv,
&aes_gcm_aad,
@@ -294,32 +376,4 @@ mod tests {
.unwrap();
assert_eq!(res, aes_gcm_res);
}
#[test]
fn encrypt_aes_256() {
let aes_gcm_key = [
0xee, 0xbc, 0x1f, 0x57, 0x48, 0x7f, 0x51, 0x92, 0x1c, 0x04, 0x65, 0x66, 0x5f, 0x8a,
0xe6, 0xd1, 0x65, 0x8b, 0xb2, 0x6d, 0xe6, 0xf8, 0xa0, 0x69, 0xa3, 0x52, 0x02, 0x93,
0xa5, 0x72, 0x07, 0x8f,
];
let aes_gcm_iv = [
0x99, 0xaa, 0x3e, 0x68, 0xed, 0x81, 0x73, 0xa0, 0xee, 0xd0, 0x66, 0x84,
];
let aes_gcm_plain = [
0xf5, 0x6e, 0x87, 0x05, 0x5b, 0xc3, 0x2d, 0x0e, 0xeb, 0x31, 0xb2, 0xea, 0xcc, 0x2b,
0xf2, 0xa5,
];
let aes_gcm_res = vec![
0xf7, 0x26, 0x44, 0x13, 0xa8, 0x4c, 0x0e, 0x7c, 0xd5, 0x36, 0x86, 0x7e, 0xb9, 0xf2,
0x17, 0x36,
];
let res = encrypt_aes(
&&SymKey::Aes256(aes_gcm_key.into()),
&aes_gcm_iv,
&aes_gcm_plain,
)
.unwrap();
assert_eq!(res, aes_gcm_res);
}
}

View File

@@ -10,49 +10,19 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[cfg_attr(debug_assertions, error("Ultravisor: '{msg}' ({rc:#06x},{rrc:#06x})"))]
#[cfg_attr(not(debug_assertions), error("Ultravisor: '{msg}' ({rc:#06x})"))]
Uv {
rc: u16,
rrc: u16,
msg: &'static str,
},
#[error("Invalid SE header provided")]
#[cfg(feature = "request")]
InvBootHdr,
#[error("{0}")]
Specification(String),
#[error("Cannot {ty} {ctx} at `{path}`")]
FileIo {
ty: FileIoErrorType,
ctx: String,
path: String,
source: std::io::Error,
},
#[error("Cannot {ty} `{path}`")]
FileAccess {
ty: FileAccessErrorType,
path: String,
source: std::io::Error,
},
#[error("Host-key verification failed: {0}")]
#[cfg(feature = "request")]
HkdVerify(HkdVerifyErrorType),
#[error("No host-key provided")]
#[cfg(feature = "request")]
NoHostkey,
#[error("To many host-keys provided")]
#[cfg(feature = "request")]
#[error("Too many host-keys provided")]
ManyHostkeys,
#[error("Cannot load {ty} from {path}")]
#[cfg(feature = "request")]
X509Load {
path: String,
ty: &'static str,
@@ -60,77 +30,67 @@ pub enum Error {
},
#[error("Internal (unexpected) error: {0}, caused by {1}")]
#[cfg(feature = "request")]
InternalSsl(&'static str, #[source] openssl::error::ErrorStack),
#[error("Signing is only supported for EC and RSA keys")]
UnsupportedSigningKey,
#[error("Verifying signatures is only supported for EC and RSA keys")]
UnsupportedVerificationKey,
#[error("Provided binary request is too small")]
BinRequestSmall,
#[error("No Config UID found: {0}")]
NoCuid(String),
// errors from request types
#[cfg(feature = "uvsecret")]
#[error("Customer Communication Key must be 32 bytes long")]
CckSize,
#[cfg(feature = "uvsecret")]
#[error("Cannot encode secrets (Too many secrets)")]
ManySecrets,
#[error("Invalid {0} user-data for signing provided. Max {} bytes allowed", .0.max())]
AsrcbInvSgnUserData(UserDataType),
#[cfg(feature = "uvsecret")]
#[error("Cannot decode secret list")]
InvSecretList(#[source] std::io::Error),
#[error("Unsupported user data signing key provided. Only EC(secp521r1) and RSA(2048 & 3072 bit) are supported")]
BinAsrcbUnsupportedUserDataSgnKey,
#[cfg(feature = "uvsecret")]
#[error("Input does not contain an add-secret request")]
NoAsrcb,
#[error("No user-key for verification provided and user-data is signed")]
BinAsrcbNoUserDataSgnKey,
#[error("Input does not contain an add-secret request version 1")]
BinAsrcbInvVersion,
#[error("Provided user-data key type ({key}) does not match with the user-data ({kind})")]
AsrcbUserDataKeyMismatch { key: String, kind: UserDataType },
#[error(
"The user-defined request signature could not be verified with the provided certificate"
)]
AsrcbUserDataSgnFail,
// errors from other crates
#[error(transparent)]
PvCore(#[from] pv_core::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
#[cfg(feature = "request")]
Crypto(#[from] openssl::error::ErrorStack),
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
#[cfg(feature = "request")]
#[error(transparent)]
Curl(#[from] curl::Error),
}
// used in macros
#[doc(hidden)]
impl Error {
pub const CRL: &str = "CRL";
pub const CERT: &str = "certificate";
}
/// Error cases for I/O operations
#[allow(missing_docs)]
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum FileIoErrorType {
#[error("read")]
Read,
#[error("write")]
Write,
}
/// Error cases for accessing files
#[allow(missing_docs)]
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum FileAccessErrorType {
#[error("open")]
Open,
#[error("create")]
Create,
pub const CRL: &'static str = "CRL";
pub const CERT: &'static str = "certificate";
}
/// Error cases for verifying host-key documents
///
#[doc = crate::requires_feat!(request)]
#[allow(missing_docs)]
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[cfg(feature = "request")]
pub enum HkdVerifyErrorType {
#[error("Signature verification failed")]
Signature,
@@ -160,37 +120,11 @@ pub enum HkdVerifyErrorType {
IbmSignInvalid(#[source] openssl::x509::X509VerifyResult, u32),
}
macro_rules! path_to_str {
($path: expr) => {
$path.as_ref().to_str().unwrap_or("no UTF-8 path")
};
}
pub(crate) use path_to_str;
macro_rules! file_error {
($ty: tt, $ctx: expr, $path:expr, $src: expr) => {
$crate::Error::FileIo {
ty: $crate::FileIoErrorType::$ty,
ctx: $ctx.to_string(),
path: $path.to_string(),
source: $src,
}
};
}
pub(crate) use file_error;
#[cfg(feature = "request")]
macro_rules! bail_hkd_verify {
($var: tt) => {
return Err($crate::Error::HkdVerify($crate::HkdVerifyErrorType::$var))
};
}
#[cfg(feature = "request")]
pub(crate) use bail_hkd_verify;
macro_rules! bail_spec {
($str: expr) => {
return Err($crate::Error::Specification($str.to_string()))
};
}
pub(crate) use bail_spec;
use crate::request::uvsecret::UserDataType;

View File

@@ -2,7 +2,6 @@
//
// Copyright IBM Corp. 2023
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
#![deny(missing_docs)]
//! pv - library for pv-tools
//!
@@ -11,155 +10,85 @@
//! `pv` provides abstraction layers for encryption, secure memory management,
//! logging, and accessing the uvdevice.
//!
//! ## Feature Flags
//! The following feature flags are available:
//! - `request`
//! - optional
//! - Enables generation of UV requests
//! - `uvsecret`
//! - optional
//! - Enables support for the UV Secret API.
//! If you do not need any OpenSSL features use `pv_core`.
//! This crate reexports all symbols from `pv_core`
mod brcb;
mod cli;
mod crypto;
mod error;
mod log;
mod req;
mod secret;
mod utils;
mod uvdevice;
mod uvsecret;
mod verify;
/// Internal macro to conveninetly document required features on items
// #[macro_export]
/// utility functions for writing TESTS!!!
//hide any test helpers on docs!
#[doc(hidden)]
macro_rules! requires_feat {
(request) => {
" Requires the feature `request`"
};
(uvsecret) => {
" Requires the feature `uvsecret`"
};
(reqsecret) => {
"Requires the features `request` & `uvsecret`"
};
}
#[allow(unused_imports)]
use requires_feat;
//only some features need this
#[allow(dead_code)]
pub mod test_utils;
pub use ::utils::assert_size;
pub use ::utils::static_assert;
const PAGESIZE: usize = 0x1000;
cfg_if::cfg_if! {
if #[cfg(feature = "request")] {
mod brcb;
mod cli;
mod crypto;
mod req;
mod secret;
mod uvsecret;
mod verify;
/// utility functions for writing TESTS!!!
#[allow(dead_code)]
//hide any test helpers on docs!
#[doc(hidden)]
pub mod test_utils;
}
}
/// Definitions and functions for interacting with the Ultravisor
pub mod uv {
pub use crate::uvdevice::{
pub use pv_core::uv::{
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
};
#[cfg(feature = "uvsecret")]
pub use crate::uvsecret::{
secret_list::SecretList,
uvc::{AddCmd, ListCmd, LockCmd},
};
pub use pv_core::uv::{AddCmd, ListCmd, LockCmd};
pub use pv_core::uv::{ListableSecretType, SecretEntry, SecretList};
}
/// Miscellaneous functions and definitions
pub mod misc {
#[cfg(feature = "request")]
pub use crate::cli::{
get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, CertificateOptions, STDIN,
STDOUT,
};
pub use crate::log::PvLogger;
pub use crate::utils::{
memeq, parse_hex, pv_guest_bit_set, read, read_exact_file, read_file, to_u16, to_u32,
try_parse_u128, try_parse_u64, write, write_file, Flags, Lsb0Flags64, Msb0Flags64,
};
#[cfg(feature = "request")]
pub use crate::utils::{read_certs, read_crls};
pub use crate::utils::{read_certs, read_crls, read_private_key};
pub use pv_core::misc::*;
pub use pv_core::PvLogger;
}
#[cfg(feature = "request")]
pub use crate::error::HkdVerifyErrorType;
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
pub use error::{Error, Result};
/// Functionalities to build UV requests
#[doc = requires_feat!(request)]
pub mod request {
pub use crate::brcb::{BootHdrMagic, BootHdrTags};
pub use crate::crypto::derive_key;
pub use crate::crypto::random_array;
pub use crate::crypto::{encrypt_aes_gcm, gen_ec_key};
pub use crate::crypto::{hash, hkdf_rfc_5869};
pub use crate::crypto::{sign_msg, verify_signature};
pub use crate::crypto::{Aes256Key, SymKey, SymKeyType};
pub use crate::req::{Aad, BinReqValues, Encrypt, Keyslot, ReqEncrCtx, Request};
pub use crate::secret::{Secret, Zeroize};
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
cfg_if::cfg_if! {
if #[cfg(feature = "request")] {
pub use crate::brcb::{BootHdrTags, BootHdrMagic};
pub use crate::crypto::{
derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, hash, hkdf_rfc_5869,
random_array, Aes256Key, SymKey, SymKeyType,
};
pub use crate::req::{Aad, Encrypt, Keyslot, ReqEncrCtx, Request};
pub use crate::secret::{Secret, Zeroize};
pub use crate::verify::HkdVerifier;
/// Reexports some useful OpenSSL symbols
///
#[doc = requires_feat!(request)]
pub mod openssl {
pub use openssl::error::ErrorStack;
pub use openssl::hash::MessageDigest;
pub use openssl::md::Md;
pub use openssl::pkey;
}
}
/// Reexports some useful OpenSSL symbols
pub mod openssl {
pub use openssl::error::ErrorStack;
pub use openssl::hash::MessageDigest;
pub use openssl::md::Md;
pub use openssl::pkey;
}
cfg_if::cfg_if! {
if #[cfg(feature = "uvsecret")] {
/// Functionalities for creating add-secret requests
pub mod uvsecret {
#[cfg(feature = "request")]
pub use crate::uvsecret::{
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion,},
ext_secret::ExtSecret,
guest_secret::GuestSecret,
};
pub use crate::uvsecret::AddSecretMagic;
pub use crate::uvsecret::UserDataType;
}
}
}
/// Version number of the request in system-endian
pub type RequestVersion = u32;
/// Request magic value
///
/// The first 8 byte of a request providing an identifier of the request type
/// for programs
pub type RequestMagic = [u8; 8];
/// A `MagicValue` is a bytepattern, that indicates if a byte slice contains the specified
/// (binary) data.
pub trait MagicValue<const N: usize> {
/// Magic value as byte array
const MAGIC: [u8; N];
/// Test whether the given slice starts with the magic value.
fn starts_with_magic(v: &[u8]) -> bool {
if v.len() < Self::MAGIC.len() {
return false;
}
crate::misc::memeq(&v[..Self::MAGIC.len()], &Self::MAGIC)
}
/// Functionalities for creating add-secret requests
pub mod uvsecret {
pub use crate::uvsecret::{
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
ext_secret::ExtSecret,
guest_secret::GuestSecret,
user_data::verify_asrcb_and_get_user_data,
};
pub use pv_core::request::uvsecret::AddSecretMagic;
pub use pv_core::request::uvsecret::UserDataType;
}
pub use pv_core::request::RequestMagic;
}
/// Provides cargo version Info about this crate.
@@ -168,39 +97,3 @@ pub mod request {
pub const fn crate_info() -> &'static str {
concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION"))
}
#[doc(hidden)]
#[macro_export]
macro_rules! file_acc_error {
($ty: tt, $path:expr, $src: expr) => {
$crate::Error::FileAccess {
ty: $crate::FileAccessErrorType::$ty,
path: $path.to_string(),
source: $src,
}
};
}
#[macro_export]
/// Create a file wrapped in a [BufWriter]
///
/// [BufWriter]: std::io#BufWriter
macro_rules! create_buffered_file {
($path: expr) => {
std::io::BufWriter::new(
std::fs::File::create($path).map_err(|e| $crate::file_acc_error!(Create, $path, e))?,
)
};
}
#[macro_export]
/// Open a file wrapped in a [BufReader]
///
/// [BufReader]: std::io#BufReader
macro_rules! open_buffered_file {
($path: expr) => {
std::io::BufReader::new(
std::fs::File::open($path).map_err(|e| $crate::file_acc_error!(Open, $path, e))?,
)
};
}

View File

@@ -2,19 +2,21 @@
//
// Copyright IBM Corp. 2023
use crate::crypto::AES_256_GCM_TAG_SIZE;
use crate::misc::to_u32;
use crate::request::{
derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, random_array, RequestMagic,
RequestVersion, SymKey, SymKeyType,
};
use crate::request::{derive_key, encrypt_aes_gcm, gen_ec_key, random_array, SymKey, SymKeyType};
use crate::{Error, Result};
use openssl::bn::{BigNum, BigNumContext};
use openssl::ec::{EcGroupRef, EcPointRef};
use openssl::error::ErrorStack;
use openssl::hash::{hash, MessageDigest};
use openssl::pkey::{PKey, PKeyRef, Private, Public};
use pv_core::request::{RequestMagic, RequestVersion};
use std::convert::TryInto;
use zerocopy::{AsBytes, BigEndian, FromBytes, U32};
use std::mem::size_of;
use std::ops::Range;
use utils::assert_size;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32};
/// Encrypt a _secret_ using self and a given private key.
pub trait Encrypt {
@@ -94,7 +96,7 @@ impl Encrypt for Keyslot {
to: &mut Vec<u8>,
) -> Result<()> {
let derived_key = derive_key(priv_key, &self.0)?;
let mut wrpk_and_kst = encrypt_aes_gcm(&derived_key.into(), &[0; 12], &[], prot_key)?;
let (mut wrpk_and_kst, ..) = encrypt_aes_gcm(&derived_key.into(), &[0; 12], &[], prot_key)?;
let phk: EcdhPubkeyCoord = self.0.as_ref().try_into()?;
to.reserve(80);
@@ -215,10 +217,11 @@ impl ReqEncrCtx {
}
}
let rql = to_u32(auth_data.len() + encr_size + 16)
.ok_or_else(|| Error::Specification("Configured request size to large".to_string()))?;
let rql = to_u32(auth_data.len() + encr_size + 16).ok_or_else(|| {
pv_core::Error::Specification("Configured request size to large".to_string())
})?;
let sea = to_u32(encr_size)
.ok_or_else(|| Error::Specification("Encrypted size to large".to_string()))?;
.ok_or_else(|| pv_core::Error::Specification("Encrypted size to large".to_string()))?;
let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic);
// copy request header to the start of the request
@@ -235,17 +238,6 @@ impl ReqEncrCtx {
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
}
/// Encrypt confidential Data with this encryption context.
///
/// * `conf` - data to be encrypted
///
/// # Errors
///
/// This function will return an error if the data could not be encrypted by OpenSSL.
pub fn encrypt(&self, conf: &[u8]) -> Result<Vec<u8>> {
encrypt_aes(&self.prot_key, &self.iv, conf)
}
/// Encrypt confidential Data with this encryption context and provide a gcm tag.
///
/// * `aad` - additional authentic data
@@ -260,7 +252,11 @@ impl ReqEncrCtx {
/// # Errors
///
/// This function will return an error if the data could not be encrypted by OpenSSL.
pub fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
pub fn encrypt_aead(
&self,
aad: &[u8],
conf: &[u8],
) -> Result<(Vec<u8>, Range<usize>, Range<usize>, Range<usize>)> {
encrypt_aes_gcm(&self.prot_key, &self.iv, aad, conf)
}
}
@@ -309,7 +305,7 @@ ecdh_from!(Public);
/// Representation of the shared parts of the request header.
/// Used by [`ReqEncrCtx`]
#[repr(C)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
struct RequestHdr {
magic: [u8; 8],
rqvn: U32<BigEndian>,
@@ -321,6 +317,7 @@ struct RequestHdr {
reserved28: u32,
sea: U32<BigEndian>,
}
assert_size!(RequestHdr, 48);
impl RequestHdr {
fn new(rqvn: u32, rql: u32, iv: [u8; 12], nks: u8, sea: u32, magic: Option<[u8; 8]>) -> Self {
@@ -379,6 +376,67 @@ pub trait Request {
fn add_hostkey(&mut self, hostkey: PKey<Public>);
}
/// A struct to represent some parts of a binary/encrypted request.
#[derive(Debug)]
#[allow(unused)]
#[allow(clippy::len_without_is_empty)]
pub struct BinReqValues<'a> {
iv: &'a [u8],
aad: &'a [u8],
req_dep_aad: &'a [u8],
encr: &'a [u8],
tag: &'a [u8],
version: u32,
len: usize,
}
impl<'a> BinReqValues<'a> {
pub(crate) const TAG_LEN: usize = AES_256_GCM_TAG_SIZE;
/// Get the locations from this request.
///
/// Does minimal sanity test, just tests to prevent panics.
/// `req` may be larger than the actual request.
pub fn get(req: &'a [u8]) -> Result<Self> {
let hdr = RequestHdr::read_from_prefix(req).ok_or(Error::BinRequestSmall)?;
let rql = hdr.rql.get() as usize;
let sea = hdr.sea.get() as usize;
if rql < req.len() || sea + Self::TAG_LEN > rql {
return Err(Error::BinRequestSmall);
}
let aad_size = rql - sea - Self::TAG_LEN;
if aad_size < size_of::<RequestHdr>() {
return Err(Error::BinRequestSmall);
}
let iv = &req[0x10..0x1c];
let aad = &req[..aad_size];
let req_dep_aad = &req[size_of::<RequestHdr>()..aad_size];
let encr = &req[aad_size..(aad_size + sea)];
let tag = &req[rql - Self::TAG_LEN..];
Ok(Self {
iv,
aad,
req_dep_aad,
encr,
tag,
version: hdr.rqvn.get(),
len: rql,
})
}
/// Returns the version of this [`BinReqValues`].
pub fn version(&self) -> u32 {
self.version
}
/// Returns the length of this [`BinReqValues`].
pub fn len(&self) -> usize {
self.len
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -451,7 +509,7 @@ mod tests {
let ks = vec![
Keyslot::new(host_key.clone()),
Keyslot::new(host_key.clone()),
Keyslot::new(host_key.clone()),
Keyslot::new(host_key),
];
let mut aad = Vec::<Aad>::new();
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));

View File

@@ -34,8 +34,8 @@ impl Zeroize for Vec<u8> {
// * Vec allocated at least capacity elements continuously
// * dst points always to a valid location
unsafe {
std::ptr::write_volatile(dst, 0);
dst = dst.add(1);
std::ptr::write_volatile(dst, 0)
}
}
std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);

View File

@@ -1,336 +1,18 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{
error::{bail_spec, file_error, path_to_str},
Error, FileIoErrorType, Result,
use crate::{Error, Result};
use openssl::{
pkey::{PKey, Private},
x509::{X509Crl, X509},
};
#[cfg(feature = "request")]
use openssl::x509::X509Crl;
#[cfg(feature = "request")]
use openssl::x509::X509;
use std::io::{Read, Write};
use std::path::Path;
use zerocopy::{AsBytes, BigEndian, FromBytes, U64};
/// Asserts a constant expression evaluates to `true`.
///
/// If the expression is not evaluated to `true` the compilation will fail.
#[macro_export]
macro_rules! static_assert {
($condition:expr) => {
const _: () = core::assert!($condition);
};
}
/// Asserts that a type has a specific size.
///
/// Useful to validate structs that are passed to C code.
/// If the expression is not evaluated to `true` the compilation will fail.
///
/// # Example
/// ```rust
/// # use pv::assert_size;
/// # fn main() {}
/// #[repr(C)]
/// struct c_struct {
/// v: u64,
/// }
/// assert_size!(c_struct, 8);
/// // assert_size!(c_struct, 7);//won't compile
/// ```
#[macro_export]
macro_rules! assert_size {
($t:ty, $sz:expr ) => {
$crate::static_assert!(::std::mem::size_of::<$t>() == $sz);
};
}
/// Trait that describes bitflags, represented by `T`.
pub trait Flags<T>: From<T> + for<'a> From<&'a T> {
/// Set the specified bit to one.
/// # Panics
///Panics if bit is >= 64
fn set_bit(&mut self, bit: u8);
/// Set the specified bit to zero.
/// # Panics
///Panics if bit is >= 64
fn unset_bit(&mut self, bit: u8);
/// Test if the specified bit is set.
/// # Panics
///Panics if bit is >= 64
fn is_set(&self, bit: u8) -> bool;
}
/// Bitflags in MSB0 ordering
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)]
pub struct Msb0Flags64(U64<BigEndian>);
impl Flags<u64> for Msb0Flags64 {
#[track_caller]
fn set_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v |= 1 << (63 - bit);
self.0.set(v)
}
#[track_caller]
fn unset_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v &= !(1 << (63 - bit));
self.0.set(v)
}
#[track_caller]
fn is_set(&self, bit: u8) -> bool {
assert!(bit < 64, "Flag bit set to greater than 63");
self.0.get() & (1 << (63 - bit)) > 0
}
}
impl From<u64> for Msb0Flags64 {
fn from(value: u64) -> Self {
Self(value.into())
}
}
impl From<&u64> for Msb0Flags64 {
fn from(value: &u64) -> Self {
(*value).into()
}
}
/// Bitflags in LSB0 ordering
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)]
pub struct Lsb0Flags64(U64<BigEndian>);
impl Flags<u64> for Lsb0Flags64 {
#[track_caller]
fn set_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v |= 1 << bit;
self.0.set(v)
}
#[track_caller]
fn unset_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v &= !(1 << bit);
self.0.set(v)
}
#[track_caller]
fn is_set(&self, bit: u8) -> bool {
assert!(bit < 64, "Flag bit set to greater than 63");
self.0.get() & (1 << bit) > 0
}
}
impl From<u64> for Lsb0Flags64 {
fn from(value: u64) -> Self {
Self(value.into())
}
}
impl From<&u64> for Lsb0Flags64 {
fn from(value: &u64) -> Self {
(*value).into()
}
}
/// Tries to convert a BE hex string into a 128 unsigned integer
/// The hexstring must contain 32chars of hexdigits
///
/// * `hex_str` - string to convert can be prepended with "0x"
/// * `ctx` - Error context string in case of an error
/// ```rust
/// # use std::error::Error;
/// # use pv::misc::try_parse_u128;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let hex = "11223344556677889900aabbccddeeff";
/// try_parse_u128(&hex, "The test")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
/// If `hex_string` is not a 32 byte hex string an Error appears
pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> {
let hex_str = if hex_str.starts_with("0x") {
hex_str.split_at(2).1
} else {
hex_str
};
if hex_str.len() != 32 {
bail_spec!(format!(
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
));
}
parse_hex(hex_str).try_into().map_err(|_| {
Error::Specification(format!(
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
))
})
}
/// Tries to convert a BE hex string into a 64 unsigned integer
/// The hexstring must *NOT* contain 16 chars of hexdigits, but
/// 16 chars at most.
///
/// * `hex_str` - string to convert can be prepended with "0x"
/// * `ctx` - Error context string in case of an error
/// ```rust
/// # use std::error::Error;
/// # use pv::misc::try_parse_u64;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let hex = "1234567890abcdef";
/// try_parse_u64(&hex, "The test")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
/// If `hex_string` is not a 32 byte hex string an Error appears
pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result<u64> {
let hex_str = if hex_str.starts_with("0x") {
hex_str.split_at(2).1
} else {
hex_str
};
if hex_str.len() > 16 {
bail_spec!(format!(
"{ctx} hexstring {hex_str} must be max 16 chars long"
));
}
Ok(u64::from_str_radix(hex_str, 16)?)
}
/// Read exactly COUNT bytes into the buffer.
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
/// # Errors
/// If this function encounters an "end of file" before completely filling
/// the buffer, it returns an error. The contents of `buf` are unspecified in this case.
///
/// If any other read error is encountered then this function immediately
/// returns. The contents of `buf` are unspecified in this case.
///
/// If this function returns an error, it is unspecified how many bytes it
/// has read, but it will never read more than would be necessary to
/// completely fill the buffer.
pub fn read_exact_file<P: AsRef<Path>, const COUNT: usize>(
path: P,
ctx: &str,
) -> Result<[u8; COUNT]> {
let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess {
ty: crate::FileAccessErrorType::Open,
path: path_to_str!(path).to_string(),
source: e,
})?;
if f.metadata()?.len() as usize != COUNT {
bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long"));
}
let mut buf = [0; COUNT];
f.read_exact(&mut buf)
.map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?;
Ok(buf)
}
/// Read content from a file and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::read` produces
pub fn read_file<P: AsRef<Path>>(path: P, ctx: &str) -> Result<Vec<u8>> {
std::fs::read(&path).map_err(|e| {
file_error!(
Read,
ctx,
path.as_ref().to_str().unwrap_or("no UTF-8 path"),
e
)
})
}
/// Reads all content from a [`std::io::Read`] and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn read<R: Read>(rd: &mut R, path: &str, ctx: &str) -> Result<Vec<u8>> {
let mut buf = vec![];
rd.read_to_end(&mut buf).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})?;
Ok(buf)
}
/// write content to a file and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn write_file<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> Result<()> {
std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})
}
/// Write content to a [`std::io::Write`] and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn write<D: AsRef<[u8]>, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> {
wr.write_all(data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})
}
/// Read all CRLs from the buffer and parse them into a vector.
///
/// # Errors
///
/// This function will return an error if the underlying openssl implementation cannot parse `buf`
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
/// as `DER` or `PEM`.
///
/// Requires the `request` feature.
#[cfg(feature = "request")]
pub fn read_crls(buf: &[u8]) -> Result<Vec<X509Crl>> {
use openssl_extensions::crl::StackableX509Crl;
X509Crl::from_der(buf)
@@ -343,11 +25,8 @@ pub fn read_crls(buf: &[u8]) -> Result<Vec<X509Crl>> {
///
/// # Errors
///
/// This function will return an error if the underlying openssl implementation cannot parse `buf`
/// as `DER` or `PEM`.
///
/// Requires the `request` feature.
#[cfg(feature = "request")]
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>> {
X509::from_der(buf)
.map(|crt| vec![crt])
@@ -355,191 +34,23 @@ pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>> {
.map_err(Error::Crypto)
}
macro_rules! usize_to_ui {
($(#[$attr:meta])* => $t: ident, $name:ident) => {
///Converts an [`usize`] to an [`
$(#[$attr])*
///`] if possible
pub fn $name(u: usize) -> Option<$t> {
if u > $t::MAX as usize {
None
} else {
Some(u as $t)
}
}
}
}
usize_to_ui! {
#[doc = r"u32"]
=> u32, to_u32}
usize_to_ui! {
#[doc = r"u16"]
=> u16, to_u16}
/// Test if both slices contain the exact same bytes.
/// Read+parse the first key from the buffer.
///
/// Do not use this to compare cryptographic values (i.e. hashes)
pub fn memeq(lhs: &[u8], rhs: &[u8]) -> bool {
let size = lhs.len();
size == rhs.len()
&& unsafe {
let l = lhs as *const _ as _;
let r = rhs as *const _ as _;
(l as usize) == (r as usize) || libc::memcmp(l, r, size) == 0
}
}
/// Converts the hexstring into a byte vector.
/// # Errors
///
/// Stops if the end or until a non hex chat is found
pub fn parse_hex(hex_str: &str) -> Vec<u8> {
let mut hex_bytes = hex_str.as_bytes().iter().map_while(|b| match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
});
let mut bytes = Vec::new();
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
bytes.push(h << 4 | l)
}
bytes
}
/// Report if the `prot_virt_guest` sysfs entry is one.
///
/// If the entry does not exist returns false.
///
/// for non-s390-architectures:
/// Returns always false
/// A non-s390 system cannot be a secure execution guest.
#[allow(unreachable_code)]
pub fn pv_guest_bit_set() -> bool {
#[cfg(not(target_arch = "s390x"))]
return false;
//s390 branch
let v = std::fs::read("/sys/firmware/uv/prot_virt_guest").unwrap_or_else(|_| vec![0]);
let v: u8 = String::from_utf8_lossy(&v[..1]).parse().unwrap_or(0);
v == 1
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
/// as `DER` or `PEM`.
pub fn read_private_key(buf: &[u8]) -> Result<PKey<Private>> {
PKey::private_key_from_der(buf)
.or_else(|_| PKey::private_key_from_pem(buf))
.map_err(Error::Crypto)
}
#[cfg(test)]
mod tests {
use std::usize;
use super::*;
#[cfg(feature = "request")]
use crate::test_utils::*;
use crate::{get_test_asset, test_utils::*};
#[test]
fn msb_flags() {
let v = 17;
let v_flag: Msb0Flags64 = v.into();
assert_eq!(v, v_flag.0.get());
let mut v: Msb0Flags64 = 4.into();
v.unset_bit(61);
assert_eq!(v.0.get(), 0);
v.set_bit(61);
assert_eq!(4, v.0.get());
let mut v = Msb0Flags64::default();
v.set_bit(0);
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(0);
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(1);
assert_eq!(&[0xc0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(2);
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(3);
assert_eq!(&[0xf0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(16);
assert_eq!(&[0xe0, 0, 0x80, 0, 0, 0, 0, 0], v.as_bytes());
}
#[test]
#[should_panic]
fn msb_flags_set_panic() {
Msb0Flags64::default().set_bit(64)
}
#[test]
#[should_panic]
fn msb_flags_unset_panic() {
Msb0Flags64::default().unset_bit(64)
}
#[test]
fn lsb_flags() {
let v = 17;
let v_flag: Lsb0Flags64 = v.into();
assert_eq!(v, v_flag.0.get());
let mut v: Lsb0Flags64 = 4.into();
v.unset_bit(2);
assert_eq!(v.0.get(), 0);
v.set_bit(2);
assert_eq!(4, v.0.get());
let mut v = Lsb0Flags64::default();
v.set_bit(0);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
v.set_bit(0);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
v.set_bit(1);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 3], v.as_bytes());
v.set_bit(2);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
v.set_bit(3);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 0xf], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
v.set_bit(16);
assert_eq!(&[0, 0, 0, 0, 0, 1, 0, 7], v.as_bytes());
}
#[test]
#[should_panic]
fn lsb_flags_set_panic() {
Lsb0Flags64::default().set_bit(64)
}
#[test]
#[should_panic]
fn lsb_flags_unset_panic() {
Lsb0Flags64::default().unset_bit(64)
}
#[test]
fn parse_hex() {
let s = "123456acbef0";
let exp = vec![0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
assert_eq!(super::parse_hex(s), exp);
let s = "00123456acbef0";
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
assert_eq!(super::parse_hex(s), exp);
let s = "00123456acbef0ii90";
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
assert_eq!(super::parse_hex(s), exp);
}
#[test]
#[cfg(feature = "request")]
fn read_crls() {
let crl = get_cert_asset("ibm.crl");
let crl_der = get_cert_asset("der.crl");
@@ -550,7 +61,6 @@ mod tests {
}
#[test]
#[cfg(feature = "request")]
fn read_certs() {
let crt = get_cert_asset("ibm.crt");
let crt_der = get_cert_asset("der.crt");
@@ -561,98 +71,16 @@ mod tests {
}
#[test]
fn to_u32() {
assert_eq!(Some(17), super::to_u32(17));
assert_eq!(Some(0), super::to_u32(0));
assert_eq!(Some(u32::MAX), super::to_u32(u32::MAX as usize));
assert_eq!(None, super::to_u32(u32::MAX as usize + 1));
assert_eq!(None, super::to_u32(usize::MAX));
fn read_private_key() {
let key = get_test_asset!("keys/rsa3072key.pem");
let key = super::read_private_key(key).unwrap();
assert_eq!(key.rsa().unwrap().size(), 384);
}
#[test]
fn parse_u128() {
assert!(matches!(
try_parse_u128("123456", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("-1234", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0011223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("dd11223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("-1223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0x123456", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("-0x1234", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0x0011223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0xdd11223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0x-1223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert_eq!(
[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("11223344556677889900aabbccddeeff", "").unwrap()
);
assert_eq!(
[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("0x11223344556677889900aabbccddeeff", "").unwrap()
);
assert_eq!(
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
);
assert_eq!(
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
);
}
#[test]
fn memeq() {
let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
let b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1];
let c = [0, 0, 1, 2, 3, 4];
assert!(super::memeq(&a, &a));
assert!(super::memeq(&a, &a.clone()));
assert!(!super::memeq(&b, &a));
assert!(!super::memeq(&b, &c));
assert!(!super::memeq(&b, &[]));
fn read_private_key_fail() {
let key = get_test_asset!("exp/secure_guest.hdr");
let key = super::read_private_key(key);
assert!(key.is_err());
}
}

View File

@@ -2,79 +2,12 @@
//
// Copyright IBM Corp. 2023
#![cfg(feature = "uvsecret")]
//! Provides functionality to manage the UV secret store.
//!
//! Provides functionality to build `add-secret` requests.
//! Also provides interfaces, to dispatch `Add Secret`, `Lock Secret Store`,
//! and `List Secrets` requests,
#[cfg(feature = "request")]
pub mod asrcb;
#[cfg(feature = "request")]
pub mod ext_secret;
#[cfg(feature = "request")]
pub mod guest_secret;
pub mod secret_list;
pub mod uvc;
use crate::request::MagicValue;
use crate::requires_feat;
#[allow(unused_imports)] //used for more convenient docstring
use asrcb::AddSecretRequest;
/// Types of (non architectured) user data for [`AddSecretRequest`]
///
#[doc = requires_feat!(uvsecret)]
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, zerocopy::AsBytes)]
pub enum UserDataType {
/// Marker that the request does not contain any user data
Null = 0x0000,
}
/// The magic value used to identify an [`AddSecretRequest`]
///
/// The magic value is ASCII:
/// ```rust
/// # use pv::request::uvsecret::AddSecretMagic;
/// # use pv::request::MagicValue;
/// # fn main() {
/// # let magic =
/// # b"asrcbM"
/// # ;
/// # assert!(AddSecretMagic::starts_with_magic(magic));
/// # }
///```
///
#[doc = requires_feat!(uvsecret)]
#[repr(C)]
#[derive(Debug, Clone, Copy, zerocopy::AsBytes)]
pub struct AddSecretMagic {
magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]
tp: UserDataType,
}
impl MagicValue<6> for AddSecretMagic {
// "asrcbM"
const MAGIC: [u8; 6] = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D];
}
impl From<UserDataType> for AddSecretMagic {
fn from(tp: UserDataType) -> Self {
Self {
magic: Self::MAGIC,
tp,
}
}
}
const SECRET_ID_SIZE: usize = 32;
fn ser_gsid<S>(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s = String::with_capacity(32 * 2 + 2);
s.push_str("0x");
let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
ser.serialize_str(&s)
}
pub mod user_data;

View File

@@ -2,23 +2,23 @@
//
// Copyright IBM Corp. 2023
use super::{AddSecretMagic, UserDataType};
use crate::requires_feat;
use super::user_data::UserData;
use crate::{
assert_size,
misc::Flags,
request::{
hkdf_rfc_5869,
openssl::{
pkey::{PKey, Public},
pkey::{PKey, Private, Public},
Md,
},
uvsecret::{ExtSecret, GuestSecret},
Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, RequestVersion, Secret,
Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, Secret,
},
uv::{ConfigUid, UvFlags},
Result,
};
use pv_core::request::RequestVersion;
use zerocopy::AsBytes;
/// Internal wrapper for Guest Secret, so that we can dump it in the form the UV wants it to be
@@ -52,6 +52,7 @@ impl From<GuestSecret> for BinGuestSecret {
}
}
/// Authenticated data w/o user data
#[repr(C)]
#[derive(Debug, Clone, Copy, AsBytes)]
struct ReqAuthData {
@@ -59,9 +60,8 @@ struct ReqAuthData {
boot_tags: BootHdrTags,
cuid: ConfigUid,
reserved90: [u8; 0x100],
prog_res190: [u8; 0x200],
}
assert_size!(ReqAuthData, 0x3e8);
assert_size!(ReqAuthData, 0x1e8);
impl ReqAuthData {
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F) -> Self {
@@ -70,7 +70,6 @@ impl ReqAuthData {
boot_tags,
cuid: [0; 0x10],
reserved90: [0; 0x100],
prog_res190: [0; 0x200],
}
}
}
@@ -96,8 +95,6 @@ impl ReqConfData {
}
/// Flags for [`AddSecretRequest`]
///
#[doc = requires_feat!(reqsecret)]
#[derive(Default, Clone, Copy, Debug)]
pub struct AddSecretFlags(UvFlags);
impl AddSecretFlags {
@@ -123,8 +120,6 @@ impl From<AddSecretFlags> for UvFlags {
}
/// Versions for [`AddSecretRequest`]
///
#[doc = requires_feat!(reqsecret)]
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddSecretVersion {
@@ -143,12 +138,6 @@ impl From<AddSecretVersion> for RequestVersion {
}
}
impl AddSecretMagic {
fn get(&self) -> crate::request::RequestMagic {
self.as_bytes().try_into().unwrap()
}
}
/// Add-secret request Control Block
///
/// An ASRCB wraps a secret to transport it securely to the Ultravisor.
@@ -158,6 +147,7 @@ impl AddSecretMagic {
/// _______________________________________________________________
/// | generic header (48)
/// | --------------------------------------------------- |
/// | Plaintext Add-Secret flags (8) |
/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) |
/// | Configuration unique ID(16) (Attestation) |
/// | Optional, defaults to 0 |
@@ -173,18 +163,19 @@ impl AddSecretMagic {
/// | AES GCM Tag (16) |
/// |_____________________________________________________________|
///```
///
#[doc = requires_feat!(reqsecret)]
#[derive(Clone, Debug)]
pub struct AddSecretRequest {
magic: AddSecretMagic,
version: AddSecretVersion,
aad: ReqAuthData,
keyslots: Vec<Keyslot>,
conf: ReqConfData,
user_data: UserData,
}
impl AddSecretRequest {
/// Offset of the user-data in the add-secret request in bytes
pub(super) const V1_USER_DATA_OFFS: usize = 0x218;
/// Create a new add-secret request.
///
/// The request has no extension secret, no configuration UID, no host-keys,
@@ -204,7 +195,7 @@ impl AddSecretRequest {
aad: ReqAuthData::new(boot_tags, flags),
keyslots: vec![],
version,
magic: UserDataType::Null.into(),
user_data: UserData::Null,
}
}
@@ -238,18 +229,41 @@ impl AddSecretRequest {
&self.conf.secret.0
}
/// Add user-data to the Add-Secret request
///
/// (Signed) user-data is a non-architectual feature. It allows to add arbitrary
/// data (message) to the request, that is signed optionally with an user defined key.
/// Allowed keys are:
/// - no key (up to 512 bytes of message)
/// - EC SECP521R1 (up to 256 byte message)
/// - RSA 2048 bit (up to 256 byte message)
/// - RSA 3072 bit (up to 128 byte message)
///
/// The signature can be verified during the verification of the secret-request on the target machine.
pub fn set_user_data(&mut self, msg: Vec<u8>, skey: Option<PKey<Private>>) -> Result<()> {
self.user_data = UserData::new(skey, msg)?;
Ok(())
}
/// compiles the authenticated area of this request
fn aad(&self, ctx: &ReqEncrCtx, conf_len: usize) -> Result<Vec<u8>> {
let cust_pub_key = ctx.key_coords()?;
let secr_auth = self.conf.secret.dump_auth();
let user_data = self.user_data.data();
let mut aad: Vec<Aad> = Vec::with_capacity(3 + self.keyslots.len());
let mut aad: Vec<Aad> = Vec::with_capacity(5 + self.keyslots.len());
aad.push(Aad::Plain(self.aad.as_bytes()));
if let Some(data) = user_data.0 {
aad.push(Aad::Plain(data));
}
if let Some(data) = &user_data.1 {
aad.push(Aad::Plain(data));
}
aad.push(Aad::Plain(cust_pub_key.as_ref()));
self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k)));
aad.push(Aad::Plain(&secr_auth));
ctx.build_aad(self.version.into(), &aad, conf_len, self.magic.get())
ctx.build_aad(self.version.into(), &aad, conf_len, self.user_data.magic())
}
#[doc(hidden)]
@@ -268,13 +282,41 @@ impl AddSecretRequest {
res.append(&mut vec![0x24; 32]);
Ok(res)
}
/// encrypt data, sign request with user-provided signing key, insert signature into aad,
/// calculate request tag
fn encrypt_with_signed_user_data(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>> {
//encrypt data w/o aead
let conf = self.conf.to_bytes();
let aad = self.aad(ctx, conf.value().len())?;
let (mut buf, aad_range, encr_range, _) = ctx.encrypt_aead(&aad, conf.value())?;
drop(aad);
// sign aad+encrypted data (w/o tag) with user signning key
// add signature to authenticated data starting with USER_DATA_OFFS
self.user_data.sign(
&mut buf[aad_range.start..encr_range.end],
Self::V1_USER_DATA_OFFS,
)?;
// encrypt again with signed data
buf[encr_range.clone()].copy_from_slice(conf.value());
ctx.encrypt_aead(&buf[aad_range], &buf[encr_range])
.map(|(buf, ..)| buf)
}
}
impl Request for AddSecretRequest {
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>> {
let conf = self.conf.to_bytes();
let aad = self.aad(ctx, conf.value().len())?;
ctx.encrypt_aead(&aad, conf.value())
match self.user_data {
UserData::Null | UserData::Unsigned(_) => {
let conf = self.conf.to_bytes();
let aad = self.aad(ctx, conf.value().len())?;
ctx.encrypt_aead(&aad, conf.value()).map(|(buf, ..)| buf)
}
_ => self.encrypt_with_signed_user_data(ctx),
}
}
fn add_hostkey(&mut self, hostkey: PKey<Public>) {

View File

@@ -2,11 +2,9 @@
//
// Copyright IBM Corp. 2023
use crate::{request::Secret, requires_feat};
use crate::request::Secret;
/// Extension Secret for [`crate::request::uvsecret::AddSecretRequest`]
///
#[doc = requires_feat!(reqsecret)]
#[derive(Debug, Clone)]
pub enum ExtSecret {
/// A bytepattern that must be equal for each request targeting the same SE-guest instance

View File

@@ -4,18 +4,16 @@
#[allow(unused_imports)] //used for more convenient docstring
use super::asrcb::AddSecretRequest;
use super::{ser_gsid, SECRET_ID_SIZE};
use crate::{
request::{hash, openssl::MessageDigest, random_array, Secret},
requires_feat, Result,
Result,
};
use pv_core::for_pv::{ser_gsid, SECRET_ID_SIZE};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
const SECRET_SIZE: usize = 32;
/// A Secret to be added in [`AddSecretRequest`]
///
#[doc = requires_feat!(reqsecret)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum GuestSecret {
/// No guest secret
@@ -105,7 +103,7 @@ mod test {
0xef, 0xc7, 0x3c, 0x62,
];
let name = "association secret".to_string();
let secret = GuestSecret::association("association secret", secret_value.clone()).unwrap();
let secret = GuestSecret::association("association secret", secret_value).unwrap();
let exp = GuestSecret::Association {
name,
id: exp_id,

View File

@@ -1,224 +0,0 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::{Serialize, Serializer};
use std::usize;
use std::{
fmt::Display,
io::{Cursor, Read, Seek, Write},
};
use zerocopy::{AsBytes, FromBytes, U16, U32};
use super::ser_gsid;
/// List of secrets used to parse the [`crate::uv::ListCmd`] result
///
/// Requires the `uvsecret` feature.
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct SecretList {
total_num_secrets: u16,
secrets: Vec<SecretEntry>,
}
impl SecretList {
/// Encodes the list in the same binary format the UV would do
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
w.write_u16::<BigEndian>(num_s)?;
w.write_u16::<BigEndian>(self.total_num_secrets)?;
w.write_all(&[0u8; 12])?;
for secret in &self.secrets {
w.write_all(secret.as_bytes())?;
}
w.flush().map_err(Error::Io)
}
/// Decodes the list from the binary format of the UV into this internal representation
pub fn decode<R: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
let num_s = r.read_u16::<BigEndian>()?;
let total_num_secrets = r.read_u16::<BigEndian>()?;
let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
r.seek(std::io::SeekFrom::Current(12))?; //skip reserved bytes
let mut buf = [0u8; SECRET_ENTRY_SIZE];
for _ in 0..num_s {
r.read_exact(&mut buf)?;
//cannot fail. buffer has the same size as the secret entry
let secr = SecretEntry::read_from(buf.as_slice()).unwrap();
v.push(secr);
}
Ok(Self {
total_num_secrets,
secrets: v,
})
}
}
impl TryFrom<ListCmd> for SecretList {
type Error = Error;
fn try_from(mut list: ListCmd) -> Result<SecretList> {
SecretList::decode(&mut Cursor::new(list.data().unwrap())).map_err(Error::InvSecretList)
}
}
impl Display for SecretList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?;
if !self.secrets.is_empty() {
writeln!(f)?;
}
for s in &self.secrets {
writeln!(f, "{s}")?;
}
Ok(())
}
}
fn ser_u32<S: Serializer>(v: &U32<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_u32(v.get())
}
fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_u16(v.get())
}
/// A secret in a [`SecretList`]
///
/// Fields are in big endian
#[repr(C)]
#[derive(Debug, PartialEq, Eq, AsBytes, FromBytes, Serialize)]
pub struct SecretEntry {
#[serde(serialize_with = "ser_u16")]
index: U16<BigEndian>,
#[serde(serialize_with = "ser_u16")]
stype: U16<BigEndian>,
#[serde(serialize_with = "ser_u32")]
len: U32<BigEndian>,
#[serde(skip)]
res_8: u64,
#[serde(serialize_with = "ser_gsid")]
id: [u8; 32],
}
const SECRET_ENTRY_SIZE: usize = 0x30;
fn stype_str(stype: u16) -> String {
match stype {
// should never match (not incl in list), but here for completeness
1 => "Null".to_string(),
2 => "Association".to_string(),
n => format!("Unknown {n}"),
}
}
impl Display for SecretEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{} {}:", self.index, stype_str(self.stype.get()))?;
write!(f, " ")?;
for b in self.id {
write!(f, "{:02x}", b)?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::{BufReader, BufWriter, Cursor};
#[test]
fn secret_entry_size() {
assert_eq!(::std::mem::size_of::<SecretEntry>(), SECRET_ENTRY_SIZE);
}
#[test]
fn dump_secret_entry() {
const EXP: &[u8] = &[
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let s = SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
};
assert_eq!(s.as_bytes(), EXP);
}
#[test]
fn secret_list_dec() {
let buf = [
0x00u8, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
// secret
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let exp = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
}],
};
let mut br = BufReader::new(Cursor::new(buf));
let sl = SecretList::decode(&mut br).unwrap();
assert_eq!(sl, exp);
}
#[test]
fn secret_list_enc() {
const EXP: &[u8] = &[
0x00, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
// secret
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let sl = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
}],
};
let mut buf = [0u8; 0x40];
{
let mut bw = BufWriter::new(&mut buf[..]);
sl.encode(&mut bw).unwrap();
}
println!("list: {sl:?}");
assert_eq!(buf, EXP);
}
}

View File

@@ -0,0 +1,611 @@
use crate::{
crypto::{sign_msg, verify_signature},
req::BinReqValues,
request::{
openssl::{
pkey::{PKey, Private},
MessageDigest,
},
uvsecret::{AddSecretRequest, AddSecretVersion},
RequestMagic,
},
Error, Result,
};
use openssl::{
nid::Nid,
pkey::{HasParams, HasPublic, Id, PKeyRef, Public},
};
use pv_core::request::uvsecret::AddSecretMagic;
use pv_core::request::uvsecret::UserDataType;
use utils::assert_size;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U16};
/// User data.
///
/// User defined data can be:
/// - 512 bytes arbitrary data
/// - 256 bytes arbitrary data + EC(secp521r1) signature
/// ```none
/// LAYOUT
/// |------------------------|
/// | user-data (256) |
/// | ec signature (139) |
/// | reserved (5) |
/// | signature size (2) (BE)|
/// | reserved (110) |
/// |------------------------|
/// ```
/// - 256 bytes arbitrary data + RSA2048 signature
/// ```none
/// LAYOUT
/// |---------------------|
/// | user-data (256) |
/// | rsa signature (256) |
/// |---------------------|
/// ```
/// - 128 bytes arbitrary data + RSA3072 signature
/// ```none
/// LAYOUT
/// |---------------------|
/// | user-data (128) |
/// | rsa signature (384) |
/// |---------------------|
/// ```
///
/// Ensures that the data+signature fits into 512 bytes
/// must be created via functions!
#[derive(Debug, Clone)]
pub(super) enum UserData {
Null,
Unsigned(Vec<u8>),
Signed(SignedUserData),
}
#[repr(C)]
#[derive(Debug, AsBytes, FromBytes, FromZeroes)]
struct EcUserData {
data: [u8; 256],
signature: [u8; EC_SIGN_MAX_SIZE],
res_18b: [u8; 5],
sgn_size: U16<BigEndian>,
res_192: [u8; 110],
}
assert_size!(EcUserData, USER_DATA_SIZE);
const USER_DATA_SIZE: usize = 0x200;
const EC_SIGN_MAX_SIZE: usize = 139;
impl EcUserData {
// Sets the signature to this data.
//
//# Panic
// Panics if `sgn` is longer than 139 bytes
fn set_signature(&mut self, sgn: &[u8]) {
debug_assert!(sgn.len() <= EC_SIGN_MAX_SIZE);
self.signature.fill(0);
self.signature[..sgn.len()].copy_from_slice(sgn);
self.res_18b.fill(0);
self.sgn_size = (sgn.len() as u16).into();
self.res_192.fill(0);
}
}
#[derive(Debug, Clone)]
pub(super) struct SignedUserData {
sign_key: PKey<Private>,
data: Vec<u8>,
}
impl UserData {
const USER_DATA_SIZE: usize = 0x200;
fn user_data_type<P: HasPublic>(sign_key: &PKeyRef<P>) -> Result<UserDataType> {
fn check_curve<P: HasParams>(pkey: &PKeyRef<P>) -> Result<bool> {
let nid = pkey.ec_key()?.group().curve_name();
match nid {
Some(nid) => Ok(nid == Nid::SECP521R1),
None => Ok(false),
}
}
match sign_key.id() {
Id::EC if check_curve(sign_key)? => Ok(UserDataType::SgnEcSECP521R1),
Id::RSA if sign_key.rsa()?.size() == 2048 / 8 => Ok(UserDataType::SgnRsa2048),
Id::RSA if sign_key.rsa()?.size() == 3072 / 8 => Ok(UserDataType::SgnRsa3072),
_ => Err(Error::BinAsrcbUnsupportedUserDataSgnKey),
}
}
pub(super) fn magic(&self) -> RequestMagic {
let magic: AddSecretMagic = self.data_type().into();
magic.get()
}
/// Creates new user data
///
/// Verifies that the provided data + signature fits into 512 bytes
///
/// #Error
/// An error is reported if the provided data and the signature would not fit into 512 bytes
/// An error is reported if the key is not of type RSA (2048|3072) or EC(specp521r1)
pub(super) fn new(sign_key: Option<PKey<Private>>, data: Vec<u8>) -> Result<Self> {
let sign_key = match sign_key {
None => {
return match data.len() > UserDataType::Unsigned.max() {
true => Err(Error::AsrcbInvSgnUserData(UserDataType::Unsigned)),
false => Ok(Self::Unsigned(data)),
};
}
Some(skey) => skey,
};
let kind = Self::user_data_type(&sign_key)?;
// does the data fit into the arbitrary buffer?
if data.len() > kind.max() {
return Err(Error::AsrcbInvSgnUserData(kind));
}
Ok(Self::Signed(SignedUserData { sign_key, data }))
}
/// Signs data in buf, writes signature to buf+user_data_offset+sign_offset if applicable.
///
/// Uses [`MessageDigest::sha512`] as digest. Does not modify the abritary user data buffer.
///
/// * buf: user data buffer, must be at least 512 bytes long
///
/// # Panic
/// panics if `buf` is smaller than 512 bytes
///
/// # Errors
/// Returns an error if signature could not be calculated.
/// It is considered no error if no signature is required by user data type
pub(super) fn sign(&self, buf: &mut [u8], user_data_offset: usize) -> Result<()> {
// get signing info or return if no signature is required
let signed_data = match self {
UserData::Null | UserData::Unsigned(_) => return Ok(()),
UserData::Signed(s) => s,
};
debug_assert!(buf.len() >= USER_DATA_SIZE);
// clear the signature area
let sgn_offset = user_data_offset + self.data_type().max();
buf[sgn_offset..user_data_offset + USER_DATA_SIZE].fill(0);
// calculate signature
let sgn = sign_msg(&signed_data.sign_key, MessageDigest::sha512(), buf)?;
// insert signature
if let UserDataType::SgnEcSECP521R1 = self.data_type() {
// Panic: will not panic buffer is 512+ bytes long
let buf_ec = EcUserData::mut_from_prefix(&mut buf[user_data_offset..]).unwrap();
buf_ec.set_signature(&sgn);
} else {
// Panic: will not panic buffer is 512+ bytes long
buf[sgn_offset..sgn_offset + sgn.len()].copy_from_slice(&sgn);
}
Ok(())
}
fn data_type(&self) -> UserDataType {
match self {
Self::Null => UserDataType::Null,
Self::Unsigned(_) => UserDataType::Unsigned,
Self::Signed(data) => Self::user_data_type(&data.sign_key).unwrap(),
}
}
/// returns a slice for the abitraty user data as first tuple part if User data is available
/// the second part contains a vector, created on the fly, which contains enough zeros to fill
/// the missing bytes to fill 512 bytes of space or None if the first slice already contains
/// 512 bytes
pub(super) fn data(&self) -> (Option<&[u8]>, Option<Vec<u8>>) {
let buf = match self {
UserData::Null => None,
UserData::Unsigned(d) => Some(d),
UserData::Signed(SignedUserData { data, .. }) => Some(data),
};
let remaining_size = Self::USER_DATA_SIZE - buf.map(|b| b.len()).unwrap_or(0);
let remaining = match remaining_size > 0 {
true => Some(vec![0; remaining_size]),
false => None,
};
(buf.map(|b| b.as_ref()), remaining)
}
}
fn format_vrfy_key(key: &PKeyRef<Public>) -> String {
let id = key.id();
match key.rsa() {
Ok(key) => format!("RSA {}", key.size() * 8),
Err(_) if id == Id::EC => "EC".to_string(),
Err(_) => "Unknown".to_string(),
}
}
fn check_key_format(kind: UserDataType, key: &PKeyRef<Public>) -> Result<()> {
let other_kind =
UserData::user_data_type(key).map_err(|_| Error::AsrcbUserDataKeyMismatch {
key: format_vrfy_key(key),
kind,
})?;
if other_kind == kind {
Ok(())
} else {
Err(Error::AsrcbUserDataKeyMismatch {
key: format_vrfy_key(key),
kind,
})
}
}
/// Verify the user data contained in the add-secret request.
///
/// First checks that the provided data contains a sound add-secret request.
/// Then performs the inverse action that happened during the add-secret generation with user-data
/// signature:
/// - extract and replace the signature with zeros
/// - verify the signature of the request until, but not including the request tag
///
/// # Returns
///
/// Extracrted user-data if available
///
/// #Errors
///
/// returns an error if
/// - No sound add-secret request presented
/// - Sinned user-data indicated, but no key provided
/// - Another keytype provided than indicated in the request
/// - Signature could not be verified by the provided key
/// - any OpenSSL error that might happen during the verification process
pub fn verify_asrcb_and_get_user_data(
mut asrcb: Vec<u8>,
key: Option<PKey<Public>>,
) -> Result<Option<Vec<u8>>> {
// check that the provided buffer contains an Add Secret request
let magic = AddSecretMagic::try_from_bytes(&asrcb)?;
let req = BinReqValues::get(&asrcb)?;
if req.version() != AddSecretVersion::One as u32 {
return Err(Error::BinAsrcbInvVersion);
}
//preventing the two lines after the truncate from panicking
let req_len = req.len();
if asrcb.len() < req_len
|| req_len < AddSecretRequest::V1_USER_DATA_OFFS + UserData::USER_DATA_SIZE
{
return Err(pv_core::Error::NoAsrcb.into());
}
// forget the tag (and all additional data that might be behind the tag)
asrcb.truncate(req_len - BinReqValues::TAG_LEN);
// get a mutable refrenence on the 512 bytes of user data
let (_, user_data) = asrcb.split_at_mut(AddSecretRequest::V1_USER_DATA_OFFS);
let user_data = &mut user_data[..UserData::USER_DATA_SIZE];
// depending on the user_data_type do:
// Null -> exit w/o user data
// Unsigned -> exit return all user data
// Signed ->
// - check that provided key matches user data keytype
// - extract user data& signature
let (key, user_data) = match (key, magic.kind()) {
(_, UserDataType::Null) => return Ok(None),
(None, UserDataType::Unsigned) => return Ok(Some(user_data.to_vec())),
(Some(key), UserDataType::Unsigned) => {
return Err(Error::AsrcbUserDataKeyMismatch {
key: format_vrfy_key(&key),
kind: UserDataType::Unsigned,
})
}
(Some(key), _) => {
check_key_format(magic.kind(), &key)?;
(key, VerifiedUserData::new(user_data, magic.kind()))
}
(None, _) => return Err(Error::BinAsrcbNoUserDataSgnKey),
};
match verify_signature(&key, MessageDigest::sha512(), &asrcb, user_data.signature())? {
false => Err(Error::AsrcbUserDataSgnFail),
true => Ok(Some(user_data.into())),
}
}
// Internal representation of the 512 bytes of user-data, signing-algorithm agnostic
struct VerifiedUserData {
data: Vec<u8>,
signature: Vec<u8>,
}
impl VerifiedUserData {
/// Reads user-data from buf depending on the indicated user data type.
/// Overwrites the signature in the buf with zeros.
///
/// #Panics
/// Panics it provided buffer is smaller that 512 bytes or kind is Null or Unsigned
fn new(buf: &mut [u8], kind: UserDataType) -> Self {
assert!(buf.len() >= 0x200);
let (ret, sgn) = match kind {
UserDataType::SgnEcSECP521R1 => {
let EcUserData {
data,
signature,
sgn_size,
..
} = EcUserData::mut_from_prefix(buf).unwrap();
let data_len: usize = data.len();
let data = data.to_vec();
let mut signature = signature.to_vec();
signature.truncate(sgn_size.get() as usize);
(Self { data, signature }, &mut buf[data_len..])
}
UserDataType::SgnRsa2048 => (
Self {
data: buf[..0x100].to_vec(),
signature: buf[0x100..].to_vec(),
},
&mut buf[0x100..],
),
UserDataType::SgnRsa3072 => (
Self {
data: buf[..0x80].to_vec(),
signature: buf[0x80..].to_vec(),
},
&mut buf[0x80..],
),
UserDataType::Null => unreachable!(),
UserDataType::Unsigned => unreachable!(),
};
//overwrite signature field with zeros
sgn.fill(0);
ret
}
fn signature(&self) -> &[u8] {
self.signature.as_ref()
}
}
impl From<VerifiedUserData> for Vec<u8> {
fn from(value: VerifiedUserData) -> Self {
value.data
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{get_test_asset, test_utils::get_test_keys};
#[test]
fn sign_null() {
let mut buf = vec![17; 0x200];
let user_data = UserData::Null;
let (data, _) = user_data.data();
assert!(data.is_none());
user_data.sign(&mut buf, 0).unwrap();
// sign should not touch the buffer
assert_eq!(buf, vec![17; 0x200]);
}
#[test]
fn sign_unsigned() {
let user_data = UserData::Unsigned(vec![0x11; 0x200]);
let (data, _) = user_data.data();
assert_eq!(data.unwrap(), &[0x11; 0x200]);
let mut buf = vec![17; 0x200];
user_data.sign(&mut buf, 0).unwrap();
// sign should not touch the buffer
assert_eq!(buf, vec![17; 0x200]);
}
#[test]
fn sign_rsa2048() {
let rsa = get_test_asset!("keys/rsa2048key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let mut buf = vec![0x17; 0x200];
let user_data = UserData::new(Some(rsa.clone()), vec![0x11; 0x100]).unwrap();
let (data, _) = user_data.data();
let data = data.unwrap();
buf[..0x100].copy_from_slice(data);
user_data.sign(&mut buf, 0).unwrap();
let vrf_user_data = VerifiedUserData::new(&mut buf, UserDataType::SgnRsa2048);
let res = verify_signature(
&rsa,
MessageDigest::sha512(),
&buf,
vrf_user_data.signature(),
)
.unwrap();
assert!(res);
}
#[test]
fn sign_rsa3072() {
let rsa = get_test_asset!("keys/rsa3072key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let mut buf = vec![0x17; 0x200];
let user_data = UserData::new(Some(rsa.clone()), vec![0x11; 0x80]).unwrap();
let (data, _) = user_data.data();
let data = data.unwrap();
buf[..0x80].copy_from_slice(data);
user_data.sign(&mut buf, 0).unwrap();
let vrf_user_data = VerifiedUserData::new(&mut buf, UserDataType::SgnRsa3072);
let res = verify_signature(
&rsa,
MessageDigest::sha512(),
&buf,
vrf_user_data.signature(),
)
.unwrap();
assert!(res);
}
#[test]
fn sign_rsa4096_fail() {
let rsa = get_test_asset!("keys/rsa4096key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let user_data = UserData::new(Some(rsa.clone()), vec![]);
assert!(matches!(
user_data.unwrap_err(),
Error::BinAsrcbUnsupportedUserDataSgnKey
));
}
#[test]
fn sign_ec() {
let (ec, _) = get_test_keys();
let mut buf = vec![0x11; 0x200];
let user_data = UserData::new(Some(ec.clone()), vec![0x11; 0x80]).unwrap();
let (data, _) = user_data.data();
let data = data.unwrap();
buf[..0x80].copy_from_slice(data);
user_data.sign(&mut buf, 0).unwrap();
let buf_ec = EcUserData::mut_from(&mut buf).unwrap();
let EcUserData {
data,
signature,
res_18b,
sgn_size,
res_192,
} = buf_ec;
assert_eq!(data, &[0x11u8; 256]);
assert_ne!(signature, &[0x11u8; 139]);
assert_eq!(res_18b, &[0u8; 5]);
assert!(sgn_size.get() <= 139);
assert_eq!(res_192, &[0u8; 110]);
let vrf_user_data = VerifiedUserData::new(&mut buf, UserDataType::SgnEcSECP521R1);
let res = verify_signature(
&ec,
MessageDigest::sha512(),
&buf,
vrf_user_data.signature(),
)
.unwrap();
assert!(res);
}
#[test]
fn sign_ec_fail() {
let ec = get_test_asset!("keys/ecsecp256k1.pem");
let ec = PKey::private_key_from_pem(ec).unwrap();
let user_data = UserData::new(Some(ec.clone()), vec![]);
assert!(matches!(
user_data.unwrap_err(),
Error::BinAsrcbUnsupportedUserDataSgnKey
));
}
#[test]
fn check_format() {
let (_, ec) = get_test_keys();
check_key_format(UserDataType::SgnEcSECP521R1, &ec).unwrap();
let res = check_key_format(UserDataType::SgnRsa2048, &ec);
assert!(matches!(res, Err(Error::AsrcbUserDataKeyMismatch { .. })));
let rsa = get_test_asset!("keys/rsa2048key.pub.pem");
let rsa = PKey::public_key_from_pem(rsa).unwrap();
check_key_format(UserDataType::SgnRsa2048, &rsa).unwrap();
let rsa = get_test_asset!("keys/rsa3072key.pub.pem");
let rsa = PKey::public_key_from_pem(rsa).unwrap();
check_key_format(UserDataType::SgnRsa3072, &rsa).unwrap();
let res = check_key_format(UserDataType::SgnRsa2048, &rsa);
assert!(matches!(res, Err(Error::AsrcbUserDataKeyMismatch { .. })));
let rsa = get_test_asset!("keys/rsa4096key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let rsa = PKey::public_key_from_pem(&rsa.public_key_to_pem().unwrap()).unwrap();
let res = check_key_format(UserDataType::SgnRsa2048, &rsa);
assert!(matches!(res, Err(Error::AsrcbUserDataKeyMismatch { .. })));
}
#[test]
fn kind() {
let (ec, _) = get_test_keys();
let kind = UserData::user_data_type(&ec).unwrap();
assert_eq!(kind, UserDataType::SgnEcSECP521R1);
let rsa = get_test_asset!("keys/rsa2048key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let kind = UserData::user_data_type(&rsa).unwrap();
assert_eq!(kind, UserDataType::SgnRsa2048);
let rsa = get_test_asset!("keys/rsa3072key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let kind = UserData::user_data_type(&rsa).unwrap();
assert_eq!(kind, UserDataType::SgnRsa3072);
let rsa = get_test_asset!("keys/rsa4096key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let kind = UserData::user_data_type(&rsa).unwrap_err();
assert!(matches!(kind, Error::BinAsrcbUnsupportedUserDataSgnKey));
}
#[test]
fn new() {
let (ec, _) = get_test_keys();
let user_data = UserData::new(
Some(ec.clone()),
vec![1; UserDataType::SgnEcSECP521R1.max()],
)
.unwrap();
assert!(matches!(user_data, UserData::Signed(_)));
let user_data = UserData::new(Some(ec), vec![1; UserDataType::SgnEcSECP521R1.max() + 1]);
assert!(matches!(
user_data,
Err(Error::AsrcbInvSgnUserData(UserDataType::SgnEcSECP521R1))
));
let user_data = UserData::new(None, vec![1; UserDataType::Unsigned.max()]).unwrap();
assert!(matches!(user_data, UserData::Unsigned(_)));
let user_data = UserData::new(None, vec![1; UserDataType::Unsigned.max() + 1]);
assert!(matches!(
user_data,
Err(Error::AsrcbInvSgnUserData(UserDataType::Unsigned))
));
}
#[test]
fn data() {
let (ec, _) = get_test_keys();
let data_in = vec![1; UserDataType::SgnEcSECP521R1.max()];
let user_data = UserData::new(Some(ec.clone()), data_in.clone()).unwrap();
let exp_pad = Some(vec![0; UserData::USER_DATA_SIZE - data_in.len()]);
let (data_out, pad) = user_data.data();
assert_eq!(data_out, Some(data_in.as_ref()));
assert_eq!(pad, exp_pad);
let data_in = vec![1; UserDataType::SgnEcSECP521R1.max() - 1];
let user_data = UserData::new(Some(ec.clone()), data_in.clone()).unwrap();
let exp_pad = Some(vec![0; UserData::USER_DATA_SIZE - data_in.len()]);
let (data_out, pad) = user_data.data();
assert_eq!(data_out, Some(data_in.as_ref()));
assert_eq!(pad, exp_pad);
}
}

View File

@@ -2,13 +2,12 @@
//
// Copyright IBM Corp. 2023
use super::AddSecretMagic;
use crate::{
request::MagicValue,
requires_feat,
uv::{uv_ioctl, UvCmd, UvDevice},
Error, Result, PAGESIZE,
};
use pv_core::request::{uvsecret::AddSecretMagic, MagicValue};
use std::io::Read;
use std::usize;
@@ -64,7 +63,7 @@ impl AddCmd {
let mut data = Vec::with_capacity(PAGESIZE);
bin_add_secret_req.read_to_end(&mut data)?;
if !AddSecretMagic::starts_with_magic(&data[..6]) {
if !AddSecretMagic::starts_with_magic(&data) {
return Err(Error::NoAsrcb);
}
Ok(Self(data))

View File

@@ -45,6 +45,12 @@ pub struct CertVerifier {
offline: bool,
}
impl std::fmt::Debug for CertVerifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("CertVerifier")
}
}
impl HkdVerifier for CertVerifier {
/// This function verifies a host-key
/// document. To do so multiple steps are required:

View File

@@ -3,11 +3,10 @@
// Copyright IBM Corp. 2023
use crate::error::bail_hkd_verify;
use crate::misc::{memeq, read_crls};
use crate::misc::read_crls;
use crate::HkdVerifyErrorType::*;
use crate::{Error, Result};
use curl::easy::{Easy2, Handler, WriteError};
use libc::c_int;
use log::debug;
use openssl::{
asn1::{Asn1Time, Asn1TimeRef},
@@ -26,9 +25,7 @@ use openssl_extensions::{
akid::{AkidCheckResult, AkidExtension},
crl::X509StoreExtension,
};
use std::cmp::Ordering;
use std::time::Duration;
use std::usize;
use std::{cmp::Ordering, ffi::c_int, time::Duration, usize};
/// Minimum security level for the keys/certificates used to establish a chain of
/// trust (see https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_auth_level.html
@@ -219,7 +216,7 @@ fn name_data_eq(entries: &X509NameRef, nid: Nid, rhs: &[u8]) -> bool {
let mut it = entries.entries_by_nid(nid);
match it.next() {
None => false,
Some(entry) => memeq(entry.data().as_slice(), rhs),
Some(entry) => entry.data().as_slice() == rhs,
}
}
@@ -376,11 +373,12 @@ fn check_x509_name_equal(lhs: &X509NameRef, rhs: &X509NameRef) -> Result<()> {
}
for l in lhs.entries() {
let ldata = l.data().as_slice();
// search for the matching value in the rhs names
// found none? -> names are not equal
if !rhs.entries().any(|r| memeq(ldata, r.data().as_slice())) {
if !rhs
.entries()
.any(|r| l.data().as_slice() == r.data().as_slice())
{
bail_hkd_verify!(IssuerMismatch);
}
}
@@ -484,20 +482,20 @@ mod test {
let ibm_wrong_subj = load_gen_cert("ibm_wrong_subject.crt");
let no_sign_crt = load_gen_cert("inter_ca.crt");
assert!(super::get_ibm_z_sign_key(&vec!(ibm_crt.clone())).is_ok());
assert!(super::get_ibm_z_sign_key(&[ibm_crt.clone()]).is_ok());
assert!(matches!(
super::get_ibm_z_sign_key(&vec!(ibm_crt.clone(), ibm_crt.clone())),
super::get_ibm_z_sign_key(&[ibm_crt.clone(), ibm_crt.clone()]),
Err(Error::HkdVerify(ManyIbmSignKeys))
));
assert!(matches!(
super::get_ibm_z_sign_key(&vec!(ibm_wrong_subj)),
super::get_ibm_z_sign_key(&[ibm_wrong_subj]),
Err(Error::HkdVerify(NoIbmSignKey))
));
assert!(matches!(
super::get_ibm_z_sign_key(&vec!(no_sign_crt.clone())),
super::get_ibm_z_sign_key(&[no_sign_crt.clone()]),
Err(Error::HkdVerify(NoIbmSignKey))
));
assert!(super::get_ibm_z_sign_key(&vec!(ibm_crt.clone(), no_sign_crt.clone())).is_ok(),);
assert!(super::get_ibm_z_sign_key(&[ibm_crt, no_sign_crt]).is_ok(),);
}
#[test]

View File

@@ -7,43 +7,30 @@
use super::{helper, helper::*, *};
use crate::{Error, HkdVerifyErrorType::*};
use core::slice;
use once_cell::sync::OnceCell;
use openssl::stack::Stack;
use std::sync::Mutex;
use crate::test_utils::*;
pub fn mock_endpt(res: &str) -> mockito::Mock {
static MOCK_SERVER: OnceCell<Mutex<mockito::Server>> = OnceCell::new();
let res_path = get_cert_asset_path(res);
mockito::mock("GET", format!("/crl/{res}").as_str())
MOCK_SERVER
.get_or_init(|| mockito::Server::new_with_port(1234).into())
.lock()
.expect("COULD NOT GET THE MOCK_SERVER LOCK")
.mock("GET", format!("/crl/{res}").as_str())
.with_header("content-type", "application/pkix-crl")
.with_body_from_file(res_path)
.create()
}
#[track_caller]
fn verify_sign_error(exp_raw: libc::c_int, obs: Error) {
verify_sign_error_slice(&[exp_raw], obs)
}
fn verify_sign_error_slice(exp_raw: &[libc::c_int], obs: Error) {
if exp_raw
.into_iter()
.filter(|e| match &obs {
Error::HkdVerify(ty) => match ty {
IbmSignInvalid(err, _d) => &&err.as_raw() == e,
_ => false,
},
e => panic!("Unexpected error type: {e:?}"),
})
.count()
== 0
{
panic!("Error {obs:?} did not match one of the expected {exp_raw:?}");
}
}
impl std::fmt::Debug for CertVerifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("CertVerifier")
}
#[test]
fn mockito_server_available() {
let _mock = mock_endpt("ibm.crt");
}
#[test]
@@ -51,7 +38,7 @@ fn store_setup() {
let ibm_str = get_cert_asset_path_string("ibm.crt");
let inter_str = get_cert_asset_path_string("inter.crt");
let store = helper::store_setup(&None, &vec![], &vec![ibm_str, inter_str]);
let store = helper::store_setup(&None, &[], &[ibm_str, inter_str]);
assert!(store.is_ok());
}
@@ -63,7 +50,7 @@ fn verify_chain_online() {
let mock_inter = mock_endpt("inter_ca.crl");
let mut store = helper::store_setup(&Some(root_crt), &vec![], &vec![]).unwrap();
let mut store = helper::store_setup(&Some(root_crt), &[], &[]).unwrap();
download_crls_into_store(&mut store, slice::from_ref(&ibm_crt)).unwrap();
let store = store.build();
@@ -71,8 +58,8 @@ fn verify_chain_online() {
let mut sk = Stack::<X509>::new().unwrap();
sk.push(inter_crt).unwrap();
verify_chain(&store, &sk, &vec![ibm_crt.clone()]).unwrap();
assert!(verify_chain(&store, &sk, &vec!(ibm_crt)).is_ok());
verify_chain(&store, &sk, &[ibm_crt.clone()]).unwrap();
assert!(verify_chain(&store, &sk, &[ibm_crt]).is_ok());
}
#[test]
@@ -82,13 +69,13 @@ fn verify_chain_offline() {
let inter_crt = load_gen_cert("inter_ca.crt");
let root_crt = get_cert_asset_path_string("root_ca.chained.crt");
let store = helper::store_setup(&Some(root_crt), &vec![inter_crl], &vec![])
let store = helper::store_setup(&Some(root_crt), &[inter_crl], &[])
.unwrap()
.build();
let mut sk = Stack::<X509>::new().unwrap();
sk.push(inter_crt).unwrap();
assert!(verify_chain(&store, &sk, &vec![ibm_crt]).is_ok());
assert!(verify_chain(&store, &sk, &[ibm_crt]).is_ok());
}
#[test]
@@ -107,8 +94,8 @@ fn verify_online() {
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crl = get_cert_asset_path_string("ibm.crl");
let verifier = CertVerifier::new(
&vec![ibm_crt, inter_crt],
&vec![ibm_crl, inter_crl],
&[ibm_crt, inter_crt],
&[ibm_crl, inter_crl],
&Some(root_crt),
false,
)
@@ -148,8 +135,8 @@ fn verify_offline() {
let hkd = load_gen_cert("host.crt");
let verifier = CertVerifier::new(
&vec![ibm_crt, inter_crt],
&vec![ibm_crl, inter_crl],
&[ibm_crt, inter_crt],
&[ibm_crl, inter_crl],
&Some(root_crt),
true,
)
@@ -172,107 +159,6 @@ fn verify_offline() {
));
}
#[test]
fn verifier_new() {
let root_chn_crt = get_cert_asset_path_string("root_ca.chained.crt");
let root_crt = get_cert_asset_path_string("root_ca.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let inter_fake_crt = get_cert_asset_path_string("fake_inter_ca.crt");
let inter_fake_crl = get_cert_asset_path_string("fake_inter_ca.crl");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crt = get_cert_asset_path_string("ibm.crt");
let ibm_early_crt = get_cert_asset_path_string("ibm_outdated_early.crl");
let ibm_late_crt = get_cert_asset_path_string("ibm_outdated_late.crl");
let ibm_rev_crt = get_cert_asset_path_string("ibm_rev.crt");
// To many signing keys
let verifier = CertVerifier::new(
&vec![ibm_crt.clone(), ibm_rev_crt.clone()],
&vec![],
&None,
true,
);
assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys))));
// no CRL for each X509
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_crt.clone()),
false,
);
verify_sign_error(3, verifier.unwrap_err());
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_crt.clone()],
&vec![],
&Some(root_chn_crt.clone()),
false,
);
verify_sign_error(3, verifier.unwrap_err());
// wrong intermediate (or ibm key)
let verifier = CertVerifier::new(
&vec![inter_fake_crt, ibm_crt.clone()],
&vec![inter_fake_crl],
&Some(root_chn_crt.clone()),
true,
);
//Depending on the OpenSSL version different error codes can appear
verify_sign_error_slice(&[20, 30], verifier.unwrap_err());
//wrong root ca
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_crt.clone()],
&vec![inter_crl.clone()],
&None,
true,
);
verify_sign_error(20, verifier.unwrap_err());
//correct signing key + intermediate cert
let _verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
)
.unwrap();
// no intermediate key
let verifier = CertVerifier::new(
&vec![ibm_crt.clone()],
&vec![],
&Some(root_chn_crt.clone()),
false,
);
verify_sign_error(20, verifier.unwrap_err());
//Ibm Sign outdated
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_early_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_late_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
// revoked
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_rev_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
verify_sign_error(23, verifier.unwrap_err());
}
#[test]
fn dist_points() {
let crt = load_gen_cert("ibm.crt");

View File

@@ -1,13 +1,19 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![cfg(all(feature = "request", feature = "uvsecret"))]
use openssl::{
ec::{EcGroup, EcKey},
nid::Nid,
pkey::Private,
};
use pv::{
get_test_asset,
request::{
openssl::pkey::{PKey, Public},
uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
uvsecret::{
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion,
ExtSecret, GuestSecret,
},
BootHdrTags, ReqEncrCtx, Request, SymKey,
},
test_utils::get_test_keys,
@@ -18,7 +24,7 @@ use pv::{
const TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]);
const CUID: ConfigUid = [0x42u8; 16];
const ASSOC_SECRET: [u8; 32] = [0x11; 32];
const ASSOC_ID: &'static str = "add_secret_request";
const ASSOC_ID: &str = "add_secret_request";
fn create_asrcb(
guest_secret: GuestSecret,
@@ -38,7 +44,7 @@ fn create_asrcb(
};
asrcb.add_hostkey(hkd);
Ok(asrcb.encrypt(ctx)?)
asrcb.encrypt(ctx)
}
fn get_crypto() -> (PKey<Public>, ReqEncrCtx) {
@@ -63,17 +69,10 @@ where
{
let (host_key, ctx) = get_crypto();
let cuid = match cuid {
true => Some(CUID.into()),
true => Some(CUID),
false => None,
};
create_asrcb(
guest_secret,
ext_secret.into(),
flags,
cuid.into(),
host_key,
&ctx,
)
create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx)
}
fn association() -> GuestSecret {
@@ -92,6 +91,93 @@ fn no_flag() -> AddSecretFlags {
AddSecretFlags::default()
}
fn create_signed_asrcb(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
let (host_key, ctx) = get_crypto();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
asrcb.add_hostkey(host_key);
asrcb.set_user_data(user_data, Some(skey)).unwrap();
asrcb.encrypt(&ctx).unwrap()
}
#[test]
fn null_none_default_ncuid_one_user_unsgn() {
let user_data_orig = vec![0x56; 0x183];
let (host_key, ctx) = get_crypto();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
asrcb.add_hostkey(host_key);
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
let asrcb = asrcb.encrypt(&ctx).unwrap();
let user_data = verify_asrcb_and_get_user_data(asrcb, None).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_one_user_ec() {
let (usr_sgn_key, _) = get_test_keys();
let usr_vrfy_key = usr_sgn_key.ec_key().unwrap();
let usr_vrfy_key = usr_vrfy_key.public_key();
let usr_vrfy_key = PKey::from_ec_key(
EcKey::from_public_key(
&EcGroup::from_curve_name(Nid::SECP521R1).unwrap(),
usr_vrfy_key,
)
.unwrap(),
)
.unwrap();
let user_data_orig = vec![0x56; 0x100];
let asrcb = create_signed_asrcb(usr_sgn_key, user_data_orig.clone());
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_one_user_rsa2048() {
let usr_sgn_key = get_test_asset!("keys/rsa2048key.pem");
let usr_sgn_key = PKey::private_key_from_pem(usr_sgn_key).unwrap();
let user_data_orig = vec![0x56; 0x100];
let asrcb = create_signed_asrcb(usr_sgn_key, user_data_orig.clone());
let usr_vrfy_key = get_test_asset!("keys/rsa2048key.pub.pem");
let usr_vrfy_key = PKey::public_key_from_pem(usr_vrfy_key).unwrap();
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_one_user_rsa3072() {
let usr_sgn_key = get_test_asset!("keys/rsa3072key.pem");
let usr_sgn_key = PKey::private_key_from_pem(usr_sgn_key).unwrap();
let user_data_orig = vec![0x56; 0x80];
let asrcb = create_signed_asrcb(usr_sgn_key, user_data_orig.clone());
let usr_vrfy_key = get_test_asset!("keys/rsa3072key.pub.pem");
let usr_vrfy_key = PKey::public_key_from_pem(usr_vrfy_key).unwrap();
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_cuid_one() {
let asrcb = gen_asrcb(GuestSecret::Null, None, no_flag(), true).unwrap();
@@ -156,9 +242,18 @@ fn null_none_default_cuid_seven() {
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
(0..7).for_each(|_| asrcb.add_hostkey(hkd.clone()));
asrcb.set_cuid(CUID.into());
asrcb.set_cuid(CUID);
let asrcb = asrcb.encrypt(&ctx).unwrap();
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_seven");
assert_eq!(asrcb, exp);
}
#[test]
fn verify_no_user_data() {
let req = get_test_asset!("exp/asrcb/null_none_default_ncuid_one");
assert!(matches!(
verify_asrcb_and_get_user_data(req.to_vec(), None),
Ok(None)
))
}

View File

@@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIAiQERmhEIwksExNpW2CsaQeFPKFEN6PIdMWUiX3Mu7FoAcGBSuBBAAK
oUQDQgAEO9U+NDdHcIZ59RzTq/6lDH2RA7sEyRlvFVQsj7feiuXh4Q4qeZNCb2sn
s7Hd1lUjFexH+Cp97/ggwSa3q2y/0Q==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDZzCCAk+gAwIBAgIUK4kgzElokcI7s6XNCRD1iA7NfMQwDQYJKoZIhvcNAQEL
BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDAxMjkxNzAxMjRaGA8zMDIzMDYw
MTE3MDEyNFowQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAOYCZGvIP+X8Y8gpHnjg57Oo45k+jsO1GeZGBtM+MfXP1HaV
bT48VlaxaLB+K0GbQ5ykBdP7Xa1nLdbFVWtmATkbrS78xzUsefpXG8WKtA3gtE9/
v3licc34dBHiDBNrZf/elC2P3EZOkwX7AYJSlhg5uehgy0Jp4MvAWZMHOgDNRf41
Tr/jCpuntUKUcdv9+CdhzD0Z634gzPckO4QXWKaBKwa8AU4GTb41/RTOF6U5Zh1Q
exx4rdf8h7s3sbfEa5ELLpMKSUeWXd7LVFUM6TSP+/zTgRxLHmiM3gGEawYFEvla
UluGqGtC2MVpu0bGQvxgJmuy0cw7rPE5RzudIOUCAwEAAaNTMFEwHQYDVR0OBBYE
FBheoZmtoNcYxXMwzZsujv51NgslMB8GA1UdIwQYMBaAFBheoZmtoNcYxXMwzZsu
jv51NgslMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADeRY7AS
waBH/YnZ/q/cixkaeMqtM9zRpOAVnBGOEbQiLVFhLXaT7XMT8r9JjbbYQW7RgaQS
fOHIxlG4jjly+/kXluF+Di54dMwrMCeAbxg7C/QQvMGVEWCufoJ9UGXWCzdZTziO
xREupk7RvjWUZL88kO9peGgsAelnvjWtgwEXpYpfOY7zwLvvm9RR7qujfjRMsip+
WTGjh4hksGQ9Yh8yFTx96QEnhZ69sU6ohtagOdW0/G2Yeh4fO/k77Afq1KNRatrZ
W3IeLGIVZIfajxfCYQ/ARmJg3I1h1Hw1pnw50h6Q8AQf50s0MtxIe6UaVUz4KqJH
RgLQPVsWXaWw5KA=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDmAmRryD/l/GPI
KR544OezqOOZPo7DtRnmRgbTPjH1z9R2lW0+PFZWsWiwfitBm0OcpAXT+12tZy3W
xVVrZgE5G60u/Mc1LHn6VxvFirQN4LRPf795YnHN+HQR4gwTa2X/3pQtj9xGTpMF
+wGCUpYYObnoYMtCaeDLwFmTBzoAzUX+NU6/4wqbp7VClHHb/fgnYcw9Get+IMz3
JDuEF1imgSsGvAFOBk2+Nf0UzhelOWYdUHsceK3X/Ie7N7G3xGuRCy6TCklHll3e
y1RVDOk0j/v804EcSx5ojN4BhGsGBRL5WlJbhqhrQtjFabtGxkL8YCZrstHMO6zx
OUc7nSDlAgMBAAECggEACx1seuTnaZbDv6V1CoRbzgvIS9yBC6YRxVX2+HhV87oC
W2SnLc2+olvaYkUGlBoLr2MhwPextDJ7PZi1owX/4yGaQd4HRmOK1XUmK2qjftQw
AnN67kO6mih6e27ABKQ4/p9j67u6+k7Lvgs+LG0PQcZwXHFpASGasl3uNjOwMt+h
IJxo1UJcNM4s0WyFLWWg7dWHE/m8QWA0Qc9zBL3cIewTgr6SiTTgQGeNFpSQThgJ
E1G9cpLvchKLCcsc3L8/03ZJX00R/GyBYcJsvsTfsva5FzFG6OSt4G+73jthEC8E
jUeI0D/tw27XjBGRYNbMlsdIHqdTWxraB1HiK3CpIQKBgQD2nYXrr5isnlOSaeww
de1QMUTY7A/IUtYGknZxCj1Q/xS483er/bmfrNRAo2YMGqO/vcJxqy7vA3yACMqR
mUP8XInkfkTILYaLEECfW4+g6/f6wgBnFdIjn4vqR8XGPFwYX6IoSvzjh5iqOTon
LVx1v2Co9cdvFiD+rGT2t9FZyQKBgQDuwxjWw2bx0iH4CXQRtqv97lDu/eB9SiIl
Nb0AYGYf6/QhVkVxOBjUHitgSDWdOy2vJ8tojuxM2qT2SmGYFmjI33WAReHvPPE0
BzjIHdCAzKRJgHk02Sh86vw9NMDiGE37Tc9GdGNz8hXF4BTyuPbnDu7ta54bn7JQ
U4xVQgLcPQKBgFnM0k8YCcgRfuz5lP+wcVGaHy++BaE0SKrr+iT/TYSI3crbHOCL
62OxUziWRzpxGN9hAcTSSKzP4C3fBaQu4wqpx6xkLjeWHD/XEzDz7kDyq8Grkun2
kCZYmmIc8G9W21IkwK9bXuqbHYQegoZxhhF6NoBPvuc8b5dMvxb3qEvBAoGBAJaL
556LCN8TWx3VgB0nLF1pphh7ddAqLUt/DdQt9FbOZVmb1x9jeqxM9Za5ES05wj19
/xDHhZGUD43IKq9F+f4mgCVIawR5TbE7mH795ZlA75n06vf6sT/Kvw8ssy4cmxcI
4dgPzSGvlDD3cN326J+aV+bWA5RxZnKlq4eanskVAoGBAK+QvIp17Lq2RdcBcFr3
1Do0M2tDzjBgdzIp+8bfJHSfe3v4EEupeNa2tD2lnRlETc2JjIMFx1SMHYTi9+ax
Iuhwl7txcijtXT+5kUO4Km8oiQGbskJLwH1iVAGIMMASJf8lQHODby2R8x3OUQnX
YdC+wJJq6gfNo436A4lkPQsM
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5gJka8g/5fxjyCkeeODn
s6jjmT6Ow7UZ5kYG0z4x9c/UdpVtPjxWVrFosH4rQZtDnKQF0/tdrWct1sVVa2YB
ORutLvzHNSx5+lcbxYq0DeC0T3+/eWJxzfh0EeIME2tl/96ULY/cRk6TBfsBglKW
GDm56GDLQmngy8BZkwc6AM1F/jVOv+MKm6e1QpRx2/34J2HMPRnrfiDM9yQ7hBdY
poErBrwBTgZNvjX9FM4XpTlmHVB7HHit1/yHuzext8RrkQsukwpJR5Zd3stUVQzp
NI/7/NOBHEseaIzeAYRrBgUS+VpSW4aoa0LYxWm7RsZC/GAma7LRzDus8TlHO50g
5QIDAQAB
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEZzCCAs+gAwIBAgIUK67CEnpp4Hbp9awAbW67M6M8t9cwDQYJKoZIhvcNAQEL
BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDAxMjkxNzAxMzZaGA8zMDIzMDYw
MTE3MDEzNlowQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCAaIwDQYJKoZIhvcNAQEBBQAD
ggGPADCCAYoCggGBAJy1pz6/Xzq9Gicai7se9HC49cPATTPXrcdVjCi11LiPDMoL
eFCeCJhXCBNLpWWDTcl2t1EFTuIv9SDguY4nWgaxVaOz0c0FLXi0DCLEUUwjPt4L
EZoRxwOAbut1N3ACE1NY0cQnDhqHUeFehp4wXUuAmCk+z3zPVRNhFjjBKXcLOphQ
ak/3C8/ajJJBznXC4r8o4MNZSl6BjJxfLArpC4xM8/5rQ2qu1CoW6nrDLeFRfFGu
CvhJ89NHnvqvUjDSb+G5gfBRFwsjw3rI80o8VYIPeM79PWILy5nMtWZDQ/J6L713
eRhrXyr42kre3c6tkkSjfciEUTwyXxcCOThX/wnN8mWg9PQY7UqyrR8mPFbSFTi0
CIL8nPi3U9tcpHOMKzrVyaYELeNhUNpDfkWE5s1XYdfiDl3Jd60GxOKpU9gCgof8
Sf21cSIcHCITqjG6Oga10ZxeCZrGEZrm/46p+KWXuWL+Tp12SvQqm7Fjc7W8MjSC
ZXEWltQcWzgq+DkOfQIDAQABo1MwUTAdBgNVHQ4EFgQU9QUBlQYenYdS6Jeo0//F
4S+xRZEwHwYDVR0jBBgwFoAU9QUBlQYenYdS6Jeo0//F4S+xRZEwDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAYEAOjinmH5+2smoKj0fkKmgFWQqH2St
TbwtC4M7xJpge4uwEj/48CZPECZzMmkKs//x1bRepX3wBfCSrVZNjoynFxZtyjCA
2Ss86F3h/FGQaLJoIGUJN1EW8MIqxQRkv0KRBPKiGay2xocErjNjxlkDeyuDliyP
y5GUF4h+pgjlVO0b5uR03loYty65jmHucPijGkF7Kgr208SCOXArWZEyLZx1Pian
PiVxJP2mnsK9wsBBNvjH1DB+gMbOW3v6nl/2xD1Q/Lz0gif7fZZiTTnt47mlqD4d
QWOH22uPO1EMG7K66CgYQd2mo3ZKpMhPeAC3OajEnY2hXOeI/w3dsvaMwIAMUQgu
y585iOfifUgEwXXAgGS0Hq1nQwDK1TjvM5sP7nbBfIlPwI78NoK9QM7p/O7UvccD
UHAjsDgmU5q/hhqs6v6gvlX3GqOnFSdviUQGeX2l9Hw6lFWM9HIKC968ykzlp9m6
uOv1xi5CIF/ZqIkDF//ZdISgZuyB4zWOcRIT
-----END CERTIFICATE-----

View File

@@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCctac+v186vRon
Gou7HvRwuPXDwE0z163HVYwotdS4jwzKC3hQngiYVwgTS6Vlg03JdrdRBU7iL/Ug
4LmOJ1oGsVWjs9HNBS14tAwixFFMIz7eCxGaEccDgG7rdTdwAhNTWNHEJw4ah1Hh
XoaeMF1LgJgpPs98z1UTYRY4wSl3CzqYUGpP9wvP2oySQc51wuK/KODDWUpegYyc
XywK6QuMTPP+a0NqrtQqFup6wy3hUXxRrgr4SfPTR576r1Iw0m/huYHwURcLI8N6
yPNKPFWCD3jO/T1iC8uZzLVmQ0Pyei+9d3kYa18q+NpK3t3OrZJEo33IhFE8Ml8X
Ajk4V/8JzfJloPT0GO1Ksq0fJjxW0hU4tAiC/Jz4t1PbXKRzjCs61cmmBC3jYVDa
Q35FhObNV2HX4g5dyXetBsTiqVPYAoKH/En9tXEiHBwiE6oxujoGtdGcXgmaxhGa
5v+Oqfill7li/k6ddkr0KpuxY3O1vDI0gmVxFpbUHFs4Kvg5Dn0CAwEAAQKCAYAP
wpkaT2bFYoVBz4KkA/3zZDabRDi/cP5yIKFi6r6E/SX9H+Auw2X75CzUu/PbXLQz
AbR3+n+vccFGWDNxH3YHXEhPmWMNKmsjS4A48uN2M8k7zYMsqoMIUB9XoMc9hYfq
BvG5t2/MrTVH5BjHyfdoA3vKEh4N4FAfDdGNTafc6A0xikzW0/KlKcafYuzfnQTu
0q6gFtfK316Dz3J/emaF6qtTN/hkv0ZpITrds8lmcqp9EHLb+5wA8KAFeCJc9JzR
dwLUf1e5eY51kQK+Iq7BsS0NujXCpEFmmUnjfrODyjkuF14ezFCCrYLz+EOdJEzV
ktCjwopSV53EkjstktkS91szUv2tWMZ9V7cZFXyHvrnmbMXOS+wqU/6gV4ZUT5XK
e7EH4mE4kre2ho2NVXHJjQWNB4ok+9+4ThmwUwhtT3+55we8u9WjjARrqZCewPq7
enmMgPJ24M17CTFd370Wufx+X1ydLdxIYXj2Q06bbyAtc+l6iVjffjoRSR0PImUC
gcEA2oRbvnTM/RqgXV39+aUGALsXOqQsWoFQIJmebseQbthf3N2GrgQx6EZZld93
EvWBLoN8/eTCAjoVRWYbX/DIr+lHXV6MmvhytUuQdpKXgmV2nXzEPSFbWKI8oHXu
rArNyHiawsiEFyzY2pKkFa8TuJ61H3zPKqZMCkH1Lb0ozqr/0hFjS7SDEbKelX0n
K66uJzktj62Qb3qLuqn/CcRokG3g5PyItVycX+TWyXiKFPk/pWfRrTcshazSLhOD
sJ1nAoHBALeXLAo8c1mICGM8QnJxma047jnWqKt8zwaXoTWu7pJwy8luUgkNDdau
FtjW3h1AcBiV6mWXXGNqXBETaBPoBbDXVtZ1QA6c0JKZKSJb4iaGHOkuopNksjcW
4RKn/rCho0eov1prbvSkAi9ghsLZFRxNgkPohazxCdZbPf+L7h7mwRLLpfTxawAS
zIZprbKDlaSijGV10yuOeHtrjnrVNleeApTtu1In/wxM3R0yYoK/d4cPbrt9Z2ZH
s6C/i4RiewKBwQCuvrQDXbJkIbvmjnjBACgJw5Py8EEL62dhepfJLUwiaGRY4sqG
NJKYllGj2b2Gas/wm3QEMcC4OSyzf9EaaxqCGoJYd3jnoRFSS+ht+hKxU2SE0ra3
0AoVmj/zbfv8lvMzgsb4R5PiWJneSPBw6r5RoJFladDd4XjjpxVji2arxvGnUwJe
Ey/Rok7RavE9LIbWlRILWRgtHgnJr5JlhCy+ezAizpSgvDvNL9pX6U47MYulQZ3a
8ElpaOf3suFYBrcCgcBmfrNLk6wiU9u66y19jwZoutvk1n6cHrXFzGln+x2eC/L5
pemOvAb5z39Z17txQURTtX6C8RnXNs3nipoUYviqXc5RNGxqvAmFjpR0Zi+/qigB
1tNUzmbgcpl9POTn35W0r/UEme38DoRZxt31chRjFLKhDvNyNa90SMQ4IvrxrYok
4CTqts0x4UzlzaH3g5bLryQKkNkv+/4viUCmOtp31/NBCegGWeX+C/t2DWcJ77Lf
aDI94ikT5PvXxpgxdHkCgcAw5MMLz6i30bQOCEQdGurjbev3oFmHyNFIxkNmUdrA
/4ixCLSXW/m2s+T09qLIFbyABNK9VvTKVvM0V4l4ZvvuZdhLCrSZTCnXBmNBhFq1
UCa1BvE8f98dDcDNTp7ICjTOaVk4ZB8yhL0f4kMIs1jJZ1XoWCbsRS6Y2YSsF6IH
Ntzs7smF3ExIaDlYrVxB/K0TPAXPcCzQ+np9KspLinY+gCThAzQ057YxzRaj00I4
ErvUlL2xPdMTZ14eNgDwQk8=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnLWnPr9fOr0aJxqLux70
cLj1w8BNM9etx1WMKLXUuI8Mygt4UJ4ImFcIE0ulZYNNyXa3UQVO4i/1IOC5jida
BrFVo7PRzQUteLQMIsRRTCM+3gsRmhHHA4Bu63U3cAITU1jRxCcOGodR4V6GnjBd
S4CYKT7PfM9VE2EWOMEpdws6mFBqT/cLz9qMkkHOdcLivyjgw1lKXoGMnF8sCukL
jEzz/mtDaq7UKhbqesMt4VF8Ua4K+Enz00ee+q9SMNJv4bmB8FEXCyPDesjzSjxV
gg94zv09YgvLmcy1ZkND8novvXd5GGtfKvjaSt7dzq2SRKN9yIRRPDJfFwI5OFf/
Cc3yZaD09BjtSrKtHyY8VtIVOLQIgvyc+LdT21ykc4wrOtXJpgQt42FQ2kN+RYTm
zVdh1+IOXcl3rQbE4qlT2AKCh/xJ/bVxIhwcIhOqMbo6BrXRnF4JmsYRmub/jqn4
pZe5Yv5OnXZK9CqbsWNztbwyNIJlcRaW1BxbOCr4OQ59AgMBAAE=
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDpMm6nXtXh9qK+
1wbdeNaYbLLwDcBa9JtNp/Ewz+uFbCuvbdHTyigWtGn7gHtALf/SzVIIhiLPFJ3R
4BKPZN9SXz0KqmI27IQL+WPSNZCW1XLlmHj5YCsGXnrE+ogpd/addDes3ByuVJ84
Sm6FuRzjwb4x7k/D01+tFRhSg9uujwr93FMjimWd0T29za8RTOvh3/lrY4nIDjvx
pcUD9M/C7w8VcvtxQNZmgnN8KYLL5SMpVjJgTKLwMgZRP8XclX/BtMMdeRvd86KC
QYUVVJqrs8kBslZqGIJ0K1en32/KpzFKj7lJ6/lkPZwQcfmaMdZnHEwbUkloy63S
GJSFsq4NRnmPc1WaOnvyk+DCRh4eehFxJ62FRm8nT+SaHEzugZQLAm4ZvV7DB8MP
TgXarFrEYYdtKP6d1/+n0CCKT0jCvj4FRVZgh2nLqUpi3mDd3cid2cZyG6GktyV8
2j9WsJ4rbrLAuGl8QUvmpnAdPVGNkij39AB2S6nPfRZIpYUcfVKBgpDFvLscmPwp
lR0IwtOq3Z8jo7n9S/Y07gY75lWBur6FiYfLGgdlWIHdZSpO12mABc5jVsivMDwi
3/XVVGT7l0ELRbVlDEFXWZlholRdsfxqmr0O/GsRN0g9+aiqaKBkKI2eblooQCBW
AGbVEEggUSW2RQ5Z9c9bSAIRGKVNVQIDAQABAoICABN0gHee/9t3bVVClz4IhAH5
DGZFBdR3LjKPKlFXAPoDb9/8GrjypEZdcuIapZQeW+0R8bm1IL5QcZvXqw5V7Ubw
AljoPuozvUIXSLt6E8AIISsEF3bZ2rBDVza/BN3nNkBYnwFHs9TcSa3mkWXyonUv
h4o4eZfuA8A3tez2BApO8/nN7qaIBOyvtXKNigEfGI08CsQ2bIF8pOxtVtlHpijh
mbHBnM/Y1IpLPXTd+bcmox8iij1igQCzch3hL8UbBXeU3xpcYKjM8YF9RqvkgESG
6uQk8fStnf4wurVn4xzv6e4KI5YGBXgJc89Wn7fviuJuh++9/VKse3a/U9TIdwck
gFknia4j/ThU0t8gCppTcu2iXRmIKYr7jBPhd/7axxPEfA17gGJ7suslm/S/5MGn
+EEm04FjhoSAHO9Ny5jnZUQ01Lurg0d0se3rrBJzYWiWn+CS30bnhrqjiQARoBpn
HtqRDGqfvt1H0N5l10/4AoqGw3BfFhGmUBhTkfLeR8MqiK8VgaVH8GlPfGVQNJgi
B/0Q1Wvu2sAFWh5xFUNceJv2hsVt/s6V8SLLB4bN9OULBwL62teB3RgFezYwVC/i
AGSOo56JflZJFDeqYrMv+UACwIUaqm739aVfCd2kda+Ef1YCWfilWloUghQHsDGJ
WtKmQgiZCow3bpf3373ZAoIBAQD+YXKT2nY1L4GsfJJoCKh48P1yO52qbt/K8ce8
vNI+ZOwCNsOXyAdcnlYmd9GiABzOlHxkjWmfAgNE9suzvKGRHnfpYLh7RPt9f9CX
ltWlkcrNxcGAyG8XPb59ygr6o69US31U9/oiC8/WJeue5x+KyaatcjNad087qUVM
SYGb4fgZ0YLhVKDUvq3zbC8D6z2vN7t5gLGEB8VvC2DY0tLi8u2PEY6laVbyPWIv
5OBbp7XHWvDS+bMKYUJATHr2DF0K10IC/DZZMGf/V2rrzjR76gZDkyZPclTZv4Ec
EQr37Ssc8Wa7IWisSEmKVO6HKbpaUE/fBRMmzAa8ikmkVTB7AoIBAQDqrnZwI25x
zdcMD8es7ymlVfb9xwymxp3rSaLvvaSbdGjnw+B1gnNo91eJGPJUbgWwMDlO9wch
3Y9B2UIMLQfr0+OP3lWvyVqdCK8LHtkH2FJhZtEGu099EZNKvTtU4b47fna6Vcll
K0o11CzsE+TuLWZNhjriEA/xCUqR7gxm8cB+nDzuDqW4GYpk/JuAYLQwcI0dgMjH
5j9le94Hz+YHkratanU98BQh/xOTVF2wfeiWeO83LgiugewiLdEENPzgCrZ72xUG
2qH0zO6Oe++9RW2JPNTXxOmTiX/fS3pvqf2hRGnQQHSLmBk//kkKL2cSVDXltsZf
19cG+1/+sVhvAoIBAQCAnOZgSNEBPP60JdukC9SaKUYLLocdt6cgpbPWAiXOdBo7
WFL196V5N3jZRDVSUfo+nCZ0dGcVhOAAS0NcoZ+SjrQT49oCQTNiFXByZEmqx5ah
CT43jZ9VQduBH9kDNiUcWnqIrkPiWNSBMnT5x2NUOXYzNY0vTSlbQc8Org+Ar/RP
oXUp1i7mwW2TqHvw4Ew0vdnkqaOW3JL+/OK/XYBQTIC9mCPmD+ds3Io7Kt4gmR+4
JouiL6tNMTVc6w40PrYAGCuA9OHJoEbEe/9ML9qrV/xIyCpTervZyBBQD3HbPidR
H+BP9qlEidvcu+pJnlhg90AawycWs4soYkavdn7nAoIBAE6UTCQQORLyEWeAgSNu
kUtDFYiz9ZCCQK5f4abUoSFMQTQZajbujyY+a5kEOV/71vu/qfC+1iyIu0cZL/xT
t6tHL5MkpxxNF4ItPi9DDPiLfkoO2z6dyWffUqpb4lnxnHjfR3Hs95pgfGC5wrDp
mGYH3ZFnuJ+pMS1MJyPlyVFO8V1zgxNS5DpYkvZtfsPlnW/6v8V5McF9XwzkBNwo
yKUQgwpd8xPiJ1uaxTs3rIdEIXUA2VhomY+VATQQk0QNWf7Kl3+xQ8Efw8rsM5SR
+xws+xhQvhPhYzR7fc2OwFhTWxF8jtGkQ2Hac9nxkxNN+/0vJTzdjl7ZLq6vzNzD
FRECggEACyj3OwkbDMnyCtCH9Wp/fLpARkpHN+LZecUL0V6Zt2Iy9aVLSZuVLe92
dA2UWcOEenMmMzON5bL+d9Nt+beyZaU8tQZYFXLLZ4j+mZZhWX6O5rHdZhlIEN4S
NSO72c9ikcp7+PPYh07hAKEFlGnv0WwvTsdCWovv9CrPrj/vkBUevKWSWRkff+pW
9kMmFgTpA2/9z5Cys45jVpUM5M5clTzNCUPBrVVVmpcTOwKUrwemy5m4PirNJgjg
YRfenxGajQkp/ypi8eh1m7ceYnOxa4Mk9YctaIym3lkGBqn/9hgoOrI0nPEDM+qQ
io4wIYBrX/SPmp7tQvWI18Ike/IDtA==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use pv::request::CertVerifier;
use pv::test_utils::*;
use pv::{Error, HkdVerifyErrorType::*};
use std::ffi::c_int;
#[track_caller]
fn verify_sign_error(exp_raw: c_int, obs: Error) {
verify_sign_error_slice(&[exp_raw], obs)
}
fn verify_sign_error_slice(exp_raw: &[c_int], obs: Error) {
if exp_raw
.iter()
.filter(|e| match &obs {
Error::HkdVerify(ty) => match ty {
IbmSignInvalid(err, _d) => &&err.as_raw() == e,
_ => false,
},
e => panic!("Unexpected error type: {e:?}"),
})
.count()
== 0
{
panic!("Error {obs:?} did not match one of the expected {exp_raw:?}");
}
}
#[test]
fn verifier_new() {
let root_chn_crt = get_cert_asset_path_string("root_ca.chained.crt");
let root_crt = get_cert_asset_path_string("root_ca.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let inter_fake_crt = get_cert_asset_path_string("fake_inter_ca.crt");
let inter_fake_crl = get_cert_asset_path_string("fake_inter_ca.crl");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crt = get_cert_asset_path_string("ibm.crt");
let ibm_early_crt = get_cert_asset_path_string("ibm_outdated_early.crl");
let ibm_late_crt = get_cert_asset_path_string("ibm_outdated_late.crl");
let ibm_rev_crt = get_cert_asset_path_string("ibm_rev.crt");
// Too many signing keys
let verifier = CertVerifier::new(&[ibm_crt.clone(), ibm_rev_crt.clone()], &[], &None, true);
assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys))));
// No CRL for each X509
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&Some(root_crt),
false,
);
verify_sign_error(3, verifier.unwrap_err());
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[],
&Some(root_chn_crt.clone()),
false,
);
verify_sign_error(3, verifier.unwrap_err());
// Wrong intermediate (or ibm key)
let verifier = CertVerifier::new(
&[inter_fake_crt, ibm_crt.clone()],
&[inter_fake_crl],
&Some(root_chn_crt.clone()),
true,
);
// Depending on the OpenSSL version different error codes can appear
verify_sign_error_slice(&[20, 30], verifier.unwrap_err());
// Wrong root ca
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&None,
true,
);
verify_sign_error(20, verifier.unwrap_err());
// Correct signing key + intermediate cert
let _verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
)
.unwrap();
// No intermediate key
let verifier = CertVerifier::new(&[ibm_crt], &[], &Some(root_chn_crt.clone()), false);
verify_sign_error(20, verifier.unwrap_err());
// IBM Sign outdated
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_early_crt],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_late_crt],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
// Revoked
let verifier = CertVerifier::new(
&[inter_crt, ibm_rev_crt],
&[inter_crl],
&Some(root_chn_crt),
false,
);
verify_sign_error(23, verifier.unwrap_err());
}

21
rust/pv_core/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "pv_core"
version = "1.0.0"
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "0.2.49"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
thiserror = "1.0.33"
utils = {path = "../utils"}
zerocopy = {version = "0.7", features = ["derive"]}
serde = { version = "1.0.139", features = ["derive"]}
byteorder = "1.3"
[dev-dependencies]
serde_test = "1"
mockito = {version = "1", default-features = false }
lazy_static = "1.1"

77
rust/pv_core/src/error.rs Normal file
View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
/// Result type for this crate
pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Error cases for this crate
#[allow(missing_docs)]
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[cfg_attr(debug_assertions, error("Ultravisor: '{msg}' ({rc:#06x},{rrc:#06x})"))]
#[cfg_attr(not(debug_assertions), error("Ultravisor: '{msg}' ({rc:#06x})"))]
Uv {
rc: u16,
rrc: u16,
msg: &'static str,
},
#[error("{0}")]
Specification(String),
#[error("Cannot {ty} {ctx} at `{path}`")]
FileIo {
ty: FileIoErrorType,
ctx: String,
path: String,
source: std::io::Error,
},
#[error("Cannot {ty} `{path}`")]
FileAccess {
ty: FileAccessErrorType,
path: String,
source: std::io::Error,
},
#[error("Cannot encode secrets (Too many secrets)")]
ManySecrets,
#[error("Cannot decode secret list")]
InvSecretList(#[source] std::io::Error),
#[error("Input does not contain an add-secret request")]
NoAsrcb,
#[error("Input contains unsupported user-data type: {0:#06x}")]
UnsupportedUserData(u16),
// errors from other crates
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
}
/// Error cases for I/O operations
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum FileIoErrorType {
#[error("read")]
Read,
#[error("write")]
Write,
}
/// Error cases for accessing files
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum FileAccessErrorType {
#[error("open")]
Open,
#[error("create")]
Create,
}

93
rust/pv_core/src/lib.rs Normal file
View File

@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![deny(missing_docs)]
#![allow(unused)]
//! pv_core - basic library for pv-tools
//!
//! This library is intened to be used by tools and libraries that
//! are used for creating and managing IBM Secure Execution guests.
//! `pv_core` provides abstraction layers for secure memory management,
//! logging, and accessing the uvdevice.
//!
//! It does not provide any cryptographic operations through OpenSSL.
//! For this use `pv` which reexports all symbos from this crate.
mod error;
mod log;
mod macros;
mod tmpfile;
mod utils;
mod uvdevice;
mod uvsecret;
pub use crate::log::PvLogger;
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
/// Miscellaneous functions and definitions
pub mod misc {
pub use crate::utils::pv_guest_bit_set;
pub use crate::utils::{create_file, open_file, read_exact_file, read_file, write_file};
pub use crate::utils::{parse_hex, to_u16, to_u32, try_parse_u128, try_parse_u64};
pub use crate::utils::{read, write};
pub use crate::utils::{Flags, Lsb0Flags64, Msb0Flags64};
pub use crate::tmpfile::TemporaryDirectory;
}
/// Definitions and functions for interacting with the Ultravisor
pub mod uv {
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd};
pub use crate::uvdevice::secret::{ListableSecretType, SecretEntry, SecretList};
pub use crate::uvdevice::{
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
};
}
/// Functionalities to verify UV requests
pub mod request {
/// Functionalities for reading add-secret requests
pub mod uvsecret {
pub use crate::uvsecret::AddSecretMagic;
pub use crate::uvsecret::UserDataType;
}
/// Version number of the request in system-endian
pub type RequestVersion = u32;
/// Request magic value
///
/// The first 8 byte of a request providing an identifier of the request type
/// for programs
pub type RequestMagic = [u8; 8];
/// A `MagicValue` is a bytepattern, that indicates if a byte slice contains the specified
/// (binary) data.
pub trait MagicValue<const N: usize> {
/// Magic value as byte array
const MAGIC: [u8; N];
/// Test whether the given slice starts with the magic value.
fn starts_with_magic(v: &[u8]) -> bool {
if v.len() < Self::MAGIC.len() {
return false;
}
v[..Self::MAGIC.len()] == Self::MAGIC
}
}
}
/// Provides cargo version Info about this crate.
///
/// Produces `pv_core-crate <version>`
pub const fn crate_info() -> &'static str {
concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION"))
}
// Internal definitions/ imports
const PAGESIZE: usize = 0x1000;
use ::utils::assert_size;
use ::utils::static_assert;
#[doc(hidden)]
/// stuff pv_core and pv share. Not intended for other users
pub mod for_pv {
pub use crate::uvdevice::secret::ser_gsid;
pub use crate::uvdevice::secret::SECRET_ID_SIZE;
}

View File

@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
macro_rules! path_to_str {
($path: expr) => {
$path.as_ref().to_str().unwrap_or("no UTF-8 path")
};
}
pub(crate) use path_to_str;
macro_rules! file_error {
($ty: tt, $ctx: expr, $path:expr, $src: expr) => {
$crate::Error::FileIo {
ty: $crate::FileIoErrorType::$ty,
ctx: $ctx.to_string(),
path: $path.to_string(),
source: $src,
}
};
}
pub(crate) use file_error;
macro_rules! bail_spec {
($str: expr) => {
return Err($crate::Error::Specification($str.to_string()))
};
}
pub(crate) use bail_spec;
#[doc(hidden)]
#[macro_export]
macro_rules! file_acc_error {
($ty: tt, $path:expr, $src: expr) => {
$crate::Error::FileAccess {
ty: $crate::FileAccessErrorType::$ty,
path: $path.to_string(),
source: $src,
}
};
}

152
rust/pv_core/src/tmpfile.rs Normal file
View File

@@ -0,0 +1,152 @@
use std::{
ffi::{CString, OsStr},
os::unix::prelude::OsStrExt,
path::{Path, PathBuf},
};
/// Rust wrapper for `libc::mkdtemp`
fn mkdtemp<P: AsRef<Path>>(template: P) -> Result<PathBuf, std::io::Error> {
let template_cstr = CString::new(template.as_ref().as_os_str().as_bytes())?;
let template_raw = template_cstr.into_raw();
unsafe {
// SAFETY: template_raw is a valid CString because it was generated by
// the `CString::new`.
let ret = libc::mkdtemp(template_raw);
if ret.is_null() {
Err(std::io::Error::last_os_error())
} else {
// SAFETY: `template_raw` is still a valid CString because it was
// generated by `CString::new` and modified by `libc::mkdtemp`.
let path_cstr = std::ffi::CString::from_raw(template_raw);
let path = OsStr::from_bytes(path_cstr.as_bytes());
let path = std::path::PathBuf::from(path);
Ok(path)
}
}
}
/// This type creates a temporary directory that is automatically removed when
/// it goes out of scope. It utilizes the `mkdtemp` function and its semantics,
/// with the addition of automatically including the template characters
/// `XXXXXX`.
#[derive(PartialEq, Eq, Debug)]
pub struct TemporaryDirectory {
path: Box<Path>,
}
impl TemporaryDirectory {
/// Creates a temporary directory using `prefix` as directory prefix.
///
/// # Errors
///
/// An error is returned if the temporary directory could not be created.
pub fn new<P: AsRef<Path>>(prefix: P) -> Result<Self, std::io::Error> {
let mut template = prefix.as_ref().to_owned();
let mut template_os_string = template.as_mut_os_string();
template_os_string.push("XXXXXX");
let temp_dir = mkdtemp(template_os_string)?;
Ok(Self {
path: temp_dir.into_boxed_path(),
})
}
/// Returns the path of the created temporary directory.
pub fn path(&self) -> &Path {
self.path.as_ref()
}
fn forget(mut self) {
self.path = PathBuf::new().into_boxed_path();
std::mem::forget(self);
}
/// Removes the created temporary directory and it's contents.
pub fn close(mut self) -> std::io::Result<()> {
let ret = std::fs::remove_dir_all(&self.path);
self.forget();
ret
}
}
impl AsRef<Path> for TemporaryDirectory {
fn as_ref(&self) -> &Path {
self.path()
}
}
impl Drop for TemporaryDirectory {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.path);
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::{mkdtemp, TemporaryDirectory};
#[test]
fn mkdtemp_test() {
let template_inv_not_last_characters = "XXXXXXyay";
let template_inv_too_less_x = "yayXXXXX";
let template_inv_path_does_not_exist = "../NA-yay/XXXXXX";
let template = "yayXXXXXX";
let err = mkdtemp(template_inv_not_last_characters).expect_err("invalid template");
let err = mkdtemp(template_inv_too_less_x).expect_err("invalid template");
let err =
mkdtemp(template_inv_path_does_not_exist).expect_err("path does not exist template");
let path = mkdtemp(template).expect("mkdtemp should work");
assert!(path.exists());
assert!(path.as_os_str().to_str().expect("works").starts_with("yay"));
std::fs::remove_dir(path);
}
#[test]
fn temporary_directory_empty_name_test() {
let temp_dir = TemporaryDirectory::new("").expect("should work");
let path = temp_dir.path().to_owned();
assert!(path.exists());
// Test that close removes the directory
temp_dir.close();
assert!(!path.exists());
}
#[test]
fn temporary_directory_drop_test() {
let temp_dir = TemporaryDirectory::new("").expect("should work");
let path = temp_dir.path().to_owned();
assert!(path.exists());
// Test that the destructor removes the directory
drop(temp_dir);
assert!(!path.exists());
}
#[test]
fn temporary_directory_close_test() {
let temp_dir = TemporaryDirectory::new("yay").expect("should work");
let path = temp_dir.path().to_owned();
assert!(path.exists());
assert!(path.as_os_str().to_str().expect("works").starts_with("yay"));
// Test that close() removes the directory
temp_dir.close();
assert!(!path.exists());
}
#[test]
fn temporary_directory_as_ref_test() {
let temp_dir = TemporaryDirectory::new("").expect("should work");
assert_eq!(temp_dir.path(), temp_dir.as_ref());
}
}

562
rust/pv_core/src/utils.rs Normal file
View File

@@ -0,0 +1,562 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{
macros::{bail_spec, file_error, path_to_str},
Error, FileAccessErrorType, FileIoErrorType, Result,
};
use std::{
fs::File,
io::{Read, Write},
path::Path,
};
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U64};
/// Trait that describes bitflags, represented by `T`.
pub trait Flags<T>: From<T> + for<'a> From<&'a T> {
/// Set the specified bit to one.
/// # Panics
///Panics if bit is >= 64
fn set_bit(&mut self, bit: u8);
/// Set the specified bit to zero.
/// # Panics
///Panics if bit is >= 64
fn unset_bit(&mut self, bit: u8);
/// Test if the specified bit is set.
/// # Panics
///Panics if bit is >= 64
fn is_set(&self, bit: u8) -> bool;
}
/// Bitflags in MSB0 ordering
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromZeroes, FromBytes)]
pub struct Msb0Flags64(U64<BigEndian>);
impl Flags<u64> for Msb0Flags64 {
#[track_caller]
fn set_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v |= 1 << (63 - bit);
self.0.set(v)
}
#[track_caller]
fn unset_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v &= !(1 << (63 - bit));
self.0.set(v)
}
#[track_caller]
fn is_set(&self, bit: u8) -> bool {
assert!(bit < 64, "Flag bit set to greater than 63");
self.0.get() & (1 << (63 - bit)) > 0
}
}
impl From<u64> for Msb0Flags64 {
fn from(value: u64) -> Self {
Self(value.into())
}
}
impl From<&u64> for Msb0Flags64 {
fn from(value: &u64) -> Self {
(*value).into()
}
}
/// Bitflags in LSB0 ordering
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromZeroes, FromBytes)]
pub struct Lsb0Flags64(U64<BigEndian>);
impl Flags<u64> for Lsb0Flags64 {
#[track_caller]
fn set_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v |= 1 << bit;
self.0.set(v)
}
#[track_caller]
fn unset_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v &= !(1 << bit);
self.0.set(v)
}
#[track_caller]
fn is_set(&self, bit: u8) -> bool {
assert!(bit < 64, "Flag bit set to greater than 63");
self.0.get() & (1 << bit) > 0
}
}
impl From<u64> for Lsb0Flags64 {
fn from(value: u64) -> Self {
Self(value.into())
}
}
impl From<&u64> for Lsb0Flags64 {
fn from(value: &u64) -> Self {
(*value).into()
}
}
/// Tries to convert a BE hex string into a 128 unsigned integer
/// The hexstring must contain 32chars of hexdigits
///
/// * `hex_str` - string to convert can be prepended with "0x"
/// * `ctx` - Error context string in case of an error
/// ```rust
/// # use std::error::Error;
/// # use pv_core::misc::try_parse_u128;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let hex = "11223344556677889900aabbccddeeff";
/// try_parse_u128(&hex, "The test")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
/// If `hex_string` is not a 32 byte hex string an Error appears
pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> {
let hex_str = if hex_str.starts_with("0x") {
hex_str.split_at(2).1
} else {
hex_str
};
if hex_str.len() != 32 {
bail_spec!(format!(
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
));
}
parse_hex(hex_str).try_into().map_err(|_| {
Error::Specification(format!(
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
))
})
}
/// Tries to convert a BE hex string into a 64 unsigned integer
/// The hexstring must *NOT* contain 16 chars of hexdigits, but
/// 16 chars at most.
///
/// * `hex_str` - string to convert can be prepended with "0x"
/// * `ctx` - Error context string in case of an error
/// ```rust
/// # use std::error::Error;
/// # use pv_core::misc::try_parse_u64;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let hex = "1234567890abcdef";
/// try_parse_u64(&hex, "The test")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
/// If `hex_string` is not a 32 byte hex string an Error appears
pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result<u64> {
let hex_str = if hex_str.starts_with("0x") {
hex_str.split_at(2).1
} else {
hex_str
};
if hex_str.len() > 16 {
bail_spec!(format!(
"{ctx} hexstring {hex_str} must be max 16 chars long"
));
}
Ok(u64::from_str_radix(hex_str, 16)?)
}
/// Open a file.
///
/// Wraps [`File::open`]
///
/// * `path` - Path to file
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<File> {
File::open(&path).map_err(|e| Error::FileAccess {
ty: FileAccessErrorType::Open,
path: path_to_str!(path).to_string(),
source: e,
})
}
/// Create a file.
///
/// Wraps [`File::create`]
///
/// * `path` - Path to file
pub fn create_file<P: AsRef<Path>>(path: P) -> Result<File> {
File::create(&path).map_err(|e| Error::FileAccess {
ty: FileAccessErrorType::Create,
path: path_to_str!(path).to_string(),
source: e,
})
}
/// Read exactly COUNT bytes into a buffer and return it.
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
/// # Errors
/// If this function encounters an "end of file" before completely filling
/// the buffer, it returns an error. The contents of `buf` are unspecified in this case.
///
/// If aber so hast du mmmeny other read error is encountered then this function immediately
/// returns. The contents of `buf` are unspecified in this case.
///
/// If this function returns an error, it is unspecified how many bytes it
/// has read, but it will never read more than would be necessary to
/// completely fill the buffer.
pub fn read_exact_file<P: AsRef<Path>, const COUNT: usize>(
path: P,
ctx: &str,
) -> Result<[u8; COUNT]> {
let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess {
ty: crate::FileAccessErrorType::Open,
path: path_to_str!(path).to_string(),
source: e,
})?;
if f.metadata()?.len() as usize != COUNT {
bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long"));
}
let mut buf = [0; COUNT];
f.read_exact(&mut buf)
.map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?;
Ok(buf)
}
/// Read content from a file and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::read` produces
pub fn read_file<P: AsRef<Path>>(path: P, ctx: &str) -> Result<Vec<u8>> {
std::fs::read(&path).map_err(|e| {
file_error!(
Read,
ctx,
path.as_ref().to_str().unwrap_or("no UTF-8 path"),
e
)
})
}
/// Reads all content from a [`std::io::Read`] and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::read` produces
pub fn read<R: Read>(rd: &mut R, path: &str, ctx: &str) -> Result<Vec<u8>> {
let mut buf = vec![];
rd.read_to_end(&mut buf).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})?;
Ok(buf)
}
/// write content to a file and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn write_file<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> Result<()> {
std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})
}
/// Write content to a [`std::io::Write`] and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn write<D: AsRef<[u8]>, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> {
wr.write_all(data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})
}
macro_rules! usize_to_ui {
($(#[$attr:meta])* => $t: ident, $name:ident) => {
///Converts an [`usize`] to an [`
$(#[$attr])*
///`] if possible
pub fn $name(u: usize) -> Option<$t> {
if u > $t::MAX as usize {
None
} else {
Some(u as $t)
}
}
}
}
usize_to_ui! {
#[doc = r"u32"]
=> u32, to_u32}
usize_to_ui! {
#[doc = r"u16"]
=> u16, to_u16}
/// Converts the hexstring into a byte vector.
///
/// Stops if the end or until a non hex chat is found
pub fn parse_hex(hex_str: &str) -> Vec<u8> {
let mut hex_bytes = hex_str.as_bytes().iter().map_while(|b| match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
});
let mut bytes = Vec::new();
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
bytes.push(h << 4 | l)
}
bytes
}
/// Report if the `prot_virt_guest` sysfs entry is one.
///
/// If the entry does not exist returns false.
///
/// for non-s390-architectures:
/// Returns always false
/// A non-s390 system cannot be a secure execution guest.
#[allow(unreachable_code)]
pub fn pv_guest_bit_set() -> bool {
#[cfg(not(target_arch = "s390x"))]
return false;
//s390 branch
let v = std::fs::read("/sys/firmware/uv/prot_virt_guest").unwrap_or_else(|_| vec![0]);
let v: u8 = String::from_utf8_lossy(&v[..1]).parse().unwrap_or(0);
v == 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn msb_flags() {
let v = 17;
let v_flag: Msb0Flags64 = v.into();
assert_eq!(v, v_flag.0.get());
let mut v: Msb0Flags64 = 4.into();
v.unset_bit(61);
assert_eq!(v.0.get(), 0);
v.set_bit(61);
assert_eq!(4, v.0.get());
let mut v = Msb0Flags64::default();
v.set_bit(0);
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(0);
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(1);
assert_eq!(&[0xc0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(2);
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(3);
assert_eq!(&[0xf0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(16);
assert_eq!(&[0xe0, 0, 0x80, 0, 0, 0, 0, 0], v.as_bytes());
}
#[test]
#[should_panic]
fn msb_flags_set_panic() {
Msb0Flags64::default().set_bit(64)
}
#[test]
#[should_panic]
fn msb_flags_unset_panic() {
Msb0Flags64::default().unset_bit(64)
}
#[test]
fn lsb_flags() {
let v = 17;
let v_flag: Lsb0Flags64 = v.into();
assert_eq!(v, v_flag.0.get());
let mut v: Lsb0Flags64 = 4.into();
v.unset_bit(2);
assert_eq!(v.0.get(), 0);
v.set_bit(2);
assert_eq!(4, v.0.get());
let mut v = Lsb0Flags64::default();
v.set_bit(0);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
v.set_bit(0);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
v.set_bit(1);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 3], v.as_bytes());
v.set_bit(2);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
v.set_bit(3);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 0xf], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
v.set_bit(16);
assert_eq!(&[0, 0, 0, 0, 0, 1, 0, 7], v.as_bytes());
}
#[test]
#[should_panic]
fn lsb_flags_set_panic() {
Lsb0Flags64::default().set_bit(64)
}
#[test]
#[should_panic]
fn lsb_flags_unset_panic() {
Lsb0Flags64::default().unset_bit(64)
}
#[test]
fn parse_hex() {
let s = "123456acbef0";
let exp = vec![0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
assert_eq!(super::parse_hex(s), exp);
let s = "00123456acbef0";
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
assert_eq!(super::parse_hex(s), exp);
let s = "00123456acbef0ii90";
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
assert_eq!(super::parse_hex(s), exp);
}
#[test]
fn to_u32() {
assert_eq!(Some(17), super::to_u32(17));
assert_eq!(Some(0), super::to_u32(0));
assert_eq!(Some(u32::MAX), super::to_u32(u32::MAX as usize));
assert_eq!(None, super::to_u32(u32::MAX as usize + 1));
assert_eq!(None, super::to_u32(usize::MAX));
}
#[test]
fn parse_u128() {
assert!(matches!(
try_parse_u128("123456", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("-1234", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0011223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("dd11223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("-1223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0x123456", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("-0x1234", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0x0011223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0xdd11223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0x-1223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert_eq!(
[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("11223344556677889900aabbccddeeff", "").unwrap()
);
assert_eq!(
[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("0x11223344556677889900aabbccddeeff", "").unwrap()
);
assert_eq!(
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
);
assert_eq!(
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
);
}
}

View File

@@ -3,13 +3,15 @@
// Copyright IBM Corp. 2023
#![allow(non_camel_case_types)]
use crate::file_acc_error;
use crate::FileAccessErrorType;
use crate::{Error, Result};
use libc::c_ulong;
use log::debug;
use std::convert::TryInto;
use std::fs::File;
use std::os::unix::prelude::{AsRawFd, RawFd};
use std::{
convert::TryInto,
ffi::c_ulong,
fs::File,
os::unix::prelude::{AsRawFd, RawFd},
};
#[cfg(not(test))]
use ::libc::ioctl;
@@ -22,6 +24,7 @@ mod ffi;
mod info;
mod test;
pub use ffi::uv_ioctl;
pub mod secret;
pub use info::UvDeviceInfo;
#[allow(dead_code)] //TODO rm when pv learns attestation
@@ -178,7 +181,11 @@ impl UvDevice {
.read(true)
.write(true)
.open(UvDevice::PATH)
.map_err(|e| file_acc_error!(Open, UvDevice::PATH, e))?,
.map_err(|e| Error::FileAccess {
ty: FileAccessErrorType::Open,
path: (UvDevice::PATH).to_string(),
source: e,
})?,
))
}

View File

@@ -3,7 +3,7 @@
// Copyright IBM Corp. 2023
use crate::{assert_size, static_assert};
use zerocopy::{AsBytes, FromBytes};
use zerocopy::{AsBytes, FromBytes, FromZeroes};
pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
@@ -55,7 +55,7 @@ assert_size!(uvio_ioctl_cb, 0x40);
/// Note that bit 0 (UVIO_IOCTL_UVDEV_INFO_NR) is always zero for `supp_uv_cmds`
/// as there is no corresponding UV-call.
#[repr(C)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
#[derive(Debug, Copy, Clone, AsBytes, FromZeroes, FromBytes)]
pub struct uvio_uvdev_info {
pub supp_uvio_cmds: u64,
pub supp_uv_cmds: u64,
@@ -84,7 +84,7 @@ pub const UVIO_ATT_UID_LEN: usize = 0x10;
/// UV will return invalid command rc.
/// Obviously all numbers are in BIG-endian!
#[repr(C)]
#[derive(Debug, AsBytes, FromBytes)]
#[derive(Debug, AsBytes, FromZeroes, FromBytes)]
pub struct uvio_attest {
pub arcb_addr: u64, //in
pub meas_addr: u64, //out

View File

@@ -9,7 +9,7 @@ use crate::{
Result,
};
use std::fmt::Display;
use zerocopy::{AsBytes, FromBytes};
use zerocopy::{AsBytes, FromZeroes};
/// Information of supported functions by the uvdevice
///

View File

@@ -0,0 +1,489 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{
assert_size,
misc::to_u16,
request::{uvsecret::AddSecretMagic, MagicValue},
uv::{uv_ioctl, UvCmd, UvDevice},
Error, Result, PAGESIZE,
};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::{Serialize, Serializer};
use std::{
fmt::Display,
io::{Cursor, Read, Seek, Write},
slice::Iter,
vec::IntoIter,
};
use zerocopy::{AsBytes, FromBytes, FromZeroes, U16, U32};
/// _List Secrets_ Ultravisor command.
///
/// The List Secrets Ultravisor call is used to list the
/// secrets that are in the secret store for the current SE-guest.
pub struct ListCmd(Vec<u8>);
impl ListCmd {
fn with_size(size: usize) -> Self {
Self(vec![0; size])
}
/// Create a new list secrets command with a one page capacity
pub fn new() -> Self {
Self::with_size(PAGESIZE)
}
}
impl Default for ListCmd {
fn default() -> Self {
Self::new()
}
}
impl UvCmd for ListCmd {
fn data(&mut self) -> Option<&mut [u8]> {
Some(self.0.as_mut_slice())
}
fn cmd(&self) -> u64 {
uv_ioctl(UvDevice::LIST_SECRET_NR)
}
fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> {
None
}
}
/// _Add Secret_ Ultravisor command.
///
/// The Add Secret Ultravisor-call is used to add a secret
/// to the secret store for the current SE-guest.
pub struct AddCmd(Vec<u8>);
impl AddCmd {
/// Create a new Add Secret command using the provided data.
///
/// # Errors
///
/// This function will return an error if the provided data does not start with the
/// ['crate::AddSecretRequest'] magic Value.
pub fn new<R: Read>(bin_add_secret_req: &mut R) -> Result<Self> {
let mut data = Vec::with_capacity(PAGESIZE);
bin_add_secret_req.read_to_end(&mut data)?;
if !AddSecretMagic::starts_with_magic(&data[..6]) {
return Err(Error::NoAsrcb);
}
Ok(Self(data))
}
}
impl UvCmd for AddCmd {
fn data(&mut self) -> Option<&mut [u8]> {
Some(&mut self.0)
}
fn cmd(&self) -> u64 {
uv_ioctl(UvDevice::ADD_SECRET_NR)
}
fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> {
match rc {
0x0101 => Some("not allowed to modify the secret store"),
0x0102 => Some("secret store locked"),
0x0103 => Some("access exception when accessing request control block"),
0x0104 => Some("unsupported add secret version"),
0x0105 => Some("invalid request size"),
0x0106 => Some("invalid number of host-keys"),
0x0107 => Some("unsupported flags specified"),
0x0108 => Some("unable to decrypt the request"),
0x0109 => Some("unsupported secret provided"),
0x010a => Some("invalid length for the specified secret"),
0x010b => Some("secret store full"),
0x010c => Some("unable to add secret"),
0x010d => Some("dump in progress, try again later"),
_ => None,
}
}
}
/// _Lock Secret Store_ Ultravisor command.
///
/// The Lock Secret Store Ultravisor-call is used to block
/// all changes to the secret store. Upon successful
/// completion of a Lock Secret Store Ultravisor-call, any
/// request to modify the secret store will fail.
pub struct LockCmd;
impl UvCmd for LockCmd {
fn cmd(&self) -> u64 {
uv_ioctl(UvDevice::LOCK_SECRET_NR)
}
fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> {
match rc {
0x0101 => Some("not allowed to modify the secret store"),
0x0102 => Some("secret store already locked"),
_ => None,
}
}
}
/// List of secrets used to parse the [`crate::uv::ListCmd`] result.
///
/// The list should not hold more than 0xffffffff elements
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct SecretList {
total_num_secrets: usize,
secrets: Vec<SecretEntry>,
}
impl<'a> IntoIterator for &'a SecretList {
type Item = &'a SecretEntry;
type IntoIter = Iter<'a, SecretEntry>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl IntoIterator for SecretList {
type Item = SecretEntry;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.secrets.into_iter()
}
}
impl FromIterator<SecretEntry> for SecretList {
fn from_iter<T: IntoIterator<Item = SecretEntry>>(iter: T) -> Self {
let secrets: Vec<_> = iter.into_iter().collect();
let total_num_secrets = secrets.len() as u16;
Self::new(total_num_secrets, secrets)
}
}
impl SecretList {
/// Creates a new SecretList.
///
/// The content of this list will very liekly not represent the status of the guest in the
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
pub fn new(total_num_secrets: u16, secrets: Vec<SecretEntry>) -> Self {
Self {
total_num_secrets: total_num_secrets as usize,
secrets,
}
}
/// Returns an iterator over the slice.
///
/// The iterator yields all secret entries from start to end.
pub fn iter(&self) -> Iter<'_, SecretEntry> {
self.secrets.iter()
}
/// Returns the length of this [`SecretList`].
pub fn len(&self) -> usize {
self.secrets.len()
}
/// Returns `true` if the [`SecretList`] contains no [`SecretEntry`].
pub fn is_empty(&self) -> bool {
self.secrets.is_empty()
}
/// Reports the number of secrets stored in UV.
///
/// This number may be not equal to the provided number of [`SecretEntry`]
pub fn total_num_secrets(&self) -> usize {
self.total_num_secrets
}
/// Encodes the list in the same binary format the UV would do
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
w.write_u16::<BigEndian>(num_s)?;
w.write_u16::<BigEndian>(
self.total_num_secrets
.try_into()
.map_err(|_| Error::ManySecrets)?,
)?;
w.write_all(&[0u8; 12])?;
for secret in &self.secrets {
w.write_all(secret.as_bytes())?;
}
w.flush().map_err(Error::Io)
}
/// Decodes the list from the binary format of the UV into this internal representation
pub fn decode<R: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
let num_s = r.read_u16::<BigEndian>()?;
let total_num_secrets = r.read_u16::<BigEndian>()? as usize;
let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
r.seek(std::io::SeekFrom::Current(12))?; //skip reserved bytes
let mut buf = [0u8; SecretEntry::STRUCT_SIZE];
for _ in 0..num_s {
r.read_exact(&mut buf)?;
//cannot fail. buffer has the same size as the secret entry
let secr = SecretEntry::read_from(buf.as_slice()).unwrap();
v.push(secr);
}
Ok(Self {
total_num_secrets,
secrets: v,
})
}
}
impl TryFrom<ListCmd> for SecretList {
type Error = Error;
fn try_from(mut list: ListCmd) -> Result<SecretList> {
SecretList::decode(&mut Cursor::new(list.data().unwrap())).map_err(Error::InvSecretList)
}
}
impl Display for SecretList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?;
if !self.secrets.is_empty() {
writeln!(f)?;
}
for s in &self.secrets {
writeln!(f, "{s}")?;
}
Ok(())
}
}
fn ser_u32<S: Serializer>(v: &U32<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_u32(v.get())
}
fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_u16(v.get())
}
/// Secret types that can appear in a [`SecretList`]
#[non_exhaustive]
#[derive(PartialEq, Eq)]
pub enum ListableSecretType {
/// Association Secret
Association,
/// Invalid secret type, that should never appear in a list
///
/// 0 is reserved
/// 1 is Null secret, with no id and not listable
Invalid(u16),
/// Unknown secret type
Unknown(u16),
}
impl ListableSecretType {
const RESERVED_0: u16 = 0x0000;
const NULL: u16 = 0x0001;
const ASSOCIATION: u16 = 0x0002;
}
impl Display for ListableSecretType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Association => write!(f, "Association"),
Self::Invalid(n) => write!(f, "Invalid({n})"),
Self::Unknown(n) => write!(f, "Unknown({n})"),
}
}
}
impl From<U16<BigEndian>> for ListableSecretType {
fn from(value: U16<BigEndian>) -> Self {
match value.get() {
Self::RESERVED_0 => Self::Invalid(Self::RESERVED_0),
Self::NULL => Self::Invalid(Self::NULL),
Self::ASSOCIATION => ListableSecretType::Association,
n => Self::Unknown(n),
}
}
}
impl From<ListableSecretType> for U16<BigEndian> {
fn from(value: ListableSecretType) -> Self {
match value {
ListableSecretType::Association => ListableSecretType::ASSOCIATION,
ListableSecretType::Invalid(n) | ListableSecretType::Unknown(n) => n,
}
.into()
}
}
#[doc(hidden)]
pub const SECRET_ID_SIZE: usize = 32;
#[doc(hidden)]
pub fn ser_gsid<S>(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s = String::with_capacity(32 * 2 + 2);
s.push_str("0x");
let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
ser.serialize_str(&s)
}
/// A secret in a [`SecretList`]
#[repr(C)]
#[derive(Debug, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)]
pub struct SecretEntry {
#[serde(serialize_with = "ser_u16")]
index: U16<BigEndian>,
#[serde(serialize_with = "ser_u16")]
stype: U16<BigEndian>,
#[serde(serialize_with = "ser_u32")]
len: U32<BigEndian>,
#[serde(skip)]
res_8: u64,
#[serde(serialize_with = "ser_gsid")]
id: [u8; SECRET_ID_SIZE],
}
assert_size!(SecretEntry, SecretEntry::STRUCT_SIZE);
impl SecretEntry {
const STRUCT_SIZE: usize = 0x30;
/// Create a new entry for a [`SecretList`].
///
/// The content of this entry will very liekly not represent the status of the guest in the
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
pub fn new(index: u16, stype: ListableSecretType, id: [u8; 32], secret_len: u32) -> Self {
Self {
index: index.into(),
stype: stype.into(),
len: secret_len.into(),
res_8: 0,
id,
}
}
/// Returns the index of this [`SecretEntry`].
pub fn index(&self) -> u16 {
self.index.get()
}
/// Returns the secret type of this [`SecretEntry`].
pub fn stype(&self) -> ListableSecretType {
self.stype.into()
}
/// Returns a reference to the id of this [`SecretEntry`].
pub fn id(&self) -> &[u8] {
&self.id
}
}
impl Display for SecretEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let stype: ListableSecretType = self.stype.into();
writeln!(f, "{} {}:", self.index, stype)?;
write!(f, " ")?;
for b in self.id {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::{BufReader, BufWriter, Cursor};
#[test]
fn dump_secret_entry() {
const EXP: &[u8] = &[
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let s = SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
};
assert_eq!(s.as_bytes(), EXP);
}
#[test]
fn secret_list_dec() {
let buf = [
0x00u8, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
// secret
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let exp = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
}],
};
let mut br = BufReader::new(Cursor::new(buf));
let sl = SecretList::decode(&mut br).unwrap();
assert_eq!(sl, exp);
}
#[test]
fn secret_list_enc() {
const EXP: &[u8] = &[
0x00, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
// secret
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let sl = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
}],
};
let mut buf = [0u8; 0x40];
{
let mut bw = BufWriter::new(&mut buf[..]);
sl.encode(&mut bw).unwrap();
}
println!("list: {sl:?}");
assert_eq!(buf, EXP);
}
}

View File

@@ -5,6 +5,7 @@
#![cfg(test)]
use std::{
ffi::{c_int, c_ulong},
os::unix::prelude::FromRawFd,
sync::{Mutex, MutexGuard},
};
@@ -28,18 +29,18 @@ fn get_lock<T>(m: &'static Mutex<T>) -> MutexGuard<'static, T> {
struct IoctlCtx {
modify: Box<dyn FnMut(&mut ffi::uvio_ioctl_cb) -> i32 + Send + Sync>,
exp_cmd: ::libc::c_ulong,
exp_cmd: c_ulong,
called: bool,
}
impl IoctlCtx {
pub fn exp_cmd(&mut self, cmd: ::libc::c_ulong) -> &mut Self {
pub fn exp_cmd(&mut self, cmd: c_ulong) -> &mut Self {
self.exp_cmd = cmd;
self
}
pub fn set_mdfy<F>(&mut self, mdfy: F) -> &mut Self
where
F: FnMut(&mut ffi::uvio_ioctl_cb) -> ::libc::c_int + 'static + Send + Sync,
F: FnMut(&mut ffi::uvio_ioctl_cb) -> c_int + 'static + Send + Sync,
{
self.modify = Box::new(mdfy);
self
@@ -62,11 +63,7 @@ impl IoctlCtx {
pub mod mock_libc {
use super::*;
pub unsafe fn ioctl(
fd: ::libc::c_int,
cmd: ::libc::c_ulong,
data: *mut ffi::uvio_ioctl_cb,
) -> ::libc::c_int {
pub unsafe fn ioctl(fd: c_int, cmd: c_ulong, data: *mut ffi::uvio_ioctl_cb) -> c_int {
let mut ctx = get_lock(&IOCTL_MTX);
assert!(!ctx.called, "IOCTL called more than once");
ctx.called = true;

View File

@@ -0,0 +1,194 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{
misc::to_u16,
request::{MagicValue, RequestMagic},
uv::{ListCmd, UvCmd},
Error, Result,
};
use byteorder::{BigEndian, ByteOrder};
use std::{
fmt::Display,
io::{Cursor, Read, Seek, Write},
mem::size_of,
};
use utils::{assert_size, static_assert};
use zerocopy::{AsBytes, FromBytes, U16, U32};
/// The magic value used to identify an add-secret request`]
///
/// The magic value is ASCII:
/// ```rust
/// # use pv_core::request::uvsecret::AddSecretMagic;
/// # use pv_core::request::MagicValue;
/// # fn main() {
/// # let magic =
/// b"asrcbM"
/// # ;
/// # assert!(AddSecretMagic::starts_with_magic(magic));
/// # }
///```
///
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, AsBytes)]
pub struct AddSecretMagic {
magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]
kind: U16<BigEndian>,
}
assert_size!(AddSecretMagic, 8);
impl MagicValue<6> for AddSecretMagic {
// "asrcbM"
const MAGIC: [u8; 6] = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D];
}
impl AddSecretMagic {
/// Get the magic value.
pub fn get(&self) -> RequestMagic {
let mut res = RequestMagic::default();
debug_assert!(res.len() == size_of::<AddSecretMagic>());
// Panic: does not panic, buf is 8 bytes long
self.write_to(&mut res).unwrap();
res
}
/// Try to convert from a byte slice.
///
/// Retuns [`None`] if the byte slice does not contain a valid magic value variant.
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
if !Self::starts_with_magic(bytes) || bytes.len() < size_of::<AddSecretMagic>() {
return Err(Error::NoAsrcb);
}
// Panic: Will not panic, bytes is at least 8 elements long
let kind = BigEndian::read_u16(&bytes[6..8]);
let kind = UserDataType::try_from(kind)?;
Ok(Self::from(kind))
}
/// Returns the [`UserDataType`] of this [`AddSecretMagic`].
pub fn kind(&self) -> UserDataType {
// Panic: Will never panic. The value is cheched during construcion of the object for
// beeing one of the enum values.
self.kind.get().try_into().unwrap()
}
}
/// Types of (non architectured) user data for an add-secret request
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserDataType {
/// Marker that the request does not contain any user data
Null = 0x0000,
/// Arbitrary user data (max 512 bytes)
Unsigned = 0x0001,
/// User data message signed with an Ec key, (max 256 byte)
SgnEcSECP521R1 = 0x0002,
/// User data message signature with a Rsa key of 2048 bit size, (max 256 byte)
SgnRsa2048 = 0x0003,
/// User data message signature with a Rsa key of 3072 bit size, (max 128 byte)
SgnRsa3072 = 0x0004,
}
impl UserDataType {
/// Returns the maximum user-data size in bytes.
pub fn max(&self) -> usize {
match self {
UserDataType::Null => 0,
UserDataType::Unsigned => 512,
UserDataType::SgnEcSECP521R1 => 256,
UserDataType::SgnRsa2048 => 256,
UserDataType::SgnRsa3072 => 128,
}
}
}
impl Display for UserDataType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Null => "None",
Self::Unsigned => "unsigned",
Self::SgnEcSECP521R1 => "ECDSA signed",
Self::SgnRsa2048 => "RSA 2048 signed",
Self::SgnRsa3072 => "RSA 3072 signed",
}
)
}
}
impl TryFrom<u16> for UserDataType {
type Error = Error;
fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
if value == UserDataType::Null as u16 {
Ok(UserDataType::Null)
} else if value == UserDataType::Unsigned as u16 {
Ok(UserDataType::Unsigned)
} else if value == UserDataType::SgnEcSECP521R1 as u16 {
Ok(UserDataType::SgnEcSECP521R1)
} else if value == UserDataType::SgnRsa2048 as u16 {
Ok(UserDataType::SgnRsa2048)
} else if value == UserDataType::SgnRsa3072 as u16 {
Ok(UserDataType::SgnRsa3072)
} else {
Err(Error::UnsupportedUserData(value))
}
}
}
impl From<UserDataType> for AddSecretMagic {
fn from(kind: UserDataType) -> Self {
Self {
magic: Self::MAGIC,
kind: (kind as u16).into(),
}
}
}
#[cfg(test)]
mod test {
use crate::{
request::{
uvsecret::{AddSecretMagic, UserDataType},
MagicValue,
},
Error,
};
#[test]
fn convert_user_data() {
assert!(matches!(
UserDataType::try_from(5),
Err(Error::UnsupportedUserData(5))
));
}
#[test]
fn magic_get() {
let user_data = AddSecretMagic::from(UserDataType::SgnEcSECP521R1);
assert_eq!(
user_data.get(),
[0x61, 0x73, 0x72, 0x63, 0x62, 0x4D, 0x00, 0x02]
);
}
#[test]
fn magic_try_from() {
let bin = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D, 0x00, 0x02];
let magic = AddSecretMagic::try_from_bytes(&bin).unwrap();
assert_eq!(
magic,
AddSecretMagic {
magic: AddSecretMagic::MAGIC,
kind: (UserDataType::SgnEcSECP521R1 as u16).into()
}
);
}
}

View File

@@ -0,0 +1,19 @@
[package]
name = "pvapconfig"
description = "A tool to configure the AP resources inside a SE guest based on UV secrets and an AP config file."
authors = ["Harald Freudenberger <freude@linux.ibm.com>"]
version = "0.9.0"
edition.workspace = true
license.workspace = true
[dependencies]
clap = { version ="4.1", features = ["derive", "wrap_help"]}
lazy_static = "1.1"
openssl = { version = "0.10" }
openssl-sys = { version = "0.9" }
pv_core = { path = "../pv_core"}
rand = "0.8"
regex = "1"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
utils = { path = "../utils" }

View File

@@ -0,0 +1,9 @@
<!--
Copyright 2023 IBM Corp.
s390-tools is free software; you can redistribute it and/or modify
it under the terms of the MIT license. See LICENSE for details.
-->
# pvapconfig
## Description
**pvapconfig** is used to automatically set up the AP configuration
within an IBM Secure Execution guest.

View File

@@ -0,0 +1,174 @@
.\" pvapconfig.1
.\"
.\" Copyright 2023 IBM Corp.
.\" s390-tools is free software; you can redistribute it and/or modify
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.\" use
.\" groff -man -Tutf8 pvapconfig.1
.\" or
.\" nroff -man pvapconfig.1
.\" to process this source
.\"
.TH PVAPCONFIG 1 "DEC 2023" "s390-tools"
.SH NAME
pvapconfig \- automatic configure APQNs within an SE KVM guest.
.SH SYNOPSIS
.TP 9
.B pvapconfig [OPTIONS]
.SH DESCRIPTION
pvapconfig is a tool for automatically configuring the APQNs within an
Secure Execution KVM guest with AP pass-through support. Based on a
given AP configuration it tries to find matching APQNs and binds and
associates them with the given secret(s).
Here is a description of pvapconfig's process:
.TP 3
1. Check AP bus: Support for AP bus needs to be available and the AP
bus needs to support APSB. APSB is only available within an KVM SE
guest with AP pass-through support.
.TP 3
2. Check Ultravisor: UV support needs to be available and the UV needs
to support the
.I list secrets
feature.
.TP 3
3. Read in and validate the AP configuration file. By default if not
overwritten by the --config option the AP configuration is read from
.I /etc/pvapconfig.yaml
and syntactically verified. See section CONFIGFILE for details about
the syntax and semantic of the configuration file.
.TP 3
4. Fetch the list of association secrets from the UV. Actually the
index of the secret and the secret id for each entry is collected. The
secret value is NOT fetched as it is NOT accessible but only usable
within the UV firmware.
.TP 3
5. Gather all APQNs available within this KVM SE guest. Collect
information about each APQN like online states, crypto card serial
numbers and master key verification patterns (MKVP).
.TP 3
6. Go through all AP config entries. For each AP config entry try to
find an APQN which is already configured (bound/associated) to
satisfy this config entry. If such a match is found, the AP config
entry is assumed to be fulfilled and marked as done.
.TP 3
7. All remaining APQNs which do not already satisfy an AP config entry
are now examined for their bind and association state and maybe reset
to unbound state.
.TP 3
8. Go through all AP config entries which are still not
fulfilled. For each such AP config entry try to search for an APQN
which would match to this entry and then prepare this APQN (bind,
maybe associate). If successful, mark the AP config entry as done.
.TP 3
9. Evaluation of the applied AP config entries. Applied means the AP
config entry has been fulfilled either in step 6 or in step 8. With
the strict option given ALL AP config entries need to apply otherwise
an error message is printed and pvapconfig returns with exit failure.
If the strict option is not given, it is enough to satisfy at least one
AP config entry from the configuration and pvapconfig will return
successfully.
.SH OPTIONS
.TP 8
.B -c, --config <configfile>
Use <configfile> as the AP config file for pvapconfig. If pvapconfig
is run without this option the default configuration file
/etc/pvapconfig.yaml is used.
.TP 8
.B -h, --help
Print pvapconfig usage information and exit.
.TP 8
.B -n, --dry-run
Do not bind, unbind or associate APQNs but only process the
configuration and the available APQNs and secrets and simulate the
bind, unbind or associate action on the chosen APQN. Use it together
with the verbose option to see which actions pvapconfig would do if
unleashed.
.TP 8
.B -s, --strict
All AP config entries need to be satisfied to have pvapconfig
terminate with success. Without this option one applied AP config
entry is enough to meet the expectations.
.TP 8
.B -v, --verbose
Print out informational messages about what pvapconfig is actually
doing.
.TP 8
.B -V, --version
Print version information and exit.
.SH CONFIGFILE
The pvapconfig yaml configuration file consists of a list of AP config
entries. Each entry may hold this information:
.TP 2
- mode: AP queue mode information, required, either "EP11" or "Accel".
.TP 2
- mkvp: AP queue Master Key Verification Pattern (MKVP), required for
EP11, hex string optional prepented with 0x. The MKVP hex string value
may hold either 16 bytes (32 hex characters) or 32 bytes (64 hex
characters) but only the leftmost 16 bytes hold MKVP information and
thus the rest is ignored.
.TP 2
- serialnr: Crypto Card Serial Number, string, optional for EP11,
ignored for Accel. As this is a real ASCII string uppercase and
lowercase character(s) count different.
.TP 2
- mingen: Card Minimal Generation, string "CEX4", "CEX5", "CEX6",
"CEX7" or "CEX8" for Accelerator, string "CEC8" for EP11, optional. If
given specifies the minimal accepted Crypto card generation.
.TP 2
- secretid: Secret id, hex string with optional 0x prepented, required
for EP11, ignored for Accel. Details see the following text.
.TP 2
- name: ASCII string, optional, but see details below.
.TP 2
- description: Description of this AP config entry, string, ignored,
just for convenience for the reader or editor of the configuration.
.PP
The secret id uniquely identifies an association secret. However, it
is a clumsy hex string of 64 characters which represent the readable
sha256 value over the secret's name. So pvapconfig can use the name
instead and calculate the secret id from the name. So the rule is:
.TP 2
- If name and secretid is given, the secretid needs to match to the
sha256 hash over the given name for this AP config entry.
.TP 2
- If only name is given then the secretid is calculated with a sha256
hash over the given name.
.TP 2
- If only the secretid is given, there is nothing more to do but
verify that the value is a hex string with 64 characters.
.SH LOCKING
Pvapconfig needs to have a consistent view of the AP resources
during lifetime. There must not run multiple instances of pvapconfig
or any manipulations of the AP resources in parallel. To prevent the
execution of multiple pvapconfig instances the lock file
/run/lock/pvapconfig.lock is established. A second instance of
pvapconfig will detect this lock file and terminated with an error
message. If for any reason this file still exists as a leftover from a
previous pvapconfig crash for example, it needs to get removed by
hand. The lock file contains the process id of the pvapconfig process
which created this file.
.SH RETURN VALUE
.TP 8
.B 0 - Successful termination.
At least one AP config entry has been applied or at least one APQN has
been found in a state matching to one AP config entry. If strict
option is given, ALL AP config entries have been applied. An AP config
entry is applied either by configuring the APQN accordingly or an APQN
has been found which already fulfills the constrains.
.RE
.TP 8
.B 1 - Failure.
Either some kind of failure happened during processing the
configuration or the configuration could not get applied
successful. In all cases pvapconfig prints out a message to standard
error with details about the failure. Also pvapconfig does NOT reset
the APQNs to the state found at the startup when failing to apply the
configuration.
.SH NOTES
For more information and details see the IBM documentation about
Confidential Computing "Introducing IBM Secure Execution for Linux"
available at https://www.ibm.com/docs/.
.SH SEE ALSO
\fBpvsecret\fR(1), \fBlszcrypt\fR(8), \fBchzcrypt\fR(8)

817
rust/pvapconfig/src/ap.rs Normal file
View File

@@ -0,0 +1,817 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! AP support functions for pvapconfig
//
use crate::helper::*;
use regex::Regex;
use std::fmt;
use std::path::Path;
use std::slice::Iter;
use std::thread;
use std::time;
const PATH_SYS_BUS_AP: &str = "/sys/bus/ap";
const PATH_SYS_BUS_AP_FEATURES: &str = "/sys/bus/ap/features";
const PATH_SYS_BUS_AP_BINDINGS: &str = "/sys/bus/ap/bindings";
const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap";
const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$";
const RE_QUEUE_DIR: &str = r"^([[:xdigit:]]{2})\.([[:xdigit:]]{4})$";
const RE_CARD_TYPE: &str = r"^CEX([3-8])([ACP])$";
const RE_EP11_MKVP: &str = r"WK\s+CUR:\s+(\S+)\s+(\S+)";
const RE_CCA_AES_MKVP: &str = r"AES\s+CUR:\s+(\S+)\s+(\S+)";
const RE_CCA_APKA_MKVP: &str = r"APKA\s+CUR:\s+(\S+)\s+(\S+)";
const SYS_BUS_AP_BINDINGS_POLL_MS: u64 = 500;
const SYS_BUS_AP_BIND_POLL_MS: u64 = 500;
const SYS_BUS_AP_BIND_TIMEOUT_MS: u64 = 10000;
const SYS_BUS_AP_ASSOC_POLL_MS: u64 = 500;
const SYS_BUS_AP_ASSOC_TIMEOUT_MS: u64 = 10000;
/// Check if AP bus support is available.
/// Returns Result with Ok(()) or Err(failurestring).
pub fn check_ap_bus_support() -> Result<(), String> {
if !Path::new(PATH_SYS_BUS_AP).is_dir() {
return Err(format!(
"AP bus support missing (path {PATH_SYS_BUS_AP} is invalid)."
));
}
Ok(())
}
/// Check if AP bus supports APSB.
///
/// When APSB support is available returns Result
/// with Ok(()) or otherwise Err(failurestring).
pub fn ap_bus_has_apsb_support() -> Result<(), String> {
if !Path::new(PATH_SYS_BUS_AP_FEATURES).is_file() {
return Err(format!(
"AP bus features support missing (file {PATH_SYS_BUS_AP_FEATURES} does not exist)."
));
}
let features = sysfs_read_string(PATH_SYS_BUS_AP_FEATURES).map_err(|err| {
format!("Failure reading AP bus features from {PATH_SYS_BUS_AP_FEATURES} ({err:?}).")
})?;
match features.find("APSB") {
Some(_) => Ok(()),
None => Err("Missing AP bus feature APSB (SE AP pass-through not enabled ?).".to_string()),
}
}
/// Wait for AP bus set up all it's devices.
///
/// This function loops until the AP bus reports that
/// - all AP queue devices have been constructed
/// - and all AP device have been bound to a device driver.
/// This may take some time and even loop forever if there
/// is something wrong with the kernel modules setup.
/// Returns true when AP bus bindings are complete,
/// otherwise false and a message is printed.
/// When AP bus binding complete is not immediately reached
/// and this function needs to loop, about every 5 seconds
/// a message is printed "Waiting for ...".
pub fn wait_for_ap_bus_bindings_complete() -> bool {
let mut counter = 0;
loop {
match sysfs_read_string(PATH_SYS_BUS_AP_BINDINGS) {
Ok(s) => {
if s.contains("complete") {
return true;
}
}
Err(err) => {
eprintln!(
"Failure reading AP bus bindings from {} ({:?}).",
PATH_SYS_BUS_AP_BINDINGS, err
);
return false;
}
}
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BINDINGS_POLL_MS));
counter += 1;
if counter % 10 == 0 {
println!("Waiting for AP bus bindings complete.");
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApqnMode {
Accel,
Ep11,
Cca,
}
#[derive(Debug, Clone)]
pub struct ApqnInfoAccel {
// empty
}
#[derive(Debug, Clone)]
pub struct ApqnInfoEp11 {
pub serialnr: String,
pub mkvp: String, // may be an empty string if no WK set
}
#[derive(Debug, Clone)]
pub struct ApqnInfoCca {
pub serialnr: String,
pub mkvp_aes: String, // may be an empty string if no MK set
pub mkvp_apka: String, // may be an empty string if no MK set
}
#[derive(Debug, Clone)]
pub enum ApqnInfo {
Accel(ApqnInfoAccel),
Ep11(ApqnInfoEp11),
Cca(ApqnInfoCca),
}
impl ApqnInfo {
fn accel_info(_carddir: &str, _queuedir: &str) -> Result<ApqnInfo, String> {
Ok(ApqnInfo::Accel(ApqnInfoAccel {}))
}
fn cca_info(carddir: &str, queuedir: &str) -> Result<ApqnInfo, String> {
let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading serialnr from {carddir}/serialnr: {:?}.",
err
))
}
};
let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.",
err
))
}
};
let mut aes_mkvp = String::new();
let re_cca_aes_mkvp = Regex::new(RE_CCA_AES_MKVP).unwrap();
if !re_cca_aes_mkvp.is_match(&mkvps) {
return Err(format!(
"APQN {} failure parsing mkvps string '{}'.",
queuedir, mkvps
));
} else {
let caps = re_cca_aes_mkvp.captures(&mkvps).unwrap();
let valid = caps.get(1).unwrap().as_str().to_lowercase();
if valid != "valid" {
eprintln!(
"Warning: APQN {} has no valid AES master key set.",
queuedir
);
} else {
aes_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if aes_mkvp.starts_with("0x") {
aes_mkvp = String::from(&aes_mkvp[2..]);
}
}
}
let mut apka_mkvp = String::new();
let re_cca_apka_mkvp = Regex::new(RE_CCA_APKA_MKVP).unwrap();
if !re_cca_apka_mkvp.is_match(&mkvps) {
return Err(format!(
"APQN {} failure parsing mkvps string '{}'.",
queuedir, mkvps
));
} else {
let caps = re_cca_apka_mkvp.captures(&mkvps).unwrap();
let valid = caps.get(1).unwrap().as_str().to_lowercase();
if valid != "valid" {
eprintln!(
"Warning: APQN {} has no valid APKA master key set.",
queuedir
);
} else {
apka_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if apka_mkvp.starts_with("0x") {
apka_mkvp = String::from(&apka_mkvp[2..]);
}
}
}
Ok(ApqnInfo::Cca(ApqnInfoCca {
serialnr,
mkvp_aes: aes_mkvp,
mkvp_apka: apka_mkvp,
}))
}
fn ep11_info(carddir: &str, queuedir: &str) -> Result<ApqnInfo, String> {
let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading serialnr from {carddir}/serialnr: {:?}.",
err
))
}
};
let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.",
err
))
}
};
let mut mkvp = String::new();
let re_ep11_mkvp = Regex::new(RE_EP11_MKVP).unwrap();
if !re_ep11_mkvp.is_match(&mkvps) {
return Err(format!(
"APQN {} failure parsing mkvps string '{}'.",
queuedir, mkvps
));
} else {
let caps = re_ep11_mkvp.captures(&mkvps).unwrap();
let valid = caps.get(1).unwrap().as_str().to_lowercase();
if valid != "valid" {
eprintln!("Warning: APQN {} has no valid wrapping key set.", queuedir);
} else {
mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if mkvp.starts_with("0x") {
mkvp = String::from(&mkvp[2..]);
}
if mkvp.len() > 32 {
mkvp = String::from(&mkvp[..32])
}
}
}
Ok(ApqnInfo::Ep11(ApqnInfoEp11 { serialnr, mkvp }))
}
fn info(mode: &ApqnMode, carddir: &str, queuedir: &str) -> Result<ApqnInfo, String> {
match mode {
ApqnMode::Accel => ApqnInfo::accel_info(carddir, queuedir),
ApqnMode::Cca => ApqnInfo::cca_info(carddir, queuedir),
ApqnMode::Ep11 => ApqnInfo::ep11_info(carddir, queuedir),
}
}
}
#[derive(Debug, Clone)]
pub struct Apqn {
pub name: String,
pub card: u32,
pub domain: u32,
pub gen: u32,
pub mode: ApqnMode,
pub info: Option<ApqnInfo>,
}
impl fmt::Display for Apqn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({},{})", self.card, self.domain)
}
}
impl Apqn {
pub fn bind_state(&self) -> Result<BindState, String> {
get_apqn_bind_state(self.card, self.domain)
}
pub fn set_bind_state(&self, state: BindState) -> Result<(), String> {
set_apqn_bind_state(self.card, self.domain, state)
}
pub fn associate_state(&self) -> Result<AssocState, String> {
get_apqn_associate_state(self.card, self.domain)
}
pub fn set_associate_state(&self, state: AssocState) -> Result<(), String> {
set_apqn_associate_state(self.card, self.domain, state)
}
}
/// Wrapper object around Vector of Apqns
pub struct ApqnList(Vec<Apqn>);
impl ApqnList {
#[cfg(test)] // only used in test code
pub fn from_apqn_vec(apqns: Vec<Apqn>) -> ApqnList {
ApqnList(apqns)
}
#[cfg(test)] // only used in test code
pub fn to_apqn_vec(&self) -> Vec<Apqn> {
self.0.clone()
}
pub fn iter(&self) -> Iter<'_, Apqn> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Scan AP bus devices in sysfs and construct the Apqnlist.
///
/// The list is a vector of struct Apqn for each APQN found in sysfs
/// which is online and the card type matches to the regular expression
/// RE_CARD_TYPE.
/// On success a vector of struct Apqn is returned. This list may be
/// empty if there are no APQNs available or do not match to the conditions.
/// On failure None is returned.
/// Fatal errors which should never happened like unable to compile a
/// static regular expression will result in calling panic.
/// # Panics
/// Panics if the compilation of a static regular expression fails.
pub fn gather_apqns() -> Option<ApqnList> {
let mut apqns: Vec<Apqn> = Vec::new();
let re_card_type = Regex::new(RE_CARD_TYPE).unwrap();
let re_queue_dir = Regex::new(RE_QUEUE_DIR).unwrap();
let card_dirs =
match sysfs_get_list_of_subdirs_matching_regex(PATH_SYS_DEVICES_AP, RE_CARD_DIR) {
Ok(r) => r,
Err(err) => {
eprintln!(
"Failure reading AP devices {} ({:?}).",
PATH_SYS_DEVICES_AP, err
);
return None;
}
};
for dir in card_dirs {
let path = format!("{PATH_SYS_DEVICES_AP}/{dir}");
let card_type = match sysfs_read_string(&format!("{path}/type")) {
Ok(r) => r,
Err(err) => {
eprintln!("Failure reading card type from {} ({:?}).", path, err);
return None;
}
};
if !re_card_type.is_match(&card_type) {
eprintln!("Failure parsing card type string '{}'.", card_type);
return None;
}
let caps = re_card_type.captures(&card_type).unwrap();
let gen = caps.get(1).unwrap().as_str().parse::<u32>().unwrap();
let mode = match caps.get(2).unwrap().as_str().parse::<char>().unwrap() {
'A' => ApqnMode::Accel,
'C' => ApqnMode::Cca,
'P' => ApqnMode::Ep11,
_ => panic!("Code inconsistence between regex RE_CARD_TYPE and evaluation code."),
};
if pv_core::misc::pv_guest_bit_set() {
// the UV blocks requests to CCA cards within SE guest with
// AP pass-through support. However, filter out CCA cards as these
// cards cause hangs during information gathering.
if mode == ApqnMode::Cca {
continue;
}
}
let queue_dirs = match sysfs_get_list_of_subdirs_matching_regex(&path, RE_QUEUE_DIR) {
Ok(r) => r,
Err(err) => {
eprintln!(
"Failure reading AP queue directories in {} ({:?}).",
path, err
);
return None;
}
};
for queue_dir in queue_dirs {
let _online = match sysfs_read_i32(&format!("{path}/{queue_dir}/online")) {
Ok(1) => true,
_ => continue,
};
let caps = re_queue_dir.captures(&queue_dir).unwrap();
let cardstr = caps.get(1).unwrap().as_str();
let card = u32::from_str_radix(cardstr, 16).unwrap();
let domstr = caps.get(2).unwrap().as_str();
let dom = u32::from_str_radix(domstr, 16).unwrap();
// For the mpvk and serialnr to fetch from the APQN within a SE
// guest the APQN needs to be bound to the guest. So if the APQN
// is not bound, temporarily bind it here until the info has
// been retrieved.
let mut tempbound = false;
if pv_core::misc::pv_guest_bit_set() {
let cbs = match get_apqn_bind_state(card, dom) {
Ok(bs) => bs,
Err(err) => {
eprintln!(
"Error: Failure reading APQN ({},{}) bind state: {}",
card, dom, err
);
BindState::NotSupported
}
};
if cbs == BindState::Unbound {
let r = set_apqn_bind_state(card, dom, BindState::Bound);
if r.is_err() {
eprintln!(
"Warning: Failure to temp. bind APQN ({},{}): {}",
card,
dom,
r.unwrap_err()
);
continue;
} else {
tempbound = true;
}
};
};
let info = match ApqnInfo::info(&mode, &path, &queue_dir) {
Err(err) => {
// print the error but continue with info set to None
eprintln!(
"Warning: Failure to gather info for APQN ({},{}): {}",
card, dom, err
);
None
}
Ok(i) => Some(i),
};
if tempbound {
let r = set_apqn_bind_state(card, dom, BindState::Unbound);
if r.is_err() {
eprintln!(
"Warning: Failure to unbind temp. bound APQN ({},{}): {}",
card,
dom,
r.unwrap_err()
);
}
};
apqns.push(Apqn {
name: queue_dir.clone(),
card,
domain: dom,
gen,
mode: mode.clone(),
info,
});
}
}
Some(ApqnList(apqns))
}
/// Sort this Apqnlist by card generation:
/// newest generation first, older generations last.
pub fn sort_by_gen(&mut self) {
self.0.sort_unstable_by(|a, b| b.gen.cmp(&a.gen));
}
/// Check MK restriction
///
/// Within one card there must not exist 2 APQNs with same
/// MK setup. This rule only applies to EP11 cards.
/// Returns true if this check passed,
/// otherwise false and a message is printed.
pub fn check_mk_restriction(&self) -> bool {
for a1 in self.0.iter() {
for a2 in self.0.iter() {
if a1.card == a2.card
&& a1.domain < a2.domain
&& a1.mode == ApqnMode::Ep11
&& a1.info.is_some()
&& a2.info.is_some()
{
let i1 = match a1.info.as_ref().unwrap() {
ApqnInfo::Ep11(i) => i,
_ => continue,
};
let i2 = match a2.info.as_ref().unwrap() {
ApqnInfo::Ep11(i) => i,
_ => continue,
};
if i1.mkvp.is_empty() || i2.mkvp.is_empty() {
continue;
}
if i1.mkvp == i2.mkvp {
eprintln!("APQN {} and APQN {} have same MPVK", a1, a2);
return false;
}
}
}
}
true
}
}
#[derive(PartialEq, Eq)]
pub enum BindState {
Bound,
Unbound,
NotSupported,
}
/// Query bind state for this APQN.
///
/// Returns a BindState enum as defined above or on failure
/// an error string. Does NOT print any error messages.
pub fn get_apqn_bind_state(card: u32, dom: u32) -> Result<BindState, String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
match sysfs_read_string(&path) {
Err(err) => Err(format!(
"Failure reading se_bind attribute for APQN({},{}): {:?}.",
card, dom, err
)),
Ok(str) => match str.as_str() {
"bound" => Ok(BindState::Bound),
"unbound" => Ok(BindState::Unbound),
"-" => Ok(BindState::NotSupported),
_ => Err(format!("Unknown bind state '{str}'.")),
},
}
}
/// Bind or unbind an APQN.
///
/// The action is determined by the BindState given in.
/// But of course only Bound and Unbound is supported - otherwise
/// this function panics!
/// The function actively loops over the bind state until
/// the requested bind state is reached or a timeout has
/// occurred (SYS_BUS_AP_BIND_TIMEOUT_MS).
/// On success () is returned, on failure an error string
/// is returned. Does NOT print any error messages.
/// # Panics
/// Panics if a desired bind state other than Bound or Unbound is given.
pub fn set_apqn_bind_state(card: u32, dom: u32, state: BindState) -> Result<(), String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
let r = match state {
BindState::Bound => sysfs_write_i32(&path, 1),
BindState::Unbound => sysfs_write_i32(&path, 0),
_ => panic!("set_apqn_bind_state called with invalid BindState."),
};
if r.is_err() {
return Err(format!(
"Failure writing se_bind attribute for APQN({},{}): {:?}.",
card,
dom,
r.unwrap_err()
));
}
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BIND_POLL_MS));
ms += SYS_BUS_AP_BIND_POLL_MS;
if ms >= SYS_BUS_AP_BIND_TIMEOUT_MS {
break Err(format!(
"Timeout setting APQN({},{}) bind state.",
card, dom
));
}
let newstate = match get_apqn_bind_state(card, dom) {
Err(err) => return Err(err),
Ok(s) => s,
};
if newstate == state {
return Ok(());
}
}
}
#[derive(PartialEq, Eq)]
pub enum AssocState {
Associated(u16),
AssociationPending,
Unassociated,
NotSupported,
}
/// Query association state for this APQN.
///
/// Returns an AssocState enum as defined above or on failure
/// an error string. Does NOT print any error messages.
pub fn get_apqn_associate_state(card: u32, dom: u32) -> Result<AssocState, String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
PATH_SYS_DEVICES_AP, card, card, dom
);
match sysfs_read_string(&path) {
Err(err) => Err(format!(
"Failure reading se_associate attribute for APQN({},{}: {:?}",
card, dom, err
)),
Ok(str) => {
if let Some(prefix) = str.strip_prefix("associated ") {
let value = &prefix.parse::<u16>();
match value {
Ok(v) => Ok(AssocState::Associated(*v)),
Err(_) => Err(format!("Invalid association index in '{str}'.")),
}
} else {
match str.as_str() {
"association pending" => Ok(AssocState::AssociationPending),
"unassociated" => Ok(AssocState::Unassociated),
"-" => Ok(AssocState::NotSupported),
_ => Err(format!("Unknown association state '{str}'.")),
}
}
}
}
}
fn set_apqn_associate_state_associate(card: u32, dom: u32, idx: u16) -> Result<(), String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
PATH_SYS_DEVICES_AP, card, card, dom
);
let r = sysfs_write_i32(&path, idx as i32);
if r.is_err() {
return Err(format!(
"Failure writing se_associate attribute for APQN({},{}): {:?}.",
card,
dom,
r.unwrap_err()
));
}
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
ms += SYS_BUS_AP_ASSOC_POLL_MS;
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
break Err(format!(
"Timeout setting APQN({},{}) association idx {} state.",
card, dom, idx
));
}
let newstate = match get_apqn_associate_state(card, dom) {
Err(err) => return Err(err),
Ok(s) => s,
};
if let AssocState::Associated(i) = newstate {
if idx == i {
return Ok(());
} else {
return Err(format!(
"Failure: APQN({},{}) is associated with {} but it should be {}.",
card, dom, i, idx
));
}
}
}
}
fn set_apqn_associate_state_unbind(card: u32, dom: u32) -> Result<(), String> {
let bindpath = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
let r = sysfs_write_i32(&bindpath, 0);
if r.is_err() {
return Err(format!(
"Failure writing se_bind attribute for APQN({},{}): {:?}.",
card,
dom,
r.unwrap_err()
));
}
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
ms += SYS_BUS_AP_ASSOC_POLL_MS;
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
break Err(format!(
"Timeout setting APQN({},{}) association unbind state.",
card, dom
));
}
let newstate = match get_apqn_associate_state(card, dom) {
Err(err) => return Err(err),
Ok(s) => s,
};
if newstate == AssocState::Unassociated {
return Ok(());
}
}
}
/// Associate or Unassociate an APQN.
///
/// The action is determined by the AssocState given in.
/// But of course only Associated and Unassociated is supported
/// otherwise this function panics!
/// The function actively loops over the association state until
/// the requested state is reached or a timeout has
/// occurred (SYS_BUS_AP_ASSOC_TIMEOUT_MS).
/// The unassociate is in fact a unbind. So the code triggers
/// an unbind and then loops over the sysfs se_associate until
/// "unassociated" is reached.
/// On success () is returned, on failure an error string
/// is returned. Does NOT print any error messages.
/// # Panics
/// Panics if a desired bind state other than Associated or
/// Unassociated is given.
pub fn set_apqn_associate_state(card: u32, dom: u32, state: AssocState) -> Result<(), String> {
match state {
AssocState::Associated(idx) => set_apqn_associate_state_associate(card, dom, idx),
AssocState::Unassociated => set_apqn_associate_state_unbind(card, dom),
_ => panic!("set_apqn_associate_state called with invalid AssocState."),
}
}
#[cfg(test)]
mod tests {
use super::*;
// These tests assume, there is an AP bus available
// Also for each APQN which is online, it is assumed
// to have a valid master key set up (for Ep11 and CCA).
#[test]
fn test_check_ap_bus_support() {
if Path::new(PATH_SYS_BUS_AP).is_dir() {
assert!(check_ap_bus_support().is_ok());
} else {
assert!(check_ap_bus_support().is_err());
}
}
#[test]
fn test_check_ap_bus_apsb_support() {
if Path::new(PATH_SYS_BUS_AP).is_dir() {
// if we are inside a secure execution guest the
// apsb check should succeed. Outside an SE guest
// the check should fail.
if pv_core::misc::pv_guest_bit_set() {
assert!(ap_bus_has_apsb_support().is_ok());
} else {
assert!(ap_bus_has_apsb_support().is_err());
}
} else {
assert!(ap_bus_has_apsb_support().is_err());
}
}
#[test]
fn test_wait_for_ap_bus_bindings_complete() {
let r = wait_for_ap_bus_bindings_complete();
if Path::new(PATH_SYS_BUS_AP).is_dir() {
assert!(r);
} else {
assert!(!r);
}
}
#[test]
fn test_gather_apqns() {
let r = ApqnList::gather_apqns();
if Path::new(PATH_SYS_BUS_AP).is_dir() {
assert!(r.is_some());
// fail if no entries found
let l = r.unwrap();
let v = l.to_apqn_vec();
for a in v {
match a.mode {
ApqnMode::Accel => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
}
ApqnMode::Ep11 => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
let info = a.info.unwrap();
let i = match &info {
ApqnInfo::Ep11(i) => i,
_ => panic!("ApqnInfo attached onto Ep11 APQN is NOT ApqnInfoEp11 ?!?"),
};
// fail if no serialnr
assert!(!i.serialnr.is_empty());
// mkvp is either empty (no WK set) or has exact 32 characters
assert!(i.mkvp.is_empty() || i.mkvp.len() == 32);
}
ApqnMode::Cca => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
let info = a.info.unwrap();
let i = match &info {
ApqnInfo::Cca(i) => i,
_ => panic!("ApqnInfo attached onto Cca APQN is NOT ApqnInfoCca ?!?"),
};
// fail if no serialnr
assert!(!i.serialnr.is_empty());
// aes mkvp is either empty (no MK set) or exact 16 characters
assert!(i.mkvp_aes.is_empty() || i.mkvp_aes.len() == 16);
// apka mkvp is either empty (no MK set) or exact 16 characters
assert!(i.mkvp_apka.is_empty() || i.mkvp_apka.len() == 16);
}
}
}
} else {
assert!(r.is_none());
}
}
}

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! Command line interface for pvapconfig
//
use clap::Parser;
use lazy_static::lazy_static;
/// The default pvapconfig config file
pub const PATH_DEFAULT_CONFIG_FILE: &str = "/etc/pvapconfig.yaml";
#[derive(Parser, Clone)]
pub struct Cli {
/// Provide a custom config file (overwrites default /etc/pvapconfig.yaml).
#[arg(short, long, value_name = "FILE")]
pub config: Option<String>,
/// Dry run: display the actions but don't actually perform them on the APQNs.
#[arg(short = 'n', long = "dry-run")]
pub dryrun: bool,
/// Enforce strict match: All config entries need to be fulfilled.
///
/// By default it is enough to successfully apply at least one config entry.
/// With the strict flag enabled, all config entries within a config file
/// need to be applied successful.
#[arg(long = "strict")]
pub strict: bool,
/// Provide more detailed output.
#[arg(short, long)]
pub verbose: bool,
/// Print version information and exit.
#[arg(short = 'V', long)]
pub version: bool,
}
lazy_static! {
pub static ref ARGS: Cli = Cli::parse();
}
impl Cli {
/// verbose returns true if the verbose command line option
/// was given, otherwise false is returned.
pub fn verbose(&self) -> bool {
self.verbose
}
/// dryrun returns true if the dry-run command line option
/// was given, otherwise false is returned.
pub fn dryrun(&self) -> bool {
self.dryrun
}
/// strict returns true if the strict flag was given, otherwise
/// false is returned.
pub fn strict(&self) -> bool {
self.strict
}
}

View File

@@ -0,0 +1,391 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! Functions around handling the pvapconfig configuration file
//
use openssl::sha::sha256;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_yaml::{self};
use std::fs::File;
use std::slice::Iter;
pub const STR_MODE_EP11: &str = "ep11";
pub const STR_MODE_ACCEL: &str = "accel";
const RE_EP11_MKVP_32: &str = r"^(0x)?([[:xdigit:]]{32})$";
const RE_EP11_MKVP_64: &str = r"^(0x)?([[:xdigit:]]{64})$";
const RE_SERIALNR: &str = r"^(\S{16})$";
const RE_EP11_GEN: &str = r"^cex(8)$";
const RE_ACCEL_GEN: &str = r"^cex([4-8])$";
const RE_SECRETID: &str = r"^(0x)?([[:xdigit:]]{64})$";
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[serde(default, deny_unknown_fields)]
pub struct ApConfigEntry {
pub name: String, // name and description are unmodified from the config file
pub description: String, // accel after validation ep11 after validation
pub mode: String, // "accel" "ep11"
pub mkvp: String, // empty 32 hex lowercase characters
pub serialnr: String, // empty empty or 16 non-whitespace characters
pub mingen: String, // empty or "cex4"..."cex8" empty or "cex8"
pub secretid: String, // empty 64 hex lowercase characters
}
impl ApConfigEntry {
fn validate_secretid(&mut self) -> Result<(), String> {
// either secret id or name may be given
if self.secretid.is_empty() && self.name.is_empty() {
return Err("Neither secretid nor name given.".to_string());
}
// if name is given, calculate sha256 digest for this name
// test for the hash calculated here can be done with openssl:
// echo -n "Hello" >in.bin; openssl dgst -sha256 -binary -out out.bin in.bin; hexdump -C out.bin
if self.name.is_empty() {
return Ok(());
}
let hash = sha256(self.name.as_bytes());
let hashstr = crate::helper::u8_to_hexstring(&hash);
// if there is a secretid given, this must match to the hash
if !self.secretid.is_empty() {
if self.secretid != hashstr {
return Err("Mismatch between sha256(name) and secretid.".to_string());
}
} else {
self.secretid = hashstr;
}
Ok(())
}
/// # Panics
/// Panics if the compilation of a static regular expression fails.
fn validate_ep11_entry(&mut self) -> Result<(), String> {
// mkvp is required
let mut mkvp = self.mkvp.trim().to_lowercase();
if mkvp.is_empty() {
return Err("Mkvp value missing.".to_string());
}
// either 64 hex or 32 hex
if Regex::new(RE_EP11_MKVP_64).unwrap().is_match(&mkvp) {
// need to cut away the last 32 hex characters
mkvp = String::from(&mkvp[..mkvp.len() - 32])
} else if Regex::new(RE_EP11_MKVP_32).unwrap().is_match(&mkvp) {
// nothing to do here
} else {
return Err(format!("Mkvp value '{}' is not valid.", &self.mkvp));
}
self.mkvp = match mkvp.strip_prefix("0x") {
Some(rest) => String::from(rest),
None => mkvp,
};
// serialnr is optional
let serialnr = self.serialnr.trim().to_string();
if !serialnr.is_empty() && !Regex::new(RE_SERIALNR).unwrap().is_match(&serialnr) {
return Err(format!("Serialnr value '{}' is not valid.", &self.serialnr));
}
self.serialnr = serialnr;
// mingen is optional, but if given only CEX8 is valid
let mingen = self.mingen.trim().to_lowercase();
if !mingen.is_empty() && !Regex::new(RE_EP11_GEN).unwrap().is_match(&mingen) {
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
}
self.mingen = mingen;
// secretid or name is required
let secretid = self.secretid.trim().to_lowercase();
if !secretid.is_empty() && !Regex::new(RE_SECRETID).unwrap().is_match(&secretid) {
return Err(format!("Secretid value '{}' is not valid.", &self.secretid));
}
self.secretid = match secretid.strip_prefix("0x") {
Some(rest) => String::from(rest),
None => secretid,
};
// name is optional, ignored here
// description is optional, ignored here
// but the secretid needs some more validation
self.validate_secretid()
}
/// # Panics
/// Panics if the compilation of a static regular expression fails.
fn validate_accel_entry(&mut self) -> Result<(), String> {
// mkvp is ignored
self.mkvp.clear();
// serialnr is ignored
self.serialnr.clear();
// mingen is optional, but if given must match to CEX4..CEX8
let mingen = self.mingen.trim().to_lowercase();
if !mingen.is_empty() && !Regex::new(RE_ACCEL_GEN).unwrap().is_match(&mingen) {
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
}
self.mingen = mingen;
// secretid is ignored
self.secretid.clear();
// name is optional, ignored here
// description is optional, ignored here
Ok(())
}
fn validate(&mut self) -> Result<(), String> {
// trim name
self.name = self.name.trim().to_string();
// mode is always required
let mode = self.mode.trim().to_lowercase();
match mode.as_str() {
STR_MODE_EP11 => {
self.mode = mode;
self.validate_ep11_entry()?;
}
STR_MODE_ACCEL => {
self.mode = mode;
self.validate_accel_entry()?;
}
_ => return Err(format!("Unknown or invalid mode '{}'.", mode)),
}
Ok(())
}
}
/// Wrapper object around Vector of ApConfigEntry
pub struct ApConfigList(Vec<ApConfigEntry>);
impl ApConfigList {
#[cfg(test)] // only used in test code
pub fn from_apconfigentry_vec(apconfigs: Vec<ApConfigEntry>) -> ApConfigList {
ApConfigList(apconfigs)
}
pub fn iter(&self) -> Iter<'_, ApConfigEntry> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn read_yaml_file(fname: &str) -> Result<Vec<ApConfigEntry>, String> {
let file = match File::open(fname) {
Ok(f) => f,
Err(err) => {
return Err(format!(
"Failure to open AP config file {}: {:?}",
fname, err
))
}
};
match serde_yaml::from_reader(file) {
Ok(cfg) => Ok(cfg),
Err(err) => Err(format!(
"Failure parsing AP config file {}: {:?}",
fname, err
)),
}
}
fn validate(config: &mut [ApConfigEntry]) -> Result<(), String> {
for (i, entry) in config.iter_mut().enumerate() {
let ename = if !entry.name.trim().is_empty() {
format!("AP config entry {} '{}'", i, entry.name.trim())
} else {
format!("AP config entry {}", i)
};
if let Err(err) = &entry.validate() {
return Err(format!("{}: {}", ename, err));
}
}
Ok(())
}
/// Read in and validate the yaml configuration from a file.
/// Returns a Result with Ok(ApConfigList) on success
/// or an Err(errorstring) on failure.
pub fn read_and_validate_yaml_file(fname: &str) -> Result<ApConfigList, String> {
let mut apconfig = ApConfigList::read_yaml_file(fname)?;
ApConfigList::validate(&mut apconfig)?;
Ok(ApConfigList(apconfig))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::fs;
use std::io::Write;
const GOOD_CONFIGS: [&str; 8] = [
"# good test 1
- name: my Accelerator
mode: AcCel
mingen: Cex7\n",
"# good test 2
- name: my Accelerator 2
description: Accelerator entry with description
mode: Accel\n",
"# good test 3
- name: my EP11 APQN 1
mode: Ep11
mkvp: 0xDB3C3B3C3F097DD55EC7EB0E7FDBCB93
serialnr: 93AADFK719460083
secretid: 0xBC9d46c052BC3574454C5715757274629a283767ed237922cfb8651c0e77320A\n",
"# good test 4
- name: my EP11 APQN 2
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
serialnr: 93aaDHzu42082261
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
"# good test 5
- name: my EP11 APQN 3
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93db3c3b3c3f097dd55ec7eb0e7fdbcb93
serialnr: 93aaDHzu42082261
secretid: 0xd146c9ae77cdff25fa87a5b3487587dc29a4e391b315c98570e8fa2e2ec91454\n",
"# no name but secretid given
- mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
"# no secretid but name given
- mode: EP11
name: My-EP11-AP-config
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93\n",
"# secretid and name given
- mode: EP11
name: My-EP11-AP-config
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
];
const BAD_CONFIGS: [&str; 12] = [
"# mode missing
- name: bad test 1
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
"# invalid mode
- name: bad test 2
mode: CCA
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
"# Accelerator with wrong CEX3
- name: bad test 3
mode: Accel
mingen: Cex3\n",
"# Accelerator with wrong CEX9
- name: bad test 4
mode: Accel
mingen: CEX9\n",
"# EP11 with mkvp missing
- name: bad test 5
mode: EP11
serialnr: 93AADHZU42082261\n",
"# EP11 with non hex mkvp
- name: bad test 6
mode: EP11
mkvp: 0xabcdefghijklmnopqqponmlkjihgfedcba
serialnr: 93AADHZU42082261\n",
"# EP11 with mkvp too big
- name: bad test 7
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93aa
serialnr: 93AADHZU42082261\n",
"# EP11 with mkvp too small
- name: bad test 8
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb
serialnr: 93AADHZU42082261\n",
"# EP11 with invalid CEXx
- name: bad test 9
mode: EP11
mingen: CEX7
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: 93AADHZU42082261\n",
"# EP11 with invalid Serialnr
- name: bad test 10
mode: EP11
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: 93AADHZU4208226\n",
"# EP11 with invalid Serialnr
- name: bad test 11
mode: EP11
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: 93AAD ZU42082261\n",
"# EP11 with sha256(name) != secretid
- name: bad test 12
mode: EP11
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: AABBCCDDEEFFGGHH
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
];
const BAD_DESERIALIZE: [&str; 2] = [
"/*\ntotal nonsense\n */\n",
"# wrong/unknown field
- name: de-serialize failure 1
type: EP11\n",
];
fn write_yaml_config_to_temp_file(content: &str) -> Result<String, String> {
let dir = env::temp_dir();
let rnd = rand::random::<u32>();
let fname = format!("{}/config-test-{}.yaml", dir.to_str().unwrap(), rnd);
let mut f = match File::create(&fname) {
Ok(f) => f,
Err(_) => return Err(format!("Failure creating temp file '{fname}'.")),
};
match f.write_all(content.as_bytes()) {
Ok(_) => Ok(fname),
Err(_) => {
fs::remove_file(&fname).ok();
Err(format!("Failure writing to temp file '{fname}'."))
}
}
}
#[test]
fn test_good_yaml() {
for yaml in GOOD_CONFIGS {
let f = write_yaml_config_to_temp_file(yaml).unwrap();
let config = ApConfigList::read_and_validate_yaml_file(&f).unwrap();
assert!(!config.is_empty());
fs::remove_file(&f).ok();
}
}
#[test]
fn test_bad_yaml() {
for yaml in BAD_CONFIGS {
let f = write_yaml_config_to_temp_file(yaml).unwrap();
let r = ApConfigList::read_and_validate_yaml_file(&f);
assert!(r.is_err());
fs::remove_file(&f).ok();
}
}
#[test]
fn test_invalid_deserizalize() {
for yaml in BAD_DESERIALIZE {
let f = write_yaml_config_to_temp_file(yaml).unwrap();
let r = ApConfigList::read_and_validate_yaml_file(&f);
assert!(r.is_err());
fs::remove_file(&f).ok();
}
}
#[test]
fn test_sha256() {
assert!(
crate::helper::u8_to_hexstring(&sha256("Hello".as_bytes()))
== "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"
);
assert!(
crate::helper::u8_to_hexstring(&sha256("SECRET1".as_bytes()))
== "03153249db7ce46b0330ffb1a760b59710531af08ec4d7f8424a6870fae49360"
);
assert!(
crate::helper::u8_to_hexstring(&sha256("SECRET2".as_bytes()))
== "258499e710e0bd3bb878d6bac7e478b30f3f3e72566989f638c4143d14f6c0b6"
);
}
}

View File

@@ -0,0 +1,279 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! Collection of helper functions for pvapconfig
//
use regex::Regex;
use std::error::Error;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::PathBuf;
pub const PATH_PVAPCONFIG_LOCK: &str = "/run/lock/pvapconfig.lock";
/// Convert u8 slice to (lowercase) hex string
pub fn u8_to_hexstring(slice: &[u8]) -> String {
let s = String::with_capacity(2 * slice.len());
slice.iter().fold(s, |acc, e| acc + &format!("{e:02x}"))
}
/// Convert hexstring to u8 vector
/// The hexstring may contain whitespaces which are ignored.
/// If there are other characters in there or if the number
/// of hex characters is uneven panic() is called.
/// # Panics
/// Panics if the given string contains characters other than
/// hex digits and whitespace. Panics if the number of hex digits
/// is not even.
#[cfg(test)] // currently only used in test code
pub fn hexstring_to_u8(hex: &str) -> Vec<u8> {
let mut s = String::new();
for c in hex.chars() {
if c.is_ascii_hexdigit() {
s.push(c);
} else if c.is_whitespace() {
// ignore
} else {
panic!("Invalid character '{c}'");
}
}
if s.len() % 2 == 1 {
panic!("Uneven # of hex characters in '{s}'");
}
let mut hex_bytes = s.as_bytes().iter().map_while(|b| match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
});
let mut bytes = Vec::with_capacity(s.len());
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
bytes.push(h << 4 | l)
}
bytes
}
/// Read sysfs file into string
pub fn sysfs_read_string(fname: &str) -> Result<String, Box<dyn Error>> {
let mut file = File::open(fname)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
let trimmed_content = String::from(content.trim());
Ok(trimmed_content)
}
/// Write string into sysfs file
pub fn sysfs_write_string(fname: &str, value: &str) -> Result<(), Box<dyn Error>> {
let mut file = OpenOptions::new().write(true).open(fname)?;
file.write_all(value.as_bytes())?;
Ok(())
}
/// Read sysfs file content and parse as i32 value
pub fn sysfs_read_i32(fname: &str) -> Result<i32, Box<dyn Error>> {
let content = sysfs_read_string(fname)?;
Ok(content.parse::<i32>()?)
}
/// Write an i32 value into a sysfs file
pub fn sysfs_write_i32(fname: &str, value: i32) -> Result<(), Box<dyn Error>> {
sysfs_write_string(fname, &value.to_string())
}
/// For a given (sysfs) directory construct a list of all subdirs
/// and give it back as a vector of strings. If there is no subdir,
/// the vector is empty.
pub fn sysfs_get_list_of_subdirs(dname: &str) -> Result<Vec<String>, Box<dyn Error>> {
let mut v: Vec<String> = Vec::new();
let entries = fs::read_dir(dname)?;
for entry in entries.flatten() {
let file_type = match entry.file_type() {
Ok(ft) => ft,
_ => continue,
};
if !file_type.is_dir() {
continue;
}
let fname = match entry.file_name().into_string() {
Ok(s) => s,
_ => continue,
};
v.push(fname);
}
Ok(v)
}
/// For a given (sysfs) directory construct a list of all subdirs which
/// match to the given regular expression and give the list back as a
/// vector of strings. If there is no subdir, the vector is empty.
pub fn sysfs_get_list_of_subdirs_matching_regex(
dname: &str,
regex: &str,
) -> Result<Vec<String>, Box<dyn Error>> {
let mut v: Vec<String> = Vec::new();
let re = Regex::new(regex)?;
let entries = sysfs_get_list_of_subdirs(dname)?;
for entry in entries {
if re.is_match(&entry) {
v.push(entry);
}
}
Ok(v)
}
/// LockFile for inter-process locking
///
/// Simple class for process locking for pvapconfig.
/// The lock concept is simple: The existence of a file is used as the
/// locking indicator. If the file exists something is locked, if it does
/// not exist something is not locked. In the lock file the PID of the
/// process created the file ("owning this file") is written in.
/// With the ProcessLock object leaving scope the associated lock file
/// is automatically deleted. It is assumed that the creation of a file
/// is an atomic operation - that's true for most filesystems but may
/// cause problems with network based filesystems.
/// Example:
/// ```
/// let lock = LockFile::lock("/var/lock/process.lock");
/// assert!(lock.is_ok());
/// let lock2 = LockFile::lock("/var/lock/process.lock");
/// assert!(lock2.is_err());
/// drop(lock);
/// let lock3 = LockFile::lock("/var/lock/process.lock");
/// assert!(lock3.is_ok());
/// ```
#[derive(Debug)]
pub struct LockFile {
lockfile: PathBuf,
}
impl LockFile {
/// Try to establish the lock file.
/// Upon success the given file is fresh created and has the pid of this
/// process written in. The function returns a new LockFile object
/// which has implemented the Drop Trait. So with this object going out
/// of scope the lock file is deleted. If establishing the lock file
/// fails for any reason (for example the file already exists), the
/// function fails with returning an Error string. This function does
/// NOT panic if establishing the lock file fails for any reason. If
/// the lock file could be esablished but writing in the PID fails, a
/// warning is printed but the function continues with returning a
/// LockFile object.
pub fn try_lock(fname: &str) -> Result<Self, String> {
let lockfile = PathBuf::from(fname);
let mut file = match OpenOptions::new()
.write(true)
.create_new(true)
.open(&lockfile)
{
Err(err) => {
return Err(format!(
"Failure trying to create lock file {fname}: {err:?}."
))
}
Ok(f) => f,
};
let _ = file
.write(format!("{}", std::process::id()).as_bytes())
.map_err(|err| {
println!("Warning: could not write PID into lockfile {fname}: {err:?}.")
});
Ok(LockFile { lockfile })
}
}
impl Drop for LockFile {
fn drop(&mut self) {
let _ = fs::remove_file(&self.lockfile).map_err(|err| {
println!(
"Warning: could not remove lockfile {}: {err:?}.",
self.lockfile.display()
)
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use pv_core::misc::TemporaryDirectory;
// Only very simple tests
const TEST_BYTES: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
const TEST_HEXSTR: &str = "0123456789abcdef";
#[test]
fn test_u8_to_hexstring() {
let str = u8_to_hexstring(&TEST_BYTES);
assert!(str == TEST_HEXSTR);
}
#[test]
fn test_hexstring_to_u8() {
let bytes = hexstring_to_u8(TEST_HEXSTR);
assert!(bytes.as_slice() == TEST_BYTES);
}
#[test]
fn test_sysfs_read_string() {
let r = sysfs_read_string("/proc/cpuinfo");
assert!(r.is_ok());
}
#[test]
fn test_sysfs_read_i32() {
let r = sysfs_read_i32("/proc/sys/kernel/random/entropy_avail");
assert!(r.is_ok());
}
#[test]
fn test_sysfs_get_list_of_subdirs() {
let r = sysfs_get_list_of_subdirs("/proc/self");
assert!(r.is_ok());
let v = r.unwrap();
assert!(!v.is_empty());
}
#[test]
fn test_sysfs_get_list_of_subdirs_matching_regex() {
let r = sysfs_get_list_of_subdirs_matching_regex("/proc/self", "fd.*");
assert!(r.is_ok());
let v = r.unwrap();
assert!(!v.is_empty());
for e in v {
assert!(e.strip_prefix("fd").is_some());
}
}
#[test]
fn test_sysfs_write_i32() {
let temp_dir =
TemporaryDirectory::new("pvtests").expect("creating a temporary directory should work");
let test_path = temp_dir.path().join("test");
let test_path = test_path.as_os_str().to_str().expect("should work");
let mut file = File::create(test_path).unwrap();
let _ = file.write_all(b"XYZ");
drop(file);
let r = sysfs_read_i32(test_path);
assert!(r.is_err());
let r = sysfs_write_i32(test_path, 999);
assert!(r.is_ok());
let r = sysfs_read_i32(test_path);
assert!(r.is_ok());
let v = r.unwrap();
assert!(v == 999);
}
#[test]
fn test_lockfile() {
let temp_dir =
TemporaryDirectory::new("pvtests").expect("creating a temporary directory should work");
let file_path = temp_dir.path().join("my.lock");
let file_path_str = file_path.to_str().expect("should work");
let r1 = LockFile::try_lock(file_path_str);
assert!(r1.is_ok());
let r2 = LockFile::try_lock(file_path_str);
assert!(r2.is_err());
drop(r1);
let r3 = LockFile::try_lock(file_path_str);
assert!(r3.is_ok());
}
}

Some files were not shown because too many files have changed in this diff Show More