Compare commits

...

120 Commits

Author SHA1 Message Date
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
Jan Höppner
d9ce54dee3 New release s390-tools-2.29.0
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 14:51:41 +02:00
Marc Hartmayer
7b056735ed rust/pv: some cargo clippy fixes
Found and fixed by the command `cargo clippy --fix -- -Dwarnings`.

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-08-04 14:18:49 +02:00
Marc Hartmayer
33fde99138 rust: pv/pvsecret: some typo fixes
It reads 'add-secret requests' and not 'add secret requests'.

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-08-04 14:18:49 +02:00
Steffen Maier
4b486e87cc zdev/dracut: fix kdump build to integrate with site support
This complements v2.27.0 commit 73c46a3056 ("zdev/dracut: fix kdump by
only activating required devices"). On older distributions, the absence of
zdev_id can cause the following harmless error messages for each udev
event:

(spawn)[387]: failed to execute '/lib/s390-tools/zdev_id' \
'/lib/s390-tools/zdev_id': No such file or directory

Kdump is still functional nonetheless.

As of v2.24.0 commit 2e89722ef0 ("zdev: make site specific udev-rule for
ccw"), the invocations of chzdev within
zdev/dracut/95zdev-kdump/module-setup.sh generate
/etc/udev/rules.d/40-zdev-id.rules. And so even though zdev-kdump
intentionally does not install zdev_id and its previous singular user
zdev/udev/81-dpm.rules into the kdump initrd, because DPM device auto
configuration is not desired in the kdump environment, zdev_id meanwhile
has an additional functionality for site-support and the generated
40-zdev-id.rules calls /lib/s390-tools/zdev_id. By installing zdev_id into
the kdump initrd, 40-zdev-id.rules can work without error.

Fixes: 73c46a3056 ("zdev/dracut: fix kdump by only activating required devices")
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
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-08-04 13:10:00 +02:00
Marc Hartmayer
a07d1bca74 pvattest: Add --output option to verify subcommand
Other tools may need to process the configuration-unique id. Provide a
machine readable format by writing to a YAML file containing a `cuid`
entry and optionally an `add` entry. New CLI options `--format` and
`--output` are introduced for this. Currently, only the output format
`yaml` is supported.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-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-08-04 13:10:00 +02:00
Marc Hartmayer
5e1ef90962 pvattest: refactor fprint_verify_result
Refactor the code responsible for printing the verification result into
a new function named `fprint_verify_result`. This function will be
reused in the future and a new output format will be added. While at it,
increase the dump data width for the addition data. In addition, add a
prefix `0x` to the values in order to indicate that these are
hexadecimal values.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-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-08-04 13:10:00 +02:00
Marc Hartmayer
8fe214d915 pvattest: pvattest_hexdump: add error checks
`fprintf` can fail, therefore check the return code of it.

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-08-04 13:10:00 +02:00
Marc Hartmayer
bec9d1dfcd pvattest: pvattest_hexdump: add beautify parameter
Add `beautify` parameter to `pvattest_hexdump`. If the parameter is set
to true, a offset and whitespaces will be added for better readability.

With beautify set to FALSE:

14141414141414141414141414141414

With beautify set to TRUE:

0x0000  1414 1414 1414 1414 1414 1414 1414 1414

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 13:10:00 +02:00
Marc Hartmayer
694d5d4638 pvattest: pvattest_log_bytes: use GBytes and handle @width == 0
The only user of `pvattest_hexdump` uses GBytes anyway, therefore let's
use GBytes as parameter type for `pvattest_hexdump`.

While at it, change the order of the parameters, constify `@width` and
handle the `@width == 0` case, which results in an hex-string without
any line breaks.

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-08-04 13:10:00 +02:00
Marc Hartmayer
a6ac7cd876 common.mak: set SHELL to /bin/bash
This fixes the following error (using GNU Make 4.3.0):

  make[2]: command: Command not found

The reason for this is that `command` is a bash builtin. `command` is
used in `common.mak` for the `combdb` Makefile target.

While at it, remove the now useless `SHELL := /bin/bash` definitions in
the sub-Makefiles.

Fixes: 3d098416c6 ("common.mak: add `compdb` Makefile target")
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-08-04 11:41:25 +02:00
Joern Siglen
28751a097a dbginfo.sh: global original Input Field Separator (IFS)
Replace local ifs_orig variables in different functions by a single
global IFS_ORI variable. This will reduce the risk of missing a local
saving and restore of the original IFS.

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>
2023-08-04 11:41:25 +02:00
Steffen Eiden
19c795be0d rust: Add README
Add a README.md to the rust subdirectory as a guideline for writing
s390-tools tools in Rust. This includes build integration, dependency
handling, and a few coding style hints. Rust related information
is also added to the main README.md.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
[hoeppner@linux.ibm.com: Adapt details in README.md]
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:41:21 +02:00
Steffen Eiden
dd82c26f87 rust: Add tool to manage UV-secrets
Add `pvsecret` a tool to create, add, list, and delete Ultravisor
secrets. `pvsecret` uses the functionality from the pv-crate
to provide an command line tool to manage the secrets.

Add a new target group PV_TARGETS in rust/Makefile that additionally
requires openssl and libcurl as pv with the feature "request" uses
openssl and libcurl fearures.

Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
[hoeppner@linux.ibm.com: Adapt man pages and help output]
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:41:09 +02:00
Steffen Eiden
c6f621d0dc rust: Add library for pv tools
Add a `pv` crate that bundles useful functions and structs for creating
requests like `Attestation`, `Add Secret`, or even `Boot` a.k.a.
Secure Execution Image.
Note pv includes a subcrate `openssl_extensions` that (temporarily)
bundles some needed `openssl-rust` functionalities that are not
upstream yet. The plan is to remove these, when they become
upstream.

The pv crate has multiple features:
 * request - code to generate requests
 * uvsecret - code to access the UV-secret api
		with request enabled also generating requests is
		possible

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:40:54 +02:00
Steffen Eiden
e6add997eb Integrate rust into s390-tools build system
The rust integration into the s390-tools build system consists of the
following steps:

- Add a subdirectory for the rust code.
- Add a Makefile that forwards rust builds to `cargo`.
- Add a `utils` crate for rust code in s390-tools.
- Add rust stuff for dotfiles:
  - gitignore
  - editorconfig
  - codespellrc (while at it, add an ignore file)

With cargo the rust ecosystem has its own build system which also is
responsible to resolve rust dependencies via downloading the dependencies
from (default) crates.io and discover the source files. Therefore, the
Makefile just calls `cargo build` to forward the build to cargo.

If a rust crate does not require external dependencies, users might call
rustc directly.

A simple `make` will build all the rust targets as well (with --release
specified). Also `make install` will work as usual.

A few Makefile configuration variables are introduced for rust/Cargo:
  - HAVE_CARGO (default 1) to toggle the build of rust code using cargo
  - CARGOFLAGS             to add custom cargo flags, e.g. --offline
  - CARGO		   Cargo binary location defaults to
                           $(where cargo)

A new global make target is defined to get the current s390-tools
version:
$ make version
  2.28.0

rust/Makefile also has the `print-rust-targets`  target to print all rust
directories/crates that should be shipped/installed.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:40:46 +02:00
Marc Hartmayer
a2baeb2fe2 zdev/dracut/zdev-lib.sh: add ShellCheck shell directive
Add a ShellCheck [1] directive before the first command in the file to tell
ShellCheck and the reader which shell to use. See
https://www.shellcheck.net/wiki/SC2148 for details.

[1] https://www.shellcheck.net/

Reviewed-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Thomas Richter
a500946e50 vmur: fix wrong command flag exclusion
Command vmur issues a warning and aborts receiving a file from
the reader when either option -t or option -b is specified.
Example:
 ./vmur re -t 0x25,0x40 22 -H /tmp/xxx.txt
 vmur: Conflicting options: -b and -t are mutually exclusive.
This is wrong as there is only one option specified.
The command should be aborted only when both flags are specified.

Fix this wrong behavior.

Fixes: d5f853c460 ("vmur: Remove option -c for dump file conversion")
Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reported-by: Benjamin Block <bblock@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
b7c9c2679e genprotimg: add support for add-secret requests
IBM Secure Execution guests may want to share additional secrets with
the Ultravisor in a secure manner. For this the concept of secret
requests and three new Ultravisor-calls were introduced.

Add support to genprotimg to prepare an Secure Execution image with the
requirement that add-secret requests must provide an extension secret
that matches the customer communication key (CCK) derived extension
secret.

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-08-02 14:48:12 +02:00
Marc Hartmayer
bc8a14895a genprotimg: improve the --comm-key description
In the future, this key will be used for additional things than the
guest dump encryption.

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-08-02 14:48:12 +02:00
Marc Hartmayer
4ae68d0430 genprotimg: add NAME macro parameter
In preparation for the next patch, add the parameter `NAME` to the
`MUT_EXCL_BOOL_FLAG` macro. This is useful for the case when the command
line flag has a different naming than the struct field.

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-08-02 14:48:12 +02:00
Marc Hartmayer
aabf97f885 genprotimg: refactor arguments related to the control flags into own struct
Refactor arguments related to the SE header control flags into own
struct with the name `PvControlFlagsArgs`. This change makes it easier
to extend the control flags arguments further, without touching the
signature of `pv_img_set_control_flags`. While at it, rename the struct
members `allow_...` to `enable_...`. This matches with the command line
option names.

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-08-02 14:48:12 +02:00
Marc Hartmayer
a9b546cb0f genprotimg: pv_img_set_control_flags: refactor code
Introduce a function for setting the control flags. This makes it easier
to add more control flags in the future.

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-08-02 14:48:12 +02:00
Marc Hartmayer
5566c31458 genprotimg: help|manpage: remove superfluous optional
A default is always optional, therefore let's remove the superfluous
"optional" statements.

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-08-02 14:48:12 +02:00
Marc Hartmayer
ff96d6158e genprotimg/boot: avoid the deletion of .lds files by using .SECONDARY
Avoid the deletion of the intermediate
`(stage3a|stage3b|stage3b_reloc).lds` files by adding them to the
special built-in target `.SECONDARY` as prerequisites. This way they're
declared as intermediate files that should never be deleted
automatically. [1]

[1] https://www.gnu.org/software/make/manual/html_node/Special-Targets.html#index-preserving-with-_002eSECONDARY

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-08-02 14:48:12 +02:00
Marc Hartmayer
2aa9071aed genprotimg: help: add missing period
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-08-02 14:48:12 +02:00
Marc Hartmayer
adf2a030e6 genprotimg: fix error message
The option name is `--enable-dump` and not `--allow-dump`.

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-08-02 14:48:12 +02:00
Marc Hartmayer
0783aa99d7 zipl/boot: simplify clean target
Simplify the clean target since there are several redundant
things (*.bin) and files that aren't built at all (.xxx and .yyy).

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-08-02 14:48:12 +02:00
Marc Hartmayer
5a848c98bb zipl/src: remove no-pie and noexecstack compiler and linker flags
Only the bootloaders cannot be built as PIE and for that there are
already the -no-pie linker and compiler flags set by the boot/Makefile.
In addition, remove the `noexecstack` linker flag as it has no use
anymore (see commit 518bf7d7357 ("zipl/boot: use
`--no-warn-rwx-segments` linker flag")). It was originally introduced to
declare the ELF segment of the bootloader stack as non-executable. But
this ELF attribute had no effect for multiple reasons:

1. ALL_LDFLAGS is not used for the bootloaders
2. no ELF loader is used for the bootloaders that would take this ELF
   attribute into account

This fixes the problem of overriding  `-fPIE` set by the distributor.

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-08-02 14:48:12 +02:00
Marc Hartmayer
e98f9b9c4a .editorconfig: provide more editor settings
Set the maximum line length for commit messages to 72 characters, and
the indentation style for Makefiles to TAB.

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-08-02 14:48:12 +02:00
Marc Hartmayer
faac2520c9 genprotimg|zipl: build debuginfo files
Currently, the debug information of the bootloader is discarded during
the raw binary creation. Change this by creating separate
<loader>.bin.debug files containing the debug information. The packager
will then be able to package these files as desired and the developer
can use them to debug the code.

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-08-02 14:48:12 +02:00
Marc Hartmayer
3d098416c6 common.mak: add compdb Makefile target
Add a new Makefile target 'compdb' to create the compilation database
'compile_commands.json' [1]. This file can then be used by the language
server 'clangd' [2], which is a possible backend for the so called
'Language Server Protocol' (LSP) [3] used by many IDEs and editors.

In addition, add this file to `.gitignore`.

[1] https://clang.llvm.org/docs/JSONCompilationDatabase.html
[2] https://clangd.llvm.org/
[3] https://microsoft.github.io/language-server-protocol/

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-08-02 14:48:12 +02:00
Peter Oberparleiter
7e53611e3a dump2tar: fix truncated paths
When creating a tar archive, dump2tar incorrectly truncates the last
character of file paths that are exactly 100 characters long.

Paths up to 100 characters can be represented in the 100-byte name field
of a tar header entry, while longer paths are handled via an additional
tar data block. For 100-character paths, dump2tar determines that a
single header is sufficient, but then uses util_strlcpy() to store the
path into the name field. Since util_strlcpy() ensures nul-terminated
strings, the final character of the path is overwritten.

Fix this by using strncpy() instead of util_strlcpy(). Also mark the
affected name fields as "nonstring" to prevent associated compiler
warnings.

Reported-by: Steffen Maier <maier@linux.ibm.com>
Fixes: d85cf20981 ("dump2tar: Change SET_STR_FIELD to copy strings correctly")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Jan Höppner
d8496160cb Makefile: Fix pretty print indentation
Commit 62ec87680a61 ("common.mak: improve cross compilation support")
added one extra whitespace during the changes of the toolchain command
definitions. The rest of the commands did not receive that change.

Since then the pretty print output looks like this:
...
CC       zipl/boot/eckd1b.o
CXX      ziomon/ziorep_collapser.o
SED     zdev/src/lszdev_usage.c
CC       hyptop/sd_cpu_items.o
MV      zfcpdump/zfcpdump-initrd
LINK     dasdfmt/dasdfmt
...

Add the additional whitespace to all other tools definitions used during
the build process.

Note: This doesn't fix the misaligned indentation for commands like
$(CC_FOR_BUILD) as those inherit the whitespace from the original
command, here $(CC).

Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Ingo Franzki
c7f10bc76d zkey: man: Fix groff/troff warnings
Fix the following warnings:

troff: zkey-ekmfweb.1:455: warning: macro 'APP=LINUX'' not defined
troff: zkey-ekmfweb.1:457: warning: macro 'encvol'.' not defined

A single quote (') at the beginning of ta line is interpreted as macro.
Fix this by starting the line with a dummy character (\&).

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Joern Siglen
f8910caa59 dbginfo.sh: include the dbginfo.sh used
include the version of dbginfo.sh just used for data collection into our
tar file - so we have the used version in case of data collection problems.

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>
2023-07-20 21:45:27 +02:00
Jan Höppner
b1d948daef gitignore: Sort list of generated executables
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Marc Hartmayer
4d4ddbd887 Recursive Makefiles: avoid race condition in the install target
The `install` Makefile target of the top Makefile has `all` and
`install-recursive` as prerequisites. This leads to the two recursive
Makefile calls `make -C <SUBDIR> all` and `make -C <SUBDIR> install`.

The problem is these two targets try to build the same object files and
this leads to a race condition between these two targets in case of a
parallel build.

Fix this problem by removing the `all` prerequisite from the `install`
target, as it is not needed since all the `install` targets in the
sub-Makefiles already have proper prerequisites.

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-07-20 21:45:27 +02:00
Mikhail Zaslonko
d205b47c08 zipl/boot: Conditionally clear memory upon reipl at ccw dump end
- Instead of always clearing the memory on reipl after the ccw dump has
  been taken, check for the special OS_INFO_FLAG_REIPL_CLEAR flag in
  os_info flags entry (indicates if sysfs 'clear' attribute has been set
  on the panicked system) and trigger diag308 with a proper subcode.
- Get rid of superfluous ipib_info structure in stage2dump.c.
- Collect ipl_pbt constants in boot/ipl.h header.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
2b7df1fcac include/boot: Rename DIAG308 constants
Add DIAG308_LOAD_NORMAL diag308 subcode (used by FCP/NVMe normal ipl).
Rename other diag308_subcode and diag308_rc constants to be in sync with
kernel naming.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
53eccc0a3d zipl/boot: Update struct os_info with new OS_INFO_FLAGS_ENTRY
Introduce new os_info flags entry pointing to the field with bit flags.
The flag OS_INFO_FLAG_REIPL_CLEAR indicates that 'clear' sysfs attribute
has been set on a panicked system.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
951cf9d7b0 zipl/boot: Reuse os_info_valid() in kdump_os_info_check()
Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
87136bb0d0 zipl/boot: Introduce os_info validation API
- Add os_info_check() function to verify os_info address, magic and
  checksum.
- Add os_info_entry_is_valid() function to check whether requested entry
  is present and valid.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
eb06ebe245 include/boot: Move zipl/boot/error.h to include/boot
- Move zipl/boot/error.h to include/boot
- Adjust include statements in zipb/boot and genprotimg/boot
- Remove error.h from tunedasd/src/tunedasd.c as not needed
- Fix tunedasd/src/Makefile

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
b06ca88cd4 zipl/boot: Make kdump_os_info_check() argument a const.
Make 'struct os_info *os_info' a const to ensure/indicate that no changes
are made to the given structure.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
5af2e30d9a zipl/boot: Move struct os_info to the separate header
- Move struct os_info from kdump.h to the new header os_info.h.
- Place os_info.h to include/boot in order to use it in zgetdump
  code as well.
- Replace hardcoded value of OS_INFO_CSUM_SIZE with a properly
  calculated one.
- Rename os_info_check() of kdump.h to kdump_os_info_check().

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mete Durlu
ea3529e624 hyptop: allow users to set speedup factor
While calculating real CPU SMT utilization, the SMT speedup factor needs
to be taken into account. Speedup factor depends on machine generations
and variations on workload the machine has. The users should be able to
determine the value according to their needs.

Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:22 +02:00
Mete Durlu
0209c11bc1 hyptop: Add real SMT utilization field
By using core utilization, thread utilization, and management
utilization, it is possible to determine how much capacity is left or
how much the real CPU SMT utilization is on lpars. Extending hyptop
with this new field provides useful information.

For more info about real CPU SMT utilization:
https://linux.mainframe.blog/smt_utilization/

Briefly:
ur = real SMT util
uc = core util
ut = thread util
um = *management util
s  = **speedup factor

	ur = ((uc * per_core_thr_count) - ut) / s + (ut - uc) + um

* management utilization:
logical core time spent on hypervisor instead of logical partition.
** speedup factor:
metric used to calculate the SMT utilization on that logical core. This
value varies depending on the workload and the machine generation due
to hardware optimization level.

Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:16 +02:00
Dan Horák
6a24660472 libekmfweb: fix permissions for /usr/include/ekmfweb
Closes: https://github.com/ibm-s390-linux/s390-tools/pull/153
Signed-off-by: Dan Horák <dan@danny.cz>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-13 11:04:13 +02:00
Dan Horák
a3bc87d87d libkmipclient: fix permissions for /usr/include/kmipclient
GitHub-ID: https://github.com/ibm-s390-linux/s390-tools/pull/153
Signed-off-by: Dan Horák <dan@danny.cz>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-13 11:04:13 +02:00
Dan Horák
85493a2581 zdev: silence "maybe uninitialized" warning in lszdev.c
The compiler doesn't fully understand the code block that precedes the
usage of `site` in the condition and thus it thinks it could be
uninitialized. Silence the warning with an explicit initialization.

In function 'get_site_from_pers',
    inlined from 'dev_table_get_value' at lszdev.c:1079:10:
lszdev.c:258:20: warning: 'site' may be used uninitialized [-Wmaybe-uninitialized]
  258 |                 if (site == SITE_FALLBACK)
      |                    ^
lszdev.c: In function 'dev_table_get_value':
lszdev.c:234:13: note: 'site' was declared here
  234 |         int site, i, num = 0;
      |             ^~~~

Closes: https://github.com/ibm-s390-linux/s390-tools/pull/152
Signed-off-by: Dan Horák <dan@danny.cz>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-13 11:04:13 +02:00
Dan Horák
ad544565fe zipl: silence "maybe uninitialized" warning in scan.c
The compiler doesn't fully understand the code block that precedes the
usage of title_off in the condition and thus it thinks it could be
uninitialized. Silence the warning with an explicit initialization.

  CC       zipl/src/scan.o
In function ‘sort_bls_fields’,
    inlined from ‘scan_bls’ at scan.c:1502:8:
scan.c:874:12: warning: ‘title_off’ may be used uninitialized [-Wmaybe-uninitialized]
  874 |         if (title_off == 0)
      |            ^
scan.c: In function ‘scan_bls’:
scan.c:842:16: note: ‘title_off’ was declared here
  842 |         size_t title_off;
      |                ^~~~~~~~~

GitHub-ID: https://github.com/ibm-s390-linux/s390-tools/pull/152
Signed-off-by: Dan Horák <dan@danny.cz>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-13 11:04:13 +02:00
Steffen Eiden
5e135a9daf Prepare for next release
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-07-11 13:15:19 +02:00
247 changed files with 17119 additions and 835 deletions

5
.codespell.ignore Normal file
View File

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

View File

@@ -1,5 +1,4 @@
[codespell]
ignore-words-list = parm,parms
skip = ''
ignore-words = .codespell.ignore
count = ''
quiet-level = 3

View File

@@ -8,6 +8,12 @@ insert_final_newline = true
charset = utf-8
indent_style = tab
tab_width = 8
trim_trailing_whitespace = true
[*.rs]
indent_style = space
indent_size = 4
tab_width = 4
[*.sh]
shell_variant = bash # used by `shfmt`
@@ -19,3 +25,9 @@ indent_size = 2
[*.py]
indent_style = space
indent_size = 4
[{Makefile,*.mak}]
indent_style = tab
[{COMMIT_EDITMSG,EDIT_DESCRIPTION}]
max_line_length = 72

12
.gitignore vendored
View File

@@ -9,6 +9,10 @@
tags
TAGS
# compile_commands.json
# (https://clang.llvm.org/docs/JSONCompilationDatabase.html)
compile_commands.json
# clangd cache (https://clangd.llvm.org/design/indexing#backgroundindex)
.cache/
@@ -19,6 +23,8 @@ TAGS
#
# Ignore generated executables and other generated files
#
**/.detect-openssl.dep.c
*.debug
ap_tools/ap-check
cmsfs-fuse/cmsfs-fuse
cpacfstats/cpacfstats
@@ -26,6 +32,7 @@ cpacfstats/cpacfstatsd
cpumf/chcpumf
cpumf/lscpumf
cpumf/lshwc
cpumf/lspai
cpumf/pai
cpuplugd/cpuplugd
dasdfmt/dasdfmt
@@ -50,8 +57,8 @@ iucvterm/src/iucvconn
iucvterm/src/iucvtty
iucvterm/src/ttyrun
iucvterm/test/test_afiucv
libap/check-dep-lock
libap/check-dep-json
libap/check-dep-lock
libekmfweb/check-dep-libekmfweb
libekmfweb/detect-openssl-version.dep
libekmfweb/libekmfweb.so
@@ -96,6 +103,7 @@ zdev/src/chzdev
zdev/src/chzdev_usage.c
zdev/src/lszdev
zdev/src/lszdev_usage.c
zdev/src/zdev_id
zdsfs/zdsfs
zdump/.check_dep_fuse
zdump/.check_dep_zgetdump
@@ -115,7 +123,6 @@ zipl/boot/*.exec
zipl/boot/.loaders
zipl/boot/data.h
zipl/src/chreipl_helper.device-mapper
zdev/src/zdev_id
zipl/src/zipl
zipl/src/zipl-editenv
zipl/src/zipl_helper.device-mapper
@@ -129,4 +136,3 @@ zkey/kmip/zkey-kmip.so
zkey/zkey
zkey/zkey-cryptsetup
zpcictl/zpcictl
**/.detect-openssl.dep.c

12
.rustfmt.toml Normal file
View File

@@ -0,0 +1,12 @@
edition = "2021"
newline_style = "Unix"
# Unstable options that help catching some mistakes in formatting and that we may want to enable
# when they become stable.
#
# They are kept here since they are useful to run from time to time.
#format_code_in_doc_comments = true
#reorder_impl_items = true
#comment_width = 100
#wrap_comments = true
#normalize_comments = true

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
@@ -56,6 +57,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 +126,7 @@ List of all individuals having contributed content to s390-tools
- Thomas Richter
- Thomas Spatzier
- Thomas Weber
- Thorsten Winkler
- Tuan Hoang
- Ursula Braun
- Utz Bacher

View File

@@ -1,6 +1,59 @@
Release history for s390-tools (MIT version)
--------------------------------------------
* __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
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
General:
- s390-tools now supports tools written in Rust.
- Add `compdb` Makefile target to create 'compile_commands.json' to LSP
backends in IDEs and editors
Add new tools / libraries:
- rust/pv: Library for pv tools written in rust
- rust/pvsecret: Tool to manage UV-secrets
Changes of existing tools:
- dbginfo.sh: Global IFS variable
- genprotimg: Add support for add-secret requests
- genprotimg: Build debuginfo files for bootloader
- hyptop: Add real SMT utilization field
- hyptop: Allow users to set speedup factor
- pvattest: Add yaml-output for verify command
- zipl: Build debuginfo files for bootloader
Bug Fixes:
- dump2tar: Fix truncated paths
- zdev/dracut: fix kdump build to integrate with site support
* __v2.28.0 (2023-07-11)__
For Linux kernel version: 6.4

View File

@@ -15,11 +15,12 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools pvattest
genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools pvattest \
rust
else
BASELIB_DIRS =
LIB_DIRS = libpv
TOOL_DIRS = genprotimg pvattest
TOOL_DIRS = genprotimg pvattest rust
endif
SUB_DIRS = $(BASELIB_DIRS) $(LIB_DIRS) $(TOOL_DIRS)

View File

@@ -15,6 +15,11 @@ The package also contains the following files:
Package contents
----------------
* rust:
all s390-tools that are written in rust and require external crates.
Disable the compilation of all tools in `rust/` using HAVE_CARGO=0
See the `rust/README.md` for Details
* dasdfmt:
Low-level format ECKD DASDs with the classical Linux disk layout or the new
z/OS compatible disk layout.
@@ -305,13 +310,14 @@ build options:
| net-snmp | `HAVE_SNMP` | osasnmpd |
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
| openssl | `HAVE_OPENSSL` | genprotimg, zkey, libekmfweb, |
| | | libkmipclient, pvattest, zgetdump |
| | | libkmipclient, pvattest, zgetdump, |
| | | rust/pvsecret, |
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
| | | libkmipclient |
| glib2 | `HAVE_GLIB2` | genprotimg, pvattest, zgetdump |
| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient,|
| | | pvattest |
| | | pvattest, rust/pvsecret, |
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
| systemd | `HAVE_SYSTEMD` | hsavmcore |
| libudev | `HAVE_LIBUDEV` | cpacfstatsd |
@@ -324,6 +330,7 @@ This table lists additional build or install options:
| | | zipl |
| initramfs-tools | `HAVE_INITRAMFS` | zdev, zipl |
| | `ZDEV_ALWAYS_UPDATE_INITRD` | zdev |
| rust | `HAVE_CARGO` | rust/* |
The s390-tools build process uses "pkg-config" and therefore it must be
available.
@@ -334,6 +341,14 @@ Build and runtime requirements for specific tools
In the following more details on the build an runtime requirements of
the different tools are provided:
* rust/pvsecret:
For building pvsecret you need OpenSSL version 1.1.1 or newer
installed (openssl-devel.rpm). Also required is cargo and libcurl.
Tip: you may skip the pvsecret build by adding
`HAVE_OPENSSL=0`, `HAVE_LIBCURL=0`, or `HAVE_CARGO=0`.
The runtime requirements are: openssl-libs (>= 1.1.1).
* dbginfo.sh:
The tar package is required to archive collected data.
@@ -371,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

@@ -17,7 +17,6 @@
# GNU awk:
# - gawk
override SHELL := /bin/bash
override .SHELLFLAGS := -O globstar -O nullglob -O extglob -c
# Include common s390-tools definitions

View File

@@ -8,10 +8,14 @@ ASAN ?= 0
ENABLE_WERROR ?= 0
OPT_FLAGS ?=
MAKECMDGOALS ?=
CARGO ?= cargo
CARGOFLAGS ?=
ifeq ($(COMMON_INCLUDED),false)
COMMON_INCLUDED := true
override SHELL := /bin/bash
# 'BUILD_ARCH' is the architecture of the machine where the build takes place
BUILD_ARCH := $(shell uname -m | sed -e 's/i.86/i386/' -e 's/sun4u/sparc64/' -e 's/arm.*/arm/' -e 's/sa110/arm/')
# 'HOST_ARCH' is the architecture of the machine that will run the compiled output
@@ -28,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 = 28
RELEASE = 30
PATCHLEVEL = 0
DISTRELEASE = build-$(shell date +%Y%m%d)
S390_TOOLS_RELEASE = $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
@@ -89,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
@@ -115,15 +119,19 @@ 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))
$(eval $(call cmd_define, CAT," CAT ",cat))
$(eval $(call cmd_define, SED," SED ",sed))
$(eval $(call cmd_define, GZIP," GZIP ",gzip))
$(eval $(call cmd_define, MV," MV ",mv))
$(eval $(call cmd_define, PERLC," PERLC ",perl -c))
$(eval $(call cmd_define, RUNTEST," RUNTEST ",$(S390_TEST_LIB_PATH)/s390_runtest))
$(eval $(call cmd_define, CAT," CAT ",cat))
$(eval $(call cmd_define, SED," SED ",sed))
$(eval $(call cmd_define, GZIP," GZIP ",gzip))
$(eval $(call cmd_define, MV," MV ",mv))
$(eval $(call cmd_define, PERLC," PERLC ",perl -c))
$(eval $(call cmd_define,CARGO_BUILD," CARGO BUILD ",$(CARGO) build))
$(eval $(call cmd_define,CARGO_TEST, " CARGO TEST ",$(CARGO) test))
$(eval $(call cmd_define,CARGO_CLEAN," CARGO CLEAN ",$(CARGO) clean))
CHECK = sparse
CHECK_SILENT := $(CHECK)
@@ -133,8 +141,10 @@ SKIP = echo " SKIP $(call reldir) due to"
INSTALL = install
CP = cp
ALL_CARGOFLAGS := $(CARGOFLAGS)
ifneq ("${V}","1")
MAKEFLAGS += --quiet
ALL_CARGOFLAGS += --quiet
echocmd=echo $1$(call reldir)$2;
RUNTEST += > /dev/null 2>&1
else
@@ -360,6 +370,7 @@ help:
@echo ' all Build all tools (default target)'
@echo ' install Install tools'
@echo ' clean Delete all generated files'
@echo ' compdb Generate compile_commands.json for clangd'
@echo ''
@echo 'OPTIONS'
@echo ' D=1 Build with debugging option "-Og"'
@@ -375,6 +386,32 @@ help:
@echo ' # make C=1 CHECK=smatch'
.PHONY: help
# 'compile_commands.json' generation
#
# Create the compilation database 'compile_commands.json'. See
# https://clang.llvm.org/docs/JSONCompilationDatabase.html for details.
#
.PHONY: compdb
compdb:
$(MAKE) clean
ifneq ($(shell command -v compiledb),)
compiledb $(MAKE)
else ifneq ($(shell command -v bear),)
ifeq ($(shell bear --help|grep -- '-- ...'),)
bear $(MAKE)
else
bear -- $(MAKE)
endif
else
$(error Please install either 'compiledb' or 'bear')
endif
# Prints the s390-tools release string
version:
$(info $(S390_TOOLS_RELEASE))
.PHONY: version
# Automatic dependency generation
#
# Create ".o.d" dependency files with the -MM compile option for all ".c" and
@@ -485,7 +522,7 @@ install_echo:
install: install_echo install_dirs
clean_echo:
$(call echocmd," CLEAN ")
$(call echocmd," CLEAN ")
clean_gcov:
rm -f -- *.gcda *.gcno *.gcov
clean_dep:

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

@@ -944,6 +944,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 +1012,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 +1079,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;

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

@@ -23,9 +23,16 @@
#define BLOCKSIZE 512
#if __has_attribute(nonstring)
# define __nonstring __attribute__ ((nonstring))
#else
# define __nonstring
#endif
/* Basic TAR header */
struct tar_header {
char name[100];
char name[100] __nonstring;
char mode[8];
char uid[8];
char gid[8];
@@ -33,7 +40,7 @@ struct tar_header {
char mtime[12];
char chksum[8];
char typeflag;
char linkname[100];
char linkname[100] __nonstring;
char magic[6];
char version[2];
char uname[32];
@@ -78,7 +85,7 @@ static void set_time(char *dest, size_t len, time_t value)
#define SET_TIME_FIELD(obj, name, value) \
set_time((obj)->name, sizeof((obj)->name), (time_t) (value))
#define SET_STR_FIELD(obj, name, value) \
util_strlcpy((obj)->name, (value), sizeof((obj)->name))
strncpy((obj)->name, (value), sizeof((obj)->name))
/* Initialize the tar file @header with the provided data */
static void init_header(struct tar_header *header, const char *filename,

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

@@ -10,7 +10,7 @@ RECURSIVE_TARGETS := all-recursive install-recursive clean-recursive
all: all-recursive
install: all install-recursive
install: install-recursive
$(INSTALL) -d -m 755 "$(PKGDATADIR)"
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 samples/check_hostkeydoc "$(PKGDATADIR)"

View File

@@ -2,6 +2,7 @@
include ../../common.mak
FILES := stage3a.bin stage3b.bin stage3b_reloc.bin
DEBUG_FILES := $(addsuffix .debug,$(FILES))
ifeq ($(HOST_ARCH),s390x)
ZIPL_DIR := $(rootdir)/zipl
@@ -71,10 +72,15 @@ stage3b_reloc.o: stage3b.bin
stage3a.elf: head.o stage3a_init.o $(ZIPL_OBJS)
stage3b.elf: head.o $(ZIPL_OBJS)
.SECONDARY: $(FILES:.bin=.lds)
%.elf: %.lds %.o
$(LINK) $(NO_PIE_LDFLAGS) $(NO_WARN_RWX_SEGMENTS_LDFLAGS) -Wl,-T,$< -Wl,--build-id=none -m64 -static -nostdlib $(filter %.o, $^) -o $@
@chmod a-x $@
%.bin.debug: %.elf
$(OBJCOPY) --only-keep-debug $< $@
@chmod a-x $@
%.bin: %.elf
$(OBJCOPY) -O binary $< $@
@chmod a-x $@
@@ -89,7 +95,7 @@ else
# `-include $(dependencies_c)` statement).
.PHONY: $(dependencies_c)
$(FILES):
$(FILES) $(DEBUG_FILES):
echo " SKIP $@ due to HOST_ARCH != s390x"
install:
@@ -97,9 +103,9 @@ install:
endif
.DEFAULT_GOAL := all
all: $(FILES)
all: $(FILES) $(DEBUG_FILES)
clean:
rm -f *.o *.elf *.bin *.map .*.d *.lds
rm -f -- *.o *.elf *.bin *.map .*.d *.lds *.debug
.PHONY: all clean

View File

@@ -11,9 +11,10 @@
#include "stage3a.h"
#include "lib/zt_common.h"
#include "boot/error.h"
#include "boot/s390.h"
#include "boot/ipl.h"
#include "sclp.h"
#include "error.h"
static volatile struct stage3a_args __section(".loader_parms") loader_parms;

View File

@@ -12,10 +12,11 @@
#include "lib/zt_common.h"
#include "boot/psw.h"
#include "boot/error.h"
#include "boot/s390.h"
#include "boot/linux_layout.h"
#include "boot/loaders_layout.h"
#include "sclp.h"
#include "error.h"
static volatile struct stage3b_args __section(".loader_parms") loader_parms;
@@ -60,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

@@ -97,18 +97,29 @@ Do not use for a production image unless you verified
the host-key document before. Optional.
.TP
\fB\-\-comm\-key\fR=\fI\,FILE\/\fR
Specifies the encryption key you want to use for the PV guest dump. Use a
secure, random, plaintext AES-256 GCM key. Optional.
Specifies the customer communication key (CCK). This key is used for the
PV guest dump encryption and to derive the CCK-derived extension secret
used for add-secret requests. Use a secure, random, plaintext AES-256
GCM key. Optional.
.TP
\fB\-\-enable\-dump\fR
Enable PV guest dumps. Requires the \fB\-\-comm-key\fR option. Optional.
Enable PV guest dumps. Requires the \fB\-\-comm\-key\fR option. Optional.
.TP
\fB\-\-disable\-dump\fR
Disable PV guest dumps. This is the default. Optional.
Disable PV guest dumps. This is the default.
.TP
\fB\-\-enable\-cck\-extension\-secret\fR
Add-secret requests must provide an extension secret that matches the
CCK-derived extension secret. Requires the \fB\-\-comm\-key\fR option.
Optional.
.TP
\fB\-\-disable\-cck\-extension\-secret\fR
Add-secret requests don't have to provide an extension secret. This is
the default.
.TP
\fB\-\-enable\-pckmo\fR
Enable the support for the DEA, TDEA, AES, and ECC PCKMO key encryption
functions. This is the default. Optional.
functions. This is the default.
.TP
\fB\-\-disable\-pckmo\fR
Disable the support for the DEA, TDEA, AES, and ECC PCKMO key encryption

View File

@@ -34,6 +34,10 @@
#define PV_PCF_PCKMO_AES __PV_BIT(57) /* PCKMO encrypt-AES-key functions allowed */
#define PV_PCF_PCKM_ECC __PV_BIT(58) /* PCKMO encrypt-ECC-key functions allowed */
/* Secret control flags */
#define PV_SCF_CCK_EXTENSION_SECRET_ENFORCMENT \
__PV_BIT(1) /* All add-secret requests must provide an extension secret */
/* maxima for the PV version 1 */
#define PV_V1_IPIB_MAX_SIZE PAGE_SIZE
#define PV_V1_PV_HDR_MAX_SIZE (2 * PAGE_SIZE)

View File

@@ -62,11 +62,12 @@ static gint pv_args_set_defaults(PvArgs *args, GError **err G_GNUC_UNUSED)
static gint pv_args_validate_options(PvArgs *args, GError **err)
{
const PvControlFlagsArgs *cf_args = &args->cf_args;
PvComponentType KERNEL = PV_COMP_TYPE_KERNEL;
/* Check for mutually exclusive arguments */
if (args->pcf && !(args->allow_pckmo == PV_NOT_SET &&
args->allow_dump == PV_NOT_SET)) {
if (cf_args->pcf &&
!(cf_args->enable_pckmo == PV_NOT_SET && cf_args->enable_dump == PV_NOT_SET)) {
g_set_error(
err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
_("The '--x-pcf' option cannot be used with the '--(enable|disable)-pckmo' or"
@@ -74,6 +75,13 @@ static gint pv_args_validate_options(PvArgs *args, GError **err)
return -1;
}
if (cf_args->scf && !(cf_args->enable_cck_extension_secret_enforcement == PV_NOT_SET)) {
g_set_error(
err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
_("The '--x-scf' option cannot be used with the '--(enable|disable)-extension-secret-required' flags.\nUse 'genprotimg --help' for more information"));
return -1;
}
/* Check for unused arguments */
if (args->unused_values->len > 0) {
g_autofree gchar *unused = NULL;
@@ -93,12 +101,20 @@ static gint pv_args_validate_options(PvArgs *args, GError **err)
}
/* Check for mandatory arguments */
if (args->allow_dump == PV_TRUE && !args->cust_comm_key_path) {
if (cf_args->enable_dump == PV_TRUE && !args->cust_comm_key_path) {
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
_("Option '--allow-dump' requires the '--comm-key' option.\nUse 'genprotimg "
_("Option '--enable-dump' requires the '--comm-key' option.\nUse 'genprotimg "
"--help' for more information"));
return -1;
}
if (cf_args->enable_cck_extension_secret_enforcement == PV_TRUE &&
!args->cust_comm_key_path) {
g_set_error(
err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
_("Option '--enable-cck-extension-secret' requires the '--comm-key' option.\nUse 'genprotimg "
"--help' for more information"));
return -1;
}
if (!args->output_path) {
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
@@ -178,11 +194,11 @@ static gboolean cb_set_string_option(const gchar *option, const gchar *value,
if (g_str_equal(option, "--x-header-key"))
args_option = &args->cust_root_key_path;
if (g_str_equal(option, "--x-pcf"))
args_option = &args->pcf;
args_option = &args->cf_args.pcf;
if (g_str_equal(option, "--x-psw"))
args_option = &args->psw_addr;
if (g_str_equal(option, "--x-scf"))
args_option = &args->scf;
args_option = &args->cf_args.scf;
if (!args_option) {
g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
@@ -217,49 +233,48 @@ static gboolean cb_remaining_values(const gchar *option G_GNUC_UNUSED,
}
#define MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, VALUE) (cb_##FLAG##_##VALUE)
#define DEFINE_MUT_EXCL_BOOL_FLAG_CB(FLAG, VALUE) \
static gboolean MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, VALUE)( \
const gchar *option G_GNUC_UNUSED, const gchar *value G_GNUC_UNUSED, \
PvArgs *args, GError **err) \
{ \
if (!(args->allow_##FLAG == PV_NOT_SET || \
args->allow_##FLAG == VALUE)) { \
g_set_error(err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, \
"'--enable-" #FLAG "' and '--disable-" #FLAG \
"' are mutually exclusive"); \
return FALSE; \
} \
args->allow_##FLAG = VALUE; \
return TRUE; \
#define DEFINE_MUT_EXCL_BOOL_FLAG_CB(FLAG, VALUE) \
static gboolean MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, VALUE)(const gchar *option G_GNUC_UNUSED, \
const gchar *value G_GNUC_UNUSED, \
PvArgs *args, GError **err) \
{ \
if (!(args->cf_args.enable_##FLAG == PV_NOT_SET || \
args->cf_args.enable_##FLAG == VALUE)) { \
g_set_error(err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, \
"'--enable-" #FLAG "' and '--disable-" #FLAG \
"' are mutually exclusive"); \
return FALSE; \
} \
args->cf_args.enable_##FLAG = VALUE; \
return TRUE; \
}
#define DEFINE_MUT_EXCL_BOOL_FLAG_CBS(FLAG) \
DEFINE_MUT_EXCL_BOOL_FLAG_CB(FLAG, PV_TRUE) \
DEFINE_MUT_EXCL_BOOL_FLAG_CB(FLAG, PV_FALSE)
#define MUT_EXCL_BOOL_FLAG(FLAG, ENABLE_DESC, DISABLE_DESC) \
{ \
.long_name = "enable-" #FLAG, \
.short_name = 0, \
.flags = G_OPTION_FLAG_NO_ARG, \
.arg = G_OPTION_ARG_CALLBACK, \
.arg_data = MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, PV_TRUE), \
.description = ENABLE_DESC, \
}, \
{ \
.long_name = "disable-" #FLAG, \
.short_name = 0, \
.flags = G_OPTION_FLAG_NO_ARG, \
.arg = G_OPTION_ARG_CALLBACK, \
.arg_data = MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, PV_FALSE), \
.description = DISABLE_DESC, \
#define MUT_EXCL_BOOL_FLAG(NAME, FLAG, ENABLE_DESC, DISABLE_DESC) \
{ \
.long_name = "enable-" #NAME, \
.short_name = 0, \
.flags = G_OPTION_FLAG_NO_ARG, \
.arg = G_OPTION_ARG_CALLBACK, \
.arg_data = MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, PV_TRUE), \
.description = ENABLE_DESC, \
}, \
{ \
.long_name = "disable-" #NAME, .short_name = 0, .flags = G_OPTION_FLAG_NO_ARG, \
.arg = G_OPTION_ARG_CALLBACK, \
.arg_data = MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, PV_FALSE), \
.description = DISABLE_DESC, \
}
#define INDENT " "
#define INDENT " "
/* Define the callbacks for mutually exclusive command line flags */
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(dump)
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(pckmo)
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(dump);
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(pckmo);
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(cck_extension_secret_enforcement);
gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
GError **err)
@@ -280,7 +295,7 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
.arg_data = &args->host_keys,
.description =
_("FILE specifies a host-key document. At least\n" INDENT
"one is required Specify this option multiple times\n" INDENT
"one is required. Specify this option multiple times\n" INDENT
"to enable the image to run on more than one host."),
.arg_description = _("FILE") },
{ .long_name = "cert",
@@ -325,27 +340,31 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
.description = _("Use the kernel parameters stored in PARMFILE\n" INDENT
"(optional)."),
.arg_description = _("PARMFILE") },
MUT_EXCL_BOOL_FLAG(dump, dump,
_("Enable PV guest dumps (optional). This option\n" INDENT
"requires the '--comm-key' option."),
_("Disable PV guest dumps (default).")),
MUT_EXCL_BOOL_FLAG(
dump,
_("Enable PV guest dumps (optional). This option\n" INDENT
"requires the '--comm-key' option."),
_("Disable PV guest dumps (default) (optional).")),
MUT_EXCL_BOOL_FLAG(
pckmo,
_("Enable the support for the DEA, TDEA, AES, and\n" INDENT
"ECC PCKMO key encryption functions (default)\n" INDENT
"(optional)."),
_("Disable the support for the DEA, TDEA, AES, and\n" INDENT
"ECC PCKMO key encryption functions (optional).")),
cck-extension-secret, cck_extension_secret_enforcement,
_("Add-secret requests must provide an extension\n" INDENT
"secret that matches the CCK-derived extension\n" INDENT
"secret (optional). This option requires the\n" INDENT
"'--comm-key' option."),
_("Add-secret requests don't have to provide\n" INDENT
"the CCK-derived extension secret (default).")),
MUT_EXCL_BOOL_FLAG(pckmo, pckmo,
_("Enable the support for the DEA, TDEA, AES, and\n" INDENT
"ECC PCKMO key encryption functions (default)."),
_("Disable the support for the DEA, TDEA, AES, and\n" INDENT
"ECC PCKMO key encryption functions (optional).")),
{ .long_name = "comm-key",
.short_name = 0,
.flags = G_OPTION_FLAG_FILENAME,
.arg = G_OPTION_ARG_CALLBACK,
.arg_data = cb_set_string_option,
.description = _(
"FILE contains the key with which you encrypt\n" INDENT
"the PV guest dump (optional). Required by\n" INDENT
"the '--enable-dump' option."),
"FILE contains the customer communication key\n" INDENT
"(CCK) (optional)."),
.arg_description = _("FILE") },
{ .long_name = "crl",
.short_name = 0,
@@ -450,6 +469,8 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
.arg_data = cb_set_string_option,
.description = _("Specify the secret control flags\n" INDENT
"as a hexadecimal value.\n" INDENT
"Optional; mutually exclusive with\n" INDENT
"'--(enable|disable)-cck-extension-secret';\n" INDENT
"Optional; default: '0x0'."),
.arg_description = _("VALUE") },
{ 0 },
@@ -487,8 +508,10 @@ PvArgs *pv_args_new(void)
g_autoptr(PvArgs) args = g_new0(PvArgs, 1);
args->unused_values = g_ptr_array_new_with_free_func(g_free);
args->allow_dump = PV_NOT_SET;
args->allow_pckmo = PV_NOT_SET;
/* `args->cf_args` is implicitly initialized with zeros since
* `g_new0` is used. So there is no reason to explicitly
* initialize the values as PV_NOT_SET == 0.
*/
return g_steal_pointer(&args);
}
@@ -497,8 +520,8 @@ void pv_args_free(PvArgs *args)
if (!args)
return;
g_free(args->pcf);
g_free(args->scf);
g_free(args->cf_args.pcf);
g_free(args->cf_args.scf);
g_free(args->psw_addr);
g_free(args->cust_root_key_path);
g_free(args->cust_comm_key_path);

View File

@@ -23,19 +23,27 @@ PvArg *pv_arg_new(PvComponentType type, const gchar *path);
void pv_arg_free(PvArg *arg);
typedef enum pv_tristate {
PV_NOT_SET = 0,
PV_TRUE,
PV_FALSE,
PV_NOT_SET = 0,
PV_TRUE,
PV_FALSE,
} PvTristate;
/* The value of PV_NOT_SET is not allowed to be changed */
STATIC_ASSERT(PV_NOT_SET == 0)
typedef struct {
gchar *pcf;
gchar *scf;
/* Add-secret requests do require CCK-extension secrets */
PvTristate enable_cck_extension_secret_enforcement;
PvTristate enable_dump;
PvTristate enable_pckmo;
} PvControlFlagsArgs;
typedef struct {
gint log_level;
gint no_verify;
gboolean offline;
gchar *pcf;
gchar *scf;
PvTristate allow_dump;
PvTristate allow_pckmo;
PvControlFlagsArgs cf_args;
gchar *psw_addr; /* PSW address which will be used for the start of
* the actual component (e.g. Linux kernel)
*/

View File

@@ -228,37 +228,40 @@ static gint pv_img_set_psw_addr(PvImage *img, const gchar *psw_addr_s,
return 0;
}
static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s,
const gchar *scf_s,
PvTristate allow_dump,
PvTristate allow_pckmo, GError **err)
static void pv_img_set_control_flag(uint64_t *flags, const PvTristate option, const uint64_t flag)
{
if (option == PV_TRUE)
*flags |= flag;
else if (option == PV_FALSE)
*flags &= ~flag;
}
static gint pv_img_set_control_flags(PvImage *img, const PvControlFlagsArgs *cf_args, GError **err)
{
uint64_t flags;
if (pcf_s) {
if (hex_str_toull(pcf_s, &flags, err) < 0)
/* Set plain control flags */
if (cf_args->pcf) {
if (hex_str_toull(cf_args->pcf, &flags, err) < 0)
return -1;
img->pcf = flags;
}
if (scf_s) {
if (hex_str_toull(scf_s, &flags, err) < 0)
pv_img_set_control_flag(&img->pcf, cf_args->enable_dump, PV_PCF_ALLOW_DUMPING);
pv_img_set_control_flag(&img->pcf, cf_args->enable_pckmo,
PV_PCF_PCKM_ECC | PV_PCF_PCKMO_AES | PV_PCF_PCKMO_DEA_TDEA);
/* Set secret control flags */
if (cf_args->scf) {
if (hex_str_toull(cf_args->scf, &flags, err) < 0)
return -1;
img->scf = flags;
}
if (allow_dump == PV_TRUE)
img->pcf |= PV_PCF_ALLOW_DUMPING;
else if (allow_dump == PV_FALSE)
img->pcf &= ~PV_PCF_ALLOW_DUMPING;
if (allow_pckmo == PV_TRUE)
img->pcf |= PV_PCF_PCKM_ECC | PV_PCF_PCKMO_AES | PV_PCF_PCKMO_DEA_TDEA;
else if (allow_pckmo == PV_FALSE)
img->pcf &= ~(PV_PCF_PCKM_ECC | PV_PCF_PCKMO_AES | PV_PCF_PCKMO_DEA_TDEA);
pv_img_set_control_flag(&img->scf, cf_args->enable_cck_extension_secret_enforcement,
PV_SCF_CCK_EXTENSION_SECRET_ENFORCMENT);
return 0;
}
@@ -610,9 +613,7 @@ PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err)
return NULL;
/* set the control flags: PCF and SCF */
if (pv_img_set_control_flags(ret, args->pcf, args->scf,
args->allow_dump, args->allow_pckmo,
err) < 0)
if (pv_img_set_control_flags(ret, &args->cf_args, err) < 0)
return NULL;
/* read in the keys */
@@ -683,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

@@ -59,7 +59,7 @@ static gint pv_ipib_init(IplParameterBlock *ipib, GSList *comps,
ipib_size = MAX(ipl_pl_hdr_size + blk0_len, (uint32_t)PAGE_SIZE);
g_assert(pv_ipib_get_size(comps_length) == ipib_size);
pv->pbt = IPL_TYPE_PV;
pv->pbt = IPL_PBT_PV;
pv->len = GUINT32_TO_BE(blk0_len);
pv->num_comp = GUINT32_TO_BE(comps_length);
/* both values will be overwritten during the IPL process by

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

@@ -116,6 +116,7 @@ static void l_sd_cpu_fill(struct sd_cpu *cpu, struct l_x_cpu_info *cpu_info,
int threads)
{
sd_cpu_cpu_time_us_set(cpu, cpu_info->lp_time);
sd_cpu_threads_per_core_set(cpu, threads);
if (threads > 1)
sd_cpu_thread_time_us_set(cpu,
cpu_info->lp_time * threads - cpu_info->mt_idle_time);
@@ -297,6 +298,7 @@ static struct sd_sys_item *l_sys_item_vec[] = {
&sd_sys_item_thread_cnt,
&sd_sys_item_core_diff,
&sd_sys_item_thread_diff,
&sd_sys_item_smt_diff,
&sd_sys_item_mgm_diff,
&sd_sys_item_core,
&sd_sys_item_thread,
@@ -326,6 +328,7 @@ static struct sd_cpu_item *l_cpu_item_vec[] = {
&sd_cpu_item_type,
&sd_cpu_item_core_diff,
&sd_cpu_item_thread_diff,
&sd_cpu_item_smt_diff,
&sd_cpu_item_mgm_diff,
&sd_cpu_item_core,
&sd_cpu_item_thread,

View File

@@ -391,3 +391,24 @@ void hyptop_helper_init(void)
if (l_iconv_ebcdic_ascii == (iconv_t) -1)
ERR_EXIT("Could not initialize iconv\n");
}
/*
* Calculate real SMT utilization
* @core_us: core utilization in us
* @thr_us: thread utilization in us
* @mgm_us: management utilization in us
* @thread_per_core: SMT thread count per core
*/
s64 ht_calculate_smt_util(u64 core_us, u64 thr_us, u64 mgm_us, int thread_per_core)
{
s64 component1, component2, smt_us;
double smt_factor = g.o.smt_factor;
component1 = thread_per_core * core_us - thr_us;
if (thread_per_core > 1)
component1 /= smt_factor;
component2 = thr_us - core_us;
smt_us = G0(component1 + component2 + mgm_us);
return smt_us;
}

View File

@@ -34,6 +34,7 @@ extern void ht_ebcdic_to_ascii(char *in, char *out, size_t len);
extern char *ht_mount_point_get(const char *fs_type);
extern u64 ht_ext_tod_2_us(void *tod_ext);
extern void ht_print_time(void);
extern s64 ht_calculate_smt_util(u64 core_us, u64 thr_us, u64 mgm_us, int thread_per_core);
/*
* Memory alloc functions

View File

@@ -74,6 +74,18 @@ In this mode no user input is accepted.
.BR "\-d <SECONDS>" " or " "\-\-delay=<SECONDS>"
Specifies the delay between screen updates.
.TP
.BR "\-m <FACTOR>" " or " "\-\-smt_factor=<FACTOR>"
Specifies a workload dependent SMT speedup factor.
For IBM z15 servers, the default value is 1.3. If the workload benefits
from SMT, you can specify a higher value. If the workload does not benefit
from SMT, specifying lower values results in more accurate reports of
real CPU SMT utilization field for LPARs. There is no hard boundary except
that it must be a positive value. Example ranges to select a sensible value
from:
For IBM z13: [0.8, 1.3]
For IBM z15: [1.1, 1.5]
.TP
.BR "\-n <ITERATIONS>" " or " "\-\-iterations=<ITERATIONS>"
Specifies the maximum number of iterations before ending.
@@ -119,6 +131,7 @@ The following fields are available under LPAR:
In "sys_list" and "sys" window:
'c' - Core dispatch time per second
'e' - Thread time per second
'S' - Real CPU SMT utilization
'm' - Management time per second
'C' - Total core dispatch time
'E' - Total thread time

View File

@@ -22,6 +22,7 @@
#include "table.h"
#define HYPTOP_OPT_DEFAULT_DELAY 2
#define HYPTOP_OPT_DEFAULT_SMT_SCALE 1.3
#define HYPTOP_MAX_WIN_DEPTH 4
#define HYPTOP_MAX_LINE 512
#define PROG_NAME "hyptop"
@@ -60,6 +61,8 @@ struct hyptop_opts {
int delay_s;
int delay_us;
double smt_factor;
};
/*

View File

@@ -39,6 +39,7 @@ static char HELP_TEXT[] =
"-t, --cpu_types TYPE[,..] CPU types used for time calculations\n"
"-b, --batch_mode Use batch mode (no curses)\n"
"-d, --delay SECONDS Delay time between screen updates\n"
"-m, --smt_factor FACTOR Machine generation dependent SMT speedup factor.\n"
"-n, --iterations NUMBER Number of iterations before ending\n";
/*
@@ -48,6 +49,7 @@ static void l_init_defaults(void)
{
g.prog_name = PROG_NAME;
g.o.delay_s = HYPTOP_OPT_DEFAULT_DELAY;
g.o.smt_factor = HYPTOP_OPT_DEFAULT_SMT_SCALE;
g.w.cur = &win_sys_list;
g.o.cur_win = &win_sys_list;
}
@@ -108,6 +110,20 @@ static void l_delay_set(char *delay_string)
g.o.delay_us = 0;
}
/*
* Set SMT factor option
*/
static void l_factor_set(char *value_string)
{
double factor;
if (sscanf(value_string, "%lf", &factor) != 1)
ERR_EXIT("The SMT factor \"%s\" is invalid\n", value_string);
if (factor <= 0)
ERR_EXIT("The SMT factor \"%s\" is <= 0\n", value_string);
g.o.smt_factor = factor;
}
/*
* Get number of occurrences of character 'c' in "str"
*/
@@ -299,6 +315,7 @@ void opts_parse(int argc, char *argv[])
{ "help", no_argument, NULL, 'h'},
{ "batch_mode", no_argument, NULL, 'b'},
{ "delay", required_argument, NULL, 'd'},
{ "smt_factor", required_argument, NULL, 'm'},
{ "window", required_argument, NULL, 'w'},
{ "sys", required_argument, NULL, 's'},
{ "iterations", required_argument, NULL, 'n'},
@@ -307,7 +324,7 @@ void opts_parse(int argc, char *argv[])
{ "cpu_types", required_argument, NULL, 't'},
{ NULL, 0, NULL, 0 }
};
static const char option_string[] = "vhbd:w:s:n:f:t:S:";
static const char option_string[] = "vhbd:m:w:s:n:f:t:S:";
l_init_defaults();
while (1) {
@@ -328,6 +345,9 @@ void opts_parse(int argc, char *argv[])
case 'd':
l_delay_set(optarg);
break;
case 'm':
l_factor_set(optarg);
break;
case 'w':
l_window_set(optarg);
break;

View File

@@ -200,6 +200,7 @@ struct sd_cpu {
struct sd_cpu_info *d_cur;
struct sd_cpu_info *d_prev;
u16 cnt;
int threads_per_core;
enum sd_cpu_state state;
};
@@ -232,6 +233,11 @@ static inline void sd_cpu_cpu_time_us_set(struct sd_cpu *cpu, u64 value)
cpu->d_cur->cpu_time_us = value;
}
static inline void sd_cpu_threads_per_core_set(struct sd_cpu *cpu, int value)
{
cpu->threads_per_core = value;
}
static inline void sd_cpu_thread_time_us_set(struct sd_cpu *cpu, u64 value)
{
cpu->d_cur->thread_time_us = value;
@@ -335,6 +341,7 @@ extern struct sd_cpu_item sd_cpu_item_state;
extern struct sd_cpu_item sd_cpu_item_cpu_diff;
extern struct sd_cpu_item sd_cpu_item_core_diff;
extern struct sd_cpu_item sd_cpu_item_thread_diff;
extern struct sd_cpu_item sd_cpu_item_smt_diff;
extern struct sd_cpu_item sd_cpu_item_mgm_diff;
extern struct sd_cpu_item sd_cpu_item_wait_diff;
extern struct sd_cpu_item sd_cpu_item_steal_diff;
@@ -398,6 +405,7 @@ static inline char *sd_sys_item_str(struct sd_sys *sys,
extern struct sd_sys_item sd_sys_item_cpu_cnt;
extern struct sd_sys_item sd_sys_item_core_cnt;
extern struct sd_sys_item sd_sys_item_thread_cnt;
extern struct sd_sys_item sd_sys_item_smt_diff;
extern struct sd_sys_item sd_sys_item_cpu_oper_cnt;
extern struct sd_sys_item sd_sys_item_cpu_deconf_cnt;
extern struct sd_sys_item sd_sys_item_cpu_stop_cnt;

View File

@@ -98,6 +98,18 @@ static u64 l_cpu_item_64(struct sd_cpu_item *item, struct sd_cpu *cpu)
return l_cpu_info_u64(cpu->d_cur, item->offset) / cpu->cnt;
}
static u64 l_cpu_smt_util(struct sd_cpu_item *item, struct sd_cpu *cpu)
{
u64 core_us, thr_us, mgm_us;
(void)item;
core_us = sd_cpu_item_u64(&sd_cpu_item_core_diff, cpu);
thr_us = sd_cpu_item_u64(&sd_cpu_item_thread_diff, cpu);
mgm_us = sd_cpu_item_u64(&sd_cpu_item_mgm_diff, cpu);
return ht_calculate_smt_util(core_us, thr_us, mgm_us, cpu->threads_per_core);
}
/*
* CPU item definitions
*/
@@ -139,6 +151,13 @@ struct sd_cpu_item sd_cpu_item_thread_diff = {
.fn_u64 = l_cpu_diff_u64,
};
struct sd_cpu_item sd_cpu_item_smt_diff = {
.table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'S', "smt"),
.type = SD_TYPE_U64,
.desc = "Real CPU SMT utilization",
.fn_u64 = l_cpu_smt_util,
};
struct sd_cpu_item sd_cpu_item_mgm_diff = {
.table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'm', "mgm"),
.type = SD_TYPE_U64,

View File

@@ -208,6 +208,18 @@ static s64 l_sys_cpu_info_diff_s64(struct sd_sys_item *item, struct sd_sys *sys)
return rc;
}
static u64 l_sys_smt_util(struct sd_sys_item *item, struct sd_sys *sys)
{
u64 core_us, thr_us, mgm_us;
(void)item;
core_us = sd_sys_item_u64(sys, &sd_sys_item_core_diff);
thr_us = sd_sys_item_u64(sys, &sd_sys_item_thread_diff);
mgm_us = sd_sys_item_u64(sys, &sd_sys_item_mgm_diff);
return ht_calculate_smt_util(core_us, thr_us, mgm_us, sys->threads_per_core);
}
/*
* System item definitions
*/
@@ -277,6 +289,13 @@ struct sd_sys_item sd_sys_item_thread_diff = {
.fn_u64 = l_sys_cpu_info_diff_u64,
};
struct sd_sys_item sd_sys_item_smt_diff = {
.table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'S', "smt"),
.type = SD_TYPE_U64,
.desc = "Real CPU SMT utilization",
.fn_u64 = l_sys_smt_util,
};
struct sd_sys_item sd_sys_item_mgm_diff = {
.table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'm', "mgm"),
.offset = SD_CPU_INFO_OFFSET(mgm_time_us),

View File

@@ -34,10 +34,10 @@
/* Secure IPL error */
#define ESECUREBOOT 0x00004512
/* kdump: No operating system information was found */
/* os_info error: No operating system information was found */
#define EOS_INFO_MISSING 0x00004520
/* kdump: The checksum of the operating system information is incorrect */
/* os_info error: The checksum of the operating system information is incorrect */
#define EOS_INFO_CSUM_FAILED 0x00004521
/* kdump: The major version of the operating system information is too high */

View File

@@ -21,10 +21,6 @@
#define IPL_MAX_SUPPORTED_VERSION 0
#define IPL_PARM_BLOCK_VERSION 0x1
/* IPL Types */
#define IPL_TYPE_PV 0x5
#ifndef __ASSEMBLER__
#include <stdint.h>
@@ -43,6 +39,16 @@ struct ipl_pb_hdr {
uint8_t pbt;
} __packed;
/* IPL Parameter Block types */
enum ipl_pbt {
IPL_PBT_FCP = 0,
IPL_PBT_SCP_DATA = 1,
IPL_PBT_CCW = 2,
IPL_PBT_ECKD = 3,
IPL_PBT_NVME = 4,
IPL_PBT_PV = 5,
};
/* IPL Parameter Block 0 with common fields */
struct ipl_pb0_common {
uint32_t len;

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)

78
include/boot/os_info.h Normal file
View File

@@ -0,0 +1,78 @@
/*
* zipl - zSeries Initial Program Loader tool
*
* os-info definitions
*
* 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 OS_INFO_H
#define OS_INFO_H
#include "lib/zt_common.h"
#include "boot/error.h"
#include "boot/s390.h"
#include <stdint.h>
#define OS_INFO_MAGIC 0x4f53494e464f535aULL /* OSINFOSZ */
#define OS_INFO_CSUM_SIZE (sizeof(struct os_info) - offsetof(struct os_info, version_major))
#define OS_INFO_FLAGS_ENTRY_SIZE (sizeof(unsigned long))
#define OS_INFO_VMCOREINFO 0
#define OS_INFO_REIPL_BLOCK 1
#define OS_INFO_FLAGS_ENTRY 2
#define OS_INFO_FLAG_REIPL_CLEAR (1UL << 0)
struct os_info_entry {
uint64_t addr;
uint64_t size;
uint32_t csum;
} __packed;
struct os_info {
uint64_t magic;
uint32_t csum;
uint16_t version_major;
uint16_t version_minor;
uint64_t crashkernel_addr;
uint64_t crashkernel_size;
struct os_info_entry entry[3];
uint8_t reserved[4004];
} __packed;
/*
* Return 0 in case of valid os_info
* Return -EOS_INFO_MISSING if os_info address is not page aligned or page is
* not accessible or os_info magic value is missing.
* Return -EOS_INFO_CSUM_FAILED if os_info checksum is invalid.
*/
static inline int os_info_check(const struct os_info *os_info)
{
if (!os_info ||
(unsigned long)os_info % PAGE_SIZE ||
!page_is_valid((unsigned long)os_info) ||
os_info->magic != OS_INFO_MAGIC)
return -EOS_INFO_MISSING;
if (os_info->csum != csum_partial(&os_info->version_major, OS_INFO_CSUM_SIZE, 0))
return -EOS_INFO_CSUM_FAILED;
return 0;
}
/*
* Return 1 in case of valid os_info_entry, otherwise 0
* Make sure that the entire os_info structure is checked first with os_info_check().
*/
static inline int os_info_entry_is_valid(const struct os_info_entry *entry)
{
return (entry &&
entry->addr &&
entry->size &&
page_is_valid(entry->addr) &&
entry->csum == csum_partial((void *)entry->addr, entry->size, 0));
}
#endif /* OS_INFO_H */

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__
@@ -285,18 +286,21 @@ static __always_inline void __ctl_set_bit(unsigned int cr, unsigned int bit)
* DIAG 308 support
*/
enum diag308_subcode {
DIAG308_REL_HSA = 2,
DIAG308_IPL = 3,
DIAG308_DUMP = 4,
DIAG308_SET = 5,
DIAG308_STORE = 6,
DIAG308_CLEAR_RESET = 0,
DIAG308_LOAD_NORMAL_RESET = 1,
DIAG308_REL_HSA = 2,
DIAG308_LOAD_CLEAR = 3,
DIAG308_LOAD_NORMAL_DUMP = 4,
DIAG308_SET = 5,
DIAG308_STORE = 6,
DIAG308_LOAD_NORMAL = 7,
DIAG308_SET_PV = 8,
DIAG308_UNPACK_PV = 10,
};
enum diag308_rc {
DIAG308_RC_OK = 0x0001,
DIAG308_RC_NO_CONF = 0x0102,
DIAG308_RC_NOCONFIG = 0x0102,
};
static __always_inline unsigned long diag308(unsigned long subcode, void *addr)

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

@@ -13,8 +13,7 @@ RECURSIVE_TARGETS = all-recursive install-recursive clean-recursive \
all: all-recursive
check: check-recursive
install: all install-recursive
install: install-recursive
clean: clean-recursive

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)
@@ -98,7 +98,7 @@ install-libekmfweb.so.$(VERSION): libekmfweb.so.$(VERSION)
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 -T libekmfweb.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so.$(VERSION)
ln -srf $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so.$(VERM)
ln -srf $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so
$(INSTALL) -d -m 770 $(DESTDIR)$(USRINCLUDEDIR)/ekmfweb
$(INSTALL) -d -m 755 $(DESTDIR)$(USRINCLUDEDIR)/ekmfweb
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 $(rootdir)include/ekmfweb/ekmfweb.h $(DESTDIR)$(USRINCLUDEDIR)/ekmfweb
install: all $(INSTALL_TARGETS)

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 \
@@ -121,7 +120,7 @@ install-libkmipclient.so.$(VERSION): libkmipclient.so.$(VERSION)
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 -T libkmipclient.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so.$(VERSION)
ln -srf $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so.$(VERM)
ln -srf $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so
$(INSTALL) -d -m 770 $(DESTDIR)$(USRINCLUDEDIR)/kmipclient
$(INSTALL) -d -m 755 $(DESTDIR)$(USRINCLUDEDIR)/kmipclient
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 $(rootdir)include/kmipclient/kmipclient.h $(DESTDIR)$(USRINCLUDEDIR)/kmipclient
install: all $(INSTALL_TARGETS)

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

@@ -9,7 +9,7 @@ RECURSIVE_TARGETS := all-recursive clean-recursive install-recursive
all: all-recursive
install: all install-recursive
install: install-recursive
clean: clean-recursive

View File

@@ -24,12 +24,27 @@ Show help options
\fBFILE\fP specifies the attestation result as input.
.TP
.B
\fB-o\fP, \fB--ouput\fP=\fBFILE\fP
\fBFILE\fP specifies the output for the verification result.
.TP
.B
\fB--hdr\fP=\fBFILE\fP
Specify the header of the guest image. Exactly one is required.
.TP
.B
\fB-a\fP, \fB--arpk\fP=\fBFILE\fP
Use \fBFILE\fP to specify the GCM-AES256 key to decrypt the attestation request. Delete this key after verification.
.TP
.B
\fB--format\fP=\fByaml\fP
Define the output format.
Default value: 'yaml'
Possible values:
.RS 4
- \fByaml\fP: Use YAML format
.RE
.TP
.B
\fB-V\fP, \fB--verbose\fP

View File

@@ -49,8 +49,10 @@ static pvattest_config_t pvattest_config = {
},
.verify = {
.input_path = NULL,
.output_path = NULL,
.hdr_path = NULL,
.arp_key_in_path = NULL,
.output_fmt = VERIFY_FMT_YAML,
},
};
typedef gboolean (*verify_options_fn_t)(GError **);
@@ -329,6 +331,15 @@ static gboolean hex_str_toull(const char *nptr, uint64_t *dst, GError **error)
.description = "Use FILE to specify the user data.\n", .arg_description = "FILE", \
}
#define _entry__verify_format(__indent) \
{ \
.long_name = "format", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
.arg = G_OPTION_ARG_CALLBACK, .arg_data = &set_verify_output_format, \
.description = "Define the output format.\n" __indent \
"Defaults to 'yaml'. (possible values: 'yaml')\n", \
.arg_description = "FORMAT", \
}
static gboolean increase_log_lvl(G_GNUC_UNUSED const char *option_name,
G_GNUC_UNUSED const char *value, G_GNUC_UNUSED void *data,
G_GNUC_UNUSED GError **error)
@@ -337,6 +348,20 @@ static gboolean increase_log_lvl(G_GNUC_UNUSED const char *option_name,
return TRUE;
}
static gboolean set_verify_output_format(const char *option_name, const char *value,
G_GNUC_UNUSED void *data, GError **error)
{
if (!g_strcmp0(value, "yaml")) {
pvattest_config.verify.output_fmt = VERIFY_FMT_YAML;
} else {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
_("Found value '%s' for option '%s', but only 'yaml' is allowed."),
value, option_name);
return FALSE;
}
return TRUE;
}
static gboolean create_set_paf(G_GNUC_UNUSED const char *option_name, const char *value,
G_GNUC_UNUSED void *data, GError **error)
{
@@ -445,13 +470,16 @@ static gboolean verify_perform(GError **error)
}
/************************* VERIFY OPTIONS ************************************/
#define verify_indent " "
#define verify_indent " "
static GOptionEntry verify_options[] = {
_entry_input(&pvattest_config.verify.input_path, "attestation result", verify_indent),
_entry_output(&pvattest_config.verify.output_path,
"verification result.\n" verify_indent "(optional)", verify_indent),
_entry_guest_hdr(&pvattest_config.verify.hdr_path, verify_indent),
_entry_att_prot_key_load(&pvattest_config.verify.arp_key_in_path, verify_indent),
_entry_verbose(verify_indent),
_entry__verify_format(verify_indent),
{ NULL },
};
@@ -631,6 +659,7 @@ static void pvattest_parse_clear_verify_config(pvattest_verify_config_t *config)
if (!config)
return;
g_free(config->input_path);
g_free(config->output_path);
g_free(config->hdr_path);
g_free(config->arp_key_in_path);
}

View File

@@ -58,8 +58,15 @@ typedef struct {
char *user_data_path; /* default NULL */
} pvattest_perform_config_t;
enum verify_output_format {
VERIFY_FMT_HUMAN,
VERIFY_FMT_YAML,
};
typedef struct {
char *input_path;
char *output_path;
enum verify_output_format output_fmt;
char *hdr_path;
char *arp_key_in_path;
} pvattest_verify_config_t;

View File

@@ -455,8 +455,7 @@ static void print_entry(const char *name, GBytes *data, const gboolean print_dat
fprintf(stream, _("%s (%#lx bytes)"), name, g_bytes_get_size(data));
if (print_data) {
fprintf(stream, ":\n");
pvattest_hexdump(g_bytes_get_data(data, NULL), g_bytes_get_size(data), 16, " ",
stream);
pvattest_hexdump(stream, data, 16, " ", TRUE);
}
fprintf(stream, "\n");
}

View File

@@ -159,24 +159,47 @@ void pvattest_log_bytes(const void *data, size_t size, size_t width, const char
g_log(PVATTEST_BYTES_LOG_DOMAIN, log_lvl, "\n");
}
void pvattest_hexdump(const void *data, size_t size, size_t width, const char *prefix, FILE *stream)
int pvattest_hexdump(FILE *stream, GBytes *bytes, const size_t width, const char *prefix,
const gboolean beautify)
{
const uint8_t *data_b = data;
const uint8_t *data;
size_t size;
pv_wrapped_g_assert(data);
pv_wrapped_g_assert(bytes);
pv_wrapped_g_assert(stream);
fprintf(stream, "%s0x0000 ", prefix);
data = g_bytes_get_data(bytes, &size);
pv_wrapped_g_assert(data);
if (beautify) {
if (fprintf(stream, "%s0x0000 ", prefix) < 0)
return -1;
} else {
if (fprintf(stream, "%s", prefix) < 0)
return -1;
}
for (size_t i = 0; i < size; i++) {
fprintf(stream, "%02x", data_b[i]);
if (i % 2 == 1)
fprintf(stream, " ");
if (fprintf(stream, "%02x", data[i]) < 0)
return -1;
if (i % 2 == 1 && beautify) {
if (fprintf(stream, " ") < 0)
return -1;
}
if (i == size - 1)
break;
if (i % width == width - 1)
fprintf(stream, "\n%s0x%04lx ", prefix, i + 1);
if (width == 0)
continue;
if (i % width == width - 1) {
if (beautify) {
if (fprintf(stream, "\n%s0x%04lx ", prefix, i + 1) < 0)
return -1;
} else {
if (fprintf(stream, "\n%s", prefix) < 0)
return -1;
}
}
}
fprintf(stream, "\n");
return 0;
}
void pvattest_log_GError(const char *info, GError *error)

View File

@@ -60,8 +60,8 @@ void pvattest_log_plain_logger(const char *log_domain, GLogLevelFlags level, con
}
void pvattest_log_bytes(const void *data, size_t size, size_t width, const char *prefix,
gboolean beautify, GLogLevelFlags log_lvl) PV_NONNULL(1);
void pvattest_hexdump(const void *data, size_t size, size_t width, const char *prefix, FILE *stream)
PV_NONNULL(1, 5);
int pvattest_hexdump(FILE *stream, GBytes *bytes, const size_t width, const char *prefix,
const gboolean beautify) PV_NONNULL(1, 2);
void pvattest_log_GError(const char *info, GError *error) PV_NONNULL(1);
#endif /* PVATTEST_LOG_H */

View File

@@ -257,14 +257,63 @@ err_exit:
}
#endif /* PVATTEST_COMPILE_PERFORM */
static int fprint_verify_result(FILE *stream, const enum verify_output_format fmt,
GBytes *config_uid, GBytes *additional_data)
{
switch (fmt) {
case VERIFY_FMT_HUMAN:
if (fprintf(stream, _("Attestation measurement verified\n")) < 0)
return -1;
if (fprintf(stream, _("Config UID:\n")) < 0)
return -1;
if (pvattest_hexdump(stream, config_uid, 0x10L, "0x", FALSE) < 0)
return -1;
if (fprintf(stream, _("\n")) < 0)
return -1;
if (additional_data) {
if (fprintf(stream, _("Additional Data:\n")) < 0)
return -1;
if (pvattest_hexdump(stream, additional_data, 0x60L, "0x", FALSE) < 0)
return -1;
if (fprintf(stream, _("\n")) < 0)
return -1;
}
break;
case VERIFY_FMT_YAML:
if (fprintf(stream, "cuid: ") < 0)
return -1;
if (pvattest_hexdump(stream, config_uid, 0L, "'0x", FALSE) < 0)
return -1;
if (fprintf(stream, _("'\n")) < 0)
return -1;
if (additional_data) {
if (fprintf(stream, "add: ") < 0)
return -1;
if (pvattest_hexdump(stream, additional_data, 0x0L, "'0x", FALSE) < 0)
return -1;
if (fprintf(stream, _("'\n")) < 0)
return -1;
}
break;
default:
g_assert_not_reached();
break;
}
return 0;
}
#define __PVATTEST_VERIFY_ERROR_MSG _("Attestation measurement verification failed")
static int do_verify(pvattest_verify_config_t *verify_config)
static int do_verify(const pvattest_verify_config_t *verify_config, const int appl_log_lvl)
{
g_autoptr(GBytes) user_data = NULL, uv_measurement = NULL, additional_data = NULL,
image_hdr = NULL, calc_measurement = NULL, config_uid = NULL,
meas_key = NULL, arp_key = NULL, nonce = NULL, serialized_arcb = NULL;
g_autofree att_meas_ctx_t *measurement_hdr = NULL;
g_autoptr(exchange_format_ctx_t) input_ctx = NULL;
const char *err_prefix = __PVATTEST_VERIFY_ERROR_MSG;
g_autoptr(GError) error = NULL;
gboolean rc;
@@ -322,21 +371,37 @@ static int do_verify(pvattest_verify_config_t *verify_config)
return PVATTEST_EXIT_MEASURE_NOT_VERIFIED;
}
pvattest_log_info(_("Attestation measurement verified"));
pvattest_log_info(_("Config UID:"));
pvattest_log_bytes(g_bytes_get_data(config_uid, NULL), g_bytes_get_size(config_uid), 16L,
"", FALSE, PVATTEST_LOG_LVL_INFO);
/* Write human-readable output to stdout */
if (appl_log_lvl >= PVATTEST_LOG_LVL_INFO) {
if (fprint_verify_result(stdout, VERIFY_FMT_HUMAN, config_uid, additional_data) <
0) {
g_set_error(&error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR,
"stdout: %s", g_strerror(errno));
err_prefix = "Failed to write output";
goto err_exit;
}
}
if (additional_data) {
pvattest_log_info(_("\nAdditional Data:"));
pvattest_log_bytes(g_bytes_get_data(additional_data, NULL),
g_bytes_get_size(additional_data), 16L, "", FALSE,
PVATTEST_LOG_LVL_INFO);
/* Write to file */
if (verify_config->output_path) {
g_autoptr(FILE) output = pv_file_open(verify_config->output_path, "wx", &error);
if (!output) {
err_prefix = "Failed to write output";
goto err_exit;
}
if (fprint_verify_result(output, verify_config->output_fmt, config_uid,
additional_data) < 0) {
g_set_error(&error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR,
"'%s': %s", verify_config->output_path, g_strerror(errno));
err_prefix = "Failed to write output";
goto err_exit;
}
}
return EXIT_SUCCESS;
err_exit:
pvattest_log_GError(__PVATTEST_VERIFY_ERROR_MSG, error);
pvattest_log_GError(err_prefix, error);
return EXIT_FAILURE;
}
@@ -389,7 +454,7 @@ int main(int argc, char *argv[])
break;
#endif /* PVATTEST_COMPILE_PERFORM */
case PVATTEST_SUBC_VERIFY:
rc = do_verify(&config->verify);
rc = do_verify(&config->verify, appl_log_lvl);
break;
default:
g_return_val_if_reached(EXIT_FAILURE);

