mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Compare commits
148 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6f844d642 | ||
|
|
7f059bef13 | ||
|
|
4543d23dcf | ||
|
|
12f7dbbb3d | ||
|
|
f7c048d0eb | ||
|
|
80e54ac888 | ||
|
|
26148740df | ||
|
|
3ab06d77fb | ||
|
|
386392690d | ||
|
|
72df42ea4e | ||
|
|
6c0f429805 | ||
|
|
0466760ec1 | ||
|
|
8ddc31ea77 | ||
|
|
60f862b21c | ||
|
|
a66f6bb075 | ||
|
|
2b015183aa | ||
|
|
b4142a9966 | ||
|
|
c1d08e1380 | ||
|
|
85c9a49e63 | ||
|
|
4939531fe5 | ||
|
|
84abb27077 | ||
|
|
6826316350 | ||
|
|
b95a3c24b8 | ||
|
|
1f8496aa92 | ||
|
|
9aa59bfc4b | ||
|
|
fa111404cb | ||
|
|
5094354c39 | ||
|
|
844058bf4d | ||
|
|
dc49b4fcf0 | ||
|
|
647e8b5c37 | ||
|
|
1ae4fd18fe | ||
|
|
561883b1d8 | ||
|
|
953af2cc67 | ||
|
|
8f37000137 | ||
|
|
ba7bf2f6d3 | ||
|
|
4a8e52a019 | ||
|
|
446ace09ad | ||
|
|
012b842652 | ||
|
|
fed12a71cf | ||
|
|
5cd9ad13e5 | ||
|
|
f2d5b649e1 | ||
|
|
a957e3efd6 | ||
|
|
463189bf60 | ||
|
|
aa2e8e1caa | ||
|
|
7bc15537c8 | ||
|
|
901f082f1a | ||
|
|
cd2647ab78 | ||
|
|
48bcfc3366 | ||
|
|
57f3527799 | ||
|
|
f51dc05f7f | ||
|
|
3f9ead5731 | ||
|
|
b4e4cd6cd5 | ||
|
|
e97c85bb80 | ||
|
|
c6443cf977 | ||
|
|
6a439e6771 | ||
|
|
2134aff491 | ||
|
|
b41ac66f36 | ||
|
|
9aa368af54 | ||
|
|
2e6cf81c2a | ||
|
|
ae27066c15 | ||
|
|
47fcc71f96 | ||
|
|
5ac171d224 | ||
|
|
67dbf094b2 | ||
|
|
3669fd4d8a | ||
|
|
b01ef782f0 | ||
|
|
a177ae2439 | ||
|
|
13fef6dbe0 | ||
|
|
508253c666 | ||
|
|
773d01e674 | ||
|
|
2da206f5a6 | ||
|
|
bf71e3bb1f | ||
|
|
6f7982dae5 | ||
|
|
f64b4087dd | ||
|
|
a739584d6f | ||
|
|
cce5f510c3 | ||
|
|
2515832469 | ||
|
|
6c5c5f7e55 | ||
|
|
736c69379d | ||
|
|
130880159a | ||
|
|
d7b1cbad8b | ||
|
|
a58460b5c7 | ||
|
|
acdce2a7a9 | ||
|
|
0981df62c0 | ||
|
|
4e28f6749b | ||
|
|
5f08033bb8 | ||
|
|
8231ec5f38 | ||
|
|
9e62005818 | ||
|
|
a0dba6bfdb | ||
|
|
ebc3384b9f | ||
|
|
e9bf141264 | ||
|
|
d8b8114e6f | ||
|
|
c4e4b926b4 | ||
|
|
6324f62da7 | ||
|
|
3ade063ea2 | ||
|
|
92a3b30323 | ||
|
|
673ff375d9 | ||
|
|
4e2ebe0370 | ||
|
|
78b053326c | ||
|
|
ab06a5d88a | ||
|
|
a0a71efde0 | ||
|
|
b4b2202ff5 | ||
|
|
a26f61c4fe | ||
|
|
a28b396d9e | ||
|
|
0012eaf68e | ||
|
|
c22d282e67 | ||
|
|
071522f7d2 | ||
|
|
ae7217806b | ||
|
|
9f6150db34 | ||
|
|
b39bdfbf6e | ||
|
|
71fe58111c | ||
|
|
5e46632767 | ||
|
|
1bd64f5b0f | ||
|
|
d311506dc4 | ||
|
|
08e4520a4f | ||
|
|
46fd42af0c | ||
|
|
a8b0d7ace8 | ||
|
|
a29b3c8997 | ||
|
|
27dce3317a | ||
|
|
4382901daa | ||
|
|
bcbb6fcae6 | ||
|
|
b16a6d4fe1 | ||
|
|
0d15a07c0a | ||
|
|
5394cd363c | ||
|
|
0906293cd8 | ||
|
|
a9e13a2d69 | ||
|
|
ee2c6d4160 | ||
|
|
455ad953a9 | ||
|
|
02a0d12988 | ||
|
|
be47b51890 | ||
|
|
2677a4182d | ||
|
|
812df795c3 | ||
|
|
a0d6edf03c | ||
|
|
81920f7cfd | ||
|
|
40dd63e2ac | ||
|
|
a8579a0727 | ||
|
|
2ab27bdaf2 | ||
|
|
03fef264bd | ||
|
|
927a48e607 | ||
|
|
fbcb3f384a | ||
|
|
c7fe21b019 | ||
|
|
50a4740443 | ||
|
|
164d4817ec | ||
|
|
abec41f514 | ||
|
|
fcb503ea3c | ||
|
|
2eea614bbf | ||
|
|
3a13cb43f2 | ||
|
|
e8fca95592 | ||
|
|
b5604850ab |
@@ -11,3 +11,11 @@ tab_width = 8
|
||||
|
||||
[*.sh]
|
||||
shell_variant = bash # used by `shfmt`
|
||||
|
||||
[*.y{a,}ml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -16,12 +16,14 @@ TAGS
|
||||
#
|
||||
# Ignore generated executables and other generated files
|
||||
#
|
||||
ap_tools/ap-check
|
||||
cmsfs-fuse/cmsfs-fuse
|
||||
cpacfstats/cpacfstats
|
||||
cpacfstats/cpacfstatsd
|
||||
cpumf/chcpumf
|
||||
cpumf/lscpumf
|
||||
cpumf/lshwc
|
||||
cpumf/pai
|
||||
cpuplugd/cpuplugd
|
||||
dasdfmt/dasdfmt
|
||||
dasdinfo/dasdinfo
|
||||
@@ -45,6 +47,8 @@ iucvterm/src/iucvconn
|
||||
iucvterm/src/iucvtty
|
||||
iucvterm/src/ttyrun
|
||||
iucvterm/test/test_afiucv
|
||||
libap/check-dep-lock
|
||||
libap/check-dep-json
|
||||
libekmfweb/check-dep-libekmfweb
|
||||
libekmfweb/detect-openssl-version.dep
|
||||
libekmfweb/libekmfweb.so
|
||||
@@ -64,6 +68,8 @@ lsstp/lsstp
|
||||
mon_tools/mon_fsstatd
|
||||
mon_tools/mon_procd
|
||||
osasnmpd/osasnmpd
|
||||
pvattest/src/pvattest
|
||||
pvattest/tools/exchange_info
|
||||
qetharp/qetharp
|
||||
qethqoat/qethqoat
|
||||
systemd/cpacfstatsd.service
|
||||
@@ -117,3 +123,4 @@ zkey/kmip/zkey-kmip.so
|
||||
zkey/zkey
|
||||
zkey/zkey-cryptsetup
|
||||
zpcictl/zpcictl
|
||||
**/.detect-openssl.dep.c
|
||||
|
||||
@@ -5,7 +5,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Alexandra Winter
|
||||
- Alexey Ishchuk
|
||||
- Andreas Herrmann
|
||||
- Andre Wild
|
||||
- André Wild
|
||||
- Antoinette Kaschner
|
||||
- Arnd Bergmann
|
||||
- Axel Wirbser
|
||||
@@ -19,6 +19,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Clemens von Mann
|
||||
- Colin Walters
|
||||
- Dan Horak
|
||||
- Dan Horák
|
||||
- Despina Papadopoulou
|
||||
- Dimitri John Ledkov
|
||||
- Eberhard Pasch
|
||||
@@ -33,6 +34,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Fedor Loshakov
|
||||
- Felix Beck
|
||||
- Frank Blaschka
|
||||
- Frank Heimes
|
||||
- Frank Munzert
|
||||
- Frank Pavlic
|
||||
- Fritz Elfert
|
||||
@@ -53,7 +55,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Ingo Franzki
|
||||
- Ingo Tuchscherer
|
||||
- Jan Glauber
|
||||
- Jan Hoeppner
|
||||
- Jan Höppner
|
||||
- Jan Willeke
|
||||
- Jason J. Herne
|
||||
- Javier Martinez Canillas
|
||||
@@ -78,6 +80,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Matthew Rosato
|
||||
- Maxim Shchetynin
|
||||
- Melissa Howland
|
||||
- Mete Durlu
|
||||
- Michael Ernst
|
||||
- Michael Holzheu
|
||||
- Michael Mueller
|
||||
|
||||
67
CHANGELOG.md
67
CHANGELOG.md
@@ -1,6 +1,71 @@
|
||||
Release history for s390-tools (MIT version)
|
||||
--------------------------------------------
|
||||
* __v2.20.0 (2021-02-04)__
|
||||
* __v2.22.0 (2022-06-20)__
|
||||
|
||||
For Linux kernel version: 5.18
|
||||
|
||||
Add new tools / libraries:
|
||||
- ap_tools: Introduce ap_tools and the ap-check tool
|
||||
- cpumf/pai: Add Processor Activity Instrumentation tool
|
||||
- libpv: New library for PV tools
|
||||
- pvattest: Add new tool to create, perform, and verify attestation measurements
|
||||
- zipl/zdump: Add Next Gen Dump (NGDump) support
|
||||
|
||||
Changes of existing tools:
|
||||
- Move man pages to System commands section (lscpumf, lshwc, pai, dbginfo.sh, zfcpdbf, zipl-switch-to-blscfg)
|
||||
- README.md: Add 70-chreipl-fcp-mpath.rules to the list of udev rule descriptions
|
||||
- Remove SysV related daemon scripts (cpacfstatsd, cpuplugd, mon_statd)
|
||||
- genprotimg: Move man page to section 1 for user commands
|
||||
- hyptop: increase initial update interval
|
||||
- libseckey: Adapt keymgmt_match() implementation to OpenSSL
|
||||
- libutil: Add util_exit_code
|
||||
- libutil: Introduce util_udev
|
||||
- zdev: Introduce the ap device type
|
||||
- zipl-editenv: Add zIPL multienvironment support
|
||||
- zipl: Implement sorting BLS entries by versions
|
||||
- zkey: Add initramfs hook
|
||||
|
||||
Bug Fixes:
|
||||
- cmsfs-fuse: Fix enabling of hard_remove option
|
||||
- s390-tools: Fix typos that were detected by lintian as 'typo-in-manual-page'
|
||||
- zkey-kmip: Fix possible use after free
|
||||
- zkey: Fix EP11 host library version checking
|
||||
- zkey_kmip: Setup ext-lib once the APQNs have been configured
|
||||
|
||||
* __v2.21.0 (2022-04-20)__
|
||||
|
||||
For Linux kernel version: 5.17
|
||||
|
||||
Add new tools / libraries:
|
||||
- libcpumf: Create library libcpumf for CPU Measurement functions
|
||||
|
||||
Changes of existing tools:
|
||||
- chreipl-fcp-mpath: bundle a pre-cooked version of the manpage for build
|
||||
environments without access to `pandoc`
|
||||
- dbginfo.sh: Add multipath info to map paths to FC addressing and prio group
|
||||
- dbginfo.sh: Collect config files of systemd-modules-load.service
|
||||
- dbginfo.sh: Sort list of environment variables for readability
|
||||
- dbginfo.sh: Replace "which" by builtin command "type"
|
||||
- dbginfo.sh: Rework script formatting (indents, order)
|
||||
- dbginfo.sh: Update sysfs collection (excludes, messages)
|
||||
- genprotimg: Add Protected Virtualization (PV) dump support
|
||||
- genprotimg: Remove DigiCert root CA pinning
|
||||
- lszcrypt: Add CEX8S support
|
||||
- zcryptctl: Add control domain handling
|
||||
- zcryptstats: Add CEX8 support
|
||||
- zipl: Allow optional entries that are left out when files are missing
|
||||
- zipl: make IPL sections defined with BLS to inherit a target field
|
||||
- zpcictl: Add option to trigger firmware reset
|
||||
|
||||
Bug Fixes:
|
||||
- cpictl: Handle excessive kernel version numbers
|
||||
- dbginfo.sh: Collect all places where modprobe.d config files could exist
|
||||
- fdasd: Fix endless menu loop on EOF
|
||||
- zdump/dfi: Fix segfault due to double free
|
||||
- zdump: Fix /dev/mem reading
|
||||
- zpcictl: Fix race of SCLP reset and Linux recovery
|
||||
|
||||
* __v2.20.0 (2022-02-04)__
|
||||
|
||||
For Linux kernel version: 5.16
|
||||
|
||||
|
||||
6
Makefile
6
Makefile
@@ -10,13 +10,13 @@ include common.mak
|
||||
#
|
||||
BASELIB_DIRS = libutil libseckey
|
||||
LIB_DIRS = libvtoc libzds libdasd libvmdump libccw libvmcp libekmfweb \
|
||||
libkmipclient
|
||||
libkmipclient libcpumf libap libpv
|
||||
TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
|
||||
tape390 osasnmpd qetharp ip_watcher qethconf scripts zconf \
|
||||
vmconvert 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
|
||||
genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools pvattest
|
||||
|
||||
SUB_DIRS = $(BASELIB_DIRS) $(LIB_DIRS) $(TOOL_DIRS)
|
||||
|
||||
@@ -33,7 +33,7 @@ endif
|
||||
|
||||
#
|
||||
# We have to build the base libraries before the other libraries are built,
|
||||
# and then build the other libraries before the tools are built. Otherwise the
|
||||
# and then build the other libraries before the tools are built. Otherwise the
|
||||
# other libraries and tools would trigger parallel "make -C" builds for the
|
||||
# base libraries and the other libraries in case of "make -j".
|
||||
#
|
||||
|
||||
55
README.md
55
README.md
@@ -33,6 +33,9 @@ Package contents
|
||||
* genprotimg:
|
||||
Create a protected virtualization image.
|
||||
|
||||
* pvattest:
|
||||
Create, perform, and verify protected virtualization attestation measurements.
|
||||
|
||||
* udev rules:
|
||||
- 59-dasd.rules: rules for unique DASD device nodes created in /dev/disk/.
|
||||
- 57-osasnmpd.rules: udev rules for osasnmpd.
|
||||
@@ -40,6 +43,9 @@ Package contents
|
||||
- 40-z90crypt.rules: udev rules for z90crypt driver
|
||||
- 90-cpi.rules: udev rule to update Control-Program-Information when KVM is
|
||||
used.
|
||||
- 70-chreipl-fcp-mpath.rules: udev rules to monitor multipath events for
|
||||
re-IPL path failover and to adjust the re-IPL
|
||||
device in case needed.
|
||||
|
||||
* systemd units:
|
||||
- cpi.service: Unit to apply CPI settings
|
||||
@@ -50,10 +56,10 @@ Package contents
|
||||
- ttyrun-getty@.service: Instance unit to manage ttyrun
|
||||
|
||||
* zipl:
|
||||
Make DASDs or tapes bootable for system IPL or system dump.
|
||||
Make DASDs, SCSIs, NVMes or tapes bootable for system IPL or system dump.
|
||||
|
||||
* zgetdump:
|
||||
Retrieve system dumps from either tapes or DASDs.
|
||||
Retrieve system dumps from either tapes, DASDs, SCSIs or NVMes.
|
||||
|
||||
* qetharp:
|
||||
Read and flush the ARP cache on OSA Express network cards.
|
||||
@@ -274,6 +280,10 @@ Package contents
|
||||
Use multipath information to change the configured FCP re-IPL path on
|
||||
detecting issues with the current path.
|
||||
|
||||
* ap-check:
|
||||
A utility called by mdevctl to assist in managing vfio_ap-passthrough
|
||||
devices.
|
||||
|
||||
For more information refer to the following publications:
|
||||
|
||||
* "Device Drivers, Features, and Commands" chapter "Useful Linux commands"
|
||||
@@ -300,22 +310,25 @@ build options:
|
||||
| net-snmp | `HAVE_SNMP` | osasnmpd |
|
||||
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
|
||||
| openssl | `HAVE_OPENSSL` | genprotimg, zkey, libekmfweb, |
|
||||
| | | libkmipclient |
|
||||
| | | libkmipclient, pvattest |
|
||||
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
|
||||
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
|
||||
| | | libkmipclient |
|
||||
| glib2 | `HAVE_GLIB2` | genprotimg |
|
||||
| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient |
|
||||
| glib2 | `HAVE_GLIB2` | genprotimg, pvattest |
|
||||
| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient,|
|
||||
| | | pvattest |
|
||||
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
|
||||
| systemd | `HAVE_SYSTEMD` | hsavmcore |
|
||||
| liblockfile | `HAVE_LOCKFILE` | ap-check |
|
||||
|
||||
This table lists additional build or install options:
|
||||
|
||||
| __COMPONENT__ | __OPTION__ | __TOOLS__ |
|
||||
|------------------|:----------------------------:|:-----------------------:|
|
||||
| dracut | `HAVE_DRACUT` | zdev, chreipl-fcp-mpath |
|
||||
| initramfs-tools | `HAVE_INITRAMFS` | zdev |
|
||||
| | `ZDEV_ALWAYS_UPDATE_INITRD` | zdev |
|
||||
| __COMPONENT__ | __OPTION__ | __TOOLS__ |
|
||||
|------------------|:----------------------------:|:------------------------:|
|
||||
| dracut | `HAVE_DRACUT` | zdev, chreipl-fcp-mpath, |
|
||||
| | | zipl |
|
||||
| initramfs-tools | `HAVE_INITRAMFS` | zdev, zipl |
|
||||
| | `ZDEV_ALWAYS_UPDATE_INITRD` | zdev |
|
||||
|
||||
The s390-tools build process uses "pkg-config" if available and hard-coded
|
||||
compiler and linker options otherwise.
|
||||
@@ -337,6 +350,16 @@ the different tools are provided:
|
||||
|
||||
The runtime requirements are: openssl-libs (>= 1.1.0) and glib2.
|
||||
|
||||
* pvattest:
|
||||
For building pvattest you need OpenSSL version 1.1.1 or newer
|
||||
installed (openssl-devel.rpm). Also required is glib2.56 or newer
|
||||
(glib2-devel.rpm) and libcurl.
|
||||
Tip: you may skip the pvattest build by adding
|
||||
`HAVE_OPENSSL=0`, `HAVE_LIBCURL=0`, or `HAVE_GLIB2=0`.
|
||||
|
||||
The runtime requirements are: openssl-libs (>= 1.1.1) and
|
||||
glib2.56 or newer.
|
||||
|
||||
* osasnmpd:
|
||||
You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm)
|
||||
installed, before building the osasnmpd subagent.
|
||||
@@ -478,5 +501,13 @@ the different tools are provided:
|
||||
Summarized: chreipl-fcp-mpath requires GNU Bash, GNU Core Utilities,
|
||||
util-linux, udev, and multipath-tools. When using `HAVE_DRACUT=1` with the
|
||||
make invocation, it also requires dracut. When using `ENABLE_DOC=1` with the
|
||||
make invocation to build a man page and render the README.md as HTML, make
|
||||
further requires pandoc and GNU awk for the build process.
|
||||
make invocation to build a fresh man page (instead of using the pre-cooked
|
||||
version) and render the README.md as HTML, make further requires pandoc and
|
||||
GNU awk for the build process.
|
||||
|
||||
* ap-check:
|
||||
For building the ap-check mdevctl callout utility you need liblockfile
|
||||
version 1.14 or newer installed (liblockfile-devel.rpm). Also required is
|
||||
json-c version 0.13 or newer (json-c-devel.rpm).
|
||||
Tip: you may skip ap-check build by adding `HAVE_LOCKFILE=0` or `HAVE_JSONC=0`
|
||||
to the make invocation.
|
||||
|
||||
47
ap_tools/Makefile
Normal file
47
ap_tools/Makefile
Normal file
@@ -0,0 +1,47 @@
|
||||
include ../common.mak
|
||||
|
||||
MDEVCTL_DIR = /etc/mdevctl.d/
|
||||
MDEVCTL_SCRIPTS = /etc/mdevctl.d/scripts.d/
|
||||
MDEVCTL_CALLOUTS = /etc/mdevctl.d/scripts.d/callouts/
|
||||
|
||||
libs = $(rootdir)/libap/libap.a \
|
||||
$(rootdir)/libutil/libutil.a
|
||||
|
||||
ifeq (${HAVE_LOCKFILE},0)
|
||||
all:
|
||||
$(SKIP) HAVE_LOCKFILE=0
|
||||
|
||||
install:
|
||||
$(SKIP) HAVE_LOCKFILE=0
|
||||
|
||||
else ifeq (${HAVE_JSONC},0)
|
||||
all:
|
||||
$(SKIP) HAVE_JSONC=0
|
||||
|
||||
install:
|
||||
$(SKIP) HAVE_JSONC=0
|
||||
else
|
||||
LDLIBS += -llockfile -ljson-c
|
||||
|
||||
all: ap-check
|
||||
|
||||
ap-check: ap-check.o $(libs)
|
||||
|
||||
install: all
|
||||
@if [ ! -d $(DESTDIR)$(MDEVCTL_CALLOUTS) ]; then \
|
||||
mkdir -p $(DESTDIR)$(MDEVCTL_CALLOUTS); \
|
||||
chown $(OWNER).$(GROUP) $(DESTDIR)$(MDEVCTL_DIR); \
|
||||
chown $(OWNER).$(GROUP) $(DESTDIR)$(MDEVCTL_SCRIPTS); \
|
||||
chown $(OWNER).$(GROUP) $(DESTDIR)$(MDEVCTL_CALLOUTS); \
|
||||
chmod 755 $(DESTDIR)$(MDEVCTL_DIR); \
|
||||
chmod 755 $(DESTDIR)$(MDEVCTL_SCRIPTS); \
|
||||
chmod 755 $(DESTDIR)$(MDEVCTL_CALLOUTS); \
|
||||
fi; \
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 ap-check \
|
||||
$(DESTDIR)$(MDEVCTL_CALLOUTS)
|
||||
endif
|
||||
|
||||
clean:
|
||||
rm -f *.o *~ ap-check core
|
||||
|
||||
.PHONY: all install clean
|
||||
911
ap_tools/ap-check.c
Normal file
911
ap_tools/ap-check.c
Normal file
@@ -0,0 +1,911 @@
|
||||
/*
|
||||
* ap-check - Validate vfio-ap mediated device configuration changes
|
||||
*
|
||||
* This tool in intended to be driven via the callout API of the mdevctl
|
||||
* utility (https://github.com/mdevctl/mdevctl/)
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "lib/ap.h"
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_opt.h"
|
||||
#include "lib/util_path.h"
|
||||
|
||||
#include "ap-check.h"
|
||||
|
||||
static const struct mdevctl_action mdevctl_action_table[NUM_MDEVCTL_ACTIONS] = {
|
||||
{MDEVCTL_ACTION_DEFINE, "define"},
|
||||
{MDEVCTL_ACTION_LIST, "list"},
|
||||
{MDEVCTL_ACTION_MODIFY, "modify"},
|
||||
{MDEVCTL_ACTION_START, "start"},
|
||||
{MDEVCTL_ACTION_STOP, "stop"},
|
||||
{MDEVCTL_ACTION_TYPES, "types"},
|
||||
{MDEVCTL_ACTION_UNDEFINE, "undefine"},
|
||||
{MDEVCTL_ACTION_ATTRIBUTES, "attributes"}
|
||||
};
|
||||
|
||||
static const struct mdevctl_event mdevctl_event_table[NUM_MDEVCTL_EVENTS] = {
|
||||
{MDEVCTL_EVENT_PRE, "pre"},
|
||||
{MDEVCTL_EVENT_POST, "post"},
|
||||
{MDEVCTL_EVENT_GET, "get"}
|
||||
};
|
||||
|
||||
/*
|
||||
* Convert mdevctl action string to an enumerated value
|
||||
*/
|
||||
static enum mdevctl_action_id validate_action(char *action)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_MDEVCTL_ACTIONS; i++) {
|
||||
if (strcmp(action, mdevctl_action_table[i].action) == 0)
|
||||
return mdevctl_action_table[i].id;
|
||||
}
|
||||
|
||||
return MDEVCTL_ACTION_UNKNOWN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert mdevctl event string to an enumerated value
|
||||
*/
|
||||
static enum mdevctl_event_id validate_event(char *event)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_MDEVCTL_EVENTS; i++) {
|
||||
if (strcmp(event, mdevctl_event_table[i].event) == 0)
|
||||
return mdevctl_event_table[i].id;
|
||||
}
|
||||
|
||||
return MDEVCTL_EVENT_UNKNOWN;
|
||||
}
|
||||
|
||||
static struct util_opt opt_vec[] = {
|
||||
UTIL_OPT_SECTION("DEVICE"),
|
||||
{
|
||||
.option = { "e", required_argument, NULL, 'e' },
|
||||
.argument = "EVENT",
|
||||
.desc = "The type of callout being issued",
|
||||
},
|
||||
{
|
||||
.option = { "a", required_argument, NULL, 'a' },
|
||||
.argument = "ACTION",
|
||||
.desc = "The action being performed on the specified device",
|
||||
},
|
||||
{
|
||||
.option = { "s", required_argument, NULL, 's' },
|
||||
.argument = "STATE",
|
||||
.desc = "The state of the associated mdevctl command",
|
||||
},
|
||||
{
|
||||
.option = { "u", required_argument, NULL, 'u' },
|
||||
.argument = "UUID",
|
||||
.desc = "Universally Unique ID for the mediated device",
|
||||
},
|
||||
{
|
||||
.option = { "p", required_argument, NULL, 'p' },
|
||||
.argument = "PDEV",
|
||||
.desc = "Parent device name, e.g. matrix",
|
||||
},
|
||||
{
|
||||
.option = { "t", required_argument, NULL, 't' },
|
||||
.argument = "TYPE",
|
||||
.desc = "Mediated device type, e.g. vfio_ap-passthrough",
|
||||
},
|
||||
UTIL_OPT_END
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize the ap_check anchor struct.
|
||||
*/
|
||||
static void ap_check_init(struct ap_check_anchor *anc)
|
||||
{
|
||||
anc->uuid = anc->parent = anc->type = NULL;
|
||||
anc->dev = vfio_ap_device_new();
|
||||
anc->cleanup_lock = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free memory of ap_check anchor struct.
|
||||
*/
|
||||
static void ap_check_cleanup(struct ap_check_anchor *anc)
|
||||
{
|
||||
if (anc->uuid)
|
||||
free(anc->uuid);
|
||||
if (anc->parent)
|
||||
free(anc->parent);
|
||||
if (anc->type)
|
||||
free(anc->type);
|
||||
if (anc->dev)
|
||||
vfio_ap_device_free(anc->dev);
|
||||
if (anc->cleanup_lock)
|
||||
ap_release_lock();
|
||||
}
|
||||
|
||||
/*
|
||||
* Exit ap_check
|
||||
*/
|
||||
static void __noreturn ap_check_exit(struct ap_check_anchor *anc, int rc)
|
||||
{
|
||||
ap_check_cleanup(anc);
|
||||
exit(rc);
|
||||
}
|
||||
|
||||
/*
|
||||
* parses the command line
|
||||
*/
|
||||
static void ap_check_parse(struct ap_check_anchor *anc,
|
||||
int argc, char *argv[])
|
||||
{
|
||||
bool action = false, event = false, state = false, bad_opts = false;
|
||||
int opt;
|
||||
|
||||
util_opt_init(opt_vec, NULL);
|
||||
|
||||
while (1) {
|
||||
opt = util_opt_getopt_long(argc, argv);
|
||||
if (opt == -1)
|
||||
break;
|
||||
switch (opt) {
|
||||
case 'e':
|
||||
if (event) {
|
||||
bad_opts = true;
|
||||
} else {
|
||||
anc->event = validate_event(optarg);
|
||||
event = true;
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
if (action) {
|
||||
bad_opts = true;
|
||||
} else {
|
||||
anc->action = validate_action(optarg);
|
||||
action = true;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (state) {
|
||||
bad_opts = true;
|
||||
} else {
|
||||
/* Ignore the state */
|
||||
state = true;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
if (anc->uuid)
|
||||
bad_opts = true;
|
||||
else
|
||||
anc->uuid = util_strdup(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
if (anc->parent)
|
||||
bad_opts = true;
|
||||
else
|
||||
anc->parent = util_strdup(optarg);
|
||||
break;
|
||||
case 't':
|
||||
if (anc->type)
|
||||
bad_opts = true;
|
||||
else
|
||||
anc->type = util_strdup(optarg);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown operand\n");
|
||||
ap_check_exit(anc, EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure we got all expected input values */
|
||||
if (!(action && event && state && anc->uuid && anc->parent &&
|
||||
anc->type) || bad_opts) {
|
||||
fprintf(stderr, "Duplicate or missing operand\n");
|
||||
ap_check_exit(anc, EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Check for invalid UUID */
|
||||
if (!is_valid_uuid(anc->uuid)) {
|
||||
fprintf(stderr, "Invalid UUID specified\n");
|
||||
ap_check_exit(anc, EXIT_FAILURE);
|
||||
}
|
||||
anc->dev->uuid = util_strdup(anc->uuid);
|
||||
|
||||
/* Check for valid type */
|
||||
if (strcmp(anc->type, VFIO_AP_TYPE) != 0)
|
||||
ap_check_exit(anc, APC_EXIT_UNKNOWN_TYPE);
|
||||
|
||||
/* Check for invalid parent - currently only 'matrix' supported */
|
||||
if (strcmp(anc->parent, "matrix") != 0) {
|
||||
fprintf(stderr, "Invalid parent specified\n");
|
||||
ap_check_exit(anc, EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Call a function for each entry in a directory:
|
||||
* int callback(const char *abs_path, const char *rel_path, void *data)
|
||||
* Continues for all entries in the directory regardless of callback return
|
||||
* code. Will return 0 or, if one or more callbacks failed, the first nonzero
|
||||
* rc received.
|
||||
*/
|
||||
static int path_for_each(const char *path,
|
||||
int (*callback)(const char *, const char *, void *),
|
||||
void *data)
|
||||
{
|
||||
struct dirent *de;
|
||||
int rc = 0;
|
||||
int r = 0;
|
||||
DIR *dir;
|
||||
char *p;
|
||||
|
||||
dir = opendir(path);
|
||||
if (!dir)
|
||||
return -1;
|
||||
|
||||
while ((de = readdir(dir))) {
|
||||
if (strcmp(de->d_name, ".") == 0 ||
|
||||
strcmp(de->d_name, "..") == 0)
|
||||
continue;
|
||||
util_asprintf(&p, "%s/%s", path, de->d_name);
|
||||
r = callback(p, de->d_name, data);
|
||||
/* Save first nonzero return code for caller */
|
||||
if (rc == 0 && r != 0)
|
||||
rc = r;
|
||||
free(p);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Report an error message when the specified configuration will conflict
|
||||
* with an existing device
|
||||
*/
|
||||
static void conflict_error(const char *uuid, unsigned int a, unsigned int d,
|
||||
bool persistent)
|
||||
{
|
||||
if (uuid) {
|
||||
if (persistent) {
|
||||
fprintf(stderr,
|
||||
"APQN %u.%u is defined for autostart by %s\n",
|
||||
a, d, uuid);
|
||||
} else {
|
||||
fprintf(stderr, "APQN %u.%u already in use by %s\n",
|
||||
a, d, uuid);
|
||||
}
|
||||
} else {
|
||||
if (persistent) {
|
||||
fprintf(stderr, "AQPN %u.%u is not defined for "
|
||||
"vfio_ap-passthrough use by the persistent "
|
||||
"ap bus mask settings\n", a, d);
|
||||
} else {
|
||||
fprintf(stderr, "AQPN %u.%u is not allowed for "
|
||||
"vfio_ap-passthrough use by the active ap "
|
||||
"bus mask settings\n", a, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare the list of adapters and domains for two devices, reporting error
|
||||
* messages for any conflicts that occur. A conflict occurs when both devices
|
||||
* have the same adapter + domain pair.
|
||||
* The function below takes advantage of the fact that the lists are known to
|
||||
* be sorted in numeric order; therefore we can use this information to run
|
||||
* the lists in parallel rather than always starting from the beginning.
|
||||
*/
|
||||
static int find_apqn_conflicts(const char *uuid,
|
||||
struct util_list *adapters,
|
||||
struct util_list *domains,
|
||||
struct util_list *adapters2,
|
||||
struct util_list *domains2,
|
||||
bool persistent)
|
||||
{
|
||||
struct vfio_ap_node *a, *a2, *d, *d2;
|
||||
int rc = 0;
|
||||
|
||||
/* Checks for conflicts with the device */
|
||||
a = util_list_start(adapters);
|
||||
a2 = util_list_start(adapters2);
|
||||
while ((a != NULL) && (a2 != NULL)) {
|
||||
if (a->id == a2->id) {
|
||||
d = util_list_start(domains);
|
||||
d2 = util_list_start(domains2);
|
||||
while ((d != NULL) && (d2 != NULL)) {
|
||||
if (d->id == d2->id) {
|
||||
/* Report error, look for more */
|
||||
conflict_error(uuid, a->id, d->id,
|
||||
persistent);
|
||||
rc = -1;
|
||||
d = util_list_next(domains, d);
|
||||
d2 = util_list_next(domains2, d2);
|
||||
} else if (d->id > d2->id) {
|
||||
d2 = util_list_next(domains2, d2);
|
||||
} else {
|
||||
d = util_list_next(domains, d);
|
||||
}
|
||||
}
|
||||
a = util_list_next(adapters, a);
|
||||
a2 = util_list_next(adapters2, a2);
|
||||
} else if (a->id > a2->id) {
|
||||
a2 = util_list_next(adapters2, a2);
|
||||
} else {
|
||||
a = util_list_next(adapters, a);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the provided path maps to a valid vfio-ap device configuration,
|
||||
* determine if its current configuration will conflict with the proposed
|
||||
* changes.
|
||||
*/
|
||||
static int check_other_mdev_cfg_cb(const char *path,
|
||||
const char *filename,
|
||||
void *data)
|
||||
{
|
||||
struct other_mdev_cb_data *cbdata = data;
|
||||
struct vfio_ap_device *dev = cbdata->dev;
|
||||
struct vfio_ap_device *dev2 = NULL;
|
||||
int rc = 0;
|
||||
|
||||
/* Skip anything that isn't an mdev config */
|
||||
if (!is_valid_uuid(filename))
|
||||
goto out;
|
||||
|
||||
/* Skip if this is the input device */
|
||||
if (strcasecmp(cbdata->uuid, filename) == 0)
|
||||
goto out;
|
||||
|
||||
/* Read the device config */
|
||||
dev2 = vfio_ap_device_new();
|
||||
if (vfio_ap_read_device_config(path, dev2) != 0)
|
||||
goto out;
|
||||
|
||||
/* If wrong device type, skip */
|
||||
if (strcmp(dev2->type, VFIO_AP_TYPE) != 0)
|
||||
goto out;
|
||||
|
||||
/* If not AUTO device, skip */
|
||||
if (dev2->manual)
|
||||
goto out;
|
||||
|
||||
/* Perform mdev-to-mdev apqn conflict analysis */
|
||||
rc = find_apqn_conflicts(filename, dev->adapters, dev->domains,
|
||||
dev2->adapters, dev2->domains, true);
|
||||
|
||||
out:
|
||||
if (dev2 != NULL)
|
||||
vfio_ap_device_free(dev2);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform conflict analysis against all other vfio-ap persistent
|
||||
* configurations.
|
||||
*/
|
||||
int check_other_mdevs_cfg(struct ap_check_anchor *anc)
|
||||
{
|
||||
struct other_mdev_cb_data cb_data;
|
||||
|
||||
if (!util_path_is_dir(VFIO_AP_CONFIG_PATH))
|
||||
return 0;
|
||||
|
||||
cb_data.uuid = anc->uuid;
|
||||
cb_data.dev = anc->dev;
|
||||
|
||||
return path_for_each(VFIO_AP_CONFIG_PATH, check_other_mdev_cfg_cb,
|
||||
&cb_data);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the provided path maps to a valid device, determine if its current
|
||||
* configuration will conflict with the proposed changes.
|
||||
*/
|
||||
static int check_other_mdev_sysfs_cb(const char *path, const char *filename,
|
||||
void *data)
|
||||
{
|
||||
struct other_mdev_cb_data *cbdata = data;
|
||||
struct vfio_ap_device *dev = cbdata->dev;
|
||||
struct vfio_ap_device *dev2;
|
||||
char *matrix_path;
|
||||
char buf[80];
|
||||
int rc = 0;
|
||||
FILE *f;
|
||||
|
||||
if (!is_valid_uuid(filename) || path == NULL ||
|
||||
strcasecmp(filename, cbdata->uuid) == 0)
|
||||
return 0;
|
||||
|
||||
dev2 = vfio_ap_device_new();
|
||||
matrix_path = path_get_vfio_ap_attr(filename, "matrix");
|
||||
f = fopen(matrix_path, "r");
|
||||
while (fgets(buf, sizeof(buf), f))
|
||||
vfio_ap_parse_matrix(dev2, buf);
|
||||
vfio_ap_sort_matrix_results(dev2);
|
||||
fclose(f);
|
||||
free(matrix_path);
|
||||
|
||||
/* Look for conflicts between target device and this device */
|
||||
rc = find_apqn_conflicts(filename, dev->adapters, dev->domains,
|
||||
dev2->adapters, dev2->domains, false);
|
||||
|
||||
vfio_ap_device_free(dev2);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Run conflict analysis against all other active vfio-ap devices */
|
||||
static int check_other_mdevs_sysfs(struct ap_check_anchor *anc)
|
||||
{
|
||||
struct other_mdev_cb_data cb_data;
|
||||
char *root;
|
||||
int rc = 0;
|
||||
|
||||
cb_data.uuid = anc->uuid;
|
||||
cb_data.dev = anc->dev;
|
||||
|
||||
root = path_get_vfio_ap_mdev("");
|
||||
if (util_path_is_dir(root))
|
||||
rc = path_for_each(root, check_other_mdev_sysfs_cb, &cb_data);
|
||||
|
||||
free(root);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if there are any conflicts between the specified device and
|
||||
* the active apmask/aqmask settings. This is done by treating the masks
|
||||
* as a temporary vfio_ap_device with all of the associated APQNs owned by
|
||||
* the system.
|
||||
*/
|
||||
static int check_sysfs_mask_conflicts(struct ap_check_anchor *anc)
|
||||
{
|
||||
struct vfio_ap_device *sysdev = vfio_ap_device_new();
|
||||
char *apmask = util_zalloc(AP_MASK_SIZE);
|
||||
char *aqmask = util_zalloc(AP_MASK_SIZE);
|
||||
int rc = 0;
|
||||
|
||||
if (ap_read_sysfs_masks(apmask, aqmask, AP_MASK_SIZE) != 0) {
|
||||
fprintf(stderr, "Error reading system AP settings\n");
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Convert the masks to a device with the associated APQNs */
|
||||
ap_mask_to_list(apmask, sysdev->adapters);
|
||||
ap_mask_to_list(aqmask, sysdev->domains);
|
||||
|
||||
/* Perform conflict analysis */
|
||||
rc = find_apqn_conflicts(NULL, anc->dev->adapters,
|
||||
anc->dev->domains, sysdev->adapters,
|
||||
sysdev->domains, false);
|
||||
out:
|
||||
free(apmask);
|
||||
free(aqmask);
|
||||
vfio_ap_device_free(sysdev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if there are any conflicts between the specified device and
|
||||
* the apmask/aqmask settings stored in udev. This is done by treating
|
||||
* the masks as a temporary vfio_ap_device with all of the associated
|
||||
* AQPNs owned by the system.
|
||||
*/
|
||||
static int check_cfg_mask_conflicts(struct ap_check_anchor *anc)
|
||||
{
|
||||
struct vfio_ap_device *sysdev = vfio_ap_device_new();
|
||||
char *apmask = util_zalloc(AP_MASK_SIZE);
|
||||
char *aqmask = util_zalloc(AP_MASK_SIZE);
|
||||
bool read_ap = false, read_aq = false;
|
||||
char *path;
|
||||
int rc = 0;
|
||||
|
||||
path = path_get_ap_udev();
|
||||
if (!ap_read_udev_masks(path, apmask, aqmask, &read_ap, &read_aq)) {
|
||||
fprintf(stderr, "Error reading system AP settings\n");
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Convert the masks to a device with the associated APQNs */
|
||||
ap_mask_to_list(apmask, sysdev->adapters);
|
||||
ap_mask_to_list(aqmask, sysdev->domains);
|
||||
|
||||
/* Perform conflict analysis */
|
||||
rc = find_apqn_conflicts(NULL, anc->dev->adapters,
|
||||
anc->dev->domains, sysdev->adapters,
|
||||
sysdev->domains, true);
|
||||
out:
|
||||
free(apmask);
|
||||
free(aqmask);
|
||||
free(path);
|
||||
vfio_ap_device_free(sysdev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Subroutine to handle checking shared between DEFINE and MODIFY actions. */
|
||||
static int ap_check_changes(struct ap_check_anchor *anc)
|
||||
{
|
||||
int rc = 0, rc2;
|
||||
|
||||
rc = ap_get_lock_callout();
|
||||
if (rc) {
|
||||
fprintf(stderr, "Failed to acquire configuration lock %d\n",
|
||||
rc);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
anc->cleanup_lock = true;
|
||||
|
||||
if (vfio_ap_read_device_config(NULL, anc->dev) != 0) {
|
||||
fprintf(stderr, "Failed to read device config\n");
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (strcmp(anc->dev->type, anc->type) != 0) {
|
||||
fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!anc->dev->manual) {
|
||||
/* Check against all other AUTO config files */
|
||||
rc = check_other_mdevs_cfg(anc);
|
||||
/* Check against the system UDEV rule for apmask/aqmask */
|
||||
rc2 = check_cfg_mask_conflicts(anc);
|
||||
/* If either hit an error, reflect this */
|
||||
rc = rc != 0 ? rc : rc2;
|
||||
}
|
||||
|
||||
/* If successful, lock must remain held until post callout */
|
||||
if (rc == 0)
|
||||
anc->cleanup_lock = false;
|
||||
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if defining the specified device is a valid operation.
|
||||
* mdevctl can reach us for a DEFINE under the following circumstances:
|
||||
* 1) the device does not exist
|
||||
* 2) the device is active but does not have a config file, so this action
|
||||
* would be to generate a config file based upon the active device.
|
||||
* DEFINE has no effect on an active device (if one exists) it only creates
|
||||
* the configuration file. The config file might be empty or may have various
|
||||
* attributes if being fed by --jsonfile or an active device.
|
||||
*/
|
||||
static int ap_check_handle_define(struct ap_check_anchor *anc)
|
||||
{
|
||||
char *path = path_get_vfio_ap_mdev_config(anc->uuid);
|
||||
|
||||
if (util_path_is_readable(path)) {
|
||||
fprintf(stderr, "Config already exists\n");
|
||||
free(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(path);
|
||||
|
||||
return ap_check_changes(anc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if modifying the specified device is a valid operation.
|
||||
* mdevctl can reach us for a MODIFY under the following circumstances:
|
||||
* 1) Modifying a MANUAL device
|
||||
* 2) Modifying an AUTO device
|
||||
* In the case of MANUAL, we don't take any action because changes made via
|
||||
* MODIFY don't take affect on the active mdev until a STOP/START cycle.
|
||||
* In the case of AUTO, we must compare the contents of the proposed device
|
||||
* with the contents of stashed AUTO mdev configurations + the system.
|
||||
*/
|
||||
static int ap_check_handle_modify(struct ap_check_anchor *anc)
|
||||
{
|
||||
char *path = path_get_vfio_ap_mdev_config(anc->uuid);
|
||||
FILE *fd = fopen(path, "r");
|
||||
|
||||
/* Determine if a base config file already exists for UUID */
|
||||
free(path);
|
||||
if (fd == NULL) {
|
||||
fprintf(stderr, "Config doesn't exist\n");
|
||||
return -1;
|
||||
}
|
||||
fclose(fd);
|
||||
|
||||
return ap_check_changes(anc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if starting the specified device is a valid operation.
|
||||
* mdevctl can reach us for a START under the following circumstances:
|
||||
* 1) STARTing a MANUAL device
|
||||
* 1a) Where the MANUAL device is defined (has a config file)
|
||||
* 1b) Where the MANUAL device is NOT defined (no config file).
|
||||
* For vfio-ap this case provides an mdev with no adapters/domains.
|
||||
* 1c) Where the MANUAL device is NOT defined but a full configuration is
|
||||
* provided via --jsonfile
|
||||
* 2) STARTing an AUTO device
|
||||
* 2a) Where the AUTO device is defined (has a config file)
|
||||
* 2b) Where the AUTO device is NOT defined but a full configuration is
|
||||
* provided via --jsonfile
|
||||
* In each case, we must compare the proposed device with the contents of
|
||||
* active mdevs + the system.
|
||||
*/
|
||||
static int ap_check_handle_start(struct ap_check_anchor *anc)
|
||||
{
|
||||
int rc = 0, rc2;
|
||||
|
||||
/* Can only start a device if vfio_ap is built-in or loaded */
|
||||
if (!util_path_is_dir(VFIO_AP_PATH)) {
|
||||
fprintf(stderr, "vfio_ap module is not loaded\n");
|
||||
ap_check_exit(anc, EXIT_FAILURE);
|
||||
}
|
||||
|
||||
rc = ap_get_lock_callout();
|
||||
if (rc) {
|
||||
fprintf(stderr, "Failed to acquire configuration lock %d\n",
|
||||
rc);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
anc->cleanup_lock = true;
|
||||
|
||||
if (vfio_ap_read_device_config(NULL, anc->dev) != 0) {
|
||||
fprintf(stderr, "Failed to read device config\n");
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (strcmp(anc->dev->type, anc->type) != 0) {
|
||||
fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Check against all other active vfio-ap devices */
|
||||
rc = check_other_mdevs_sysfs(anc);
|
||||
/* Check against the system sysfs values for apmask/aqmask */
|
||||
rc2 = check_sysfs_mask_conflicts(anc);
|
||||
/* If either hit an error, reflect this */
|
||||
rc = rc != 0 ? rc : rc2;
|
||||
|
||||
/* If successful, lock must remain held until post callout */
|
||||
if (rc == 0)
|
||||
anc->cleanup_lock = false;
|
||||
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Acquire the appropriate serialization so that the specified device can be
|
||||
* STOPped.
|
||||
*/
|
||||
static int ap_check_handle_stop(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ap_get_lock_callout();
|
||||
if (rc) {
|
||||
fprintf(stderr, "Failed to acquire configuration lock %d\n",
|
||||
rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* The lock must remain held until post callout */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if UNDEFINEing the specified device is a valid operation.
|
||||
* mdevctl can reach us for an UNDEFINE under the following circumstances:
|
||||
* 1) UNDEFINEing an active device
|
||||
* 2) UNDEFINEing an inactive device
|
||||
* UNDEFINE has no effect on the active device, it only removes the config
|
||||
* file.
|
||||
*/
|
||||
static int ap_check_handle_undefine(struct ap_check_anchor *anc)
|
||||
{
|
||||
char *path = path_get_vfio_ap_mdev_config(anc->uuid);
|
||||
int rc = 0;
|
||||
|
||||
rc = ap_get_lock_callout();
|
||||
if (rc) {
|
||||
fprintf(stderr, "Failed to acquire configuration lock %d\n",
|
||||
rc);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
anc->cleanup_lock = true;
|
||||
|
||||
if (vfio_ap_read_device_config(path, anc->dev) != 0) {
|
||||
fprintf(stderr, "Failed to read device config\n");
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (strcmp(anc->dev->type, anc->type) != 0) {
|
||||
fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Success: lock must remain held until post callout */
|
||||
anc->cleanup_lock = false;
|
||||
|
||||
out:
|
||||
free(path);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* For callouts where the "pre" callout would have acquired the lock, it is
|
||||
* now safe to remove the lock as all changes have been committed.
|
||||
*/
|
||||
static int ap_check_handle_post(void)
|
||||
{
|
||||
return ap_release_lock();
|
||||
}
|
||||
|
||||
/* For the specified device, print the attributes to stdout in JSON format */
|
||||
static int ap_check_handle_get_attributes(struct ap_check_anchor *anc)
|
||||
{
|
||||
struct vfio_ap_device *dev = anc->dev;
|
||||
struct vfio_ap_node *node;
|
||||
bool has_attr = false;
|
||||
char buf[80];
|
||||
char *path;
|
||||
FILE *f;
|
||||
|
||||
path = path_get_vfio_ap_attr(anc->uuid, "matrix");
|
||||
f = fopen(path, "r");
|
||||
while (fgets(buf, sizeof(buf), f))
|
||||
vfio_ap_parse_matrix(dev, buf);
|
||||
vfio_ap_sort_matrix_results(dev);
|
||||
fclose(f);
|
||||
free(path);
|
||||
|
||||
path = path_get_vfio_ap_attr(anc->uuid, "control_domains");
|
||||
f = fopen(path, "r");
|
||||
while (fgets(buf, sizeof(buf), f))
|
||||
vfio_ap_parse_control(dev, buf);
|
||||
fclose(f);
|
||||
free(path);
|
||||
|
||||
printf("[{");
|
||||
|
||||
if (!util_list_is_empty(dev->adapters)) {
|
||||
util_list_iterate(dev->adapters, node) {
|
||||
if (has_attr)
|
||||
printf("},{");
|
||||
printf("\"assign_adapter\": \"%u\"", node->id);
|
||||
has_attr = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!util_list_is_empty(dev->domains)) {
|
||||
util_list_iterate(dev->domains, node) {
|
||||
if (has_attr)
|
||||
printf("},{");
|
||||
printf("\"assign_domain\": \"%u\"", node->id);
|
||||
has_attr = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!util_list_is_empty(dev->controls)) {
|
||||
util_list_iterate(dev->controls, node) {
|
||||
if (has_attr)
|
||||
printf("},{");
|
||||
printf("\"assign_control_domain\": \"%u\"", node->id);
|
||||
has_attr = true;
|
||||
}
|
||||
}
|
||||
|
||||
printf("}]\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine which mdevctl action is being checked and handle accordingly.
|
||||
*/
|
||||
static int ap_check_handle_action(struct ap_check_anchor *anc)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
switch (anc->event) {
|
||||
case MDEVCTL_EVENT_PRE:
|
||||
switch (anc->action) {
|
||||
case MDEVCTL_ACTION_DEFINE:
|
||||
rc = ap_check_handle_define(anc);
|
||||
break;
|
||||
case MDEVCTL_ACTION_MODIFY:
|
||||
rc = ap_check_handle_modify(anc);
|
||||
break;
|
||||
case MDEVCTL_ACTION_START:
|
||||
rc = ap_check_handle_start(anc);
|
||||
break;
|
||||
case MDEVCTL_ACTION_STOP:
|
||||
rc = ap_check_handle_stop();
|
||||
break;
|
||||
case MDEVCTL_ACTION_UNDEFINE:
|
||||
rc = ap_check_handle_undefine(anc);
|
||||
break;
|
||||
case MDEVCTL_ACTION_LIST:
|
||||
case MDEVCTL_ACTION_TYPES:
|
||||
default:
|
||||
/* Ignore some actions including unknown ones */
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MDEVCTL_EVENT_POST:
|
||||
switch (anc->action) {
|
||||
case MDEVCTL_ACTION_DEFINE:
|
||||
case MDEVCTL_ACTION_MODIFY:
|
||||
case MDEVCTL_ACTION_START:
|
||||
case MDEVCTL_ACTION_STOP:
|
||||
case MDEVCTL_ACTION_UNDEFINE:
|
||||
ap_check_handle_post();
|
||||
break;
|
||||
default:
|
||||
/* Ignore other post events */
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MDEVCTL_EVENT_GET:
|
||||
switch (anc->action) {
|
||||
case MDEVCTL_ACTION_ATTRIBUTES:
|
||||
rc = ap_check_handle_get_attributes(anc);
|
||||
break;
|
||||
default:
|
||||
/* Ignore some actions including unknown ones */
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Ignore any unknown events */
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct ap_check_anchor anchor;
|
||||
int rc;
|
||||
|
||||
ap_check_init(&anchor);
|
||||
|
||||
ap_check_parse(&anchor, argc, argv);
|
||||
|
||||
rc = ap_check_handle_action(&anchor);
|
||||
|
||||
ap_check_exit(&anchor, rc);
|
||||
}
|
||||
74
ap_tools/ap-check.h
Normal file
74
ap_tools/ap-check.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* ap-check - Validate vfio-ap mediated device configuration changes
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 AP_CHECK_H
|
||||
#define AP_CHECK_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/*
|
||||
* List of all of the supported mdevctl actions
|
||||
*/
|
||||
enum mdevctl_action_id {
|
||||
MDEVCTL_ACTION_DEFINE = 0,
|
||||
MDEVCTL_ACTION_LIST,
|
||||
MDEVCTL_ACTION_MODIFY,
|
||||
MDEVCTL_ACTION_START,
|
||||
MDEVCTL_ACTION_STOP,
|
||||
MDEVCTL_ACTION_TYPES,
|
||||
MDEVCTL_ACTION_UNDEFINE,
|
||||
MDEVCTL_ACTION_ATTRIBUTES,
|
||||
/* UNKNOWN must always be the last in the list */
|
||||
MDEVCTL_ACTION_UNKNOWN,
|
||||
};
|
||||
#define NUM_MDEVCTL_ACTIONS MDEVCTL_ACTION_UNKNOWN
|
||||
|
||||
struct mdevctl_action {
|
||||
enum mdevctl_action_id id;
|
||||
const char action[32];
|
||||
};
|
||||
|
||||
enum mdevctl_event_id {
|
||||
MDEVCTL_EVENT_PRE = 0,
|
||||
MDEVCTL_EVENT_POST,
|
||||
MDEVCTL_EVENT_GET,
|
||||
MDEVCTL_EVENT_UNKNOWN,
|
||||
};
|
||||
#define NUM_MDEVCTL_EVENTS MDEVCTL_EVENT_UNKNOWN
|
||||
|
||||
struct mdevctl_event {
|
||||
enum mdevctl_event_id id;
|
||||
const char event[32];
|
||||
};
|
||||
|
||||
/* ap-check special exit codes */
|
||||
#define APC_EXIT_UNKNOWN_TYPE 2
|
||||
|
||||
struct ap_check_anchor {
|
||||
enum mdevctl_event_id event;
|
||||
enum mdevctl_action_id action;
|
||||
char *uuid;
|
||||
char *parent;
|
||||
char *type;
|
||||
struct vfio_ap_device *dev;
|
||||
/* Active Masks */
|
||||
char apmask[80];
|
||||
char aqmask[80];
|
||||
/* Persistent Masks */
|
||||
char p_apmask[80];
|
||||
char p_aqmask[80];
|
||||
bool cleanup_lock;
|
||||
};
|
||||
|
||||
struct other_mdev_cb_data {
|
||||
const char *uuid;
|
||||
struct vfio_ap_device *dev;
|
||||
};
|
||||
|
||||
#endif /* AP_CHECK_H */
|
||||
1
chreipl-fcp-mpath/.chreipl-fcp-mpath.7.cksum
Normal file
1
chreipl-fcp-mpath/.chreipl-fcp-mpath.7.cksum
Normal file
@@ -0,0 +1 @@
|
||||
8e604dab39577678bc9f24bfe5e2ee35354b2f4b1759edd16093157b1ecce9d3 README.md
|
||||
1
chreipl-fcp-mpath/.gitignore
vendored
1
chreipl-fcp-mpath/.gitignore
vendored
@@ -9,4 +9,3 @@
|
||||
/README.html
|
||||
/README.pdf
|
||||
/chreipl-fcp-mpath.md
|
||||
/chreipl-fcp-mpath.7
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
# - chreipl-fcp-mpath.mak
|
||||
# bash:
|
||||
# - bash
|
||||
# GNU coreutils:
|
||||
# - sha256sum
|
||||
# If $(ENABLE_DOC) is `1`:
|
||||
# GNU awk:
|
||||
# - gawk
|
||||
@@ -100,20 +102,41 @@ chreiplzfcpmp-doc-man-meta = \
|
||||
-V footer="s390-tools $(S390_TOOLS_RELEASE)"
|
||||
|
||||
chreipl-fcp-mpath.7: PANDOCFLAGS += $(chreiplzfcpmp-doc-man-meta)
|
||||
chreipl-fcp-mpath.7: .chreipl-fcp-mpath.7.cksum
|
||||
|
||||
.INTERMEDIATE: chreipl-fcp-mpath.md
|
||||
chreipl-fcp-mpath.md: README.md
|
||||
gawk -- '/NOT-IN-MAN \{/,/NOT-IN-MAN \}/ { next } { print }' $(<) > $(@)
|
||||
|
||||
.chreipl-fcp-mpath.7.cksum: README.md
|
||||
sha256sum $(<) > $(@)
|
||||
|
||||
.PHONY: chreipl-fcp-mpath-doc-clean
|
||||
chreipl-fcp-mpath-doc-clean:
|
||||
rm -f README.html README.pdf chreipl-fcp-mpath.md chreipl-fcp-mpath.7
|
||||
rm -f README.html README.pdf chreipl-fcp-mpath.md
|
||||
|
||||
chreipl-fcp-mpath: chreipl-fcp-mpath-doc
|
||||
chreipl-fcp-mpath-clean: chreipl-fcp-mpath-doc-clean
|
||||
|
||||
else # $(ENABLE_DOC) != 1
|
||||
|
||||
# We bundle a pre-cooked man page with the source-code so that distributions
|
||||
# don't need `pandoc` in order to be able to ship the man page. As of this
|
||||
# writing multiple distributions don't have a packaged version of it.
|
||||
#
|
||||
# In order to remember to regenerate this pre-cooked version whenever the
|
||||
# README.md is changed, we also generate a checksum of the README.md, bundle
|
||||
# that as well, and compare that whenever `make` is called. This way, the
|
||||
# freshness of the man page can be checked, even if `pandoc` is not available,
|
||||
# or ENABLE_DOC disabled.
|
||||
|
||||
MANPAGE_FRESH := $(shell sha256sum --check .chreipl-fcp-mpath.7.cksum >/dev/null && echo 1 || echo 0)
|
||||
ifeq ($(MANPAGE_FRESH),0)
|
||||
$(warning chreipl-fcp-mpath.7 is outdated, please regenerate it by calling `make ENABLE_DOC=1`)
|
||||
endif
|
||||
|
||||
endif # $(ENABLE_DOC) == 1
|
||||
|
||||
#
|
||||
## Install
|
||||
#
|
||||
@@ -180,8 +203,6 @@ chreipl-fcp-mpath-install: chreipl-fcp-mpath-install-dracut-config
|
||||
|
||||
endif
|
||||
|
||||
ifeq ($(ENABLE_DOC),1)
|
||||
|
||||
# chreipl-fcp-mpath: install man page
|
||||
INSTDIRS += $(MANDIR)
|
||||
$(DESTDIR)$(MANDIR)/man7: install_dirs
|
||||
@@ -194,8 +215,6 @@ chreipl-fcp-mpath-install-man-page: chreipl-fcp-mpath.7
|
||||
|
||||
chreipl-fcp-mpath-install: chreipl-fcp-mpath-install-man-page
|
||||
|
||||
endif
|
||||
|
||||
#
|
||||
## Utility
|
||||
#
|
||||
|
||||
@@ -120,18 +120,20 @@ in place, but the toolset has some software dependencies besides the
|
||||
requirements in section [Requirements](#requirements):
|
||||
|
||||
- GNU Bash;
|
||||
- GNU Core Utilities (mktemp, readlink, sync, truncate);
|
||||
- GNU Core Utilities (mktemp, readlink, sync, truncate, sha256sum);
|
||||
- util-linux (flock, hexdump, logger);
|
||||
- udev / systemd-udev;
|
||||
- multipath-tools.
|
||||
|
||||
To make use of the optional dracut configuration you need: dracut.
|
||||
|
||||
To build and install the documentation (man page) you need:
|
||||
To build a fresh version of the documentation (man page) you need:
|
||||
|
||||
- pandoc;
|
||||
- GNU Core Utilities (date);
|
||||
- GNU awk.
|
||||
- GNU awk;
|
||||
|
||||
otherwise the pre-cooked version shipped with the source will be used.
|
||||
|
||||
INSTALLATION
|
||||
============
|
||||
@@ -160,7 +162,7 @@ steps and copies the resulting components to their final destination.
|
||||
| Option | Values | Default | Effect
|
||||
| :----- | :----: | :-----: | :-----
|
||||
| HAVE_DRACUT | 0, 1 | 0 | Install a dracut configuration file that includes **chreipl-fcp-mpath** in the initial ramdisks built with **dracut**.
|
||||
| ENABLE_DOC | 0, 1 | 0 | Build and install a man page for **chreipl-fcp-mpath**.
|
||||
| ENABLE_DOC | 0, 1 | 0 | Build a fresh version of the man page for **chreipl-fcp-mpath**.
|
||||
|
||||
Specify any options as arguments for both the **make** and **make install**
|
||||
command as shown in the following example:
|
||||
@@ -195,8 +197,8 @@ the boot record to find the new initial ramdisk.
|
||||
|
||||
~ # zipl
|
||||
|
||||
With dracut and documentation enabled, **make install** deploys the following
|
||||
files to these default locations:
|
||||
With dracut enabled, **make install** deploys the following files to these
|
||||
default locations:
|
||||
|
||||
/usr/lib/chreipl-fcp-mpath/chreipl-fcp-mpath-common.sh
|
||||
/usr/lib/dracut/dracut.conf.d/70-chreipl-fcp-mpath.conf
|
||||
|
||||
183
chreipl-fcp-mpath/chreipl-fcp-mpath.7
Normal file
183
chreipl-fcp-mpath/chreipl-fcp-mpath.7
Normal file
@@ -0,0 +1,183 @@
|
||||
.\" Automatically generated by Pandoc 2.16.1
|
||||
.\"
|
||||
.TH "CHREIPL-FCP-MPATH" "7" "2022-02-24" "s390-tools 2.20.0-build-20220224" "Administrator Manual"
|
||||
.hy
|
||||
.SH NAME
|
||||
.PP
|
||||
chreipl-fcp-mpath - use multipath information for re-IPL path failover
|
||||
on a running Linux instance
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The IPL process of Linux on Z or LinuxONE from an FCP-attached SCSI
|
||||
volume uses exactly one path to the volume.
|
||||
If this path is unavailable, the IPL fails.
|
||||
.PP
|
||||
The \f[B]chreipl-fcp-mpath\f[R] toolset monitors \f[B]udev\f[R] events
|
||||
about paths to the re-IPL volume.
|
||||
If the currently configured re-IPL path becomes unavailable, the toolset
|
||||
checks for operational paths to the same volume.
|
||||
If available, it reconfigures the re-IPL settings to use an operational
|
||||
path.
|
||||
.PP
|
||||
Thus, re-IPL from an FCP-attached SCSI volume can be successful despite
|
||||
path failures on a running Linux instance if at least one path to the
|
||||
re-IPL volume remains operational.
|
||||
.PP
|
||||
\f[B]Chreipl-fcp-mpath\f[R] requires \f[B]udev\f[R],
|
||||
\f[B]multipathd\f[R] and \f[B]dm-multipath\f[R].
|
||||
Once installed, the toolset runs automatically and autonomously.
|
||||
No user intervention is possible or required.
|
||||
.PP
|
||||
Other than installing the toolset, there is no user interface for
|
||||
\f[B]chreipl-fcp-mpath\f[R].
|
||||
.SS Requirements
|
||||
.PP
|
||||
The \f[B]chreipl-fcp-mpath\f[R] tool has the following requirements on
|
||||
the Linux instance that is being monitored:
|
||||
.IP \[bu] 2
|
||||
The Linux instance must have started successfully, during IPL.
|
||||
.IP \[bu] 2
|
||||
The running Linux instance must use \f[B]dm-multipath\f[R] and
|
||||
\f[B]multipathd\f[R] for the configured re-IPL volume - a volume that
|
||||
contains a zipl boot record and has one of its paths used in the re-IPL
|
||||
configuration.
|
||||
.IP \[bu] 2
|
||||
\f[B]udev\f[R] must run.
|
||||
.IP \[bu] 2
|
||||
The toolset must observe at least one event about the configured re-IPL
|
||||
path.
|
||||
Examples for such events are: the SCSI disk comes online, or a path of
|
||||
the corresponding multipath device goes down or comes back online.
|
||||
.RS 2
|
||||
.IP \[bu] 2
|
||||
The WWID of the re-IPL volume must not change while the Linux instance
|
||||
is running.
|
||||
.RE
|
||||
.IP \[bu] 2
|
||||
When the configured re-IPL path becomes unavailable while the Linux
|
||||
instance is running, at least one operational path to the re-IPL volume
|
||||
must be available, or must become available.
|
||||
If no such path is available when the Linux instance is rebooted, the
|
||||
re-IPL path is not changed.
|
||||
.IP \[bu] 2
|
||||
The tool assumes that any manually reconfigured re-IPL device is valid
|
||||
and operational.
|
||||
.RS 2
|
||||
.PP
|
||||
The tool treats a newly configured re-IPL device like the initially
|
||||
configured re-IPL device.
|
||||
In particular, if the newly configured re-IPL device fulfills the
|
||||
requirements of the tool, re-IPL path failover takes place if the
|
||||
configured re-IPL path becomes unavailable.
|
||||
.RE
|
||||
.SS Caution with Manual Changes to the Configured re-IPL Target
|
||||
.PP
|
||||
\f[B]chreipl-fcp-mpath\f[R] is designed to accept operator-inititated
|
||||
changes of the re-IPL device.
|
||||
However, concurrent changes by the operator and tool driven changes can
|
||||
result in the operator change being overwritten.
|
||||
.PP
|
||||
To avoid this problem, change the re-IPL device only during steady-state
|
||||
operations, when no path events happen.
|
||||
Alternatively, make sure that no events are processed while you change
|
||||
the device.
|
||||
See EXAMPLES for one way to suspend event processing.
|
||||
.SH MESSAGES
|
||||
.PP
|
||||
During monitoring and event processing, \f[B]chreipl-fcp-mpath\f[R]
|
||||
writes messages to the syslog.
|
||||
.PP
|
||||
When the configured re-IPL path is changed to a different path to the
|
||||
same volume (priority \f[I]daemon.notice\f[R]):
|
||||
.RS
|
||||
.PP
|
||||
Changed re-IPL path to: <device-bus-id>:<wwpn>:<lun>.
|
||||
.RE
|
||||
.PP
|
||||
When a path event indicates that the last available path has become
|
||||
non-operational (priority \f[I]daemon.alert\f[R]):
|
||||
.RS
|
||||
.PP
|
||||
The re-IPL device cannot be changed because no operational path to the
|
||||
re-IPL volume remains.
|
||||
The next re-IPL might fail unless you re-attach or enable at least one
|
||||
valid path to the re-IPL volume.
|
||||
.RE
|
||||
.PP
|
||||
When changing the configured re-IPL device failed because of an error
|
||||
with the used Linux kernel interface (priority \f[I]daemon.crit\f[R]):
|
||||
.RS
|
||||
.PP
|
||||
Changing the re-IPL device failed.
|
||||
The current re-IPL settings might be inconsistent.
|
||||
Check and correct the settings (see the README.md of chreipl-fcp-mpath)
|
||||
to make sure that the current re-IPL device is valid.
|
||||
.RE
|
||||
.PP
|
||||
A failure to change the re-IPL device can indicate an inconsistent
|
||||
setting that cannot be corrected automatically by
|
||||
\f[B]chreipl-fcp-mpath\f[R].
|
||||
As a result, the next re-IPL might fail or might not use the intended
|
||||
re-IPL device.
|
||||
.PP
|
||||
You can use the following tools to check and correct the current
|
||||
settings:
|
||||
.IP \[bu] 2
|
||||
\f[B]lsreipl\f[R] to confirm that the intended re-IPL device is
|
||||
configured;
|
||||
.IP \[bu] 2
|
||||
\f[B]chreipl\f[R] to change the re-IPL device;
|
||||
.IP \[bu] 2
|
||||
\f[B]lszfcp\f[R] to inspect the state of available paths to the re-IPL
|
||||
device.
|
||||
.SH EXAMPLES
|
||||
.SS Manual Changes to the Configured re-IPL Device
|
||||
.PP
|
||||
As outlined in DESCRIPTION, be cautious when manually changing the
|
||||
configured re-IPL device.
|
||||
Assure that your reconfiguration actions do not collide with concurrent
|
||||
automatic event processing by \f[B]chreipl-fcp-mpath\f[R].
|
||||
You can avoid such collisions, by stopping event processing, making your
|
||||
changes, and then re-enabling event processing.
|
||||
You need \f[I]root\f[R] privileges for running the commands in the
|
||||
following example:
|
||||
.IP
|
||||
.nf
|
||||
\f[C]
|
||||
\[ti] # udevadm settle
|
||||
\[ti] # udevadm control --stop-exec-queue
|
||||
\[ti] # chreipl ...
|
||||
\[ti] # udevadm control --start-exec-queue
|
||||
\f[R]
|
||||
.fi
|
||||
.SS Listing messages with journalctl
|
||||
.PP
|
||||
If your Linux instance includes \f[B]journalctl\f[R], use the following
|
||||
command to list all messages that are issued by
|
||||
\f[B]chreipl-fcp-mpath\f[R]:
|
||||
.IP
|
||||
.nf
|
||||
\f[C]
|
||||
\[ti] # journalctl -t chreipl-fcp-mpath
|
||||
\f[R]
|
||||
.fi
|
||||
.PP
|
||||
To list only messages that were issued since the last IPL, use this
|
||||
command:
|
||||
.IP
|
||||
.nf
|
||||
\f[C]
|
||||
\[ti] # journalctl -t chreipl-fcp-mpath -b
|
||||
\f[R]
|
||||
.fi
|
||||
.SH REPORTING BUGS
|
||||
.PP
|
||||
Use the \f[B]Issues\f[R] functionality on GitHub to report any bugs in
|
||||
\f[B]chreipl-fcp-mpath\f[R]: s390-tools
|
||||
Issues (https://github.com/ibm-s390-linux/s390-tools/issues).
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
\f[B]chreipl\f[R](8), \f[B]dracut\f[R](8), \f[B]journalctl\f[R](1),
|
||||
\f[B]lsreipl(8)\f[R], \f[B]lszfcp\f[R](8), \f[B]multipath\f[R](8),
|
||||
\f[B]multipathd\f[R](8), \f[B]udev\f[R](7), \f[B]udevadm\f[R](8),
|
||||
\f[B]zipl\f[R](8)
|
||||
@@ -4492,6 +4492,15 @@ static void destroy_file_object(struct file *f)
|
||||
free(f);
|
||||
}
|
||||
|
||||
static void *cmsfs_oper_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
|
||||
{
|
||||
(void)conn;
|
||||
|
||||
/* force immediate file removal */
|
||||
cfg->hard_remove = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct file_operations fops_fixed = {
|
||||
.cache_data = cache_file_fixed,
|
||||
.write_data = extend_block_fixed,
|
||||
@@ -4507,6 +4516,7 @@ static struct file_operations fops_variable = {
|
||||
};
|
||||
|
||||
static struct fuse_operations cmsfs_oper = {
|
||||
.init = cmsfs_oper_init,
|
||||
.getattr = cmsfs_getattr,
|
||||
.statfs = cmsfs_statfs,
|
||||
.readdir = cmsfs_readdir,
|
||||
@@ -4683,8 +4693,6 @@ int main(int argc, char *argv[])
|
||||
fuse_opt_add_arg(&args, "-oro");
|
||||
/* force single threaded mode which requires no locking */
|
||||
fuse_opt_add_arg(&args, "-s");
|
||||
/* force immediate file removal */
|
||||
fuse_opt_add_arg(&args, "-ohard_remove");
|
||||
|
||||
if (cmsfs.mode == BINARY_MODE &&
|
||||
(cmsfs.codepage_from != NULL || cmsfs.codepage_to != NULL))
|
||||
|
||||
32
common.mak
32
common.mak
@@ -5,7 +5,7 @@ COMMON_INCLUDED = true
|
||||
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
||||
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
||||
VERSION = 2
|
||||
RELEASE = 20
|
||||
RELEASE = 22
|
||||
PATCHLEVEL = 0
|
||||
DISTRELEASE = build-$(shell date +%Y%m%d)
|
||||
S390_TOOLS_RELEASE = $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
||||
@@ -59,11 +59,8 @@ define cmd_define
|
||||
endef
|
||||
|
||||
$(eval $(call cmd_define, AS," AS ",$(CROSS_COMPILE)as))
|
||||
$(eval $(call cmd_define, LINK," LINK ",$(CROSS_COMPILE)gcc))
|
||||
$(eval $(call cmd_define, LD," LD ",$(CROSS_COMPILE)ld))
|
||||
$(eval $(call cmd_define, CC," CC ",$(CROSS_COMPILE)gcc))
|
||||
$(eval $(call cmd_define, HOSTCC," HOSTCC ",gcc))
|
||||
$(eval $(call cmd_define, LINKXX," LINKXX ",$(CROSS_COMPILE)g++))
|
||||
$(eval $(call cmd_define, CXX," CXX ",$(CROSS_COMPILE)g++))
|
||||
$(eval $(call cmd_define, CPP," CPP ",$(CROSS_COMPILE)gcc -E))
|
||||
$(eval $(call cmd_define, AR," AR ",$(CROSS_COMPILE)ar))
|
||||
@@ -84,6 +81,8 @@ CHECKTOOL = $(call echocmd," CHECK ",/$@)$(CHECK_SILENT)
|
||||
SKIP = echo " SKIP $(call reldir) due to"
|
||||
|
||||
INSTALL = install
|
||||
LINK = $(CC)
|
||||
LINKXX = $(CXX)
|
||||
CP = cp
|
||||
ifneq ("${V}","1")
|
||||
MAKEFLAGS += --quiet
|
||||
@@ -92,13 +91,12 @@ ifneq ("${V}","1")
|
||||
else
|
||||
echocmd=
|
||||
endif
|
||||
DEFAULT_CFLAGS = -g -rdynamic -fstack-protector-all -W -Wall -Wformat-security
|
||||
ifeq ("${W}","1")
|
||||
DEFAULT_CFLAGS = -g -rdynamic -fstack-protector-all -W -Wall -Wformat-security -Wextra
|
||||
else
|
||||
DEFAULT_CFLAGS = -g -rdynamic -fstack-protector-all -W -Wall -Wformat-security
|
||||
DEFAULT_CFLAGS += -Wextra -Wshadow -Wundef -Wuninitialized -Wdouble-promotion -Wconversion
|
||||
endif
|
||||
ifeq ("${D}","1")
|
||||
DEFAULT_CFLAGS += -Og
|
||||
DEFAULT_CFLAGS += -Og -g3 -ggdb3
|
||||
else
|
||||
DEFAULT_CFLAGS += -O3
|
||||
endif
|
||||
@@ -120,7 +118,7 @@ endif
|
||||
# Returns "yes" on success and nothing otherwise
|
||||
#
|
||||
define check_header_prereq
|
||||
$(shell printf "#include <%s>\n int main(void) {return 0;}" $1 | \
|
||||
$(shell printf "#include <%s>\n int main(void) {return 0;}\n" $1 | \
|
||||
( $(CC) $(filter-out --coverage, $(ALL_CFLAGS)) $(ALL_CPPFLAGS) \
|
||||
$2 -o /dev/null -xc - ) >/dev/null 2>&1 && echo -n yes)
|
||||
endef
|
||||
@@ -135,7 +133,7 @@ endef
|
||||
# $5: Additional compiler & linker options (optional)
|
||||
#
|
||||
check_dep=\
|
||||
printf "\#include <%s>\n int main(void) {return 0;}" $2 | ( $(CC) $(filter-out --coverage, $(ALL_CFLAGS)) $(ALL_CPPFLAGS) $5 -o /dev/null -xc - ) > /dev/null 2>&1; \
|
||||
printf "\#include <%s>\n int main(void) {return 0;}\n" $2 | ( $(CC) $(filter-out --coverage, $(ALL_CFLAGS)) $(ALL_CPPFLAGS) $5 -o /dev/null -xc - ) > /dev/null 2>&1; \
|
||||
if [ $$? != 0 ]; \
|
||||
then \
|
||||
printf " REQCHK %s (%s)\n" $1 $2; \
|
||||
@@ -252,11 +250,9 @@ export AS LD CC CPP AR NM STRIP OBJCOPY OBJDUMP INSTALL CFLAGS CXXFLAGS \
|
||||
|
||||
ifneq ($(shell $(CC_SILENT) -dumpspecs 2>/dev/null | grep -e '[^f]no-pie'),)
|
||||
NO_PIE_CFLAGS := -fno-pie
|
||||
NO_PIE_LINKFLAGS := -no-pie
|
||||
NO_PIE_LDFLAGS := -no-pie
|
||||
else
|
||||
NO_PIE_CFLAGS :=
|
||||
NO_PIE_LINKFLAGS :=
|
||||
NO_PIE_LDFLAGS :=
|
||||
endif
|
||||
|
||||
@@ -371,6 +367,10 @@ $(rootdir)/libvmcp/libvmcp.a: $(rootdir)/libvmcp
|
||||
$(MAKE) -C $(rootdir)/libvmcp/ libvmcp.a
|
||||
.PHONY: $(rootdir)/libvmcp
|
||||
|
||||
$(rootdir)/libcpumf/libcpumf.a: $(rootdir)/libcpumf
|
||||
$(MAKE) -C $(rootdir)/libcpumf/ libcpumf.a
|
||||
.PHONY: $(rootdir)/libcpumf
|
||||
|
||||
$(rootdir)/libekmfweb/libekmfweb.so: $(rootdir)/libekmfweb
|
||||
$(MAKE) -C $(rootdir)/libekmfweb/ libekmfweb.so
|
||||
.PHONY: $(rootdir)/libekmfweb
|
||||
@@ -383,6 +383,14 @@ $(rootdir)/libkmipclient/libkmipclient.so: $(rootdir)/libkmipclient
|
||||
$(MAKE) -C $(rootdir)/libkmipclient/ libkmipclient.so
|
||||
.PHONY: $(rootdir)/libkmipclient
|
||||
|
||||
$(rootdir)/libap/libap.a: $(rootdir)/libap
|
||||
$(MAKE) -C $(rootdir)/libap/ libap.a
|
||||
.PHONY: $(rootdir)/libap
|
||||
|
||||
$(rootdir)/libpv/libpv.a: $(rootdir)/libpv
|
||||
$(MAKE) -C $(rootdir)/libpv libpv.a
|
||||
.PHONY: $(rootdir)/libpv
|
||||
|
||||
$(rootdir)/zipl/boot/data.o:
|
||||
$(MAKE) -C $(rootdir)/zipl/boot/ data.o
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
include ../common.mak
|
||||
|
||||
BIN_FILES = lscpumf chcpumf lshwc
|
||||
MAN_FILES = lscpumf.1 chcpumf.8 lshwc.1
|
||||
BIN_FILES = lscpumf chcpumf lshwc pai
|
||||
MAN_FILES = lscpumf.8 chcpumf.8 lshwc.8 pai.8
|
||||
|
||||
all: $(BIN_FILES)
|
||||
|
||||
libs = $(rootdir)/libutil/libutil.a
|
||||
libs = $(rootdir)/libcpumf/libcpumf.a $(rootdir)/libutil/libutil.a
|
||||
|
||||
lscpumf: lscpumf.o $(libs)
|
||||
chcpumf: chcpumf.o $(libs)
|
||||
lshwc: lshwc.o $(libs)
|
||||
pai: pai.o $(libs)
|
||||
|
||||
install: all install-man
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8
|
||||
@@ -22,8 +23,8 @@ clean:
|
||||
|
||||
install-man:
|
||||
for man in $(MAN_FILES); do \
|
||||
msection=`echo $$man |sed 's/.*\.\([1-9]\)$$/man\1/'` ; \
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 -D man/$$man $(DESTDIR)$(MANDIR)/$$msection/$$man ; \
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 man/$$man \
|
||||
$(DESTDIR)$(MANDIR)/man8 ; \
|
||||
done
|
||||
|
||||
.PHONY: all install clean
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* chcpumf - Change CPU Measurement Facility Characteristics
|
||||
*
|
||||
* Copyright IBM Corp. 2020
|
||||
* Copyright IBM Corp. 2020, 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
@@ -20,9 +20,9 @@
|
||||
#include "lib/util_prg.h"
|
||||
#include "lib/util_base.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
static int verbose;
|
||||
static unsigned int verbose;
|
||||
static unsigned long min_sdb, max_sdb;
|
||||
|
||||
static struct util_opt opt_vec[] = {
|
||||
@@ -85,55 +85,30 @@ static long parse_buffersize(char *string)
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static void read_sfb(unsigned long *min, unsigned long *max)
|
||||
{
|
||||
unsigned long cur_min_sdb, cur_max_sdb;
|
||||
FILE *fp;
|
||||
|
||||
if (geteuid())
|
||||
errx(EXIT_FAILURE, "Must run as root");
|
||||
fp = fopen(PERF_SFB_SIZE, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, PERF_SFB_SIZE);
|
||||
if (fscanf(fp, "%ld,%ld", &cur_min_sdb, &cur_max_sdb) != 2) {
|
||||
fclose(fp);
|
||||
errx(EXIT_FAILURE, "Can not parse file " PERF_SFB_SIZE);
|
||||
} else {
|
||||
if (*min == 0)
|
||||
*min = cur_min_sdb;
|
||||
if (*max == 0)
|
||||
*max = cur_max_sdb;
|
||||
}
|
||||
fclose(fp);
|
||||
if (*min >= *max)
|
||||
errx(EXIT_FAILURE,
|
||||
"The specified maximum must be greater than the minimum");
|
||||
}
|
||||
|
||||
static int write_sfb(unsigned long min, unsigned long max)
|
||||
static int write_sfb(unsigned int min, unsigned int max)
|
||||
{
|
||||
int rc = EXIT_SUCCESS;
|
||||
char text[64];
|
||||
size_t len;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(PERF_SFB_SIZE, "w");
|
||||
fp = fopen(S390_CPUMSF_BUFFERSZ, "w");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, PERF_SFB_SIZE);
|
||||
snprintf(text, sizeof text, "%ld,%ld", min, max);
|
||||
err(EXIT_FAILURE, S390_CPUMSF_BUFFERSZ);
|
||||
snprintf(text, sizeof(text), "%u,%u", min, max);
|
||||
len = strlen(text) + 1;
|
||||
if (fwrite(text, 1, len, fp) != len) {
|
||||
warn(PERF_SFB_SIZE);
|
||||
warn(S390_CPUMSF_BUFFERSZ);
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
if (fclose(fp)) {
|
||||
warn(PERF_SFB_SIZE);
|
||||
warn(S390_CPUMSF_BUFFERSZ);
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
if (verbose && rc != EXIT_FAILURE)
|
||||
warnx("Sampling buffer sizes:\n"
|
||||
" Minimum:%7ld sample-data-blocks\n"
|
||||
" Maximum:%7ld sample-data-blocks\n",
|
||||
" Minimum:%7d sample-data-blocks\n"
|
||||
" Maximum:%7d sample-data-blocks\n",
|
||||
min, max);
|
||||
return rc;
|
||||
}
|
||||
@@ -183,15 +158,21 @@ static int parse_args(int argc, char **argv)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct stat sbuf;
|
||||
unsigned long my_min, my_max;
|
||||
|
||||
util_prg_init(&prg);
|
||||
util_opt_init(opt_vec, NULL);
|
||||
|
||||
parse_args(argc, argv);
|
||||
if (stat(PERF_PATH PERF_SF, &sbuf))
|
||||
if (geteuid())
|
||||
errx(EXIT_FAILURE, "Must run as root");
|
||||
if (!libcpumf_have_sfb())
|
||||
errx(EXIT_FAILURE,
|
||||
"No CPU-measurement sampling facility detected");
|
||||
read_sfb(&min_sdb, &max_sdb);
|
||||
libcpumf_sfb_info(&my_min, &my_max);
|
||||
if (!min_sdb)
|
||||
min_sdb = my_min;
|
||||
if (!max_sdb)
|
||||
max_sdb = my_max;
|
||||
return write_sfb(min_sdb, max_sdb);
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Defines for CPU Measurement Facility Characteristics
|
||||
*
|
||||
* Copyright IBM Corp. 2020
|
||||
*
|
||||
* 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 DEFINES_H
|
||||
#define DEFINES_H
|
||||
|
||||
#define PERF_SFB_SIZE "/sys/module/kernel/parameters/cpum_sfb_size"
|
||||
#define PERF_PATH "/sys/bus/event_source/devices/"
|
||||
#define PERF_SF "cpum_sf"
|
||||
#define PERF_CF "cpum_cf"
|
||||
|
||||
#endif
|
||||
778
cpumf/lscpumf.c
778
cpumf/lscpumf.c
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* lscpumf - Show CPU Measurement Facility Characteristics
|
||||
*
|
||||
* Copyright IBM Corp. 2020
|
||||
* Copyright IBM Corp. 2020, 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "lib/util_opt.h"
|
||||
#include "lib/util_prg.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
#define ACTION_NONE 0
|
||||
#define ACTION_INFO 1
|
||||
@@ -45,9 +45,6 @@ static bool actions[ACTION_SAMPLE + 1]; /* Specified command line options */
|
||||
#define PER_SDBT_SIZE 511
|
||||
|
||||
/* File names to read data from */
|
||||
#define SERVICELEVEL "/proc/service_levels"
|
||||
#define CPUMF_CF_TYPE "/sys/devices/cpum_cf/type"
|
||||
#define CPUMF_SF_TYPE "/sys/devices/cpum_sf/type"
|
||||
|
||||
static struct util_opt opt_vec[] = {
|
||||
UTIL_OPT_SECTION("OPTIONS"),
|
||||
@@ -92,19 +89,18 @@ static char prefix[32]; /* Counter prefix */
|
||||
static bool show_names;
|
||||
|
||||
static struct cpumf_info {
|
||||
unsigned int first_vn; /* Counter facility first version nr */
|
||||
unsigned int second_vn; /* Counter facility second version nr */
|
||||
unsigned int authorization; /* Counter facility authorization */
|
||||
float version;
|
||||
int first_vn; /* Counter facility first version nr */
|
||||
int second_vn; /* Counter facility second version nr */
|
||||
int authorization; /* Counter facility authorization */
|
||||
unsigned long min_rate; /* Minimum sampling rate */
|
||||
unsigned long max_rate; /* Maximum sampling rate */
|
||||
unsigned long cpu_speed; /* CPU Cycles per micro second */
|
||||
unsigned int basic_sample_sz; /* # of Bytes per basic sample */
|
||||
unsigned int diag_sample_sz; /* # of bytes per diagnostic sample */
|
||||
unsigned char have_counter; /* CPUM counter facility detected */
|
||||
unsigned char have_samples; /* CPUM sampling facility detected */
|
||||
unsigned int min_sfb; /* Minimum sampling buffer size */
|
||||
unsigned int max_sfb; /* Maximum sampling buffer size */
|
||||
int basic_sample_sz; /* # of Bytes per basic sample */
|
||||
int diag_sample_sz; /* # of bytes per diagnostic sample */
|
||||
bool have_counter; /* CPUM counter facility detected */
|
||||
bool have_samples; /* CPUM sampling facility detected */
|
||||
unsigned long min_sfb; /* Minimum sampling buffer size */
|
||||
unsigned long max_sfb; /* Maximum sampling buffer size */
|
||||
unsigned short machine_type; /* Machine Type */
|
||||
} cpumf;
|
||||
|
||||
@@ -2559,32 +2555,598 @@ static struct counters cpumcf_z15_counters[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static struct counters cpumcf_z16_counters[] = {
|
||||
{
|
||||
.ctrnum = 128,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1D_RO_EXCL_WRITES",
|
||||
.desc = "A directory write to the Level-1 Data cache where"
|
||||
"\n\t\tthe line was originally in a Read-Only state in the"
|
||||
"\n\t\tcache but has been updated to be in the Exclusive"
|
||||
"\n\t\tstate that allows stores to the cache line.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 129,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DTLB2_WRITES",
|
||||
.desc = "A translation has been written into The Translation"
|
||||
"\n\t\tLookaside Buffer 2 (TLB2) and the request was made"
|
||||
"\n\t\tby the Level-1 Data cache. This is a replacement"
|
||||
"\n\t\tfor what was provided for the DTLB on z13 and prior"
|
||||
"\n\t\tmachines.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 130,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DTLB2_MISSES",
|
||||
.desc = "A TLB2 miss is in progress for a request made by"
|
||||
"\n\t\tthe Level-1 Data cache. Incremented by one for"
|
||||
"\n\t\tevery TLB2 miss in progress for the Level-1 Data"
|
||||
"\n\t\tcache on this cycle. This is a replacement for what"
|
||||
"\n\t\twas provided for the DTLB on z13 and prior"
|
||||
"\n\t\tmachines.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 131,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "CRSTE_1MB_WRITES",
|
||||
.desc = "A translation entry was written into the Combined"
|
||||
"\n\t\tRegion and Segment Table Entry array in the Level-2"
|
||||
"\n\t\tTLB for a one-megabyte page.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 132,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DTLB2_GPAGE_WRITES",
|
||||
.desc = "A translation entry for a two-gigabyte page was"
|
||||
"\n\t\twritten into the Level-2 TLB.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 134,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ITLB2_WRITES",
|
||||
.desc = "A translation entry has been written into the"
|
||||
"\n\t\tTranslation Lookaside Buffer 2 (TLB2) and the"
|
||||
"\n\t\trequest was made by the instruction cache. This is"
|
||||
"\n\t\ta replacement for what was provided for the ITLB on"
|
||||
"\n\t\tz13 and prior machines.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 135,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ITLB2_MISSES",
|
||||
.desc = "A TLB2 miss is in progress for a request made by"
|
||||
"\n\t\tthe Level-1 Instruction cache. Incremented by one"
|
||||
"\n\t\tfor every TLB2 miss in progress for the Level-1"
|
||||
"\n\t\tInstruction cache in a cycle. This is a replacement"
|
||||
"\n\t\tfor what was provided for the ITLB on z13 and prior"
|
||||
"\n\t\tmachines.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 137,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TLB2_PTE_WRITES",
|
||||
.desc = "A translation entry was written into the Page Table"
|
||||
"\n\t\tEntry array in the Level-2 TLB.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 138,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TLB2_CRSTE_WRITES",
|
||||
.desc = "Translation entries were written into the Combined"
|
||||
"\n\t\tRegion and Segment Table Entry array and the Page"
|
||||
"\n\t\tTable Entry array in the Level-2 TLB.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 139,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TLB2_ENGINES_BUSY",
|
||||
.desc = "The number of Level-2 TLB translation engines busy"
|
||||
"\n\t\tin a cycle.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 140,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_C_TEND",
|
||||
.desc = "A TEND instruction has completed in a constrained"
|
||||
"\n\t\ttransactional-execution mode.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 141,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_NC_TEND",
|
||||
.desc = "A TEND instruction has completed in a non-"
|
||||
"\n\t\tconstrained transactional-execution mode.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 143,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1C_TLB2_MISSES",
|
||||
.desc = "Increments by one for any cycle where a level-1"
|
||||
"\n\t\tcache or level-2 TLB miss is in progress.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 145,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_REQ",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestor’s Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 146,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_REQ_IV",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestor’s Level-2 cache with"
|
||||
"\n\t\tintervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 147,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_REQ_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestor’s Level-2 cache after using"
|
||||
"\n\t\tchip level horizontal persistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 148,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_REQ_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestor’s Level-2 cache after using"
|
||||
"\n\t\tdrawer level horizontal persistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 149,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 150,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP_IV",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 151,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache after using chip"
|
||||
"\n\t\tlevel horizontal persistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 152,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache using drawer level"
|
||||
"\n\t\thorizontal persistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 153,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_MODULE",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Module Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 154,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_DRAWER",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Drawer Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 155,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_OFF_DRAWER",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an Off-Drawer Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 156,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom On-Chip memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 157,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_MODULE_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom On-Module memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 158,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_DRAWER_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom On-Drawer memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 159,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_OFF_DRAWER_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom Off-Drawer memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 160,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_MODULE_IV",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Module Level-2"
|
||||
"\n\t\tcache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 161,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_MODULE_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Module Level-2"
|
||||
"\n\t\tcache using chip horizontal persistence, Chip-HP"
|
||||
"\n\t\thit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 162,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_MODULE_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Module Level-2"
|
||||
"\n\t\tcache using drawer level horizontal persistence,"
|
||||
"\n\t\tDrawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 163,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_DRAWER_IV",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Drawer Level-2"
|
||||
"\n\t\tcache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 164,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_DRAWER_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tinstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Drawer Level-2"
|
||||
"\n\t\tcache using chip level horizontal persistence, Chip-"
|
||||
"\n\t\tHP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 165,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_DRAWER_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tinstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Drawer Level-2"
|
||||
"\n\t\tcache using drawer level horizontal persistence,"
|
||||
"\n\t\tDrawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 166,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_OFF_DRAWER_IV",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tinstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an Off-Drawer Level-2"
|
||||
"\n\t\tcache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 167,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_OFF_DRAWER_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tinstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an Off-Drawer Level-2"
|
||||
"\n\t\tcache using chip level horizontal persistence, Chip-"
|
||||
"\n\t\tHP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 168,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_OFF_DRAWER_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an Off-Drawer Level-2"
|
||||
"\n\t\tcache using drawer level horizontal persistence,"
|
||||
"\n\t\tDrawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 169,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_REQ",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tthe requestors Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 170,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_REQ_IV",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestors Level-2 cache with"
|
||||
"\n\t\tintervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 171,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_REQ_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestors Level-2 cache using chip level"
|
||||
"\n\t\thorizontal persistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 172,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_REQ_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestor’s Level-2 cache using drawer"
|
||||
"\n\t\tlevel horizontal persistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 173,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_CHIP",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 174,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_CHIP_IV",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tan On-Chip Level-2 cache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 175,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_CHIP_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache using chip level"
|
||||
"\n\t\thorizontal persistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 176,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_CHIP_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip level 2 cache using drawer level"
|
||||
"\n\t\thorizontal persistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 177,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_MODULE",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Module Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 178,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_DRAWER",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tan On-Drawer Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 179,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_OFF_DRAWER",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tan Off-Drawer Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 180,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_CHIP_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom On-Chip memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 181,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_MODULE_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom On-Module memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 182,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_DRAWER_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom On-Drawer memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 183,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_OFF_DRAWER_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom Off-Drawer memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 224,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "BCD_DFP_EXECUTION_SLOTS",
|
||||
.desc = "Count of floating point execution slots used for"
|
||||
"\n\t\tfinished Binary Coded Decimal to Decimal Floating"
|
||||
"\n\t\tPoint conversions. Instructions: CDZT, CXZT, CZDT,"
|
||||
"\n\t\tCZXT.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 225,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "VX_BCD_EXECUTION_SLOTS",
|
||||
.desc = "Count of floating point execution slots used for"
|
||||
"\n\t\tfinished vector arithmetic Binary Coded Decimal"
|
||||
"\n\t\tinstructions. Instructions: VAP, VSP, VMP, VMSP,"
|
||||
"\n\t\tVDP, VSDP, VRP, VLIP, VSRP, VPSOP, VCP, VTP, VPKZ,"
|
||||
"\n\t\tVUPKZ, VCVB, VCVBG, VCVD, VCVDG.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 226,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DECIMAL_INSTRUCTIONS",
|
||||
.desc = "Decimal instruction dispatched. Instructions: CVB,"
|
||||
"\n\t\tCVD, AP, CP, DP, ED, EDMK, MP, SRP, SP, ZAP.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 232,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "LAST_HOST_TRANSLATIONS",
|
||||
.desc = "Last Host Translation done",
|
||||
},
|
||||
{
|
||||
.ctrnum = 244,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_NC_TABORT",
|
||||
.desc = "A transaction abort has occurred in a non-"
|
||||
"\n\t\tconstrained transactional-execution mode.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 245,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_C_TABORT_NO_SPECIAL",
|
||||
.desc = "A transaction abort has occurred in a constrained"
|
||||
"\n\t\ttransactional-execution mode and the CPU is not"
|
||||
"\n\t\tusing any special logic to allow the transaction to"
|
||||
"\n\t\tcomplete.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 246,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_C_TABORT_SPECIAL",
|
||||
.desc = "A transaction abort has occurred in a constrained"
|
||||
"\n\t\ttransactional-execution mode and the CPU is using"
|
||||
"\n\t\tspecial logic to allow the transaction to complete.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 248,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DFLT_ACCESS",
|
||||
.desc = "Cycles CPU spent obtaining access to Deflate unit",
|
||||
},
|
||||
{
|
||||
.ctrnum = 253,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DFLT_CYCLES",
|
||||
.desc = "Cycles CPU is using Deflate unit",
|
||||
},
|
||||
{
|
||||
.ctrnum = 256,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "SORTL",
|
||||
.desc = "Increments by one for every SORT LISTS instruction"
|
||||
"\n\t\texecuted.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 265,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DFLT_CC",
|
||||
.desc = "Increments by one for every DEFLATE CONVERSION CALL"
|
||||
"\n\t\tinstruction executed.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 266,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DFLT_CCFINISH",
|
||||
.desc = "Increments by one for every DEFLATE CONVERSION CALL"
|
||||
"\n\t\tinstruction executed that ended in Condition Codes"
|
||||
"\n\t\t0, 1 or 2.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 267,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_INVOCATIONS",
|
||||
.desc = "Increments by one for every Neural Network"
|
||||
"\n\t\tProcessing Assist instruction executed.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 268,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_COMPLETIONS",
|
||||
.desc = "Increments by one for every Neural Network"
|
||||
"\n\t\tProcessing Assist instruction executed that ended"
|
||||
"\n\t\tin Condition Codes 0, 1 or 2.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 269,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_WAIT_LOCK",
|
||||
.desc = "Cycles CPU spent obtaining access to IBM Z"
|
||||
"\n\t\tIntegrated Accelerator for AI.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 270,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_HOLD_LOCK",
|
||||
.desc = "Cycles CPU is using IBM Z Integrated Accelerator"
|
||||
"\n\t\tfor AI.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 448,
|
||||
.ctrset = CPUMF_CTRSET_MT_DIAG,
|
||||
.name = "MT_DIAG_CYCLES_ONE_THR_ACTIVE",
|
||||
.desc = "Cycle count with one thread active",
|
||||
},
|
||||
{
|
||||
.ctrnum = 449,
|
||||
.ctrset = CPUMF_CTRSET_MT_DIAG,
|
||||
.name = "MT_DIAG_CYCLES_TWO_THR_ACTIVE",
|
||||
.desc = "Cycle count with two threads active",
|
||||
},
|
||||
};
|
||||
|
||||
/* Return the type number of the CPU Measurement facility from the sysfs file.
|
||||
* If the type number is equal to PERF_TYPE_RAW, then the prefix is 'r' to
|
||||
* specify the raw counter number by the perf tool.
|
||||
* If perf_pmu_register() kernel function assigned any other (higher) type
|
||||
* number, set the prefix to <type-nr>:
|
||||
*/
|
||||
static int read_cpumf_type(const char *filename, const char *type)
|
||||
static void set_prefix(int nr)
|
||||
{
|
||||
int nr, rc = EXIT_FAILURE;
|
||||
FILE *fp = fopen(filename, "r");
|
||||
|
||||
if (fp == NULL) {
|
||||
warnx("No CPU-measurement %s facility detected", type);
|
||||
return rc;
|
||||
}
|
||||
if (fscanf(fp, "%d", &nr) != 1) {
|
||||
warnx("Can not parse file %s", filename);
|
||||
} else {
|
||||
rc = EXIT_SUCCESS;
|
||||
if (nr == PERF_TYPE_RAW)
|
||||
strcat(prefix, "r");
|
||||
else
|
||||
snprintf(prefix, sizeof prefix, "%d:", nr);
|
||||
}
|
||||
fclose(fp);
|
||||
return rc;
|
||||
if (nr == PERF_TYPE_RAW)
|
||||
strcat(prefix, "r");
|
||||
else
|
||||
snprintf(prefix, sizeof(prefix), "%d:", nr);
|
||||
}
|
||||
|
||||
/* Parse tool parameters. In case of --help or --version, print
|
||||
@@ -2661,8 +3223,6 @@ static unsigned long div_ceil(unsigned long a, unsigned long b)
|
||||
|
||||
static void show_info(struct cpumf_info *p, int details)
|
||||
{
|
||||
struct stat sbuf;
|
||||
|
||||
if (!p->have_counter && !p->have_samples) {
|
||||
warnx("No CPU-measurement facilities detected");
|
||||
return;
|
||||
@@ -2672,7 +3232,7 @@ static void show_info(struct cpumf_info *p, int details)
|
||||
if (details) {
|
||||
printf("----------------------------------------------"
|
||||
"----------------------------\n");
|
||||
printf("Version: %3.1f\n\n", p->version);
|
||||
printf("Version: %d.%d\n\n", p->first_vn, p->second_vn);
|
||||
printf("Authorized counter sets:\n");
|
||||
if (!p->authorization)
|
||||
printf(" None\n");
|
||||
@@ -2689,8 +3249,7 @@ static void show_info(struct cpumf_info *p, int details)
|
||||
if (0x8000 & p->authorization)
|
||||
printf(" Coprocessor Group counter Set\n");
|
||||
printf("\nLinux perf event support: %s\n\n",
|
||||
(stat(PERF_PATH PERF_CF, &sbuf)) ? "No" :
|
||||
"Yes (PMU: " PERF_CF ")");
|
||||
!p->have_counter ? "No" : "Yes (PMU: cpum_cf)");
|
||||
|
||||
}
|
||||
} else
|
||||
@@ -2718,19 +3277,18 @@ static void show_info(struct cpumf_info *p, int details)
|
||||
p->diag_sample_sz);
|
||||
|
||||
printf("\nLinux perf event support: %s\n\n",
|
||||
(stat(PERF_PATH PERF_SF, &sbuf)) ? "No" :
|
||||
"Yes (PMU: " PERF_SF ")");
|
||||
!p->have_samples ? "No" : "Yes (PMU: cpum_sf)");
|
||||
|
||||
printf("Current sampling buffer settings for %s:\n",
|
||||
PERF_SF);
|
||||
printf("Current sampling buffer settings for"
|
||||
" cpum_sf:\n");
|
||||
printf(" Basic-sampling mode\n");
|
||||
total = p->min_sfb + div_ceil(p->min_sfb, PER_SDBT_SIZE);
|
||||
human(text, sizeof text, PAGE_SIZE * total);
|
||||
printf(" Minimum: %6d"
|
||||
human(text, sizeof(text), PAGE_SIZE * total);
|
||||
printf(" Minimum: %6ld"
|
||||
" sample-data-blocks (%6s)\n", p->min_sfb, text);
|
||||
total = p->max_sfb + div_ceil(p->max_sfb, PER_SDBT_SIZE);
|
||||
human(text, sizeof text, PAGE_SIZE * total);
|
||||
printf(" Maximum: %6d"
|
||||
human(text, sizeof(text), PAGE_SIZE * total);
|
||||
printf(" Maximum: %6ld"
|
||||
" sample-data-blocks (%6s)\n\n", p->max_sfb,
|
||||
text);
|
||||
|
||||
@@ -2739,13 +3297,13 @@ static void show_info(struct cpumf_info *p, int details)
|
||||
fdiag = div_ceil(p->diag_sample_sz, p->basic_sample_sz);
|
||||
total = fdiag * p->min_sfb
|
||||
+ div_ceil(p->min_sfb, PER_SDBT_SIZE);
|
||||
human(text, sizeof text, PAGE_SIZE * total);
|
||||
human(text, sizeof(text), PAGE_SIZE * total);
|
||||
printf(" Minimum: %6ld"
|
||||
" sample-data-blocks (%6s)\n",
|
||||
fdiag * p->min_sfb, text);
|
||||
total = fdiag * p->max_sfb
|
||||
+ div_ceil(p->max_sfb * fdiag, PER_SDBT_SIZE);
|
||||
human(text, sizeof text, PAGE_SIZE * total);
|
||||
human(text, sizeof(text), PAGE_SIZE * total);
|
||||
printf(" Maximum: %6ld"
|
||||
" sample-data-blocks (%6s)\n", fdiag * p->max_sfb,
|
||||
text);
|
||||
@@ -2755,27 +3313,6 @@ static void show_info(struct cpumf_info *p, int details)
|
||||
warnx("No CPU-measurement sampling facility detected");
|
||||
}
|
||||
|
||||
/* Read CPU Measurement sampling facility device driver minimum and maximum
|
||||
* buffer size
|
||||
*/
|
||||
static int read_sfb(struct cpumf_info *p)
|
||||
{
|
||||
int rc = EXIT_SUCCESS;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(PERF_SFB_SIZE, "r");
|
||||
if (!fp) {
|
||||
warn(PERF_SFB_SIZE);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (fscanf(fp, "%d,%d", &p->min_sfb, &p->max_sfb) != 2) {
|
||||
warnx("Can not parse %s", PERF_SFB_SIZE);
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
fclose(fp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Set the counter name for z15 counter numbered 265. It is either named
|
||||
* DFLT_CCERROR or DFLT_CCFINISH, depending on the linux version. The
|
||||
* counter was renamed from CCERROR to CCFINISH in linux version 5.8.
|
||||
@@ -2803,81 +3340,23 @@ static void read_ccerror(struct counters *cp, size_t cp_cnt)
|
||||
/* Read allnecessary information from /sysfs file /proc/service_levels */
|
||||
static int read_info(void)
|
||||
{
|
||||
char *linep = NULL;
|
||||
size_t line_sz;
|
||||
ssize_t nbytes;
|
||||
FILE *slp;
|
||||
int rc;
|
||||
int rc = EXIT_FAILURE;
|
||||
|
||||
memset(&cpumf, 0, sizeof cpumf);
|
||||
slp = fopen(SERVICELEVEL, "r");
|
||||
if (!slp) {
|
||||
warn(SERVICELEVEL);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
while ((nbytes = getline(&linep, &line_sz, slp)) != EOF) {
|
||||
if (!strncmp(linep, "CPU-MF: Counter facility:", 25)) {
|
||||
rc = sscanf(linep, "CPU-MF: Counter facility:"
|
||||
" version=%f authorization=%x",
|
||||
&cpumf.version, &cpumf.authorization);
|
||||
if (rc != 2) {
|
||||
warnx("Can not parse line %s", linep);
|
||||
rc = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
cpumf.have_counter = 1;
|
||||
cpumf.first_vn = (int)cpumf.version;
|
||||
cpumf.second_vn = ((int)(10 * cpumf.version) % 10);
|
||||
}
|
||||
if (!strncmp(linep, "CPU-MF: Sampling facility: min", 30)) {
|
||||
rc = sscanf(linep, "CPU-MF: Sampling facility:"
|
||||
" min_rate=%ld max_rate=%ld cpu_speed=%ld",
|
||||
&cpumf.min_rate, &cpumf.max_rate,
|
||||
&cpumf.cpu_speed);
|
||||
if (rc != 3) {
|
||||
warnx("Can not parse line %s", linep);
|
||||
rc = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
cpumf.have_samples = 1;
|
||||
}
|
||||
if (!strncmp(linep, "CPU-MF: Sampling facility: mode=basic", 37)) {
|
||||
rc = sscanf(linep, "CPU-MF: Sampling facility:"
|
||||
" mode=basic sample_size=%u",
|
||||
&cpumf.basic_sample_sz);
|
||||
if (rc != 1) {
|
||||
warnx("Can not parse line %s", linep);
|
||||
rc = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (!strncmp(linep, "CPU-MF: Sampling facility: mode=diag", 36)) {
|
||||
rc = sscanf(linep, "CPU-MF: Sampling facility:"
|
||||
" mode=diagnostic sample_size=%u",
|
||||
&cpumf.diag_sample_sz);
|
||||
if (rc != 1) {
|
||||
warnx("Can not parse line %s", linep);
|
||||
rc = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cpumf.have_samples) {
|
||||
rc = read_sfb(&cpumf);
|
||||
if (rc == EXIT_FAILURE)
|
||||
goto out;
|
||||
}
|
||||
cpumf.have_counter = libcpumf_cpumcf_info(&cpumf.first_vn,
|
||||
&cpumf.second_vn,
|
||||
&cpumf.authorization);
|
||||
cpumf.have_samples = libcpumf_cpumsf_info(&cpumf.min_rate,
|
||||
&cpumf.max_rate,
|
||||
&cpumf.cpu_speed,
|
||||
&cpumf.basic_sample_sz,
|
||||
&cpumf.diag_sample_sz);
|
||||
if (cpumf.have_samples)
|
||||
libcpumf_sfb_info(&cpumf.min_sfb, &cpumf.max_sfb);
|
||||
cpumf.machine_type = util_arch_machine_type();
|
||||
if (cpumf.machine_type == UTIL_ARCH_MACHINE_TYPE_UNKNOWN) {
|
||||
if (cpumf.machine_type == UTIL_ARCH_MACHINE_TYPE_UNKNOWN)
|
||||
rc = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
rc = EXIT_SUCCESS;
|
||||
out:
|
||||
fclose(slp);
|
||||
free(linep);
|
||||
|
||||
else
|
||||
rc = EXIT_SUCCESS;
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -2991,6 +3470,9 @@ static struct counters *get_counter(int ctrset, size_t *len)
|
||||
*len = ARRAY_SIZE(cpumcf_z15_counters);
|
||||
read_ccerror(cp, *len);
|
||||
break;
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z16:
|
||||
cp = cpumcf_z16_counters;
|
||||
*len = ARRAY_SIZE(cpumcf_z16_counters);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3051,14 +3533,18 @@ int main(int argc, char **argv)
|
||||
case ACTION_CNT:
|
||||
case ACTION_CNTALL:
|
||||
all = ret == ACTION_CNTALL;
|
||||
ret = read_cpumf_type(CPUMF_CF_TYPE, "counter");
|
||||
if (ret == EXIT_SUCCESS)
|
||||
ret = libcpumf_pmutype(S390_CPUMF_CF);
|
||||
if (ret >= EXIT_SUCCESS) {
|
||||
set_prefix(ret);
|
||||
show_counter(all);
|
||||
}
|
||||
break;
|
||||
case ACTION_SAMPLE:
|
||||
ret = read_cpumf_type(CPUMF_SF_TYPE, "sampling");
|
||||
if (ret == EXIT_SUCCESS)
|
||||
ret = libcpumf_pmutype(S390_CPUMF_SF);
|
||||
if (ret >= EXIT_SUCCESS) {
|
||||
set_prefix(ret);
|
||||
show_sample();
|
||||
}
|
||||
break;
|
||||
case ACTION_NONE:
|
||||
case ACTION_INFO:
|
||||
|
||||
141
cpumf/lshwc.c
141
cpumf/lshwc.c
@@ -38,17 +38,19 @@
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_scandir.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
#include "lshwc.h"
|
||||
|
||||
#define SERVICELEVEL "/proc/service_levels"
|
||||
#define CPUS_ONLINE "/sys/devices/system/cpu/online"
|
||||
#define CPUS_POSSIBLE "/sys/devices/system/cpu/possible"
|
||||
#define CPUS_KERNELMAX "/sys/devices/system/cpu/kernel_max"
|
||||
#define MAXCTRS 512
|
||||
#define IOCTLSLEEP 60U
|
||||
|
||||
static unsigned int read_interval = IOCTLSLEEP, cfvn, csvn, authorization;
|
||||
static unsigned int read_interval = IOCTLSLEEP;
|
||||
static int cfvn, csvn, authorization;
|
||||
static unsigned long loop_count = 1;
|
||||
static unsigned char *ioctlbuffer;
|
||||
static bool allcpu;
|
||||
@@ -61,25 +63,6 @@ static struct ctrname { /* List of defined counters */
|
||||
unsigned long *ccv; /* Per CPU counter value */
|
||||
} ctrname[MAXCTRS];
|
||||
|
||||
/* Open file and extract counter number */
|
||||
static int read_counter(const char *p)
|
||||
{
|
||||
FILE *fp = fopen(p, "r");
|
||||
int rc = 0, ctr;
|
||||
|
||||
if (fp) {
|
||||
rc = fscanf(fp, "event=%x", &ctr);
|
||||
fclose(fp);
|
||||
}
|
||||
return rc == 1 ? ctr : -EINVAL;
|
||||
}
|
||||
|
||||
static int add_countername(char *name, int nr)
|
||||
{
|
||||
ctrname[nr].name = strdup(name);
|
||||
return ctrname[nr].name ? 0 : -ENOMEM;
|
||||
}
|
||||
|
||||
static bool read_counternames(void)
|
||||
{
|
||||
struct dirent **namelist = NULL;
|
||||
@@ -95,13 +78,12 @@ static bool read_counternames(void)
|
||||
}
|
||||
for (i = 0; i < count && ctr >= 0; i++) {
|
||||
util_asprintf(&ctrpath, "%s/%s", path, namelist[i]->d_name);
|
||||
ctr = read_counter(ctrpath);
|
||||
if (util_file_read_va(ctrpath, "event=%x", &ctr) == 1)
|
||||
ctrname[ctr].name = util_strdup(namelist[i]->d_name);
|
||||
else
|
||||
warnx("Cannot parse %s", ctrpath);
|
||||
free(ctrpath);
|
||||
if (ctr >= 0)
|
||||
ctr = add_countername(namelist[i]->d_name, ctr);
|
||||
}
|
||||
if (ctr < 0)
|
||||
warnx("Cannot parse %s", path);
|
||||
util_scandir_free(namelist, count);
|
||||
free(path);
|
||||
return ctr < 0 ? false : true;
|
||||
@@ -151,20 +133,6 @@ static unsigned long getnumber(char *word, char stopchar)
|
||||
return no;
|
||||
}
|
||||
|
||||
/* Remove all whitespace from string. */
|
||||
static void kill_whitespace(char *s)
|
||||
{
|
||||
char *cp = s;
|
||||
|
||||
for (; *s != '\0'; ++s) {
|
||||
if (isspace(*s))
|
||||
continue;
|
||||
if (isprint(*s))
|
||||
*cp++ = *s;
|
||||
}
|
||||
*cp = '\0';
|
||||
}
|
||||
|
||||
/* Read file to get all online CPUs */
|
||||
static bool get_cpus(char *file, char *buf, size_t bufsz)
|
||||
{
|
||||
@@ -242,29 +210,34 @@ static char *show_ctrset(unsigned long set)
|
||||
static void parse_cpulist(char *parm, struct s390_hwctr_start *start)
|
||||
{
|
||||
uint64_t *words = start->cpumask;
|
||||
unsigned long i, no_a, no_b;
|
||||
char *cp, *tokens[16]; /* Used to parse command line params */
|
||||
char cpubuf[256];
|
||||
unsigned int i, no_a, no_b;
|
||||
cpu_set_t cpulist;
|
||||
int rc;
|
||||
|
||||
CPU_ZERO(&cpulist);
|
||||
start->data_bytes = 0;
|
||||
if (parm)
|
||||
kill_whitespace(parm);
|
||||
if (!parm || *parm == ':') {
|
||||
/* No CPU list or just counter sets */
|
||||
if (!get_cpus(CPUS_ONLINE, cpubuf, sizeof(cpubuf)))
|
||||
exit(EXIT_FAILURE);
|
||||
if (parm)
|
||||
strcat(cpubuf, parm);
|
||||
parm = cpubuf;
|
||||
start->counter_sets = S390_HWCTR_ALL; /* Default all counter sets */
|
||||
|
||||
if (parm) { /* CPU list with optional counter set */
|
||||
char *cp = strchr(parm, ':');
|
||||
|
||||
if (cp) { /* Handle counter set */
|
||||
*cp = '\0';
|
||||
start->counter_sets = parse_ctrset(++cp);
|
||||
}
|
||||
|
||||
if (strlen(parm) > 0) /* Handle CPU list */
|
||||
rc = libcpumf_cpuset(parm, &cpulist);
|
||||
else
|
||||
rc = libcpumf_cpuset_fn(S390_CPUS_ONLINE, &cpulist);
|
||||
if (rc)
|
||||
errx(EXIT_FAILURE, "Cannot use CPU list %s", parm);
|
||||
} else { /* No CPU list and no counter sets */
|
||||
rc = libcpumf_cpuset_fn(S390_CPUS_ONLINE, &cpulist);
|
||||
if (rc)
|
||||
err(EXIT_FAILURE, "Cannot read file " S390_CPUS_ONLINE);
|
||||
}
|
||||
|
||||
cp = strchr(parm, ':');
|
||||
if (cp) { /* Handle counter set */
|
||||
*cp = '\0';
|
||||
start->counter_sets = parse_ctrset(++cp);
|
||||
} else {
|
||||
start->counter_sets = S390_HWCTR_ALL;
|
||||
}
|
||||
/* Check with authorized counter sets */
|
||||
if ((start->counter_sets & authorization) != start->counter_sets) {
|
||||
unsigned int noton = ~(start->counter_sets & authorization);
|
||||
@@ -276,19 +249,10 @@ static void parse_cpulist(char *parm, struct s390_hwctr_start *start)
|
||||
show_ctrset(noton));
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tokens) && (tokens[i] = strtok(parm, ","));
|
||||
++i, parm = NULL) {
|
||||
cp = strchr(tokens[i], '-'); /* Range character? */
|
||||
if (cp) {
|
||||
no_a = getnumber(tokens[i], *cp);
|
||||
no_b = getnumber(++cp, '\0');
|
||||
} else {
|
||||
no_b = getnumber(tokens[i], '\0');
|
||||
no_a = no_b;
|
||||
}
|
||||
if (!check_set(no_a, no_b, start->counter_sets))
|
||||
errx(EXIT_FAILURE, "Invalid CPU list %s", tokens[i]);
|
||||
}
|
||||
for (rc = 0; rc < CPU_SETSIZE; ++rc)
|
||||
if (CPU_ISSET(rc, &cpulist))
|
||||
if (!check_set(rc, rc, start->counter_sets))
|
||||
errx(EXIT_FAILURE, "Invalid CPU %d", rc);
|
||||
|
||||
/* Convert the CPU list to a bitmask for kernel cpumask_t */
|
||||
for (i = 0, no_b = 0; i < max_possible_cpus; ++i) {
|
||||
@@ -632,37 +596,6 @@ static int do_it(char *s)
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* Read counter first and second version number */
|
||||
static bool get_cvn(void)
|
||||
{
|
||||
char *linep = NULL;
|
||||
bool good = false;
|
||||
size_t line_sz;
|
||||
ssize_t nbytes;
|
||||
FILE *slp;
|
||||
|
||||
slp = fopen(SERVICELEVEL, "r");
|
||||
if (!slp) {
|
||||
warn(SERVICELEVEL);
|
||||
return false;
|
||||
}
|
||||
while ((nbytes = getline(&linep, &line_sz, slp)) != EOF) {
|
||||
if (!strncmp(linep, "CPU-MF: Counter facility:", 25)) {
|
||||
int rc;
|
||||
|
||||
rc = sscanf(linep, "CPU-MF: Counter facility: version=%d.%d authorization=%x",
|
||||
&cfvn, &csvn, &authorization);
|
||||
good = rc == 3;
|
||||
if (!good)
|
||||
warnx("Cannot parse line %s", linep);
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(slp);
|
||||
free(linep);
|
||||
return good;
|
||||
}
|
||||
|
||||
static struct util_opt opt_vec[] = {
|
||||
UTIL_OPT_SECTION("OPTIONS"),
|
||||
{
|
||||
@@ -746,7 +679,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
have_support();
|
||||
if (!get_cvn())
|
||||
if (!libcpumf_cpumcf_info(&cfvn, &csvn, &authorization))
|
||||
return EXIT_FAILURE;
|
||||
if (!check_setpossible())
|
||||
return EXIT_FAILURE;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
\" lscpumf.1
|
||||
\" lscpumf.8
|
||||
.\"
|
||||
.\"
|
||||
.\" Copyright IBM Corp. 2014, 2020
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\" ----------------------------------------------------------------------
|
||||
.TH lscpumf "1" "May 2020" "s390-tools" "CPU-MF management programs"
|
||||
.TH lscpumf "8" "May 2022" "s390-tools" "CPU-MF management programs"
|
||||
.
|
||||
.ds c \fBlscpumf\fP
|
||||
.
|
||||
@@ -1,4 +1,4 @@
|
||||
.\" lshwc.1
|
||||
.\" lshwc.8
|
||||
.\"
|
||||
.\"
|
||||
.\" Copyright IBM Corp. 2021
|
||||
@@ -7,7 +7,7 @@
|
||||
.\" ----------------------------------------------------------------------
|
||||
.ds c \fBlshwc\fP
|
||||
.
|
||||
.TH \*c "1" "February 2021" "s390-tools" "CPU-MF management programs"
|
||||
.TH \*c "8" "May 2022" "s390-tools" "CPU-MF management programs"
|
||||
.
|
||||
.SH NAME
|
||||
\*c \- extract CPU Measurement Facilities counter sets
|
||||
@@ -130,5 +130,5 @@ Date,Time,CPU,PROBLEM_STATE_CPU_CYCLES(32),PROBLEM_STATE_INSTRUCTIONS(33)
|
||||
.ft
|
||||
.fi
|
||||
.SH "SEE ALSO"
|
||||
.BR lscpumf (1)
|
||||
.BR lscpumf (8)
|
||||
.BR chcpumf (8)
|
||||
268
cpumf/man/pai.8
Normal file
268
cpumf/man/pai.8
Normal file
@@ -0,0 +1,268 @@
|
||||
.\" pai.8
|
||||
.\"
|
||||
.\"
|
||||
.\" Copyright IBM Corp. 2022
|
||||
.\" 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 \fBpai\fP
|
||||
.
|
||||
.TH \*c "8" "May 2022" "s390-tools" "PAI Management Programs"
|
||||
.
|
||||
.SH NAME
|
||||
\*c \- record and report Processor Activity Instrumentation (PAI) counters
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
\*c
|
||||
.RB [ \-V ][ \-m | \-\-mapsize
|
||||
.IR size ]
|
||||
.RB [ \-i | \-\-interval
|
||||
.IR ms ]
|
||||
.BR \-c | \-\-crypto [ \fIcpulist ][: \fIdata\fR "] [" \fIloops\fP ]
|
||||
.br
|
||||
\*c
|
||||
.RB [ \-V ][ \-m | \-\-mapsize
|
||||
.IR size ]
|
||||
.RB [ \-i | \-\-interval
|
||||
.IR ms ]
|
||||
.BR \-n | \-\-nnpa [ \fIcpulist ][: \fIdata\fR "] [" \fIloops\fP ]
|
||||
.br
|
||||
\*c
|
||||
.RB [ \-V ][ \-H | \-\-humantime ][ \-S | \-\-summary "] " \-r | \-\-report " [" \fIfiles\fP ]
|
||||
.br
|
||||
\*c
|
||||
.BR \-h | \-\-help
|
||||
.br
|
||||
\*c
|
||||
.BR \-v | \-\-version
|
||||
.
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
\*c
|
||||
counters tally calls for specific CPU instructions.
|
||||
The \*c command records PAI counters in a ring buffer.
|
||||
\*c can record counter data for all CPUs or for selected CPUs.
|
||||
The main command options are
|
||||
.B \-c
|
||||
for recording cryptographic CPU instructions,
|
||||
.B \-n
|
||||
for recording NNPA CPU instructions
|
||||
and
|
||||
.B \-r
|
||||
for reporting.
|
||||
If all three options are omitted, option
|
||||
.B \-r
|
||||
is assumed and a message is printed.
|
||||
Recording stores data, by CPU, in files
|
||||
.I paicrypto.<XXX>,
|
||||
for option
|
||||
.B \-c
|
||||
or
|
||||
.I painnpa.<XXX>,
|
||||
for option
|
||||
.B \-n
|
||||
where <XXX> specifies the CPU number with leading
|
||||
zeros.
|
||||
The files are created in the working directory,
|
||||
existing files are overwritten.
|
||||
Reporting evaluates files that are created by recording.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-c ", " \-\-crypto "\fR[\fIcpulist\fR][:\fIdata\fR]"
|
||||
Records data for all (default) or a specified list of CPUs.
|
||||
The CPU list is a comma-separated list of CPU numbers and ranges.
|
||||
In a range, a hyphen separates the first CPU number
|
||||
from the last CPU number.
|
||||
By default \*c lists all CPUs.
|
||||
.RS
|
||||
The optional data specification
|
||||
follows the colon
|
||||
and determines additional collection of data.
|
||||
The specification consists of alphabetic
|
||||
characters that can be upper or lower case:
|
||||
.IP c|C
|
||||
Include task rename system calls
|
||||
.B exec
|
||||
and
|
||||
.BR prctl .
|
||||
.IP f|F
|
||||
Include task creation and deletion system calls
|
||||
.B fork
|
||||
and
|
||||
.BR exit .
|
||||
.IP s|S
|
||||
Include context switch records created by the kernel scheduler.
|
||||
.IP k|K
|
||||
Include only PAI cryptographic counters changed during system call
|
||||
execution.
|
||||
.IP u|U
|
||||
Include only PAI cryptographic counters changed during user space
|
||||
execution.
|
||||
.RE
|
||||
.
|
||||
.TP
|
||||
.BR \-n ", " \-\-nnpa "\fR[\fIcpulist\fR][:\fIdata\fR]"
|
||||
Records data for all (default) or a specified list of CPUs.
|
||||
The CPU list is a comma-separated list of CPU numbers and ranges.
|
||||
In a range, a hyphen separates the first CPU number
|
||||
from the last CPU number.
|
||||
By default \*c lists all CPUs.
|
||||
.RS
|
||||
The optional data specification
|
||||
follows the colon
|
||||
and determines additional collection of data.
|
||||
The specification consists of alphabetic
|
||||
characters that can be upper or lower case:
|
||||
.IP c|C
|
||||
Include task rename system calls
|
||||
.B exec
|
||||
and
|
||||
.BR prctl .
|
||||
.IP f|F
|
||||
Include task creation and deletion system calls
|
||||
.B fork
|
||||
and
|
||||
.BR exit .
|
||||
.IP s|S
|
||||
Include context switch records created by the kernel scheduler.
|
||||
.RE
|
||||
.
|
||||
.TP
|
||||
.BR \-r ", " \-\-report
|
||||
Generates a report from the specified files.
|
||||
Files is a list of blank-separated file names.
|
||||
If no files are specified,
|
||||
uses all files named
|
||||
.I paicrypto.<X>,
|
||||
in the working directory, where
|
||||
.I <X>
|
||||
is a CPU number that
|
||||
identifies the CPU for which the data was recorded.
|
||||
.
|
||||
.TP
|
||||
.BR \-h ", " \-\-help
|
||||
Displays help information, then exits.
|
||||
.
|
||||
.TP
|
||||
.BR \-v ", " \-\-version
|
||||
Displays version information, then exits.
|
||||
.
|
||||
.TP
|
||||
.BR \-V ", " \-\-verbose
|
||||
Displays the following information during reporting:
|
||||
the file name and the hexadecimal offset for each sample header.
|
||||
Displays the following information during recording:
|
||||
the read position in the ring buffer,
|
||||
the file name, and the hexadecimal offset for each sample header.
|
||||
.
|
||||
.TP
|
||||
.BR \-H ", " \-\-humantime
|
||||
Changes the time stamp format for sample entries from a hexadecimal
|
||||
number to a human readable <seconds>.<nanoseconds> format.
|
||||
The time stamp specifies the elapsed time since Linux was booted.
|
||||
.
|
||||
.TP
|
||||
.BR \-S ", " \-\-summary
|
||||
Prints a summary of counter names and
|
||||
counter numbers with non-zero values.
|
||||
The summary is the last line in the command output.
|
||||
It shows the sum of the counter values of all processed files.
|
||||
.
|
||||
.TP
|
||||
.BR \-i ", " \-\-interval "\ ms"
|
||||
Specifies the waiting time,
|
||||
in milliseconds,
|
||||
between ring buffer read operations during recording.
|
||||
The default is 1000 milliseconds.
|
||||
Argument
|
||||
.B loops
|
||||
is an integer that specifies the number of
|
||||
read operations during recording. The default is 1.
|
||||
.
|
||||
.TP
|
||||
.BR \-m ", " \-\-mapsize "\ size"
|
||||
Specifies the size of the ring buffer
|
||||
that holds the collected data.
|
||||
The value specifies the number of 4 KB pages
|
||||
to be allocated and must be a power of 2.
|
||||
The default size is 512 pages.
|
||||
The ring buffer is created with the
|
||||
.IR mmap (2)
|
||||
system call.
|
||||
.
|
||||
.SH ARGUMENT
|
||||
The command line options determine how command line
|
||||
arguments are interpreted.
|
||||
For option
|
||||
.B \-c
|
||||
to start recording, the argument specifies
|
||||
the number of read operations.
|
||||
If omitted, the default is one.
|
||||
For option
|
||||
.B \-r
|
||||
to start reporting, the argument specifies
|
||||
the file names to be read.
|
||||
If omitted all files in the current directory
|
||||
with file name
|
||||
.I paicryto.<X>
|
||||
are read.
|
||||
.SH "Concurrency with perf tool"
|
||||
The following concurrency restrictions apply
|
||||
for \*c and the
|
||||
.B perf
|
||||
tool.
|
||||
Both tools use the
|
||||
.IR perf_event_open ()
|
||||
system call and use the same device driver.
|
||||
The device driver supports limited concurrency:
|
||||
.IP Counting:
|
||||
Counting pai events can run in parallel.
|
||||
.IP Sampling:
|
||||
Only one sampling event pai_crypto/CRYPTO_ALL can be active at any one time.
|
||||
If a sampling event is active, no pai counting event can be active.
|
||||
Both tools stop with an error message if they detect a collision
|
||||
reported by the device driver.
|
||||
.SH "EXAMPLES"
|
||||
This example collects cryptographic counter on CPU 0.
|
||||
The program runs for 10 seconds
|
||||
(10 intervals of 1000 milliseconds).
|
||||
.sp 1
|
||||
.nf
|
||||
.ft CW
|
||||
# \*c -c0 10
|
||||
.ft R
|
||||
.fi
|
||||
.sp 1
|
||||
This example displays the data
|
||||
that is collected in the first example.
|
||||
Each data line shows the time stamp in jiffies,
|
||||
followed by the CPU number,
|
||||
the event number,
|
||||
process and thread identifier (pid/tid) separated by slash (/),
|
||||
and a comma-separated list of counter number, colon (:),
|
||||
and the counter value n hexadecimal notation.
|
||||
.sp 1
|
||||
.nf
|
||||
.ft CW
|
||||
# \*c -r
|
||||
0x62a668f2fa 0 event 4096 sample pid 4956/4956 9:0xa7,73:0x8,74:0x18
|
||||
0x6319c75653 0 event 4096 sample pid 4972/4972 32:0x1
|
||||
0x6319e2ddee 0 event 4096 sample pid 4972/4972 32:0x1
|
||||
0x631d3e44f5 0 event 4096 sample pid 4972/4972 32:0x2
|
||||
0x631d4cfc2e 0 event 4096 sample pid 4972/4972 32:0x3
|
||||
0x631d529fdd 0 event 4096 sample pid 4972/4972 32:0x2
|
||||
0x631d84cfa0 0 event 4096 sample pid 4972/4972 73:0x1
|
||||
0x636e9826bc 0 event 4096 sample pid 4984/4984 9:0xa7,73:0x8,74:0x18
|
||||
0x636f81a137 0 event 4096 sample pid 4984/4984 9:0x2,74:0x7
|
||||
0x6378026e54 0 event 4096 sample pid 4984/4984 73:0x2
|
||||
0x637bcdc8da 0 event 4096 sample pid 4984/4984 73:0x2
|
||||
0x637bd426cd 0 event 4096 sample pid 4984/4984 73:0x6
|
||||
0x637c503384 0 event 4096 sample pid 4984/4984 73:0x2
|
||||
0x64991d83ba 0 event 4096 sample pid 5026/5026 9:0xb0,73:0x8,74:0x18
|
||||
#
|
||||
.ft R
|
||||
.fi
|
||||
.sp 1
|
||||
.SH "SEE ALSO"
|
||||
perf(1)
|
||||
1121
cpumf/pai.c
Normal file
1121
cpumf/pai.c
Normal file
File diff suppressed because it is too large
Load Diff
105
cpumf/pai.h
Normal file
105
cpumf/pai.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* pai - Extract CPU Processor Activity Instrumentation (PAI) facility data.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 PAI_H
|
||||
#define PAI_H
|
||||
|
||||
#define S390_EVT_PAI_CRYPTO 0x1000
|
||||
|
||||
enum { /* Event attribute specifications */
|
||||
S390_EVTATTR_CTX_SWITCH = 1,
|
||||
S390_EVTATTR_COMM = 2,
|
||||
S390_EVTATTR_FORK = 4,
|
||||
S390_EVTATTR_USERSPACE = 8,
|
||||
S390_EVTATTR_KERNELSPACE = 16
|
||||
};
|
||||
|
||||
struct pai_event {
|
||||
struct util_list_node node; /* List node */
|
||||
struct perf_event_attr attr; /* Perf_event_open(2) attr */
|
||||
int fd; /* Perf event file descriptor */
|
||||
void *map_addr; /* Sampling data mapping address */
|
||||
size_t map_size; /* Sampling size mapping size */
|
||||
char file_name[16]; /* File name of sampled data */
|
||||
int file_fd; /* Map data output file descriptor */
|
||||
int cpu; /* Perf_event_open(2) CPU */
|
||||
unsigned long flags; /* Perf_event_open(2) flags */
|
||||
};
|
||||
|
||||
struct pai_event_out { /* Output for CRYPTO_ALL event */
|
||||
__u32 type; /* Header type, see PERF_RECORD_xxx */
|
||||
__u16 misc; /* Header misc, value depends on type */
|
||||
__u64 time; /* Time stamp valid for all entries */
|
||||
__u32 cpu; /* CPU number valid for all entries */
|
||||
union {
|
||||
/* Fields from PERF_RECORD_FORK|EXIT */
|
||||
struct {
|
||||
__u32 pid, ppid;
|
||||
__u32 tid, ptid;
|
||||
__u64 time;
|
||||
} s_fork;
|
||||
/* Fields from PERF_RECORD_COMM */
|
||||
struct {
|
||||
__u32 pid, tid;
|
||||
__u8 cmd[16];
|
||||
} s_comm;
|
||||
/* Fields from PERF_RECORD_SWITCH_CPU_WIDE */
|
||||
struct {
|
||||
__u32 next_prev_pid, next_prev_tid;
|
||||
} s_cs;
|
||||
/* Fields from PERF_RECORD_LOST_SAMPLES */
|
||||
struct {
|
||||
__u64 lost;
|
||||
} s_lost;
|
||||
/* Relevant fields from PERF_RECORD_SAMPLE, time and cpu
|
||||
* are stored above
|
||||
*/
|
||||
struct {
|
||||
__u64 ip;
|
||||
__u64 period;
|
||||
__u32 pid, tid;
|
||||
} s_sample;
|
||||
/* Fields from PERF_RECORD_[UN]THROTTLE */
|
||||
struct {
|
||||
__u64 time;
|
||||
__u64 id;
|
||||
__u64 stream_id;
|
||||
} s_throttle;
|
||||
} u;
|
||||
/* Information on last context switch out */
|
||||
struct cs_switch {
|
||||
unsigned char valid;
|
||||
__u32 topid, totid;
|
||||
__u32 frompid, fromtid;
|
||||
} cs_switch;
|
||||
void *raw; /* Pointer to key/value array for crypto counters */
|
||||
};
|
||||
|
||||
struct data_pos { /* Perf event mapped ring buffer */
|
||||
__u64 data_head; /* Head in the data section */
|
||||
__u64 data_tail; /* User-space written tail */
|
||||
__u64 data_offset; /* Where the buffer starts */
|
||||
__u64 data_size; /* Data buffer size */
|
||||
};
|
||||
|
||||
struct event_name { /* Event list for number to name xlate */
|
||||
char *name; /* Event name */
|
||||
__u64 config; /* Event config value */
|
||||
__u64 total; /* Total counter value */
|
||||
};
|
||||
|
||||
struct pmu_events { /* Event list for PMU number to name xlate */
|
||||
struct util_list_node node; /* List node */
|
||||
char *name; /* PMU name */
|
||||
int type; /* PMU type */
|
||||
int lstlen; /* # of entries in lst */
|
||||
unsigned long base; /* Base event number */
|
||||
struct event_name *lst; /* List of event names */
|
||||
};
|
||||
#endif /* PAI_H */
|
||||
@@ -47,7 +47,7 @@ Print DASD uid without z/VM minidisk token
|
||||
|
||||
z/VM may provide an additional token that can be used to distinguish
|
||||
between different minidisks (see --extended-uid option). To remain
|
||||
compatibile with systems that were installed on older Linux or z/VM
|
||||
compatible with systems that were installed on older Linux or z/VM
|
||||
levels, the -u option will print the uid excluding any z/VM-provided
|
||||
minidisk token.
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
#!/bin/bash
|
||||
### BEGIN INIT INFO
|
||||
# Provides: cpacfstatsd
|
||||
# Required-Start: $local_fs
|
||||
# Required-Stop: $local_fs
|
||||
# Should-Start:
|
||||
# Should-Stop:
|
||||
# Default-Start: 2 3 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Start the cpacfstatsd daemon for Linux on System z
|
||||
# Description: CPACF statistics collection daemon process for Linux on System z
|
||||
### END INIT INFO
|
||||
|
||||
DAEMON=cpacfstatsd
|
||||
DAEMON_PATH=/usr/sbin/cpacfstatsd
|
||||
RUN_PID_FILE=/run/cpacfstatsd.pid
|
||||
RETVAL=0
|
||||
OPTIONS=""
|
||||
|
||||
# source function library
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
start()
|
||||
{
|
||||
if [ ! -f $RUN_PID_FILE ]; then
|
||||
echo -n $"Starting $DAEMON:"
|
||||
$DAEMON_PATH $OPTIONS
|
||||
if [ $? == "0" ]; then
|
||||
log_success_msg
|
||||
else
|
||||
log_failure_msg
|
||||
fi
|
||||
echo
|
||||
else
|
||||
echo "$DAEMON (pid $(cat $RUN_PID_FILE)) is already running..."
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
stop()
|
||||
{
|
||||
echo -n $"Stopping $DAEMON:"
|
||||
if [ -f $RUN_PID_FILE ]; then
|
||||
killproc $DAEMON_PATH -TERM
|
||||
log_success_msg
|
||||
rm -f $RUN_PID_FILE
|
||||
else
|
||||
log_failure_msg
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
||||
status()
|
||||
{
|
||||
if [ ! -f $RUN_PID_FILE ]; then
|
||||
echo "$DAEMON is not running."
|
||||
echo
|
||||
else
|
||||
echo "$DAEMON (pid $(cat $RUN_PID_FILE)) is running."
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
# How are we called?
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $DAEMON {start|stop|status|restart}"
|
||||
RETVAL=1
|
||||
esac
|
||||
|
||||
exit $RETVAL
|
||||
@@ -1,116 +0,0 @@
|
||||
#!/bin/bash
|
||||
### BEGIN INIT INFO
|
||||
# Provides: cpuplugd
|
||||
# Required-Start: $local_fs $remote_fs
|
||||
# Required-Stop: $local_fs $remote_fs
|
||||
# Should-Start:
|
||||
# Should-Stop:
|
||||
# Default-Start: 2 3 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Start the cpu hotplug daemon for Linux on System z
|
||||
# Description: Starts the cpuplugd. It uses the configuration
|
||||
# file /etc/cpuplugd.conf
|
||||
### END INIT INFO
|
||||
|
||||
# chkconfig: 235 01 99
|
||||
|
||||
DAEMON=cpuplugd
|
||||
DAEMON_PATH=/usr/sbin/cpuplugd
|
||||
CONFIG_FILE=/etc/cpuplugd.conf
|
||||
RUN_PID_FILE=/run/cpuplugd.pid
|
||||
RETVAL=0
|
||||
OPTIONS="-c $CONFIG_FILE"
|
||||
|
||||
# source function library
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
start()
|
||||
{
|
||||
if [ ! -f $RUN_PID_FILE ]; then
|
||||
echo -n $"Starting $DAEMON:"
|
||||
$DAEMON_PATH $OPTIONS
|
||||
if [ $? == "0" ]; then
|
||||
log_success_msg
|
||||
else
|
||||
log_failure_msg
|
||||
fi
|
||||
echo
|
||||
else
|
||||
echo "$DAEMON (pid $(cat $RUN_PID_FILE)) is already running..."
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
stop()
|
||||
{
|
||||
echo -n $"Stopping $DAEMON:"
|
||||
if [ -f $RUN_PID_FILE ]; then
|
||||
killproc $DAEMON_PATH -TERM
|
||||
log_success_msg
|
||||
rm -f $RUN_PID_FILE
|
||||
else
|
||||
log_failure_msg
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
#
|
||||
# We have to wait 2-3 seconds here. When the daemon is stopped it takes
|
||||
# the time we sleep to reactivate cpus. If we restart to fast and
|
||||
# cpuplugd wasn't able to restore some settings we may get a undesired
|
||||
# online cpu count after cpuplugd shutdown
|
||||
#
|
||||
sleep 4
|
||||
start
|
||||
}
|
||||
|
||||
status()
|
||||
{
|
||||
if [ ! -f $RUN_PID_FILE ]; then
|
||||
echo "$DAEMON is not running."
|
||||
echo
|
||||
else
|
||||
echo "$DAEMON (pid $(cat $RUN_PID_FILE), options: $OPTIONS) is running."
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
reload()
|
||||
{
|
||||
echo -n $"Reloading $DAEMON: "
|
||||
if [ -f $RUN_PID_FILE ]; then
|
||||
killproc $DAEMON_PATH -HUP
|
||||
log_success_msg
|
||||
else
|
||||
log_failure_msg
|
||||
fi
|
||||
RETVAL=$?
|
||||
echo
|
||||
}
|
||||
|
||||
|
||||
# How are we called?
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
reload|force-reload)
|
||||
reload
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $DAEMON {start|stop|status|restart|reload}"
|
||||
RETVAL=1
|
||||
esac
|
||||
|
||||
exit $RETVAL
|
||||
@@ -1,161 +0,0 @@
|
||||
#!/bin/bash
|
||||
### BEGIN INIT INFO
|
||||
# Provides: mon_statd
|
||||
# Required-Start: $local_fs $remote_fs
|
||||
# Required-Stop: $local_fs $remote_fs
|
||||
# Should-Start:
|
||||
# Should-Stop:
|
||||
# Default-Start: 2 3 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Configure the mon_fsstatd and mon_procd daemons.
|
||||
# Description: Configures the mon_fsstatd and mon_procd daemons. It uses the
|
||||
# configuration file /etc/sysconfig/mon_statd.
|
||||
### END INIT INFO
|
||||
|
||||
# chkconfig: 235 01 99
|
||||
|
||||
DAEMON=mon_statd
|
||||
FSSTATD=mon_fsstatd
|
||||
PROCD=mon_procd
|
||||
FSSTATD_PATH=/usr/sbin/$FSSTATD
|
||||
PROCD_PATH=/usr/sbin/$PROCD
|
||||
CONFIG_FILE=/etc/sysconfig/$DAEMON
|
||||
FSSTATD_PID_FILE=/run/$FSSTATD.pid
|
||||
PROCD_PID_FILE=/run/$PROCD.pid
|
||||
|
||||
# source function library
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Source config file
|
||||
if [ -f $CONFIG_FILE ]; then
|
||||
. $CONFIG_FILE
|
||||
fi
|
||||
|
||||
UDEVSETTLE=/usr/bin/udevadm
|
||||
if [ ! -e $UDEVSETTLE ]
|
||||
then
|
||||
UDEVSETTLE=/sbin/udevsettle
|
||||
UDEVSETTLE_CALL="$UDEVSETTLE --timeout=10"
|
||||
else
|
||||
UDEVSETTLE_CALL="$UDEVSETTLE settle --timeout=10"
|
||||
fi
|
||||
|
||||
load_kernel_module()
|
||||
{
|
||||
if [ ! -e /dev/monwriter ]; then
|
||||
echo "Loading monwriter module..."
|
||||
modprobe monwriter 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
if [ -e $UDEVSETTLE ]
|
||||
then
|
||||
$UDEVSETTLE_CALL
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
start_daemon()
|
||||
{
|
||||
local daemon_name=$1
|
||||
local daemon_interval=$2
|
||||
local daemon_pid_file=$3
|
||||
local daemon_path=$4
|
||||
|
||||
if [ ! -f $daemon_pid_file ]; then
|
||||
load_kernel_module
|
||||
echo -n "Starting $daemon_name:"
|
||||
$daemon_path -i $daemon_interval
|
||||
if [ $? -eq 0 ]; then
|
||||
log_success_msg
|
||||
else
|
||||
log_failure_msg
|
||||
fi
|
||||
else
|
||||
echo "$daemon_name (pid $(cat $daemon_pid_file)) is already running..."
|
||||
fi
|
||||
}
|
||||
|
||||
start()
|
||||
{
|
||||
if [ "$FSSTAT" = "yes" ]; then
|
||||
start_daemon $FSSTATD $FSSTAT_INTERVAL $FSSTATD_PID_FILE \
|
||||
$FSSTATD_PATH
|
||||
fi
|
||||
|
||||
if [ "$PROC" = "yes" ]; then
|
||||
start_daemon $PROCD $PROC_INTERVAL $PROCD_PID_FILE \
|
||||
$PROCD_PATH
|
||||
fi
|
||||
}
|
||||
|
||||
stop_daemon()
|
||||
{
|
||||
local daemon_name=$1
|
||||
local daemon_pid_file=$2
|
||||
local daemon_path=$3
|
||||
|
||||
echo -n "Stopping $daemon_name:"
|
||||
if [ -f $daemon_pid_file ]; then
|
||||
killproc $daemon_path -TERM
|
||||
log_success_msg
|
||||
rm -f $daemon_pid_file
|
||||
else
|
||||
log_failure_msg
|
||||
fi
|
||||
}
|
||||
|
||||
stop()
|
||||
{
|
||||
if [ "$FSSTAT" = "yes" ]; then
|
||||
stop_daemon $FSSTATD $FSSTATD_PID_FILE $FSSTATD_PATH
|
||||
fi
|
||||
if [ "$PROC" = "yes" ]; then
|
||||
stop_daemon $PROCD $PROCD_PID_FILE $PROCD_PATH
|
||||
fi
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
status_daemon()
|
||||
{
|
||||
local daemon_name=$1
|
||||
local daemon_pid_file=$2
|
||||
local daemon_interval=$3
|
||||
|
||||
if [ ! -f $daemon_pid_file ]; then
|
||||
echo "$daemon_name is not running."
|
||||
else
|
||||
echo "$daemon_name (pid $(cat $daemon_pid_file), interval: $daemon_interval) is running."
|
||||
fi
|
||||
}
|
||||
|
||||
status()
|
||||
{
|
||||
status_daemon $FSSTATD $FSSTATD_PID_FILE $FSSTAT_INTERVAL
|
||||
status_daemon $PROCD $PROCD_PID_FILE $PROC_INTERVAL
|
||||
}
|
||||
|
||||
# How are we called?
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
restart|reload|force-reload)
|
||||
restart
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $DAEMON {start|stop|status|restart|reload}"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -408,8 +408,10 @@ static int read_line(void)
|
||||
{
|
||||
bzero(line_buffer, LINE_LENGTH);
|
||||
line_ptr = line_buffer;
|
||||
if (!fgets(line_buffer, LINE_LENGTH, stdin))
|
||||
if (!fgets(line_buffer, LINE_LENGTH, stdin)) {
|
||||
clearerr(stdin);
|
||||
return 0;
|
||||
}
|
||||
while (*line_ptr && !isgraph(*line_ptr))
|
||||
line_ptr++;
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ ALL_CFLAGS := $(NO_PIE_CFLAGS) -Os -g \
|
||||
-fno-delete-null-pointer-checks -fno-stack-protector \
|
||||
-fexec-charset=IBM1047 -m64 -mpacked-stack \
|
||||
-mstack-size=4096 -mstack-guard=128 -msoft-float \
|
||||
-Wall -Wformat-security -Wextra -Werror
|
||||
-Wall -Wformat-security -Wextra -Werror \
|
||||
-Wno-array-bounds
|
||||
|
||||
FILES := stage3a.bin stage3b.bin stage3b_reloc.bin
|
||||
|
||||
@@ -74,11 +75,11 @@ stage3b_reloc.elf:
|
||||
|
||||
%.elf: %.o
|
||||
case $* in \
|
||||
stage3a) SFLAGS="$(NO_PIE_LINKFLAGS) -nostdlib -Wl,-T,stage3a.lds";; \
|
||||
stage3b) SFLAGS="$(NO_PIE_LINKFLAGS) -nostdlib -Wl,-T,stage3b.lds";; \
|
||||
stage3b_reloc) SFLAGS="$(NO_PIE_LINKFLAGS) -nostdlib -Wl,-estage3b_reloc_start,-Ttext,0";; \
|
||||
stage3a) SFLAGS="-Wl,-T,stage3a.lds";; \
|
||||
stage3b) SFLAGS="-Wl,-T,stage3b.lds";; \
|
||||
stage3b_reloc) SFLAGS="-Wl,-estage3b_reloc_start,-Ttext,0";; \
|
||||
esac; \
|
||||
$(LINK) $$SFLAGS -m64 $(filter %.o, $^) -o $@
|
||||
$(LINK) $$SFLAGS $(NO_PIE_LDFLAGS) -m64 -static -nostdlib $(filter %.o, $^) -o $@
|
||||
@chmod a-x $@
|
||||
|
||||
%.bin: %.elf
|
||||
|
||||
@@ -4,8 +4,8 @@ include ../../common.mak
|
||||
all:
|
||||
|
||||
install:
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8
|
||||
$(INSTALL) -m 644 -c genprotimg.8 $(DESTDIR)$(MANDIR)/man8
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man1
|
||||
$(INSTALL) -m 644 -c genprotimg.1 $(DESTDIR)$(MANDIR)/man1
|
||||
|
||||
clean:
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\"
|
||||
.TH GENPROTIMG 8 "November 2020" "s390-tools"
|
||||
.TH GENPROTIMG 1 "May 2022" "s390-tools"
|
||||
.SH NAME
|
||||
genprotimg \- Create a protected virtualization image
|
||||
|
||||
@@ -87,13 +87,23 @@ CRLs. Optional.
|
||||
.TP
|
||||
\fB\-\-root\-ca\fR=\fI\,FILE\/\fR
|
||||
Specifies the root CA certificate for the verification. If omitted,
|
||||
the DigiCert root CA certificate installed on the system is used. Use
|
||||
the system wide root CAs installed on the system is used. Use
|
||||
this only if you trust the specified certificate. Optional.
|
||||
.TP
|
||||
\fB\-\-no-verify\fR
|
||||
Do not require the host-key documents to be valid. For testing
|
||||
purposes, do not use for a production image. 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.
|
||||
.TP
|
||||
\fB\-\-enable\-dump\fR
|
||||
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.
|
||||
.TP
|
||||
\fB\-\-enable\-pckmo\fR
|
||||
Enable the support for the DEA, TDEA, AES, and ECC PCKMO key encryption
|
||||
functions. This is the default. Optional.
|
||||
@@ -105,21 +115,25 @@ functions. Optional.
|
||||
\fB\-v\fR, \fB\-\-version\fR
|
||||
Prints version information, then exits.
|
||||
|
||||
.SH EXAMPLE
|
||||
.SH EXAMPLES
|
||||
|
||||
These are examples to generate a protected virtualization image in
|
||||
\fI\,/boot/vmlinuz.pv\/\fR, using the kernel file \fI\,vmlinuz\/\fR, the
|
||||
initrd in \fI\,initramfs\/\fR, the kernel parameters contained in
|
||||
\fI\,parmfile\/\fR, the intermediate CA in \fI\,DigiCertCA.crt\/\fR, the
|
||||
IBM Z signing key in \fI\,ibm-z-host-key-signing.crt\/\fR, and the
|
||||
host-key document in \fI\,host_key.crt\/\fR. An AES-256 GCM key is stored in
|
||||
\fI\,comm-key\/\fR, which is used for the PV guest dump support in the second
|
||||
example.
|
||||
|
||||
Generate a protected virtualization image:
|
||||
|
||||
.PP
|
||||
Generate a protected virtualization image in
|
||||
\fI\,/boot/vmlinuz.pv\/\fR, using the kernel file \fI\,vmlinuz\/\fR,
|
||||
the initrd in \fI\,initramfs\/\fR, the kernel parameters contained in
|
||||
\fI\,parmfile\/\fR, the intermediate CA in \fI\,DigiCertCA.crt\/\fR,
|
||||
the IBM Z signing key in \fI\,ibm-z-host-key-signing.crt\/\fR, and the
|
||||
host-key document in \fI\,host_key.crt\/\fR:
|
||||
.PP
|
||||
.Vb 1
|
||||
.EX
|
||||
\& genprotimg \-i \fI\,vmlinuz\/\fR \-r \fI\,initramfs\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt \-o \fI\,/boot/vmlinuz.pv\/\fR
|
||||
.EE
|
||||
.Ve
|
||||
.B genprotimg \-i \fI\,vmlinuz\/\fR \-r \fI\,initramfs\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/vmlinuz.pv\/\fR
|
||||
|
||||
Generate a protected virtualization image with PV guest dump support:
|
||||
.PP
|
||||
.B genprotimg \-i \fI\,vmlinuz\/\fR \-r \fI\,initramfs\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/vmlinuz.pv\/\fR \-\-enable\-dump \-\-comm\-key \fI\,comm-key\fR
|
||||
|
||||
.SH NOTES
|
||||
.IP "1." 4
|
||||
@@ -23,6 +23,7 @@ BODY_FILE=$(mktemp)
|
||||
ISSUER_DN_FILE=$(mktemp)
|
||||
SUBJECT_DN_FILE=$(mktemp)
|
||||
DEF_ISSUER_DN_FILE=$(mktemp)
|
||||
CANONICAL_ISSUER_DN_FILE=$(mktemp)
|
||||
CRL_SERIAL_FILE=$(mktemp)
|
||||
|
||||
# Cleanup on exit
|
||||
@@ -30,7 +31,7 @@ cleanup()
|
||||
{
|
||||
rm -f $ISSUER_PUBKEY_FILE $SIGNATURE_FILE $BODY_FILE \
|
||||
$ISSUER_DN_FILE $SUBJECT_DN_FILE $DEF_ISSUER_DN_FILE \
|
||||
$CRL_SERIAL_FILE
|
||||
$CANONICAL_ISSUER_DN_FILE $CRL_SERIAL_FILE
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
@@ -47,13 +48,21 @@ set -e
|
||||
usage()
|
||||
{
|
||||
cat <<-EOF
|
||||
Usage: `basename $1` host-key-doc signing-key-cert [-c CA-cert] [-r CRL]
|
||||
Usage: `basename $1` [-d] [-c CA-cert] [-r CRL] host-key-doc signing-key-cert
|
||||
|
||||
Verify an IBM Secure Execution host key document against
|
||||
a signing key.
|
||||
|
||||
Options:
|
||||
-d disable default issuer check of host-key-doc
|
||||
-c CA-cert trusted CA certificate
|
||||
-r CRL list of revoked host-key-docs
|
||||
|
||||
Note that in order to have the full trust chain verified
|
||||
it is necessary to provide the issueing CA's certificate.
|
||||
it is necessary to provide the issuing CA's certificate.
|
||||
The default issuer check may be disabled if a non-default
|
||||
signing key certificate needs to be verified against the
|
||||
CA certificate.
|
||||
|
||||
EOF
|
||||
}
|
||||
@@ -121,20 +130,34 @@ default_issuer()
|
||||
commonName = International Business Machines Corporation
|
||||
countryName = US
|
||||
localityName = Poughkeepsie
|
||||
organizationalUnitName = IBM Z Host Key Signing Service
|
||||
organizationalUnitName = Key Signing Service
|
||||
organizationName = International Business Machines Corporation
|
||||
stateOrProvinceName = New York
|
||||
EOF
|
||||
}
|
||||
|
||||
verify_issuer_files()
|
||||
# As organizationalUnitName can have an arbitrary prefix but must
|
||||
# end with "Key Signing Service" let's normalize the OU name by
|
||||
# stripping off the prefix
|
||||
verify_default_issuer()
|
||||
{
|
||||
default_issuer > $DEF_ISSUER_DN_FILE
|
||||
|
||||
if ! diff $ISSUER_DN_FILE $DEF_ISSUER_DN_FILE
|
||||
sed "s/\(^[ ]*organizationalUnitName[ ]*=[ ]*\).*\(Key Signing Service$\)/\1\2/" \
|
||||
$ISSUER_DN_FILE > $CANONICAL_ISSUER_DN_FILE
|
||||
|
||||
if ! diff $CANONICAL_ISSUER_DN_FILE $DEF_ISSUER_DN_FILE
|
||||
then
|
||||
echo Incorrect default issuer >&2 && exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_issuer_files()
|
||||
{
|
||||
if [ $1 -eq 1 ]
|
||||
then
|
||||
verify_default_issuer
|
||||
fi
|
||||
|
||||
if diff $ISSUER_DN_FILE $SUBJECT_DN_FILE
|
||||
then
|
||||
@@ -196,14 +219,16 @@ check_file()
|
||||
# check args
|
||||
CRL_FILE=
|
||||
CA_FILE=
|
||||
CHECK_DEFAULT_ISSUER=1
|
||||
|
||||
args=$(getopt -qu "r:c:h" $*)
|
||||
args=$(getopt -qu "dr:c:h" $*)
|
||||
if [ $? = 0 ]
|
||||
then
|
||||
set -- $args
|
||||
while [ $1 != "" ]
|
||||
do
|
||||
case $1 in
|
||||
-d) CHECK_DEFAULT_ISSUER=0; shift;;
|
||||
-r) CRL_FILE=$2; shift 2;;
|
||||
-c) CA_FILE=$2; shift 2;;
|
||||
-h) usage $0; exit 0;;
|
||||
@@ -244,7 +269,7 @@ exit 1
|
||||
# Verify the issuer
|
||||
canonical_dn x509 $HKD_FILE issuer $ISSUER_DN_FILE
|
||||
canonical_dn x509 $HKSK_FILE subject $SUBJECT_DN_FILE
|
||||
verify_issuer_files
|
||||
verify_issuer_files $CHECK_DEFAULT_ISSUER
|
||||
|
||||
# Verify dates
|
||||
verify_dates $(cert_time $HKD_FILE startdate) $(cert_time $HKD_FILE enddate)
|
||||
@@ -261,7 +286,7 @@ then
|
||||
echo -n "CRL "
|
||||
canonical_dn crl $CRL_FILE issuer $ISSUER_DN_FILE
|
||||
canonical_dn x509 $HKSK_FILE subject $SUBJECT_DN_FILE
|
||||
verify_issuer_files
|
||||
verify_issuer_files $CHECK_DEFAULT_ISSUER
|
||||
|
||||
verify_dates $(crl_time $CRL_FILE lastupdate) $(crl_time $CRL_FILE nextupdate) 'CRL'
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright IBM Corp. 2020, 2022
|
||||
*
|
||||
* 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 COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@
|
||||
*/
|
||||
#define PV_CERTS_SECURITY_LEVEL 2
|
||||
|
||||
/* SKID for DigiCert Assured ID Root CA */
|
||||
#define DIGICERT_ASSURED_ID_ROOT_CA_SKID "45EBA2AFF492CB82312D518BA7A7219DF36DC80F"
|
||||
|
||||
union ecdh_pub_key {
|
||||
struct {
|
||||
uint8_t x[80];
|
||||
|
||||
@@ -24,11 +24,15 @@
|
||||
#define PV_MAGIC_NUMBER 0x49424d5365634578ULL
|
||||
#define PV_VERSION_1 0x00000100U
|
||||
|
||||
/* Internal helper macro */
|
||||
#define __PV_BIT(nr) (1ULL << (63 - (nr)))
|
||||
|
||||
/* Plaintext control flags */
|
||||
#define PV_PCF_PCKM_ECC (1ULL << 5) /* PCKMO encrypt-ECC-key functions allowed */
|
||||
#define PV_PCF_PCKMO_AES (1ULL << 6) /* PCKMO encrypt-AES-key functions allowed */
|
||||
#define PV_PCF_PCKMO_DEA_TDEA (1ULL << 7) /* PCKMO encrypt-DEA/TDEA-key functions allowed */
|
||||
#define PV_PCF_NO_DECRYPTION (1ULL << 28) /* prevent Ultravisor decryption during unpack operation */
|
||||
#define PV_PCF_ALLOW_DUMPING __PV_BIT(34) /* dumping of the configuration is allowed */
|
||||
#define PV_PCF_NO_DECRYPTION __PV_BIT(35) /* prevent Ultravisor decryption during unpack operation */
|
||||
#define PV_PCF_PCKMO_DEA_TDEA __PV_BIT(56) /* PCKMO encrypt-DEA/TDEA-key functions allowed */
|
||||
#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 */
|
||||
|
||||
/* maxima for the PV version 1 */
|
||||
#define PV_V1_IPIB_MAX_SIZE PAGE_SIZE
|
||||
|
||||
@@ -64,13 +64,17 @@ static gint pv_args_validate_options(PvArgs *args, GError **err)
|
||||
{
|
||||
PvComponentType KERNEL = PV_COMP_TYPE_KERNEL;
|
||||
|
||||
if (args->pcf && args->allow_pckmo != PV_NOT_SET) {
|
||||
g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
|
||||
_("The '--x-pcf' and '--(enable|disable)-pckmo' options are mutually"
|
||||
" exclusive.\nUse 'genprotimg --help' for more information"));
|
||||
/* Check for mutually exclusive arguments */
|
||||
if (args->pcf && !(args->allow_pckmo == PV_NOT_SET &&
|
||||
args->allow_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"
|
||||
" '--(enable|disable)-dump' flags.\nUse 'genprotimg --help' for more information"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check for unused arguments */
|
||||
if (args->unused_values->len > 0) {
|
||||
g_autofree gchar *unused = NULL;
|
||||
|
||||
@@ -88,6 +92,14 @@ static gint pv_args_validate_options(PvArgs *args, GError **err)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check for mandatory arguments */
|
||||
if (args->allow_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 "
|
||||
"--help' for more information"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!args->output_path) {
|
||||
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
|
||||
_("Option '--output' is required.\nUse 'genprotimg --help' for more information"));
|
||||
@@ -111,7 +123,7 @@ static gint pv_args_validate_options(PvArgs *args, GError **err)
|
||||
g_strv_length(args->untrusted_cert_paths) == 0)) {
|
||||
g_set_error(
|
||||
err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
|
||||
_("Either specify the IBM Z signing key and (DigiCert) intermediate CA certificate\n"
|
||||
_("Either specify the IBM Z signing key and intermediate CA certificate\n"
|
||||
"by using the '--cert' option, or use the '--no-verify' flag to disable the\n"
|
||||
"host-key document verification completely (at your own risk)."));
|
||||
return -1;
|
||||
@@ -155,14 +167,14 @@ static gboolean cb_set_string_option(const gchar *option, const gchar *value,
|
||||
{
|
||||
gchar **args_option = NULL;
|
||||
|
||||
if (g_str_equal(option, "--comm-key"))
|
||||
args_option = &args->cust_comm_key_path;
|
||||
if (g_str_equal(option, "--root-ca"))
|
||||
args_option = &args->root_ca_path;
|
||||
if (g_str_equal(option, "-o") || g_str_equal(option, "--output"))
|
||||
args_option = &args->output_path;
|
||||
if (g_str_equal(option, "--x-comp-key"))
|
||||
args_option = &args->xts_key_path;
|
||||
if (g_str_equal(option, "--x-comm-key"))
|
||||
args_option = &args->cust_comm_key_path;
|
||||
if (g_str_equal(option, "--x-header-key"))
|
||||
args_option = &args->cust_root_key_path;
|
||||
if (g_str_equal(option, "--x-pcf"))
|
||||
@@ -188,19 +200,6 @@ static gboolean cb_set_string_option(const gchar *option, const gchar *value,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean cb_enable_disable_flag(const gchar *option, const gchar *value G_GNUC_UNUSED,
|
||||
PvArgs *args, GError **err G_GNUC_UNUSED)
|
||||
{
|
||||
if (g_str_equal(option, "--enable-pckmo"))
|
||||
args->allow_pckmo = PV_TRUE;
|
||||
else if (g_str_equal(option, "--disable-pckmo"))
|
||||
args->allow_pckmo = PV_FALSE;
|
||||
else
|
||||
g_assert_not_reached();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean cb_set_log_level(const gchar *option G_GNUC_UNUSED,
|
||||
const gchar *value G_GNUC_UNUSED, PvArgs *args,
|
||||
GError **err G_GNUC_UNUSED)
|
||||
@@ -217,8 +216,51 @@ static gboolean cb_remaining_values(const gchar *option G_GNUC_UNUSED,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#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_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 INDENT " "
|
||||
|
||||
/* Define the callbacks for mutually exclusive command line flags */
|
||||
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(dump)
|
||||
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(pckmo)
|
||||
|
||||
gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
|
||||
GError **err)
|
||||
{
|
||||
@@ -282,21 +324,28 @@ 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") },
|
||||
{.long_name = "enable-pckmo",
|
||||
.short_name = 0,
|
||||
.flags = G_OPTION_FLAG_NO_ARG,
|
||||
.arg = G_OPTION_ARG_CALLBACK,
|
||||
.arg_data = cb_enable_disable_flag,
|
||||
.description = _("Enable the support for the DEA, TDEA, AES, and\n" INDENT
|
||||
"ECC PCKMO key encryption functions (default)\n" INDENT
|
||||
"(optional).")},
|
||||
{.long_name = "disable-pckmo",
|
||||
.short_name = 0,
|
||||
.flags = G_OPTION_FLAG_NO_ARG,
|
||||
.arg = G_OPTION_ARG_CALLBACK,
|
||||
.arg_data = cb_enable_disable_flag,
|
||||
.description = _("Disable the support for the DEA, TDEA, AES, and\n" INDENT
|
||||
"ECC PCKMO key encryption functions (optional).")},
|
||||
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).")),
|
||||
{ .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."),
|
||||
.arg_description = _("FILE") },
|
||||
{ .long_name = "crl",
|
||||
.short_name = 0,
|
||||
.flags = G_OPTION_FLAG_NONE,
|
||||
@@ -355,15 +404,6 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
|
||||
};
|
||||
|
||||
GOptionEntry x_entries[] = {
|
||||
{ .long_name = "x-comm-key",
|
||||
.short_name = 0,
|
||||
.flags = G_OPTION_FLAG_FILENAME,
|
||||
.arg = G_OPTION_ARG_CALLBACK,
|
||||
.arg_data = cb_set_string_option,
|
||||
.description = _(
|
||||
"Use FILE as the customer communication key.\n" INDENT
|
||||
"Optional; default: auto-generated."),
|
||||
.arg_description = _("FILE") },
|
||||
{ .long_name = "x-comp-key",
|
||||
.short_name = 0,
|
||||
.flags = G_OPTION_FLAG_FILENAME,
|
||||
@@ -446,6 +486,7 @@ 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;
|
||||
return g_steal_pointer(&args);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ typedef struct {
|
||||
gboolean offline;
|
||||
gchar *pcf;
|
||||
gchar *scf;
|
||||
PvTristate allow_dump;
|
||||
PvTristate allow_pckmo;
|
||||
gchar *psw_addr; /* PSW address which will be used for the start of
|
||||
* the actual component (e.g. Linux kernel)
|
||||
|
||||
@@ -229,7 +229,9 @@ static gint pv_img_set_psw_addr(PvImage *img, const gchar *psw_addr_s,
|
||||
}
|
||||
|
||||
static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s,
|
||||
const gchar *scf_s, PvTristate allow_pckmo, GError **err)
|
||||
const gchar *scf_s,
|
||||
PvTristate allow_dump,
|
||||
PvTristate allow_pckmo, GError **err)
|
||||
{
|
||||
uint64_t flags;
|
||||
|
||||
@@ -247,6 +249,11 @@ static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s,
|
||||
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)
|
||||
@@ -304,9 +311,10 @@ static gint pv_img_hostkey_verify(GSList *host_key_certs,
|
||||
}
|
||||
|
||||
/* Load all untrusted certificates (e.g. IBM Z signing key and
|
||||
* DigiCert intermediate CA) that are required to establish a chain of
|
||||
* trust starting from the host-key document up to the root CA (if not
|
||||
* otherwise specified that's the DigiCert Assured ID Root CA).
|
||||
* intermediate CA) that are required to establish a chain of trust
|
||||
* starting from the host-key document up to the root CA (if not
|
||||
* otherwise specified that can be one of the system wide installed
|
||||
* root CAs, e.g. DigiCert).
|
||||
*/
|
||||
untrusted_certs_with_path = load_certificates(untrusted_cert_paths, err);
|
||||
if (!untrusted_certs_with_path)
|
||||
@@ -341,9 +349,8 @@ static gint pv_img_hostkey_verify(GSList *host_key_certs,
|
||||
* For this we must check:
|
||||
*
|
||||
* 1. Can a chain of trust be established ending in a root CA
|
||||
* 2. Is the correct root CA ued? It has either to be the
|
||||
* 'DigiCert Assured ID Root CA' or the root CA specified via
|
||||
* command line.
|
||||
* 2. Is the correct root CA used? It has either to be a system CA
|
||||
* or the root CA specified via command line.
|
||||
*/
|
||||
for (gint i = 0; i < sk_X509_num(ibm_signing_certs); ++i) {
|
||||
X509 *ibm_signing_cert = sk_X509_value(ibm_signing_certs, i);
|
||||
@@ -364,17 +371,12 @@ static gint pv_img_hostkey_verify(GSList *host_key_certs,
|
||||
if (verify_cert(ibm_signing_cert, ctx, err) < 0)
|
||||
goto error;
|
||||
|
||||
/* Verify the build chain of trust chain. If the user passes a
|
||||
* trusted root CA on the command line then the check for the
|
||||
* Subject Key Identifier (SKID) is skipped, otherwise let's
|
||||
* check if the SKID meets our expectation.
|
||||
/* If there is a chain of trust using either the provided root
|
||||
* CA on the command line or a system wide trusted root CA.
|
||||
*/
|
||||
if (!root_ca_path &&
|
||||
check_chain_parameters(X509_STORE_CTX_get0_chain(ctx),
|
||||
get_digicert_assured_id_root_ca_skid(),
|
||||
err) < 0) {
|
||||
if (check_chain_parameters(X509_STORE_CTX_get0_chain(ctx),
|
||||
err) < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ibm_signing_crls = store_ctx_find_valid_crls(ctx, ibm_signing_cert, err);
|
||||
if (!ibm_signing_crls) {
|
||||
@@ -588,7 +590,7 @@ PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err)
|
||||
g_warning(_("host-key document verification is disabled. Your workload is not secured."));
|
||||
|
||||
if (args->root_ca_path)
|
||||
g_warning(_("A different root CA than the default DigiCert root CA is selected. Ensure that this root CA is trusted."));
|
||||
g_warning(_("The root CA is selected through the command line. Ensure that this root CA is trusted."));
|
||||
|
||||
ret->comps = pv_img_comps_new(EVP_sha512(), EVP_sha512(), EVP_sha512(), err);
|
||||
if (!ret->comps)
|
||||
@@ -608,7 +610,9 @@ 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_pckmo, err) < 0)
|
||||
if (pv_img_set_control_flags(ret, args->pcf, args->scf,
|
||||
args->allow_dump, args->allow_pckmo,
|
||||
err) < 0)
|
||||
return NULL;
|
||||
|
||||
/* read in the keys */
|
||||
|
||||
@@ -1079,8 +1079,8 @@ int store_set_verify_param(X509_STORE *store, GError **err)
|
||||
g_abort();
|
||||
|
||||
/* The maximum depth level of the chain of trust for the verification of
|
||||
* the IBM Z signing key is 2, i.e. IBM Z signing key -> (DigiCert)
|
||||
* intermediate CA -> (DigiCert) root CA
|
||||
* the IBM Z signing key is 2, i.e. IBM Z signing key -> intermediate CA
|
||||
* -> root CA
|
||||
*/
|
||||
X509_VERIFY_PARAM_set_depth(param, 2);
|
||||
|
||||
@@ -1267,46 +1267,38 @@ static int security_level_to_bits(int level)
|
||||
return security_bits[level];
|
||||
}
|
||||
|
||||
static ASN1_OCTET_STRING *digicert_assured_id_root_ca;
|
||||
|
||||
const ASN1_OCTET_STRING *get_digicert_assured_id_root_ca_skid(void)
|
||||
{
|
||||
pv_crypto_init();
|
||||
return digicert_assured_id_root_ca;
|
||||
}
|
||||
|
||||
/* Used for the caching of the downloaded CRLs */
|
||||
static GHashTable *cached_crls;
|
||||
|
||||
void pv_crypto_init(void)
|
||||
{
|
||||
if (digicert_assured_id_root_ca)
|
||||
if (cached_crls)
|
||||
return;
|
||||
|
||||
cached_crls = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
|
||||
(GDestroyNotify)X509_CRL_free);
|
||||
digicert_assured_id_root_ca = s2i_ASN1_OCTET_STRING(
|
||||
NULL, NULL, DIGICERT_ASSURED_ID_ROOT_CA_SKID);
|
||||
}
|
||||
|
||||
void pv_crypto_cleanup(void)
|
||||
{
|
||||
if (!digicert_assured_id_root_ca)
|
||||
if (!cached_crls)
|
||||
return;
|
||||
g_clear_pointer(&cached_crls, g_hash_table_destroy);
|
||||
g_clear_pointer(&digicert_assured_id_root_ca, ASN1_OCTET_STRING_free);
|
||||
}
|
||||
|
||||
gint check_chain_parameters(const STACK_OF_X509 *chain,
|
||||
const ASN1_OCTET_STRING *skid, GError **err)
|
||||
GError **err)
|
||||
{
|
||||
const ASN1_OCTET_STRING *ca_skid = NULL;
|
||||
const X509_NAME *ca_x509_subject = NULL;
|
||||
g_autofree gchar *ca_subject = NULL;
|
||||
gint len = sk_X509_num(chain);
|
||||
X509 *ca = NULL;
|
||||
|
||||
g_assert(skid);
|
||||
/* at least one root and one leaf certificate must be defined */
|
||||
g_assert(len >= 2);
|
||||
if (len < 2) {
|
||||
g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("there must be at least on root and one leaf certificate in the chain of trust"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* get the root certificate of the chain of trust */
|
||||
ca = sk_X509_value(chain, len - 1);
|
||||
@@ -1316,19 +1308,21 @@ gint check_chain_parameters(const STACK_OF_X509 *chain,
|
||||
return -1;
|
||||
}
|
||||
|
||||
ca_skid = X509_get0_subject_key_id(ca);
|
||||
if (!ca_skid) {
|
||||
g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_MALFORMED_ROOT_CA,
|
||||
_("malformed root certificate"));
|
||||
ca_x509_subject = X509_get_subject_name(ca);
|
||||
if (!ca_x509_subject) {
|
||||
g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("subject of the root CA cannot be retrieved"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ASN1_STRING_cmp(ca_skid, skid) != 0) {
|
||||
g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_WRONG_CA_USED,
|
||||
_("expecting DigiCert root CA to be used"));
|
||||
ca_subject = X509_NAME_oneline(ca_x509_subject, NULL, 0);
|
||||
if (!ca_subject) {
|
||||
g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("subject name of the root CA cannot be retrieved"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_info("Root CA used: '%s'", ca_subject);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,6 @@ int check_crl_valid_for_cert(X509_CRL *crl, X509 *cert,
|
||||
gint verify_flags, GError **err);
|
||||
void pv_crypto_init(void);
|
||||
void pv_crypto_cleanup(void);
|
||||
const ASN1_OCTET_STRING *get_digicert_assured_id_root_ca_skid(void);
|
||||
gint verify_host_key(X509 *host_key, GSList *issuer_pairs,
|
||||
gint verify_flags, int level, GError **err);
|
||||
X509 *load_cert_from_file(const char *path, GError **err);
|
||||
@@ -138,8 +137,7 @@ X509_STORE *store_setup(const gchar *root_ca_path,
|
||||
int store_set_verify_param(X509_STORE *store, GError **err);
|
||||
X509_CRL *load_crl_by_cert(X509 *cert, GError **err);
|
||||
STACK_OF_X509_CRL *try_load_crls_by_certs(GSList *certs_with_path);
|
||||
gint check_chain_parameters(const STACK_OF_X509 *chain,
|
||||
const ASN1_OCTET_STRING *skid, GError **err);
|
||||
gint check_chain_parameters(const STACK_OF_X509 *chain, GError **err);
|
||||
X509_NAME *c2b_name(const X509_NAME *name);
|
||||
|
||||
STACK_OF_X509 *delete_ibm_signing_certs(STACK_OF_X509 *certs);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "helper.h"
|
||||
#include "table.h"
|
||||
|
||||
#define SD_DG_INIT_INTERVAL_MS 200
|
||||
#define SD_DG_INIT_INTERVAL_SEC 1
|
||||
#define SD_SYS_ID_SIZE 9
|
||||
|
||||
/*
|
||||
|
||||
@@ -150,7 +150,7 @@ void sd_update(void)
|
||||
*/
|
||||
void sd_dg_register(struct sd_dg *dg, int has_core_data)
|
||||
{
|
||||
struct timespec ts = {0, SD_DG_INIT_INTERVAL_MS * 1000000};
|
||||
struct timespec ts = {SD_DG_INIT_INTERVAL_SEC, 0};
|
||||
struct sd_sys_item *sys_item;
|
||||
struct sd_cpu_item *cpu_item;
|
||||
unsigned int i;
|
||||
|
||||
185
include/boot/boot_defs.h
Normal file
185
include/boot/boot_defs.h
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Boot and dump related definitions
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 BOOT_DEFS_H
|
||||
#define BOOT_DEFS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/zt_common.h"
|
||||
|
||||
#define ZIPL_MAGIC "zIPL"
|
||||
#define ZIPL_MAGIC_SIZE 4
|
||||
|
||||
/*
|
||||
* ECKD dump parameter
|
||||
*/
|
||||
struct eckd_dump_param {
|
||||
uint32_t blk_start;
|
||||
uint32_t blk_end;
|
||||
uint16_t blk_size;
|
||||
uint8_t num_heads;
|
||||
uint8_t bpt;
|
||||
char reserved[4];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* FBA dump parameter
|
||||
*/
|
||||
struct fba_dump_param {
|
||||
uint32_t res1;
|
||||
uint32_t blk_start;
|
||||
uint32_t res2;
|
||||
uint32_t blk_end;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* SCSI dump parameter
|
||||
*/
|
||||
struct scsi_dump_param {
|
||||
uint64_t block;
|
||||
uint64_t reserved;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Layout of block pointer for linear devices
|
||||
* e.g. SCSI
|
||||
*/
|
||||
struct linear_blockptr {
|
||||
uint64_t blockno;
|
||||
uint16_t size;
|
||||
uint16_t blockct;
|
||||
uint8_t reserved[4];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Layout of block pointer for cylinder/head/sector devices
|
||||
* e.g. ECKD
|
||||
*/
|
||||
struct eckd_blockptr {
|
||||
uint16_t cyl;
|
||||
uint16_t head;
|
||||
uint8_t sec;
|
||||
uint16_t size;
|
||||
uint8_t blockct;
|
||||
uint8_t reserved[8];
|
||||
} __packed;
|
||||
|
||||
typedef enum {
|
||||
COMPONENT_EXECUTE = 0x01,
|
||||
COMPONENT_LOAD = 0x02,
|
||||
COMPONENT_SIGNATURE = 0x03
|
||||
} component_type;
|
||||
|
||||
typedef enum {
|
||||
COMPONENT_HEADER_IPL = 0x00,
|
||||
COMPONENT_HEADER_DUMP = 0x01
|
||||
} component_header_type;
|
||||
|
||||
struct component_header {
|
||||
uint8_t magic[4];
|
||||
uint8_t type;
|
||||
uint8_t reserved[27];
|
||||
} __packed;
|
||||
|
||||
struct signature_header {
|
||||
uint8_t format;
|
||||
uint8_t reserved[3];
|
||||
uint32_t length;
|
||||
} __packed;
|
||||
|
||||
typedef union {
|
||||
uint64_t load_address;
|
||||
uint64_t load_psw;
|
||||
struct signature_header sig_head;
|
||||
} component_data;
|
||||
|
||||
struct component_entry {
|
||||
uint8_t data[23];
|
||||
uint8_t type;
|
||||
component_data compdat;
|
||||
} __packed;
|
||||
|
||||
/* SCSI dump super block */
|
||||
|
||||
struct scsi_dump_sb {
|
||||
uint64_t magic;
|
||||
uint64_t version;
|
||||
uint64_t part_start;
|
||||
uint64_t part_size;
|
||||
uint64_t dump_offset;
|
||||
uint64_t dump_size;
|
||||
uint64_t csum_offset;
|
||||
uint64_t csum_size;
|
||||
uint64_t csum;
|
||||
} __packed;
|
||||
|
||||
#define SCSI_DUMP_SB_MAGIC 0x5a46435044554d50ULL /* ZFCPDUMP */
|
||||
/* To avoid a csum entry of 0 a seed is used */
|
||||
#define SCSI_DUMP_SB_SEED 0x12345678
|
||||
#define SCSI_DUMP_SB_CSUM_SIZE 4096
|
||||
|
||||
/* Boot info */
|
||||
|
||||
#define BOOT_INFO_VERSION 1
|
||||
#define BOOT_INFO_MAGIC "zIPL"
|
||||
|
||||
#define BOOT_INFO_DEV_TYPE_ECKD 0x00
|
||||
#define BOOT_INFO_DEV_TYPE_FBA 0x01
|
||||
#define BOOT_INFO_DEV_TYPE_SCSI 0x02
|
||||
|
||||
#define BOOT_INFO_BP_TYPE_IPL 0x00
|
||||
#define BOOT_INFO_BP_TYPE_DUMP 0x01
|
||||
|
||||
#ifdef __s390x__
|
||||
#define BOOT_INFO_FLAGS_ARCH 0x01
|
||||
#else
|
||||
#define BOOT_INFO_FLAGS_ARCH 0x00
|
||||
#endif
|
||||
|
||||
struct boot_info_bp_dump {
|
||||
union {
|
||||
struct eckd_dump_param eckd;
|
||||
struct fba_dump_param fba;
|
||||
struct scsi_dump_param scsi;
|
||||
} param;
|
||||
uint8_t unused[16];
|
||||
} __packed;
|
||||
|
||||
struct boot_info_bp_ipl {
|
||||
union {
|
||||
struct eckd_blockptr eckd;
|
||||
struct linear_blockptr lin;
|
||||
} bm_ptr;
|
||||
uint8_t unused[16];
|
||||
} __packed;
|
||||
|
||||
struct boot_info {
|
||||
char magic[4];
|
||||
uint8_t version;
|
||||
uint8_t bp_type;
|
||||
uint8_t dev_type;
|
||||
uint8_t flags;
|
||||
union {
|
||||
struct boot_info_bp_dump dump;
|
||||
struct boot_info_bp_ipl ipl;
|
||||
} bp;
|
||||
} __packed;
|
||||
|
||||
#define DISK_LAYOUT_ID 0x00000001
|
||||
|
||||
struct scsi_mbr {
|
||||
uint8_t magic[4];
|
||||
uint32_t version_id;
|
||||
uint8_t reserved[8];
|
||||
struct linear_blockptr program_table_pointer;
|
||||
uint8_t reserved2[0x50];
|
||||
struct boot_info boot_info;
|
||||
} __packed;
|
||||
|
||||
#endif /* BOOT_DEFS_H */
|
||||
@@ -16,6 +16,8 @@
|
||||
#define __LC_IPLDEV 0x0c6c
|
||||
#define __LC_OS_INFO 0x0e18
|
||||
|
||||
#define LOWCORE_SIZE _AC(0x2000, UL)
|
||||
|
||||
#define PAGE_SIZE _AC(4096, UL)
|
||||
|
||||
/* Minimum size of a stack frame in bytes */
|
||||
@@ -56,6 +58,26 @@ struct psw32_t {
|
||||
|
||||
void load_wait_psw(uint64_t, struct psw_t *);
|
||||
|
||||
struct subchannel_id {
|
||||
uint32_t cssid:8;
|
||||
uint32_t:4;
|
||||
uint32_t m:1;
|
||||
uint32_t ssid:2;
|
||||
uint32_t one:1;
|
||||
uint32_t sch_no:16;
|
||||
} __packed __aligned(4);
|
||||
|
||||
struct tpi_info {
|
||||
struct subchannel_id schid;
|
||||
uint32_t intparm;
|
||||
uint32_t adapter_IO:1;
|
||||
uint32_t directed_irq:1;
|
||||
uint32_t isc:3;
|
||||
uint32_t:12;
|
||||
uint32_t type:3;
|
||||
uint32_t:12;
|
||||
} __packed __aligned(4);
|
||||
|
||||
struct _lowcore {
|
||||
uint8_t pad_0x0000[0x0014-0x0000]; /* 0x0000 */
|
||||
uint32_t ipl_parmblock_ptr; /* 0x0014 */
|
||||
@@ -78,10 +100,15 @@ struct _lowcore {
|
||||
uint8_t pad_0x00a4[0x00a8-0x00a4]; /* 0x00a4 */
|
||||
uint64_t trans_exc_code; /* 0x00a8 */
|
||||
uint64_t monitor_code; /* 0x00b0 */
|
||||
uint16_t subchannel_id; /* 0x00b8 */
|
||||
uint16_t subchannel_nr; /* 0x00ba */
|
||||
uint32_t io_int_parm; /* 0x00bc */
|
||||
uint32_t io_int_word; /* 0x00c0 */
|
||||
union {
|
||||
struct {
|
||||
uint16_t subchannel_id; /* 0x00b8 */
|
||||
uint16_t subchannel_nr; /* 0x00ba */
|
||||
uint32_t io_int_parm; /* 0x00bc */
|
||||
uint32_t io_int_word; /* 0x00c0 */
|
||||
};
|
||||
struct tpi_info tpi_info; /* 0x00b8 */
|
||||
};
|
||||
uint8_t pad_0x00c4[0x00c8-0x00c4]; /* 0x00c4 */
|
||||
uint32_t stfl_fac_list; /* 0x00c8 */
|
||||
uint8_t pad_0x00cc[0x00e8-0x00cc]; /* 0x00cc */
|
||||
@@ -210,6 +237,7 @@ struct _lowcore {
|
||||
/* align to the top of the prefix area */
|
||||
uint8_t pad_0x1900[0x2000-0x1900]; /* 0x1900 */
|
||||
} __packed __aligned(8192);
|
||||
STATIC_ASSERT(sizeof(struct _lowcore) == LOWCORE_SIZE)
|
||||
|
||||
#define S390_lowcore (*((struct _lowcore *) 0))
|
||||
|
||||
|
||||
94
include/lib/ap.h
Normal file
94
include/lib/ap.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* libap - A collection of tools for ap/vfio-ap management
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIB_AP_H
|
||||
#define LIB_AP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lib/util_list.h"
|
||||
|
||||
#define VFIO_AP_PATH "/sys/devices/vfio_ap"
|
||||
#define VFIO_AP_PARENT_PATH "devices/vfio_ap/matrix"
|
||||
#define VFIO_AP_CONFIG_PATH "/etc/mdevctl.d/matrix"
|
||||
#define VFIO_AP_TYPE "vfio_ap-passthrough"
|
||||
#define AP_UDEV_FILE "/etc/udev/rules.d/41-ap.rules"
|
||||
#define AP_LOCKFILE "/run/lock/s390apconfig.lock"
|
||||
|
||||
#define AP_LOCK_RETRIES 15
|
||||
|
||||
/* apmask and aqmask are each represented as 67 character strings with:
|
||||
* '0x' leading characters
|
||||
* 64 hex digits (to represent 256 bits)
|
||||
* terminating character
|
||||
*/
|
||||
#define AP_MASK_SIZE 67
|
||||
#define AP_MAX_MASK_VALUE 255
|
||||
|
||||
/* List structure used for keeping track of lists of adapter/domain IDs */
|
||||
struct vfio_ap_node {
|
||||
struct util_list_node node; /* prev/next list info */
|
||||
unsigned int id; /* list entry (adapter, domain, etc) */
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure used to represent a vfio_ap-passthrough device configuration.
|
||||
* The list of adapters and domains can be used to derive the APQNs for the
|
||||
* device. The type value is used to verify that the device (when read from
|
||||
* a mdevctl config file) is of type vfio_ap-passthrough. The manual value
|
||||
* represents whether the device is started on-demand after boot (true) or
|
||||
* automatically during boot (false).
|
||||
*/
|
||||
struct vfio_ap_device {
|
||||
char *uuid; /* Unique ID for this mdev */
|
||||
struct util_list *adapters; /* List of adapters for device */
|
||||
struct util_list *domains; /* List of usage domains for device */
|
||||
struct util_list *controls; /* List of control domains for device */
|
||||
char *type; /* mdev type string */
|
||||
bool manual; /* manual/auto start setting for mdev */
|
||||
};
|
||||
|
||||
/* General Utility Functions */
|
||||
void print_ap_device(struct vfio_ap_device *dev);
|
||||
bool is_valid_uuid(const char *uuid);
|
||||
int ap_test_bit(int n, const char *hexbytestr);
|
||||
void ap_set_bit(int n, char *hexbytestr, bool val);
|
||||
|
||||
/* Path-related functions */
|
||||
char *path_get_vfio_ap_mdev(const char *uuid);
|
||||
char *path_get_vfio_ap_mdev_config(const char *uuid);
|
||||
char *path_get_vfio_ap_attr(const char *uuid, const char *attr);
|
||||
char *path_get_ap_udev(void);
|
||||
|
||||
/* Functions for manipulating sysfs to read active device info */
|
||||
void vfio_ap_parse_matrix(struct vfio_ap_device *dev, char *matrix);
|
||||
void vfio_ap_sort_matrix_results(struct vfio_ap_device *dev);
|
||||
void vfio_ap_parse_control(struct vfio_ap_device *dev, char *control);
|
||||
|
||||
/* Functions for reading JSON device config */
|
||||
int vfio_ap_read_device_config(const char *path, struct vfio_ap_device *dev);
|
||||
|
||||
/* Functions for managing vfio_ap device structures */
|
||||
struct vfio_ap_device *vfio_ap_device_new(void);
|
||||
void vfio_ap_device_clear(struct vfio_ap_device *dev);
|
||||
void vfio_ap_device_free(struct vfio_ap_device *dev);
|
||||
|
||||
/* Functions for acquiring current vfio_ap device info */
|
||||
int ap_read_sysfs_masks(char *ap, char *aq, int size);
|
||||
bool ap_read_udev_masks(char *path, char *ap, char *aq, bool *read_ap,
|
||||
bool *read_aq);
|
||||
void ap_mask_to_list(char *mask, struct util_list *list);
|
||||
void ap_list_remove_all(struct util_list *list);
|
||||
|
||||
/* Lock Functions */
|
||||
int ap_get_lock(void);
|
||||
int ap_get_lock_callout(void);
|
||||
int ap_release_lock(void);
|
||||
|
||||
#endif /* LIB_AP_H */
|
||||
157
include/lib/libcpumf.h
Normal file
157
include/lib/libcpumf.h
Normal file
@@ -0,0 +1,157 @@
|
||||
/* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIBCPUMF_H
|
||||
#define LIBCPUMF_H
|
||||
|
||||
#include <sched.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define S390_CPUMF_CF "/sys/devices/cpum_cf/"
|
||||
#define S390_CPUMF_CFDIAG "/sys/devices/cpum_cf_diag/"
|
||||
#define S390_CPUMF_SF "/sys/devices/cpum_sf/"
|
||||
#define S390_CPUS_POSSIBLE "/sys/devices/system/cpu/possible"
|
||||
#define S390_CPUS_ONLINE "/sys/devices/system/cpu/online"
|
||||
#define S390_CPUMSF_BUFFERSZ "/sys/module/kernel/parameters/cpum_sfb_size"
|
||||
#define S390_SYSFS_PAI_CRYPTO "/sys/devices/pai_crypto/"
|
||||
#define S390_SYSFS_PAI_NNPA "/sys/devices/pai_nnpa/"
|
||||
|
||||
/**
|
||||
* Read out the PMU type from a given file.
|
||||
*
|
||||
* Return the PMU type number assigned to this PMU by the kernel. This is
|
||||
* a non zero number.
|
||||
*
|
||||
* @param[in] dirname Name of the event directory in sysfs
|
||||
* @retval >=0 Number of PMU assigned by the kernel
|
||||
* @retval -1 PMU unknown to kernel
|
||||
*/
|
||||
int libcpumf_pmutype(const char *dirname);
|
||||
|
||||
/**
|
||||
* Read out the PMU name from a given type.
|
||||
*
|
||||
* Return the PMU name this PMU was registered in kernel. If the PMU was
|
||||
* registered without a name, it is not listed in the directory.
|
||||
* The caller must free the memory returned by name.
|
||||
*
|
||||
* @param[in] wanted_type Type number of the PMU
|
||||
* @param[out] name Name of the PMU (when retval is 0)
|
||||
* @retval 0 PMU wanted_type detected and PMU name valid
|
||||
* @retval -1 No PMU with wanted_type
|
||||
*/
|
||||
int libcpumf_pmuname(unsigned int wanted_type, char **name);
|
||||
|
||||
/**
|
||||
* Read out the CPU list from a given file name, for example from files
|
||||
* /sys/devices/system/cpu/online or /sys/devices/system/cpu/possible.
|
||||
*
|
||||
* Return the cpu_set_t created from parsing the CPU list in the second
|
||||
* parameter.
|
||||
*
|
||||
* @param[in] buffer Comma separated string of a CPU list
|
||||
* @param[in] filename Name of a sysfs CPU list file name
|
||||
* @param[out] mask Converted buffer into cpu_set_t mask structure
|
||||
* @retval 0 Successful conversion of cpulist
|
||||
* @retval -1 Unsuccessful conversion of cpulist
|
||||
*/
|
||||
int libcpumf_cpuset(const char *buffer, cpu_set_t *mask);
|
||||
int libcpumf_cpuset_fn(const char *filename, cpu_set_t *mask);
|
||||
|
||||
/**
|
||||
* Read CPU Measurement Counting Facility hardware information
|
||||
*
|
||||
* Return true if CPU Measurement Counter facility information has been
|
||||
* retrieved and is valid.
|
||||
*
|
||||
* Return false if the information could not be extracted from the file.
|
||||
*
|
||||
* @param[out] cfvn Contains CPUMF counter first version number
|
||||
* @param[out] csvn Contains CPUMF counter second version number
|
||||
* @param[out] auth Contains CPUMF counter set authorization level
|
||||
* @retval true Information returned in parameters is valid
|
||||
* @retval false Information could not be retrieved
|
||||
*/
|
||||
bool libcpumf_cpumcf_info(int *cfvn, int *csvn, int *auth);
|
||||
|
||||
/**
|
||||
* Return true if CPU Measurement Counter Facility is available.
|
||||
*
|
||||
* @retval true CPU Measurement Counter Facility is available
|
||||
* @retval false CPU Measurement Counter Facility is not available
|
||||
*/
|
||||
bool libcpumf_have_cpumcf(void);
|
||||
|
||||
/**
|
||||
* Read CPU Measurement Sampling Facility hardware information
|
||||
*
|
||||
* Read all necessary information from /sysfs file /proc/service_levels
|
||||
* to return CPU Measurement Counter Sampling facility information
|
||||
* characteristics.
|
||||
* Return true on success and false when the data can not be retrieved.
|
||||
*
|
||||
* @param[out] min Minimum supported sampling interval
|
||||
* @param[out] max Maximum supported sampling interval
|
||||
* @param[out] speed Current CPU speed, number of CPU cylces per
|
||||
* microsecond
|
||||
* @param[out] basic_sz Basic sample size in bytes
|
||||
* @param[out] diag_sz Diagnostic sample size in bytes
|
||||
* @retval true Information returned in parameters is valid
|
||||
* @retval false Information could not be retrieved
|
||||
*/
|
||||
bool libcpumf_cpumsf_info(unsigned long *min, unsigned long *max,
|
||||
unsigned long *speed, int *basic_sz, int *diag_sz);
|
||||
|
||||
/**
|
||||
* Return true if CPU Measurement Sampling Facility is available.
|
||||
*
|
||||
* @retval true CPU Measurement Sampling Facility is available
|
||||
* @retval false CPU Measurement Sampling Facility is not available
|
||||
*/
|
||||
bool libcpumf_have_cpumsf(void);
|
||||
|
||||
/**
|
||||
* Return true if CPU Measurement Sampling Facility buffer sizes are
|
||||
* available.
|
||||
*
|
||||
* @retval true CPU Measurement Sampling Facility buffer sizes are
|
||||
* available
|
||||
* @retval false CPU Measurement Sampling Facility buffer sizes are
|
||||
* not available
|
||||
*/
|
||||
bool libcpumf_have_sfb(void);
|
||||
|
||||
/**
|
||||
* Read CPU Measurement Sampling Facility supported sampling buffer sizes.
|
||||
*
|
||||
* Return the minimum and maximum CPU Measurement sampling facitity buffer
|
||||
* sizes supported.
|
||||
* Return true on success and false otherwise.
|
||||
*
|
||||
* @param[out] min Minimum supported sampling buffer size
|
||||
* @param[out] max Maximum supported sampling buffer size
|
||||
* @retval true Information returned in parameters is valid
|
||||
* @retval false Information could not be retrieved
|
||||
*/
|
||||
bool libcpumf_sfb_info(unsigned long *min, unsigned long *max);
|
||||
|
||||
/**
|
||||
* Return true if PAI_CRYPTO counter Facility is supported.
|
||||
*
|
||||
* @retval true PAI_CRYPTO counter Facility is available
|
||||
* @retval false PAI_CRYPTO counter Facility is not available
|
||||
*/
|
||||
bool libcpumf_have_pai_crypto(void);
|
||||
|
||||
/**
|
||||
* Return true if PAI_NNPA counter Facility is supported. This PMU facility
|
||||
* supports the Neural Network Processing Assist (NNPA) counter set.
|
||||
*
|
||||
* @retval true PAI_NNPA counter Facility is available
|
||||
* @retval false PAI_NNPA counter Facility is not available
|
||||
*/
|
||||
bool libcpumf_have_pai_nnpa(void);
|
||||
#endif
|
||||
@@ -26,6 +26,7 @@ enum util_arch_machine_type {
|
||||
UTIL_ARCH_MACHINE_TYPE_Z14_ZR1 = 3907,
|
||||
UTIL_ARCH_MACHINE_TYPE_Z15 = 8561,
|
||||
UTIL_ARCH_MACHINE_TYPE_Z15_T02 = 8562,
|
||||
UTIL_ARCH_MACHINE_TYPE_Z16 = 3931,
|
||||
};
|
||||
|
||||
int util_arch_machine_type(void);
|
||||
|
||||
21
include/lib/util_exit_code.h
Normal file
21
include/lib/util_exit_code.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @defgroup util_exit_code_h util_exit_code: General purpose exit codes
|
||||
* @{
|
||||
* @brief General purpose exit codes
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIB_UTIL_EXIT_CODE_H
|
||||
#define LIB_UTIL_EXIT_CODE_H
|
||||
|
||||
typedef enum {
|
||||
UTIL_EXIT_OK = 0, /* Program finished successfully */
|
||||
UTIL_EXIT_RUNTIME_ERROR = 15, /* A run-time error occurred */
|
||||
UTIL_EXIT_OUT_OF_MEMORY = 22, /* Not enough available memory */
|
||||
} util_exit_code_t;
|
||||
|
||||
#endif /** LIB_UTIL_EXIT_CODE_H @} */
|
||||
@@ -11,6 +11,8 @@
|
||||
#ifndef LIB_UTIL_FILE_H
|
||||
#define LIB_UTIL_FILE_H
|
||||
|
||||
#include "lib/util_exit_code.h"
|
||||
|
||||
int util_file_read_line(char *str, size_t size, const char *fmt, ...);
|
||||
int util_file_read_i(int *val, int base, const char *fmt, ...);
|
||||
int util_file_read_l(long *val, int base, const char *fmt, ...);
|
||||
@@ -26,4 +28,10 @@ int util_file_write_ul(unsigned long val, int base, const char *fmt, ...);
|
||||
int util_file_write_ull(unsigned long long val, int base, const char *fmt, ...);
|
||||
|
||||
int util_file_read_va(const char *path, const char *fmt, ...);
|
||||
|
||||
util_exit_code_t util_file_read_fd_buf(FILE *fd, void **buffer_ptr,
|
||||
size_t *size_ptr);
|
||||
char *util_file_read_fd(FILE *fd, int chomp);
|
||||
char *util_file_read_text_file(const char *path, int chomp);
|
||||
|
||||
#endif /** LIB_UTIL_FILE_H @} */
|
||||
|
||||
44
include/lib/util_udev.h
Normal file
44
include/lib/util_udev.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* @defgroup util_udev_h util_udev: UDEV interface
|
||||
* @{
|
||||
* @brief Work with UDEV files
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIB_UTIL_UDEV_H
|
||||
#define LIB_UTIL_UDEV_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "lib/util_exit_code.h"
|
||||
#include "lib/util_list.h"
|
||||
|
||||
/* Single key-operator-value entry in a udev rule line.*/
|
||||
struct util_udev_entry_node {
|
||||
struct util_list_node node;
|
||||
char *key;
|
||||
char *op;
|
||||
char *value;
|
||||
};
|
||||
|
||||
/* Single udev line in a udev rule file. */
|
||||
struct util_udev_line_node {
|
||||
struct util_list_node node;
|
||||
struct util_list entries;
|
||||
char *line;
|
||||
};
|
||||
|
||||
/* Udev rule file. */
|
||||
struct util_udev_file {
|
||||
struct util_list lines;
|
||||
};
|
||||
|
||||
util_exit_code_t util_udev_read_file(const char *path,
|
||||
struct util_udev_file **file_ptr);
|
||||
void util_udev_free_file(struct util_udev_file *file);
|
||||
void util_udev_file_print(struct util_udev_file *file);
|
||||
|
||||
#endif /** LIB_UTIL_UDEV_H @} */
|
||||
438
include/libpv/cert.h
Normal file
438
include/libpv/cert.h
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* Certificate functions and definitions.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIBPV_CERT_H
|
||||
#define LIBPV_CERT_H
|
||||
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "libpv/common.h"
|
||||
|
||||
#define PV_IBM_Z_SUBJECT_COMMON_NAME "International Business Machines Corporation"
|
||||
#define PV_IBM_Z_SUBJECT_COUNTRY_NAME "US"
|
||||
#define PV_IBM_Z_SUBJECT_LOCALITY_NAME "Poughkeepsie"
|
||||
#define PV_IBM_Z_SUBJECT_ORGANIZATIONAL_UNIT_NAME_SUFFIX "Key Signing Service"
|
||||
#define PV_IBM_Z_SUBJECT_ORGANIZATION_NAME "International Business Machines Corporation"
|
||||
#define PV_IBM_Z_SUBJECT_STATE "New York"
|
||||
#define PV_IMB_Z_SUBJECT_ENTRY_COUNT 6
|
||||
|
||||
/* 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).
|
||||
*/
|
||||
#define PV_CERTS_SECURITY_LEVEL 2
|
||||
|
||||
/** pv_cert_init:
|
||||
*
|
||||
* Should not be called by user.
|
||||
* Use pv_init() instead which
|
||||
* calls this function during creation.
|
||||
*
|
||||
* Sets up data structures for caching CRLs.
|
||||
*/
|
||||
void pv_cert_init(void);
|
||||
|
||||
/** pv_cert_cleanup:
|
||||
*
|
||||
* Should not be called by user.
|
||||
* Use pv_cleanup() instead which
|
||||
* calls this function during creation.
|
||||
*
|
||||
* Cleans up data structures for caching CRLs.
|
||||
*/
|
||||
void pv_cert_cleanup(void);
|
||||
|
||||
#define PV_CERT_ERROR g_quark_from_static_string("pv-cert-error-quark")
|
||||
typedef enum {
|
||||
PV_CERT_ERROR_CERT_REVOKED,
|
||||
PV_CERT_ERROR_CERT_SIGNATURE_INVALID,
|
||||
PV_CERT_ERROR_CERT_SUBJECT_ISSUER_MISMATCH,
|
||||
PV_CERT_ERROR_CRL_DOWNLOAD_FAILED,
|
||||
PV_CERT_ERROR_CRL_SIGNATURE_INVALID,
|
||||
PV_CERT_ERROR_CRL_SUBJECT_ISSUER_MISMATCH,
|
||||
PV_CERT_ERROR_FAILED_DOWNLOAD_CRL,
|
||||
PV_CERT_ERROR_INTERNAL,
|
||||
PV_CERT_ERROR_INVALID_PARM,
|
||||
PV_CERT_ERROR_INVALID_SIGNATURE_ALGORITHM,
|
||||
PV_CERT_ERROR_INVALID_VALIDITY_PERIOD,
|
||||
PV_CERT_ERROR_LOAD_CRL,
|
||||
PV_CERT_ERROR_LOAD_DEFAULT_CA,
|
||||
PV_CERT_ERROR_LOAD_ROOT_CA,
|
||||
PV_CERT_ERROR_MALFORMED_CERTIFICATE,
|
||||
PV_CERT_ERROR_MALFORMED_ROOT_CA,
|
||||
PV_CERT_ERROR_NO_CRL,
|
||||
PV_CERT_ERROR_NO_CRLDP,
|
||||
PV_CERT_ERROR_NO_ISSUER_IBM_Z_FOUND,
|
||||
PV_CERT_ERROR_NO_PUBLIC_KEY,
|
||||
PV_CERT_ERROR_READ_CERTIFICATE,
|
||||
PV_CERT_ERROR_READ_CRL,
|
||||
PV_CERT_ERROR_SIGNATURE_ALGORITHM_MISMATCH,
|
||||
PV_CERT_ERROR_SKID_AKID_MISMATCH,
|
||||
PV_CERT_ERROR_VERIFICATION_FAILED,
|
||||
PV_CERT_ERROR_WRONG_CA_USED,
|
||||
} PvCertErrors;
|
||||
|
||||
/** PvX509WithPath - X509 certificate associated with a path
|
||||
*/
|
||||
typedef struct {
|
||||
X509 *cert;
|
||||
char *path;
|
||||
} PvX509WithPath;
|
||||
|
||||
/** pv_x509_with_path_new:
|
||||
*
|
||||
* @cert: X509 certificate
|
||||
* @path: Path of that X509 certificate
|
||||
*
|
||||
* Returns: (nullable) (transfer full): new X509 with path
|
||||
*/
|
||||
PvX509WithPath *pv_x509_with_path_new(X509 *cert, const char *path);
|
||||
|
||||
/** pv_x509_with_path_free:
|
||||
*
|
||||
* Frees the path and the PvX509WithPath; Decreases the refcount of the X509
|
||||
*/
|
||||
void pv_x509_with_path_free(PvX509WithPath *cert);
|
||||
|
||||
typedef STACK_OF(DIST_POINT) STACK_OF_DIST_POINT;
|
||||
typedef STACK_OF(X509) STACK_OF_X509;
|
||||
typedef STACK_OF(X509_CRL) STACK_OF_X509_CRL;
|
||||
typedef GSList PvCertWithPathList;
|
||||
|
||||
typedef struct {
|
||||
X509 *cert;
|
||||
STACK_OF_X509_CRL *crls;
|
||||
} PvX509Pair;
|
||||
|
||||
/** pv_x509_pair_new_take:
|
||||
* @cert: ptr to X509
|
||||
* @crls: ptr to CRLs
|
||||
*
|
||||
* Takes a X509 and the associated CRLs and builds a pair.
|
||||
* Both, *cert and *crls will be NULL afterwards, and owned by the pair.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): New PvX509Pair
|
||||
*/
|
||||
PvX509Pair *pv_x509_pair_new_take(X509 **cert, STACK_OF_X509_CRL **crls);
|
||||
|
||||
/** pv_x509_pair_free:
|
||||
*
|
||||
* Decreases the refcount of the X509 and crls.
|
||||
* Frees the PvX509Pair.
|
||||
*/
|
||||
void pv_x509_pair_free(PvX509Pair *pair);
|
||||
|
||||
void STACK_OF_DIST_POINT_free(STACK_OF_DIST_POINT *stack);
|
||||
void STACK_OF_X509_free(STACK_OF_X509 *stack);
|
||||
void STACK_OF_X509_CRL_free(STACK_OF_X509_CRL *stack);
|
||||
|
||||
/** pv_x509_from_pem_der_data:
|
||||
*
|
||||
* @data: GBytes containing the cert in PEM format
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): X509 cert
|
||||
*/
|
||||
X509 *pv_x509_from_pem_der_data(GBytes *data, GError **error);
|
||||
|
||||
/** pv_x509_get_ec_pubkey:
|
||||
*
|
||||
* @cert: X509 to extract elliptic curve pubkey from
|
||||
* @nid: numerical identifier of the expected curve
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): corresponding pupkey for the given certificate
|
||||
*/
|
||||
EVP_PKEY *pv_x509_get_ec_pubkey(X509 *cert, int nid, GError **error);
|
||||
|
||||
/** pv_get_ec_pubkeys:
|
||||
*
|
||||
* @certs_with_path: List of PvX509WithPath
|
||||
* @nid: numerical identifier of the expected curve
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): List of corresponding public keys for the given certificate
|
||||
*/
|
||||
GSList *pv_get_ec_pubkeys(PvCertWithPathList *certs_with_path, int nid, GError **error);
|
||||
|
||||
/* pv_load_certificates:
|
||||
*
|
||||
* @cert_paths: list of cert paths.
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* @cert_paths must contain at least one element, otherwise an error is
|
||||
* reported.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): List of PvX509WithPath corresponding to the given paths
|
||||
*/
|
||||
PvCertWithPathList *pv_load_certificates(char **cert_paths, GError **error);
|
||||
|
||||
/* pv_load_first_cert_from_file:
|
||||
*
|
||||
* @path: location of the x509
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* This function reads in only the first certificate and ignores all other. This
|
||||
* is only relevant for the PEM file format. For the host-key document and the
|
||||
* root CA this behavior is expected.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): PvX509WithPath corresponding to the given path
|
||||
*/
|
||||
X509 *pv_load_first_cert_from_file(const char *path, GError **error);
|
||||
|
||||
/* pv_load_first_crl_from_file:
|
||||
*
|
||||
* @path: location of the x509 CRL
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* This function reads in only the first CRL and ignores all other. This
|
||||
* is only relevant for the PEM file format.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): X509_CRL corresponding to the given path
|
||||
*/
|
||||
X509_CRL *pv_load_first_crl_from_file(const char *path, GError **error);
|
||||
|
||||
/** pv_store_setup_crl_download:
|
||||
*
|
||||
* @st: X509_STORE
|
||||
*/
|
||||
void pv_store_setup_crl_download(X509_STORE *st);
|
||||
|
||||
/** pv_load_first_crl_by_cert:
|
||||
* @cert: X509 to specify the download location.
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* This function returns the first X509_CRL found from the CRL distribution
|
||||
* points specified in @cert.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): x509 CRL corresponding to the given X509
|
||||
*/
|
||||
X509_CRL *pv_load_first_crl_by_cert(X509 *cert, GError **error);
|
||||
|
||||
/** pv_try_load_crls_by_certs:
|
||||
*
|
||||
* @certs_with_path: List of PvX509WithPath
|
||||
*
|
||||
* Returns: (nullable) (transfer full): Stack of CRLs corresponding to the given X509
|
||||
*/
|
||||
STACK_OF_X509_CRL *pv_try_load_crls_by_certs(PvCertWithPathList *certs_with_path);
|
||||
|
||||
/** pv_store_setup:
|
||||
*
|
||||
* @root_ca_path: Location of the rootCA or NULL if SystemRoot CA shall be used
|
||||
* @crl_paths: List of CRL paths or NULL
|
||||
* @cert_with_crl_paths: List of (untrusted) X509 paths
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* The untrusted certs need to be verified before actually verifying a Host Key Document.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): X509_store with given input data.
|
||||
*
|
||||
*/
|
||||
X509_STORE *pv_store_setup(char *root_ca_path, char **crl_paths, char **cert_with_crl_paths,
|
||||
GError **error);
|
||||
|
||||
/** pv_get_x509_stack:
|
||||
*
|
||||
* x509_with_path_list: list of PvX509WithPath
|
||||
*
|
||||
* Returns: (nullable) (transfer full): Stack of X509 corresponding to the given x509 with path
|
||||
*/
|
||||
STACK_OF_X509 *pv_get_x509_stack(const GSList *x509_with_path_list);
|
||||
|
||||
/** pv_init_store_ctx:
|
||||
*
|
||||
* @ctx: a uninitialized Store CTX
|
||||
* @trusted: X509_STORE with a trusted rootCA
|
||||
* @chain: untrusted X509s
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Can be called multiple times on the same context if X509_STORE_CTX_cleanup(ctx)
|
||||
* was called before.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success
|
||||
* -1 in failure
|
||||
*/
|
||||
int pv_init_store_ctx(X509_STORE_CTX *ctx, X509_STORE *trusted, STACK_OF_X509 *chain,
|
||||
GError **error) PV_NONNULL(1, 2, 3);
|
||||
|
||||
/** pv_init_store_ctx:
|
||||
*
|
||||
* @trusted: X509_STORE with a trusted rootCA
|
||||
* @chain: untrusted X509s
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): X509_STORE_CTX setup with the input data
|
||||
*/
|
||||
X509_STORE_CTX *pv_create_store_ctx(X509_STORE *trusted, STACK_OF_X509 *chain, GError **error)
|
||||
PV_NONNULL(1, 2);
|
||||
/** pv_remove_ibm_signing_certs:
|
||||
*
|
||||
* @certs: Stack of X509s
|
||||
*
|
||||
* Returns: (transfer full):
|
||||
* List of all IBM Z signing key certificates in @certs and remove them
|
||||
* from the chain.
|
||||
* Empty stack if no IBM Z signing key is found.
|
||||
*/
|
||||
STACK_OF_X509 *pv_remove_ibm_signing_certs(STACK_OF_X509 *certs);
|
||||
|
||||
/** pv_c2b_name:
|
||||
*
|
||||
* Workaround to fix the mismatch between issuer name of the
|
||||
* IBM Z signing CRLs and the IBM Z signing key subject name.
|
||||
*
|
||||
* In RFC 5280 the attributes of a (subject/issuer) name is not mandatory
|
||||
* ordered. The problem is that our certificates are not consistent in the order
|
||||
* (see https://tools.ietf.org/html/rfc5280#section-4.1.2.4 for details).
|
||||
*
|
||||
* This function tries to reorder the name attributes such that
|
||||
* further OpenSSL calls can work with it. The caller is
|
||||
* responsible to free the returned value.
|
||||
*/
|
||||
X509_NAME *pv_c2b_name(const X509_NAME *name);
|
||||
|
||||
/** pv_verify_host_key:
|
||||
*
|
||||
* @host_key: X509 to be verified
|
||||
* @issuer_pairs: IBM signing key X509+CRLs Pairs used for verification
|
||||
* @level: Security level. see PV_CERTS_SECURITY_LEVEL
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns:
|
||||
* 0 if Host key could be verified with one of the IBM signing keys
|
||||
* -1 if no IBM signing key could verify the authenticity of the given host key
|
||||
*
|
||||
*/
|
||||
int pv_verify_host_key(X509 *host_key, GSList *issuer_pairs, int verify_flags, int level,
|
||||
GError **error);
|
||||
|
||||
/** pv_verify_cert:
|
||||
*
|
||||
* @ctx: trusted store ctx used for verification
|
||||
* @cert: X509 to be verified
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Cannot be used to verify host keys with IBM signing keys, as IBM signing
|
||||
* keys are no intermediate CAs. Use pv_verify_host_key() instead.
|
||||
*
|
||||
* Returns:
|
||||
* 0 if @cert could be verified
|
||||
* -1 if @cert could not be verified
|
||||
*/
|
||||
int pv_verify_cert(X509_STORE_CTX *ctx, X509 *cert, GError **error) PV_NONNULL(1, 2);
|
||||
|
||||
/** pv_check_crl_valid_for_cert:
|
||||
*
|
||||
* @crl: CRL to be verified
|
||||
* @cert: Cert that probably issued the given CRL
|
||||
* @verify_flags: X509 Verification flags (X509_V_FLAG_<TYPE>)
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Verify whether a revocation list @crl is valid and is issued by @cert. For
|
||||
* this multiple steps must be done:
|
||||
*
|
||||
* 1. verify issuer of the CRL matches with the suject name of @cert
|
||||
* 2. verify the validity period of the CRL
|
||||
* 3. verify the signature of the CRL
|
||||
*
|
||||
* Important: This function does not verify whether @cert is allowed to issue a
|
||||
* CRL.
|
||||
*
|
||||
* Returns:
|
||||
* 0 if @crl is valid and issued by @cert
|
||||
* -1 otherwise
|
||||
*/
|
||||
int pv_verify_crl(X509_CRL *crl, X509 *cert, int verify_flags, GError **error);
|
||||
|
||||
/** pv_check_chain_parameters:
|
||||
*
|
||||
* @chain: chain of trust to be validated
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Verifies that chain has at least a RootCA ans intermediate CA
|
||||
* and logs the used ROD CA subject
|
||||
*
|
||||
* Returns:
|
||||
* 0 @chain is valid
|
||||
* -1 otherwise
|
||||
*/
|
||||
int pv_check_chain_parameters(const STACK_OF_X509 *chain, GError **error);
|
||||
|
||||
/** pv_store_set_verify_param:
|
||||
*
|
||||
* @store: X509_STORE to set parameters
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success
|
||||
* -1 on failure
|
||||
*/
|
||||
int pv_store_set_verify_param(X509_STORE *store, GError **error);
|
||||
|
||||
/** pv_store_ctx_find_valid_crls:
|
||||
*
|
||||
* @ctx: STORE_CTX for searching CRLs
|
||||
* @cert: X509 to match CRLs aggainst
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): STACK of CRLs related to given @crl fin @ctx
|
||||
*/
|
||||
STACK_OF_X509_CRL *pv_store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, GError **error)
|
||||
PV_NONNULL(1, 2);
|
||||
|
||||
/** pv_verify_host_key_doc:
|
||||
*
|
||||
* @host_key_certs_with_path: X509s to be verified
|
||||
* @trusted. X509_STORE with a rusted RootCA
|
||||
* @untrusted_certs: STACK OF untrusted X509s
|
||||
* @online: true if CRLs shall be downloaded
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns:
|
||||
* 0 if all given HKDs could be verified using the chain of trust.
|
||||
* -1 otherwise
|
||||
*/
|
||||
int pv_verify_host_key_doc(PvCertWithPathList *host_key_certs_with_path, X509_STORE *trusted,
|
||||
STACK_OF_X509 *untrusted_certs, gboolean online, GError **error)
|
||||
PV_NONNULL(1, 2, 3);
|
||||
|
||||
/** pv_verify_host_key_docs_by_path:
|
||||
*
|
||||
* @host_key_paths: locations of X509 to be verified
|
||||
* @optional_root_ca_path: rootCA location or NULL if Default shall be used
|
||||
* @optional_crl_paths: locations of CRLs or NULL
|
||||
* @untrusted_cert_paths: locations of IntermediateCAs including the IBM signing key
|
||||
* @online: true if CRLs shall be downloaded
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns:
|
||||
* 0 if all given HKDs could be verfied using the chain of trust.
|
||||
* -1 otherwise
|
||||
*/
|
||||
int pv_verify_host_key_docs_by_path(char **host_key_paths, char *optional_root_ca_path,
|
||||
char **optional_crl_paths, char **untrusted_cert_paths,
|
||||
gboolean online, GError **error) PV_NONNULL(1, 4);
|
||||
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(AUTHORITY_KEYID, AUTHORITY_KEYID_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvX509WithPath, pv_x509_with_path_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_DIST_POINT, STACK_OF_DIST_POINT_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_X509, STACK_OF_X509_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_X509_CRL, STACK_OF_X509_CRL_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509, X509_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_CRL, X509_CRL_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_LOOKUP, X509_LOOKUP_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_NAME, X509_NAME_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_VERIFY_PARAM, X509_VERIFY_PARAM_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvX509Pair, pv_x509_pair_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_STORE, X509_STORE_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_STORE_CTX, X509_STORE_CTX_free)
|
||||
|
||||
#endif /* LIBPV_CERT_H */
|
||||
35
include/libpv/common.h
Normal file
35
include/libpv/common.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Libpv common definitions.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIBPV_COMMON_H
|
||||
#define LIBPV_COMMON_H
|
||||
|
||||
/* must be included before any (other) glib header to verify that
|
||||
* the glib version is supported
|
||||
*/
|
||||
#include "libpv/glib-helper.h"
|
||||
|
||||
#include <glib/gi18n-lib.h>
|
||||
|
||||
#include "libpv/openssl-compat.h"
|
||||
#include "libpv/macros.h"
|
||||
|
||||
/** pv_init:
|
||||
*
|
||||
* Must be called before any libpv call.
|
||||
*/
|
||||
int pv_init(void);
|
||||
|
||||
/** pv_cleanup:
|
||||
*
|
||||
* Must be called when done with using libpv.
|
||||
*/
|
||||
void pv_cleanup(void);
|
||||
|
||||
#endif /* LIBPV_COMMON_H */
|
||||
200
include/libpv/crypto.h
Normal file
200
include/libpv/crypto.h
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* General cryptography helper functions and definitions
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIBPV_CRYPTO_H
|
||||
#define LIBPV_CRYPTO_H
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "libpv/common.h"
|
||||
|
||||
typedef struct pv_cipher_parms {
|
||||
const EVP_CIPHER *cipher;
|
||||
size_t tag_size;
|
||||
GBytes *key;
|
||||
union {
|
||||
GBytes *iv;
|
||||
GBytes *tweak;
|
||||
};
|
||||
} PvCipherParms;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t x[80];
|
||||
uint8_t y[80];
|
||||
};
|
||||
uint8_t data[160];
|
||||
} PvEcdhPubKey;
|
||||
G_STATIC_ASSERT(sizeof(PvEcdhPubKey) == 160);
|
||||
|
||||
typedef GSList PvEvpKeyList;
|
||||
|
||||
enum PvCryptoMode {
|
||||
PV_ENCRYPT,
|
||||
PV_DECRYPT,
|
||||
};
|
||||
|
||||
/** pv_get_openssl_error:
|
||||
*
|
||||
* Returns: (transfer full): String representing the error.
|
||||
*/
|
||||
const char *pv_get_openssl_error(void);
|
||||
|
||||
/**
|
||||
* pv_BIO_reset:
|
||||
* @b: BIO to reset
|
||||
*
|
||||
* Resets a BIO to its initial state.
|
||||
*
|
||||
* Returns: 0 in case of success, -1 otherwise.
|
||||
*/
|
||||
int pv_BIO_reset(BIO *b);
|
||||
|
||||
/**
|
||||
* pv_generate_rand_data:
|
||||
* @size: number of generated random bytes using a crypographically secure pseudo random generator
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Creates a new #GBytes with @size random bytes using a cryptographically
|
||||
* secure pseudo random generator.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): a new #GBytes, or %NULL in case of an error
|
||||
*/
|
||||
GBytes *pv_generate_rand_data(size_t size, GError **error);
|
||||
|
||||
/**
|
||||
* pv_generate_key:
|
||||
* @cipher: specifies the OpenSSL cipher for which a cryptographically secure key should be generated
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Creates a random key for @cipher using a cryptographically secure pseudo
|
||||
* random generator.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): a new #GBytes, or %NULL in case of an error
|
||||
*/
|
||||
GBytes *pv_generate_key(const EVP_CIPHER *cipher, GError **error) PV_NONNULL(1);
|
||||
|
||||
/**
|
||||
* pv_generate_iv:
|
||||
* @cipher: specifies the OpenSSL cipher for which a cryptographically secure IV should be generated
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Creates a random IV for @cipher using a cryptographically secure pseudo
|
||||
* random generator.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): a new #GBytes, or %NULL in case of an error
|
||||
*/
|
||||
GBytes *pv_generate_iv(const EVP_CIPHER *cipher, GError **error) PV_NONNULL(1);
|
||||
|
||||
/* Symmetric en/decryption functions */
|
||||
|
||||
/**
|
||||
* pv_gcm_encrypt:
|
||||
* @plain: data to encrypt
|
||||
* @aad: (optional): additional data that should be authenticated with the key
|
||||
* @parms:
|
||||
* @cipher: (out): location to store the ciphertext
|
||||
* @tag: (out): location to store the generated GCM tag
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Encrypts the @plain data and authenticates @aad data.
|
||||
*
|
||||
* Returns: number of bytes, or -1 in case of an error
|
||||
*/
|
||||
int64_t pv_gcm_encrypt(GBytes *plain, GBytes *aad, const PvCipherParms *parms, GBytes **cipher,
|
||||
GBytes **tag, GError **error) PV_NONNULL(1, 3, 4, 5);
|
||||
|
||||
/**
|
||||
* pv_gcm_decrypt:
|
||||
* @cipher: ciphertext to decrypt
|
||||
* @aad: (optional): additional date to authenticate
|
||||
* @tag: the GCM tag
|
||||
* @parms:
|
||||
* @plain: (out): location to store the decrypted data
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Decrypts the @cipher data and authenticates the @aad data.
|
||||
*
|
||||
* Returns: number of bytes, or -1 in case of an error
|
||||
*/
|
||||
int64_t pv_gcm_decrypt(GBytes *cipher, GBytes *aad, GBytes *tag, const PvCipherParms *parms,
|
||||
GBytes **plain, GError **error) PV_NONNULL(1, 3, 4, 5);
|
||||
|
||||
/** pv_hkdf_extract_and_expand:
|
||||
* @derived_key_len: size of the output key
|
||||
* @key: input key
|
||||
* @salt: salt for the extraction
|
||||
* @info: infor for the expansion
|
||||
* @md: EVP mode of operation
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Performs a RFC 5869 HKDF.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): Result of RFC 5869 HKDF
|
||||
*
|
||||
*/
|
||||
GBytes *pv_hkdf_extract_and_expand(size_t derived_key_len, GBytes *key, GBytes *salt, GBytes *info,
|
||||
const EVP_MD *md, GError **error) PV_NONNULL(2, 3, 4, 5);
|
||||
|
||||
/** pv_generate_ec_key:
|
||||
*
|
||||
* @nid: Numerical identifier of the curve
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): new random key based on the given curve
|
||||
*/
|
||||
EVP_PKEY *pv_generate_ec_key(int nid, GError **error);
|
||||
|
||||
/** pv_evp_pkey_to_ecdh_pub_key:
|
||||
*
|
||||
* @key: input key in EVP_PKEY format
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: the public part of the input @key in ECDH format.
|
||||
*/
|
||||
PvEcdhPubKey *pv_evp_pkey_to_ecdh_pub_key(EVP_PKEY *key, GError **error) PV_NONNULL(1);
|
||||
|
||||
/** pv_derive_exchange_key:
|
||||
* @cust: Customer Key
|
||||
* @host: Host key
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): Shared Secret of @cust and @host
|
||||
*/
|
||||
GBytes *pv_derive_exchange_key(EVP_PKEY *cust, EVP_PKEY *host, GError **error) PV_NONNULL(1, 2);
|
||||
|
||||
GQuark pv_crypto_error_quark(void);
|
||||
#define PV_CRYPTO_ERROR pv_crypto_error_quark()
|
||||
typedef enum {
|
||||
PV_CRYPTO_ERROR_DERIVE,
|
||||
PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
PV_CRYPTO_ERROR_INTERNAL,
|
||||
PV_CRYPTO_ERROR_INVALID_KEY_SIZE,
|
||||
PV_CRYPTO_ERROR_KEYGENERATION,
|
||||
PV_CRYPTO_ERROR_RANDOMIZATION,
|
||||
PV_CRYPTO_ERROR_READ_FILE,
|
||||
PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY,
|
||||
PV_CRYPTO_ERROR_NO_MATCH_TAG,
|
||||
} PvCryptoErrors;
|
||||
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(ASN1_INTEGER, ASN1_INTEGER_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(ASN1_OCTET_STRING, ASN1_OCTET_STRING_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BIO, BIO_free_all)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BIGNUM, BN_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BN_CTX, BN_CTX_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EC_GROUP, EC_GROUP_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EC_KEY, EC_KEY_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EC_POINT, EC_POINT_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_CIPHER_CTX, EVP_CIPHER_CTX_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_PKEY, EVP_PKEY_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_PKEY_CTX, EVP_PKEY_CTX_free)
|
||||
|
||||
#endif /* LIBPV_CRYPTO_H */
|
||||
53
include/libpv/curl.h
Normal file
53
include/libpv/curl.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Libcurl utils
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIBPV_CURL_H
|
||||
#define LIBPV_CURL_H
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "libpv/common.h"
|
||||
|
||||
#define CRL_DOWNLOAD_TIMEOUT_MS 3000
|
||||
#define CRL_DOWNLOAD_MAX_SIZE 0x100000
|
||||
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURL, curl_easy_cleanup)
|
||||
|
||||
/** curl_download:
|
||||
* @url: URL to specify location of data
|
||||
* @timeout_ms: time to wait until fail
|
||||
* @max_size: Maximum size of the downloaded data
|
||||
* @error: return location for a GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): Downloaded data as #GByteArray
|
||||
*/
|
||||
GByteArray *curl_download(const char *url, long timeout_ms, uint max_size, GError **err);
|
||||
|
||||
/** pv_curl_init:
|
||||
*
|
||||
* Should not be called by user.
|
||||
* Use pv_init() instead which
|
||||
* calls this function during creation.
|
||||
*/
|
||||
int pv_curl_init(void);
|
||||
|
||||
/** pv_curl_cleanup:
|
||||
*
|
||||
* Should not be called by user.
|
||||
* Use pv_cleanup() instead which
|
||||
* calls this function during creation.
|
||||
*/
|
||||
void pv_curl_cleanup(void);
|
||||
|
||||
#define PV_CURL_ERROR g_quark_from_static_string("pv-curl-error-quark")
|
||||
typedef enum {
|
||||
PV_CURL_ERROR_CURL_INIT_FAILED,
|
||||
PV_CURL_ERROR_DOWNLOAD_FAILED,
|
||||
} PvCurlErrors;
|
||||
|
||||
#endif /* LIBPV_CURL_H */
|
||||
117
include/libpv/glib-helper.h
Normal file
117
include/libpv/glib-helper.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Glib convenience functions
|
||||
* Shall be used instead of manually including glib.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIBPV_GLIB_HELPER_H
|
||||
#define LIBPV_GLIB_HELPER_H
|
||||
|
||||
#if defined(GLIB_VERSION_MIN_REQUIRED)
|
||||
#if GLIB_VERSION_MIN_REQUIRED < GLIB_VERSION_2_56
|
||||
#error "GLIB_VERSION must be at least 2.56"
|
||||
#endif
|
||||
#else
|
||||
#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_56
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
#include <gmodule.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "libpv/macros.h"
|
||||
|
||||
#ifdef __clang__
|
||||
#define WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(...) \
|
||||
DO_PRAGMA(clang diagnostic push) \
|
||||
DO_PRAGMA(clang diagnostic ignored "-Wunused-function") \
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(__VA_ARGS__) \
|
||||
DO_PRAGMA(clang diagnostic pop)
|
||||
#else
|
||||
#define WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(...) G_DEFINE_AUTOPTR_CLEANUP_FUNC(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#define pv_wrapped_g_assert(__expr) g_assert(__expr)
|
||||
|
||||
/** pv_sec_gbytes_new_take:
|
||||
*
|
||||
* #g_bytes_new_take() with secure cleanup
|
||||
*/
|
||||
GBytes *pv_sec_gbytes_new_take(void *data, size_t size);
|
||||
|
||||
/** pv_sec_gbytes_new:
|
||||
*
|
||||
* #g_bytes_new() with secure cleanup
|
||||
*/
|
||||
GBytes *pv_sec_gbytes_new(const void *data, size_t size);
|
||||
|
||||
/** pv_file_get_content_as_secure_bytes:
|
||||
*
|
||||
* @filename: path to file for reading in
|
||||
*
|
||||
* read file and save as secure gbytes
|
||||
*
|
||||
* Return:
|
||||
* Content of file as #GBytes with secure cleanup
|
||||
*/
|
||||
GBytes *pv_file_get_content_as_secure_bytes(const char *filename);
|
||||
|
||||
/** pv_file_get_content_as_g_bytes:
|
||||
*
|
||||
* @filename: path to file for reading in
|
||||
*
|
||||
* read file and save as gbytes
|
||||
*
|
||||
* Return:
|
||||
* Content of file as #GBytes
|
||||
*/
|
||||
GBytes *pv_file_get_content_as_g_bytes(const char *filename, GError **error);
|
||||
|
||||
/** pv_file_seek:
|
||||
*
|
||||
* fseek with error reporting
|
||||
*/
|
||||
int pv_file_seek(FILE *file, long offset, int whence, GError **error);
|
||||
|
||||
/** pv_file_write:
|
||||
*
|
||||
* fwrite with error reporting
|
||||
*/
|
||||
size_t pv_file_write(FILE *file, const void *ptr, size_t size, GError **error);
|
||||
|
||||
/** pv_file_close:
|
||||
*
|
||||
* fclose with error reporting
|
||||
*/
|
||||
long pv_file_close(FILE *file, GError **error);
|
||||
|
||||
/** pv_file_tell:
|
||||
*
|
||||
* ftell with error reporting
|
||||
*/
|
||||
long pv_file_tell(FILE *file, GError **error);
|
||||
|
||||
/** pv_file_open:
|
||||
*
|
||||
* fopen with error reporting
|
||||
*/
|
||||
FILE *pv_file_open(const char *filename, const char *mode, GError **error);
|
||||
|
||||
/** pv_gbytes_memcpy:
|
||||
*
|
||||
* memcpy with size check.
|
||||
*/
|
||||
void *pv_gbytes_memcpy(void *dst, size_t dst_size, GBytes *src);
|
||||
|
||||
#define PV_GLIB_HELPER_ERROR g_quark_from_static_string("pv-glib-helper_error-quark")
|
||||
typedef enum {
|
||||
PV_GLIB_HELPER_FILE_ERROR,
|
||||
} pv_glib_helper_error_e;
|
||||
|
||||
void pv_auto_close_file(FILE *file);
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(FILE, pv_auto_close_file)
|
||||
|
||||
#endif /* LIBPV_GLIB_HELPER_H */
|
||||
119
include/libpv/hash.h
Normal file
119
include/libpv/hash.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Hashing definitions.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIBPV_HASH_H
|
||||
#define LIBPV_HASH_H
|
||||
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
#include "libpv/common.h"
|
||||
|
||||
/** pv_digest_ctx_new:
|
||||
* @md: mode of digest, e.g. #EVP_sha256()
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): a new #EVP_MD_CTX, or %NULL in case of an error
|
||||
*/
|
||||
EVP_MD_CTX *pv_digest_ctx_new(const EVP_MD *md, GError **error);
|
||||
|
||||
/** pv_digest_ctx_update:
|
||||
* @ctx: EVP_MD_CTX to add data
|
||||
* @data: #GBytes to add to the context
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Adds @data to the digest context. Can be called multiple times.
|
||||
*
|
||||
* Returns: 0 in case of success, -1 otherwise.
|
||||
*/
|
||||
int pv_digest_ctx_update(EVP_MD_CTX *ctx, GBytes *data, GError **error);
|
||||
|
||||
/** pv_digest_ctx_update_raw:
|
||||
* @ctx: #EVP_MD_CTX to add data
|
||||
* @buf: data to add to the context
|
||||
* @size: size of @buf
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Adds @buf to the digest context. Can be called multiple times.
|
||||
*
|
||||
* Returns: 0 in case of success, -1 otherwise.
|
||||
*/
|
||||
int pv_digest_ctx_update_raw(EVP_MD_CTX *ctx, const uint8_t *buf, size_t size, GError **error);
|
||||
|
||||
/** pv_digest_ctx_finalize:
|
||||
* @ctx: #EVP_MD_CTX with data to digest
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Calculates the digest of all previously added data. Do not use @ctx afterwards.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): Digest of all data added before as #GBytes, or NULL in case of error.
|
||||
*/
|
||||
GBytes *pv_digest_ctx_finalize(EVP_MD_CTX *ctx, GError **error);
|
||||
|
||||
/** pv_sha256_hash:
|
||||
* @buf: data for which a sha256 hash sould be calculated
|
||||
* @size: size of @buf
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Shorthand for initializing a sha256-digest ctx, updating, and finalizing.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): SHA256 of @buf as #GBytes, or NULL in case of error.
|
||||
*/
|
||||
GBytes *pv_sha256_hash(uint8_t *buf, size_t size, GError **error);
|
||||
|
||||
/** pv_hmac_ctx_new:
|
||||
* @key: key used for the HMAC
|
||||
* @md: mode of digest, e.g. #EVP_sha512()
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Returns: (nullable) (transfer full): New #HMAC_CTX or NULL in case of error
|
||||
*/
|
||||
HMAC_CTX *pv_hmac_ctx_new(GBytes *key, const EVP_MD *md, GError **error);
|
||||
|
||||
/** pv_hmac_ctx_update_raw:
|
||||
* @ctx: #HMAC_CTX to add data
|
||||
* @buf: data to add to the context
|
||||
* @size: size of @buf
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Adds @buf to the HMAC context. Can be called multiple times.
|
||||
*
|
||||
* Returns: 0 in case of success, -1 otherwise.
|
||||
*/
|
||||
int pv_hmac_ctx_update_raw(HMAC_CTX *ctx, const void *data, size_t size, GError **error);
|
||||
|
||||
/** pv_hmac_ctx_update:
|
||||
* @ctx: #HMAC_CTX to add data
|
||||
* @data: #GBytes to add to the context
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Adds @data to the HMAC context. Can be called multiple times.
|
||||
*
|
||||
* Returns: 0 in case of success, -1 otherwise.
|
||||
*/
|
||||
|
||||
int pv_hmac_ctx_update(HMAC_CTX *ctx, GBytes *data, GError **error);
|
||||
|
||||
/** pv_hmac_ctx_finalize:
|
||||
* @ctx: #HMAC_CTX with data to digest
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Calculates the HMAC of all previously added data. Do not use @ctx afterwards.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): HMAC of all data added before as #GBytes, or NULL in case of error.
|
||||
*/
|
||||
GBytes *pv_hamc_ctx_finalize(HMAC_CTX *ctx, GError **error);
|
||||
|
||||
#define PV_HASH_ERROR g_quark_from_static_string("pv-crypro-error-quark")
|
||||
typedef enum {
|
||||
PV_HASH_ERROR_INTERNAL,
|
||||
} PvHashErrors;
|
||||
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_MD_CTX, EVP_MD_CTX_free)
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(HMAC_CTX, HMAC_CTX_free)
|
||||
|
||||
#endif /* LIBPV_HASH_H */
|
||||
16
include/libpv/macros.h
Normal file
16
include/libpv/macros.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Libpv common macro definitions.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIBPV_MACROS_H
|
||||
#define LIBPV_MACROS_H
|
||||
|
||||
#define PV_NONNULL(...)
|
||||
#define DO_PRAGMA(x) _Pragma(#x)
|
||||
|
||||
#endif /* LIBPV_MACROS_H */
|
||||
29
include/libpv/openssl-compat.h
Normal file
29
include/libpv/openssl-compat.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* OpenSSL compatibility utils
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef LIBPV_OPENSSL_COMPAT_H
|
||||
#define LIBPV_OPENSSL_COMPAT_H
|
||||
|
||||
#include <openssl/opensslv.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||
#define pv_X509_STORE_CTX_get_current_cert(ctx) X509_STORE_CTX_get_current_cert(ctx)
|
||||
#define pv_X509_STORE_CTX_get1_crls(ctx, nm) X509_STORE_CTX_get1_crls((ctx), (nm))
|
||||
#define pv_X509_STORE_set_lookup_crls(st, cb) X509_STORE_set_lookup_crls(st, cb)
|
||||
#elif OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||
#define pv_X509_STORE_CTX_get_current_cert(ctx) \
|
||||
X509_STORE_CTX_get_current_cert((X509_STORE_CTX *)(ctx))
|
||||
#define pv_X509_STORE_CTX_get1_crls(ctx, nm) \
|
||||
X509_STORE_CTX_get1_crls((X509_STORE_CTX *)(ctx), (X509_NAME *)(nm))
|
||||
#define pv_X509_STORE_set_lookup_crls(st, cb) \
|
||||
X509_STORE_set_lookup_crls(st, (X509_STORE_CTX_lookup_crls_fn)(cb))
|
||||
#endif
|
||||
|
||||
#endif /* LIBPV_OPENSSL_COMPAT_H */
|
||||
98
include/libpv/se-hdr.h
Normal file
98
include/libpv/se-hdr.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* PV/SE header definitions
|
||||
*
|
||||
* Copyright IBM Corp. 2020
|
||||
*
|
||||
* 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 LIBPV_SE_HDR_H
|
||||
#define LIBPV_SE_HDR_H
|
||||
|
||||
#include "libpv/common.h"
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "boot/s390.h"
|
||||
#include "libpv/crypto.h"
|
||||
|
||||
/* Magic number which is used to identify the file containing the PV
|
||||
* header
|
||||
*/
|
||||
#define PV_MAGIC_NUMBER 0x49424d5365634578ULL
|
||||
#define PV_VERSION_1 0x00000100U
|
||||
|
||||
/* Internal helper macro */
|
||||
#define __PV_BIT(nr) (1ULL << (63 - (nr)))
|
||||
|
||||
/* Plaintext control flags */
|
||||
/* dumping of the configuration is allowed */
|
||||
#define PV_PCF_ALLOW_DUMPING __PV_BIT(34)
|
||||
/* prevent Ultravisor decryption during unpack operation */
|
||||
#define PV_PCF_NO_DECRYPTION __PV_BIT(35)
|
||||
/* PCKMO encrypt-DEA/TDEA-key functions allowed */
|
||||
#define PV_PCF_PCKMO_DEA_TDEA __PV_BIT(56)
|
||||
/* PCKMO encrypt-AES-key functions allowed */
|
||||
#define PV_PCF_PCKMO_AES __PV_BIT(57)
|
||||
/* PCKMO encrypt-ECC-key functions allowed */
|
||||
#define PV_PCF_PCKM_ECC __PV_BIT(58)
|
||||
|
||||
/* maxima for the PV version 1 */
|
||||
#define PV_V1_IPIB_MAX_SIZE PAGE_SIZE
|
||||
#define PV_V1_PV_HDR_MIN_SIZE \
|
||||
(sizeof(struct pv_hdr_head) + sizeof(struct pv_hdr_encrypted) + \
|
||||
sizeof(((struct pv_hdr *)0)->tag) + 1 * sizeof(struct pv_hdr_key_slot))
|
||||
#define PV_V1_PV_HDR_MAX_SIZE (2 * PAGE_SIZE)
|
||||
|
||||
#define PV_IMAGE_ENCR_KEY_SIZE 64
|
||||
|
||||
typedef struct pv_hdr_key_slot {
|
||||
uint8_t digest_key[SHA256_DIGEST_LENGTH];
|
||||
uint8_t wrapped_key[32];
|
||||
uint8_t tag[16];
|
||||
} __packed PvHdrKeySlot;
|
||||
|
||||
typedef struct pv_hdr_opt_item {
|
||||
uint32_t otype;
|
||||
uint8_t ibk[32];
|
||||
uint8_t data[];
|
||||
} __packed PvHdrOptItem;
|
||||
|
||||
/* integrity protected data (by GCM tag), but non-encrypted */
|
||||
struct pv_hdr_head {
|
||||
uint64_t magic;
|
||||
uint32_t version;
|
||||
uint32_t phs;
|
||||
uint8_t iv[12];
|
||||
uint32_t res1;
|
||||
uint64_t nks;
|
||||
uint64_t sea;
|
||||
uint64_t nep;
|
||||
uint64_t pcf;
|
||||
PvEcdhPubKey cust_pub_key;
|
||||
uint8_t pld[SHA512_DIGEST_LENGTH];
|
||||
uint8_t ald[SHA512_DIGEST_LENGTH];
|
||||
uint8_t tld[SHA512_DIGEST_LENGTH];
|
||||
} __packed;
|
||||
|
||||
/* Must not have any padding */
|
||||
struct pv_hdr_encrypted {
|
||||
uint8_t cust_comm_key[32];
|
||||
uint8_t img_enc_key_1[PV_IMAGE_ENCR_KEY_SIZE / 2];
|
||||
uint8_t img_enc_key_2[PV_IMAGE_ENCR_KEY_SIZE / 2];
|
||||
struct psw_t psw;
|
||||
uint64_t scf;
|
||||
uint32_t noi;
|
||||
uint32_t res2;
|
||||
};
|
||||
G_STATIC_ASSERT(sizeof(struct pv_hdr_encrypted) == 32 + 32 + 32 + sizeof(struct psw_t) + 8 + 4 + 4);
|
||||
|
||||
typedef struct pv_hdr {
|
||||
struct pv_hdr_head head;
|
||||
struct pv_hdr_key_slot *slots;
|
||||
struct pv_hdr_encrypted *encrypted;
|
||||
struct pv_hdr_opt_item **optional_items;
|
||||
uint8_t tag[16];
|
||||
} PvHdr;
|
||||
|
||||
#endif /* LIBPV_SE_HDR_H */
|
||||
@@ -139,8 +139,20 @@ the same as 0.0.5000).
|
||||
.BR "\-L" " or " "\-\-loadparm"
|
||||
Specifies an entry in the
|
||||
.BR zipl (8)
|
||||
boot menu. If this option is omitted, the default menu entry is used.
|
||||
boot menu, and an active namespace in the installed zIPL environment block
|
||||
for evaluation of zIPL environment variables in the kernel command line.
|
||||
|
||||
This option requires an argument, which has to be a string of the form
|
||||
"nSX", where optional number 'n' indicates the boot menu entry, and optional
|
||||
token 'SX' specifies the active namespace. This token consists of a mandatory
|
||||
leading capital 'S' and of an optional trailing character 'X', which is
|
||||
either any decimal digit, indicating ID of the active site, or a capital 'S'.
|
||||
In the last case, ID of the active namespace is determined as the SSID
|
||||
(Subchannel Set ID).
|
||||
|
||||
If the menu entry is omitted in the argument, then the default menu entry
|
||||
is used. If the 'X' is omitted, or the whole token 'SX' is omitted, then the
|
||||
common namespace of the installed zIPL environment block will be activated.
|
||||
.TP
|
||||
.BR "\-c" " or " "\-\-clear"
|
||||
Specify whether memory should be cleared on re-IPL. Possible values are 0 to
|
||||
@@ -158,9 +170,11 @@ loadparm:
|
||||
\fB# chreipl ccw 0.0.7e78\fP
|
||||
|
||||
2. Next time reboot from the CCW device with the bus-ID 0.0.7e78
|
||||
using the first entry of the zipl boot menu:
|
||||
using the first entry of the zipl boot menu and the second site namespace
|
||||
in the installed zIPL environment block for evaluation of zIPL environment
|
||||
variables in the kernel command line:
|
||||
|
||||
\fB# chreipl ccw -d 0.0.7e78 -L 1\fP
|
||||
\fB# chreipl ccw -d 0.0.7e78 -L 1S2\fP
|
||||
.SH fcp
|
||||
Use the fcp re-IPL target for SCSI disks that are accessed by the hardware
|
||||
using Fibre Channel Protocol (FCP) channels.
|
||||
@@ -306,4 +320,5 @@ that you would otherwise have to specify with the ccw or fcp target.
|
||||
.BR lsreipl (8),
|
||||
.BR zipl (8),
|
||||
.BR zipl.conf (5),
|
||||
.BR zipl-editenv (8)
|
||||
.BR reboot (8)
|
||||
|
||||
37
libap/Makefile
Normal file
37
libap/Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
include ../common.mak
|
||||
|
||||
lib = libap.a
|
||||
|
||||
check-dep-lock:
|
||||
touch check-dep-lock
|
||||
ifneq (${HAVE_LOCKFILE},0)
|
||||
$(call check_dep, \
|
||||
"libap", \
|
||||
"lockfile.h", \
|
||||
"liblockfile-devel", \
|
||||
"HAVE_LOCKFILE=0")
|
||||
ALL_CPPFLAGS += -DHAVE_LOCKFILE
|
||||
endif
|
||||
|
||||
check-dep-json:
|
||||
touch check-dep-json
|
||||
ifneq (${HAVE_JSONC},0)
|
||||
$(call check_dep, \
|
||||
"libap", \
|
||||
"json-c/json.h", \
|
||||
"json-c-devel", \
|
||||
"HAVE_JSONC=0")
|
||||
ALL_CPPFLAGS += -DHAVE_JSONC
|
||||
endif
|
||||
|
||||
all: $(lib)
|
||||
objects = ap.o
|
||||
|
||||
$(lib): $(objects)
|
||||
|
||||
$(objects): check-dep-lock check-dep-json
|
||||
|
||||
install: all
|
||||
|
||||
clean:
|
||||
rm -f *.o check-dep-lock check-dep-json $(lib)
|
||||
760
libap/ap.c
Normal file
760
libap/ap.c
Normal file
@@ -0,0 +1,760 @@
|
||||
/*
|
||||
* libap - A collection of tools for ap/vfio-ap management
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef HAVE_JSONC
|
||||
#include <json-c/json.h>
|
||||
#endif /* HAVE_JSONC */
|
||||
|
||||
#ifdef HAVE_LOCKFILE
|
||||
#include <lockfile.h>
|
||||
#endif /* HAVE_LOCKFILE */
|
||||
|
||||
#include "lib/ap.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_panic.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_udev.h"
|
||||
|
||||
/*
|
||||
* Return sysfs path to a bus attribute
|
||||
* Note: caller is responsible for freeing the returned string
|
||||
*/
|
||||
static char *path_get_bus_attr(const char *bus, const char *attr)
|
||||
{
|
||||
return util_path_sysfs("bus/%s/%s", bus, attr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare two vfio_ap nodes based upon their id value. Return:
|
||||
* -1: a < b
|
||||
* 1: a > b
|
||||
* 0: a == b
|
||||
*/
|
||||
static int vfio_ap_node_cmp(void *a, void *b, void *UNUSED(data))
|
||||
{
|
||||
struct vfio_ap_node *a_node = a, *b_node = b;
|
||||
|
||||
if (a_node->id < b_node->id)
|
||||
return -1;
|
||||
return (a_node->id > b_node->id);
|
||||
}
|
||||
|
||||
static void vfio_ap_node_add_tail(struct util_list *list, unsigned int val)
|
||||
{
|
||||
struct vfio_ap_node *node = util_zalloc(sizeof(struct vfio_ap_node));
|
||||
|
||||
node->id = val;
|
||||
util_list_add_tail(list, node);
|
||||
}
|
||||
|
||||
/* Remove duplicate entries from a sorted vfio_ap_node list */
|
||||
static void vfio_ap_node_remove_dupes(struct util_list *list)
|
||||
{
|
||||
struct vfio_ap_node *node, *check, *next;
|
||||
|
||||
util_list_iterate(list, node) {
|
||||
/* Remove any subsequent duplicates */
|
||||
check = util_list_next(list, node);
|
||||
|
||||
while (check) {
|
||||
next = util_list_next(list, check);
|
||||
if (node->id == check->id) {
|
||||
util_list_remove(list, check);
|
||||
free(check);
|
||||
}
|
||||
check = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool starts_with(const char *str, const char *s)
|
||||
{
|
||||
size_t len = strlen(s);
|
||||
|
||||
return (strncmp(str, s, len) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Pass a comma-delimited string of IDs (adapter, domains or control domains)
|
||||
* and add these IDs to the input list. The resulting list will be sorted
|
||||
* and duplicates removed before returning.
|
||||
*/
|
||||
static void modify_device_attr(struct util_list *list, char *value)
|
||||
{
|
||||
unsigned int val;
|
||||
char *curr;
|
||||
|
||||
curr = strtok(value, ",");
|
||||
if (curr == NULL)
|
||||
return;
|
||||
|
||||
/* Create list from input setting */
|
||||
while (curr != NULL) {
|
||||
val = strtol(curr, NULL, 0);
|
||||
vfio_ap_node_add_tail(list, val);
|
||||
curr = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
/* Cleanup the list */
|
||||
util_list_sort(list, vfio_ap_node_cmp, NULL);
|
||||
vfio_ap_node_remove_dupes(list);
|
||||
}
|
||||
|
||||
static void load_attr_to_device(struct vfio_ap_device *dev, char *attr,
|
||||
const char *value)
|
||||
{
|
||||
char *v = util_strdup(value);
|
||||
|
||||
if (strcmp(attr, "assign_adapter") == 0)
|
||||
modify_device_attr(dev->adapters, v);
|
||||
else if (strcmp(attr, "assign_domain") == 0)
|
||||
modify_device_attr(dev->domains, v);
|
||||
else if (strcmp(attr, "assign_control_domain") == 0)
|
||||
modify_device_attr(dev->controls, v);
|
||||
|
||||
free(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the contents of the vfio-ap device struct to stderr. Used for
|
||||
* debugging.
|
||||
*
|
||||
* @param[in] dev vfio-ap device strcucture to print
|
||||
*/
|
||||
void print_ap_device(struct vfio_ap_device *dev)
|
||||
{
|
||||
struct vfio_ap_node *node;
|
||||
|
||||
warnx("Device %s:", dev->uuid);
|
||||
warnx("Type: %s", dev->type);
|
||||
if (dev->manual)
|
||||
warnx("Start: MANUAL");
|
||||
else
|
||||
warnx("Start: AUTO");
|
||||
if (util_list_is_empty(dev->adapters)) {
|
||||
warnx("Adapters: (none)");
|
||||
} else {
|
||||
warnx("Adapters:");
|
||||
util_list_iterate(dev->adapters, node) {
|
||||
warnx(" %u", node->id);
|
||||
}
|
||||
}
|
||||
if (util_list_is_empty(dev->domains)) {
|
||||
warnx("Domains: (none)");
|
||||
} else {
|
||||
warnx("Domains:");
|
||||
util_list_iterate(dev->domains, node) {
|
||||
warnx(" %u", node->id);
|
||||
}
|
||||
}
|
||||
if (util_list_is_empty(dev->controls)) {
|
||||
warnx("Controls: (none)");
|
||||
} else {
|
||||
warnx("Controls:");
|
||||
util_list_iterate(dev->controls, node) {
|
||||
warnx(" %u", node->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the input string is a valid Universally Unique Identifier
|
||||
*
|
||||
* @param[in] uuid Character string to inspect
|
||||
*
|
||||
* @retval true Specified string is a valid UUID
|
||||
* @retval false Specified string is not a valid UUID
|
||||
*/
|
||||
bool is_valid_uuid(const char *uuid)
|
||||
{
|
||||
uint32_t s1, s2, s3, s4;
|
||||
uint64_t s5;
|
||||
char d;
|
||||
|
||||
return (strlen(uuid) == 36 && sscanf(uuid, "%8x-%4x-%4x-%4x-%12x %c",
|
||||
&s1, &s2, &s3, &s4,
|
||||
(unsigned int *) &s5, &d) == 5);
|
||||
}
|
||||
|
||||
/*
|
||||
* For an input string of hex characters, find the nth character and return its
|
||||
* numeric value to the caller.
|
||||
*/
|
||||
static int get_hexbyte_value(int n, const char *hexbytestr)
|
||||
{
|
||||
int i = 0, v = 0;
|
||||
char c;
|
||||
|
||||
if (strncmp(hexbytestr, "0x", 2) == 0)
|
||||
i = 2;
|
||||
|
||||
/*
|
||||
* The specified mask is only valid if it includes at least 64 hex
|
||||
* digits. The specified mask may optionally include a leading '0x'
|
||||
*/
|
||||
util_assert((strlen(hexbytestr) >= (size_t)(AP_MASK_SIZE - i - 1)),
|
||||
"Invalid hex string provided for mask: %s", hexbytestr);
|
||||
|
||||
c = hexbytestr[i + n / 4];
|
||||
if (c >= '0' && c <= '9')
|
||||
v = c - '0';
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
v = 10 + c - 'a';
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
v = 10 + c - 'A';
|
||||
else
|
||||
util_assert(false, "Could not parse hex digit '%c'", c);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* For an input hex string, determine if the specified nth bit is ON or OFF.
|
||||
*
|
||||
* @param[in] n Bit number to test
|
||||
* @param[in, out] hexbytestr Character string of hex characters
|
||||
*
|
||||
* @retval 0 Specified bit is OFF
|
||||
* @retval != 0 Specified bit is ON
|
||||
*/
|
||||
int ap_test_bit(int n, const char *hexbytestr)
|
||||
{
|
||||
int v;
|
||||
|
||||
v = get_hexbyte_value(n, hexbytestr);
|
||||
|
||||
return v & (1 << (3 - (n % 4)));
|
||||
}
|
||||
|
||||
/**
|
||||
* For an input hex string, set the nth bit true/false
|
||||
*
|
||||
* @param[in] n Bit number to set
|
||||
* @param[in, out] hexbytestr Character string of hex characters
|
||||
* @param[in] val Bit is to be set ON (true) or OFF (false)
|
||||
*/
|
||||
void ap_set_bit(int n, char *hexbytestr, bool val)
|
||||
{
|
||||
char c = 0;
|
||||
int v, m, i = 0;
|
||||
|
||||
v = get_hexbyte_value(n, hexbytestr);
|
||||
|
||||
/* Calculate the bit mask */
|
||||
m = (1 << (3 - (n % 4)));
|
||||
|
||||
/* Return if bit already at correct value */
|
||||
if (((val) && ((v & m) != 0)) || (!val && ((v & m) == 0)))
|
||||
return;
|
||||
|
||||
if (val)
|
||||
v = v + m;
|
||||
else
|
||||
v = v - m;
|
||||
|
||||
if (v < 10)
|
||||
c = '0' + v;
|
||||
else if (v >= 10 && v <= 15)
|
||||
c = 'a' + (v - 10);
|
||||
else
|
||||
util_assert(false, "Could not set bit value '%d'", v);
|
||||
|
||||
if (strncmp(hexbytestr, "0x", 2) == 0)
|
||||
i = 2;
|
||||
|
||||
/* Set the new value */
|
||||
hexbytestr[i + n / 4] = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sysfs path to vfio_ap mdev
|
||||
* Note: caller is responsible for freeing the returned string
|
||||
*
|
||||
* @param[in] uuid Character string containing an mdev UUID
|
||||
*
|
||||
* @retval != 0 sysfs path for a vfio-ap device with this UUID
|
||||
*/
|
||||
char *path_get_vfio_ap_mdev(const char *uuid)
|
||||
{
|
||||
return util_path_sysfs("%s/%s", VFIO_AP_PARENT_PATH, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return path to mdevctl config file for specified UUID
|
||||
* Note: caller is responsible for freeing the returned string
|
||||
*
|
||||
* @param[in] uuid Character string containing an mdev UUID
|
||||
*
|
||||
* @retval != 0 config file path for vfio-ap device with this UUID
|
||||
*/
|
||||
char *path_get_vfio_ap_mdev_config(const char *uuid)
|
||||
{
|
||||
char *path;
|
||||
|
||||
util_asprintf(&path, "%s/%s", VFIO_AP_CONFIG_PATH, uuid);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sysfs path to vfio_ap mdev attribute (matrix, remove, ...)
|
||||
* Note: caller is responsible for freeing the returned string
|
||||
*
|
||||
* @param[in] uuid Character string containing an mdev UUID
|
||||
* @param[in] attr Character string containing device attribute name
|
||||
*
|
||||
* @retval != 0 sysfs path to specified attribute for this device
|
||||
*/
|
||||
char *path_get_vfio_ap_attr(const char *uuid, const char *attr)
|
||||
{
|
||||
return util_path_sysfs("%s/%s/%s", VFIO_AP_PARENT_PATH, uuid, attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return path to ap udev config file
|
||||
* Note: caller is responsible for freeing the returned string
|
||||
*
|
||||
* @retval != 0 path to the ap udev config file
|
||||
*/
|
||||
char *path_get_ap_udev(void)
|
||||
{
|
||||
char *path;
|
||||
|
||||
util_asprintf(&path, "%s", AP_UDEV_FILE);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take one line from the active 'matrix' attribute and parse it
|
||||
* into a list of adapters and domains. Each line of the 'matrix'
|
||||
* attribute is presented as a "adapter.domain" (e.g. "03.0005") where the
|
||||
* numeric values are always hexadecimal.
|
||||
* In a case where no adapters are assigned, a valid string might be ".0005"
|
||||
* In a case where no domains are assigned, a valid string might be "03."
|
||||
*
|
||||
* @param[in, out] dev Vfio-ap struct that will be updated
|
||||
* @param[in] matrix Character string to parse
|
||||
*/
|
||||
void vfio_ap_parse_matrix(struct vfio_ap_device *dev, char *matrix)
|
||||
{
|
||||
char *curr;
|
||||
int val;
|
||||
|
||||
if (!matrix)
|
||||
return;
|
||||
|
||||
if (*matrix != '.') {
|
||||
/* Handle a device with adapters */
|
||||
curr = strtok(matrix, ".");
|
||||
val = strtol(curr, NULL, 16);
|
||||
vfio_ap_node_add_tail(dev->adapters, val);
|
||||
curr = strtok(NULL, "\n");
|
||||
} else {
|
||||
/* Handle a device with no adapters */
|
||||
curr = strtok(matrix + 1, "\n");
|
||||
}
|
||||
|
||||
/* Leave now if the device has no domains */
|
||||
if (!curr)
|
||||
return;
|
||||
/* Get the domain */
|
||||
val = strtol(curr, NULL, 16);
|
||||
vfio_ap_node_add_tail(dev->domains, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to sort the results of repeated vfio_ap_parse_matrix calls
|
||||
*
|
||||
* @param[in, out] dev Vfio-ap struct whose lists will be sorted
|
||||
*/
|
||||
void vfio_ap_sort_matrix_results(struct vfio_ap_device *dev)
|
||||
{
|
||||
/* Sort the lists for later use */
|
||||
util_list_sort(dev->adapters, vfio_ap_node_cmp, NULL);
|
||||
util_list_sort(dev->domains, vfio_ap_node_cmp, NULL);
|
||||
|
||||
/* Run the lists and delete duplicates */
|
||||
vfio_ap_node_remove_dupes(dev->adapters);
|
||||
vfio_ap_node_remove_dupes(dev->domains);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the string provided by the active 'control_domains' attribute and
|
||||
* parse it into a list of control domains
|
||||
*
|
||||
* @param[in, out] dev Vfio-ap struct that will be updated
|
||||
* @param[in] control Character string to parse
|
||||
*/
|
||||
void vfio_ap_parse_control(struct vfio_ap_device *dev, char *control)
|
||||
{
|
||||
char *curr;
|
||||
int val;
|
||||
|
||||
curr = strtok(control, "\n");
|
||||
|
||||
while (curr != NULL) {
|
||||
val = strtol(curr, NULL, 16);
|
||||
vfio_ap_node_add_tail(dev->controls, val);
|
||||
curr = strtok(NULL, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_JSONC
|
||||
|
||||
/**
|
||||
* For a given path, read in the contents. If no path is provided, get the
|
||||
* input from stdin instead.
|
||||
*
|
||||
* @param[in] path Path to mdevctl config file
|
||||
* @param[in, out] dev Vfio-ap struct that will be updated
|
||||
*
|
||||
* @retval 0 Config read successfully, dev updated
|
||||
* @retval -1 Failed to read config, dev may have partial info
|
||||
*/
|
||||
int vfio_ap_read_device_config(const char *path, struct vfio_ap_device *dev)
|
||||
{
|
||||
json_object *root, *type, *start, *attrs, *attr;
|
||||
int i, len, rc = 0;
|
||||
const char *val;
|
||||
|
||||
if (path == NULL)
|
||||
root = json_object_from_fd(STDIN_FILENO);
|
||||
else
|
||||
root = json_object_from_file(path);
|
||||
if (root == NULL)
|
||||
return -1;
|
||||
|
||||
if (json_object_object_get_ex(root, "mdev_type", &type)) {
|
||||
val = json_object_get_string(type);
|
||||
if (!val)
|
||||
goto err;
|
||||
dev->type = util_strdup(val);
|
||||
if (strcmp(val, "vfio_ap-passthrough") != 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (json_object_object_get_ex(root, "start", &start)) {
|
||||
val = json_object_get_string(start);
|
||||
if (!val)
|
||||
goto err;
|
||||
if (strcmp(val, "auto") == 0)
|
||||
dev->manual = false;
|
||||
else if (strcmp(val, "manual") == 0)
|
||||
dev->manual = true;
|
||||
else
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (json_object_object_get_ex(root, "attrs", &attrs)) {
|
||||
len = json_object_array_length(attrs);
|
||||
for (i = 0; i < len; i++) {
|
||||
attr = json_object_array_get_idx(attrs, i);
|
||||
json_object_object_foreach(attr, key, setting) {
|
||||
val = json_object_get_string(setting);
|
||||
load_attr_to_device(dev, key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
json_object_put(root);
|
||||
return rc;
|
||||
|
||||
err:
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
#else
|
||||
int vfio_ap_read_device_config(const char *path, struct vfio_ap_device *dev)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
#endif /* HAVE_JSONC */
|
||||
|
||||
/**
|
||||
* Allocate and initialize a vfio-ap device structure.
|
||||
*
|
||||
* @retval !=0 Address of the new vfio-ap device structure
|
||||
*/
|
||||
struct vfio_ap_device *vfio_ap_device_new(void)
|
||||
{
|
||||
struct vfio_ap_device *dev;
|
||||
|
||||
dev = util_zalloc(sizeof(struct vfio_ap_device));
|
||||
|
||||
dev->manual = false;
|
||||
dev->type = NULL;
|
||||
dev->uuid = NULL;
|
||||
|
||||
dev->adapters = util_list_new(struct vfio_ap_node, node);
|
||||
dev->domains = util_list_new(struct vfio_ap_node, node);
|
||||
dev->controls = util_list_new(struct vfio_ap_node, node);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initialize a vfio-ap device structure, leaving the structure allocated.
|
||||
*
|
||||
* @param[in, out] dev Vfio-ap struct that will be re-initialized
|
||||
*/
|
||||
void vfio_ap_device_clear(struct vfio_ap_device *dev)
|
||||
{
|
||||
if (dev == NULL)
|
||||
return;
|
||||
|
||||
dev->manual = false;
|
||||
if (dev->type) {
|
||||
free(dev->type);
|
||||
dev->type = NULL;
|
||||
}
|
||||
if (dev->uuid) {
|
||||
free(dev->uuid);
|
||||
dev->uuid = NULL;
|
||||
}
|
||||
|
||||
ap_list_remove_all(dev->adapters);
|
||||
ap_list_remove_all(dev->domains);
|
||||
ap_list_remove_all(dev->controls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear and release a vfio-ap device structure.
|
||||
*
|
||||
* @param[in, out] dev Vfio-ap struct that will be freed
|
||||
*/
|
||||
void vfio_ap_device_free(struct vfio_ap_device *dev)
|
||||
{
|
||||
if (dev == NULL)
|
||||
return;
|
||||
|
||||
vfio_ap_device_clear(dev);
|
||||
util_list_free(dev->adapters);
|
||||
util_list_free(dev->domains);
|
||||
util_list_free(dev->controls);
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static int read_sysfs_mask(const char *path, char *mask, int size)
|
||||
{
|
||||
return util_file_read_line(mask, size, "%s", path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the apmask and aqmask from sysfs
|
||||
*
|
||||
* @param[in, out] ap Buffer to hold apmask contents
|
||||
* @param[in, out] aq Buffer to hold aqmask contents
|
||||
* @param[in] size Size of the mask buffers
|
||||
*
|
||||
* @retval 0 Both mask values read successfully
|
||||
* @retval != 0 Failed to read one or both mask values
|
||||
*/
|
||||
int ap_read_sysfs_masks(char *ap, char *aq, int size)
|
||||
{
|
||||
char *path;
|
||||
int rc = 0;
|
||||
|
||||
path = path_get_bus_attr("ap", "apmask");
|
||||
rc = read_sysfs_mask(path, ap, size);
|
||||
free(path);
|
||||
if (rc != 0)
|
||||
goto out;
|
||||
path = path_get_bus_attr("ap", "aqmask");
|
||||
rc = read_sysfs_mask(path, aq, size);
|
||||
free(path);
|
||||
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the apmask and aqmask from udev, falling back to sysfs if a udev rule
|
||||
* is not available or does not provide values for both masks.
|
||||
* The values in read_ap and read_aq tell the caller whether each mask was
|
||||
* successfully loaded from udev or if the sysfs value was substituted
|
||||
* Note: both ap and aq must point to a string that is at least AP_MASK_SIZE
|
||||
* in length.
|
||||
*
|
||||
* @param[in] path Path to the ap udev file
|
||||
* @param[in, out] ap Buffer to hold apmask contents
|
||||
* @param[in, out] aq Buffer to hold aqmask contents
|
||||
* @param[out] read_ap Specifies if an apmask value was read from udev
|
||||
* @param[out] read_aq Specifies if an aqmask value was read from udev
|
||||
*
|
||||
* @retval true Udev file read successfully or did not exist
|
||||
* @retval false Udev file exists but error was encountered
|
||||
*/
|
||||
bool ap_read_udev_masks(char *path, char *ap, char *aq, bool *read_ap,
|
||||
bool *read_aq)
|
||||
{
|
||||
struct util_udev_entry_node *entry;
|
||||
struct util_udev_file *file = NULL;
|
||||
struct util_udev_line_node *line;
|
||||
char sysap[AP_MASK_SIZE];
|
||||
char sysaq[AP_MASK_SIZE];
|
||||
int rc;
|
||||
|
||||
/* Assume we fail to read both masks */
|
||||
*read_ap = *read_aq = false;
|
||||
|
||||
/* If a udev file doesn't exist, quietly use the active masks */
|
||||
if (!util_path_exists(path))
|
||||
goto out;
|
||||
|
||||
rc = util_udev_read_file(path, &file);
|
||||
|
||||
/* If errors were encountered reading the udev file, exit now */
|
||||
if (rc)
|
||||
return false;
|
||||
|
||||
util_list_iterate(&file->lines, line) {
|
||||
entry = util_list_start(&line->entries);
|
||||
|
||||
/* Skip comments and empty lines. */
|
||||
if (!entry)
|
||||
continue;
|
||||
|
||||
if (starts_with(entry->key, "ATTR{")) {
|
||||
if (strstr(entry->key, "apmask")) {
|
||||
util_strlcpy(ap, entry->value, AP_MASK_SIZE);
|
||||
*read_ap = true;
|
||||
} else if (strstr(entry->key, "aqmask")) {
|
||||
util_strlcpy(aq, entry->value, AP_MASK_SIZE);
|
||||
*read_aq = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
util_udev_free_file(file);
|
||||
|
||||
out:
|
||||
/* If we didn't read in masks, use current sysfs values */
|
||||
if ((!*read_ap) || (!*read_aq)) {
|
||||
rc = ap_read_sysfs_masks(sysap, sysaq, AP_MASK_SIZE);
|
||||
if (rc != 0)
|
||||
return false;
|
||||
if (!*read_ap)
|
||||
strcpy(ap, sysap);
|
||||
if (!*read_aq)
|
||||
strcpy(aq, sysaq);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given bitmask, create a list of vfio_ap_node entries corresponding
|
||||
* to the ON bits in the mask.
|
||||
*
|
||||
* @param[in] mask Character string of hex characters
|
||||
* @param[in, out] list List to be updated with entries for each ON bit
|
||||
*/
|
||||
void ap_mask_to_list(char *mask, struct util_list *list)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (mask == NULL || list == NULL)
|
||||
return;
|
||||
|
||||
for (i = 0; i <= AP_MAX_MASK_VALUE; i++) {
|
||||
if (ap_test_bit(i, mask))
|
||||
vfio_ap_node_add_tail(list, i);
|
||||
}
|
||||
|
||||
/* Could have duplicates if the input list was not empty */
|
||||
util_list_sort(list, vfio_ap_node_cmp, NULL);
|
||||
vfio_ap_node_remove_dupes(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the specified list, remove all elements and free each node
|
||||
*
|
||||
* @param[in, out] list List that will have all entries removed
|
||||
*/
|
||||
void ap_list_remove_all(struct util_list *list)
|
||||
{
|
||||
struct ap_node *node;
|
||||
|
||||
while (!util_list_is_empty(list)) {
|
||||
node = util_list_start(list);
|
||||
util_list_remove(list, node);
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_LOCKFILE
|
||||
static int ap_lockfile_create(int flags)
|
||||
{
|
||||
return lockfile_create(AP_LOCKFILE, AP_LOCK_RETRIES, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire the ap config lock using this process PID (L_PID)
|
||||
*
|
||||
* @retval 0 Lock successfully acquired on behalf of L_PID
|
||||
* @retval != 0 Error, lock was not obtained
|
||||
*/
|
||||
int ap_get_lock(void)
|
||||
{
|
||||
return ap_lockfile_create(L_PID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire the ap config lock using the parent process PID (L_PPID) -- intended
|
||||
* for use by the mdevctl callout ap-check utility
|
||||
*
|
||||
* @retval 0 Lock successfully acquired on behalf of L_PPID
|
||||
* @retval != 0 Error, lock was not obtained
|
||||
*/
|
||||
int ap_get_lock_callout(void)
|
||||
{
|
||||
return ap_lockfile_create(L_PPID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the ap config lock
|
||||
*
|
||||
* @retval 0 Lock successfully released or file didn't exist
|
||||
* @retval != 0 Error removing the lockfile
|
||||
*/
|
||||
int ap_release_lock(void)
|
||||
{
|
||||
return lockfile_remove(AP_LOCKFILE);
|
||||
}
|
||||
#else
|
||||
/* If no liblockfile, actions are performed without acquiring the file lock */
|
||||
int ap_get_lock(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ap_get_lock_callout(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ap_release_lock(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* HAVE_LOCKFILE */
|
||||
19
libcpumf/Makefile
Normal file
19
libcpumf/Makefile
Normal file
@@ -0,0 +1,19 @@
|
||||
include ../common.mak
|
||||
|
||||
lib = libcpumf.a
|
||||
|
||||
examples = libcpumf_example
|
||||
|
||||
all: $(lib)
|
||||
examples: $(lib) $(examples)
|
||||
|
||||
objects = libcpumf_pmutype.o libcpumf_cpuset.o libcpumf_support.o
|
||||
|
||||
$(lib): $(objects)
|
||||
|
||||
install: all
|
||||
|
||||
libcpumf_example: libcpumf_example.o $(lib) $(rootdir)/libutil/libutil.a
|
||||
|
||||
clean:
|
||||
rm -f *.o $(lib) $(examples)
|
||||
75
libcpumf/libcpumf_cpuset.c
Normal file
75
libcpumf/libcpumf_cpuset.c
Normal file
@@ -0,0 +1,75 @@
|
||||
/* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
int libcpumf_cpuset(const char *parm, cpu_set_t *mask)
|
||||
{
|
||||
char *cp, *buffer = strdup(parm);
|
||||
int to, from, rc;
|
||||
|
||||
if (!buffer) /* Errno set to ENOMEM */
|
||||
return -1;
|
||||
/* Check for invalid characters, such as 11.12 instead 11-12
|
||||
* but allow blanks and newline. Newline is appended
|
||||
* when the string is taken from sysfs files, for example
|
||||
* /sys/devices/system/cpu/online
|
||||
*/
|
||||
if (strspn(buffer, "0123456789-,\n ") != strlen(buffer)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
CPU_ZERO(mask);
|
||||
for (; (cp = strtok(buffer, ",")); buffer = NULL) {
|
||||
char *dash = strchr(cp, '-'); /* Range character? */
|
||||
bool is_ok;
|
||||
|
||||
if (dash) {
|
||||
rc = sscanf(cp, "%d-%d", &from, &to);
|
||||
is_ok = rc == 2;
|
||||
} else {
|
||||
rc = sscanf(cp, "%d", &to);
|
||||
from = to;
|
||||
is_ok = rc == 1;
|
||||
}
|
||||
if (!is_ok) {
|
||||
errno = ERANGE;
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
for (; from <= to; ++from)
|
||||
CPU_SET(from, mask);
|
||||
}
|
||||
rc = 0;
|
||||
out:
|
||||
free(buffer);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int libcpumf_cpuset_fn(const char *filename, cpu_set_t *mask)
|
||||
{
|
||||
char *txt = NULL;
|
||||
ssize_t ret = -1;
|
||||
size_t len = 0;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(filename, "r");
|
||||
if (!fp)
|
||||
return ret;
|
||||
/* Read out file, one line expected */
|
||||
ret = getline(&txt, &len, fp);
|
||||
fclose(fp);
|
||||
if (ret > 0)
|
||||
ret = libcpumf_cpuset(txt, mask);
|
||||
free(txt);
|
||||
return ret;
|
||||
}
|
||||
89
libcpumf/libcpumf_example.c
Normal file
89
libcpumf/libcpumf_example.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
unsigned long min, max, speed, sfb_min, sfb_max;
|
||||
int rc, pmu, cfvn, csvn, auth;
|
||||
char *pmuname;
|
||||
cpu_set_t set;
|
||||
|
||||
pmu = libcpumf_pmutype(S390_CPUMF_CF);
|
||||
if (pmu >= 0)
|
||||
printf("PMU %stype %d\n", S390_CPUMF_CF, pmu);
|
||||
else
|
||||
printf("PMU %stype error %d\n", S390_CPUMF_CF, errno);
|
||||
pmu = libcpumf_pmutype(S390_CPUMF_SF);
|
||||
if (pmu >= 0)
|
||||
printf("PMU %stype %d\n", S390_CPUMF_SF, pmu);
|
||||
else
|
||||
printf("PMU %stype error %d\n", S390_CPUMF_SF, errno);
|
||||
pmu = libcpumf_pmutype(S390_CPUMF_CFDIAG);
|
||||
if (pmu >= 0)
|
||||
printf("PMU %stype %d\n", S390_CPUMF_CFDIAG, pmu);
|
||||
else
|
||||
printf("PMU %stype error %d\n", S390_CPUMF_CFDIAG, errno);
|
||||
|
||||
rc = libcpumf_cpuset_fn(S390_CPUS_ONLINE, &set);
|
||||
if (rc == 0) {
|
||||
puts("Online CPUs:");
|
||||
for (int i = 0; i < CPU_SETSIZE; ++i)
|
||||
if (CPU_ISSET(i, &set))
|
||||
printf("%d ", i);
|
||||
putchar('\n');
|
||||
}
|
||||
rc = libcpumf_cpuset("0-7,9,11-12 ,15", &set);
|
||||
if (rc == 0) {
|
||||
puts("String CPUs:");
|
||||
for (int i = 0; i < CPU_SETSIZE; ++i)
|
||||
if (CPU_ISSET(i, &set))
|
||||
printf("%d ", i);
|
||||
putchar('\n');
|
||||
} else {
|
||||
printf("libcpumf_cpuset input invalid %d\n", errno);
|
||||
}
|
||||
|
||||
printf("CPUMCF support %d\n", libcpumf_have_cpumcf());
|
||||
rc = libcpumf_cpumcf_info(&cfvn, &csvn, &auth);
|
||||
printf("libcpumf_cpumcf_info %d", rc);
|
||||
if (rc)
|
||||
printf(" cfvn %d csvn %d authorization %#x", cfvn, csvn, auth);
|
||||
putchar('\n');
|
||||
|
||||
printf("CPUMSF support %d\n", libcpumf_have_cpumsf());
|
||||
rc = libcpumf_cpumsf_info(&min, &max, &speed, &cfvn, &csvn);
|
||||
printf("libcpumf_cpumsf_info %d", rc);
|
||||
if (rc)
|
||||
printf(" min %ld max %ld speed %#lx basic %d diag %d", min,
|
||||
max, speed, cfvn, csvn);
|
||||
putchar('\n');
|
||||
|
||||
printf("CPUMSF have sfb %d\n", libcpumf_have_sfb());
|
||||
rc = libcpumf_sfb_info(&sfb_min, &sfb_max);
|
||||
printf("libcpumf_sfb_info %d", rc);
|
||||
if (rc)
|
||||
printf(" sfb_min %lu sfb_max %lu", sfb_min, sfb_max);
|
||||
putchar('\n');
|
||||
|
||||
printf("PAI crypto support %d\n", libcpumf_have_pai_crypto());
|
||||
|
||||
rc = libcpumf_pmuname(10, &pmuname);
|
||||
if (rc) {
|
||||
printf("PMU type 10 PMU name lookup error %d\n", rc);
|
||||
} else {
|
||||
printf("PMU type 10 PMU name %s\n", pmuname);
|
||||
free(pmuname);
|
||||
}
|
||||
|
||||
printf("PAI NNPA support %d\n", libcpumf_have_pai_nnpa());
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
72
libcpumf/libcpumf_pmutype.c
Normal file
72
libcpumf/libcpumf_pmutype.c
Normal file
@@ -0,0 +1,72 @@
|
||||
/* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_scandir.h"
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
int libcpumf_pmutype(const char *dirname)
|
||||
{
|
||||
FILE *file;
|
||||
char *fn;
|
||||
int ret;
|
||||
|
||||
ret = asprintf(&fn, "%s/type", dirname);
|
||||
if (ret == -1) /* No memory, errno set */
|
||||
return ret;
|
||||
file = fopen(fn, "r");
|
||||
free(fn);
|
||||
ret = -1; /* Errno set on file open error */
|
||||
if (file) {
|
||||
/* Read out a single number from that file */
|
||||
if (fscanf(file, "%u", &ret) != 1)
|
||||
/* Unexpected format error, set errno */
|
||||
errno = -ERANGE;
|
||||
fclose(file);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int libcpumf_pmuname(unsigned int wanted_type, char **name)
|
||||
{
|
||||
struct dirent **de_vec;
|
||||
unsigned int type;
|
||||
char *dirname;
|
||||
char *path;
|
||||
int count;
|
||||
int rc;
|
||||
|
||||
path = util_path_sysfs("devices");
|
||||
count = util_scandir(&de_vec, alphasort, path, "(pai|cpum_).*");
|
||||
free(path);
|
||||
|
||||
*name = NULL;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (de_vec[i]->d_type == DT_DIR) {
|
||||
dirname = de_vec[i]->d_name;
|
||||
path = util_path_sysfs("devices/%s/type", dirname);
|
||||
rc = util_file_read_ui(&type, 10, path);
|
||||
if (rc)
|
||||
warn("Failed to open %s", path);
|
||||
free(path);
|
||||
if (!rc && type == wanted_type) {
|
||||
*name = util_strdup(dirname);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
out:
|
||||
util_scandir_free(de_vec, count);
|
||||
return *name ? 0 : -1;
|
||||
}
|
||||
155
libcpumf/libcpumf_support.c
Normal file
155
libcpumf/libcpumf_support.c
Normal file
@@ -0,0 +1,155 @@
|
||||
/* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <err.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
#define SERVICELEVEL "/proc/service_levels"
|
||||
|
||||
bool libcpumf_cpumcf_info(int *cfvn, int *csvn, int *auth)
|
||||
{
|
||||
char *linep = NULL;
|
||||
bool rc = false;
|
||||
size_t line_sz;
|
||||
ssize_t nbytes;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(SERVICELEVEL, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, SERVICELEVEL);
|
||||
|
||||
while ((nbytes = getline(&linep, &line_sz, fp)) != EOF) {
|
||||
if (!strncmp(linep, "CPU-MF: Counter facility:", 25)) {
|
||||
int cnt = sscanf(linep, "CPU-MF: Counter facility:"
|
||||
" version=%d.%d authorization=%x",
|
||||
cfvn, csvn, auth);
|
||||
if (cnt != 3) {
|
||||
warnx("Can not parse line %s", linep);
|
||||
goto out;
|
||||
}
|
||||
rc = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
out:
|
||||
fclose(fp);
|
||||
free(linep);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool libcpumf_have_cpumcf(void)
|
||||
{
|
||||
int cfvn, csvn, auth;
|
||||
|
||||
return libcpumf_cpumcf_info(&cfvn, &csvn, &auth);
|
||||
}
|
||||
|
||||
bool libcpumf_cpumsf_info(unsigned long *min, unsigned long *max,
|
||||
unsigned long *speed, int *basic_sz, int *diag_sz)
|
||||
{
|
||||
char *linep = NULL;
|
||||
bool rc = true;
|
||||
size_t line_sz;
|
||||
ssize_t nbytes;
|
||||
int hit = 0;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(SERVICELEVEL, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, SERVICELEVEL);
|
||||
|
||||
while ((nbytes = getline(&linep, &line_sz, fp)) != EOF) {
|
||||
int ok;
|
||||
|
||||
if (!strncmp(linep, "CPU-MF: Sampling facility: min", 30)) {
|
||||
ok = sscanf(linep, "CPU-MF: Sampling facility:"
|
||||
" min_rate=%ld max_rate=%ld cpu_speed=%ld",
|
||||
min, max, speed);
|
||||
if (ok != 3) {
|
||||
warnx("Can not parse line %s", linep);
|
||||
goto out;
|
||||
}
|
||||
hit += 1;
|
||||
}
|
||||
if (!strncmp(linep, "CPU-MF: Sampling facility: mode=basic", 37)) {
|
||||
ok = sscanf(linep, "CPU-MF: Sampling facility:"
|
||||
" mode=basic sample_size=%u", basic_sz);
|
||||
if (ok != 1) {
|
||||
warnx("Can not parse line %s", linep);
|
||||
goto out;
|
||||
}
|
||||
hit += 1;
|
||||
}
|
||||
if (!strncmp(linep, "CPU-MF: Sampling facility: mode=diag", 36)) {
|
||||
ok = sscanf(linep, "CPU-MF: Sampling facility:"
|
||||
" mode=diagnostic sample_size=%u",
|
||||
diag_sz);
|
||||
if (ok != 1) {
|
||||
warnx("Can not parse line %s", linep);
|
||||
goto out;
|
||||
}
|
||||
hit += 1;
|
||||
}
|
||||
}
|
||||
out:
|
||||
fclose(fp);
|
||||
free(linep);
|
||||
if (hit != 3)
|
||||
rc = false;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool libcpumf_have_cpumsf(void)
|
||||
{
|
||||
unsigned long a, b, c;
|
||||
int basic_sz, diag_sz;
|
||||
|
||||
return libcpumf_cpumsf_info(&a, &b, &c, &basic_sz, &diag_sz);
|
||||
}
|
||||
|
||||
bool libcpumf_have_sfb(void)
|
||||
{
|
||||
unsigned long a, b;
|
||||
|
||||
return libcpumf_sfb_info(&a, &b);
|
||||
}
|
||||
|
||||
bool libcpumf_sfb_info(unsigned long *min, unsigned long *max)
|
||||
{
|
||||
int rc = false;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(S390_CPUMSF_BUFFERSZ, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, "%s", S390_CPUMSF_BUFFERSZ);
|
||||
if (fscanf(fp, "%lu,%lu", min, max) == 2)
|
||||
rc = true;
|
||||
fclose(fp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool libcpumf_have_pai_crypto(void)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
return (stat(S390_SYSFS_PAI_CRYPTO, &statbuf) == -1) ? false : true;
|
||||
}
|
||||
|
||||
bool libcpumf_have_pai_nnpa(void)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
return (stat(S390_SYSFS_PAI_NNPA, &statbuf) == -1) ? false : true;
|
||||
}
|
||||
@@ -259,7 +259,7 @@ int kmip_decode_bignum(const unsigned char *data, uint32_t length, BIGNUM **bn)
|
||||
if (data[0] & 0x80) {
|
||||
neg = 1;
|
||||
|
||||
tmp = malloc(length);
|
||||
tmp = calloc(1, length);
|
||||
if (tmp == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
101
libpv/Makefile
Normal file
101
libpv/Makefile
Normal file
@@ -0,0 +1,101 @@
|
||||
# Common definitions
|
||||
include ../common.mak
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
LIB := libpv.a
|
||||
|
||||
ifneq ($(shell sh -c 'command -v pkg-config'),)
|
||||
GLIB2_CFLAGS := $(shell pkg-config --silence-errors --cflags glib-2.0)
|
||||
GLIB2_LIBS := $(shell pkg-config --silence-errors --libs glib-2.0)
|
||||
LIBCRYPTO_CFLAGS := $(shell pkg-config --silence-errors --cflags libcrypto openssl)
|
||||
LIBCRYPTO_LIBS := $(shell pkg-config --silence-errors --libs libcrypto openssl)
|
||||
LIBCURL_CFLAGS := $(shell pkg-config --silence-errors --cflags libcurl)
|
||||
LIBCURL_LIBS := $(shell pkg-config --silence-errors --libs libcurl)
|
||||
else
|
||||
GLIB2_CFLAGS := -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include
|
||||
GLIB2_LIBS := -lglib-2.0
|
||||
LIBCRYPTO_CFLAGS :=
|
||||
LIBCRYPTO_LIBS := -lcrypto -lssl
|
||||
LIBCURL_CFLAGS := -I/usr/include/s390x-linux-gnu
|
||||
LIBCURL_LIBS := -lcurl
|
||||
endif
|
||||
LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) $(LIBCURL_LIBS)
|
||||
|
||||
WARNINGS := -Wall -Wextra -Wshadow \
|
||||
-Wcast-align -Wwrite-strings -Wmissing-prototypes \
|
||||
-Wmissing-declarations -Wredundant-decls -Wnested-externs \
|
||||
-Wno-long-long -Wuninitialized -Wconversion -Wstrict-prototypes \
|
||||
-Wpointer-arith -Wno-error=inline \
|
||||
-Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \
|
||||
-Werror \
|
||||
$(NULL)
|
||||
|
||||
ALL_CFLAGS += -std=gnu11 \
|
||||
-DOPENSSL_API_COMPAT=0x10101000L \
|
||||
$(GLIB2_CFLAGS) \
|
||||
$(LIBCRYPTO_CFLAGS) \
|
||||
$(LIBCURL_CFLAGS) \
|
||||
$(WARNINGS) \
|
||||
$(NULL)
|
||||
|
||||
BUILD_TARGETS := skip-$(LIB)
|
||||
ifneq (${HAVE_OPENSSL},0)
|
||||
ifneq (${HAVE_GLIB2},0)
|
||||
ifneq (${HAVE_LIBCURL},0)
|
||||
BUILD_TARGETS := $(LIB)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
sources := $(wildcard *.c)
|
||||
objects := $(patsubst %.c,%.o,$(sources))
|
||||
|
||||
all: $(BUILD_TARGETS) .check_dep-$(LIB)
|
||||
|
||||
$(LIB): $(objects)
|
||||
$(LIB): ALL_CFLAGS += -fPIC
|
||||
|
||||
$(objects): .check-dep-$(LIB)
|
||||
|
||||
install: all
|
||||
|
||||
clean:
|
||||
rm -f -- $(objects)
|
||||
rm -f -- $(LIB)
|
||||
rm -f -- .check-dep-$(LIB) .detect-openssl.dep.c
|
||||
|
||||
skip-$(LIB):
|
||||
echo " SKIP $(LIB) due to unresolved dependencies"
|
||||
|
||||
.PHONY: all install clean skip-$(LIB) install-$(LIB)
|
||||
|
||||
|
||||
.detect-openssl.dep.c:
|
||||
echo "#include <openssl/evp.h>" > $@
|
||||
echo "#if OPENSSL_VERSION_NUMBER < 0x10101000L" >> $@
|
||||
echo " #error openssl version 1.1.0 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-$(LIB): .detect-openssl.dep.c
|
||||
$(call check_dep, \
|
||||
"$(LIB)", \
|
||||
"glib.h", \
|
||||
"glib2-devel / libglib2.0-dev", \
|
||||
"HAVE_GLIB2=0")
|
||||
$(call check_dep, \
|
||||
"$(LIB)", \
|
||||
$^, \
|
||||
"openssl-devel / libssl-dev version >= 1.1.0", \
|
||||
"HAVE_OPENSSL=0", \
|
||||
"-I.")
|
||||
$(call check_dep, \
|
||||
"$(LIB)", \
|
||||
"curl/curl.h", \
|
||||
"libcurl-devel", \
|
||||
"HAVE_LIBCURL=0")
|
||||
touch $@
|
||||
1645
libpv/cert.c
Normal file
1645
libpv/cert.c
Normal file
File diff suppressed because it is too large
Load Diff
45
libpv/common.c
Normal file
45
libpv/common.c
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Libpv common functions.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*
|
||||
*/
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include "libpv/common.h"
|
||||
#include "libpv/cert.h"
|
||||
#include "libpv/curl.h"
|
||||
|
||||
/* setup and tear down */
|
||||
int pv_init(void)
|
||||
{
|
||||
static size_t openssl_initalized;
|
||||
|
||||
if (g_once_init_enter(&openssl_initalized)) {
|
||||
if (OPENSSL_VERSION_NUMBER < 0x1000100fL)
|
||||
g_assert_not_reached();
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
#else
|
||||
OPENSSL_init_crypto(0, NULL);
|
||||
#endif
|
||||
|
||||
if (pv_curl_init() != 0)
|
||||
return -1;
|
||||
|
||||
pv_cert_init();
|
||||
g_once_init_leave(&openssl_initalized, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pv_cleanup(void)
|
||||
{
|
||||
pv_cert_cleanup();
|
||||
pv_curl_cleanup();
|
||||
}
|
||||
15
libpv/config.h
Normal file
15
libpv/config.h
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Config file.
|
||||
* Must be include before any other header.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 LIBPV_CONFIG_H
|
||||
#define LIBPV_CONFIG_H
|
||||
#define GETTEXT_PACKAGE "libpv"
|
||||
|
||||
#endif /* LIBPV_CONFIG_H */
|
||||
529
libpv/crypto.c
Normal file
529
libpv/crypto.c
Normal file
@@ -0,0 +1,529 @@
|
||||
/*
|
||||
* Cryptography functions
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/kdf.h>
|
||||
|
||||
#include "lib/zt_common.h"
|
||||
#include "libpv/crypto.h"
|
||||
#include "libpv/glib-helper.h"
|
||||
#include "libpv/hash.h"
|
||||
|
||||
const char *pv_get_openssl_error(void)
|
||||
{
|
||||
const char *ret;
|
||||
BIO *bio;
|
||||
char *buf;
|
||||
long len;
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
ERR_print_errors(bio);
|
||||
len = BIO_get_mem_data(bio, &buf);
|
||||
if (len < 0)
|
||||
ret = "Cannot receive OpenSSL error message.";
|
||||
else
|
||||
ret = g_strndup(buf, (size_t)len);
|
||||
BIO_free(bio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int pv_BIO_reset(BIO *b)
|
||||
{
|
||||
int rc = BIO_reset(b);
|
||||
|
||||
if (rc != 1 && (BIO_method_type(b) == BIO_TYPE_FILE && rc != 0))
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
GBytes *pv_generate_rand_data(size_t size, GError **error)
|
||||
{
|
||||
g_autofree uint8_t *data = NULL;
|
||||
|
||||
if (size > INT_MAX) {
|
||||
g_set_error_literal(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_RANDOMIZATION,
|
||||
"Too many random data requested. Split it up");
|
||||
OPENSSL_clear_free(data, size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data = g_malloc(size);
|
||||
if (RAND_bytes(data, (int)size) != 1) {
|
||||
g_set_error_literal(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_RANDOMIZATION,
|
||||
"The required amount of random data is not available");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pv_sec_gbytes_new_take(g_steal_pointer(&data), size);
|
||||
}
|
||||
|
||||
GBytes *pv_generate_key(const EVP_CIPHER *cipher, GError **error)
|
||||
{
|
||||
int size;
|
||||
|
||||
pv_wrapped_g_assert(cipher);
|
||||
|
||||
size = EVP_CIPHER_key_length(cipher);
|
||||
if (size <= 0) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION,
|
||||
"Unknown cipher");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pv_generate_rand_data((guint)size, error);
|
||||
}
|
||||
|
||||
GBytes *pv_generate_iv(const EVP_CIPHER *cipher, GError **error)
|
||||
{
|
||||
int size;
|
||||
|
||||
pv_wrapped_g_assert(cipher);
|
||||
|
||||
size = EVP_CIPHER_iv_length(cipher);
|
||||
if (size <= 0) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION,
|
||||
"Unknown cipher");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pv_generate_rand_data((guint)size, error);
|
||||
}
|
||||
|
||||
static int64_t pv_gcm_encrypt_decrypt(GBytes *input, GBytes *aad, const PvCipherParms *parms,
|
||||
GBytes **output, GBytes **tagp, enum PvCryptoMode mode,
|
||||
GError **error)
|
||||
{
|
||||
const uint8_t *in_data, *aad_data = NULL, *iv_data, *key_data;
|
||||
size_t in_size, aad_size = 0, iv_size, key_size, out_size;
|
||||
const EVP_CIPHER *cipher = parms->cipher;
|
||||
const size_t tag_size = parms->tag_size;
|
||||
gboolean encrypt = mode == PV_ENCRYPT;
|
||||
g_autoptr(EVP_CIPHER_CTX) ctx = NULL;
|
||||
g_autofree uint8_t *out_data = NULL;
|
||||
g_autofree uint8_t *tag_data = NULL;
|
||||
const GBytes *key = parms->key;
|
||||
const GBytes *iv = parms->iv;
|
||||
int cipher_block_size;
|
||||
int64_t ret = -1;
|
||||
int len = -1;
|
||||
GBytes *tag;
|
||||
|
||||
g_assert(tagp);
|
||||
g_assert(cipher);
|
||||
g_assert(key);
|
||||
g_assert(iv);
|
||||
|
||||
tag = *tagp;
|
||||
in_data = g_bytes_get_data((GBytes *)input, &in_size);
|
||||
if (aad)
|
||||
aad_data = g_bytes_get_data((GBytes *)aad, &aad_size);
|
||||
iv_data = g_bytes_get_data((GBytes *)iv, &iv_size);
|
||||
key_data = g_bytes_get_data((GBytes *)key, &key_size);
|
||||
out_size = in_size;
|
||||
cipher_block_size = EVP_CIPHER_block_size(cipher);
|
||||
|
||||
/* Checks for later casts */
|
||||
g_assert(aad_size <= INT_MAX);
|
||||
g_assert(in_size <= INT_MAX);
|
||||
g_assert(iv_size <= INT_MAX);
|
||||
g_assert(cipher_block_size > 0);
|
||||
|
||||
ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx)
|
||||
g_abort();
|
||||
|
||||
if (tag_size == 0 || (tag_size % (size_t)cipher_block_size != 0)) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"Passed tag size is incorrect");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Has the passed key the correct size? */
|
||||
if (EVP_CIPHER_key_length(cipher) != (int)key_size) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"Passed key has incorrect size: %ld != %d", key_size,
|
||||
EVP_CIPHER_key_length(cipher));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* First, set the cipher algorithm so we can verify our key/IV lengths
|
||||
*/
|
||||
if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"EVP_CIPHER_CTX_new failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set IV length */
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int)iv_size, NULL) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"EVP_CIPHER_CTX_ex failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Initialise key and IV */
|
||||
if (EVP_CipherInit_ex(ctx, NULL, NULL, key_data, iv_data, encrypt) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"EVP_CipherInit_ex failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Allocate output data */
|
||||
out_data = g_malloc0(out_size);
|
||||
if (encrypt)
|
||||
tag_data = g_malloc0(tag_size);
|
||||
|
||||
if (aad_size > 0) {
|
||||
/* Provide any AAD data */
|
||||
if (EVP_CipherUpdate(ctx, NULL, &len, aad_data, (int)aad_size) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"EVP_CipherUpdate failed");
|
||||
return -1;
|
||||
}
|
||||
g_assert(len == (int)aad_size);
|
||||
}
|
||||
|
||||
/* Provide data to be en/decrypted */
|
||||
if (EVP_CipherUpdate(ctx, out_data, &len, in_data, (int)in_size) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"EVP_CipherUpdate failed");
|
||||
return -1;
|
||||
}
|
||||
ret = len;
|
||||
|
||||
if (!encrypt) {
|
||||
const uint8_t *tmp_tag_data = NULL;
|
||||
size_t tmp_tag_size = 0;
|
||||
|
||||
if (tag)
|
||||
tmp_tag_data = g_bytes_get_data(tag, &tmp_tag_size);
|
||||
if (tag_size != tmp_tag_size) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"Getting the GCM tag failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set expected tag value */
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, (int)tmp_tag_size,
|
||||
(uint8_t *)tmp_tag_data) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"Setting the GCM tag failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finalize the en/decryption */
|
||||
if (EVP_CipherFinal_ex(ctx, (uint8_t *)out_data + len, &len) != 1) {
|
||||
if (encrypt)
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"Encrypting failed (EVP_CipherFinal_ex)");
|
||||
else
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_MATCH_TAG,
|
||||
"Verifying the GCM tag failed");
|
||||
return -1;
|
||||
}
|
||||
ret += len;
|
||||
|
||||
if (encrypt) {
|
||||
/* Get the tag */
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, (int)tag_size, tag_data) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
"Getting the GCM tag failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_assert(!*tagp);
|
||||
*tagp = g_bytes_new_take(g_steal_pointer(&tag_data), tag_size);
|
||||
}
|
||||
g_assert(ret == (int)out_size);
|
||||
g_assert(out_size == in_size);
|
||||
|
||||
g_assert(!*output);
|
||||
*output = pv_sec_gbytes_new_take(g_steal_pointer(&out_data), out_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t pv_gcm_encrypt(GBytes *plain, GBytes *aad, const PvCipherParms *parms, GBytes **cipher,
|
||||
GBytes **tag, GError **error)
|
||||
{
|
||||
pv_wrapped_g_assert(plain);
|
||||
pv_wrapped_g_assert(parms);
|
||||
pv_wrapped_g_assert(cipher);
|
||||
pv_wrapped_g_assert(tag);
|
||||
|
||||
return pv_gcm_encrypt_decrypt(plain, aad, parms, cipher, tag, PV_ENCRYPT, error);
|
||||
}
|
||||
|
||||
int64_t pv_gcm_decrypt(GBytes *cipher, GBytes *aad, GBytes *tag, const PvCipherParms *parms,
|
||||
GBytes **plain, GError **error)
|
||||
{
|
||||
pv_wrapped_g_assert(cipher);
|
||||
pv_wrapped_g_assert(tag);
|
||||
pv_wrapped_g_assert(parms);
|
||||
pv_wrapped_g_assert(plain);
|
||||
|
||||
return pv_gcm_encrypt_decrypt(cipher, aad, parms, plain, &tag, PV_DECRYPT, error);
|
||||
}
|
||||
|
||||
GBytes *pv_hkdf_extract_and_expand(size_t derived_key_len, GBytes *key, GBytes *salt, GBytes *info,
|
||||
const EVP_MD *md, G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
const unsigned char *salt_data, *key_data, *info_data;
|
||||
g_autoptr(EVP_PKEY_CTX) ctx = NULL;
|
||||
size_t salt_len, key_len, info_len;
|
||||
g_autofree unsigned char *derived_key = NULL;
|
||||
|
||||
g_assert(derived_key_len > 0);
|
||||
pv_wrapped_g_assert(key);
|
||||
pv_wrapped_g_assert(salt);
|
||||
pv_wrapped_g_assert(info);
|
||||
pv_wrapped_g_assert(md);
|
||||
|
||||
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
|
||||
if (!ctx)
|
||||
g_abort();
|
||||
|
||||
if (EVP_PKEY_derive_init(ctx) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_set_hkdf_md(ctx, md) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
salt_data = g_bytes_get_data(salt, &salt_len);
|
||||
if (salt_len > INT_MAX) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt_data, (int)salt_len) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
key_data = g_bytes_get_data(key, &key_len);
|
||||
if (key_len > INT_MAX) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_set1_hkdf_key(ctx, key_data, (int)key_len) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
info_data = g_bytes_get_data(info, &info_len);
|
||||
if (info_len > INT_MAX) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_add1_hkdf_info(ctx, (unsigned char *)info_data, (int)info_len) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
derived_key = g_malloc0(derived_key_len);
|
||||
if (EVP_PKEY_derive(ctx, derived_key, &derived_key_len) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL,
|
||||
"FAILED to derive key via HKDF");
|
||||
printf("%s\n", pv_get_openssl_error());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pv_sec_gbytes_new_take(g_steal_pointer(&derived_key), derived_key_len);
|
||||
}
|
||||
|
||||
EVP_PKEY *pv_generate_ec_key(int nid, GError **error)
|
||||
{
|
||||
g_autoptr(EVP_PKEY_CTX) ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
|
||||
g_autoptr(EVP_PKEY) ret = NULL;
|
||||
|
||||
g_assert(ctx);
|
||||
|
||||
if (EVP_PKEY_keygen_init(ctx) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION,
|
||||
_("EC key could not be auto-generated"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION,
|
||||
_("EC key could not be auto-generated"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_keygen(ctx, &ret) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION,
|
||||
_("EC key could not be auto-generated"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_steal_pointer(&ret);
|
||||
}
|
||||
|
||||
/* Convert a EVP_PKEY to the key format used in the PV header */
|
||||
PvEcdhPubKey *pv_evp_pkey_to_ecdh_pub_key(EVP_PKEY *key, GError **error)
|
||||
{
|
||||
g_autofree PvEcdhPubKey *ret = g_new0(PvEcdhPubKey, 1);
|
||||
g_autoptr(BIGNUM) pub_x_big = NULL, pub_y_big = NULL;
|
||||
g_autoptr(EC_KEY) ec_key = NULL;
|
||||
const EC_POINT *pub_key;
|
||||
const EC_GROUP *grp;
|
||||
|
||||
pv_wrapped_g_assert(key);
|
||||
|
||||
ec_key = EVP_PKEY_get1_EC_KEY(key);
|
||||
if (!ec_key) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("Key has the wrong type"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pub_key = EC_KEY_get0_public_key(ec_key);
|
||||
if (!pub_key) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("Failed to get public key"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
grp = EC_KEY_get0_group(ec_key);
|
||||
if (!grp) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("Failed to get EC group"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pub_x_big = BN_new();
|
||||
if (!pub_x_big)
|
||||
g_abort();
|
||||
|
||||
pub_y_big = BN_new();
|
||||
if (!pub_y_big)
|
||||
g_abort();
|
||||
|
||||
if (EC_POINT_get_affine_coordinates_GFp(grp, pub_key, pub_x_big, pub_y_big, NULL) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("Cannot convert key to internal format"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (BN_bn2binpad(pub_x_big, ret->x, sizeof(ret->x)) < 0) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("Cannot convert key to internal format"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (BN_bn2binpad(pub_y_big, ret->y, sizeof(ret->y)) < 0) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("Cannot convert key to internal format"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_steal_pointer(&ret);
|
||||
}
|
||||
|
||||
static GBytes *derive_key(EVP_PKEY *key1, EVP_PKEY *key2, GError **error)
|
||||
{
|
||||
g_autoptr(EVP_PKEY_CTX) ctx = NULL;
|
||||
uint8_t *data = NULL;
|
||||
size_t data_size, key_size;
|
||||
|
||||
ctx = EVP_PKEY_CTX_new(key1, NULL);
|
||||
if (!ctx)
|
||||
g_abort();
|
||||
|
||||
if (EVP_PKEY_derive_init(ctx) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("Key derivation failed"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_derive_set_peer(ctx, key2) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL,
|
||||
_("Key derivation failed"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Determine buffer length */
|
||||
if (EVP_PKEY_derive(ctx, NULL, &key_size) != 1) {
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_DERIVE,
|
||||
_("Key derivation failed"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data_size = key_size;
|
||||
data = OPENSSL_malloc(data_size);
|
||||
if (!data)
|
||||
g_abort();
|
||||
if (EVP_PKEY_derive(ctx, data, &data_size) != 1) {
|
||||
OPENSSL_clear_free(data, data_size);
|
||||
g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_DERIVE,
|
||||
_("Key derivation failed"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_assert(data_size == key_size);
|
||||
return pv_sec_gbytes_new_take(g_steal_pointer(&data), data_size);
|
||||
}
|
||||
|
||||
GBytes *pv_derive_exchange_key(EVP_PKEY *cust, EVP_PKEY *host, GError **error)
|
||||
{
|
||||
const guint8 append[] = { 0x00, 0x00, 0x00, 0x01 };
|
||||
g_autoptr(GBytes) derived_key = NULL, ret = NULL;
|
||||
g_autoptr(GByteArray) der_key_ga = NULL;
|
||||
g_autofree uint8_t *raw = NULL;
|
||||
size_t raw_len;
|
||||
|
||||
pv_wrapped_g_assert(cust);
|
||||
pv_wrapped_g_assert(host);
|
||||
|
||||
derived_key = derive_key(cust, host, error);
|
||||
if (!derived_key)
|
||||
return NULL;
|
||||
|
||||
der_key_ga = g_bytes_unref_to_array(g_steal_pointer(&derived_key));
|
||||
/* ANSI X.9.63-2011: 66 bytes x with leading 7 bits and
|
||||
* concatenate 32 bit int '1'
|
||||
*/
|
||||
der_key_ga = g_byte_array_append(der_key_ga, append, sizeof(append));
|
||||
/* free GBytesArray and get underlying data */
|
||||
raw_len = der_key_ga->len;
|
||||
raw = g_byte_array_free(g_steal_pointer(&der_key_ga), FALSE);
|
||||
|
||||
ret = pv_sha256_hash(raw, raw_len, error);
|
||||
OPENSSL_cleanse(raw, raw_len);
|
||||
return g_steal_pointer(&ret);
|
||||
}
|
||||
|
||||
GQuark pv_crypto_error_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("pv-crypto-error-quark");
|
||||
}
|
||||
116
libpv/curl.c
Normal file
116
libpv/curl.c
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Libcurl utils
|
||||
*
|
||||
* Copyright IBM Corp. 2020
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "lib/zt_common.h"
|
||||
#include "libpv/curl.h"
|
||||
|
||||
struct UserData {
|
||||
GByteArray *buffer;
|
||||
uint max_size;
|
||||
};
|
||||
|
||||
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
g_assert(userdata);
|
||||
struct UserData *data = (struct UserData *)userdata;
|
||||
GByteArray *buffer = data->buffer;
|
||||
uint64_t actual_size;
|
||||
size_t err;
|
||||
|
||||
g_assert(buffer);
|
||||
|
||||
if (!g_uint64_checked_mul(&actual_size, size, nmemb))
|
||||
g_abort();
|
||||
|
||||
/* Signal an error condition by returning a amount that differs
|
||||
* from the amount passed to the callback. This results in a
|
||||
* CURLE_WRITE_ERROR.
|
||||
*/
|
||||
err = actual_size + 1;
|
||||
|
||||
if (actual_size > G_MAXUINT)
|
||||
return err;
|
||||
|
||||
data->buffer = g_byte_array_append(buffer, (uint8_t *)ptr, (uint)actual_size);
|
||||
if (data->buffer->len > data->max_size)
|
||||
return err;
|
||||
|
||||
return actual_size;
|
||||
}
|
||||
|
||||
int pv_curl_init(void)
|
||||
{
|
||||
if (curl_global_init(CURL_GLOBAL_ALL) != 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pv_curl_cleanup(void)
|
||||
{
|
||||
curl_global_cleanup();
|
||||
}
|
||||
|
||||
GByteArray *curl_download(const char *url, long timeout_ms, uint max_size, GError **err)
|
||||
{
|
||||
g_autoptr(GByteArray) ret = NULL;
|
||||
g_autoptr(CURL) handle = NULL;
|
||||
g_autofree char *agent = NULL;
|
||||
struct UserData userdata;
|
||||
CURLcode rc;
|
||||
|
||||
/* set up curl session */
|
||||
handle = curl_easy_init();
|
||||
if (!handle)
|
||||
g_abort();
|
||||
|
||||
/* follow redirection */
|
||||
rc = curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
if (rc != CURLE_OK)
|
||||
goto curl_err;
|
||||
rc = curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, timeout_ms);
|
||||
if (rc != CURLE_OK)
|
||||
goto curl_err;
|
||||
rc = curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L);
|
||||
if (rc != CURLE_OK)
|
||||
goto curl_err;
|
||||
agent = g_strdup_printf("%s/%s", GETTEXT_PACKAGE, RELEASE_STRING);
|
||||
rc = curl_easy_setopt(handle, CURLOPT_USERAGENT, agent);
|
||||
if (rc != CURLE_OK)
|
||||
goto curl_err;
|
||||
rc = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
if (rc != CURLE_OK)
|
||||
goto curl_err;
|
||||
ret = g_byte_array_new();
|
||||
userdata.buffer = ret;
|
||||
userdata.max_size = max_size;
|
||||
rc = curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void *)&userdata);
|
||||
if (rc != CURLE_OK)
|
||||
goto curl_err;
|
||||
rc = curl_easy_setopt(handle, CURLOPT_URL, url);
|
||||
if (rc != CURLE_OK)
|
||||
goto curl_err;
|
||||
|
||||
rc = curl_easy_perform(handle);
|
||||
if (rc != CURLE_OK) {
|
||||
g_set_error(err, PV_CURL_ERROR, PV_CURL_ERROR_DOWNLOAD_FAILED,
|
||||
_("download failed: %s"), curl_easy_strerror(rc));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_steal_pointer(&ret);
|
||||
curl_err:
|
||||
g_set_error(err, PV_CURL_ERROR, PV_CURL_ERROR_CURL_INIT_FAILED,
|
||||
_("cURL initialization failed: %s"), curl_easy_strerror(rc));
|
||||
return NULL;
|
||||
}
|
||||
179
libpv/glib-helper.c
Normal file
179
libpv/glib-helper.c
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Glib convenience functions
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
#include "libpv/glib-helper.h"
|
||||
|
||||
struct __data {
|
||||
void *data;
|
||||
size_t size;
|
||||
GFreeFunc free_func;
|
||||
};
|
||||
|
||||
static void __data_clear_and_free(void *p)
|
||||
{
|
||||
struct __data *ptr = p;
|
||||
|
||||
if (!ptr)
|
||||
return;
|
||||
|
||||
if (ptr->data) {
|
||||
OPENSSL_cleanse(ptr->data, ptr->size);
|
||||
ptr->free_func(ptr->data);
|
||||
}
|
||||
g_free(ptr);
|
||||
}
|
||||
|
||||
static GBytes *pv_sec_gbytes_new_take_func(void *data, size_t size, GFreeFunc free_func)
|
||||
{
|
||||
struct __data *tmp = g_new(struct __data, 1);
|
||||
|
||||
tmp->data = data;
|
||||
tmp->size = size;
|
||||
tmp->free_func = free_func;
|
||||
|
||||
return g_bytes_new_with_free_func(data, size, __data_clear_and_free, tmp);
|
||||
}
|
||||
|
||||
GBytes *pv_sec_gbytes_new_take(void *data, size_t size)
|
||||
{
|
||||
return pv_sec_gbytes_new_take_func(data, size, g_free);
|
||||
}
|
||||
|
||||
GBytes *pv_sec_gbytes_new(const void *data, size_t size)
|
||||
{
|
||||
g_autofree void *tmp_data = NULL;
|
||||
|
||||
g_return_val_if_fail(data || size != 0, NULL);
|
||||
|
||||
tmp_data = g_malloc(size);
|
||||
memcpy(tmp_data, data, size);
|
||||
return pv_sec_gbytes_new_take(g_steal_pointer(&tmp_data), size);
|
||||
}
|
||||
|
||||
int pv_file_seek(FILE *file, long offset, int whence, GError **error)
|
||||
{
|
||||
int cached_errno;
|
||||
int ret = fseek(file, offset, whence);
|
||||
|
||||
if (ret) {
|
||||
cached_errno = errno;
|
||||
g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR,
|
||||
"Cannot seek: %s", g_strerror(cached_errno));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t pv_file_write(FILE *file, const void *ptr, size_t size, GError **error)
|
||||
{
|
||||
int cached_errno;
|
||||
size_t n = fwrite(ptr, 1, size, file);
|
||||
|
||||
if (n != size) {
|
||||
cached_errno = errno;
|
||||
g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR,
|
||||
"Cannot write: %s", g_strerror(cached_errno));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
long pv_file_close(FILE *file, GError **error)
|
||||
{
|
||||
int cached_errno;
|
||||
int ret = fclose(file);
|
||||
|
||||
if (ret) {
|
||||
cached_errno = errno;
|
||||
g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR,
|
||||
"Cannot close: %s", g_strerror(cached_errno));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void pv_auto_close_file(FILE *file)
|
||||
{
|
||||
if (!file)
|
||||
return;
|
||||
|
||||
(void)pv_file_close(file, NULL);
|
||||
}
|
||||
|
||||
long pv_file_tell(FILE *file, GError **error)
|
||||
{
|
||||
int cached_errno;
|
||||
long n = ftell(file);
|
||||
|
||||
if (n < 0) {
|
||||
cached_errno = errno;
|
||||
g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR,
|
||||
"Cannot tell: %s", g_strerror(cached_errno));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
FILE *pv_file_open(const char *filename, const char *mode, GError **error)
|
||||
{
|
||||
FILE *file = fopen(filename, mode);
|
||||
int cached_errno;
|
||||
|
||||
if (!file) {
|
||||
cached_errno = errno;
|
||||
g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR,
|
||||
"Cannot open '%s'. %s", filename, g_strerror(cached_errno));
|
||||
return NULL;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
GBytes *pv_file_get_content_as_g_bytes(const char *filename, GError **error)
|
||||
{
|
||||
g_autofree char *data = NULL;
|
||||
size_t data_size;
|
||||
|
||||
if (!g_file_get_contents(filename, &data, &data_size, error))
|
||||
return NULL;
|
||||
|
||||
return g_bytes_new_take(g_steal_pointer(&data), data_size);
|
||||
}
|
||||
|
||||
GBytes *pv_file_get_content_as_secure_bytes(const char *filename)
|
||||
{
|
||||
g_autoptr(FILE) f = fopen(filename, "rb");
|
||||
g_autofree char *data = NULL;
|
||||
ssize_t file_size;
|
||||
size_t data_size;
|
||||
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
file_size = ftell(f);
|
||||
if (file_size < 0)
|
||||
return NULL;
|
||||
data_size = (size_t)file_size;
|
||||
fseek(f, 0, SEEK_SET);
|
||||
data = g_malloc0(data_size);
|
||||
if (data_size != fread(data, 1, data_size, f))
|
||||
return NULL;
|
||||
return pv_sec_gbytes_new_take(g_steal_pointer(&data), data_size);
|
||||
}
|
||||
|
||||
void *pv_gbytes_memcpy(void *dst, size_t dst_size, GBytes *src)
|
||||
{
|
||||
size_t src_size;
|
||||
const void *src_data = g_bytes_get_data(src, &src_size);
|
||||
|
||||
if (dst_size < src_size)
|
||||
return NULL;
|
||||
return memcpy(dst, src_data, src_size);
|
||||
}
|
||||
147
libpv/hash.c
Normal file
147
libpv/hash.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Hashing functions.
|
||||
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include "libpv/crypto.h"
|
||||
#include "libpv/hash.h"
|
||||
|
||||
GBytes *pv_sha256_hash(uint8_t *buf, size_t size, GError **error)
|
||||
{
|
||||
g_autoptr(EVP_MD_CTX) ctx = NULL;
|
||||
|
||||
ctx = pv_digest_ctx_new(EVP_sha256(), error);
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
|
||||
if (pv_digest_ctx_update_raw(ctx, buf, size, error) != 0)
|
||||
return NULL;
|
||||
|
||||
return pv_digest_ctx_finalize(ctx, error);
|
||||
}
|
||||
|
||||
EVP_MD_CTX *pv_digest_ctx_new(const EVP_MD *md, GError **error)
|
||||
{
|
||||
g_autoptr(EVP_MD_CTX) ctx = EVP_MD_CTX_new();
|
||||
|
||||
if (!ctx) {
|
||||
g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
|
||||
_("Hash context generation failed"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (EVP_DigestInit_ex(ctx, md, NULL) != 1) {
|
||||
g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
|
||||
_("EVP_DigestInit_ex failed"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_steal_pointer(&ctx);
|
||||
}
|
||||
|
||||
int pv_digest_ctx_update_raw(EVP_MD_CTX *ctx, const uint8_t *buf, size_t size, GError **error)
|
||||
{
|
||||
if (!buf || size == 0)
|
||||
return 0;
|
||||
|
||||
if (EVP_DigestUpdate(ctx, buf, size) != 1) {
|
||||
g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
|
||||
_("EVP_DigestUpdate failed"));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pv_digest_ctx_update(EVP_MD_CTX *ctx, GBytes *data, GError **error)
|
||||
{
|
||||
const uint8_t *buf;
|
||||
size_t buf_size;
|
||||
|
||||
if (!data)
|
||||
return 0;
|
||||
buf = g_bytes_get_data((GBytes *)data, &buf_size);
|
||||
return pv_digest_ctx_update_raw(ctx, buf, buf_size, error);
|
||||
}
|
||||
|
||||
GBytes *pv_digest_ctx_finalize(EVP_MD_CTX *ctx, GError **error)
|
||||
{
|
||||
int md_size = EVP_MD_size(EVP_MD_CTX_md(ctx));
|
||||
g_autofree uint8_t *digest = NULL;
|
||||
unsigned int digest_size;
|
||||
|
||||
g_assert(md_size > 0);
|
||||
|
||||
digest = g_malloc0((uint)md_size);
|
||||
if (EVP_DigestFinal_ex(ctx, digest, &digest_size) != 1) {
|
||||
g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
|
||||
_("EVP_DigestFinal_ex failed"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_assert(digest_size == (uint)md_size);
|
||||
return g_bytes_new_take(g_steal_pointer(&digest), digest_size);
|
||||
}
|
||||
|
||||
HMAC_CTX *pv_hmac_ctx_new(GBytes *key, const EVP_MD *md, GError **error)
|
||||
{
|
||||
g_autoptr(HMAC_CTX) ctx = HMAC_CTX_new();
|
||||
const uint8_t *key_data;
|
||||
size_t key_size;
|
||||
|
||||
key_data = g_bytes_get_data(key, &key_size);
|
||||
|
||||
if (HMAC_Init_ex(ctx, key_data, (int)key_size, md, NULL) != 1) {
|
||||
g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
|
||||
"unable to create HMAC context: %s", pv_get_openssl_error());
|
||||
return NULL;
|
||||
}
|
||||
return g_steal_pointer(&ctx);
|
||||
}
|
||||
|
||||
int pv_hmac_ctx_update_raw(HMAC_CTX *ctx, const void *buf, size_t size, GError **error)
|
||||
{
|
||||
if (!buf || size == 0)
|
||||
return 0;
|
||||
|
||||
if (HMAC_Update(ctx, buf, size) != 1) {
|
||||
g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
|
||||
"unable to add data to HMAC context: %s", pv_get_openssl_error());
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pv_hmac_ctx_update(HMAC_CTX *ctx, GBytes *data, GError **error)
|
||||
{
|
||||
const uint8_t *buf;
|
||||
size_t buf_size;
|
||||
|
||||
if (!data)
|
||||
return 0;
|
||||
buf = g_bytes_get_data((GBytes *)data, &buf_size);
|
||||
return pv_hmac_ctx_update_raw(ctx, buf, buf_size, error);
|
||||
}
|
||||
|
||||
GBytes *pv_hamc_ctx_finalize(HMAC_CTX *ctx, GError **error)
|
||||
{
|
||||
int md_size = EVP_MD_size(HMAC_CTX_get_md(ctx));
|
||||
g_autofree uint8_t *hmac = NULL;
|
||||
unsigned int hmac_size = 0;
|
||||
|
||||
g_assert(md_size > 0);
|
||||
|
||||
hmac = g_malloc0((unsigned int)md_size);
|
||||
|
||||
if (HMAC_Final(ctx, hmac, &hmac_size) != 1) {
|
||||
g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
|
||||
"unable to calculate HMAC: %s", pv_get_openssl_error());
|
||||
return NULL;
|
||||
}
|
||||
return g_bytes_new_take(g_steal_pointer(&hmac), hmac_size);
|
||||
}
|
||||
@@ -1549,6 +1549,59 @@ int SK_EP11_reencipher_key(const struct sk_ext_ep11_lib *ep11_lib,
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
memcpy(blob, lrb.payload, lrb.pllen);
|
||||
|
||||
/* re-encipher MACed SPKI */
|
||||
rb.domain = domain;
|
||||
lrb.domain = domain;
|
||||
|
||||
resp_len = sizeof(resp);
|
||||
req_len = ep11.dll_xcpa_cmdblock(req, sizeof(req), XCP_ADM_REENCRYPT,
|
||||
&rb, NULL, key_token + hdr->len,
|
||||
key_token_length - hdr->len);
|
||||
if (req_len < 0) {
|
||||
sk_debug(debug, "Failed to build XCP command block");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
rv = ep11.dll_m_admin(resp, &resp_len, NULL, NULL, req, req_len, NULL,
|
||||
0, ep11_lib->target);
|
||||
if (rv != CKR_OK || resp_len == 0) {
|
||||
sk_debug(debug, "Command XCP_ADM_REENCRYPT failed. "
|
||||
"rc = 0x%lx, resp_len = %ld", rv, resp_len);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
rc = ep11.dll_xcpa_internal_rv(resp, resp_len, &lrb, &rv);
|
||||
if (rc != 0) {
|
||||
sk_debug(debug, "Failed to parse response. rc = %d", rc);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (rv != CKR_OK) {
|
||||
sk_debug(debug, "Failed to re-encrypt the EP11 secure key. "
|
||||
"rc = 0x%lx", rv);
|
||||
switch (rv) {
|
||||
case CKR_IBM_WKID_MISMATCH:
|
||||
sk_debug(debug, "The EP11 secure key is currently "
|
||||
"encrypted under a different master that does "
|
||||
"not match the master key in the CURRENT "
|
||||
"master key register of APQN %02X.%04X",
|
||||
card, domain);
|
||||
break;
|
||||
}
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (key_token_length - hdr->len != lrb.pllen) {
|
||||
sk_debug(debug, "Re-encrypted EP11 secure key size has "
|
||||
"changed: org-len: %lu, new-len: %lu",
|
||||
hdr->len - sizeof(*hdr), lrb.pllen);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
memcpy(key_token + hdr->len, lrb.payload, lrb.pllen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -93,6 +93,8 @@ const char *util_arch_machine_type_to_str(int type)
|
||||
return "IBM z15";
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z15_T02:
|
||||
return "IBM z15 Model T02";
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z16:
|
||||
return "IBM z16";
|
||||
default:
|
||||
return "Unknown machine type";
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <linux/limits.h>
|
||||
@@ -21,11 +22,16 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/util_exit_code.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_panic.h"
|
||||
#include "lib/util_prg.h"
|
||||
|
||||
#define READ_CHUNK_SIZE 4096
|
||||
|
||||
extern const char *toolname;
|
||||
|
||||
/*
|
||||
* Read the first line of a file into given buffer
|
||||
*/
|
||||
@@ -536,3 +542,129 @@ int util_file_read_va(const char *path, const char *fmt, ...)
|
||||
return -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an error message indicating an out-of-memory situation and exit.
|
||||
*/
|
||||
static void oom(void)
|
||||
{
|
||||
fprintf(stderr, "Out of memory\n");
|
||||
|
||||
/* We can't rely on our clean-up routines to work reliably during an
|
||||
* OOM situation, so just exit here.
|
||||
*/
|
||||
exit(UTIL_EXIT_OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all data from @fd and return address of resulting buffer in
|
||||
* @buffer_ptr. If @size_ptr is non-zero, use it to store the size of the
|
||||
* resulting buffer. Return %UTIL_EXIT_OK on success.
|
||||
*
|
||||
* @param[in] fd File descriptor to read data from
|
||||
* @param[in, out] buffer_ptr Buffer to read data into
|
||||
* @param[in, out] size_ptr Buffer to save size of data read into buffer_ptr
|
||||
*
|
||||
* @retval 0 read was successful
|
||||
* @retval UTIL_EXIT_RUNTIME_ERROR error while reading file
|
||||
*/
|
||||
util_exit_code_t util_file_read_fd_buf(FILE *fd, void **buffer_ptr,
|
||||
size_t *size_ptr)
|
||||
{
|
||||
char *buffer = NULL;
|
||||
size_t done = 0;
|
||||
|
||||
while (!feof(fd)) {
|
||||
buffer = realloc(buffer, done + READ_CHUNK_SIZE);
|
||||
if (!buffer)
|
||||
oom();
|
||||
done += fread(&buffer[done], 1, READ_CHUNK_SIZE, fd);
|
||||
if (ferror(fd)) {
|
||||
free(buffer);
|
||||
return UTIL_EXIT_RUNTIME_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
buffer = realloc(buffer, done);
|
||||
if (!buffer && done > 0)
|
||||
oom();
|
||||
|
||||
*buffer_ptr = buffer;
|
||||
if (size_ptr)
|
||||
*size_ptr = done;
|
||||
|
||||
return UTIL_EXIT_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read text from @fd and return resulting NULL-terminated text buffer.
|
||||
* If @chomp is non-zero, remove trailing newline character. Return %NULL
|
||||
* on error or when unprintable characters are read.
|
||||
*
|
||||
* @param[in] fd File descriptor to read data from
|
||||
* @param[in] chomp Flag to indicate trailing newlines should be removed
|
||||
*
|
||||
* @retval NULL Error reading from fd
|
||||
* @retval != 0 Data read successfully, buffer returned
|
||||
*/
|
||||
char *util_file_read_fd(FILE *fd, int chomp)
|
||||
{
|
||||
char *buffer;
|
||||
size_t done, i;
|
||||
|
||||
if (util_file_read_fd_buf(fd, (void **) &buffer, &done))
|
||||
return NULL;
|
||||
|
||||
/* Check if this is a text file at all (required to filter out
|
||||
* binary sysfs attributes).
|
||||
*/
|
||||
for (i = 0; i < done; i++) {
|
||||
if (!isgraph(buffer[i]) && !isspace(buffer[i])) {
|
||||
free(buffer);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove trailing new-line character if requested. */
|
||||
if (chomp && done > 0 && buffer[done - 1] == '\n')
|
||||
done--;
|
||||
|
||||
/* NULL-terminate. */
|
||||
buffer = realloc(buffer, done + 1);
|
||||
if (!buffer)
|
||||
oom();
|
||||
buffer[done] = 0;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read file as text and return NULL-terminated contents. Remove trailing
|
||||
* newline if CHOMP is specified.
|
||||
*
|
||||
* @param[in] path Path to the file that will be read from
|
||||
* @param[in] chomp Flag to indicate trailing newlines should be removed
|
||||
*
|
||||
* @retval NULL Error reading from file
|
||||
* @retval != 0 File read successfully, contents returned in buffer
|
||||
*/
|
||||
char *util_file_read_text_file(const char *path, int chomp)
|
||||
{
|
||||
char *buffer = NULL;
|
||||
FILE *fd;
|
||||
|
||||
fd = fopen(path, "r");
|
||||
if (!fd)
|
||||
goto out;
|
||||
|
||||
buffer = util_file_read_fd(fd, chomp);
|
||||
fclose(fd);
|
||||
|
||||
out:
|
||||
if (!buffer) {
|
||||
fprintf(stderr, "Could not read file %s: %s\n", path,
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ static int mbr_table_ext_search(int fh, size_t blk_start_mbr,
|
||||
start = blk_start_mbr + le32toh(mbr.part_entry_vec[0].blk_start);
|
||||
cnt = le32toh(mbr.part_entry_vec[0].blk_cnt);
|
||||
|
||||
if ((start == blk_start) && (cnt == blk_cnt))
|
||||
if ((start <= blk_start) && (cnt >= (blk_start - start) + blk_cnt))
|
||||
return part_num;
|
||||
|
||||
/* Second entry contains relative offset for next logical volume */
|
||||
@@ -162,11 +162,11 @@ static int mbr_table_search(int fh, struct mbr *mbr, size_t blk_start,
|
||||
* The kernel sets count for extended partitions explicitly.
|
||||
* Therefore we do not check count here.
|
||||
*/
|
||||
if (mbr_part_is_ext(type) && (start == blk_start)) {
|
||||
if (mbr_part_is_ext(type) && (start <= blk_start)) {
|
||||
*part_ext = 1;
|
||||
return part_num;
|
||||
}
|
||||
if ((start == blk_start) && (cnt == blk_cnt))
|
||||
if ((start <= blk_start) && (cnt >= (blk_start - start) + blk_cnt))
|
||||
return part_num;
|
||||
if (!mbr_part_is_ext(type))
|
||||
continue;
|
||||
@@ -207,7 +207,7 @@ static int gpt_table_search(int fh, struct gpt *gpt, size_t blk_start,
|
||||
end = le64toh(part_entry->blk_end);
|
||||
if (start == 0) /* Empty slot */
|
||||
continue;
|
||||
if ((start == blk_start) && (end == blk_end))
|
||||
if ((start <= blk_start) && (end >= blk_end))
|
||||
return part_num;
|
||||
}
|
||||
return 0;
|
||||
|
||||
362
libutil/util_udev.c
Normal file
362
libutil/util_udev.c
Normal file
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* util - Utility function library
|
||||
*
|
||||
* UDEV helper functions
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lib/util_exit_code.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_list.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_udev.h"
|
||||
|
||||
/* Create a newly allocated udev entry. */
|
||||
static struct util_udev_entry_node *util_udev_entry_node_new(const char *key,
|
||||
const char *op,
|
||||
const char *value)
|
||||
{
|
||||
struct util_udev_entry_node *entry;
|
||||
|
||||
entry = util_zalloc(sizeof(struct util_udev_entry_node));
|
||||
entry->key = util_strdup(key);
|
||||
entry->op = util_strdup(op);
|
||||
entry->value = util_strdup(value);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/* Release resources associated with udev entry. */
|
||||
static void util_udev_entry_node_free(struct util_udev_entry_node *entry)
|
||||
{
|
||||
if (!entry)
|
||||
return;
|
||||
free(entry->key);
|
||||
free(entry->op);
|
||||
free(entry->value);
|
||||
free(entry);
|
||||
}
|
||||
|
||||
/* Create a newly allocated udev line. */
|
||||
static struct util_udev_line_node *util_udev_line_node_new(void)
|
||||
{
|
||||
struct util_udev_line_node *line;
|
||||
|
||||
line = util_zalloc(sizeof(struct util_udev_line_node));
|
||||
util_list_init(&line->entries, struct util_udev_entry_node, node);
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
/* Release resources associated with udev line. */
|
||||
static void util_udev_line_node_free(struct util_udev_line_node *line)
|
||||
{
|
||||
struct util_udev_entry_node *e, *n;
|
||||
|
||||
if (!line)
|
||||
return;
|
||||
util_list_iterate_safe(&line->entries, e, n) {
|
||||
util_list_remove(&line->entries, e);
|
||||
util_udev_entry_node_free(e);
|
||||
}
|
||||
free(line->line);
|
||||
free(line);
|
||||
}
|
||||
|
||||
/* Create a newly allocated udev file. */
|
||||
static struct util_udev_file *util_udev_file_new(void)
|
||||
{
|
||||
struct util_udev_file *file;
|
||||
|
||||
file = util_zalloc(sizeof(struct util_udev_file));
|
||||
util_list_init(&file->lines, struct util_udev_line_node, node);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release resources associated with udev file.
|
||||
*
|
||||
* @param[in, out] file Udev file structure to be freed
|
||||
*/
|
||||
void util_udev_free_file(struct util_udev_file *file)
|
||||
{
|
||||
struct util_udev_line_node *l, *n;
|
||||
|
||||
if (!file)
|
||||
return;
|
||||
util_list_iterate_safe(&file->lines, l, n) {
|
||||
util_list_remove(&file->lines, l);
|
||||
util_udev_line_node_free(l);
|
||||
}
|
||||
free(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the contents of a udev file to stdout. Used for debugging.
|
||||
*
|
||||
* @param[in] file Udev file structure to print
|
||||
*/
|
||||
void util_udev_file_print(struct util_udev_file *file)
|
||||
{
|
||||
struct util_udev_line_node *l;
|
||||
struct util_udev_entry_node *e;
|
||||
|
||||
printf("util_udev_file at %p\n", (void *) file);
|
||||
if (!file)
|
||||
return;
|
||||
util_list_iterate(&file->lines, l) {
|
||||
printf(" util_udev_line_node at %p\n", (void *) l);
|
||||
printf(" line='%s'\n", l->line);
|
||||
util_list_iterate(&l->entries, e) {
|
||||
printf(" util_udev_entry at %p\n", (void *) e);
|
||||
printf(" '%s' '%s' '%s'\n", e->key, e->op,
|
||||
e->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void skip_whitespace(const char **s_ptr)
|
||||
{
|
||||
const char *s = *s_ptr;
|
||||
|
||||
while (*s && isspace(*s))
|
||||
s++;
|
||||
|
||||
*s_ptr = s;
|
||||
}
|
||||
|
||||
static char *parse_key(const char **s_ptr)
|
||||
{
|
||||
const char *s, *e;
|
||||
char *key;
|
||||
|
||||
s = *s_ptr;
|
||||
/* Parse \w+(\{[^\}]*\})? */
|
||||
e = s;
|
||||
while (*e && (isalnum(*e) || *e == '_'))
|
||||
e++;
|
||||
if (*e == '{') {
|
||||
while (*e && *e != '}')
|
||||
e++;
|
||||
if (*e == '}')
|
||||
e++;
|
||||
}
|
||||
|
||||
if (e == s)
|
||||
return NULL;
|
||||
|
||||
/* s points to key start, e to character after key end. */
|
||||
key = util_zalloc(e - s + 1);
|
||||
memcpy(key, s, e - s);
|
||||
|
||||
*s_ptr = e;
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static char *parse_op(const char **s_ptr)
|
||||
{
|
||||
const char *ops[] = { "==", "!=", "=", "+=", ":=", NULL };
|
||||
const char *entry;
|
||||
size_t len;
|
||||
int i;
|
||||
|
||||
entry = *s_ptr;
|
||||
for (i = 0; ops[i]; i++) {
|
||||
len = strlen(ops[i]);
|
||||
if (strncmp(entry, ops[i], len) == 0) {
|
||||
*s_ptr += len;
|
||||
return util_strdup(ops[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *parse_value(const char **s_ptr)
|
||||
{
|
||||
const char *s, *e;
|
||||
char *value;
|
||||
|
||||
/* Parse: ^\s*(.*)\s*$ */
|
||||
s = *s_ptr;
|
||||
skip_whitespace(&s);
|
||||
e = s;
|
||||
while (*e)
|
||||
e++;
|
||||
e--;
|
||||
while (e > s && isspace(*e))
|
||||
e--;
|
||||
e++;
|
||||
|
||||
*s_ptr = e;
|
||||
|
||||
/* Remove quotes. */
|
||||
if ((*s == '"' && *(e - 1) == '"') ||
|
||||
(*s == '\'' && *(e - 1) == '\'')) {
|
||||
s++;
|
||||
e--;
|
||||
}
|
||||
|
||||
/* s points to value start, e to character after value end. */
|
||||
value = util_zalloc(e - s + 1);
|
||||
memcpy(value, s, e - s);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool parse_util_udev_entry(struct util_udev_line_node *line,
|
||||
const char *entry)
|
||||
{
|
||||
char *key = NULL, *op = NULL, *value = NULL;
|
||||
struct util_udev_entry_node *e;
|
||||
bool rc = false;
|
||||
|
||||
/* Parse: ^\s*(\w+)\s*(==|!=|=|\+=|:=)\s*"?([^"]*)"\s*$ */
|
||||
|
||||
/* Parse key. */
|
||||
skip_whitespace(&entry);
|
||||
key = parse_key(&entry);
|
||||
if (!key)
|
||||
goto out;
|
||||
|
||||
/* Parse operator. */
|
||||
skip_whitespace(&entry);
|
||||
op = parse_op(&entry);
|
||||
if (!op)
|
||||
goto out;
|
||||
|
||||
/* Parse value. */
|
||||
skip_whitespace(&entry);
|
||||
value = parse_value(&entry);
|
||||
if (!value)
|
||||
goto out;
|
||||
skip_whitespace(&entry);
|
||||
|
||||
/* Check for unrecognized characters at end of entry. */
|
||||
if (*entry != 0)
|
||||
goto out;
|
||||
|
||||
/* Add entry to list. */
|
||||
e = util_udev_entry_node_new(key, op, value);
|
||||
util_list_add_tail(&line->entries, e);
|
||||
rc = true;
|
||||
|
||||
out:
|
||||
free(key);
|
||||
free(op);
|
||||
free(value);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void replace_unquoted(char *s, char from, char to)
|
||||
{
|
||||
char quoted = 0;
|
||||
|
||||
for (; *s; s++) {
|
||||
if (quoted) {
|
||||
/* Skip until quote end is found. */
|
||||
if (*s == quoted)
|
||||
quoted = 0;
|
||||
continue;
|
||||
}
|
||||
if (*s == '"' || *s == '\'') {
|
||||
quoted = *s;
|
||||
continue;
|
||||
}
|
||||
if (*s == from)
|
||||
*s = to;
|
||||
}
|
||||
}
|
||||
|
||||
static bool parse_util_udev_line(struct util_udev_file *file, const char *line)
|
||||
{
|
||||
char *copy, *curr, *next;
|
||||
struct util_udev_line_node *l;
|
||||
int i;
|
||||
bool result = true;
|
||||
|
||||
l = util_udev_line_node_new();
|
||||
l->line = util_strdup(line);
|
||||
|
||||
/* Check for empty lines and comment lines. */
|
||||
for (i = 0; line[i] && isspace(line[i]); i++);
|
||||
if (line[i] == 0 || line[i] == '#')
|
||||
goto ok;
|
||||
|
||||
/* Parse each comma-separated entry. */
|
||||
copy = util_strdup(line);
|
||||
|
||||
/* A hack to differentiate between quoted and unquoted commas. */
|
||||
replace_unquoted(copy, ',', 1);
|
||||
|
||||
next = copy;
|
||||
while ((curr = strsep(&next, "\1"))) {
|
||||
if (!parse_util_udev_entry(l, curr)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(copy);
|
||||
|
||||
ok:
|
||||
if (result)
|
||||
util_list_add_tail(&file->lines, l);
|
||||
else
|
||||
util_udev_line_node_free(l);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new util_udev_file structure and read the contents of a specified
|
||||
* udev file into that structure.
|
||||
*
|
||||
* @param[in] path Path to the udev file that will be read in
|
||||
* @param[in, out] file_ptr A buffer to store resulting udev file structure
|
||||
*
|
||||
* @retval 0 Udev file read successfully
|
||||
* @retval UTIL_EXIT_RUNTIME_ERROR Error reading the udev file
|
||||
*/
|
||||
util_exit_code_t util_udev_read_file(const char *path,
|
||||
struct util_udev_file **file_ptr)
|
||||
{
|
||||
char *text, *curr, *next;
|
||||
struct util_udev_file *file;
|
||||
int once = 0;
|
||||
|
||||
text = util_file_read_text_file(path, 0);
|
||||
if (!text)
|
||||
return UTIL_EXIT_RUNTIME_ERROR;
|
||||
file = util_udev_file_new();
|
||||
|
||||
/* Iterate over each line. */
|
||||
next = text;
|
||||
while ((curr = strsep(&next, "\n"))) {
|
||||
if (parse_util_udev_line(file, curr))
|
||||
continue;
|
||||
if (!once) {
|
||||
once = 1;
|
||||
fprintf(stderr, "Unrecognized udev rule in %s:\n",
|
||||
path);
|
||||
}
|
||||
fprintf(stderr, "%s\n", curr);
|
||||
}
|
||||
|
||||
free(text);
|
||||
*file_ptr = file;
|
||||
|
||||
return UTIL_EXIT_OK;
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* lsstp - Display STP system information
|
||||
*
|
||||
* Copyright IBM Corp. 2020, 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
22
pvattest/Makefile
Normal file
22
pvattest/Makefile
Normal file
@@ -0,0 +1,22 @@
|
||||
# Common definitions
|
||||
include ../common.mak
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
PKGDATADIR := "$(DESTDIR)$(TOOLS_DATADIR)/pvattest"
|
||||
SUBDIRS := src man tools
|
||||
RECURSIVE_TARGETS := all-recursive clean-recursive install-recursive
|
||||
|
||||
all: all-recursive
|
||||
|
||||
install: all install-recursive
|
||||
|
||||
clean: clean-recursive
|
||||
|
||||
$(RECURSIVE_TARGETS):
|
||||
@target=`echo $@ |sed s/-recursive//`; \
|
||||
for d in $(SUBDIRS); do \
|
||||
$(MAKE) -C $$d $$target || exit 1; \
|
||||
done
|
||||
|
||||
.PHONY: all install clean $(RECURSIVE_TARGETS)
|
||||
100
pvattest/README.md
Normal file
100
pvattest/README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# pvattest
|
||||
|
||||
Use `pvattest` to attest an IBM Secure Execution guest running on z16 and later.
|
||||
|
||||
With `pvattest` you can create attestation requests in a trusted environment and attest
|
||||
an IBM Secure Execution for Linux guest to verify that a provider is running the correct image.
|
||||
To achieve this, use the following commands:
|
||||
* `create` On a trusted system, creates an attestation request.
|
||||
* `perform` Performs an attestation measurement on the SE-guest to be attested. For this a
|
||||
attestation request is sent to the Ultravisor (UV) and the answer received. The `perform`
|
||||
command requires IBM z16 or later z/Architecture hardware.
|
||||
* `verify` On a trusted system, compares the answer from the Ultravisor to the
|
||||
expected answer. If they differ, the Secure Execution guest might be a different guest
|
||||
than expected, or not secure at all.
|
||||
|
||||
For meaningful results, run `create` and `verify` only in a trusted environment,
|
||||
like your workstation or a previously attested IBM Secure Execution guest.
|
||||
Otherwise, the attestation can be compromised.
|
||||
For all certificates, revocation lists, and host-key documents, both the PEM and DER input
|
||||
formats are supported. If you run this program on a non S390 System, 'perform' is not be available.
|
||||
|
||||
## Getting started
|
||||
|
||||
If all dependencies are met (see the s390-tools README) issue `make` in the source tree to build `pvattest`.
|
||||
|
||||
## Details
|
||||
### create
|
||||
`pvattest create` needs the host-key-document, a location to store the
|
||||
attestation request protection key, and a location to store the request data.
|
||||
Unless the `--no-verify` flag is set it additionally requires the IBM signing key
|
||||
and the intermediate CA. The output contains the request in binary form which serves as input
|
||||
to `pvattest perform`. Must be run in a trusted environment. Especially, do not create the request
|
||||
on a system you want to attest. The attestation request protection key is valid for this request only,
|
||||
must be kept until the verification is completed and must be destroyed afterwards
|
||||
Keep the key secret.
|
||||
|
||||
### perform
|
||||
`pvattest perform` needs a request in binary form generated by `pvattest create`and
|
||||
a location to store the output. It will send the request to the device at `/dev/uv`
|
||||
which passes the request to the Ultravisor.
|
||||
Kernel will then send the request to the Ultravisor which will calculate the answer.
|
||||
The Answer is then passed back to userspace and handled by `pvattest`
|
||||
The output includes the original request and the answer from the Ultravisor.
|
||||
|
||||
### verify
|
||||
`pvattest verify` needs the SE-guest header, the attestation request protection key,
|
||||
and the attestation request and the response to the `pvattest perform` command from the Ultravisor.
|
||||
It calculates the measurement in the trusted environment and compares it to the response from
|
||||
the Ultravisor in the previous step.
|
||||
The following return codes are possible:
|
||||
|
||||
0. successful verification: The calculated measurement matches the response from the Ultravisor
|
||||
|
||||
1. failed verification: The command ended with an error, for example, because of incorrect input or an invalid SE header
|
||||
|
||||
2. failed verification: The calculated measurement does not match the response from the Ultravisor
|
||||
|
||||
Run `pvattest verify` in a trusted environment. Especially, do not verify on the system you want to attest.
|
||||
|
||||
## Measurement
|
||||
The measurement is a cryptographic measurement of the following block.
|
||||
Only HMAC-SHA512 is supported.
|
||||
|
||||
| Start | Size | Content |
|
||||
|---------|------------|---------------------------------------------------------------|
|
||||
| 0x0 | 0x40 | Page List Digest (from SE header) |
|
||||
| 0x40 | 0x40 | Address List Digest (from SE header) |
|
||||
| 0x80 | 0x40 | Tweak List Digest (from SE header) |
|
||||
| 0xc0 | 0x10 | SE Header Tag (from SE header) |
|
||||
| 0xd0 | 0x10 | Configuration UID (generated by UV, included in the answer) |
|
||||
| 0xe0 | 0x02 | User Data Length (defined during measurement on the SE-guest) |
|
||||
| 0xe2 | 0x02 | Zeros |
|
||||
| 0xe4 | 0x04 | Additional Data Length (set by UV, included in the answer) |
|
||||
| 0xe8 | 0 - 0x100 | User Data (generated during measurement on the SE-guest) |
|
||||
| ... | 0 or 0x10 | Optional Nonce (generated during request creation) |
|
||||
| ... | 0 - 0x8000 | Additional Data (generated by UV, included in the answer) |
|
||||
|
||||
### User Data
|
||||
By default `pvattest` does not include any User Data, therefore the length is zero.
|
||||
`User Data` is data generated by the SE guest and passed to UV during the measurement.
|
||||
The `User Data` must be known to or be replicable by the verifier to verify the correctness of the User Data.
|
||||
The addition of user data is currently an experimental setting.
|
||||
|
||||
### Additional Data
|
||||
`Additional data` is data known to the Ultravisor. By default UV will not include any `Additional Data`.
|
||||
Adding `Additional Data` is currently an experimental setting.
|
||||
|
||||
## Example
|
||||
|
||||
Create an attestation request in a trusted environment:
|
||||
|
||||
`pvattest create -k hkd.crt --arpk arp.key -o arcb.bin --cert IntermediateCA.crt --cert IbmSigningKey.crt`
|
||||
|
||||
Perform an attestation measurement on an IBM Secure Execution guest:
|
||||
|
||||
`pvattest perform --input arcb.bin --output measurement.bin`
|
||||
|
||||
Verify the response from the Ultravisor against the attestation request in a trusted environment:
|
||||
|
||||
`pvattest verify --input measurement.bin --arpk arp.key --hdr se_guest.hdr`
|
||||
9
pvattest/man/Makefile
Normal file
9
pvattest/man/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
include ../../common.mak
|
||||
|
||||
all:
|
||||
|
||||
install:
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man1
|
||||
$(INSTALL) -m 644 -c *.1 -t $(DESTDIR)$(MANDIR)/man1
|
||||
|
||||
.PHONY: all install clean
|
||||
73
pvattest/man/pvattest-create.1
Normal file
73
pvattest/man/pvattest-create.1
Normal file
@@ -0,0 +1,73 @@
|
||||
.\" Copyright 2022 IBM Corp.
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\"
|
||||
.TH pvattest-create 1 "07 June 2022" "s390-tools" "Attestation Manual"
|
||||
.nh
|
||||
.ad l
|
||||
.SH NAME
|
||||
\fBpvattest [OPTION?] create [OPTIONS] \fP- create an attestation measurement request
|
||||
\fB
|
||||
.SH DESCRIPTION
|
||||
Prepare attestation measurement requests for an IBM Secure Execution guest. Only prepare attestation requests in a trusted environment, such as your workstation. The 'pvattest create' command creates a randomly generated key to protect the attestation request. This key is only valid for this specific request.In order to avoid compromising the attestation, do not publish the protection key and delete it after verification. Every 'create' command generates a new, random protection key.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B
|
||||
\fB-h\fP, \fB--help\fP
|
||||
Show help options
|
||||
.TP
|
||||
.B
|
||||
\fB-k\fP, \fB--host-key-document\fP=\fBFILE\fP
|
||||
Specify one or more host key documents.
|
||||
.TP
|
||||
.B
|
||||
\fB-C\fP, \fB--cert\fP=\fBFILE\fP
|
||||
Specifies the certificate that is 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 root CA). Required. Ignored when \fB--no-verify\fP is specified.
|
||||
.TP
|
||||
.B
|
||||
\fB--crl\fP=\fBFILE\fP
|
||||
Specify \fBFILE\fP to be a certificate revocation list (optional).
|
||||
.TP
|
||||
.B
|
||||
\fB--root-ca\fP=\fBFILE\fP
|
||||
Use \fBFILE\fP as the trusted root CA instead the root CAs that are installed on the system (optional).
|
||||
.TP
|
||||
.B
|
||||
\fB-o\fP, \fB--output\fP=\fBFILE\fP
|
||||
\fBFILE\fP specifies the output for the attestation request control block.
|
||||
.TP
|
||||
.B
|
||||
\fB-a\fP, \fB--arpk\fP=\fBFILE\fP
|
||||
Save the protection key as GCM-AES256 key in \fBFILE\fP Do not publish this key, otherwise your attestation is compromised.
|
||||
.TP
|
||||
.B
|
||||
\fB--no-verify\fP
|
||||
Disable the host-key-document verification. Does not require the host-key documents to be valid. For testing purposes, do not use for a production image. (Optional)
|
||||
.TP
|
||||
.B
|
||||
\fB--offline\fP
|
||||
Don't download CRLs (optional).
|
||||
.TP
|
||||
.B
|
||||
\fB-V\fP, \fB--verbose\fP
|
||||
Provide more detailed output (optional)
|
||||
.SH EXAMPLE
|
||||
Create an attestation request with the protection key 'arp.key', write the request to 'arcb.bin', and verify the host-key document using the CA-signed key 'DigiCertCA.crt' and the intermediate key 'IbmSigningKey.crt'.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
pvattest create -k hkd.crt --arpk arp.key -o attreq.bin --cert DigiCertCA.crt --cert IbmSigningKey.crt
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
Create an attestation request with the protection key 'arp.key', write the request to 'arcb.bin', verify the host-key document using the CA-signed key 'DigiCertCA.crt' and the intermediate key 'IbmSigningKey.crt', and instead of downloading the certificate revocation list use certificate revocation lists 'DigiCertCA.crl', 'IbmSigningKey.crl', and 'rootCA.crl'.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
pvattest create -k hkd.crt --arpk arp.key -o attreq.bin --cert DigiCertCA.crt --cert IbmSigningKey.crt --offline --crl DigiCertCA.crl --crl IbmSigningKey.crl --crl rootCA.crl
|
||||
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
.SH SEE ALSO
|
||||
\fBpvattest\fP(1), \fBpvattest-verify\fP(1), \fBpvattest-perform\fP(1)
|
||||
47
pvattest/man/pvattest-perform.1
Normal file
47
pvattest/man/pvattest-perform.1
Normal file
@@ -0,0 +1,47 @@
|
||||
.\" Copyright 2022 IBM Corp.
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\"
|
||||
.TH pvattest-perform 1 "07 June 2022" "s390-tools" "Attestation Manual"
|
||||
.nh
|
||||
.ad l
|
||||
.SH NAME
|
||||
\fBpvattest [OPTION?] perform [OPTIONS] \fP- execute an attestation measurement request
|
||||
\fB
|
||||
.SH DESCRIPTION
|
||||
Run a measurement of this system using '/dev/uv'. Works only if this device is available and the attestation Ultravisor facility is present. The input must be an attestation request created with 'pvattest create'. Output will contain the original request, the attestation measurement result, the configuration UID, and if requested in the request Additional Data.
|
||||
.RE
|
||||
.PP
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B
|
||||
\fB-h\fP, \fB--help\fP
|
||||
Show help options
|
||||
.TP
|
||||
.B
|
||||
\fB-i\fP, \fB--input\fP=\fBFILE\fP
|
||||
\fBFILE\fP specifies the attestation request as input.
|
||||
.TP
|
||||
.B
|
||||
\fB-o\fP, \fB--output\fP=\fBFILE\fP
|
||||
\fBFILE\fP specifies the output for the attestation result.
|
||||
.TP
|
||||
.B
|
||||
\fB-V\fP, \fB--verbose\fP
|
||||
Provide more detailed output (optional)
|
||||
.RE
|
||||
.PP
|
||||
|
||||
.SH EXAMPLE
|
||||
Perform an attestation measurement with the attestation request 'arcb.bin' and write the output to 'measurement.bin'.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
pvattest perform --input attreq.bin --output attresp.bin
|
||||
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
.SH SEE ALSO
|
||||
\fBpvattest\fP(1), \fBpvattest-create\fP(1), \fBpvattest-verify\fP(1)
|
||||
60
pvattest/man/pvattest-verify.1
Normal file
60
pvattest/man/pvattest-verify.1
Normal file
@@ -0,0 +1,60 @@
|
||||
.\" Copyright 2022 IBM Corp.
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\"
|
||||
.TH pvattest-verify 1 "07 June 2022" "s390-tools" "Attestation Manual"
|
||||
.nh
|
||||
.ad l
|
||||
.SH NAME
|
||||
\fBpvattest [OPTION?] verify [OPTIONS] \fP- verify an attestation measurement
|
||||
\fB
|
||||
.SH DESCRIPTION
|
||||
Verify that a previously generated attestation measurement of an IBM Secure Execution guest is as expected. Only verify attestation requests in a trusted environment, such as your workstation. Input must contain the response as produced by 'pvattest perform'. The protection key must be the one that was used to create the request by 'pvattest create'. Please delete it after verification. The header must be the IBM Secure Execution header of the image that was attested during 'pvattest perform'
|
||||
.RE
|
||||
.PP
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B
|
||||
\fB-h\fP, \fB--help\fP
|
||||
Show help options
|
||||
.TP
|
||||
.B
|
||||
\fB-i\fP, \fB--input\fP=\fBFILE\fP
|
||||
\fBFILE\fP specifies the attestation result as input.
|
||||
.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-V\fP, \fB--verbose\fP
|
||||
Provide more detailed output (optional)
|
||||
.RE
|
||||
.PP
|
||||
|
||||
.SH EXAMPLE
|
||||
To verify a measurement in 'measurement.bin' with the protection key 'arp.kep' and SE-guest header 'se_guest.hdr'.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
pvattest verify --input attresp.bin --arpk arp.key --hdr se_guest.hdr
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
If the verification was successful the program exists with zero.
|
||||
If the verification failed it exists with 2 and prints the following to stderr:
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
ERROR: Attestation measurement verification failed:
|
||||
Calculated and received attestation measurement are not the same.
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
.SH SEE ALSO
|
||||
\fBpvattest\fP(1), \fBpvattest-create\fP(1), \fBpvattest-perform\fP(1)
|
||||
104
pvattest/man/pvattest.1
Normal file
104
pvattest/man/pvattest.1
Normal file
@@ -0,0 +1,104 @@
|
||||
.\" Copyright 2022 IBM Corp.
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\"
|
||||
.TH pvattest 1 "07 June 2022" "s390-tools" "Attestation Manual"
|
||||
.nh
|
||||
.ad l
|
||||
.SH NAME
|
||||
\fBpvattest [OPTION?] COMMAND [OPTIONS] \fP- create, perform, and verify attestation measurements
|
||||
\fB
|
||||
.RE
|
||||
\fB
|
||||
.SH SYNOPSIS
|
||||
.nf
|
||||
.fam C
|
||||
\fBpvattest\fP \fIcreate\fP [\fIOPTIONS\fP]
|
||||
\fBpvattest\fP \fIperform\fP [\fIOPTIONS\fP]
|
||||
\fBpvattest\fP \fIverify\fP [\fIOPTIONS\fP]
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
.fam T
|
||||
.fi
|
||||
.SH DESCRIPTION
|
||||
Use \fBpvattest\fP to attest that an IBM Secure Execution guest is the correct guest, and that it was started in a secure manner.
|
||||
Run '\fBpvattest\fP \fIcreate\fP' and '\fBpvattest\fP \fIverify\fP' in a trusted environment only.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
create On a trusted system, creates an attestation request.
|
||||
|
||||
perform On the SE-guest to be attested, sends the attestation request to the Ultravisor and receives the answer.
|
||||
|
||||
verify On a trusted system, compares the answer from the Ultravisor to the one from your trusted environment. If they differ, the Secure Execution guest might be compromised.
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
For meaningful results, run '\fIcreate\fP' and '\fIverify\fP' in a trusted environment, like your workstation or a previously attested IBM Secure Execution guest. Otherwise, the attestation might be tampered with. For all certificates, revocation lists, and host-key documents, both the PEM and DER input formats are supported. If you run \fBpvattest\fP on a machine architecture other than z/Architecture, 'measure' is not available.
|
||||
.PP
|
||||
Use '\fBpvattest\fP [COMMAND] \fB-h\fP' to get detailed help
|
||||
.RE
|
||||
.PP
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B
|
||||
\fB-h\fP, \fB--help\fP
|
||||
Show help options
|
||||
.TP
|
||||
.B
|
||||
\fB-v\fP, \fB--version\fP
|
||||
Print the version and exit.
|
||||
.TP
|
||||
.B
|
||||
\fB-V\fP, \fB--verbose\fP
|
||||
Provide more detailed output (optional)
|
||||
.RE
|
||||
.PP
|
||||
|
||||
.SH EXAMPLE
|
||||
For details refer to the man page of the command.
|
||||
.PP
|
||||
Create the request on a trusted system.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
trusted:~$ pvattest create -k hkd.crt --cert CA.crt --cert ibmsk.crt --arpk arp.key -o attreq.bin
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
On the SE-guest, \fIperform\fP the attestation.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
seguest:~$ pvattest perform -i attreq.bin -o attresp.bin
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
On a trusted system, \fIverify\fP that the response is correct. Here, the protection key from the creation and the SE-guest’s header is used to \fIverify\fP the measurement.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
trusted:~$ pvattest verify -i attresp.bin --arpk arp.key --hdr se_guest.hdr
|
||||
trusted:~$ echo $?
|
||||
0
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
|
||||
If the measurements do not match \fBpvattest\fP exits with code 2 and emits an error message. The SE-guest attestation failed.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
trusted:~$ pvattest verify -i wrongresp.bin --arpk arp.key --hdr se_guest.hdr
|
||||
ERROR: Attestation measurement verification failed:
|
||||
Calculated and received attestation measurement are not the same.
|
||||
trusted:~$ echo $?
|
||||
2
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
|
||||
.SH SEE ALSO
|
||||
\fBpvattest\fP-\fIcreate\fP(1), \fBpvattest-\fIverify\fP\fP(1), \fBpvattest\fP-\fIperform\fP(1)
|
||||
2
pvattest/src/.gitignore
vendored
Normal file
2
pvattest/src/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.check-dep-pvattest
|
||||
.detect-openssl.dep.c
|
||||
124
pvattest/src/Makefile
Normal file
124
pvattest/src/Makefile
Normal file
@@ -0,0 +1,124 @@
|
||||
include ../../common.mak
|
||||
|
||||
BIN_PROGRAM = pvattest
|
||||
PKGDATADIR ?= "$(DESTDIR)$(TOOLS_DATADIR)/$(BIN_PROGRAM)"
|
||||
|
||||
SRC_DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||
ROOT_DIR = $(rootdir)
|
||||
PVATTESTDIR := $(ROOT_DIR)/pvattest
|
||||
INCLUDE_PATHS = "$(SRC_DIR)" "$(ROOT_DIR)/include"
|
||||
INCLUDE_PARMS = $(addprefix -I,$(INCLUDE_PATHS))
|
||||
|
||||
LIBPV_DIR = $(ROOT_DIR)/libpv
|
||||
LIBPV = $(LIBPV_DIR)/libpv.a
|
||||
|
||||
WARNINGS := -Wall -Wextra -Wshadow \
|
||||
-Wcast-align -Wwrite-strings -Wmissing-prototypes \
|
||||
-Wmissing-declarations -Wredundant-decls -Wnested-externs \
|
||||
-Wno-long-long -Wuninitialized -Wconversion -Wstrict-prototypes \
|
||||
-Wpointer-arith -Wno-error=inline \
|
||||
-Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \
|
||||
-Werror \
|
||||
$(NULL)
|
||||
|
||||
PVATTEST_SRCS := $(wildcard *.c) \
|
||||
$(NULL)
|
||||
|
||||
$(BIN_PROGRAM)_SRCS := \
|
||||
$(PVATTEST_SRCS) \
|
||||
$(NULL)
|
||||
|
||||
$(BIN_PROGRAM)_OBJS := $($(BIN_PROGRAM)_SRCS:.c=.o)
|
||||
|
||||
ifneq ($(shell sh -c 'command -v pkg-config'),)
|
||||
GLIB2_CFLAGS := $(shell pkg-config --silence-errors --cflags glib-2.0)
|
||||
GLIB2_LIBS := $(shell pkg-config --silence-errors --libs glib-2.0)
|
||||
LIBCRYPTO_CFLAGS := $(shell pkg-config --silence-errors --cflags libcrypto openssl)
|
||||
LIBCRYPTO_LIBS := $(shell pkg-config --silence-errors --libs libcrypto openssl)
|
||||
LIBCURL_CFLAGS := $(shell pkg-config --silence-errors --cflags libcurl)
|
||||
LIBCURL_LIBS := $(shell pkg-config --silence-errors --libs libcurl)
|
||||
else
|
||||
GLIB2_CFLAGS := -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include
|
||||
GLIB2_LIBS := -lglib-2.0
|
||||
LIBCRYPTO_CFLAGS :=
|
||||
LIBCRYPTO_LIBS := -lcrypto -lssl
|
||||
LIBCURL_CFLAGS := -I/usr/include/s390x-linux-gnu
|
||||
LIBCURL_LIBS := -lcurl
|
||||
endif
|
||||
|
||||
ALL_CFLAGS += -std=gnu11 \
|
||||
-DPKGDATADIR=$(PKGDATADIR) \
|
||||
-DOPENSSL_API_COMPAT=0x10101000L \
|
||||
$(GLIB2_CFLAGS) \
|
||||
$(LIBCRYPTO_CFLAGS) \
|
||||
$(LIBCURL_CFLAGS) \
|
||||
$(WARNINGS) \
|
||||
$(NULL)
|
||||
|
||||
ifneq ($(call check_header_prereq,"asm/uvdevice.h"),yes)
|
||||
ALL_CFLAGS += -DPVATTEST_NO_PERFORM
|
||||
endif
|
||||
|
||||
ALL_CPPFLAGS += $(INCLUDE_PARMS)
|
||||
LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) $(LIBCURL_LIBS)
|
||||
|
||||
BUILD_TARGETS := skip-$(BIN_PROGRAM)
|
||||
INSTALL_TARGETS := skip-$(BIN_PROGRAM)
|
||||
ifneq (${HAVE_OPENSSL},0)
|
||||
ifneq (${HAVE_GLIB2},0)
|
||||
ifneq (${HAVE_LIBCURL}, 0)
|
||||
BUILD_TARGETS := $(BIN_PROGRAM)
|
||||
INSTALL_TARGETS := install-$(BIN_PROGRAM)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
all: $(BUILD_TARGETS) .check-dep-$(BIN_PROGRAM)
|
||||
|
||||
install: $(INSTALL_TARGETS)
|
||||
|
||||
$(BIN_PROGRAM): $($(BIN_PROGRAM)_OBJS) $(LIBPV)
|
||||
|
||||
skip-$(BIN_PROGRAM):
|
||||
echo " SKIP $(BIN_PROGRAM) due to unresolved dependencies"
|
||||
|
||||
clean:
|
||||
$(RM) -f -- $($(BIN_PROGRAM)_OBJS) $(BIN_PROGRAM) .check-dep-$(BIN_PROGRAM) .detect-openssl.dep.c
|
||||
|
||||
install-$(BIN_PROGRAM): $(BIN_PROGRAM)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
|
||||
$(INSTALL) -c $^ $(DESTDIR)$(USRBINDIR)
|
||||
|
||||
|
||||
.PHONY: all install clean skip-$(BIN_PROGRAM) install-$(BIN_PROGRAM)
|
||||
|
||||
$($(BIN_PROGRAM)_OBJS): .check-dep-$(BIN_PROGRAM)
|
||||
|
||||
.detect-openssl.dep.c:
|
||||
echo "#include <openssl/evp.h>" > $@
|
||||
echo "#if OPENSSL_VERSION_NUMBER < 0x10101000L" >> $@
|
||||
echo " #error openssl version 1.1.0 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-$(BIN_PROGRAM): .detect-openssl.dep.c
|
||||
$(call check_dep, \
|
||||
"$(BIN_PROGRAM)", \
|
||||
"glib.h", \
|
||||
"glib2-devel / libglib2.0-dev", \
|
||||
"HAVE_GLIB2=0")
|
||||
$(call check_dep, \
|
||||
"$(BIN_PROGRAM)", \
|
||||
"openssl/evp.h", \
|
||||
"openssl-devel / libssl-dev version >= 1.1.0", \
|
||||
"HAVE_OPENSSL=0", \
|
||||
"-I.")
|
||||
$(call check_dep, \
|
||||
"$(BIN_PROGRAM)", \
|
||||
"curl/curl.h", \
|
||||
"libcurl-devel", \
|
||||
"HAVE_LIBCURL=0")
|
||||
touch $@
|
||||
423
pvattest/src/arcb.c
Normal file
423
pvattest/src/arcb.c
Normal file
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* Attestation Request Control Block related functions
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "libpv/crypto.h"
|
||||
#include "libpv/hash.h"
|
||||
|
||||
#include "arcb.h"
|
||||
#include "common.h"
|
||||
#include "log.h"
|
||||
|
||||
#define ARVN_VERSION_1 0x0100
|
||||
#define MAX_ARL 0x2000
|
||||
|
||||
typedef struct arcb_v1_hdr {
|
||||
uint64_t reserved0; /* 0x0000 */
|
||||
be32_t arvn; /* 0x0008 */
|
||||
be32_t arl; /* 0x000c */
|
||||
uint8_t iv[ARCB_V1_IV_SIZE]; /* 0x0010 */
|
||||
uint32_t reserved1c; /* 0x001c */
|
||||
uint8_t reserved20[7]; /* 0x0020 */
|
||||
uint8_t nks; /* 0x0027 */
|
||||
uint32_t reserved28; /* 0x0028 */
|
||||
be32_t sea; /* 0x002c */
|
||||
be64_t paf; /* 0x0030 */
|
||||
be32_t mai; /* 0x0038 */
|
||||
uint32_t reserved3c; /* 0x003c */
|
||||
PvEcdhPubKey cpk; /* 0x0040 */
|
||||
} __packed arcb_v1_hdr_t;
|
||||
G_STATIC_ASSERT(sizeof(arcb_v1_hdr_t) == 0xe0);
|
||||
|
||||
typedef struct arcb_v1_key_slot {
|
||||
uint8_t phkh[ARCB_V1_PHKH_SIZE];
|
||||
uint8_t warpk[ARCB_V1_ATTEST_PROT_KEY_SIZE];
|
||||
uint8_t kst[ARCB_V1_TAG_SIZE];
|
||||
} __packed arcb_v1_key_slot_t;
|
||||
G_STATIC_ASSERT(sizeof(arcb_v1_key_slot_t) == 0x50);
|
||||
|
||||
struct arcb_v1 {
|
||||
/* authenticated data */
|
||||
uint32_t arvn;
|
||||
uint32_t mai;
|
||||
uint64_t paf;
|
||||
GBytes *iv;
|
||||
EVP_PKEY *evp_cust_pub_key;
|
||||
GSList *host_key_slots;
|
||||
|
||||
/* confidential data */
|
||||
GBytes *confidential_measurement_key;
|
||||
GBytes *confidential_optional_nonce;
|
||||
GBytes *confidential_att_req_prot_key;
|
||||
};
|
||||
|
||||
void arcb_v1_clear_free(arcb_v1_t *arcb)
|
||||
{
|
||||
if (!arcb)
|
||||
return;
|
||||
|
||||
g_slist_free_full(arcb->host_key_slots, g_free);
|
||||
g_bytes_unref(arcb->confidential_measurement_key);
|
||||
g_bytes_unref(arcb->confidential_optional_nonce);
|
||||
g_bytes_unref(arcb->confidential_att_req_prot_key);
|
||||
g_bytes_unref(arcb->iv);
|
||||
EVP_PKEY_free(arcb->evp_cust_pub_key);
|
||||
g_free(arcb);
|
||||
}
|
||||
|
||||
static void arcb_v1_set_paf(arcb_v1_t *arcb, const uint64_t paf, GError **error)
|
||||
{
|
||||
const uint64_t known_flags = ARCB_V1_PAF_ALL & ~ARCB_V1_PAF_NONCE;
|
||||
|
||||
if ((paf & ARCB_V1_PAF_NONCE) != 0) {
|
||||
g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_PAF,
|
||||
_("The given paf (%#.16lx) specifies the NONCE flag (%#.16lx)."), paf,
|
||||
ARCB_V1_PAF_NONCE);
|
||||
return;
|
||||
}
|
||||
if ((paf & ~known_flags) != 0)
|
||||
pvattest_log_warning(
|
||||
_("The given paf (%#.16lx) specifies unknown flags. Use at your own risk!"),
|
||||
paf, known_flags);
|
||||
arcb->paf = paf;
|
||||
}
|
||||
|
||||
arcb_v1_t *arcb_v1_new(GBytes *arpk, GBytes *iv, uint32_t mai, EVP_PKEY *evp_cpk, GBytes *mkey,
|
||||
uint64_t paf, GError **error)
|
||||
{
|
||||
g_autoptr(arcb_v1_t) arcb = g_new0(arcb_v1_t, 1);
|
||||
|
||||
g_assert(g_bytes_get_size(iv) == ARCB_V1_IV_SIZE);
|
||||
g_assert(g_bytes_get_size(arpk) == ARCB_V1_ATTEST_PROT_KEY_SIZE);
|
||||
g_assert(g_bytes_get_size(mkey) == HMAC_SHA512_KEY_SIZE);
|
||||
|
||||
pv_wrapped_g_assert(arpk);
|
||||
pv_wrapped_g_assert(iv);
|
||||
pv_wrapped_g_assert(evp_cpk);
|
||||
pv_wrapped_g_assert(mkey);
|
||||
|
||||
arcb->arvn = ARVN_VERSION_1;
|
||||
arcb->mai = mai;
|
||||
arcb_v1_set_paf(arcb, paf, error);
|
||||
if (*error)
|
||||
return NULL;
|
||||
arcb->iv = g_bytes_ref(iv);
|
||||
|
||||
if (EVP_PKEY_up_ref(evp_cpk) != 1)
|
||||
g_abort();
|
||||
arcb->evp_cust_pub_key = evp_cpk;
|
||||
|
||||
arcb->confidential_att_req_prot_key = g_bytes_ref(arpk);
|
||||
arcb->confidential_measurement_key = g_bytes_ref(mkey);
|
||||
|
||||
return g_steal_pointer(&arcb);
|
||||
}
|
||||
|
||||
int arcb_v1_add_key_slot(arcb_v1_t *arcb, EVP_PKEY *evp_host, GError **error)
|
||||
{
|
||||
g_autoptr(GBytes) warpk = NULL, tag = NULL, phkh = NULL;
|
||||
g_autoptr(GBytes) exchange_key = NULL, iv = NULL;
|
||||
g_autofree arcb_v1_key_slot_t *key_slot = NULL;
|
||||
g_autofree PvEcdhPubKey *ecdh_host = NULL;
|
||||
g_autofree uint8_t *iv_raw = NULL;
|
||||
PvCipherParms parms;
|
||||
int64_t gcm_rc;
|
||||
|
||||
g_assert(arcb->confidential_att_req_prot_key);
|
||||
|
||||
pv_wrapped_g_assert(arcb);
|
||||
pv_wrapped_g_assert(evp_host);
|
||||
|
||||
/* encrypt (=wrap) attestation request protection key, store warpk + tag */
|
||||
exchange_key = pv_derive_exchange_key(arcb->evp_cust_pub_key, evp_host, error);
|
||||
if (!exchange_key)
|
||||
return -1;
|
||||
|
||||
iv_raw = g_malloc0(ARCB_V1_IV_SIZE);
|
||||
iv = g_bytes_new_take(g_steal_pointer(&iv_raw), ARCB_V1_IV_SIZE);
|
||||
if (!iv)
|
||||
g_abort();
|
||||
|
||||
parms.key = exchange_key;
|
||||
parms.iv = iv;
|
||||
parms.cipher = EVP_aes_256_gcm();
|
||||
parms.tag_size = ARCB_V1_TAG_SIZE;
|
||||
gcm_rc = pv_gcm_encrypt(arcb->confidential_att_req_prot_key, NULL, &parms, &warpk, &tag,
|
||||
error);
|
||||
if (gcm_rc != ARCB_V1_ATTEST_PROT_KEY_SIZE)
|
||||
return -1;
|
||||
|
||||
/* calculate public host key hash */
|
||||
ecdh_host = pv_evp_pkey_to_ecdh_pub_key(evp_host, error);
|
||||
if (!ecdh_host)
|
||||
return -1;
|
||||
phkh = pv_sha256_hash(ecdh_host->data, sizeof(ecdh_host->data), error);
|
||||
if (!phkh)
|
||||
return -1;
|
||||
|
||||
/* copy to list */
|
||||
g_assert(g_bytes_get_size(warpk) == sizeof(key_slot->warpk));
|
||||
g_assert(g_bytes_get_size(tag) == sizeof(key_slot->kst));
|
||||
g_assert(g_bytes_get_size(phkh) == sizeof(key_slot->phkh));
|
||||
|
||||
key_slot = g_malloc0(sizeof(*key_slot));
|
||||
pv_gbytes_memcpy(key_slot->warpk, sizeof(key_slot->warpk), warpk);
|
||||
pv_gbytes_memcpy(key_slot->kst, sizeof(key_slot->warpk), tag);
|
||||
pv_gbytes_memcpy(key_slot->phkh, sizeof(key_slot->warpk), phkh);
|
||||
|
||||
arcb->host_key_slots = g_slist_prepend(arcb->host_key_slots, g_steal_pointer(&key_slot));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void arcb_v1_set_nonce(arcb_v1_t *arcb, GBytes *nonce)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
pv_wrapped_g_assert(nonce);
|
||||
arcb_v1_rm_nonce(arcb);
|
||||
g_assert(!arcb->confidential_optional_nonce);
|
||||
|
||||
g_assert(g_bytes_get_size(nonce) == ARCB_V1_NONCE_SIZE);
|
||||
arcb->confidential_optional_nonce = g_bytes_ref(nonce);
|
||||
|
||||
arcb->paf |= ARCB_V1_PAF_NONCE;
|
||||
}
|
||||
|
||||
void arcb_v1_rm_nonce(arcb_v1_t *arcb)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
if (!arcb->confidential_optional_nonce)
|
||||
return;
|
||||
g_bytes_unref(arcb->confidential_optional_nonce);
|
||||
arcb->confidential_optional_nonce = NULL;
|
||||
arcb->paf &= ~ARCB_V1_PAF_NONCE;
|
||||
}
|
||||
|
||||
GBytes *arcb_v1_serialize(const arcb_v1_t *arcb, GError **error)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
g_autoptr(GByteArray) arcb_gba = NULL;
|
||||
g_autoptr(GBytes) confidential_area = NULL;
|
||||
g_autoptr(GBytes) aad = NULL;
|
||||
g_autoptr(GBytes) art = NULL;
|
||||
g_autoptr(GBytes) encrypted_area = NULL;
|
||||
g_autoptr(GBytes) result = NULL;
|
||||
g_autofree PvEcdhPubKey *ecdh_cpk = NULL;
|
||||
PvCipherParms parms = {
|
||||
.cipher = EVP_aes_256_gcm(),
|
||||
.tag_size = AES_256_GCM_TAG_SIZE,
|
||||
};
|
||||
size_t att_req_len = 0, nks = 0, sea = 0;
|
||||
|
||||
arcb_v1_hdr_t hdr = {
|
||||
.arvn = GUINT32_TO_BE(arcb->arvn),
|
||||
.paf = GUINT64_TO_BE(arcb->paf),
|
||||
.mai = GUINT32_TO_BE(arcb->mai),
|
||||
};
|
||||
|
||||
g_assert(arcb->host_key_slots);
|
||||
|
||||
/* calculate sizes */
|
||||
nks = g_slist_length(arcb->host_key_slots);
|
||||
g_assert(nks < 0xFF);
|
||||
|
||||
sea = g_bytes_get_size(arcb->confidential_measurement_key);
|
||||
if (arcb->confidential_optional_nonce)
|
||||
sea += g_bytes_get_size(arcb->confidential_optional_nonce);
|
||||
|
||||
g_assert(sea == HMAC_SHA512_KEY_SIZE || sea == HMAC_SHA512_KEY_SIZE + ARCB_V1_NONCE_SIZE);
|
||||
|
||||
att_req_len = sizeof(hdr) + nks * sizeof(arcb_v1_key_slot_t) + HMAC_SHA512_KEY_SIZE +
|
||||
ARCB_V1_TAG_SIZE;
|
||||
if (arcb->confidential_optional_nonce)
|
||||
att_req_len += ARCB_V1_NONCE_SIZE;
|
||||
|
||||
g_assert(att_req_len <= MAX_ARL);
|
||||
|
||||
/* copy plain data to contiguous memory */
|
||||
hdr.arl = GUINT32_TO_BE((uint32_t)att_req_len);
|
||||
|
||||
pv_gbytes_memcpy(hdr.iv, ARCB_V1_IV_SIZE, arcb->iv);
|
||||
hdr.nks = (uint8_t)nks;
|
||||
hdr.sea = GUINT32_TO_BE((uint32_t)sea);
|
||||
ecdh_cpk = pv_evp_pkey_to_ecdh_pub_key(arcb->evp_cust_pub_key, error);
|
||||
memcpy(&hdr.cpk, ecdh_cpk, sizeof(*ecdh_cpk));
|
||||
arcb_gba = g_byte_array_sized_new((guint)att_req_len);
|
||||
g_byte_array_append(arcb_gba, (const uint8_t *)&hdr, sizeof(hdr));
|
||||
|
||||
for (GSList *elem = arcb->host_key_slots; elem; elem = elem->next)
|
||||
g_byte_array_append(arcb_gba, elem->data, sizeof(arcb_v1_key_slot_t));
|
||||
|
||||
/* encrypt the confidential data */
|
||||
confidential_area = secure_gbytes_concat(arcb->confidential_measurement_key,
|
||||
arcb->confidential_optional_nonce);
|
||||
parms.key = arcb->confidential_att_req_prot_key;
|
||||
parms.iv = arcb->iv;
|
||||
aad = g_bytes_new(arcb_gba->data, arcb_gba->len);
|
||||
pv_gcm_encrypt(confidential_area, aad, &parms, &encrypted_area, &art, error);
|
||||
if (*error)
|
||||
return NULL;
|
||||
|
||||
g_byte_array_append(arcb_gba, g_bytes_get_data(encrypted_area, NULL), (guint)sea);
|
||||
g_byte_array_append(arcb_gba, g_bytes_get_data(art, NULL), ARCB_V1_TAG_SIZE);
|
||||
|
||||
result = g_byte_array_free_to_bytes(arcb_gba);
|
||||
arcb_gba = NULL;
|
||||
return g_steal_pointer(&result);
|
||||
}
|
||||
|
||||
uint32_t arcb_v1_get_required_measurement_size(const arcb_v1_t *arcb, GError **error)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
switch (arcb->mai) {
|
||||
case MAI_HMAC_SHA512:
|
||||
return HMAC_SHA512_KEY_SIZE;
|
||||
default:
|
||||
g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_MAI,
|
||||
_("Unknown measurement algorithm ID specified (%#x)."), arcb->mai);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t arcb_v1_get_required_additional_size(const arcb_v1_t *arcb)
|
||||
{
|
||||
uint32_t size = 0;
|
||||
|
||||
pv_wrapped_g_assert(arcb);
|
||||
|
||||
if (arcb_v1_additional_has_phkh_image(arcb))
|
||||
size += ARCB_V1_PHKH_SIZE;
|
||||
if (arcb_v1_additional_has_phkh_attest(arcb))
|
||||
size += ARCB_V1_PHKH_SIZE;
|
||||
return size;
|
||||
}
|
||||
|
||||
gboolean arcb_v1_use_nonce(const arcb_v1_t *arcb)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
return arcb->confidential_optional_nonce != NULL;
|
||||
}
|
||||
|
||||
gboolean arcb_v1_additional_has_phkh_image(const arcb_v1_t *arcb)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
return (arcb->paf & ARCB_V1_PAF_AAD_PHKH_HEADER) != 0;
|
||||
}
|
||||
|
||||
gboolean arcb_v1_additional_has_phkh_attest(const arcb_v1_t *arcb)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
return (arcb->paf & ARCB_V1_PAF_AAD_PHKH_ATTEST) != 0;
|
||||
}
|
||||
|
||||
GBytes *arcb_v1_get_measurement_key(const arcb_v1_t *arcb)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
return g_bytes_ref(arcb->confidential_measurement_key);
|
||||
}
|
||||
|
||||
GBytes *arcb_v1_get_nonce(const arcb_v1_t *arcb)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
if (arcb->confidential_optional_nonce)
|
||||
return g_bytes_ref(arcb->confidential_optional_nonce);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GBytes *arcb_v1_get_arp_key(const arcb_v1_t *arcb)
|
||||
{
|
||||
pv_wrapped_g_assert(arcb);
|
||||
return g_bytes_ref(arcb->confidential_att_req_prot_key);
|
||||
}
|
||||
|
||||
static gboolean is_v1_arcb(size_t aad_size, size_t sea, size_t arl, size_t serialized_arcb_size,
|
||||
uint32_t arcb_version, gboolean has_nonce)
|
||||
{
|
||||
gboolean result = aad_size + sea + ARCB_V1_TAG_SIZE == arl;
|
||||
|
||||
result &= arl <= serialized_arcb_size;
|
||||
result &= arcb_version == ARVN_VERSION_1;
|
||||
result &= has_nonce ? sea == HMAC_SHA512_KEY_SIZE + ARCB_V1_NONCE_SIZE :
|
||||
sea == HMAC_SHA512_KEY_SIZE;
|
||||
return result;
|
||||
}
|
||||
|
||||
gboolean arcb_v1_verify_serialized_arcb(GBytes *serialized_arcb, GBytes *arpk,
|
||||
GBytes **measurement_key, GBytes **optional_nonce,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GBytes) encr = NULL, decr = NULL, aad = NULL, tag = NULL, iv = NULL;
|
||||
const struct arcb_v1_hdr *serialized_arcb_hdr;
|
||||
const uint8_t *encr_u8, *aad_u8, *tag_u8;
|
||||
const uint8_t *serialized_arcb_u8;
|
||||
size_t serialized_arcb_size;
|
||||
uint32_t arcb_version, mai;
|
||||
size_t aad_size, arl, sea;
|
||||
PvCipherParms parms;
|
||||
gboolean has_nonce;
|
||||
uint64_t paf;
|
||||
|
||||
pv_wrapped_g_assert(serialized_arcb);
|
||||
pv_wrapped_g_assert(arpk);
|
||||
serialized_arcb_u8 = g_bytes_get_data(serialized_arcb, &serialized_arcb_size);
|
||||
serialized_arcb_hdr = (const arcb_v1_hdr_t *)serialized_arcb_u8;
|
||||
arl = GUINT32_FROM_BE(serialized_arcb_hdr->arl);
|
||||
arcb_version = GUINT32_FROM_BE(serialized_arcb_hdr->arvn);
|
||||
mai = GUINT32_FROM_BE(serialized_arcb_hdr->mai);
|
||||
|
||||
aad_u8 = serialized_arcb_u8;
|
||||
aad_size = sizeof(*serialized_arcb_hdr) +
|
||||
serialized_arcb_hdr->nks * sizeof(arcb_v1_key_slot_t);
|
||||
encr_u8 = aad_u8 + aad_size;
|
||||
sea = GUINT32_FROM_BE(serialized_arcb_hdr->sea);
|
||||
tag_u8 = encr_u8 + sea;
|
||||
paf = GUINT64_FROM_BE(serialized_arcb_hdr->paf);
|
||||
has_nonce = (paf & ARCB_V1_PAF_NONCE) != 0;
|
||||
|
||||
if (!is_v1_arcb(aad_size, sea, arl, serialized_arcb_size, arcb_version, has_nonce)) {
|
||||
g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_ARCB,
|
||||
_("The provided attestation request is not valid"));
|
||||
return FALSE;
|
||||
}
|
||||
if (mai != MAI_HMAC_SHA512) {
|
||||
g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_MAI,
|
||||
_("Unsupported measurement argument ID (%#x)"), mai);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
aad = g_bytes_new(aad_u8, aad_size);
|
||||
encr = g_bytes_new(encr_u8, sea);
|
||||
tag = g_bytes_new(tag_u8, ARCB_V1_TAG_SIZE);
|
||||
iv = g_bytes_new(serialized_arcb_hdr->iv, sizeof(serialized_arcb_hdr->iv));
|
||||
|
||||
parms.cipher = EVP_aes_256_gcm();
|
||||
parms.tag_size = AES_256_GCM_TAG_SIZE;
|
||||
parms.key = arpk;
|
||||
parms.iv = iv;
|
||||
pv_gcm_decrypt(encr, aad, tag, &parms, &decr, error);
|
||||
if (*error) {
|
||||
GError *tmp_error = NULL;
|
||||
|
||||
g_set_error(&tmp_error, ARCB_ERROR, ARCB_ERR_INVALID_ARCB,
|
||||
_("Cannot verify the attestation request: %s"), (*error)->message);
|
||||
g_clear_error(error);
|
||||
g_propagate_error(error, tmp_error);
|
||||
return FALSE;
|
||||
}
|
||||
if (measurement_key)
|
||||
*measurement_key = g_bytes_new(g_bytes_get_data(decr, NULL), HMAC_SHA512_KEY_SIZE);
|
||||
if (optional_nonce && has_nonce)
|
||||
*optional_nonce =
|
||||
g_bytes_new((uint8_t *)g_bytes_get_data(decr, NULL) + HMAC_SHA512_KEY_SIZE,
|
||||
ARCB_V1_NONCE_SIZE);
|
||||
return TRUE;
|
||||
}
|
||||
152
pvattest/src/arcb.h
Normal file
152
pvattest/src/arcb.h
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Attestation Request Control Block related functions
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 PVATTEST_ARCB_H
|
||||
#define PVATTEST_ARCB_H
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include "libpv/glib-helper.h"
|
||||
|
||||
#include "lib/zt_common.h"
|
||||
#include "libpv/crypto.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define MAI_HMAC_RESERVED_INVALID 0
|
||||
#define MAI_HMAC_SHA512 0x1
|
||||
|
||||
#define HMAC_SHA512_KEY_SIZE 64
|
||||
#define ARCB_V1_ATTEST_PROT_KEY_SIZE 32
|
||||
#define ARCB_V1_NONCE_SIZE 16
|
||||
#define ARCB_V1_TAG_SIZE 16
|
||||
#define ARCB_V1_IV_SIZE 12
|
||||
#define ARCB_V1_PHKH_SIZE 32
|
||||
|
||||
#define BIT(bit) ((uint64_t)1 << (63 - (bit)))
|
||||
/* Optional nonce in ARCB */
|
||||
#define ARCB_V1_PAF_NONCE BIT(1)
|
||||
/* Public host key hash used to unseal SE header added to additional data to be measured */
|
||||
#define ARCB_V1_PAF_AAD_PHKH_HEADER BIT(2)
|
||||
/* Public host key hash used to unseal this attestation added to additional data to be measured */
|
||||
#define ARCB_V1_PAF_AAD_PHKH_ATTEST BIT(3)
|
||||
/* Temporary backup-host-key use allowed */
|
||||
#define ARCB_V1_PAF_TMP_BACKUP_ALLOWED BIT(62)
|
||||
|
||||
/* Global not-host-specific key allowed */
|
||||
#define ARCB_V1_PAF_GLOBAL_NHS_KEY_ALLOWED BIT(63)
|
||||
|
||||
#define ARCB_V1_PAF_ALL \
|
||||
(ARCB_V1_PAF_NONCE | ARCB_V1_PAF_AAD_PHKH_HEADER | ARCB_V1_PAF_AAD_PHKH_ATTEST | \
|
||||
ARCB_V1_PAF_TMP_BACKUP_ALLOWED | ARCB_V1_PAF_GLOBAL_NHS_KEY_ALLOWED)
|
||||
|
||||
typedef struct arcb_v1 arcb_v1_t;
|
||||
|
||||
/** arcb_v1_new:
|
||||
*
|
||||
* @arpk: Attestation Request Protection key. AES-GCM-256 key to
|
||||
* protect Measurement Key and Nonce.
|
||||
* Must be ´ARCB_V1_ATTEST_PROT_KEY_SIZE´ bytes long.
|
||||
* @iv: IV for protecting Measuremt Key and Nonce.
|
||||
* Should be random for each new ARPK.
|
||||
* Must be ´ARCB_V1_IV_SIZE´ bytes long.
|
||||
* @mai: Measurement Algorithm Identifier for the attestation measurement.
|
||||
* See ´enum mai´
|
||||
* @evp_cpk: Customer key in EVP_PKEY format. Must contain private and public key pair.
|
||||
* @mkey: Measurement key to calculate the Measurement.
|
||||
* Must be ´HMAC_SHA512_KEY_SIZE´ bytes long.
|
||||
* @paf: Plain text Attestation Flags. See ´enum plaintext_attestattion_flags´.
|
||||
* ´ARCB_V1_PAF_NONCE´ must not be set.
|
||||
* @error: GError. *error will != NULL if error occours.
|
||||
*
|
||||
* arpk, mkey, and iv must me correct size
|
||||
* If not this is considered as a programming error (No warning;
|
||||
* Results in Assertion or undefined behavior).
|
||||
*
|
||||
* GBytes will be ref'ed.
|
||||
*
|
||||
* All numbers must be in system byte order and will be converted to big endian
|
||||
* if needed.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): new ARCB context.
|
||||
*/
|
||||
arcb_v1_t *arcb_v1_new(GBytes *arpk, GBytes *iv, uint32_t mai, EVP_PKEY *evp_cpk, GBytes *mkey,
|
||||
uint64_t paf, GError **error) PV_NONNULL(1, 2, 4, 5);
|
||||
void arcb_v1_clear_free(arcb_v1_t *arcb);
|
||||
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(arcb_v1_t, arcb_v1_clear_free)
|
||||
|
||||
/** arcb_v1_add_key_slot:
|
||||
*
|
||||
* @arcb: ARCB context.
|
||||
* @evp_host: Host public key.
|
||||
* @error: GError. *error will != NULL if error occours.
|
||||
*
|
||||
* Builds a key slot. Calculates exchange key, wraps ARPK with the exchange key.
|
||||
* Calculates the public host key hash. Calculates the key slot tag.
|
||||
* Adds it to the ARCB.
|
||||
*
|
||||
* Returns: 0 in case of success, -1 otherwise
|
||||
*/
|
||||
int arcb_v1_add_key_slot(arcb_v1_t *arcb, EVP_PKEY *evp_host, GError **error) PV_NONNULL(1, 2);
|
||||
void arcb_v1_set_nonce(arcb_v1_t *arcb, GBytes *nonce) PV_NONNULL(1, 2);
|
||||
void arcb_v1_rm_nonce(arcb_v1_t *arcb) PV_NONNULL(1);
|
||||
|
||||
/** arcb_v1_serialize:
|
||||
*
|
||||
* @arcb: ARCB context.
|
||||
* @error: GError. *error will != NULL if error occurs.
|
||||
*
|
||||
* Will create a valid ARCB for the UV. Including encrypting confidential data.
|
||||
* At least one key_slot must be added beforehand.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): The serialized ARCB which can be added to the
|
||||
* Retrieve Attestation Measurement UVC as GBytes.
|
||||
*/
|
||||
GBytes *arcb_v1_serialize(const arcb_v1_t *arcb, GError **error) PV_NONNULL(1, 2);
|
||||
|
||||
uint32_t arcb_v1_get_required_measurement_size(const arcb_v1_t *arcb, GError **error)
|
||||
PV_NONNULL(1, 2);
|
||||
uint32_t arcb_v1_get_required_additional_size(const arcb_v1_t *arcb) PV_NONNULL(1);
|
||||
gboolean arcb_v1_use_nonce(const arcb_v1_t *arcb) PV_NONNULL(1);
|
||||
gboolean arcb_v1_additional_has_phkh_image(const arcb_v1_t *arcb) PV_NONNULL(1);
|
||||
gboolean arcb_v1_additional_has_phkh_attest(const arcb_v1_t *arcb) PV_NONNULL(1);
|
||||
|
||||
GBytes *arcb_v1_get_measurement_key(const arcb_v1_t *arcb) PV_NONNULL(1);
|
||||
GBytes *arcb_v1_get_nonce(const arcb_v1_t *arcb) PV_NONNULL(1);
|
||||
GBytes *arcb_v1_get_arp_key(const arcb_v1_t *arcb) PV_NONNULL(1);
|
||||
|
||||
/** arcb_v1_verify_serialized_arcb:
|
||||
*
|
||||
* @serialized_arcb: binary ARCB in UV readable format.
|
||||
* @arpk: Attestation Request Protection key that was used to create serialized_arpk
|
||||
* @measurement_key: Output parameter: decrypted measurement key if no error.
|
||||
* May be NULL if not interested for this output.
|
||||
* @optional_nonce: Output parameter: decrypted nonce if no error.
|
||||
* May be NULL if not interested for this output.
|
||||
* @error: GError. *error will != NULL if error occurs.
|
||||
*
|
||||
*
|
||||
* Checks if sizes are sound and flags are known by this implementation.
|
||||
* Decrypts Measurement key and nonce (if given) and verifies ARCB tag.
|
||||
*
|
||||
* Returns: TRUE if ARCB is valid, including matching ARCB tag. Otherwise FALSE.
|
||||
*
|
||||
*/
|
||||
gboolean arcb_v1_verify_serialized_arcb(GBytes *serialized_arcb, GBytes *arpk,
|
||||
GBytes **measurement_key, GBytes **optional_nonce,
|
||||
GError **error) PV_NONNULL(1, 2);
|
||||
|
||||
#define ARCB_ERROR g_quark_from_static_string("pv-arcb_error-quark")
|
||||
typedef enum arcb_error {
|
||||
ARCB_ERR_INVALID_ARCB,
|
||||
ARCB_ERR_INVALID_PAF,
|
||||
ARCB_ERR_INVALID_MAI,
|
||||
ARCB_ERR_UNABLE_ENCR_ARPK,
|
||||
} arcb_error_e;
|
||||
|
||||
#endif /* PVATTEST_ARCB_H */
|
||||
649
pvattest/src/argparse.c
Normal file
649
pvattest/src/argparse.c
Normal file
@@ -0,0 +1,649 @@
|
||||
/*
|
||||
* Definitions used for parsing arguments.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "argparse.h"
|
||||
#include "log.h"
|
||||
#include "common.h"
|
||||
|
||||
#define DEFAULT_OUTPUT_FILE_NAME "attest.bin"
|
||||
#define DEFAULT_OPTION_PHKH_IMG FALSE
|
||||
#define DEFAULT_OPTION_PHKH_ATT FALSE
|
||||
#define DEFAULT_OPTION_NO_VERIFY FALSE
|
||||
#define DEFAULT_OPTION_ONLINE TRUE
|
||||
#define DEFAULT_OPTION_NONCE TRUE
|
||||
|
||||
static pvattest_config_t pvattest_config = {
|
||||
.general = {
|
||||
.log_level = PVATTEST_LOG_LVL_DEFAULT,
|
||||
},
|
||||
.create = {
|
||||
.output_path = NULL,
|
||||
.host_key_document_paths = NULL,
|
||||
.crl_paths = NULL,
|
||||
.root_ca_path = NULL,
|
||||
.certificate_paths = NULL,
|
||||
.arp_key_out_path = NULL,
|
||||
.phkh_img = DEFAULT_OPTION_PHKH_IMG,
|
||||
.phkh_att = DEFAULT_OPTION_PHKH_ATT,
|
||||
.online = DEFAULT_OPTION_ONLINE,
|
||||
.use_nonce = DEFAULT_OPTION_NONCE,
|
||||
.paf = 0,
|
||||
.x_aad_size = -1,
|
||||
},
|
||||
.perform = {
|
||||
.output_path = NULL,
|
||||
.input_path = NULL,
|
||||
},
|
||||
.verify = {
|
||||
.input_path = NULL,
|
||||
.hdr_path = NULL,
|
||||
.arp_key_in_path = NULL,
|
||||
},
|
||||
};
|
||||
typedef gboolean (*verify_options_fn_t)(GError **);
|
||||
|
||||
static gboolean check_for_non_null(const void *ptr, const char *msg, GError **error)
|
||||
{
|
||||
if (!ptr) {
|
||||
g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG, "%s", msg);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean _check_for_invalid_path(const char *path, gboolean must_exist, GError **error)
|
||||
{
|
||||
int cached_errno = 0;
|
||||
|
||||
g_assert(path);
|
||||
|
||||
if (must_exist) {
|
||||
if (access(path, F_OK | R_OK) != 0)
|
||||
cached_errno = errno;
|
||||
}
|
||||
if (cached_errno) {
|
||||
g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG, "Cannot access '%s': %s",
|
||||
path, g_strerror(cached_errno));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean check_for_optional_invalid_path(const char *path, gboolean must_exist,
|
||||
GError **error)
|
||||
{
|
||||
if (!path)
|
||||
return TRUE;
|
||||
return _check_for_invalid_path(path, must_exist, error);
|
||||
}
|
||||
|
||||
static gboolean check_for_invalid_path(const char *path, gboolean must_exist, const char *null_msg,
|
||||
GError **error)
|
||||
{
|
||||
if (!check_for_non_null(path, null_msg, error))
|
||||
return FALSE;
|
||||
return _check_for_invalid_path(path, must_exist, error);
|
||||
}
|
||||
|
||||
static gboolean _check_file_list(char **path_list, gboolean must_exist, GError **error)
|
||||
{
|
||||
char *path = NULL;
|
||||
for (char **path_it = path_list; path_it != NULL && *path_it != NULL; path_it++) {
|
||||
path = *path_it;
|
||||
if (!_check_for_invalid_path(path, must_exist, error))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean check_optional_file_list(char **path_list, gboolean must_exist, GError **error)
|
||||
{
|
||||
if (!path_list)
|
||||
return TRUE;
|
||||
return _check_file_list(path_list, must_exist, error);
|
||||
}
|
||||
|
||||
static gboolean check_file_list(char **path_list, gboolean must_exist, const char *null_msg,
|
||||
GError **error)
|
||||
{
|
||||
if (!check_for_non_null(path_list, null_msg, error))
|
||||
return FALSE;
|
||||
return _check_file_list(path_list, must_exist, error);
|
||||
}
|
||||
|
||||
static gboolean hex_str_toull(const char *nptr, uint64_t *dst, GError **error)
|
||||
{
|
||||
uint64_t value;
|
||||
gchar *end;
|
||||
|
||||
g_assert(dst);
|
||||
|
||||
if (!g_str_is_ascii(nptr)) {
|
||||
g_set_error(
|
||||
error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG,
|
||||
_("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"),
|
||||
nptr);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
value = g_ascii_strtoull(nptr, &end, 16);
|
||||
if ((value == G_MAXUINT64 && errno == ERANGE) || (end && *end != '\0')) {
|
||||
g_set_error(
|
||||
error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG,
|
||||
_("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"),
|
||||
nptr);
|
||||
return FALSE;
|
||||
}
|
||||
*dst = value;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
||||
/************************* SHARED OPTIONS *************************************/
|
||||
/* NOTE REQUIRED */
|
||||
#define _entry_host_key_document(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "host-key-document", .short_name = 'k', .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data, \
|
||||
.description = "Specify one or more host key documents.\n", \
|
||||
.arg_description = "FILE", \
|
||||
}
|
||||
|
||||
/* NOTE REQUIRED */
|
||||
#define _entry_certs(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "cert", .short_name = 'C', .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data, \
|
||||
.description = \
|
||||
"Specifies the certificate that is used to establish a chain\n" __indent \
|
||||
"of trust for the verification of the host-key documents. Specify\n" __indent \
|
||||
"this option twice to specify the IBM Z signing key and the\n" __indent \
|
||||
"intermediate CA certificate (signed by the root CA). Required.\n" __indent \
|
||||
"Ignored when --no-verify is specified.\n", \
|
||||
.arg_description = "FILE", \
|
||||
}
|
||||
|
||||
/* NOTE REQUIRED */
|
||||
#define _entry_crls(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "crl", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data, \
|
||||
.description = "Specify FILE to be a certificate revocation list\n" __indent \
|
||||
"(optional).", \
|
||||
.arg_description = "FILE", \
|
||||
}
|
||||
|
||||
/* NOTE REQUIRED */
|
||||
#define _entry_root_ca(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "root-ca", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data, \
|
||||
.description = "Use FILE as the trusted root CA instead the\n" __indent \
|
||||
"root CAs that are installed on the system (optional).", \
|
||||
.arg_description = "FILE", \
|
||||
}
|
||||
|
||||
/* NOTE REQUIRED */
|
||||
#define _entry_guest_hdr(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "hdr", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \
|
||||
.description = \
|
||||
"Specify the header of the guest image. Exactly one is required.\n", \
|
||||
.arg_description = "FILE", \
|
||||
}
|
||||
|
||||
/* NOTE REQUIRED */
|
||||
#define _entry_input(__arg_data, __additional_text, __indent) \
|
||||
{ \
|
||||
.long_name = "input", .short_name = 'i', .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \
|
||||
.description = "FILE specifies the " __additional_text "\n" __indent \
|
||||
" as input.\n", \
|
||||
.arg_description = "FILE", \
|
||||
}
|
||||
|
||||
/* NOTE REQUIRED */
|
||||
#define _entry_output(__arg_data, __additional_text, __indent) \
|
||||
{ \
|
||||
.long_name = "output", .short_name = 'o', .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \
|
||||
.description = "FILE specifies the output for the\n" __indent __additional_text \
|
||||
".\n", \
|
||||
.arg_description = "FILE", \
|
||||
}
|
||||
|
||||
/* NOTE REQUIRED */
|
||||
#define _entry_att_prot_key_save(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "arpk", .short_name = 'a', .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \
|
||||
.description = \
|
||||
"Save the protection key as GCM-AES256 key in FILE\n" __indent \
|
||||
"Do not publish this key, otherwise your attestation is compromised.\n", \
|
||||
.arg_description = "FILE", \
|
||||
}
|
||||
|
||||
/* NOTE REQUIRED */
|
||||
#define _entry_att_prot_key_load(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "arpk", .short_name = 'a', .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \
|
||||
.description = "Use FILE to specify the GCM-AES256 key to decrypt\n" __indent \
|
||||
"the attestation request.\n" __indent \
|
||||
"Delete this key after verification.\n", \
|
||||
.arg_description = "FILE", \
|
||||
}
|
||||
|
||||
#define _entry_phkh_img(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "x-phkh-img", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \
|
||||
.description = "add the public host key hash of the\n" __indent \
|
||||
"image header used to decrypt\n" __indent \
|
||||
"the secure guest to the measurement. (optional)\n" \
|
||||
}
|
||||
|
||||
#define _entry_phkh_att(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "x-phkh-att", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \
|
||||
.description = "add the public host key hash of the\n" __indent \
|
||||
"attestation header used to decrypt\n" __indent \
|
||||
"the attestation request to the measurement. (optional)\n" \
|
||||
}
|
||||
|
||||
#define _entry_no_verify(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "no-verify", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \
|
||||
.description = \
|
||||
"Disable the host-key-document verification.\n" __indent \
|
||||
"Does not require the host-key documents to be valid.\n" __indent \
|
||||
"For testing purposes, do not use for a production image.\n" __indent \
|
||||
"(optional)\n", \
|
||||
}
|
||||
|
||||
#define _entry_offline_maps_to_online(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "offline", .short_name = 0, .flags = G_OPTION_FLAG_REVERSE, \
|
||||
.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \
|
||||
.description = "Don't download CRLs (optional).\n", \
|
||||
}
|
||||
|
||||
#define _entry_verbose(__indent) \
|
||||
{ \
|
||||
.long_name = "verbose", .short_name = 'V', .flags = G_OPTION_FLAG_NO_ARG, \
|
||||
.arg = G_OPTION_ARG_CALLBACK, .arg_data = &increase_log_lvl, \
|
||||
.description = "Provide more detailed output (optional)\n", \
|
||||
.arg_description = NULL, \
|
||||
}
|
||||
|
||||
#define _entry_x_paf(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "x-paf", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_CALLBACK, .arg_data = __arg_data, \
|
||||
.description = "Specify the Plain text Attestation Flags\n" __indent \
|
||||
"as a hexadecimal value. Flags that change\n" __indent \
|
||||
"the paf (--phkh-*) take precedence over\n" __indent \
|
||||
"this flag.\n" __indent \
|
||||
"Setting the nonce paf is not allowed here.\n" __indent \
|
||||
"(optional, default 0x0)\n", \
|
||||
.arg_description = "HEX", \
|
||||
}
|
||||
|
||||
#define _entry_x_no_nonce(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "x-no-nonce", .short_name = 0, .flags = G_OPTION_FLAG_REVERSE, \
|
||||
.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \
|
||||
.description = "Do not use a nonce in the request.\n" __indent \
|
||||
"(optional, not recommended)\n" \
|
||||
}
|
||||
|
||||
#define _entry_x_aad_size(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "x-add-size", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_INT, .arg_data = __arg_data, \
|
||||
.description = "Specify the size of the additional area\n" __indent \
|
||||
"Overwrite every flag that changes\n" __indent \
|
||||
"this size implicitly. No verification is performed!\n" __indent \
|
||||
"Ignored if negative.\n" __indent "(optional, default ignored)\n", \
|
||||
.arg_description = "INT" \
|
||||
}
|
||||
|
||||
#define _entry_x_user_data(__arg_data, __indent) \
|
||||
{ \
|
||||
.long_name = "x-user-data", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \
|
||||
.description = "Use FILE to specify the user data.\n", .arg_description = "FILE", \
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
pvattest_log_increase_log_lvl(&pvattest_config.general.log_level);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean create_set_paf(G_GNUC_UNUSED const char *option_name, const char *value,
|
||||
G_GNUC_UNUSED void *data, GError **error)
|
||||
{
|
||||
return hex_str_toull(value, &pvattest_config.create.paf, error);
|
||||
}
|
||||
|
||||
/***************************** GENERAL OPTIONS ********************************/
|
||||
static gboolean print_version = FALSE;
|
||||
|
||||
static GOptionEntry general_options[] = {
|
||||
{
|
||||
.long_name = "version",
|
||||
.short_name = 'v',
|
||||
.flags = G_OPTION_FLAG_NONE,
|
||||
.arg = G_OPTION_ARG_NONE,
|
||||
.arg_data = &print_version,
|
||||
.description = "Print the version and exit.\n",
|
||||
.arg_description = NULL,
|
||||
},
|
||||
_entry_verbose(""),
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
/************************* CREATE ATTESTATION OPTIONS *************************/
|
||||
#define create_indent " "
|
||||
|
||||
static GOptionEntry create_options[] = {
|
||||
_entry_host_key_document(&pvattest_config.create.host_key_document_paths, create_indent),
|
||||
_entry_certs(&pvattest_config.create.certificate_paths, create_indent),
|
||||
_entry_crls(&pvattest_config.create.crl_paths, create_indent),
|
||||
_entry_root_ca(&pvattest_config.create.root_ca_path, create_indent),
|
||||
_entry_output(&pvattest_config.create.output_path, "attestation request", create_indent),
|
||||
_entry_att_prot_key_save(&pvattest_config.create.arp_key_out_path, create_indent),
|
||||
|
||||
_entry_no_verify(&pvattest_config.create.no_verify, create_indent),
|
||||
_entry_offline_maps_to_online(&pvattest_config.create.online, create_indent),
|
||||
_entry_verbose(create_indent),
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static GOptionEntry experimental_create_options[] = {
|
||||
_entry_x_no_nonce(&pvattest_config.create.use_nonce, create_indent),
|
||||
_entry_x_paf(&create_set_paf, create_indent),
|
||||
_entry_x_aad_size(&pvattest_config.create.x_aad_size, create_indent),
|
||||
_entry_phkh_img(&pvattest_config.create.phkh_img, create_indent),
|
||||
_entry_phkh_att(&pvattest_config.create.phkh_att, create_indent),
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static gboolean verify_create(GError **error)
|
||||
{
|
||||
if (!check_file_list(pvattest_config.create.host_key_document_paths, TRUE,
|
||||
_("Specify --host-key-document at least once."), error))
|
||||
return FALSE;
|
||||
if (!pvattest_config.create.no_verify) {
|
||||
if (!check_file_list(
|
||||
pvattest_config.create.certificate_paths, TRUE,
|
||||
_("Either specify the IBM Z signing key and"
|
||||
" intermediate CA certificate\nby using the '--cert' option, or"
|
||||
" use the '--no-verify' flag to disable the\nhost-key document"
|
||||
" verification completely (at your own risk).\n"
|
||||
"Only use this option in test environments or if"
|
||||
" you trust the unverified document."),
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
if (!check_for_invalid_path(pvattest_config.create.arp_key_out_path, FALSE,
|
||||
_("Missing argument for --arpk."), error))
|
||||
return FALSE;
|
||||
if (!check_for_invalid_path(pvattest_config.create.output_path, FALSE,
|
||||
_("Missing argument for --output."), error))
|
||||
return FALSE;
|
||||
if (!check_optional_file_list(pvattest_config.create.crl_paths, TRUE, error))
|
||||
return FALSE;
|
||||
if (!check_for_optional_invalid_path(pvattest_config.create.root_ca_path, TRUE, error))
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
};
|
||||
|
||||
/************************* MEASUREMENT OPTIONS ********************************/
|
||||
#define perform_indent " "
|
||||
|
||||
static GOptionEntry perform_options[] = {
|
||||
_entry_input(&pvattest_config.perform.input_path, "attestation request", perform_indent),
|
||||
_entry_output(&pvattest_config.perform.output_path, "attestation result", perform_indent),
|
||||
_entry_verbose(perform_indent),
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static GOptionEntry experimental_perform_options[] = {
|
||||
_entry_x_user_data(&pvattest_config.perform.user_data_path, perform_indent),
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static gboolean verify_perform(GError **error)
|
||||
{
|
||||
if (!check_for_invalid_path(pvattest_config.perform.input_path, TRUE,
|
||||
_("Missing argument for --input."), error))
|
||||
return FALSE;
|
||||
if (!check_for_invalid_path(pvattest_config.perform.output_path, FALSE,
|
||||
_("Missing argument for --output."), error))
|
||||
return FALSE;
|
||||
if (!check_for_optional_invalid_path(pvattest_config.perform.user_data_path, TRUE, error))
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/************************* VERIFY OPTIONS ************************************/
|
||||
#define verify_indent " "
|
||||
|
||||
static GOptionEntry verify_options[] = {
|
||||
_entry_input(&pvattest_config.verify.input_path, "attestation result", 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),
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static gboolean verify_verify(GError **error)
|
||||
{
|
||||
if (!check_for_invalid_path(pvattest_config.verify.input_path, TRUE,
|
||||
_("Missing argument for --input."), error))
|
||||
return FALSE;
|
||||
if (!check_for_invalid_path(pvattest_config.verify.hdr_path, TRUE,
|
||||
_("Missing argument for --hdr."), error))
|
||||
return FALSE;
|
||||
if (!check_for_invalid_path(pvattest_config.verify.arp_key_in_path, TRUE,
|
||||
_("Missing argument for --arpk."), error))
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/************************** OPTIONS END ***************************************/
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
static char summary[] =
|
||||
"\n"
|
||||
"Create, perform, and verify attestation measurements for IBM Secure Execution guest"
|
||||
" systems.\n"
|
||||
"\n"
|
||||
"COMMANDS\n"
|
||||
" create On a trusted system, creates an attestation request.\n"
|
||||
" perform On the SE-guest to be attested, sends the attestation request\n"
|
||||
" to the Ultravisor and receives the answer.\n"
|
||||
#ifndef PVATTEST_COMPILE_PERFORM
|
||||
" (not supported on this platform)\n"
|
||||
#endif /* PVATTEST_COMPILE_PERFORM */
|
||||
|
||||
" verify On a trusted system, compares the one from your trusted system.\n"
|
||||
" If they differ, the Secure Execution guest might not be compromised\n"
|
||||
"\n"
|
||||
"Use '" GETTEXT_PACKAGE " [COMMAND] -h' to get detailed help\n";
|
||||
static char create_summary[] =
|
||||
"Create attestation measurement requests to attest an\n"
|
||||
"IBM Secure Execution guest. Only build attestation requests in a trusted\n"
|
||||
"environment such as your Workstation.\n"
|
||||
"To avoid compromising the attestation do not publish the\n"
|
||||
"protection key and delete it after verification.\n"
|
||||
"Every 'create' will generate a new, random protection key.\n";
|
||||
static char perform_summary[] =
|
||||
#ifndef PVATTEST_COMPILE_PERFORM
|
||||
"This system does NOT support 'perform'.\n"
|
||||
#endif /* PVATTEST_COMPILE_PERFORM */
|
||||
"Perform a measurement of this IBM Secure Execution guest using '/dev/uv'.\n";
|
||||
static char verify_summary[] =
|
||||
"Verify that a previously generated attestation measurement of an\n"
|
||||
"IBM Secure Execution guest yielded the expected results.\n"
|
||||
"Verify attestation requests only in a trusted environment, such as your workstation.";
|
||||
|
||||
static void print_version_and_exit(void)
|
||||
{
|
||||
printf("%s version %s\n", GETTEXT_PACKAGE, RELEASE_STRING);
|
||||
printf("%s\n", COPYRIGHT_NOTICE);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static GOptionContext *create_ctx(GOptionEntry *options, GOptionEntry *experimental_options,
|
||||
const char *param_name, const char *opt_summary)
|
||||
{
|
||||
GOptionContext *ret = g_option_context_new(param_name);
|
||||
GOptionGroup *x_group = NULL;
|
||||
g_option_context_add_main_entries(ret, options, NULL);
|
||||
g_option_context_set_summary(ret, opt_summary);
|
||||
if (experimental_options) {
|
||||
x_group = g_option_group_new(
|
||||
"experimental",
|
||||
"Experimental Options; Do not use in a production environment",
|
||||
"Show experimental options", NULL, NULL);
|
||||
g_option_group_add_entries(x_group, experimental_options);
|
||||
g_option_context_add_group(ret, x_group);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum pvattest_command pvattest_parse(int *argc, char **argvp[], pvattest_config_t **config,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GOptionContext) main_context = NULL, subc_context = NULL;
|
||||
char **argv = *argvp;
|
||||
enum pvattest_command subc = PVATTEST_SUBC_INVALID;
|
||||
verify_options_fn_t verify_options_fn = NULL;
|
||||
|
||||
pv_wrapped_g_assert(argc);
|
||||
pv_wrapped_g_assert(argvp);
|
||||
pv_wrapped_g_assert(config);
|
||||
|
||||
/*
|
||||
* First parse until the first non dash argument. This must be one of the commands.
|
||||
* (strict POSIX parsing)
|
||||
*/
|
||||
main_context = g_option_context_new(
|
||||
"COMMAND [OPTIONS] - create, perform, and verify attestation measurements");
|
||||
g_option_context_set_strict_posix(main_context, TRUE);
|
||||
g_option_context_add_main_entries(main_context, general_options, NULL);
|
||||
g_option_context_set_summary(main_context, summary);
|
||||
|
||||
if (!g_option_context_parse(main_context, argc, argvp, error))
|
||||
return PVATTEST_SUBC_INVALID;
|
||||
if (print_version)
|
||||
print_version_and_exit();
|
||||
|
||||
/*
|
||||
* Parse depending on the specified command
|
||||
*/
|
||||
else if (g_strcmp0(argv[1], PVATTEST_SUBC_STR_CREATE) == 0) {
|
||||
subc_context =
|
||||
create_ctx(create_options, experimental_create_options,
|
||||
"create [OPTIONS] - create an attestation measurement request",
|
||||
create_summary);
|
||||
subc = PVATTEST_SUBC_CREATE;
|
||||
verify_options_fn = &verify_create;
|
||||
} else if (g_strcmp0(argv[1], PVATTEST_SUBC_STR_PERFORM) == 0) {
|
||||
subc_context =
|
||||
create_ctx(perform_options, experimental_perform_options,
|
||||
"perform [OPTIONS] - perform an attestation measurement request",
|
||||
perform_summary);
|
||||
subc = PVATTEST_SUBC_PERFORM;
|
||||
verify_options_fn = &verify_perform;
|
||||
#ifndef PVATTEST_COMPILE_PERFORM
|
||||
g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG,
|
||||
_("This system does not support the 'perform' command."));
|
||||
return PVATTEST_SUBC_INVALID;
|
||||
#endif /* PVATTEST_COMPILE_PERFORM */
|
||||
} else if (g_strcmp0(argv[1], PVATTEST_SUBC_STR_VERIFY) == 0) {
|
||||
subc_context = create_ctx(verify_options, NULL,
|
||||
"verify [OPTIONS] - verify an attestation measurement",
|
||||
verify_summary);
|
||||
subc = PVATTEST_SUBC_VERIFY;
|
||||
verify_options_fn = &verify_verify;
|
||||
} else {
|
||||
if (argv[1])
|
||||
g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARGV,
|
||||
_("Invalid command specified: %s."), argv[1]);
|
||||
else
|
||||
g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARGV,
|
||||
_("No command specified."));
|
||||
return PVATTEST_SUBC_INVALID;
|
||||
}
|
||||
g_assert(verify_options_fn);
|
||||
|
||||
if (!g_option_context_parse(subc_context, argc, argvp, error))
|
||||
return PVATTEST_SUBC_INVALID;
|
||||
|
||||
if (!verify_options_fn(error))
|
||||
return PVATTEST_SUBC_INVALID;
|
||||
|
||||
*config = &pvattest_config;
|
||||
return subc;
|
||||
}
|
||||
|
||||
static void pvattest_parse_clear_create_config(pvattest_create_config_t *config)
|
||||
{
|
||||
if (!config)
|
||||
return;
|
||||
g_strfreev(config->host_key_document_paths);
|
||||
g_strfreev(config->certificate_paths);
|
||||
g_free(config->arp_key_out_path);
|
||||
g_free(config->output_path);
|
||||
}
|
||||
|
||||
static void pvattest_parse_clear_perform_config(pvattest_perform_config_t *config)
|
||||
{
|
||||
if (!config)
|
||||
return;
|
||||
g_free(config->input_path);
|
||||
g_free(config->output_path);
|
||||
}
|
||||
|
||||
static void pvattest_parse_clear_verify_config(pvattest_verify_config_t *config)
|
||||
{
|
||||
if (!config)
|
||||
return;
|
||||
g_free(config->input_path);
|
||||
g_free(config->hdr_path);
|
||||
g_free(config->arp_key_in_path);
|
||||
}
|
||||
|
||||
void pvattest_parse_clear_config(pvattest_config_t *config)
|
||||
{
|
||||
if (!config)
|
||||
return;
|
||||
pvattest_parse_clear_create_config(&config->create);
|
||||
pvattest_parse_clear_perform_config(&config->perform);
|
||||
pvattest_parse_clear_verify_config(&config->verify);
|
||||
}
|
||||
106
pvattest/src/argparse.h
Normal file
106
pvattest/src/argparse.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Definitions used for parsing arguments.
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* 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 PVATTEST_ARGPARSE_H
|
||||
#define PVATTEST_ARGPARSE_H
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "libpv/glib-helper.h"
|
||||
#include "libpv/macros.h"
|
||||
|
||||
#define PVATTEST_SUBC_STR_CREATE "create"
|
||||
#define PVATTEST_SUBC_STR_PERFORM "perform"
|
||||
#define PVATTEST_SUBC_STR_VERIFY "verify"
|
||||
|
||||
enum pvattest_command {
|
||||
PVATTEST_SUBC_INVALID,
|
||||
PVATTEST_SUBC_CREATE,
|
||||
PVATTEST_SUBC_PERFORM,
|
||||
PVATTEST_SUBC_VERIFY,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int log_level;
|
||||
} pvattest_general_config_t;
|
||||
|
||||
typedef struct {
|
||||
char **host_key_document_paths;
|
||||
char **certificate_paths;
|
||||
char **crl_paths;
|
||||
char *root_ca_path;
|
||||
|
||||
char *arp_key_out_path;
|
||||
char *output_path;
|
||||
|
||||
gboolean phkh_img;
|
||||
gboolean phkh_att;
|
||||
gboolean no_verify;
|
||||
gboolean online;
|
||||
|
||||
/* experimental flags */
|
||||
gboolean use_nonce; /* default TRUE */
|
||||
uint64_t paf; /* default 0 */
|
||||
int x_aad_size; /* default -1 -> ignore */
|
||||
} pvattest_create_config_t;
|
||||
|
||||
typedef struct {
|
||||
char *output_path;
|
||||
char *input_path;
|
||||
/* experimental flags */
|
||||
char *user_data_path; /* default NULL */
|
||||
} pvattest_perform_config_t;
|
||||
|
||||
typedef struct {
|
||||
char *input_path;
|
||||
char *hdr_path;
|
||||
char *arp_key_in_path;
|
||||
} pvattest_verify_config_t;
|
||||
|
||||
typedef struct {
|
||||
pvattest_general_config_t general;
|
||||
pvattest_create_config_t create;
|
||||
pvattest_perform_config_t perform;
|
||||
pvattest_verify_config_t verify;
|
||||
} pvattest_config_t;
|
||||
|
||||
/**
|
||||
* pvattest_parse_clear_config:
|
||||
*
|
||||
* @config: struct to be cleared
|
||||
*
|
||||
* clears but not frees all config.
|
||||
* all non config members such like char* will be freed.
|
||||
*/
|
||||
void pvattest_parse_clear_config(pvattest_config_t *config);
|
||||
|
||||
/**
|
||||
* pvattest_parse:
|
||||
*
|
||||
* @argc: ptr to argument count
|
||||
* @argv: ptr to argument vector
|
||||
* @config: output: ptr to parsed config. Target is statically allocated.
|
||||
* You are responsible for freeing all non config ptrs.
|
||||
* use #pvattest_parse_clear_config for that.
|
||||
*
|
||||
* Will not return if verbose or help parsed.
|
||||
*
|
||||
* Returns: selected command as enum
|
||||
*/
|
||||
enum pvattest_command pvattest_parse(int *argc, char **argvp[], pvattest_config_t **config,
|
||||
GError **error) PV_NONNULL(1, 2, 3);
|
||||
|
||||
#define PVATTEST_ERROR g_quark_from_static_string("pv-pvattest_error-quark")
|
||||
typedef enum {
|
||||
PVATTEST_ERR_INV_ARGV,
|
||||
PVATTEST_ERR_INV_ARG,
|
||||
} pv_pvattest_error_e;
|
||||
|
||||
#endif /* PVATTEST_ARGPARSE_H */
|
||||
148
pvattest/src/attestation.c
Normal file
148
pvattest/src/attestation.c
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Attestation related functions
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
/* Must be included before any other header */
|
||||
#include "config.h"
|
||||
|
||||
#include "libpv/cert.h"
|
||||
#include "libpv/hash.h"
|
||||
#include "libpv/se-hdr.h"
|
||||
|
||||
#include "exchange_format.h"
|
||||
#include "attestation.h"
|
||||
|
||||
G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->pld) == sizeof(((struct pv_hdr_head *)0)->pld));
|
||||
G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->ald) == sizeof(((struct pv_hdr_head *)0)->ald));
|
||||
G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->tld) == sizeof(((struct pv_hdr_head *)0)->tld));
|
||||
G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->tag) == sizeof(((struct pv_hdr *)0)->tag));
|
||||
|
||||
struct att_meas_sizes {
|
||||
uint16_t user_data_len;
|
||||
uint16_t zeros;
|
||||
uint32_t additional_data_len;
|
||||
} __packed;
|
||||
G_STATIC_ASSERT(sizeof(struct att_meas_sizes) == 8);
|
||||
|
||||
/*
|
||||
* All optional arguments may be NULL
|
||||
* user_data is up to 256 bytes long, or NULL.
|
||||
* nonce is 16 bytes long or NULL.
|
||||
* additional_data is up to 32768 bytes long or NULL.
|
||||
*/
|
||||
GBytes *att_gen_measurement_hmac_sha512(const att_meas_ctx_t *meas_ctx, GBytes *measurement_key,
|
||||
GBytes *optional_user_data, GBytes *optional_nonce,
|
||||
GBytes *optional_additional_data, GError **error)
|
||||
{
|
||||
struct att_meas_sizes meas_sizes = {};
|
||||
g_autoptr(HMAC_CTX) hmac_ctx = NULL;
|
||||
size_t additional_data_size = 0;
|
||||
size_t user_data_size = 0;
|
||||
size_t nonce_size = 0;
|
||||
|
||||
pv_wrapped_g_assert(meas_ctx);
|
||||
pv_wrapped_g_assert(measurement_key);
|
||||
|
||||
if (optional_user_data)
|
||||
user_data_size = g_bytes_get_size(optional_user_data);
|
||||
if (optional_additional_data)
|
||||
additional_data_size = g_bytes_get_size(optional_additional_data);
|
||||
if (optional_nonce)
|
||||
nonce_size = g_bytes_get_size(optional_nonce);
|
||||
|
||||
/* checks for these sizes resulting in GErrors are done before */
|
||||
g_assert(user_data_size <= PVATTEST_USER_DATA_MAX_SIZE);
|
||||
g_assert(additional_data_size <= PVATTEST_ADDITIONAL_MAX_SIZE);
|
||||
g_assert(nonce_size == 0 || nonce_size == ARCB_V1_NONCE_SIZE);
|
||||
|
||||
pv_wrapped_g_assert(meas_ctx);
|
||||
pv_wrapped_g_assert(measurement_key);
|
||||
|
||||
hmac_ctx = pv_hmac_ctx_new(measurement_key, EVP_sha512(), error);
|
||||
if (!hmac_ctx)
|
||||
return NULL;
|
||||
|
||||
meas_sizes.user_data_len = GUINT16_TO_BE((uint16_t)user_data_size);
|
||||
meas_sizes.zeros = 0;
|
||||
meas_sizes.additional_data_len = GUINT32_TO_BE((uint32_t)additional_data_size);
|
||||
|
||||
if (pv_hmac_ctx_update_raw(hmac_ctx, meas_ctx, sizeof(*meas_ctx), error) != 0)
|
||||
return NULL;
|
||||
|
||||
/* add the sizes of user and additional data. */
|
||||
if (pv_hmac_ctx_update_raw(hmac_ctx, &meas_sizes, sizeof(meas_sizes), error))
|
||||
return NULL;
|
||||
|
||||
/* update optional data. if NULL passed (or size = 0) nothing will happen to the HMAC_CTX */
|
||||
if (pv_hmac_ctx_update(hmac_ctx, optional_user_data, error) != 0)
|
||||
return NULL;
|
||||
if (pv_hmac_ctx_update(hmac_ctx, optional_nonce, error) != 0)
|
||||
return NULL;
|
||||
if (pv_hmac_ctx_update(hmac_ctx, optional_additional_data, error) != 0)
|
||||
return NULL;
|
||||
return pv_hamc_ctx_finalize(hmac_ctx, error);
|
||||
}
|
||||
|
||||
att_meas_ctx_t *att_extract_from_hdr(GBytes *se_hdr, GError **error)
|
||||
{
|
||||
g_autofree att_meas_ctx_t *meas = NULL;
|
||||
const struct pv_hdr *hdr = NULL;
|
||||
size_t se_hdr_tag_offset;
|
||||
size_t se_hdr_size;
|
||||
uint8_t *hdr_u8;
|
||||
|
||||
pv_wrapped_g_assert(se_hdr);
|
||||
|
||||
hdr = g_bytes_get_data(se_hdr, &se_hdr_size);
|
||||
hdr_u8 = (uint8_t *)hdr;
|
||||
|
||||
if (se_hdr_size < PV_V1_PV_HDR_MIN_SIZE) {
|
||||
g_set_error(error, ATT_ERROR, ATT_ERR_INVALID_HDR,
|
||||
_("Invalid SE header provided."));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (GUINT32_FROM_BE(hdr->head.phs) != se_hdr_size ||
|
||||
GUINT64_FROM_BE(hdr->head.magic) != PV_MAGIC_NUMBER) {
|
||||
g_set_error(error, ATT_ERROR, ATT_ERR_INVALID_HDR,
|
||||
_("Invalid SE header provided."));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
se_hdr_tag_offset = GUINT32_FROM_BE(hdr->head.phs) - sizeof(hdr->tag);
|
||||
meas = g_new0(att_meas_ctx_t, 1);
|
||||
|
||||
memcpy(meas->pld, hdr->head.pld, sizeof(meas->pld));
|
||||
memcpy(meas->ald, hdr->head.ald, sizeof(meas->ald));
|
||||
memcpy(meas->tld, hdr->head.tld, sizeof(meas->tld));
|
||||
memcpy(meas->tag, hdr_u8 + se_hdr_tag_offset, sizeof(meas->tag));
|
||||
|
||||
return g_steal_pointer(&meas);
|
||||
}
|
||||
|
||||
void att_add_uid(att_meas_ctx_t *meas_ctx, GBytes *config_uid)
|
||||
{
|
||||
pv_wrapped_g_assert(meas_ctx);
|
||||
pv_wrapped_g_assert(config_uid);
|
||||
|
||||
g_assert(g_bytes_get_size(config_uid) == ATT_CONFIG_UID_SIZE);
|
||||
pv_gbytes_memcpy(meas_ctx->config_uid, ATT_CONFIG_UID_SIZE, config_uid);
|
||||
}
|
||||
|
||||
gboolean att_verify_measurement(const GBytes *calculated_measurement,
|
||||
const GBytes *uvio_measurement, GError **error)
|
||||
{
|
||||
pv_wrapped_g_assert(calculated_measurement);
|
||||
pv_wrapped_g_assert(uvio_measurement);
|
||||
|
||||
if (g_bytes_compare(calculated_measurement, uvio_measurement) != 0) {
|
||||
g_set_error(error, ATT_ERROR, ATT_ERR_MEASUREMENT_VERIFICATION_FAILED,
|
||||
_("Calculated and received attestation measurement are not equal."));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user