11
rust/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
*.rs.bk
# Generated during make build can be removed at any point
.check-dep-pvtools
.check-cargo

1380
rust/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

14
rust/Cargo.toml Normal file
View File

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

124
rust/Makefile Normal file
View File

@@ -0,0 +1,124 @@
include ../common.mak
HAVE_CARGO ?= 1
HAVE_OPENSSL ?= 1
HAVE_LIBCURL ?= 1
INSTALL_TARGETS := skip-build
BUILD_TARGETS := skip-build
PV_BUILD_TARGETS := skip-pv-build
CARGO_TARGETS :=
PV_TARGETS :=
CARGO_TEST_TARGETS :=
ifneq (${HAVE_CARGO},0)
CARGO_TARGETS :=
BUILD_TARGETS = $(CARGO_TARGETS)
INSTALL_TARGETS := install-rust-tools install-man
CARGO_TEST_TARGETS = $(addsuffix .test, $(CARGO_TARGETS))
ifneq (${HAVE_OPENSSL},0)
ifneq (${HAVE_LIBCURL},0)
PV_TARGETS := pvsecret
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)
# build release targets by default
ifeq ("${D}","0")
ALL_CARGOFLAGS += --release
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)
$(PV_TARGETS): .check-dep-pvtools
$(PV_TARGETS) $(CARGO_TARGETS): .check-cargo .no-cross-compile
$(CARGO_BUILD) --bin $@ $(ALL_CARGOFLAGS)
.PHONY: $(PV_TARGETS) $(CARGO_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"
skip-pv-build:
echo " SKIP rust-pv-tools due to unresolved dependencies"
all: $(BUILD_TARGETS)
install: $(INSTALL_TARGETS)
print-rust-targets:
echo $(BUILD_TARGETS)
clean:
$(CARGO_CLEAN) ${ALL_CARGOFLAGS}
$(RM) -- .check-dep-pvtools .detect-openssl.dep.c .check-cargo
rust-test: $(CARGO_TEST_TARGETS)
install-rust-tools: $(BUILD_TARGETS)
$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
$(foreach target,$(CARGO_TARGETS),\
$(INSTALL) target/release/$(target) $(DESTDIR)$(USRBINDIR);)
$(foreach target,$(PV_TARGETS),\
$(INSTALL) target/release/$(target) $(DESTDIR)$(USRBINDIR);)
install-man:
$(foreach target,$(CARGO_TARGETS),\
$(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;)
$(foreach target,$(PV_TARGETS),\
$(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;)
.PHONY: all install clean skip-build install-rust-tools print-rust-targets install-man rust-test
.check-cargo:
ifeq ($(shell command -v $(CARGO)),)
$(call check_dep, \
"rust/cargo", \
"invalid-incl", \
"cargo", \
"HAVE_CARGO=0")
endif
touch $@
.no-cross-compile:
ifneq ($(HOST_ARCH), $(BUILD_ARCH))
$(error Cross compiling is not supported for rust code. Specify HAVE_CARGO=0 to disable rust compilation)
endif
.PHONY: .no-cross-compile
.detect-openssl.dep.c:
echo "#include <openssl/evp.h>" > $@
echo "#if OPENSSL_VERSION_NUMBER < 0x10101000L" >> $@
echo " #error openssl version 1.1.1 is required" >> $@
echo "#endif" >> $@
echo "static void __attribute__((unused)) test(void) {" >> $@
echo " EVP_MD_CTX *ctx = EVP_MD_CTX_new();" >> $@
echo " EVP_MD_CTX_free(ctx);" >> $@
echo "}" >> $@
.check-dep-pvtools: .detect-openssl.dep.c
$(call check_dep, \
"Rust-pv", \
$^, \
"openssl-devel / libssl-dev version >= 1.1.1", \
"HAVE_OPENSSL=0", \
"-I.")
$(call check_dep, \
"Rust-pv", \
"curl/curl.h", \
"libcurl-devel", \
"HAVE_LIBCURL=0")
touch $@

144
rust/README.md Normal file
View File

@@ -0,0 +1,144 @@
# s390-tools tools written in rust
## Setting up rust development and build environment
Please refer to the official documentation to set up a working rust environment:
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 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
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.
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
* __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.
## 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
sense to use C and C is still allowed to be used for a new tool/library.
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 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.
The following list of crates should cover a wide variety of use cases. This list
is a start, but can change over time.
* [anyhow](https://crates.io/crates/anyhow)
* Flexible concrete Error type built on std::error::Error
* [byteorder](https://crates.io/crates/byteorder)
* Library for reading/writing numbers in big-endian and little-endian.
* [cfg-if](https://crates.io/crates/cfg-if)
* A macro to ergonomically define an item depending on a large number of
#[cfg] parameters. Structured like an if-else chain, the first matching
branch is the item that gets emitted.
* [clap](https://crates.io/crates/clap)
* A simple to use, efficient, and full-featured Command Line Argument Parser
* [curl](https://crates.io/crates/curl)
* Rust bindings to libcurl for making HTTP requests
* [libc](https://crates.io/crates/libc)
* Raw FFI bindings to platform libraries like libc.
* [log](https://crates.io/crates/log)
* A lightweight logging facade for Rust
* [openssl](https://crates.io/crates/openssl)
* OpenSSL bindings
* [serde](https://crates.io/crates/serde)
* A generic serialization/deserialization framework
* [serde_yaml](https://crates.io/crates/serde_yaml)
* YAML data format for Serde
* [thiserror](https://crates.io/crates/thiserror)
* derive(Error)
* [zerocopy](https://crates.io/crates/zerocopy)
* Utilities for zero-copy parsing and serialization
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.
Add the tool to the _s390-tools_ build system:
```Makefile
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
Do not communicate the version defined in the `toml` file by default. Use
`release_string` from the `rust/utils` crate instead:
```rust
use utils::release_string;
fn print_version() {
println!(
"{} version {}\nCopyright IBM Corp. 2023",
env!("CARGO_PKG_NAME"), // collapses into the crates name
release_string!() // this (very likely) collapses into a compile time constant
);
}
```
### Unsafe rust
rust allows you to write unsafe rust. Try to avoid it, it can make rust
_unsafe_. If you need to, e.g. interacting with other languages like C, keep
the `unsafe` block as small as possible and add a reasoning using `// SAFETY:
`why this code is safe. Example:
```rust
// Get the raw pointer and do an ioctl.
//
// SAFETY: the passed pointer points to a valid memory region that
// contains the expected C-struct. The struct outlives this function.
unsafe {
let ptr: *mut ffi::uvio_ioctl_cb = cb as *mut _;
rc = ioctl(raw_fd, cmd, ptr);
}
```
### Coding style
Make `cargo fmt` and `cargo clippy` happy!
### Testing
Prefer writing tests using rustdoc. Use explicit rust tests for more edge case tests.

32
rust/pv/Cargo.toml Normal file
View File

@@ -0,0 +1,32 @@
[package]
name = "pv"
version = "0.9.0"
edition.workspace = true
license.workspace = true
[dependencies]
libc = "0.2.49"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
thiserror = "1.0.33"
zerocopy = "0.6"
cfg-if = "1.0.0"
# dependencies for request feature
clap = { version ="4", features = ["derive", "wrap_help"], optional = true }
curl = { version ="0.4.7", optional = true }
openssl = {version = "0.10.49", optional = true }
openssl_extensions = { path = "openssl_extensions", optional = true }
serde = { version = "1.0.139", features = ["derive"], optional = true }
# misc optional dependencies
byteorder = {version = "1.3", optional = true }
[dev-dependencies]
mockito = {version = "1", default-features = false }
serde_test = "1"
lazy_static = "1.1"
[features]
default = []
request = ["dep:openssl", "dep:curl", "dep:openssl_extensions", "dep:serde", "dep:clap"]
uvsecret = ["dep:byteorder", "dep:serde"]

View File

@@ -0,0 +1,12 @@
[package]
name = "openssl_extensions"
version = "0.1.0"
edition.workspace = true
license.workspace = true
[dependencies]
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

@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![allow(
clippy::inconsistent_digit_grouping,
clippy::uninlined_format_args,
clippy::unusual_byte_groupings
)]
use std::env;
fn main() {
if let Ok(vars) = env::var("DEP_OPENSSL_CONF") {
for var in vars.split(',') {
println!("cargo:rustc-cfg=osslconf=\"{}\"", var);
}
}
if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") {
let version = u64::from_str_radix(&version, 16).unwrap();
if version >= 0x1_00_01_00_0 {
println!("cargo:rustc-cfg=ossl101");
}
if version >= 0x1_00_02_00_0 {
println!("cargo:rustc-cfg=ossl102");
}
if version >= 0x1_01_00_00_0 {
println!("cargo:rustc-cfg=ossl110");
}
if version >= 0x1_01_00_07_0 {
println!("cargo:rustc-cfg=ossl110g");
}
if version >= 0x1_01_00_08_0 {
println!("cargo:rustc-cfg=ossl110h");
}
if version >= 0x1_01_01_00_0 {
println!("cargo:rustc-cfg=ossl111");
}
if version >= 0x3_00_00_00_0 {
println!("cargo:rustc-cfg=ossl300");
}
}
}

View File

@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use std::fmt;
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
use libc::c_int;
use openssl::x509::{X509CrlRef, X509Ref};
mod ffi {
extern "C" {
pub fn X509_check_akid(
issuer: *const openssl_sys::X509,
akid: *const openssl_sys::AUTHORITY_KEYID,
) -> ::libc::c_int;
}
}
foreign_type! {
type CType = openssl_sys::AUTHORITY_KEYID;
fn drop = openssl_sys::AUTHORITY_KEYID_free;
/// An `Authority Key Identifier`.
pub struct Akid;
/// Reference to `Akid`
pub struct AkidRef;
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct AkidCheckResult(c_int);
impl fmt::Debug for AkidCheckResult {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("AkidCheckResult")
.field("code", &self.0)
.finish()
}
}
impl AkidCheckResult {
/// Creates an `AkidCheckResult` from a raw error number.
unsafe fn from_raw(err: c_int) -> AkidCheckResult {
AkidCheckResult(err)
}
pub const OK: AkidCheckResult = AkidCheckResult(openssl_sys::X509_V_OK);
pub const ERR_AKID_ISSUER_SERIAL_MISMATCH: AkidCheckResult =
AkidCheckResult(openssl_sys::X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH);
pub const ERR_AKID_SKID_MISMATCH: AkidCheckResult =
AkidCheckResult(openssl_sys::X509_V_ERR_AKID_SKID_MISMATCH);
}
impl AkidRef {
///Check if the `Akid` matches the issuer
///
pub fn check(&self, issuer: &X509Ref) -> AkidCheckResult {
unsafe {
let res = ffi::X509_check_akid(issuer.as_ptr(), self.as_ptr());
AkidCheckResult::from_raw(res)
}
}
}
pub trait AkidExtension {
fn akid(&self) -> Option<Akid>;
}
impl AkidExtension for X509Ref {
fn akid(&self) -> Option<Akid> {
unsafe {
let ptr = openssl_sys::X509_get_ext_d2i(
self.as_ptr(),
openssl_sys::NID_authority_key_identifier,
std::ptr::null_mut(),
std::ptr::null_mut(),
);
if ptr.is_null() {
None
} else {
Some(Akid::from_ptr(ptr as *mut _))
}
}
}
}
impl AkidExtension for X509CrlRef {
fn akid(&self) -> Option<Akid> {
unsafe {
let ptr = openssl_sys::X509_CRL_get_ext_d2i(
self.as_ptr(),
openssl_sys::NID_authority_key_identifier,
std::ptr::null_mut(),
std::ptr::null_mut(),
);
if ptr.is_null() {
None
} else {
Some(Akid::from_ptr(ptr as *mut _))
}
}
}
}
#[cfg(test)]
mod test {
use crate::test_utils::load_gen_cert;
use super::*;
#[test]
fn akid() {
let cert = load_gen_cert("ibm.crt");
let ca = load_gen_cert("root_ca.crt");
let akid = cert.akid().unwrap();
let res = akid.check(&ca);
assert_eq!(res, AkidCheckResult::OK);
}
}

View File

@@ -0,0 +1,128 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
pub use crate::stackable_crl::*;
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl::{
error::ErrorStack,
stack::{Stack, StackRef},
x509::{
store::{X509StoreBuilderRef, X509StoreRef},
X509CrlRef, X509NameRef, X509Ref, X509StoreContextRef, X509,
},
};
pub fn opt_to_ptr<T: ForeignTypeRef>(o: Option<&T>) -> *mut T::CType {
match o {
None => std::ptr::null_mut(),
Some(p) => p.as_ptr(),
}
}
mod ffi {
extern "C" {
#[cfg(ossl110)]
pub fn X509_STORE_CTX_get1_crls(
ctx: *mut openssl_sys::X509_STORE_CTX,
nm: *mut openssl_sys::X509_NAME,
) -> *mut openssl_sys::stack_st_X509_CRL;
pub fn X509_STORE_add_crl(
xs: *mut openssl_sys::X509_STORE,
x: *mut openssl_sys::X509_CRL,
) -> libc::c_int;
}
}
pub trait X509StoreExtension {
fn add_crl(&mut self, crl: &X509CrlRef) -> Result<(), ErrorStack>;
}
impl X509StoreExtension for X509StoreBuilderRef {
fn add_crl(&mut self, crl: &X509CrlRef) -> Result<(), ErrorStack> {
unsafe {
{
let r = ffi::X509_STORE_add_crl(self.as_ptr(), crl.as_ptr());
if r <= 0 {
Err(ErrorStack::get())
} else {
Ok(())
}
}
}
}
}
pub trait X509StoreContextExtension {
fn init_opt<F, T>(
&mut self,
trust: &X509StoreRef,
cert: Option<&X509Ref>,
cert_chain: Option<&StackRef<X509>>,
with_context: F,
) -> Result<T, ErrorStack>
where
F: FnOnce(&mut X509StoreContextRef) -> std::result::Result<T, ErrorStack>;
fn crls(
&mut self,
subj: &X509NameRef,
) -> std::result::Result<Stack<StackableX509Crl>, ErrorStack>;
}
impl X509StoreContextExtension for X509StoreContextRef {
fn init_opt<F, T>(
&mut self,
trust: &X509StoreRef,
cert: Option<&X509Ref>,
cert_chain: Option<&StackRef<X509>>,
with_context: F,
) -> Result<T, ErrorStack>
where
F: FnOnce(&mut X509StoreContextRef) -> std::result::Result<T, ErrorStack>,
{
struct Cleanup<'a>(&'a mut X509StoreContextRef);
impl<'a> Drop for Cleanup<'a> {
fn drop(&mut self) {
unsafe {
openssl_sys::X509_STORE_CTX_cleanup(self.0.as_ptr());
}
}
}
unsafe {
{
let r = openssl_sys::X509_STORE_CTX_init(
self.as_ptr(),
trust.as_ptr(),
opt_to_ptr(cert),
opt_to_ptr(cert_chain),
);
if r <= 0 {
Err(ErrorStack::get())
} else {
Ok(r)
}
}?;
}
let cleanup = Cleanup(self);
with_context(cleanup.0)
}
/// Get all Certificate Revocation Lists with the subject currently stored
#[cfg(ossl110)]
fn crls(
&mut self,
subj: &X509NameRef,
) -> std::result::Result<Stack<StackableX509Crl>, ErrorStack> {
unsafe {
{
let r = ffi::X509_STORE_CTX_get1_crls(self.as_ptr(), subj.as_ptr());
if r.is_null() {
Err(ErrorStack::get())
} else {
Ok(Stack::from_ptr(r))
}
}
}
}
}

View File

@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![doc(hidden)]
/// Extensions to the rust-openssl crate, that are not upstream yet
/// Upstreaming mostly work in progress
pub mod akid;
pub mod crl;
mod stackable_crl;
/// Test if two CRLs are equal.
///
/// relates to X509_CRL_match
/// (Upstream is missing that functionality)
pub fn x509_crl_eq(a: &openssl::x509::X509CrlRef, b: &openssl::x509::X509CrlRef) -> bool {
use foreign_types::ForeignTypeRef;
let cmp = unsafe { openssl_sys::X509_CRL_match(a.as_ptr(), b.as_ptr()) };
cmp == 0
}
#[allow(dead_code)]
mod test_utils {
include!("../../src/test_utils.rs");
}

View File

@@ -0,0 +1,142 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
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;
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
impl ForeignType for StackableX509Crl {
type CType = openssl_sys::X509_CRL;
type Ref = X509CrlRef;
unsafe fn from_ptr(ptr: *mut openssl_sys::X509_CRL) -> StackableX509Crl {
StackableX509Crl(ptr)
}
fn as_ptr(&self) -> *mut openssl_sys::X509_CRL {
self.0
}
}
impl Drop for StackableX509Crl {
fn drop(&mut self) {
unsafe { (openssl_sys::X509_CRL_free)(self.0) }
}
}
impl ::std::ops::Deref for StackableX509Crl {
type Target = X509CrlRef;
fn deref(&self) -> &X509CrlRef {
unsafe { ForeignTypeRef::from_ptr(self.0) }
}
}
impl ::std::ops::DerefMut for StackableX509Crl {
fn deref_mut(&mut self) -> &mut X509CrlRef {
unsafe { ForeignTypeRef::from_ptr_mut(self.0) }
}
}
#[allow(clippy::explicit_auto_deref)]
impl ::std::borrow::Borrow<X509CrlRef> for StackableX509Crl {
fn borrow(&self) -> &X509CrlRef {
&**self
}
}
#[allow(clippy::explicit_auto_deref)]
impl ::std::convert::AsRef<X509CrlRef> for StackableX509Crl {
fn as_ref(&self) -> &X509CrlRef {
&**self
}
}
impl Stackable for StackableX509Crl {
type StackType = openssl_sys::stack_st_X509_CRL;
}
pub struct MemBioSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
impl<'a> Drop for MemBioSlice<'a> {
fn drop(&mut self) {
unsafe {
openssl_sys::BIO_free_all(self.0);
}
}
}
impl<'a> MemBioSlice<'a> {
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
openssl_sys::init();
assert!(buf.len() <= c_int::max_value() as usize);
let bio = unsafe {
{
let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int);
if r.is_null() {
Err(ErrorStack::get())
} else {
Ok(r)
}
}?
};
Ok(MemBioSlice(bio, PhantomData))
}
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
self.0
}
}
impl StackableX509Crl {
pub fn stack_from_pem(pem: &[u8]) -> Result<Vec<X509Crl>, ErrorStack> {
unsafe {
openssl_sys::init();
let bio = MemBioSlice::new(pem)?;
let mut crls = vec![];
loop {
let r = openssl_sys::PEM_read_bio_X509_CRL(
bio.as_ptr(),
ptr::null_mut(),
None,
ptr::null_mut(),
);
if r.is_null() {
let err = openssl_sys::ERR_peek_last_error();
if openssl_sys::ERR_GET_LIB(err) as c_int == openssl_sys::ERR_LIB_PEM
&& openssl_sys::ERR_GET_REASON(err) == openssl_sys::PEM_R_NO_START_LINE
{
openssl_sys::ERR_clear_error();
break;
}
return Err(ErrorStack::get());
} else {
crls.push(X509Crl::from_ptr(r));
}
}
Ok(crls)
}
}
}
impl From<X509Crl> for StackableX509Crl {
fn from(value: X509Crl) -> Self {
unsafe {
openssl_sys::X509_CRL_up_ref(value.as_ptr());
StackableX509Crl::from_ptr(value.as_ptr())
}
}
}
impl From<StackableX509Crl> for X509Crl {
fn from(value: StackableX509Crl) -> Self {
unsafe {
openssl_sys::X509_CRL_up_ref(value.as_ptr());
X509Crl::from_ptr(value.as_ptr())
}
}
}

View File

@@ -0,0 +1 @@
../../tests/assets

247
rust/pv/src/brcb.rs Normal file
View File

@@ -0,0 +1,247 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use std::{
io::{Read, Seek, SeekFrom::Current},
mem::size_of,
};
// (SE) boot request control block aka SE header
use crate::{
assert_size, request::MagicValue, requires_feat, static_assert, Error, Result, PAGESIZE,
};
use log::debug;
use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64};
/// Struct containing all SE-header tags.
///
/// Contains:
/// Page List Digest (pld)
/// Address List Digest (ald)
/// Tweak List Digest (tld)
/// SE Header Tag (seht)
///
#[doc = requires_feat!(request)]
#[repr(C)]
#[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq)]
pub struct BootHdrTags {
pld: [u8; BootHdrHead::DIGEST_SIZE],
ald: [u8; BootHdrHead::DIGEST_SIZE],
tld: [u8; BootHdrHead::DIGEST_SIZE],
seht: [u8; BootHdrHead::SEHT_SIZE],
}
/// Magiv value for a SE-(boot)header
pub struct BootHdrMagic;
impl MagicValue<8> for BootHdrMagic {
const MAGIC: [u8; 8] = [0x49, 0x42, 0x4d, 0x53, 0x65, 0x63, 0x45, 0x78];
}
impl BootHdrTags {
/// Returns a reference to the SE-hdr tag of this [`BootHdrTags`].
pub fn seht(&self) -> &[u8; 16] {
&self.seht
}
/// Creates a new [`BootHdrTags`]. Useful for writing tests.
#[doc(hidden)]
pub const fn new(pld: [u8; 64], ald: [u8; 64], tld: [u8; 64], seht: [u8; 16]) -> Self {
Self {
ald,
tld,
pld,
seht,
}
}
/// returns false if no hdr found, true otherwise
/// in the very unlikel case an IO error can appear
/// when seeking to the beginning of the header
fn seek_se_hdr_start<R>(img: &mut R) -> Result<bool>
where
R: Read + Seek,
{
const MAX_ITER: usize = 0x15;
const BUF_SIZE: i64 = 8;
static_assert!(BootHdrMagic::MAGIC.len() == BUF_SIZE as usize);
let mut buf = [0; BUF_SIZE as usize];
for _ in [0; MAX_ITER] {
match img.read_exact(&mut buf) {
Ok(it) => it,
Err(_) => return Ok(false),
};
if BootHdrMagic::starts_with_magic(&buf) {
// go back to the beginning of the header
img.seek(Current(-BUF_SIZE))?;
return Ok(true);
}
// goto next page start
// or report invalid file format if file ends "early"
match img.seek(Current(PAGESIZE as i64 - BUF_SIZE)) {
Ok(it) => it,
Err(_) => return Ok(false),
};
}
Ok(false)
}
/// Deserializes a (SE) boot header and extracts the tags.
///
/// Searches for the header; if found extracts the tags.
///
/// # Errors
///
/// This function will return an error if `hdr` is not at least as long as the header specifies
/// in bytes 12-15 or the first 8 bytes do not contain the magic value.
pub fn from_se_image<R>(img: &mut R) -> Result<Self>
where
R: Read + Seek,
{
if !Self::seek_se_hdr_start(img)? {
debug!("No boot hdr found");
return Err(Error::InvBootHdr);
}
// read in the header
let mut hdr = vec![0u8; size_of::<BootHdrHead>()];
img.read_exact(&mut hdr)?;
let hdr_head = match BootHdrHead::read_from_prefix(hdr.as_mut_slice()) {
Some(hdr) => hdr,
None => {
debug!("Boot hdr is to small");
return Err(Error::InvBootHdr);
}
};
//Some sanity checks
if !BootHdrMagic::starts_with_magic(&hdr) || hdr_head.version.get() != 0x100 {
debug!("Inv magic or size");
return Err(Error::InvBootHdr);
}
//go to the Bot header tag
img.seek(Current(
hdr_head.size.get() as i64
- size_of::<BootHdrHead>() as i64
- BootHdrHead::SEHT_SIZE as i64,
))?;
// read in the tag
let mut seht = [0u8; BootHdrHead::SEHT_SIZE];
img.read_exact(seht.as_mut_slice())?;
Ok(BootHdrTags {
pld: hdr_head.pld,
ald: hdr_head.ald,
tld: hdr_head.tld,
seht,
})
}
}
#[repr(C)]
#[derive(Debug, Clone, FromBytes)]
struct BootHdrHead {
magic: U64<BigEndian>,
version: U32<BigEndian>,
size: U32<BigEndian>,
iv: [u8; 12],
res1: u32,
nks: U64<BigEndian>,
sea: U64<BigEndian>,
nep: U64<BigEndian>,
pcf: U64<BigEndian>,
user_pubkey: [u8; 160],
pld: [u8; Self::DIGEST_SIZE],
ald: [u8; Self::DIGEST_SIZE],
tld: [u8; Self::DIGEST_SIZE],
}
assert_size!(BootHdrHead, 0x1A0);
impl BootHdrHead {
const DIGEST_SIZE: usize = 0x40;
const SEHT_SIZE: usize = 0x10;
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
use crate::get_test_asset;
use crate::Error;
const EXP_HDR: BootHdrTags = BootHdrTags {
pld: [
0xbe, 0x94, 0xb5, 0xea, 0xb3, 0xc1, 0xb1, 0x18, 0xc7, 0x57, 0xd7, 0xdb, 0x7e, 0xa0,
0xf6, 0x5d, 0x9b, 0x64, 0x82, 0x3a, 0x8d, 0xc5, 0x5b, 0xf8, 0xa8, 0x72, 0x5b, 0x58,
0x07, 0x2d, 0x9d, 0x42, 0x58, 0xc5, 0x3e, 0x8a, 0x5d, 0xa8, 0x2d, 0xfb, 0x21, 0x92,
0xd9, 0x1d, 0x07, 0xbc, 0x1c, 0x39, 0xb9, 0x5d, 0x63, 0x21, 0xd3, 0xba, 0x16, 0xa7,
0x51, 0xa6, 0xe3, 0xe3, 0x2f, 0x3e, 0x01, 0x61,
],
ald: [
0x28, 0x58, 0xc3, 0x36, 0x8b, 0x2a, 0x0a, 0xf0, 0xc5, 0xea, 0x0f, 0xde, 0x79, 0x05,
0xeb, 0x15, 0xaf, 0x9c, 0xd1, 0xdd, 0x73, 0x71, 0x65, 0x93, 0x3c, 0xda, 0xa2, 0xb8,
0x50, 0xb6, 0xa8, 0xe2, 0xf0, 0xf4, 0x2c, 0x7b, 0x36, 0xdd, 0x53, 0x81, 0x09, 0x62,
0x88, 0xdc, 0x09, 0x2d, 0xaa, 0x8a, 0x6f, 0xac, 0xec, 0x25, 0x34, 0x13, 0x7b, 0xc9,
0x4c, 0xa8, 0x0b, 0xda, 0x4f, 0xcb, 0x93, 0x28,
],
tld: [
0x48, 0x60, 0xeb, 0xcf, 0x7b, 0x9d, 0x24, 0xeb, 0x90, 0x9a, 0x79, 0x53, 0x56, 0xad,
0x32, 0xc9, 0x36, 0xb6, 0x21, 0x65, 0x98, 0x8a, 0x9f, 0xfc, 0xd6, 0x61, 0x70, 0xdb,
0xc5, 0x90, 0xc2, 0x30, 0x10, 0xd7, 0x95, 0x2f, 0xa8, 0x82, 0xd1, 0xbb, 0x79, 0x55,
0x8f, 0x9b, 0xe0, 0xa5, 0x49, 0xd8, 0xd7, 0xa9, 0x4a, 0xe7, 0x20, 0xe5, 0xc0, 0x76,
0x0a, 0x82, 0x5d, 0x47, 0x9f, 0xe6, 0x7a, 0xf5,
],
seht: [
0x92, 0x30, 0x9d, 0x45, 0x89, 0xb9, 0xa8, 0x5b, 0x42, 0x7f, 0x87, 0x53, 0x17, 0x1d,
0x15, 0x20,
],
};
#[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)).unwrap();
assert_eq!(hdr_tags, EXP_HDR);
}
#[test]
fn from_se_image_fail() {
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
let short_hdr = &bin_hdr[1..];
assert!(matches!(
BootHdrTags::from_se_image(&mut Cursor::new(short_hdr)),
Err(Error::InvBootHdr)
));
// mess up magic
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)),
Err(Error::InvBootHdr)
));
//header is at a non expected position
let mut img = vec![0u8; PAGESIZE];
img[0x008..0x288].copy_from_slice(bin_hdr);
assert!(matches!(
BootHdrTags::from_se_image(&mut Cursor::new(img)),
Err(Error::InvBootHdr)
));
}
#[test]
fn from_se_image_img() {
let mut img = vec![0u8; 0x13000];
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
img[0x12000..0x12280].copy_from_slice(bin_hdr);
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(img)).unwrap();
assert_eq!(hdr_tags, EXP_HDR);
}
}

180
rust/pv/src/cli.rs Normal file
View File

@@ -0,0 +1,180 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
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"])),
)]
pub struct CertificateOptions {
/// Use FILE as a host-key document.
///
/// Can be specified multiple times and must be used at least once.
#[arg(
short = 'k',
long = "host-key-document",
value_name = "FILE",
required = true,
value_hint = ValueHint::FilePath,
use_value_delimiter = true,
value_delimiter = ',',
)]
pub host_key_documents: Vec<String>,
/// 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.
#[arg(long)]
pub no_verify: bool,
/// Use FILE as a certificate to verify the host-key(s).
///
/// 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).
#[arg(
short= 'C',
long = "cert",
value_name = "FILE",
alias("crt"),
value_hint = ValueHint::FilePath,
use_value_delimiter = true,
value_delimiter = ',',
)]
pub certs: Vec<String>,
/// Use FILE as a certificate revocation list.
///
/// That 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",
requires("certs"),
value_name = "FILE",
value_hint = ValueHint::FilePath,
use_value_delimiter = true,
value_delimiter = ',',
)]
pub crls: Vec<String>,
/// Make no attempt to download CRLs.
#[arg(long, requires("certs"))]
pub offline: bool,
/// Use FILE as the root-CA certificate for the verification.
///
/// 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>,
}
impl CertificateOptions {
/// Returns the verifier of this [`CertificateOptions`] based on the given CLI options.
///
/// # Errors
///
/// This function will return an error if [`crate::request::HkdVerifier`] cannot be created.
pub fn verifier(&self) -> Result<Box<dyn crate::verify::HkdVerifier>> {
use crate::verify::{CertVerifier, NoVerifyHkd};
match self.no_verify {
true => {
log::warn!(
"Host-key document verification is disabled. The secret may not be protected."
);
Ok(Box::new(NoVerifyHkd))
}
false => Ok(Box::new(CertVerifier::new(
&self.certs,
&self.crls,
&self.root_ca,
self.offline,
)?)),
}
}
}
/// 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)))
}
}
/// 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)))
}
}
#[cfg(test)]
mod test {
use clap::Parser;
use super::*;
#[test]
#[rustfmt::skip]
fn cli_args() {
//Verify only that some arguments are optional, we do not want to test clap, only the
//configuration
let valid_args = [vec!["pgr", "-k", "hkd.crt", "--no-verify"], vec!["pgr", "-k", "hkd.crt", "--crt", "abc.crt"]];
// Test for the minimal amount of flags to yield an invalid combination
let invalid_args = [
vec!["pgr", "-k", "hkd.crt"],
vec!["pgr", "--no-verify", "--crt", "abc.crt"],
vec!["pgr", "--no-verify", "--crt", "abc.crt", "--offline"],
vec!["pgr", "--no-verify", "--crt", "abc.crt", "--crl", "abc.crl"],
vec!["pgr", "--no-verify", "--crt", "abc.crt", "--root-ca", "root.crt"],
vec!["pgr", "--offline"],
vec!["pgr", "--crl", "abc.crl"],
vec!["pgr", "--root-ca", "root.crt"],
];
#[derive(Parser, Debug)]
struct TestParser {
#[command(flatten)]
pub verify_args: CertificateOptions,
}
for arg in valid_args {
let res = TestParser::try_parse_from(&arg);
assert!(res.is_ok());
}
for arg in invalid_args {
let res = TestParser::try_parse_from(&arg);
assert!(res.is_err());
}
}
}

325
rust/pv/src/crypto.rs Normal file
View File

@@ -0,0 +1,325 @@
// SPDX-License-Identifier: MIT
//
// 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_ctx::{HkdfMode, PkeyCtx},
symm::{encrypt, encrypt_aead, Cipher},
};
use std::convert::TryInto;
/// 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]>;
/// Types of symmetric keys, to specify during construction.
///
#[doc = requires_feat!(request)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymKeyType {
/// AES 256 key (32 bytes)
Aes256,
}
/// Types of symmetric keys
///
#[doc = requires_feat!(request)]
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SymKey {
/// AES 256 key (32 bytes)
Aes256(Aes256Key),
}
impl SymKey {
/// Generates a random symmetric key.
///
/// * `key_tp` - type of the symmetric key
///
/// # Errors
///
/// This function will return an error if the Key cannot be generated.
pub fn random(key_tp: SymKeyType) -> Result<Self> {
match key_tp {
SymKeyType::Aes256 => Ok(Self::Aes256(random_array().map(|v| v.into())?)),
}
}
/// Returns a reference to the value of this [`SymKey`].
pub fn value(&self) -> &[u8] {
match self {
Self::Aes256(key) => key.value(),
}
}
}
impl Aes256Key {
/// Generates an AES256 key from an digest (hash).
///
/// # Panics
///
/// Panics if `digset` is not 32 bytes long.
fn from_digest(digest: DigestBytes) -> Self {
let key: [u8; 32] = digest
.as_ref()
.try_into()
.expect("Unexpected OpenSSl Error. Sha256 hash not 32 bytes long");
key.into()
}
}
impl From<Aes256Key> for SymKey {
fn from(value: Aes256Key) -> Self {
Self::Aes256(value)
}
}
/// 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.
pub fn hkdf_rfc_5869<const COUNT: usize>(
md: &MdRef,
ikm: &[u8],
salt: &[u8],
info: &[u8],
) -> Result<[u8; COUNT]> {
let mut ctx = PkeyCtx::new_id(Id::HKDF)?;
ctx.derive_init()?;
ctx.set_hkdf_mode(HkdfMode::EXTRACT_THEN_EXPAND)?;
ctx.set_hkdf_md(md)?;
ctx.set_hkdf_salt(salt)?;
ctx.set_hkdf_key(ikm)?;
ctx.add_hkdf_info(info)?;
let mut res = [0; COUNT];
ctx.derive(Some(&mut res))?;
Ok(res)
}
/// 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.
pub fn derive_key(k1: &PKey<Private>, k2: &PKey<Public>) -> Result<Aes256Key> {
let mut der = Deriver::new(k1)?;
der.set_peer(k2)?;
let mut key = der.derive_to_vec()?;
key.extend([0, 0, 0, 1]);
let secr = Secret::new(key);
Ok(Aes256Key::from_digest(hash(
MessageDigest::sha256(),
secr.value(),
)?))
}
/// Generate a random array.
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if the entropy source fails or is not available.
pub fn random_array<const COUNT: usize>() -> Result<[u8; COUNT]> {
let mut rand = [0; COUNT];
rand_bytes(&mut rand)?;
Ok(rand)
}
/// 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.
pub fn gen_ec_key() -> Result<PKey<Private>> {
let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
let key: EcKey<Private> = EcKey::generate(&group)?;
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
/// * `iv` - initialisation vector
/// * `aad` - additional authentic data
/// * `conf` - data to be encrypted
///
#[doc = requires_feat!(request)]
/// # Returns
/// [`Vec<u8>`] with the following content:
/// 1. `aad`
/// 2. `encr(conf)`
/// 3. `aes gcm tag`
///
/// # 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];
let encr = match key {
SymKey::Aes256(key) => encrypt_aead(
Cipher::aes_256_gcm(),
key.value(),
Some(iv),
aad,
conf,
&mut tag,
)?,
};
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);
Ok(res)
}
/// Calculate the hash of a slice.
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if OpenSSL could not compute the hash.
pub fn hash(t: MessageDigest, data: &[u8]) -> Result<DigestBytes> {
openssl::hash::hash(t, data).map_err(Error::Crypto)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn derive_key() {
let (cust_key, host_key) = get_test_keys();
let exp_key: Aes256Key = [
0x75, 0x32, 0x77, 0x55, 0x8f, 0x3b, 0x60, 0x3, 0x41, 0x9e, 0xf2, 0x49, 0xae, 0x3c,
0x4b, 0x55, 0xaa, 0xd7, 0x7d, 0x9, 0xd9, 0x7f, 0xdd, 0x1f, 0xc8, 0x8f, 0xd8, 0xf0,
0xcf, 0x22, 0xf1, 0x49,
]
.into();
let calc_key = super::derive_key(&cust_key, &host_key).unwrap();
assert_eq!(&calc_key, &exp_key);
}
#[test]
fn hkdf_rfc_5869() {
use openssl::md::Md;
// RFC 6869 test vector 1
let ikm = [0x0bu8; 22];
let salt: [u8; 13] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
];
let info: [u8; 10] = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
let exp: [u8; 42] = [
0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
];
let res: [u8; 42] = super::hkdf_rfc_5869(Md::sha256(), &ikm, &salt, &info).unwrap();
assert_eq!(exp, res);
}
#[test]
fn encrypt_aes_256_gcm() {
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_aad = [
0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec,
0x78, 0xde,
];
let aes_gcm_res = vec![
0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec,
0x78, 0xde, 0xf7, 0x26, 0x44, 0x13, 0xa8, 0x4c, 0x0e, 0x7c, 0xd5, 0x36, 0x86, 0x7e,
0xb9, 0xf2, 0x17, 0x36, 0x67, 0xba, 0x05, 0x10, 0x26, 0x2a, 0xe4, 0x87, 0xd7, 0x37,
0xee, 0x62, 0x98, 0xf7, 0x7e, 0x0c,
];
let res = encrypt_aes_gcm(
&SymKey::Aes256(aes_gcm_key.into()),
&aes_gcm_iv,
&aes_gcm_aad,
&aes_gcm_plain,
)
.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);
}
}

196
rust/pv/src/error.rs Normal file
View File

@@ -0,0 +1,196 @@
// 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("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")]
ManyHostkeys,
#[error("Cannot load {ty} from {path}")]
#[cfg(feature = "request")]
X509Load {
path: String,
ty: &'static str,
source: openssl::error::ErrorStack,
},
#[error("Internal (unexpected) error: {0}, caused by {1}")]
#[cfg(feature = "request")]
InternalSsl(&'static str, #[source] openssl::error::ErrorStack),
#[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,
#[cfg(feature = "uvsecret")]
#[error("Cannot decode secret list")]
InvSecretList(#[source] std::io::Error),
#[cfg(feature = "uvsecret")]
#[error("Input does not contain an add-secret request")]
NoAsrcb,
// errors from other crates
#[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,
}
/// 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,
#[error("No valid CRL found")]
NoCrl,
#[error("Host-key document is revoked.")]
HdkRevoked,
#[error("Not enough bits of security. ({0}, {1} expected)")]
SecurityBits(u32, u32),
#[error("Authority Key Id mismatch")]
Akid,
#[error("CRL has no validity period")]
NoValidityPeriod,
#[error("Specify one IBM Z signing key")]
NoIbmSignKey,
#[error("Specify only one IBM Z signing key")]
ManyIbmSignKeys,
#[error("Before validity period")]
BeforeValidity,
#[error("After validity period")]
AfterValidity,
#[error("Issuer mismatch")]
IssuerMismatch,
#[error("No CRL distribution points found")]
NoCrlDP,
#[error("The IBM Z signing key could not be verified. Error occurred at level {1}")]
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;

206
rust/pv/src/lib.rs Normal file
View File

@@ -0,0 +1,206 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
#![deny(missing_docs)]
//! pv - 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` 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.
mod error;
mod log;
mod utils;
mod uvdevice;
/// Internal macro to conveninetly document required features on items
// #[macro_export]
#[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)]
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::{
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
};
#[cfg(feature = "uvsecret")]
pub use crate::uvsecret::{
secret_list::SecretList,
uvc::{AddCmd, ListCmd, LockCmd},
};
}
/// 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};
}
#[cfg(feature = "request")]
pub use crate::error::HkdVerifyErrorType;
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
/// Functionalities to build UV requests
#[doc = requires_feat!(request)]
pub mod request {
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;
}
}
}
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)
}
}
}
/// Provides cargo version Info about this crate.
///
/// Produces `pv-crate <version>`
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))?,
)
};
}

48
rust/pv/src/log.rs Normal file
View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use log::{self, Level, LevelFilter, Log, Metadata, Record};
/// A simple Logger that prints to stderr if the verbosity level is high enough.
/// Prints log-level for Debug+Trace
#[derive(Clone, Default, Debug)]
pub struct PvLogger;
fn to_level(verbosity: u8) -> LevelFilter {
match verbosity {
// Error and Warn on by default
0 => LevelFilter::Warn,
1 => LevelFilter::Info,
2 => LevelFilter::Debug,
_ => LevelFilter::Trace,
}
}
impl PvLogger {
/// Set self as the logger for this application.
///
/// # Errors
///
/// An error is returned if a logger has already been set.
pub fn start(&'static self, verbosity: u8) -> Result<(), log::SetLoggerError> {
log::set_logger(self).map(|()| log::set_max_level(to_level(verbosity)))
}
}
impl Log for PvLogger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
if record.level() > Level::Info {
eprintln!("{}: {}", record.level(), record.args());
} else {
eprintln!("{}", record.args());
}
}
}
fn flush(&self) {}
}

528
rust/pv/src/req.rs Normal file
View File

@@ -0,0 +1,528 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
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::{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 std::convert::TryInto;
use zerocopy::{AsBytes, BigEndian, FromBytes, U32};
/// Encrypt a _secret_ using self and a given private key.
pub trait Encrypt {
/// Encrypts `secret` using `self` and `priv_key` the encryption.
///
/// # Returns
/// the encrypted data.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt(&self, secret: &[u8], priv_key: &PKey<Private>) -> Result<Vec<u8>> {
let mut res = Vec::with_capacity(80);
self.encrypt_to(secret, priv_key, &mut res)?;
Ok(res)
}
/// Encrypts `secret` using `self` and `priv_key` the encryption.
/// Appends the encrypted data to `to`
///
/// # Returns
/// The encrypted data.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt_to(&self, secret: &[u8], priv_key: &PKey<Private>, to: &mut Vec<u8>) -> Result<()>;
}
/// Types of Authenticated Data
pub enum Aad<'a> {
/// Authenticated Keyslot
Ks(&'a Keyslot),
/// Unchanged authenticated data
Plain(&'a [u8]),
/// Authenticated data that has to be encrypted in beforehand
Encr(&'a dyn Encrypt),
}
/// IBM Z Host key-slot
///
/// Layout in binary format:
/// ```none
/// _______________________________________________________________
/// | Public Host Key Hash (32) |
/// | Wrapped(=Encrypted) Request Protection Key(32) |
/// | Key Slot Tag (16) |
/// |_____________________________________________________________|
/// ```
#[derive(Debug, Clone)]
pub struct Keyslot(PKey<Public>);
impl Keyslot {
/// Size of a host-key hash
pub const PHKH_SIZE: u32 = 0x20;
/// Creates a new Keyslot from the provided public key
pub fn new(hostkey: PKey<Public>) -> Self {
Self(hostkey)
}
}
impl Encrypt for Keyslot {
/// Encrypts the given request protection key `prot_key`.
///
/// The AES256 encryption key is derived from `self` as public key, and `priv_key` as private key.
/// # Returns
/// The encrypted Keyslot.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt_to(
&self,
prot_key: &[u8],
priv_key: &PKey<Private>,
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 phk: EcdhPubkeyCoord = self.0.as_ref().try_into()?;
to.reserve(80);
to.extend_from_slice(&hash(MessageDigest::sha256(), phk.as_ref())?);
to.append(&mut wrpk_and_kst);
Ok(())
}
}
/// Context used to mange the encryption of requests.
/// Intended to be used by [`Request`] implementations
#[derive(Debug)]
pub struct ReqEncrCtx {
iv: [u8; 12],
priv_key: PKey<Private>,
prot_key: SymKey,
}
impl ReqEncrCtx {
/// Create a new encryption context that uses AES256.
///
/// * `iv` - Initialization vector for the request encryption
/// * `priv_key` - Private key to wrap [`Keyslot`]
/// * `prot_key` - Symmetric key for request encryption. Part of [`Keyslot`]
///
/// If an argument is set to `None` a ranom is generated
///
/// # Errors
///
/// This function will return an error if OpenSSL could not generate a random value.
pub fn new_aes_256<I, P, S>(iv: I, priv_key: P, prot_key: S) -> Result<Self>
where
I: Into<Option<[u8; 12]>>,
P: Into<Option<PKey<Private>>>,
S: Into<Option<SymKey>>,
{
let iv = iv.into().unwrap_or(random_array()?);
let priv_key = priv_key.into().unwrap_or(gen_ec_key()?);
let prot_key = prot_key
.into()
.unwrap_or(SymKey::random(SymKeyType::Aes256)?);
Ok(ReqEncrCtx {
iv,
priv_key,
prot_key,
})
}
///
/// Create a new encryption context with random input values.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not generate a random value.
pub fn random(ket_tp: SymKeyType) -> Result<Self> {
match ket_tp {
SymKeyType::Aes256 => Self::new_aes_256(None, None, None),
}
}
///Panics if data does not fit into bin_aad+offs
// #[track_caller]
// pub fn copy_to_bin_aad(_bin_aad: &mut [u8], _aad_offs: usize, _data: &[u8]) {
// todo!();
// }
/// Build the authenticated data for a request.
/// # Returns
/// ```none
/// _______________________________________________________________
/// | MAGIC (8) Version Number (4) Size (4)|
/// | IV (12) Reserved (4)|
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
/// | --------------------------------------------------- |
/// | Request type dependent AAD data |
/// |-------------------------------------------------------------|
/// ```
///
pub fn build_aad<O>(
&self,
version: RequestVersion,
aad: &Vec<Aad>,
encr_size: usize,
magic: O,
) -> Result<Vec<u8>>
where
O: Into<Option<RequestMagic>>,
{
self.build_aad_impl(version, aad, encr_size, magic.into())
}
/// Concrete implementation for [`ReqEncrCtx::build_aad`].
fn build_aad_impl(
&self,
version: RequestVersion,
aad: &Vec<Aad>,
encr_size: usize,
magic: Option<RequestMagic>,
) -> Result<Vec<u8>> {
let nks = aad.iter().filter(|a| matches!(a, Aad::Ks(_))).count();
let nks: u8 = match nks {
0 => Err(Error::NoHostkey),
n if n > u8::MAX as usize => Err(Error::ManyHostkeys),
n => Ok(n as u8),
}?;
let mut auth_data: Vec<u8> = Vec::with_capacity(2048);
//reserve space for the request header
auth_data.resize(std::mem::size_of::<RequestHdr>(), 0);
for a in aad {
match a {
Aad::Plain(p) => auth_data.extend_from_slice(p),
Aad::Ks(ks) => {
ks.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
}
Aad::Encr(e) => {
e.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
}
}
}
let rql = to_u32(auth_data.len() + encr_size + 16)
.ok_or_else(|| 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()))?;
let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic);
// copy request header to the start of the request
auth_data[..std::mem::size_of::<RequestHdr>()].copy_from_slice(req_hdr.as_bytes());
Ok(auth_data)
}
/// get the public coordinates from the private key (Customer private key)
/// # Errors
///
/// This function will return an error if the public key could not be extracted by OpenSSL.
/// Very unlikely.
pub fn key_coords(&self) -> Result<EcdhPubkeyCoord> {
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
/// * `conf` - data to be encrypted
///
/// # Returns
/// [`Vec<u8>`] with the following content:
/// 1. `aad`
/// 2. `encr(conf)`
/// 3. `aes gcm tag`
///
/// # 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>> {
encrypt_aes_gcm(&self.prot_key, &self.iv, aad, conf)
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct EcdhPubkeyCoord([u8; 160]);
impl AsRef<[u8]> for EcdhPubkeyCoord {
fn as_ref(&self) -> &[u8] {
self.0.as_slice()
}
}
/// Get the pub ecdh coordinates in the format the Ultravisor expects it:
/// The two coordinates are pdadded to 80 bytes each.
fn get_pub_ecdh_points(pkey: &EcPointRef, grp: &EcGroupRef) -> Result<[u8; 160], ErrorStack> {
const ECDH_PUB_KEY_COORD_POINT_SIZE: i32 = 0x50;
let mut x = BigNum::new()?;
let mut y = BigNum::new()?;
let mut bn_ctx = BigNumContext::new()?;
pkey.affine_coordinates(grp, &mut x, &mut y, &mut bn_ctx)?;
let mut coord: Vec<u8> = x.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE)?;
coord.append(&mut y.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE)?);
Ok(coord.try_into().unwrap())
}
macro_rules! ecdh_from {
($type: ty) => {
impl TryFrom<&PKeyRef<$type>> for EcdhPubkeyCoord {
type Error = ErrorStack;
fn try_from(key: &PKeyRef<$type>) -> Result<Self, Self::Error> {
let k = key.ec_key()?;
k.check_key()?;
let grp = k.group();
let pub_key = k.public_key();
let coord = get_pub_ecdh_points(pub_key, grp)?;
Ok(EcdhPubkeyCoord(coord))
}
}
};
}
ecdh_from!(Private);
ecdh_from!(Public);
/// Representation of the shared parts of the request header.
/// Used by [`ReqEncrCtx`]
#[repr(C)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
struct RequestHdr {
magic: [u8; 8],
rqvn: U32<BigEndian>,
rql: U32<BigEndian>,
iv: [u8; 12],
reserved1c: [u8; 4],
reserved20: [u8; 7],
nks: u8,
reserved28: u32,
sea: U32<BigEndian>,
}
impl RequestHdr {
fn new(rqvn: u32, rql: u32, iv: [u8; 12], nks: u8, sea: u32, magic: Option<[u8; 8]>) -> Self {
Self {
magic: magic.unwrap_or_default(),
rqvn: rqvn.into(),
rql: rql.into(),
iv,
reserved1c: [0; 4],
reserved20: [0; 7],
nks,
reserved28: 0,
sea: sea.into(),
}
}
}
/// A trait representing a request for the Ultravisor.
///
/// All requests share a few things:
/// * All requests need to be encrypted on a trusted machine
/// * All requests have at least one Hostkeyslot
///
/// The encryption setup is handled by [`ReqEncrCtx`]. Implementers need to pass the data to the
/// `ReqEncrCtx` when implementing `encrypt`. A hostkey should be represented by [`Keyslot`] during
/// encryption.
///
/// An UV request consists of an authenticated area (AAD), an encrypted area (Encr) and a 16 byte tag.
/// The AAD contains a general header and Request type defined data (including Keyslots).
/// It is encrypted with an Request protection key (symmetric). This key is encrypted with a
/// (generated) private key and the public key of the host system (Host key)
/// ```none
/// _______________________________________________________________
/// | MAGIC (8) Version Number (4) Size (4)|
/// | IV (12) Reserved (4)|
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
/// | --------------------------------------------------- |
/// | Request type dependent AAD data |
/// | ---------------------------------------------------- |
/// | Encrypted (request type dependent) data |
/// | ---------------------------------------------------- |
/// | AES GCM Tag (16) |
/// |_____________________________________________________________|
///```
pub trait Request {
/// Encrypt the request into its binary format
///
/// # Errors
///
/// This function will return an error if the encryption fails, the request does not have at
/// least a hostkey, or other implementation dependent contracts are not met.
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>>;
/// Add a host-key to this request
///
/// Must be called at least once, otherwise {`Request::encrypt`} will fail
fn add_hostkey(&mut self, hostkey: PKey<Public>);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::get_test_asset;
use crate::request::SymKey;
use crate::test_utils::*;
use openssl::ec::EcGroup;
use openssl::nid::Nid;
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
#[test]
fn encr_build_aad() {
let (cust_key, host_key) = get_test_keys();
let ks = Keyslot::new(host_key);
let ctx = ReqEncrCtx::new_aes_256(
Some([0x11; 12]),
Some(cust_key),
Some(SymKey::Aes256([0x17; 32].into())),
)
.unwrap();
let v = [0x55; 8];
let aad = Aad::Plain(&v);
let aad = ctx
.build_aad(0x200, &vec![aad, Aad::Ks(&ks)], 16, Some(TEST_MAGIC))
.unwrap();
let mut aad_exp = vec![
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, //progr
0, 0, 2, 0, // vers
0, 0, 0, 168, //size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //res
1, //nks
0, 0, 0, 0, // res
0, 0, 0, 16, // sea
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, //aad
];
aad_exp.extend_from_slice(get_test_asset!("exp/keyslot.bin"));
assert_eq!(&aad, &aad_exp);
}
#[test]
fn encr_build_aad_nks_no() {
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let aad = Vec::<Aad>::new();
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
assert!(matches!(aad, Err(Error::NoHostkey)));
}
#[test]
fn encr_build_aad_nks_many() {
let (_, host_key) = get_test_keys();
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let ks: Vec<Keyslot> = (0..257).map(|_| Keyslot::new(host_key.clone())).collect();
let mut aad = Vec::<Aad>::new();
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
assert!(matches!(aad, Err(Error::ManyHostkeys)));
}
#[test]
fn encr_build_aad_nks() {
let (_, host_key) = get_test_keys();
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let ks = vec![
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)));
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC)).unwrap();
assert_eq!(aad.get(39).unwrap(), &3u8);
}
#[test]
fn req_hdr() {
let hdr = RequestHdr::new(0x200, 22, [0x11; 12], 15, 44, None);
let hdr_bin = hdr.as_bytes();
let hdr_bin_exp = [
0u8, 0, 0, 0, 0, 0, 0, 0, //magic
0, 0, 2, 0, // vers
0, 0, 0, 22, //size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //res
15, //nks
0, 0, 0, 0, // res
0, 0, 0, 44, // sea
];
assert_eq!(hdr_bin, &hdr_bin_exp);
}
#[test]
fn req_hdr2() {
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
let hdr_bin = hdr.as_bytes_mut();
let hdr_bin_exp = [
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, //magic
0, 0, 2, 0, // vers
0, 0, 0x12, 0x34, //size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //res
15, //nks
0, 0, 0, 0, // res
0, 0, 0, 44, // sea
];
assert_eq!(hdr_bin, &hdr_bin_exp);
}
#[test]
fn keyslot() {
let (cust_key, host_key) = get_test_keys();
let exp_keyslot = get_test_asset!("exp/keyslot.bin").to_vec();
let keyslot = Keyslot::new(host_key);
let encr_ks = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap();
assert_eq!(exp_keyslot, encr_ks);
let encr_ks = keyslot.encrypt(&[0x16u8; 32], &cust_key).unwrap();
assert_ne!(exp_keyslot, encr_ks);
}
#[test]
fn get_pub_ecdh_points() {
let (cust_key, _) = get_test_keys();
let pub_key = get_test_asset!("keys/public_cust.bin");
assert_eq!(pub_key.len(), 160);
let points = cust_key.ec_key().unwrap();
let points = points.public_key();
let grp = EcGroup::from_curve_name(Nid::SECP521R1).unwrap();
let points = super::get_pub_ecdh_points(points, &grp).unwrap();
assert_eq!(&points, pub_key);
}
}

117
rust/pv/src/secret.rs Normal file
View File

@@ -0,0 +1,117 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use std::fmt::Debug;
/// Trait for securely zeroizing memory.
///
/// To be used with [`Secret`]
pub trait Zeroize {
/// Reliably overwrites the given buffer with zeros,
fn zeroize(&mut self);
}
/* Automatically impl Zeroize for u8 arrays */
impl<const COUNT: usize> Zeroize for [u8; COUNT] {
/// Reliably overwrites the given buffer with zeros,
/// by performing a volatile write followed by a memory barrier
fn zeroize(&mut self) {
// SAFETY: given buffer(self) has the correct (compile time) size
unsafe { std::ptr::write_volatile(self, [0u8; COUNT]) };
std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
}
}
impl Zeroize for Vec<u8> {
/// Reliably overwrites the given buffer with zeros,
/// by overwriting the whole vector's capacity with zeros.
fn zeroize(&mut self) {
//TODO use `volatile_set_memory` when stabilized
let mut dst = self.as_mut_ptr();
for _ in 0..self.capacity() {
// SAFETY:
// * 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::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
}
}
/// Thin wrapper around an type implementing Zeroize.
///
/// A `Secret` represents a confidential value that must be securely overwritten during drop.
/// Will never leak its wrapped value during [`Debug`]
///
/// ```rust
/// use pv::request::Secret;
/// fn foo(value: Secret<[u8; 2]>) {
/// println!("value: {value:?}");
/// }
/// # fn main() {
/// foo([1,2].into());
/// // prints:
/// // in debug builds:
/// // value: Secret([1, 2])
/// // in release builds:
/// // value: Secret(***)
/// # }
/// ```
#[derive(Clone, PartialEq, Eq, Default)]
pub struct Secret<C: Zeroize>(C);
impl<C: Zeroize> Secret<C> {
/// Convert a type into a self overwriting one.
///
/// Prefer using [`Into`]
pub fn new(v: C) -> Self {
Secret(v)
}
/// Get a reference to the contained value
pub fn value(&self) -> &C {
&self.0
}
/// Get a imutable reference to the contained value
///
/// NOTE that modifications to a mutable reference can trigger reallocation.
/// e.g. a [`Vec`] might expand if more space needed. -> preallocate enough space
/// or operate on slices. The old locations can and will **NOT** be zeroized.
pub fn value_mut(&mut self) -> &mut C {
&mut self.0
}
}
impl<C: Zeroize + Debug> Debug for Secret<C> {
#[allow(unreachable_code)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// do NOT leak secrets in production builds
#[cfg(not(debug_assertions))]
return write!(f, "Secret(***)");
let mut b = f.debug_tuple("Secret");
b.field(&self.0);
b.finish()
}
}
impl<C: Zeroize> From<C> for Secret<C> {
fn from(v: C) -> Secret<C> {
Secret(v)
}
}
impl<C: Zeroize> Zeroize for Secret<C> {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<C: Zeroize> Drop for Secret<C> {
fn drop(&mut self) {
self.0.zeroize();
}
}

120
rust/pv/src/test_utils.rs Normal file
View File

@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// DO NOT USE ANY OF THESE ITEMS IN PRODUCTION CODE
// USED FOR INTERNAL UNIT AND FVT TESTING ONLY!!!
use openssl::{
bn::BigNum,
ec::{EcGroup, EcKey},
error::ErrorStack,
nid::Nid,
pkey::{PKey, Private, Public},
x509::{X509Crl, X509},
};
use std::{
fs,
path::{Path, PathBuf},
};
/// TEST ONLY! Loads the specified asset into the binary at compile time.
///
/// For testing-assets only!
/// The asset must be present at `{crate}/test/assets/{file}`
#[doc(hidden)]
#[macro_export]
macro_rules! get_test_asset {
($file:expr) => {
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/assets/", $file))
};
}
pub fn get_cert_asset_path<P: AsRef<Path>>(path: P) -> PathBuf {
let mut p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.push("tests");
p.push("assets");
p.push("cert");
p.push(path);
println!("CERT path: {}", p.to_str().unwrap());
p
}
pub fn get_cert_asset_path_string(path: &'static str) -> String {
get_cert_asset_path(path)
.into_os_string()
.into_string()
.unwrap()
}
/// TEST ONLY! Load an cert
///
/// panic on errors
pub fn get_cert_asset(path: &'static str) -> Vec<u8> {
let p = get_cert_asset_path(path);
fs::read(p).unwrap()
}
/// TEST ONLY! Load cert found in the asset path
///
/// panic on errors
pub fn load_gen_cert(asset_path: &'static str) -> X509 {
let buf = get_cert_asset(asset_path);
let mut cert = X509::from_der(&buf)
.map(|crt| vec![crt])
.or_else(|_| X509::stack_from_pem(&buf))
.unwrap();
assert_eq!(cert.len(), 1);
cert.pop().unwrap()
}
/// TEST ONLY! Load the crl found in the asset path
///
/// panic on errors
pub fn load_gen_crl(asset_path: &'static str) -> X509Crl {
let buf = get_cert_asset(asset_path);
X509Crl::from_der(&buf)
.or_else(|_| X509Crl::from_pem(&buf))
.unwrap()
}
/// TEST ONLY! Get a fixed private/public pair and a fixed public key
///
/// Intened for TESTING only. All parts of the key including the private key are checked in git and
/// visible for the public
pub fn get_test_keys() -> (PKey<Private>, PKey<Public>) {
let pub_key = get_test_asset!("keys/public_cust.bin");
let priv_key = get_test_asset!("keys/private_cust.bin");
let host_key = get_test_asset!("keys/host.pem.crt");
assert_eq!(pub_key.len(), 160);
assert_eq!(priv_key.len(), 80);
let cust_key = get_keypair(pub_key, priv_key).unwrap();
let host_key = X509::from_pem(host_key).unwrap().public_key().unwrap();
(cust_key, host_key)
}
fn read_ecdh_pubkey(coords: &[u8]) -> Result<PKey<Public>, ErrorStack> {
assert!(coords.len() == 160);
let x = BigNum::from_slice(&coords[..80])?;
let y = BigNum::from_slice(&coords[80..])?;
let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
PKey::from_ec_key(key)
}
fn get_keypair(pub_coords: &[u8], priv_num: &[u8]) -> Result<PKey<Private>, ErrorStack> {
assert!(pub_coords.len() == 160);
assert!(priv_num.len() == 80);
let pub_key = read_ecdh_pubkey(pub_coords)?;
let pub_key = pub_key.ec_key()?;
let pub_key = pub_key.public_key();
let priv_key = BigNum::from_slice(priv_num)?;
let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
let key = EcKey::from_private_components(&group, &priv_key, pub_key)?;
key.check_key()?;
PKey::from_ec_key(key)
}

658
rust/pv/src/utils.rs Normal file
View File

@@ -0,0 +1,658 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{
error::{bail_spec, file_error, path_to_str},
Error, FileIoErrorType, Result,
};
#[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`
/// 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)
.map(|crl| vec![crl])
.or_else(|_| StackableX509Crl::stack_from_pem(buf))
.map_err(Error::Crypto)
}
/// Read all certificates from the buffer and parse them into a vector.
///
/// # 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")]
pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>> {
X509::from_der(buf)
.map(|crt| vec![crt])
.or_else(|_| X509::stack_from_pem(buf))
.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.
///
/// 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.
///
/// 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 std::usize;
use super::*;
#[cfg(feature = "request")]
use crate::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");
let fail = get_cert_asset("ibm.crt");
assert_eq!(super::read_crls(&crl).unwrap().len(), 1);
assert_eq!(super::read_crls(&crl_der).unwrap().len(), 1);
assert_eq!(super::read_crls(&fail).unwrap().len(), 0);
}
#[test]
#[cfg(feature = "request")]
fn read_certs() {
let crt = get_cert_asset("ibm.crt");
let crt_der = get_cert_asset("der.crt");
let fail = get_cert_asset("ibm.crl");
assert_eq!(super::read_certs(&crt).unwrap().len(), 1);
assert_eq!(super::read_certs(&crt_der).unwrap().len(), 1);
assert_eq!(super::read_certs(&fail).unwrap().len(), 0);
}
#[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()
);
}
#[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, &[]));
}
}

208
rust/pv/src/uvdevice.rs Normal file
View File

@@ -0,0 +1,208 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![allow(non_camel_case_types)]
use crate::file_acc_error;
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};
#[cfg(not(test))]
use ::libc::ioctl;
#[cfg(test)]
use test::mock_libc::ioctl;
/// Contains the rust representation of asm/uvdevice.h
/// from kernel version: 6.5 verify
mod ffi;
mod info;
mod test;
pub use ffi::uv_ioctl;
pub use info::UvDeviceInfo;
#[allow(dead_code)] //TODO rm when pv learns attestation
pub type AttestationUserData = [u8; ffi::UVIO_ATT_USER_DATA_LEN];
///Configuration Unique Id of the Secure Execution guest
pub type ConfigUid = [u8; ffi::UVIO_ATT_UID_LEN];
/// Bitflags as used by the Ultravisor in MSB0 ordering
///
/// Wraps an u64 to set/get individual bits
pub type UvFlags = crate::misc::Msb0Flags64;
/// Fire an ioctl.
///
/// # Safety:
/// Raw fd must point to an open file
fn ioctl_raw(raw_fd: RawFd, cmd: c_ulong, cb: &mut IoctlCb) -> Result<()> {
debug!("calling unsafe fn wrapper uv::ioctl_raw with {raw_fd:#x?}, {cmd:#x?}, {cb:?}");
let rc;
// Get the raw pointer and do an ioctl.
//
// SAFETY: the passed pointer points to a valid memory region that
// contains the expected C-struct. The struct outlives this function.
unsafe {
rc = ioctl(raw_fd, cmd, cb.as_ptr_mut());
}
debug!("ioctl resulted with {cb:?}");
match rc {
0 => Ok(()),
//NOTE io::Error handles all errnos ioctl uses
_ => Err(std::io::Error::last_os_error().into()),
}
}
/// Converts UV return codes into human readable error messages
fn rc_fmt<C: UvCmd>(rc: u16, rrc: u16, cmd: &mut C) -> &'static str {
let s = match (rc, rrc) {
(0x0000, _) => Some("invalid rc"),
(0x0002, _) => Some("invalid UV command"),
(0x0005, _) => Some("request has an invalid size"),
(0x0030, _) => Some("home address space control bit has R-bit set to one"),
(0x0031, _) => Some("access exception"),
(0x0032, _) => Some("request contains virtual address translating to an invalid address"),
(UvDevice::RC_MORE_DATA, _) => unreachable!("This is no Error!!!!"),
(UvDevice::RC_SUCCESS, _) => unreachable!("This is no Error!!!!"),
_ => cmd.rc_fmt(rc, rrc),
};
s.unwrap_or("unexpected error-code")
}
/// Ultravisor Command.
pub trait UvCmd {
/// Returns the uvdevice IOCTL command that his command uses.
///
/// # Returns
/// The IOCTL cmd for this UvCmd usually sth like `uv_ioctl!(CMD_NR)`
fn cmd(&self) -> u64;
/// Converts UV return codes into human readable error messages
///
/// no need to handle `0x0000, 0x0001, 0x0002, 0x0005, 0x0030, 0x0031, 0x0032, 0x0100`
fn rc_fmt(&self, rc: u16, rrc: u16) -> Option<&'static str>;
/// Returns data used by this command if available.
fn data(&mut self) -> Option<&mut [u8]> {
None
}
}
/// [`UvDevice`] IOCTL control block.
#[derive(Debug)]
struct IoctlCb(ffi::uvio_ioctl_cb);
impl IoctlCb {
fn new(data: Option<&mut [u8]>) -> Result<Self> {
let (data_raw, data_size) = match data {
Some(data) => (
data.as_mut_ptr(),
data.len()
.try_into()
.map_err(|_| Error::Specification("passed data too large".to_string()))?,
),
None => (std::ptr::null_mut(), 0),
};
Ok(Self(ffi::uvio_ioctl_cb {
flags: 0,
uv_rc: 0,
uv_rrc: 0,
argument_addr: data_raw as u64,
argument_len: data_size,
reserved14: [0; 44],
}))
}
fn rc(&self) -> u16 {
self.0.uv_rc
}
fn rrc(&self) -> u16 {
self.0.uv_rrc
}
fn as_ptr_mut(&mut self) -> *mut ffi::uvio_ioctl_cb {
&mut self.0 as *mut _
}
}
/// The Ultravisor has two codes that represent a successful execution.
/// These are represented by this enum.
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UvcSuccess {
/// Command executed successfully
RC_SUCCESS = UvDevice::RC_SUCCESS,
/// Command executed successfully, but there is more data available and the buffer was to small
/// to hold it all. The returned data is still valid.
RC_MORE_DATA = UvDevice::RC_MORE_DATA,
}
/// The UvDevice is a (virtual) device on s390 machines to send Ultravisor commands from userspace.
pub struct UvDevice(File);
impl UvDevice {
const RC_SUCCESS: u16 = 0x0001;
const RC_MORE_DATA: u16 = 0x0100;
const PATH: &'static str = "/dev/uv";
/// IOCTL number for the info UVC
pub const INFO_NR: u8 = ffi::UVIO_IOCTL_UVDEV_INFO_NR;
/// IOCTL number for the attestation UVC
pub const ATTESTATION_NR: u8 = ffi::UVIO_IOCTL_ATT_NR;
/// IOCTL number for the add secret UVC
pub const ADD_SECRET_NR: u8 = ffi::UVIO_IOCTL_ADD_SECRET_NR;
/// IOCTL number for the list secret UVC
pub const LIST_SECRET_NR: u8 = ffi::UVIO_IOCTL_LIST_SECRETS_NR;
/// IOCTL number for the lock ksecret UVC
pub const LOCK_SECRET_NR: u8 = ffi::UVIO_IOCTL_LOCK_SECRETS_NR;
/// Maximum length for add-secret requests
pub const ADD_SECRET_MAX_LEN: usize = ffi::UVIO_ADD_SECRET_MAX_LEN;
/// Size of the buffer for list secret requests
pub const LIST_SECRETS_LEN: usize = ffi::UVIO_LIST_SECRETS_LEN;
/// Open the uvdevice located at `/dev/uv`
///
/// # Errors
///
/// This function will return an error if the device file cannot be opened.
pub fn open() -> Result<Self> {
Ok(Self(
std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(UvDevice::PATH)
.map_err(|e| file_acc_error!(Open, UvDevice::PATH, e))?,
))
}
/// Send an Ultravisor Command via this uvdevice.
///
/// This works by sending an IOCTL to the uvdevice.
/// # Errors
///
/// This function will return an error if the IOCTL fails or the Ultravisor does not report
/// a success.
/// # Returns
/// [`UvcSuccess`] if the UVC ececuted successfully
pub fn send_cmd<C: UvCmd>(&self, cmd: &mut C) -> Result<UvcSuccess> {
let mut cb = IoctlCb::new(cmd.data())?;
ioctl_raw(self.0.as_raw_fd(), cmd.cmd(), &mut cb)?;
match (cb.rc(), cb.rrc()) {
(Self::RC_SUCCESS, _) => Ok(UvcSuccess::RC_SUCCESS),
(Self::RC_MORE_DATA, _) => Ok(UvcSuccess::RC_MORE_DATA),
(rc, rrc) => Err(Error::Uv {
rc,
rrc,
msg: rc_fmt(rc, rrc, cmd),
}),
}
}
}

128
rust/pv/src/uvdevice/ffi.rs Normal file
View File

@@ -0,0 +1,128 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{assert_size, static_assert};
use zerocopy::{AsBytes, FromBytes};
pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
pub const UVIO_ATT_ADDITIONAL_MAX_LEN: usize = 0x8000;
pub const UVIO_ADD_SECRET_MAX_LEN: usize = 0x100000;
pub const UVIO_LIST_SECRETS_LEN: usize = 0x1000;
// equal to ascii 'u'
pub const UVIO_TYPE_UVC: u8 = 117u8;
pub const UVIO_IOCTL_UVDEV_INFO_NR: u8 = 0;
pub const UVIO_IOCTL_ATT_NR: u8 = 1;
pub const UVIO_IOCTL_ADD_SECRET_NR: u8 = 2;
pub const UVIO_IOCTL_LIST_SECRETS_NR: u8 = 3;
pub const UVIO_IOCTL_LOCK_SECRETS_NR: u8 = 4;
/// Uvdevice IOCTL control block
/// Programs can use this struct to communicate with the uvdevice via IOCTLs
/// `argument_{addr,len}` specifies in/out data depending on the request
///
/// 'uv_rc' and `uv_rrc` are the response and reason response codes from the
/// Ultravisor.
///
/// `flags` is currently unused and to be set zero
///
#[repr(C)]
#[derive(Debug)]
pub struct uvio_ioctl_cb {
pub flags: u32,
pub uv_rc: u16,
pub uv_rrc: u16,
pub argument_addr: u64,
pub argument_len: u32,
pub reserved14: [u8; 44usize],
}
assert_size!(uvio_ioctl_cb, 0x40);
/// Information of supported functions by the uvdevice
///
/// * `supp_uvio_cmds` - supported IOCTLs by this device
/// * `supp_uv_cmds` - supported UVCs corresponding to the IOCTL
///
/// UVIO request to get information about supported request types by this
/// uvdevice and the Ultravisor.
/// Everything is output. Bits are in LSB0 ordering.
/// If the bit is set in both, `supp_uvio_cmds` and `supp_uv_cmds`,
/// the uvdevice and the Ultravisor support that call.
///
/// 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)]
pub struct uvio_uvdev_info {
pub supp_uvio_cmds: u64,
pub supp_uv_cmds: u64,
}
assert_size!(uvio_uvdev_info, 0x10);
pub const UVIO_ATT_USER_DATA_LEN: usize = 0x100;
pub const UVIO_ATT_UID_LEN: usize = 0x10;
/// Request Attestation Measurement control block
///
/// The Attestation Request has two input and two outputs.
/// ARCB and User Data are inputs for the UV.
/// Measurement and Additional Data are outputs generated by UV.
///
/// The Attestation Request Control Block (ARCB) is a cryptographically verified
/// and secured request to UV and User Data is some plaintext data which is
/// going to be included in the Attestation Measurement calculation.
///
/// Measurement is a cryptographic measurement of the callers properties,
/// optional data configured by the ARCB and the user data. If specified by the
/// ARCB, UV will add some Additional Data to the measurement calculation.
/// This Additional Data is then returned as well.
///
/// If the Retrieve Attestation Measurement UV facility is not present,
/// UV will return invalid command rc.
/// Obviously all numbers are in BIG-endian!
#[repr(C)]
#[derive(Debug, AsBytes, FromBytes)]
pub struct uvio_attest {
pub arcb_addr: u64, //in
pub meas_addr: u64, //out
pub add_data_addr: u64, //out
pub user_data: [u8; UVIO_ATT_USER_DATA_LEN], //in
pub config_uid: [u8; UVIO_ATT_UID_LEN], //out
pub arcb_len: u32,
pub meas_len: u32,
pub add_data_len: u32,
pub user_data_len: u16,
pub reserved136: u16,
}
assert_size!(uvio_attest, 0x138);
#[allow(dead_code)] //TODO rm when pv learns attestation
impl uvio_attest {
pub const ARCB_MAX_LEN: usize = UVIO_ATT_ARCB_MAX_LEN;
pub const MEASUREMENT_MAX_LEN: usize = UVIO_ATT_MEASUREMENT_MAX_LEN;
pub const ADDITIONAL_MAX_LEN: usize = UVIO_ATT_ADDITIONAL_MAX_LEN;
}
/// corresponds to the UV_IOCTL macro
pub const fn uv_ioctl(nr: u8) -> u64 {
iowr(UVIO_TYPE_UVC, nr, std::mem::size_of::<uvio_ioctl_cb>())
}
static_assert!(uv_ioctl(UVIO_IOCTL_ATT_NR) == 0xc0407501);
/// corresponds to the __IOWR macro
const fn iowr(ty: u8, nr: u8, size: usize) -> u64 {
// constants and calculation from linux: asm-generic/ioctl.h
const _IOC_WRITE: u32 = 1;
const _IOC_READ: u32 = 2;
const _IOC_NRSHIFT: u32 = 0;
const _IOC_TYPESHIFT: u32 = 8;
const _IOC_SIZESHIFT: u32 = 16;
const _IOC_DIRSHIFT: u32 = 30;
((_IOC_READ | _IOC_WRITE) as u64) << _IOC_DIRSHIFT
| ((ty as u64) << _IOC_TYPESHIFT)
| ((nr as u64) << _IOC_NRSHIFT)
| ((size as u64) << _IOC_SIZESHIFT)
}

View File

@@ -0,0 +1,130 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use super::ffi::uvio_uvdev_info;
use crate::{
misc::{Flags, Lsb0Flags64},
uv::{uv_ioctl, UvCmd, UvDevice},
Result,
};
use std::fmt::Display;
use zerocopy::{AsBytes, FromBytes};
/// Information of supported functions by the uvdevice
///
/// * `supp_uvio_cmds` - supported IOCTLs by this device
/// * `supp_uv_cmds` - supported UVCs corresponding to the IOCTL
///
/// UVIO request to get information about supported request types by this
/// uvdevice and the Ultravisor.
/// Everything is output.
/// If the bit is set in both, `supp_uvio_cmds` and `supp_uv_cmds`,
/// the uvdevice and the Ultravisor support that call.
///
/// Note that bit 0 ([`UvDevice::INFO_NR`]) is always zero for `supp_uv_cmds`
/// as there is no corresponding UV-call.
///
#[derive(Debug)]
pub struct UvDeviceInfo {
supp_uvio_cmds: Lsb0Flags64,
supp_uv_cmds: Option<Lsb0Flags64>,
}
impl UvDeviceInfo {
/// Get information from the uvdevice.
///
/// # Errors
///
/// This function will return an error if the ioctl fails and the error code is not
/// [`libc::ENOTTY`].
/// `ENOTTY` is most likely because the uvdevice does not support the info IOCTL.
/// In that case one can safely assume that the device only supports the Attestation IOCTL.
/// Therefore this is what this function returns IOCTL support for Attestation and _Data not
/// available_ for the UV Attestation facility.
/// To check if the Ultravisor supports the Attestation call check at
/// `/sys/firmware/uv/query/facilities` and check for bit 28 (Msb0 ordering!)
pub fn get(uv: &UvDevice) -> Result<Self> {
let mut cmd = uvio_uvdev_info::new_zeroed();
match uv.send_cmd(&mut cmd) {
Ok(_) => Ok(cmd.into()),
Err(crate::Error::Io(e)) if e.raw_os_error() == Some(libc::ENOTTY) => Ok(Self {
supp_uvio_cmds: (UvDevice::ATTESTATION_NR as u64).into(),
supp_uv_cmds: None,
}),
Err(e) => Err(e),
}
}
}
impl From<uvio_uvdev_info> for UvDeviceInfo {
fn from(value: uvio_uvdev_info) -> Self {
Self {
supp_uvio_cmds: value.supp_uvio_cmds.into(),
supp_uv_cmds: Some(value.supp_uv_cmds.into()),
}
}
}
impl UvCmd for uvio_uvdev_info {
fn cmd(&self) -> u64 {
uv_ioctl(UvDevice::INFO_NR)
}
fn data(&mut self) -> Option<&mut [u8]> {
Some(self.as_bytes_mut())
}
fn rc_fmt(&self, _: u16, _: u16) -> Option<&'static str> {
None
}
}
fn nr_as_string(nr: u8) -> Option<&'static str> {
match nr {
UvDevice::INFO_NR => Some("Info"),
UvDevice::ATTESTATION_NR => Some("Attestation"),
UvDevice::ADD_SECRET_NR => Some("Add Secret"),
UvDevice::LIST_SECRET_NR => Some("List Secrets"),
UvDevice::LOCK_SECRET_NR => Some("Lock Secret Store"),
_ => None,
}
}
fn print_uvdevice_cmd(nr: u8, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match nr_as_string(nr) {
Some(s) => write!(f, "{s}"),
None => write!(f, "Unknown ({nr})"),
}
}
fn parse_flags(uv_cmds: &Lsb0Flags64, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let supp_cmds: Vec<_> = (0u8..64)
.filter(|v| -> bool { uv_cmds.is_set(*v) })
.enumerate()
.collect();
let num_supp_cmds = supp_cmds.len();
if num_supp_cmds == 0 {
println!("None");
return Ok(());
}
for (n, cmd) in supp_cmds {
print_uvdevice_cmd(cmd, f)?;
if n != num_supp_cmds - 1 {
write!(f, ", ")?;
}
}
writeln!(f)
}
impl Display for UvDeviceInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "uvdevice supports:")?;
parse_flags(&self.supp_uvio_cmds, f)?;
writeln!(f, "Ultravisor-calls available:")?;
match &self.supp_uv_cmds {
Some(cmds) => parse_flags(cmds, f),
None => writeln!(f, "Data not available"),
}
}
}

View File

@@ -0,0 +1,231 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![cfg(test)]
use std::{
os::unix::prelude::FromRawFd,
sync::{Mutex, MutexGuard},
};
use super::*;
use lazy_static::lazy_static;
lazy_static! {
/// needed to serialize all tests as tests operate on static data required by the mock
static ref TEST_LOCK: Mutex<()> = Mutex::new(());
/// exists to have a lazy static mod variable
static ref IOCTL_MTX: Mutex<IoctlCtx> = Mutex::new(IoctlCtx::new());
}
fn get_lock<T>(m: &'static Mutex<T>) -> MutexGuard<'static, T> {
match m.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}
struct IoctlCtx {
modify: Box<dyn FnMut(&mut ffi::uvio_ioctl_cb) -> i32 + Send + Sync>,
exp_cmd: ::libc::c_ulong,
called: bool,
}
impl IoctlCtx {
pub fn exp_cmd(&mut self, cmd: ::libc::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,
{
self.modify = Box::new(mdfy);
self
}
pub fn reset(&mut self) -> bool {
let old = self.called;
self.called = false;
old
}
fn new() -> Self {
Self {
modify: Box::new(|_| -1),
exp_cmd: 0,
called: false,
}
}
}
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 {
let mut ctx = get_lock(&IOCTL_MTX);
assert!(!ctx.called, "IOCTL called more than once");
ctx.called = true;
assert_eq!(cmd, ctx.exp_cmd, "IOCTL cmd mismatch");
assert_eq!(fd, 17, "IOCTL fd mismatch");
let data_ref: &mut ffi::uvio_ioctl_cb = &mut *data;
(ctx.modify)(data_ref)
}
}
impl ffi::uvio_ioctl_cb {
fn addr_eq(&self, exp: u64) -> &Self {
assert_eq!(
self.argument_addr, exp,
"ioctl arg addr not eq: {} == {}",
self.argument_addr, exp
);
self
}
fn size_eq(&self, exp: u32) -> &Self {
assert_eq!(
self.argument_len, exp,
"ioctl arg len not eq: {} == {}",
self.argument_len, exp
);
self
}
fn set_rc(&mut self, rc: u16) -> &mut Self {
self.uv_rc = rc;
self
}
fn set_rrc(&mut self, rrc: u16) -> &mut Self {
self.uv_rrc = rrc;
self
}
}
const TEST_CMD: u64 = 17;
struct TestCmd(Option<Vec<u8>>);
impl UvCmd for TestCmd {
fn cmd(&self) -> u64 {
TEST_CMD
}
fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> {
None
}
fn data(&mut self) -> Option<&mut [u8]> {
match &mut self.0 {
None => None,
Some(d) => Some(d.as_mut_slice()),
}
}
}
impl UvDevice {
///use some random fd for `uvdevice` its OK, as the ioctl is mocked and never touches the passed file
fn test_dev() -> Self {
UvDevice(unsafe { std::fs::File::from_raw_fd(17) })
}
}
#[test]
fn ioctl_fail() {
let _m = get_lock(&TEST_LOCK);
let mut mock_cmd = TestCmd(None);
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|_| -1);
let uv = UvDevice::test_dev();
let res = uv.send_cmd(&mut mock_cmd);
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
assert!(matches!(res, Err(Error::Io(_))));
}
#[test]
fn ioctl_simpleo() {
let _m = get_lock(&TEST_LOCK);
let mut mock_cmd = TestCmd(None);
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|cb| {
cb.set_rc(1).addr_eq(0).size_eq(0);
0
});
let uv = UvDevice::test_dev();
let res = uv.send_cmd(&mut mock_cmd);
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
assert!(res.is_ok());
}
#[test]
fn ioctl_simple_err() {
let _m = get_lock(&TEST_LOCK);
let mut mock_cmd = TestCmd(None);
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|cb| {
cb.set_rc(17).set_rrc(3).addr_eq(0).size_eq(0);
0
});
let uv = UvDevice::test_dev();
let res = uv.send_cmd(&mut mock_cmd);
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
assert!(matches!(res, Err(Error::Uv{rc, rrc, ..}) if rc == 17 && rrc == 3 ));
}
#[test]
fn ioctl_write_data() {
let _m = get_lock(&TEST_LOCK);
let cmd_data = vec![0u8; 32];
let cmd_data_len = cmd_data.len();
let data_addr = cmd_data.as_ptr() as u64;
let mut mock_cmd = TestCmd(Some(cmd_data));
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(move |cb| {
cb.set_rc(1).addr_eq(data_addr).size_eq(32);
unsafe {
::libc::memset(cb.argument_addr as *mut ::libc::c_void, 0x42, cmd_data_len);
}
0
});
let uv = UvDevice::test_dev();
let res = uv.send_cmd(&mut mock_cmd);
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
assert_eq!(res.unwrap(), UvcSuccess::RC_SUCCESS);
}
#[test]
fn ioctl_read_data() {
let _m = get_lock(&TEST_LOCK);
let cmd_data = vec![42u8; 32];
let cmd_data_len = cmd_data.len();
let data_addr = cmd_data.as_ptr() as u64;
let data_exp = cmd_data.clone();
let mut mock_cmd = TestCmd(Some(cmd_data));
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(move |cb| {
cb.set_rc(1).addr_eq(data_addr).size_eq(32);
unsafe {
let data = std::slice::from_raw_parts(cb.argument_addr as *const u8, cmd_data_len);
assert_eq!(data, data_exp);
}
0
});
let uv = UvDevice::test_dev();
let res = uv.send_cmd(&mut mock_cmd);
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
assert_eq!(res.unwrap(), UvcSuccess::RC_SUCCESS);
}

80
rust/pv/src/uvsecret.rs Normal file
View File

@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
//
// 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)
}

View File

@@ -0,0 +1,314 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use super::{AddSecretMagic, UserDataType};
use crate::requires_feat;
use crate::{
assert_size,
misc::Flags,
request::{
hkdf_rfc_5869,
openssl::{
pkey::{PKey, Public},
Md,
},
uvsecret::{ExtSecret, GuestSecret},
Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, RequestVersion, Secret,
},
uv::{ConfigUid, UvFlags},
Result,
};
use zerocopy::AsBytes;
/// Internal wrapper for Guest Secret, so that we can dump it in the form the UV wants it to be
#[derive(Debug, Clone)]
struct BinGuestSecret(GuestSecret);
impl BinGuestSecret {
/// Reference to the confidential data
fn confidential(&self) -> &[u8] {
match &self.0 {
GuestSecret::Null => &[],
GuestSecret::Association { secret, .. } => secret.value().as_slice(),
}
}
fn dump_auth(&self) -> Vec<u8> {
match &self.0 {
GuestSecret::Null => vec![0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
GuestSecret::Association { id, .. } => {
let mut buf = vec![0; 48];
buf[3] = 2;
buf[7] = 0x20;
buf[16..48].copy_from_slice(id.as_slice());
buf
}
}
}
}
impl From<GuestSecret> for BinGuestSecret {
fn from(secret: GuestSecret) -> Self {
BinGuestSecret(secret)
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, AsBytes)]
struct ReqAuthData {
flags: UvFlags,
boot_tags: BootHdrTags,
cuid: ConfigUid,
reserved90: [u8; 0x100],
prog_res190: [u8; 0x200],
}
assert_size!(ReqAuthData, 0x3e8);
impl ReqAuthData {
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F) -> Self {
ReqAuthData {
flags: flags.into(),
boot_tags,
cuid: [0; 0x10],
reserved90: [0; 0x100],
prog_res190: [0; 0x200],
}
}
}
#[derive(Debug, Clone)]
struct ReqConfData {
secret: BinGuestSecret,
extension_secret: Secret<[u8; 32]>,
}
impl ReqConfData {
fn to_bytes(&self) -> Secret<Vec<u8>> {
let secret = self.secret.confidential();
let mut v = vec![0; secret.len() + 32];
if !secret.is_empty() {
v[..secret.len()].copy_from_slice(secret);
}
v[secret.len()..32 + secret.len()]
.copy_from_slice(self.extension_secret.value().as_slice());
v.into()
}
}
/// Flags for [`AddSecretRequest`]
///
#[doc = requires_feat!(reqsecret)]
#[derive(Default, Clone, Copy, Debug)]
pub struct AddSecretFlags(UvFlags);
impl AddSecretFlags {
/// Enables the disable-dump flag
///
/// After the request was dispatched successfully,
/// the UV will not provide any dump decryption information for the SE-guest anymore.
pub fn set_disable_dump(&mut self) {
self.0.set_bit(0)
}
}
impl From<&u64> for AddSecretFlags {
fn from(v: &u64) -> Self {
Self(v.into())
}
}
impl From<AddSecretFlags> for UvFlags {
fn from(f: AddSecretFlags) -> Self {
f.0
}
}
/// Versions for [`AddSecretRequest`]
///
#[doc = requires_feat!(reqsecret)]
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddSecretVersion {
/// Version 1 (= 0x0100)
One = 0x0100,
#[cfg(not(doc))]
#[cfg(any(debug_assertions, test))]
/// Only for testing
Inv = 0,
}
impl From<AddSecretVersion> for RequestVersion {
fn from(val: AddSecretVersion) -> Self {
val as 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.
///
/// Layout:
///```none
/// _______________________________________________________________
/// | generic header (48)
/// | --------------------------------------------------- |
/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) |
/// | Configuration unique ID(16) (Attestation) |
/// | Optional, defaults to 0 |
/// | Reserved(256) |
/// | User Data(512) (reserved) |
/// | Customer Public Key (160) generated for each request |
/// | N Keyslots(80 each) |
/// | Secret header (Secret dependent) |
/// | --------------------------------------------------- |
/// | Secret to add (Secret type dependent)(may be 0 bytes) | Encrypted
/// | Extension secret(32) Optional, defaults to 0 | Encrypted
/// | --------------------------------------------------- |
/// | 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,
}
impl AddSecretRequest {
/// Create a new add-secret request.
///
/// The request has no extension secret, no configuration UID, no host-keys,
/// and no user data
///
pub fn new(
version: AddSecretVersion,
secret: GuestSecret,
boot_tags: BootHdrTags,
flags: AddSecretFlags,
) -> Self {
AddSecretRequest {
conf: ReqConfData {
extension_secret: Secret::new([0; 32]),
secret: secret.into(),
},
aad: ReqAuthData::new(boot_tags, flags),
keyslots: vec![],
version,
magic: UserDataType::Null.into(),
}
}
/// Sets the Configuration Unique Id of this [`AddSecretRequest`].
pub fn set_cuid(&mut self, cuid: ConfigUid) {
self.aad.cuid = cuid;
}
/// Sets the extension secret of this [`AddSecretRequest`].
///
/// # Errors
///
/// This function will return an error if the key derivation fails for a [`ExtSecret::Derived`].
pub fn set_ext_secret(&mut self, ext_secret: ExtSecret) -> Result<()> {
const DER_EXT_SECRET_INFO: &[u8] = "IBM Z Ultravisor Add-Secret".as_bytes();
self.conf.extension_secret = match ext_secret {
ExtSecret::Simple(s) => s,
ExtSecret::Derived(cck) => hkdf_rfc_5869(
Md::sha512(),
cck.value(),
self.aad.boot_tags.seht(),
DER_EXT_SECRET_INFO,
)?
.into(),
};
Ok(())
}
/// Returns a reference to the guest secret of this [`AddSecretRequest`].
pub fn guest_secret(&self) -> &GuestSecret {
&self.conf.secret.0
}
/// 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 mut aad: Vec<Aad> = Vec::with_capacity(3 + self.keyslots.len());
aad.push(Aad::Plain(self.aad.as_bytes()));
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())
}
#[doc(hidden)]
#[cfg(any(debug_assertions, test))]
pub fn aad_and_conf(&self, ctx: &ReqEncrCtx) -> Result<(Vec<u8>, Vec<u8>)> {
let conf = self.conf.to_bytes();
let aad = self.aad(ctx, conf.value().len())?;
Ok((aad, conf.value().to_owned()))
}
#[doc(hidden)]
#[cfg(any(debug_assertions, test))]
pub fn no_encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>> {
let (mut res, mut conf) = self.aad_and_conf(ctx)?;
res.append(&mut conf);
res.append(&mut vec![0x24; 32]);
Ok(res)
}
}
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())
}
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
self.keyslots.push(Keyslot::new(hostkey))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn guest_secret_bin_null() {
let gs: BinGuestSecret = GuestSecret::Null.into();
let gs_bytes = gs.dump_auth();
let exp = vec![0u8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(exp, gs_bytes);
assert_eq!(&Vec::<u8>::new(), gs.confidential())
}
#[test]
fn guest_secret_bin_ap() {
let gs: BinGuestSecret = GuestSecret::Association {
name: "test".to_string(),
id: [1; 32],
secret: [2; 32].into(),
}
.into();
let gs_bytes_auth = gs.dump_auth();
let mut exp = vec![0u8, 0, 0, 2, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
exp.extend([1; 32]);
assert_eq!(exp, gs_bytes_auth);
assert_eq!(&[2; 32], gs.confidential());
}
}

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{request::Secret, requires_feat};
/// 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
Simple(Secret<[u8; 32]>), // contains the secret
/// A secret that is derived from the Customer communication key from the SE-header
Derived(Secret<[u8; 32]>), // contains the cck
}

View File

@@ -0,0 +1,146 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#[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,
};
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
Null,
/// Association secret used to associate an extension card to a SE guest
///
/// Create Associations using [`GuestSecret::association`]
Association {
/// Name of the secret
name: String,
#[serde(serialize_with = "ser_gsid", deserialize_with = "de_gsid")]
/// SHA256 hash of [`GuestSecret::Association::name`]
id: [u8; SECRET_ID_SIZE],
/// Confidential actual assocuiation secret (32 bytes)
#[serde(skip)]
secret: Secret<[u8; SECRET_SIZE]>,
},
}
impl GuestSecret {
/// Create a new [`GuestSecret::Association`].
///
/// * `name` - Name of the secret. Will be hashed into a 32 byte id
/// * `secret` - Value of the secret. Ranom if [`Option::None`]
///
/// # Errors
///
/// This function will return an error if OpenSSL cannot create a hash.
pub fn association<O>(name: &str, secret: O) -> Result<GuestSecret>
where
O: Into<Option<[u8; SECRET_SIZE]>>,
{
let id = hash(MessageDigest::sha256(), name.as_bytes())?.to_vec();
let secret = match secret.into() {
Some(s) => s,
None => random_array()?,
};
Ok(GuestSecret::Association {
name: name.to_string(),
id: id.try_into().unwrap(),
secret: secret.into(),
})
}
}
fn de_gsid<'de, D>(de: D) -> Result<[u8; 32], D::Error>
where
D: serde::Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> serde::de::Visitor<'de> for FieldVisitor {
type Value = [u8; SECRET_ID_SIZE];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a `32 bytes long hexstring` prepended with 0x")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if s.len() != SECRET_ID_SIZE * 2 + 2 {
return Err(serde::de::Error::invalid_length(s.len(), &self));
}
let nb = s.strip_prefix("0x").ok_or_else(|| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)
})?;
crate::misc::parse_hex(nb)
.try_into()
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self))
}
}
de.deserialize_identifier(FieldVisitor)
}
#[cfg(test)]
mod test {
use super::*;
use serde_test::{assert_tokens, Token};
//todo test GuestSecret::association
#[test]
fn association() {
let secret_value = [0x11; 32];
let exp_id = [
0x75, 0xad, 0x01, 0xb4, 0x03, 0xa9, 0xe4, 0x59, 0x5d, 0xf0, 0x7a, 0xce, 0x38, 0x12,
0x97, 0x99, 0xdd, 0xad, 0x90, 0x8a, 0x8f, 0x82, 0xf9, 0xc3, 0x2c, 0xdd, 0x7d, 0x53,
0xef, 0xc7, 0x3c, 0x62,
];
let name = "association secret".to_string();
let secret = GuestSecret::association("association secret", secret_value).unwrap();
let exp = GuestSecret::Association {
name,
id: exp_id,
secret: secret_value.into(),
};
assert_eq!(secret, exp);
}
#[test]
fn ap_asc_parse() {
let id = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
0x89, 0xab, 0xcd, 0xef,
];
let asc = GuestSecret::Association {
name: "test123".to_string(),
id,
secret: [0; 32].into(),
};
assert_tokens(
&asc,
&[
Token::StructVariant {
name: "GuestSecret",
variant: "Association",
len: 2,
},
Token::String("name"),
Token::String("test123"),
Token::String("id"),
Token::String("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
Token::StructVariantEnd,
],
);
}
}

View File

@@ -0,0 +1,224 @@
// 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, "{b:02x}")?;
}
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);
}
}

124
rust/pv/src/uvsecret/uvc.rs Normal file
View File

@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use super::AddSecretMagic;
use crate::{
request::MagicValue,
requires_feat,
uv::{uv_ioctl, UvCmd, UvDevice},
Error, Result, PAGESIZE,
};
use std::io::Read;
use std::usize;
/// _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.
///
#[doc = requires_feat!(uvsecret)]
pub struct ListCmd(Vec<u8>);
impl ListCmd {
fn with_size(size: usize) -> Self {
Self(vec![0; size])
}
}
impl Default for ListCmd {
fn default() -> Self {
Self::with_size(PAGESIZE)
}
}
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.
///
#[doc = requires_feat!(uvsecret)]
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.
///
#[doc = requires_feat!(uvsecret)]
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,
}
}
}

158
rust/pv/src/verify.rs Normal file
View File

@@ -0,0 +1,158 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use core::slice;
use log::debug;
use openssl::stack::Stack;
use openssl::x509::store::X509Store;
use openssl::x509::{CrlStatus, X509Ref, X509StoreContext, X509};
use openssl_extensions::crl::StackableX509Crl;
use openssl_extensions::crl::X509StoreContextExtension;
use crate::error::bail_hkd_verify;
use crate::misc::{read_certs, read_file};
use crate::Result;
mod helper;
mod test;
/// A HkdVerifier verifies that a host-key document(HKD) can be trusted.
///
/// If the verification fails the HKD should not be used to create requests.
pub trait HkdVerifier {
/// Checks if the given host-key document can be trusted.
///
/// #Errors
///
/// This function will return an error if the Hostkey cannot be trusted.
/// Refer to the concrete Error type for the specific reason.
fn verify(&self, hkd: &X509Ref) -> Result<()>;
}
/// A "verifier" that does not verify and accepts all given host-keys as valid.
pub struct NoVerifyHkd;
impl HkdVerifier for NoVerifyHkd {
fn verify(&self, _hkd: &X509Ref) -> Result<()> {
Ok(())
}
}
/// A Verifier that checks the host-key document against a chain of trust.
pub struct CertVerifier {
store: X509Store,
ibm_z_sign_key: X509,
offline: bool,
}
impl HkdVerifier for CertVerifier {
/// This function verifies a host-key
/// document. To do so multiple steps are required:
///
/// 1. issuer(host_key) == subject(ibm_z_sign_key)
/// 2. Signature verification
/// 3. @hkd must not be expired
/// 4. @hkd must not be revoked
fn verify(&self, hkd: &X509Ref) -> Result<()> {
helper::verify_hkd_options(hkd, &self.ibm_z_sign_key)?;
// verify that the hkd was signed with the key of the IBM signing key
if !hkd.verify(self.ibm_z_sign_key.public_key()?.as_ref())? {
bail_hkd_verify!(Signature);
}
// Find matching crl for sign key in the store or download them
let crls = self.hkd_crls(hkd)?;
// Verify that the CLRs are still valid
let mut verified_crls = Vec::with_capacity(crls.len());
for crl in &crls {
if helper::verify_crl(crl, &self.ibm_z_sign_key).is_some() {
verified_crls.push(crl.to_owned());
}
}
// Test if hkd was revoked (min1 required)
if verified_crls.is_empty() {
bail_hkd_verify!(NoCrl);
}
for crl in &verified_crls {
match crl.get_by_cert(&hkd.to_owned()) {
CrlStatus::NotRevoked => (),
_ => bail_hkd_verify!(HdkRevoked),
}
}
debug!("HKD: verified");
Ok(())
}
}
impl CertVerifier {
///Download the CLRs that a HKD refers to.
pub fn hkd_crls(&self, hkd: &X509Ref) -> Result<Stack<StackableX509Crl>> {
let mut ctx = X509StoreContext::new()?;
// Unfortunately we cannot use a dedicated function here and have to use a closure (E0434)
// Otherwise, we cannot refer to self
let mut crls = ctx.init_opt(&self.store, None, None, |ctx| {
let subject = self.ibm_z_sign_key.subject_name();
match ctx.crls(subject) {
Ok(crls) => Ok(crls),
_ => {
// reorder the name and try again
let broken_subj = helper::reorder_x509_names(subject)?;
ctx.crls(&broken_subj).or_else(helper::stack_err_hlp)
}
}
})?;
if !self.offline {
// Try to download a CRL if defined in the HKD
if let Some(crl) = helper::download_first_crl_from_x509(hkd)? {
crl.into_iter().try_for_each(|c| crls.push(c.into()))?;
}
}
Ok(crls)
}
}
impl CertVerifier {
/// Create a `CertVerifier`.
///
/// * `cert_paths` - Paths to Cerificates for the chain of trust
/// * `crl_paths` - Paths to certificate revocation lists for the chain of trust
/// * `root_ca_path` - Path to the root of trust
/// * `offline` - if set to true the verification process will not try to download CRLs from the
/// internet.
/// # Errors
///
/// This function will return an error if the chain of trust could not be established.
pub fn new(
cert_paths: &[String],
crl_paths: &[String],
root_ca_path: &Option<String>,
offline: bool,
) -> Result<Self> {
let mut store = helper::store_setup(root_ca_path, crl_paths, cert_paths)?;
let mut untr_certs = Vec::with_capacity(cert_paths.len());
for path in cert_paths {
let mut crt = read_certs(&read_file(path, "certificate")?)?;
if !offline {
helper::download_crls_into_store(&mut store, &crt)?;
}
untr_certs.append(&mut crt);
}
// remove the IBM signing certificate from chain.
// We have to verify them separately as they are not marked as intermediate certs
let (ibm_z_sign_key, chain) = helper::extract_ibm_sign_key(untr_certs)?;
let store = store.build();
helper::verify_chain(&store, &chain, slice::from_ref(&ibm_z_sign_key))?;
Ok(Self {
store,
ibm_z_sign_key,
offline,
})
}
}

View File

@@ -0,0 +1,518 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::error::bail_hkd_verify;
use crate::misc::{memeq, 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},
error::ErrorStack,
nid::Nid,
ssl::SslFiletype,
stack::{Stack, Stackable},
x509::{
store::{File, X509Lookup, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef},
verify::{X509VerifyFlags, X509VerifyParam},
X509Crl, X509CrlRef, X509Name, X509NameRef, X509PurposeId, X509Ref, X509StoreContext,
X509StoreContextRef, X509VerifyResult, X509,
},
};
use openssl_extensions::{
akid::{AkidCheckResult, AkidExtension},
crl::X509StoreExtension,
};
use std::cmp::Ordering;
use std::time::Duration;
use std::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
/// for details).
///
const SECURITY_LEVEL: usize = 2;
const SECURITY_BITS_ARRAY: [u32; 6] = [0, 80, 112, 128, 192, 256];
const SECURITY_BITS: u32 = SECURITY_BITS_ARRAY[SECURITY_LEVEL];
const SECURITY_CHAIN_MAX_LEN: c_int = 2;
/// verifies that the HKD
/// * has enough security bits
/// * is inside its validity period
/// * issuer name is the subject name of the [`sign_key`]
/// * the Authority Key ID matches the Signing Key ID of the [`sign_key`]
pub fn verify_hkd_options(hkd: &X509Ref, sign_key: &X509Ref) -> Result<()> {
let hk_pkey = hkd.public_key()?;
let security_bits = hk_pkey.security_bits();
if SECURITY_BITS > 0 && SECURITY_BITS > security_bits {
return Err(Error::HkdVerify(SecurityBits(security_bits, SECURITY_BITS)));
}
// TODO rust-openssl fix X509::not.after/before() impl to return Option& not panic on nullptr from C?
//try_... rust-openssl
// verify that the hkd is still valid
check_validity_period(hkd.not_before(), hkd.not_after())?;
// check if hkd.issuer_name == issuer.subject
check_x509_name_equal(sign_key.subject_name(), hkd.issuer_name())?;
// verify that the AKID of the hkd matches the SKID of the issuer
if let Some(akid) = hkd.akid() {
if akid.check(sign_key) != AkidCheckResult::OK {
bail_hkd_verify!(Akid);
}
}
Ok(())
}
pub fn verify_crl(crl: &X509CrlRef, issuer: &X509Ref) -> Option<()> {
let last = crl.last_update();
let next = crl.next_update()?;
check_validity_period(last, next).ok()?;
if let Some(akid) = crl.akid() {
if akid.check(issuer) != AkidCheckResult::OK {
return None;
}
}
check_x509_name_equal(crl.issuer_name(), issuer.subject_name()).ok()?;
match crl.verify(issuer.public_key().ok()?.as_ref()).ok()? {
true => Some(()),
false => None,
}
}
/// Setup the x509Store such that it can be used it for verifying certificates
pub fn store_setup(
root_ca_path: &Option<String>,
crl_paths: &[String],
cert_w_crl_paths: &[String],
) -> Result<X509StoreBuilder> {
let mut x509store = X509StoreBuilder::new()?;
match root_ca_path {
None => x509store.set_default_paths()?,
Some(p) => load_root_ca(p, &mut x509store)?,
}
for crl in crl_paths {
load_crl_to_store(&mut x509store, crl, true).map_err(|source| Error::X509Load {
path: crl.to_owned(),
ty: Error::CRL,
source,
})?;
}
for crl in cert_w_crl_paths {
load_crl_to_store(&mut x509store, crl, false).map_err(|source| Error::X509Load {
path: crl.to_owned(),
ty: Error::CRL,
source,
})?;
}
let mut param = X509VerifyParam::new()?;
let flags = X509VerifyFlags::X509_STRICT
| X509VerifyFlags::CRL_CHECK
| X509VerifyFlags::CRL_CHECK_ALL
| X509VerifyFlags::TRUSTED_FIRST
| X509VerifyFlags::CHECK_SS_SIGNATURE
| X509VerifyFlags::POLICY_CHECK;
param.set_depth(SECURITY_CHAIN_MAX_LEN);
param.set_auth_level(SECURITY_LEVEL as i32);
param.set_purpose(X509PurposeId::ANY)?;
param.set_flags(flags)?;
x509store.set_param(&param)?;
Ok(x509store)
}
/// Verify that the given IBM signing keys can be trusted
/// -> check the chain: IBMsignKey<-InterCA(s)<-RootCA
pub fn verify_chain(
store: &X509StoreRef,
untrusted_certs: &Stack<X509>,
sign_keys: &[X509],
) -> Result<()> {
fn verify_fun(ctx: &mut X509StoreContextRef) -> std::result::Result<bool, ErrorStack> {
// verify certificate
let res = ctx.verify_cert()?;
if !res {
debug!("Failed to verify the singing key with the chain of trust");
return Ok(res);
}
// verify that the chain is as expected
let chain = match ctx.chain() {
Some(c) => c,
None => {
debug!("No verification chain in verify-context. (openssl BUG)");
ctx.set_error(X509VerifyResult::APPLICATION_VERIFICATION);
return Ok(false);
}
};
if chain.len() < SECURITY_CHAIN_MAX_LEN as usize {
debug!("Verification expects one root and at least one intermediate certificate",);
ctx.set_error(X509VerifyResult::APPLICATION_VERIFICATION);
Ok(false)
} else {
Ok(true)
}
}
let mut store_ctx = X509StoreContext::new()?;
for sign_key in sign_keys {
// (rust)OpenSSL should not error out on `X509_verify_cert`\
// (Internal (probably unrecoverable) error like OOM)
if !store_ctx
.init(store, sign_key, untrusted_certs, verify_fun)
.map_err(|e| Error::InternalSsl("The IBM Z signing key could not be verified.", e))?
{
return Err(Error::HkdVerify(IbmSignInvalid(
store_ctx.error(),
store_ctx.error_depth(),
)));
}
}
Ok(())
}
/// Consumes and splits the given vector into a single IBM Z signing key and other certificates
///
/// Error if not exactly one IBM Z signing key available
pub fn extract_ibm_sign_key(certs: Vec<X509>) -> Result<(X509, Stack<X509>)> {
let ibm_z_sign_key = get_ibm_z_sign_key(&certs)?;
let mut chain = Stack::<X509>::new()?;
for x in certs.into_iter().filter(|x| !is_ibm_signing_cert(x)) {
chain.push(x)?;
}
Ok((ibm_z_sign_key, chain))
}
/// for all certs load the first CRL specified into our store
pub fn download_crls_into_store(store: &mut X509StoreBuilderRef, crts: &[X509]) -> Result<()> {
for crt in crts {
debug!("Download crls for {crt:?}");
if let Some(crl) = download_first_crl_from_x509(crt)? {
crl.iter().try_for_each(|c| store.add_crl(c))?;
}
}
Ok(())
}
// Name Entry values of an IBM Z key signing cert
//Asn1StringRef::as_slice aka ASN1_STRING_get0_data gives a string without \0 delimiter
const IBM_Z_COMMON_NAME: &[u8; 43usize] = b"International Business Machines Corporation";
const IBM_Z_COUNTRY_NAME: &[u8; 2usize] = b"US";
const IBM_Z_LOCALITY_NAME: &[u8; 12usize] = b"Poughkeepsie";
const IBM_Z_ORGANIZATIONAL_UNIT_NAME_SUFFIX: &str = "Key Signing Service";
const IBM_Z_ORGANIZATION_NAME: &[u8; 43usize] = b"International Business Machines Corporation";
const IBM_Z_STATE: &[u8; 8usize] = b"New York";
const IMB_Z_ENTRY_COUNT: usize = 6;
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),
}
}
fn is_ibm_signing_cert(cert: &X509) -> bool {
let subj = cert.subject_name();
if subj.entries().count() != IMB_Z_ENTRY_COUNT
|| !name_data_eq(subj, Nid::COUNTRYNAME, IBM_Z_COUNTRY_NAME)
|| !name_data_eq(subj, Nid::STATEORPROVINCENAME, IBM_Z_STATE)
|| !name_data_eq(subj, Nid::LOCALITYNAME, IBM_Z_LOCALITY_NAME)
|| !name_data_eq(subj, Nid::ORGANIZATIONNAME, IBM_Z_ORGANIZATION_NAME)
|| !name_data_eq(subj, Nid::COMMONNAME, IBM_Z_COMMON_NAME)
{
return false;
}
return match subj.entries_by_nid(Nid::ORGANIZATIONALUNITNAME).next() {
None => false,
Some(entry) => match entry.data().as_utf8() {
Err(_) => false,
Ok(s) => s
.as_bytes()
.ends_with(IBM_Z_ORGANIZATIONAL_UNIT_NAME_SUFFIX.as_bytes()),
},
};
}
fn get_ibm_z_sign_key(certs: &[X509]) -> Result<X509> {
let mut ibm_sign_keys = certs.iter().filter(|x| is_ibm_signing_cert(x)).cloned();
match ibm_sign_keys.next() {
None => bail_hkd_verify!(NoIbmSignKey),
Some(k) => match ibm_sign_keys.next() {
None => Ok(k),
Some(_) => bail_hkd_verify!(ManyIbmSignKeys),
},
}
}
fn load_root_ca(path: &str, x509_store: &mut X509StoreBuilder) -> Result<()> {
let lu = x509_store.add_lookup(X509Lookup::<File>::file())?;
// Try to load cert as PEM file
match lu.load_cert_file(path, SslFiletype::PEM) {
Ok(_) => lu
.load_crl_file(path, SslFiletype::PEM)
.map(|_| ())
.or(Ok(())),
// Not a PEM file? try ASN1
Err(_) => lu
.load_cert_file(path, SslFiletype::ASN1)
.map(|_| ())
.map_err(|source| Error::X509Load {
path: path.to_string(),
ty: Error::CERT,
source,
}),
}
}
fn load_crl_to_store(
x509_store: &mut X509StoreBuilder,
path: &str,
err_out_empty_crl: bool,
) -> std::result::Result<(), openssl::error::ErrorStack> {
let lu = x509_store.add_lookup(X509Lookup::<File>::file())?;
// Try to load cert as PEM file
if lu.load_crl_file(path, SslFiletype::PEM).is_err() {
// Not a PEM file? try read as ASN1
let res = lu.load_crl_file(path, SslFiletype::ASN1);
if err_out_empty_crl {
res?;
}
}
Ok(())
}
///Run through the forest of the distribution points and find them
pub fn x509_dist_points(cert: &X509Ref) -> Vec<String> {
let mut res = Vec::<String>::with_capacity(1);
let dps = match cert.crl_distribution_points() {
Some(d) => d,
None => return res,
};
for dp in dps {
let dp_nm = match dp.distpoint() {
Some(nm) => nm,
None => continue,
};
let dp_gns = match dp_nm.fullname() {
Some(gns) => gns,
None => continue,
};
for dp_gn in dp_gns {
match dp_gn.uri() {
Some(uri) => res.push(uri.to_string()),
None => continue,
};
}
}
res
}
const CRL_TIMEOUT_MAX: Duration = Duration::from_secs(3);
/// Searches for CRL Distribution points and downloads the CRL. Stops after the first successful
/// download.
///
/// Error if sth bad(=unexpected) happens (not bad: crl not available at link, unexpected format)
/// Other issues are mapped to Ok(None)
pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<X509Crl>>> {
struct Buf(Vec<u8>);
impl Handler for Buf {
fn write(&mut self, data: &[u8]) -> std::result::Result<usize, WriteError> {
self.0.extend_from_slice(data);
Ok(data.len())
}
}
for dist_point in x509_dist_points(cert) {
// A typical certificate is about 1200 bytes long
let mut handle = Easy2::new(Buf(Vec::with_capacity(1500)));
handle.url(&dist_point)?;
handle.get(true)?;
handle.follow_location(true)?;
handle.timeout(CRL_TIMEOUT_MAX)?;
handle.useragent("s390-tools-pv-crl")?;
if handle.perform().is_err() {
continue;
}
match read_crls(&handle.get_ref().0) {
Err(_) => continue,
Ok(crl) => return Ok(Some(crl)),
}
}
Ok(None)
}
fn check_validity_period(not_before: &Asn1TimeRef, not_after: &Asn1TimeRef) -> Result<()> {
let now = Asn1Time::days_from_now(0)?;
if let Ordering::Less = now.compare(not_before)? {
bail_hkd_verify!(BeforeValidity);
}
match now.compare(not_after)? {
Ordering::Less => Ok(()),
_ => bail_hkd_verify!(AfterValidity),
}
}
fn check_x509_name_equal(lhs: &X509NameRef, rhs: &X509NameRef) -> Result<()> {
if lhs.entries().count() != rhs.entries().count() {
bail_hkd_verify!(IssuerMismatch);
}
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())) {
bail_hkd_verify!(IssuerMismatch);
}
}
Ok(())
}
const NIDS_CORRECT_ORDER: [Nid; 6] = [
Nid::COUNTRYNAME,
Nid::ORGANIZATIONNAME,
Nid::ORGANIZATIONALUNITNAME,
Nid::LOCALITYNAME,
Nid::STATEORPROVINCENAME,
Nid::COMMONNAME,
];
/**
* Workaround to fix the mismatch between issuer name of the
* IBM Z signing CRLs and the IBM Z signing key subject name.
*/
pub fn reorder_x509_names(subject: &X509NameRef) -> std::result::Result<X509Name, ErrorStack> {
let mut correct_subj = X509Name::builder()?;
for nid in NIDS_CORRECT_ORDER {
if let Some(name) = subject.entries_by_nid(nid).next() {
correct_subj.append_entry(name)?;
}
}
Ok(correct_subj.build())
}
pub fn stack_err_hlp<T: Stackable>(
e: ErrorStack,
) -> std::result::Result<Stack<T>, openssl::error::ErrorStack> {
match e.errors().len() {
0 => Stack::<T>::new(),
_ => Err(e),
}
}
#[cfg(test)]
/// tests for some private functions
mod test {
use openssl_extensions::x509_crl_eq;
use super::*;
use crate::test_utils::*;
use std::time::{Duration, SystemTime};
fn sys_to_asn1_time(syst: SystemTime) -> Asn1Time {
let secs = syst
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
Asn1Time::from_unix(secs as i64).unwrap()
}
#[test]
fn check_validity_period() {
let day = Duration::from_secs(60 * 60 * 24);
let yesterday = sys_to_asn1_time(SystemTime::now() - day);
let tomorrow = sys_to_asn1_time(SystemTime::now() + day);
assert!(super::check_validity_period(&yesterday, &tomorrow).is_ok());
assert!(matches!(
super::check_validity_period(&tomorrow, &tomorrow),
Err(Error::HkdVerify(BeforeValidity))
));
assert!(matches!(
super::check_validity_period(&yesterday, &yesterday),
Err(Error::HkdVerify(AfterValidity))
));
}
#[test]
fn x509_name_equal() {
let sign_crt = load_gen_cert("ibm.crt");
let hkd = load_gen_cert("host.crt");
let other = load_gen_cert("inter_ca.crt");
assert!(super::check_x509_name_equal(sign_crt.subject_name(), hkd.issuer_name()).is_ok(),);
assert!(matches!(
super::check_x509_name_equal(other.subject_name(), hkd.subject_name()),
Err(Error::HkdVerify(IssuerMismatch))
));
}
#[test]
fn is_ibm_z_sign_key() {
let ibm_crt = load_gen_cert("ibm.crt");
let no_ibm_crt = load_gen_cert("inter_ca.crt");
let ibm_wrong_subj = load_gen_cert("ibm_wrong_subject.crt");
assert!(is_ibm_signing_cert(&ibm_crt));
assert!(!is_ibm_signing_cert(&no_ibm_crt));
assert!(!is_ibm_signing_cert(&ibm_wrong_subj));
}
#[test]
fn get_ibm_z_sign_key() {
let ibm_crt = load_gen_cert("ibm.crt");
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(&[ibm_crt.clone()]).is_ok());
assert!(matches!(
super::get_ibm_z_sign_key(&[ibm_crt.clone(), ibm_crt.clone()]),
Err(Error::HkdVerify(ManyIbmSignKeys))
));
assert!(matches!(
super::get_ibm_z_sign_key(&[ibm_wrong_subj]),
Err(Error::HkdVerify(NoIbmSignKey))
));
assert!(matches!(
super::get_ibm_z_sign_key(&[no_sign_crt.clone()]),
Err(Error::HkdVerify(NoIbmSignKey))
));
assert!(super::get_ibm_z_sign_key(&[ibm_crt, no_sign_crt]).is_ok(),);
}
#[test]
fn download_first_crl_from_x509() {
let ibm_crt = load_gen_cert("ibm.crt");
let inter_crl = load_gen_crl("inter_ca.crl");
let _m_inter = super::super::test::mock_endpt("inter_ca.crl");
let crl_d = super::download_first_crl_from_x509(&ibm_crt)
.unwrap()
.unwrap();
assert_eq!(crl_d.len(), 1);
assert!(x509_crl_eq(
crl_d.first().unwrap().as_ref(),
inter_crl.as_ref()
));
}
}

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