From ab2402c95f031ad97544a86e4418cb765313eee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 31 Aug 2018 10:07:35 +0200 Subject: [PATCH 01/56] drop LOADLIBES variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove depreciated LOADLIBES variable from the Makefile rules, LDLIBS serves the same purpose these days. Signed-off-by: Dan Horák --- common.mak | 2 +- cpuplugd/Makefile | 2 +- ipl_tools/Makefile | 2 +- vmconvert/Makefile | 2 +- vmur/Makefile | 2 +- ziomon/Makefile | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common.mak b/common.mak index 571fb30..6f0990e 100644 --- a/common.mak +++ b/common.mak @@ -239,7 +239,7 @@ endif $(CXX) $(ALL_CPPFLAGS) $(ALL_CXXFLAGS) -c $< -o $@ %: %.o - $(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ %.a: $(AR) rcs $@ $^ diff --git a/cpuplugd/Makefile b/cpuplugd/Makefile index a9a49ab..916638d 100644 --- a/cpuplugd/Makefile +++ b/cpuplugd/Makefile @@ -7,7 +7,7 @@ LDLIBS += -lm OBJECTS = daemon.o cpu.o info.o terms.o config.o main.o getopt.o mem.o cpuplugd: $(OBJECTS) - $(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ clean: rm -f cpuplugd $(OBJECTS) diff --git a/ipl_tools/Makefile b/ipl_tools/Makefile index 128ec3e..506d5cd 100644 --- a/ipl_tools/Makefile +++ b/ipl_tools/Makefile @@ -6,7 +6,7 @@ objects = main.o ccw.o fcp.o system.o shutdown.o \ cmd_lsshut.o cmd_chshut.o cmd_lsreipl.o cmd_chreipl.o proc.o chreipl: $(objects) - $(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ lsreipl: ln -sf chreipl lsreipl diff --git a/vmconvert/Makefile b/vmconvert/Makefile index 4d0216c..380eb19 100644 --- a/vmconvert/Makefile +++ b/vmconvert/Makefile @@ -9,7 +9,7 @@ libs = $(rootdir)/libvmdump/libvmdump.a objects = vmconvert.o vmconvert: $(objects) $(libs) - $(LINKXX) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINKXX) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ install: all $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) $(DESTDIR)$(MANDIR)/man1 diff --git a/vmur/Makefile b/vmur/Makefile index 1a6bddc..2c1c2d5 100644 --- a/vmur/Makefile +++ b/vmur/Makefile @@ -11,7 +11,7 @@ libs = $(rootdir)/libvmdump/libvmdump.a \ objects = vmur.o vmur: $(objects) $(libs) - $(LINKXX) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINKXX) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ install: all $(INSTALL) -d -m 755 $(DESTDIR)$(USRSBINDIR) $(DESTDIR)$(MANDIR)/man8 diff --git a/ziomon/Makefile b/ziomon/Makefile index 778401b..61c2399 100644 --- a/ziomon/Makefile +++ b/ziomon/Makefile @@ -12,33 +12,33 @@ ziomon_mgr_main.o: ziomon_mgr.c ziomon_mgr: LDLIBS += -lm ziomon_mgr: ziomon_dacc.o ziomon_util.o ziomon_mgr_main.o ziomon_tools.o \ ziomon_zfcpdd.o ziomon_msg_tools.o - $(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ ziomon_util_main.o: ziomon_util.c ziomon_util.h $(CC) -DWITH_MAIN $(ALL_CFLAGS) $(ALL_CPPFLAGS) -c $< -o $@ ziomon_util: LDLIBS += -lm ziomon_util: ziomon_util_main.o ziomon_tools.o - $(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ ziomon_zfcpdd_main.o: ziomon_zfcpdd.c ziomon_zfcpdd.h $(CC) -DWITH_MAIN $(ALL_CFLAGS) $(ALL_CPPFLAGS) -c $< -o $@ ziomon_zfcpdd: LDLIBS += -lm -lrt -lpthread ziomon_zfcpdd: ziomon_zfcpdd_main.o ziomon_tools.o - $(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ ziorep_traffic: ziorep_traffic.o ziorep_framer.o ziorep_frameset.o \ ziorep_printers.o ziomon_dacc.o ziomon_util.o \ ziomon_msg_tools.o ziomon_tools.o ziomon_zfcpdd.o \ ziorep_cfgreader.o ziorep_collapser.o ziorep_utils.o \ ziorep_filters.o - $(LINKXX) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINKXX) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ ziorep_utilization: ziorep_utilization.o ziorep_framer.o ziorep_frameset.o \ ziorep_printers.o ziomon_dacc.o ziomon_util.o \ ziomon_msg_tools.o ziomon_tools.o ziomon_zfcpdd.o \ ziorep_cfgreader.o ziorep_collapser.o ziorep_utils.o \ ziorep_filters.o - $(LINKXX) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ + $(LINKXX) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ install: all $(SED) -e 's/%S390_TOOLS_VERSION%/$(S390_TOOLS_RELEASE)/' \ -- 2.21.3 From ed7cf76fd149a9fed3ce9f728e072bccc44997cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 31 Aug 2018 10:13:38 +0200 Subject: [PATCH 02/56] zkey: Drop redundant include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dan Horák --- zkey/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/zkey/Makefile b/zkey/Makefile index 68f35cf..725cb3b 100644 --- a/zkey/Makefile +++ b/zkey/Makefile @@ -22,7 +22,6 @@ else INSTALL_TARGETS += zkey-cryptsetup-skip-cryptsetup2 endif -CPPFLAGS += -I../include LIBS = $(rootdir)/libutil/libutil.a detect-libcryptsetup.h: -- 2.21.3 From 3a354d9d8e83a36edb9ce68fb85b3cc4afd19991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 31 Aug 2018 10:17:07 +0200 Subject: [PATCH 03/56] zkey: Be consistent when refering to libutil.a MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dan Horák --- zkey/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zkey/Makefile b/zkey/Makefile index 725cb3b..7e2047a 100644 --- a/zkey/Makefile +++ b/zkey/Makefile @@ -22,7 +22,7 @@ else INSTALL_TARGETS += zkey-cryptsetup-skip-cryptsetup2 endif -LIBS = $(rootdir)/libutil/libutil.a +libs = $(rootdir)/libutil/libutil.a detect-libcryptsetup.h: echo "#include " > detect-libcryptsetup.h @@ -69,10 +69,10 @@ keystore.o: keystore.c keystore.h properties.h zkey-cryptsetup.o: check-dep-zkey-cryptsetup zkey-cryptsetup.c pkey.h misc.h zkey: LDLIBS = -ldl -lcrypto -zkey: zkey.o pkey.o properties.o keystore.o $(LIBS) +zkey: zkey.o pkey.o properties.o keystore.o $(libs) zkey-cryptsetup: LDLIBS = -ldl -lcryptsetup -ljson-c -zkey-cryptsetup: zkey-cryptsetup.o pkey.o $(LIBS) +zkey-cryptsetup: zkey-cryptsetup.o pkey.o $(libs) install-common: $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) -- 2.21.3 From 913721b06be3b4662593c3f1a344256c46ce841a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 31 Aug 2018 04:29:39 -0400 Subject: [PATCH 04/56] zkey: Be explicit about linking the tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I've met cases when the make's default rule for linking was used instead omitting the ALL_LDFLAGS variable. The linking rule from common.mak is defined for linking *.o files only, here we have libutil.a too. Signed-off-by: Dan Horák --- zkey/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zkey/Makefile b/zkey/Makefile index 7e2047a..901ddd4 100644 --- a/zkey/Makefile +++ b/zkey/Makefile @@ -70,9 +70,11 @@ zkey-cryptsetup.o: check-dep-zkey-cryptsetup zkey-cryptsetup.c pkey.h misc.h zkey: LDLIBS = -ldl -lcrypto zkey: zkey.o pkey.o properties.o keystore.o $(libs) + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ zkey-cryptsetup: LDLIBS = -ldl -lcryptsetup -ljson-c zkey-cryptsetup: zkey-cryptsetup.o pkey.o $(libs) + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ install-common: $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) -- 2.21.3 From 6a3da6ec34a0947bd14cd0a36ab08b607236a414 Mon Sep 17 00:00:00 2001 From: Ingo Franzki Date: Wed, 17 Oct 2018 13:52:48 +0200 Subject: [PATCH 05/56] zkey: Makefile: Avoid relink of modules during 'make install' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because targets check-dep-zkey and check-dep-zkey-cryptsetup do not produce any file, any targets that have a pre-req on those targets are rebuilt during 'make install'. Also correct .PHONY targets. Fixes: https://github.com/ibm-s390-tools/s390-tools/issues/46 Signed-off-by: Ingo Franzki Signed-off-by: Jan Höppner --- zkey/Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/zkey/Makefile b/zkey/Makefile index 901ddd4..bc7bc33 100644 --- a/zkey/Makefile +++ b/zkey/Makefile @@ -37,6 +37,7 @@ check-dep-zkey: "openssl/evp.h", \ "openssl-devel", \ "HAVE_OPENSSL=0") + touch check-dep-zkey check-dep-zkey-cryptsetup: detect-libcryptsetup.h $(call check_dep, \ @@ -50,6 +51,7 @@ check-dep-zkey-cryptsetup: detect-libcryptsetup.h "json-c/json.h", \ "json-c-devel", \ "HAVE_JSONC=0") + touch check-dep-zkey-cryptsetup zkey-skip: echo " SKIP zkey due to HAVE_OPENSSL=0" @@ -93,6 +95,9 @@ install-zkey-cryptsetup: install: all install-common $(INSTALL_TARGETS) clean: - rm -f *.o zkey zkey-cryptsetup detect-libcryptsetup.h + rm -f *.o zkey zkey-cryptsetup detect-libcryptsetup.h \ + check-dep-zkey check-dep-zkey-cryptsetup -.PHONY: all install clean +.PHONY: all install clean zkey-skip zkey-cryptsetup-skip-cryptsetup2 \ + zkey-cryptsetup-skip-jsonc install-common install-zkey \ + install-zkey-cryptsetup -- 2.21.3 From e7b59a9f7cc5d049ef68331855a881beb85a1347 Mon Sep 17 00:00:00 2001 From: Ingo Franzki Date: Thu, 25 Oct 2018 12:57:29 +0200 Subject: [PATCH 06/56] zkey: Makefile: Don't rebuild .o.d files on 'make install' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .o.d make targets in common.mak do not expect that header files are generated by a make target. When a new header file is generated, the .o.d targets will be rebuilt on the next make invocation, because that new header file is then detected, and is then treated as a new dependency of all .o.d targets. Signed-off-by: Ingo Franzki Signed-off-by: Jan Höppner --- zkey/Makefile | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/zkey/Makefile b/zkey/Makefile index bc7bc33..a44b14b 100644 --- a/zkey/Makefile +++ b/zkey/Makefile @@ -24,12 +24,12 @@ endif libs = $(rootdir)/libutil/libutil.a -detect-libcryptsetup.h: - echo "#include " > detect-libcryptsetup.h - echo "#ifndef CRYPT_LUKS2" >> detect-libcryptsetup.h - echo " #error libcryptsetup version 2.0.3 is required" >> detect-libcryptsetup.h - echo "#endif" >> detect-libcryptsetup.h - echo "int i = CRYPT_SLOT_UNBOUND;" >> detect-libcryptsetup.h +detect-libcryptsetup.dep: + echo "#include " > detect-libcryptsetup.dep + echo "#ifndef CRYPT_LUKS2" >> detect-libcryptsetup.dep + echo " #error libcryptsetup version 2.0.3 is required" >> detect-libcryptsetup.dep + echo "#endif" >> detect-libcryptsetup.dep + echo "int i = CRYPT_SLOT_UNBOUND;" >> detect-libcryptsetup.dep check-dep-zkey: $(call check_dep, \ @@ -39,10 +39,10 @@ check-dep-zkey: "HAVE_OPENSSL=0") touch check-dep-zkey -check-dep-zkey-cryptsetup: detect-libcryptsetup.h +check-dep-zkey-cryptsetup: detect-libcryptsetup.dep $(call check_dep, \ "zkey-cryptsetup", \ - "detect-libcryptsetup.h", \ + "detect-libcryptsetup.dep", \ "cryptsetup-devel version 2.0.3", \ "HAVE_CRYPTSETUP2=0", \ "-I.") @@ -95,7 +95,7 @@ install-zkey-cryptsetup: install: all install-common $(INSTALL_TARGETS) clean: - rm -f *.o zkey zkey-cryptsetup detect-libcryptsetup.h \ + rm -f *.o zkey zkey-cryptsetup detect-libcryptsetup.dep \ check-dep-zkey check-dep-zkey-cryptsetup .PHONY: all install clean zkey-skip zkey-cryptsetup-skip-cryptsetup2 \ -- 2.21.3 From dc92c4ce7963dcf5018c3ef3672b6d217f29842f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 19 Nov 2018 11:26:40 +0100 Subject: [PATCH 07/56] zpcictl: Add tool to manage PCI devices (#1525409) Summary: zpcictl: Add tool to manage PCI devices Description: Use the zpcictl tool to manage PCI devices on the IBM Z platform. Initial functions include generating firmware error logs, resetting PCI devices, and preparing a device for further repair actions. --- .gitignore | 1 + Makefile | 2 +- zpcictl/Makefile | 18 +++ zpcictl/zpcictl.8 | 80 ++++++++++ zpcictl/zpcictl.c | 379 ++++++++++++++++++++++++++++++++++++++++++++++ zpcictl/zpcictl.h | 60 ++++++++ 6 files changed, 539 insertions(+), 1 deletion(-) create mode 100644 zpcictl/Makefile create mode 100644 zpcictl/zpcictl.8 create mode 100644 zpcictl/zpcictl.c create mode 100644 zpcictl/zpcictl.h diff --git a/.gitignore b/.gitignore index 41feaf6..042233f 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,4 @@ zipl/src/chreipl_helper.device-mapper zipl/src/zipl zipl/src/zipl_helper.device-mapper zkey/zkey +zpcictl/zpcictl diff --git a/Makefile b/Makefile index adc92b6..bb2b900 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ 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 + systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl SUB_DIRS = $(LIB_DIRS) $(TOOL_DIRS) all: $(TOOL_DIRS) diff --git a/zpcictl/Makefile b/zpcictl/Makefile new file mode 100644 index 0000000..2b315c5 --- /dev/null +++ b/zpcictl/Makefile @@ -0,0 +1,18 @@ +include ../common.mak + +all: zpcictl + +libs = $(rootdir)/libutil/libutil.a + +zpcictl: zpcictl.o $(libs) + +install: all + $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8 + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zpcictl $(DESTDIR)$(BINDIR) + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 zpcictl.8 \ + $(DESTDIR)$(MANDIR)/man8 + +clean: + rm -f *.o *~ zpcictl core + +.PHONY: all install clean diff --git a/zpcictl/zpcictl.8 b/zpcictl/zpcictl.8 new file mode 100644 index 0000000..4f8fcd8 --- /dev/null +++ b/zpcictl/zpcictl.8 @@ -0,0 +1,80 @@ +.\" Copyright 2017 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. +.\" +.\" Macro for inserting an option description prologue. +.\" .OD [] [args] +.de OD +. ds args " +. if !'\\$3'' .as args \fI\\$3\fP +. if !'\\$4'' .as args \\$4 +. if !'\\$5'' .as args \fI\\$5\fP +. if !'\\$6'' .as args \\$6 +. if !'\\$7'' .as args \fI\\$7\fP +. PD 0 +. if !'\\$2'' .IP "\fB\-\\$2\fP \\*[args]" 4 +. if !'\\$1'' .IP "\fB\-\-\\$1\fP \\*[args]" 4 +. PD +.. +. +.TH zpcictl 8 "Oct 2018" s390-tools zpcictl +. +.SH NAME +zpcictl - Manage PCI devices on z Systems +. +. +.SH SYNOPSIS +.B "zpcictl" +.I "OPTIONS" +.I "DEVICE" +. +. +.SH DESCRIPTION +.B zpcictl +is a tool for managing PCI devices on the IBM z Systems platform. It is +especially used for reporting errorneous PCI devices to the service element. + +.B Note: +For NVMe devices additional data (such as S.M.A.R.T. data) is collected and sent +with any error handling action. The smartmontools are required to be installed +for this to work. +.PP +. +. +.SH DEVICE +.B DEVICE +can be either the PCI slot address (e.g. 0000:00:00.0) or the main device node +of an NVMe device (e.g. /dev/nvme0). +. +. +.SH OPTIONS +.SS Error Handling +.OD reset "" "DEVICE" +Reset +.I DEVICE +and initiate a re-initialisation of the adapter. +.PP +. +.OD deconfigure "" "DEVICE" +De-configure +.I DEVICE +and prepare for any repair action. This action will move the +PCI device from a configured to a reserved state. +.PP +. +.OD report-error "" "DEVICE" +Report any device error for +.IR DEVICE . +The +.I DEVICE +is marked as erroneous and no further action is initiated on it. +.PP +. +.SS Misc +.OD help "h" "" +Print usage information, then exit. +.PP +. +.OD version "v" "" +Print version information, then exit. +.PP diff --git a/zpcictl/zpcictl.c b/zpcictl/zpcictl.c new file mode 100644 index 0000000..5f63b17 --- /dev/null +++ b/zpcictl/zpcictl.c @@ -0,0 +1,379 @@ +/* + * zpcictl - Manage PCI devices on z Systems + * + * Copyright IBM Corp. 2018 + * + * 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 +#include +#include +#include +#include + +#include "lib/util_base.h" +#include "lib/util_libc.h" +#include "lib/util_opt.h" +#include "lib/util_path.h" +#include "lib/util_prg.h" +#include "lib/util_proc.h" +#include "lib/util_rec.h" +#include "lib/util_scandir.h" + +#include "zpcictl.h" + +#define SMARTCTL_CMDLINE "smartctl -x %s 2>/dev/null" + +static const struct util_prg prg = { + .desc = "Use zpcictl to manage PCI devices on s390\n" + "DEVICE is the slot id or node of the device (e.g. /dev/nvme0)", + .args = "DEVICE", + .copyright_vec = { + { + .owner = "IBM Corp.", + .pub_first = 2018, + .pub_last = 2018, + }, + UTIL_PRG_COPYRIGHT_END + } +}; + +/* Defines for options with no short command */ +#define OPT_RESET 128 +#define OPT_DECONF 129 +#define OPT_REPORT_ERR 130 + +static struct util_opt opt_vec[] = { + UTIL_OPT_SECTION("ERROR HANDLING"), + { + .option = { "reset", no_argument, NULL, OPT_RESET }, + .desc = "Reset device", + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = { "deconfigure", no_argument, NULL, OPT_DECONF }, + .desc = "De-configure device and prepare for any repair action", + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = { "report-error", no_argument, NULL, OPT_REPORT_ERR }, + .desc = "Report device error to service element (SE)", + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + UTIL_OPT_SECTION("MISC"), + UTIL_OPT_HELP, + UTIL_OPT_VERSION, + UTIL_OPT_END +}; + +static int is_char_dev(const char *dev) +{ + struct stat s; + + if (stat(dev, &s)) + return 0; + + return S_ISCHR(s.st_mode); +} + +static int is_blk_dev(const char *dev) +{ + struct stat s; + + if (stat(dev, &s)) + return 0; + + return S_ISBLK(s.st_mode); +} + +static void fopen_err(char *path) +{ + warnx("Could not open file %s: %s", path, strerror(errno)); + free(path); + exit(EXIT_FAILURE); +} + +#define READ_CHUNK_SIZE 512 + +static char *collect_smart_data(struct zpci_device *pdev) +{ + char *buffer = NULL; + size_t count = 0; + char *cmd; + FILE *fd; + + util_asprintf(&cmd, SMARTCTL_CMDLINE, pdev->device); + fd = popen(cmd, "r"); + if (!fd) + goto out; + + while (!feof(fd)) { + buffer = realloc(buffer, count + READ_CHUNK_SIZE); + if (!buffer) { + warnx("Could not collect S.M.A.R.T. data"); + goto out; + } + count += fread(&buffer[count], 1, READ_CHUNK_SIZE, fd); + if (ferror(fd)) { + free(buffer); + buffer = NULL; + goto out; + } + } + + buffer = realloc(buffer, count); + if (!buffer && count > 0) + warnx("Could not collect S.M.A.R.T. data"); + if (buffer) + buffer[count] = '\0'; + +out: + pclose(fd); + free(cmd); + + return buffer; +} + +static unsigned int sysfs_read_value(struct zpci_device *pdev, const char *attr) +{ + unsigned int val; + char *path; + FILE *fp; + + path = util_path_sysfs("bus/pci/devices/%s/%s", pdev->slot, attr); + fp = fopen(path, "r"); + if (!fp) + fopen_err(path); + fscanf(fp, "%x", &val); + fclose(fp); + free(path); + + return val; +} + +static void sysfs_write_data(struct zpci_report_error *report, char *slot) +{ + char *path; + int fd, rc; + + path = util_path_sysfs("bus/pci/devices/%s/report_error", slot); + fd = open(path, O_WRONLY); + if (!fd) + fopen_err(path); + rc = write(fd, report, sizeof(*report)); + if (rc == -1) + warnx("Could not write to file: %s: %s", path, strerror(errno)); + if (close(fd)) + warnx("Could not close file: %s: %s", path, strerror(errno)); + free(path); +} + +static void sysfs_get_slot_addr(const char *dev, char *slot) +{ + unsigned int major, minor; + struct stat dev_stat; + char addr[13]; + char *path; + FILE *fp; + + if (stat(dev, &dev_stat) != 0) { + errx(EXIT_FAILURE, "Could not get stat information for %s: %s", + dev, strerror(errno)); + } + major = major(dev_stat.st_rdev); + minor = minor(dev_stat.st_rdev); + + path = util_path_sysfs("dev/char/%u:%u/address", major, minor); + fp = fopen(path, "r"); + if (!fp) + fopen_err(path); + fscanf(fp, "%s", addr); + fclose(fp); + free(path); + + strcpy(slot, addr); +} + +static void get_device_node(struct zpci_device *pdev) +{ + struct dirent **de_vec; + char *path, *dev; + char slot[13]; + int count, i; + + path = util_path_sysfs("bus/pci/devices/%s/nvme", pdev->slot); + count = util_scandir(&de_vec, alphasort, path, "nvme*"); + if (count == -1) { + warnx("Could not read directory %s: %s", path, strerror(errno)); + free(path); + exit(EXIT_FAILURE); + } + + for (i = 0; i < count; i++) { + util_asprintf(&dev, "/dev/%s", de_vec[i]->d_name); + sysfs_get_slot_addr(dev, slot); + if (strcmp(slot, pdev->slot) == 0) { + pdev->device = dev; + break; + } + } + + util_scandir_free(de_vec, count); + free(path); +} + +static int device_exists(char *dev) +{ + char *path; + int rc = 0; + + path = util_path_sysfs("bus/pci/devices/%s", dev); + if (util_path_exists(path) || util_path_exists(dev)) + rc = 1; + free(path); + + return rc; +} + +static void get_device_info(struct zpci_device *pdev, char *dev) +{ + if (!device_exists(dev)) + errx(EXIT_FAILURE, "Device %s not found", dev); + if (is_blk_dev(dev)) + errx(EXIT_FAILURE, "Unsupported device type %s", dev); + if (is_char_dev(dev)) { + sysfs_get_slot_addr(dev, pdev->slot); + pdev->device = dev; + } else { + strcpy(pdev->slot, dev); + } + + pdev->class = sysfs_read_value(pdev, "class"); + pdev->fid = sysfs_read_value(pdev, "function_id"); + pdev->pchid = sysfs_read_value(pdev, "pchid"); + + /* In case a slot address was specified, we still need to figure out + * the device node for NVMe devices. Otherwise we won't be able to + * collect S.M.A.R.T. data at a later point. + */ + if (!pdev->device && pdev->class == PCI_CLASS_NVME) + get_device_node(pdev); +} + +/* + * Issue an SCLP Adapter Error Notification event with a specific action + * qualifier. + * + * Collect additional information when possible (e.g. S.M.A.R.T. data for NVMe + * devices). + */ +static void sclp_issue_action(struct zpci_device *pdev, int action) +{ + struct zpci_report_error report = { + .header = { 0 }, + .data = { 0 } + }; + char *sdata = NULL; + + report.header.version = 1; + report.header.action = action; + report.header.length = sizeof(report.data); + report.data.timestamp = (__u64)time(NULL); + report.data.err_log_id = 0x4713; + + if (pdev->class == PCI_CLASS_NVME) + sdata = collect_smart_data(pdev); + if (sdata) { + strncpy(report.data.log_data, sdata, sizeof(report.data.log_data)); + free(sdata); + } + sysfs_write_data(&report, pdev->slot); +} + +/* + * Reset the PCI device and initiate a re-initialization. + */ +static void sclp_reset_device(struct zpci_device *pdev) +{ + sclp_issue_action(pdev, SCLP_ERRNOTIFY_AQ_RESET); +} + +/* + * De-Configure/repair PCI device. Moves the device from configured + * to reserved state. + */ +static void sclp_deconfigure(struct zpci_device *pdev) +{ + sclp_issue_action(pdev, SCLP_ERRNOTIFY_AQ_DECONF); +} + +/* + * Report an error to the SE. + */ +static void sclp_report_error(struct zpci_device *pdev) +{ + sclp_issue_action(pdev, SCLP_ERRNOTIFY_AQ_REPORT_ERR); +} + +static void parse_cmdline(int argc, char *argv[], struct options *opts) +{ + int cmd; + + util_prg_init(&prg); + util_opt_init(opt_vec, NULL); + + do { + cmd = util_opt_getopt_long(argc, argv); + + switch (cmd) { + case OPT_RESET: + opts->reset = 1; + break; + case OPT_DECONF: + opts->deconfigure = 1; + break; + case OPT_REPORT_ERR: + opts->report = 1; + break; + case 'h': + util_prg_print_help(); + util_opt_print_help(); + exit(EXIT_SUCCESS); + case 'v': + util_prg_print_version(); + exit(EXIT_SUCCESS); + case -1: + /* End of options string */ + if (argc == 1) { + errx(EXIT_FAILURE, + "Use '%s --help' for more information", + argv[0]); + } + break; + } + } while (cmd != -1); +} + +int main(int argc, char *argv[]) +{ + struct zpci_device pdev = { 0 }; + struct options opts = { 0 }; + + parse_cmdline(argc, argv, &opts); + + if (optind >= argc) + errx(EXIT_FAILURE, "No device specified"); + + get_device_info(&pdev, argv[optind]); + + if (opts.reset) + sclp_reset_device(&pdev); + else if (opts.deconfigure) + sclp_deconfigure(&pdev); + else if (opts.report) + sclp_report_error(&pdev); + + return 0; +} diff --git a/zpcictl/zpcictl.h b/zpcictl/zpcictl.h new file mode 100644 index 0000000..5187e7c --- /dev/null +++ b/zpcictl/zpcictl.h @@ -0,0 +1,60 @@ +/* + * zpcictl - Manage PCI devices on z Systems + * + * Copyright IBM Corp. 2018 + * + * 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 ZPCICTL_H +#define ZPCICTL_H + +#include +#include "lib/zt_common.h" + +#define SCLP_ERRNOTIFY_AQ_RESET 0 +#define SCLP_ERRNOTIFY_AQ_DECONF 1 +#define SCLP_ERRNOTIFY_AQ_REPORT_ERR 2 + +#define PCI_CLASS_UNCLASSIFIED 0x000000U +#define PCI_CLASS_NVME 0x010802U +#define PCI_CLASS_NETWORK 0x020000U + +struct options { + unsigned int reset; + unsigned int deconfigure; + unsigned int report; +}; + +struct zpci_device { + u16 fid; + u16 pchid; + u32 class; + char slot[13]; + char *device; +}; + +struct zpci_report_error_header { + __u8 version; /* Interface version byte */ + __u8 action; /* Action qualifier byte + * 0: Adapter Reset Request + * 1: Deconfigure and repair action requested + * 2: Informational Report + */ + __u16 length; /* Length of Subsequent Data (up to 4K – SCLP header) */ + __u8 data[0]; /* Subsequent Data passed verbatim to SCLP ET 24 */ +}; + +struct zpci_report_error_data { + __u64 timestamp; + __u64 err_log_id; + char log_data[4054]; /* We cannot exceed a total of 4074 bytes (header + data) */ +}; + +struct zpci_report_error { + struct zpci_report_error_header header; + struct zpci_report_error_data data; +} __packed; + +#endif /* ZPCICTL_H */ -- 2.21.3 From c17b203ee85a63c812654e937f0c5cb3da6229e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 19 Nov 2018 11:35:09 +0100 Subject: [PATCH 08/56] zpcictl: Read device link to obtain device address (#1639220) Description: zpcictl: Read device link to obtain device address Symptom: Issuing zpcictl --report-error /dev/nvme0 leads to: zpcictl: Could not open file /sys/dev/char/249:0/address: No such file or directory Problem: The sysfs attribute 'address' in /sys/dev/char/x:x/ is not present on all kernel versions. Solution: Read the device link using readlink() to obtain the device address instead. Reproduction: For example, run zpcictl --report-error /dev/nvme0 before kernel 4.8. --- zpcictl/zpcictl.8 | 13 +++++---- zpcictl/zpcictl.c | 72 +++++++++++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/zpcictl/zpcictl.8 b/zpcictl/zpcictl.8 index 4f8fcd8..41fab9a 100644 --- a/zpcictl/zpcictl.8 +++ b/zpcictl/zpcictl.8 @@ -1,4 +1,4 @@ -.\" Copyright 2017 IBM Corp. +.\" Copyright IBM Corp. 2018 .\" s390-tools is free software; you can redistribute it and/or modify .\" it under the terms of the MIT license. See LICENSE for details. .\" @@ -30,9 +30,10 @@ zpcictl - Manage PCI devices on z Systems . . .SH DESCRIPTION +With .B zpcictl -is a tool for managing PCI devices on the IBM z Systems platform. It is -especially used for reporting errorneous PCI devices to the service element. +, you can manage PCI devices on the IBM z Systems platform. It is especially +used for reporting erroneous PCI devices to the service element. .B Note: For NVMe devices additional data (such as S.M.A.R.T. data) is collected and sent @@ -44,7 +45,9 @@ for this to work. .SH DEVICE .B DEVICE can be either the PCI slot address (e.g. 0000:00:00.0) or the main device node -of an NVMe device (e.g. /dev/nvme0). +of an NVMe device (e.g. +.I /dev/nvme0 +). . . .SH OPTIONS @@ -52,7 +55,7 @@ of an NVMe device (e.g. /dev/nvme0). .OD reset "" "DEVICE" Reset .I DEVICE -and initiate a re-initialisation of the adapter. +and initiate a re-initialization of the PCI device. .PP . .OD deconfigure "" "DEVICE" diff --git a/zpcictl/zpcictl.c b/zpcictl/zpcictl.c index 5f63b17..a9e38fb 100644 --- a/zpcictl/zpcictl.c +++ b/zpcictl/zpcictl.c @@ -104,6 +104,9 @@ static char *collect_smart_data(struct zpci_device *pdev) char *cmd; FILE *fd; + if (!pdev->device) + return NULL; + util_asprintf(&cmd, SMARTCTL_CMDLINE, pdev->device); fd = popen(cmd, "r"); if (!fd) @@ -155,45 +158,60 @@ static unsigned int sysfs_read_value(struct zpci_device *pdev, const char *attr) static void sysfs_write_data(struct zpci_report_error *report, char *slot) { + size_t r_size; char *path; - int fd, rc; + FILE *fp; + + r_size = sizeof(*report); path = util_path_sysfs("bus/pci/devices/%s/report_error", slot); - fd = open(path, O_WRONLY); - if (!fd) + fp = fopen(path, "w"); + if (!fp) fopen_err(path); - rc = write(fd, report, sizeof(*report)); - if (rc == -1) + if (fwrite(report, 1, r_size, fp) != r_size) warnx("Could not write to file: %s: %s", path, strerror(errno)); - if (close(fd)) + if (fclose(fp)) warnx("Could not close file: %s: %s", path, strerror(errno)); free(path); } -static void sysfs_get_slot_addr(const char *dev, char *slot) +/* lstat() doesn't work for sysfs files, so we have to work with a fixed size */ +#define READLINK_SIZE 256 + +static int sysfs_get_slot_addr(const char *dev, char *slot) { + char device[READLINK_SIZE], *result; unsigned int major, minor; struct stat dev_stat; - char addr[13]; + ssize_t len; char *path; - FILE *fp; if (stat(dev, &dev_stat) != 0) { - errx(EXIT_FAILURE, "Could not get stat information for %s: %s", - dev, strerror(errno)); + warnx("Could not get stat information for %s: %s", + dev, strerror(errno)); + return 0; } major = major(dev_stat.st_rdev); minor = minor(dev_stat.st_rdev); - path = util_path_sysfs("dev/char/%u:%u/address", major, minor); - fp = fopen(path, "r"); - if (!fp) - fopen_err(path); - fscanf(fp, "%s", addr); - fclose(fp); + path = util_path_sysfs("dev/char/%u:%u/device", major, minor); + len = readlink(path, device, READLINK_SIZE - 1); free(path); + if (len != -1) { + device[len] = '\0'; + } else { + warnx("Could not read device link for %s", dev); + return 0; + } + + result = strrchr(device, '/'); + if (result) + result++; + else + result = device; + strcpy(slot, result); - strcpy(slot, addr); + return 1; } static void get_device_node(struct zpci_device *pdev) @@ -208,12 +226,13 @@ static void get_device_node(struct zpci_device *pdev) if (count == -1) { warnx("Could not read directory %s: %s", path, strerror(errno)); free(path); - exit(EXIT_FAILURE); + return; } for (i = 0; i < count; i++) { util_asprintf(&dev, "/dev/%s", de_vec[i]->d_name); - sysfs_get_slot_addr(dev, slot); + if (!sysfs_get_slot_addr(dev, slot)) + continue; if (strcmp(slot, pdev->slot) == 0) { pdev->device = dev; break; @@ -240,11 +259,13 @@ static int device_exists(char *dev) static void get_device_info(struct zpci_device *pdev, char *dev) { if (!device_exists(dev)) - errx(EXIT_FAILURE, "Device %s not found", dev); + errx(EXIT_FAILURE, "Could not find device %s", dev); if (is_blk_dev(dev)) errx(EXIT_FAILURE, "Unsupported device type %s", dev); if (is_char_dev(dev)) { - sysfs_get_slot_addr(dev, pdev->slot); + if (!sysfs_get_slot_addr(dev, pdev->slot)) + errx(EXIT_FAILURE, + "Could not determine slot address for %s", dev); pdev->device = dev; } else { strcpy(pdev->slot, dev); @@ -254,9 +275,10 @@ static void get_device_info(struct zpci_device *pdev, char *dev) pdev->fid = sysfs_read_value(pdev, "function_id"); pdev->pchid = sysfs_read_value(pdev, "pchid"); - /* In case a slot address was specified, we still need to figure out - * the device node for NVMe devices. Otherwise we won't be able to - * collect S.M.A.R.T. data at a later point. + /* + * In case a slot address was specified, the device node for NVMe + * devices is still needed. Otherwise it won't be possible to collect + * S.M.A.R.T. data at a later point. */ if (!pdev->device && pdev->class == PCI_CLASS_NVME) get_device_node(pdev); -- 2.21.3 From 8d8e0a970dbde0ca4486192aed806d837f080370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 19 Nov 2018 11:36:36 +0100 Subject: [PATCH 09/56] zpcictl: Change wording of man-page and help output (#1643451) Description: zpcictl: Change wording of man-page and help output Symptom: - Problem: Wording not clear enough and platform description is wrong. Solution: Change wording and use the correct platform description. Reproduction: - Upstream-ID: aaaebb2030c80151ecac528f22cb9a52752b868c --- zpcictl/zpcictl.8 | 38 +++++++++++++++----------------------- zpcictl/zpcictl.c | 15 ++++++++------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/zpcictl/zpcictl.8 b/zpcictl/zpcictl.8 index 41fab9a..38aa344 100644 --- a/zpcictl/zpcictl.8 +++ b/zpcictl/zpcictl.8 @@ -20,7 +20,7 @@ .TH zpcictl 8 "Oct 2018" s390-tools zpcictl . .SH NAME -zpcictl - Manage PCI devices on z Systems +zpcictl - Manage PCI devices on IBM Z . . .SH SYNOPSIS @@ -30,50 +30,42 @@ zpcictl - Manage PCI devices on z Systems . . .SH DESCRIPTION -With +Use .B zpcictl -, you can manage PCI devices on the IBM z Systems platform. It is especially -used for reporting erroneous PCI devices to the service element. +to manage PCI devices on the IBM Z platform. In particular, +use this command to report defective PCI devices to the service element. .B Note: For NVMe devices additional data (such as S.M.A.R.T. data) is collected and sent -with any error handling action. The smartmontools are required to be installed -for this to work. +with any error handling action. For this extendend data collection, the +smartmontools must be installed. .PP . . .SH DEVICE -.B DEVICE -can be either the PCI slot address (e.g. 0000:00:00.0) or the main device node -of an NVMe device (e.g. +A PCI slot address (e.g. 0000:00:00.0) or the main device node of an NVMe +device (e.g. .I /dev/nvme0 ). . . .SH OPTIONS -.SS Error Handling +.SS Error Handling Options .OD reset "" "DEVICE" -Reset -.I DEVICE -and initiate a re-initialization of the PCI device. +Reset and re-initialize the PCI device. .PP . .OD deconfigure "" "DEVICE" -De-configure -.I DEVICE -and prepare for any repair action. This action will move the -PCI device from a configured to a reserved state. +Deconfigure the PCI device and prepare for any repair action. This action +changes the status of the PCI device from configured to reserved. .PP . .OD report-error "" "DEVICE" -Report any device error for -.IR DEVICE . -The -.I DEVICE -is marked as erroneous and no further action is initiated on it. +Report any device error for the PCI device. +The device is marked as defective but no further action is taken. .PP . -.SS Misc +.SS General Options .OD help "h" "" Print usage information, then exit. .PP diff --git a/zpcictl/zpcictl.c b/zpcictl/zpcictl.c index a9e38fb..7cfa0d3 100644 --- a/zpcictl/zpcictl.c +++ b/zpcictl/zpcictl.c @@ -27,8 +27,9 @@ #define SMARTCTL_CMDLINE "smartctl -x %s 2>/dev/null" static const struct util_prg prg = { - .desc = "Use zpcictl to manage PCI devices on s390\n" - "DEVICE is the slot id or node of the device (e.g. /dev/nvme0)", + .desc = "Use zpcictl to manage PCI devices on IBM Z\n" + "DEVICE is the slot ID or node of the device " + "(e.g. 0000:00:00.0 or /dev/nvme0)", .args = "DEVICE", .copyright_vec = { { @@ -46,23 +47,23 @@ static const struct util_prg prg = { #define OPT_REPORT_ERR 130 static struct util_opt opt_vec[] = { - UTIL_OPT_SECTION("ERROR HANDLING"), + UTIL_OPT_SECTION("ERROR HANDLING OPTIONS"), { .option = { "reset", no_argument, NULL, OPT_RESET }, - .desc = "Reset device", + .desc = "Reset the device", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "deconfigure", no_argument, NULL, OPT_DECONF }, - .desc = "De-configure device and prepare for any repair action", + .desc = "Deconfigure the device to prepare for any repair action", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "report-error", no_argument, NULL, OPT_REPORT_ERR }, - .desc = "Report device error to service element (SE)", + .desc = "Report a device error to the service element (SE)", .flags = UTIL_OPT_FLAG_NOSHORT, }, - UTIL_OPT_SECTION("MISC"), + UTIL_OPT_SECTION("GENERAL OPTIONS"), UTIL_OPT_HELP, UTIL_OPT_VERSION, UTIL_OPT_END -- 2.21.3 From 916022e17d4cb189c0d83028ed09d3f4600046dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 19 Nov 2018 11:37:34 +0100 Subject: [PATCH 10/56] zdev: qeth BridgePort and VNICC attribute conflict (#1643452) Description: zdev: qeth BridgePort and VNICC attribute conflict Symptom: chzdev cannot set VNICC attributes due to a conflict with BridgePort attributes. Problem: Existing conflict checking always assumes a BridgePort and a VNICC attribute are active. Solution: Introduce a function that determines if BridgePort or VNICC attributes are active and use only active attributes for conflict checking. Reproduction: Set VNICC attribute with chzdev w/o active BridgePort attributes. Upstream-ID: ffe91d1b3082730905caee75b6ec8b05e3cf46a3 --- zdev/src/qeth.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/zdev/src/qeth.c b/zdev/src/qeth.c index 46bc23d..6191ad1 100644 --- a/zdev/src/qeth.c +++ b/zdev/src/qeth.c @@ -1171,6 +1171,37 @@ static exit_code_t check_ineffective_settings(struct setting_list *list, return rc; } +/* Check if a possibly conflicting setting is active in the configuration */ +static bool conflict_setting_active(struct setting *s) +{ + enum qeth_attr_group_type t; + + t = get_attr_group_type(s); + if (t != group_bridge && t != group_vnicc) { + /* Check BridgePort and VNICC attributes only */ + return false; + } + if (s->specified) { + /* Specified on the command line: We are strict here and do not + * allow to specify VNICC and BridgePort attributes in the same + * command to avoid issues when attributes are enabled/disabled + * in the wrong order. Example: disable VNICC and enable + * BridgePort in the same command would result in an error + * because BridgePort attributes are set first. + */ + return true; + } + if (attrib_match_default(s->attrib, s->value)) { + /* Not active if set to default value */ + return false; + } + if (s->actual_value && strncmp(s->actual_value, "n/a", 3) == 0) { + /* Not active if in n/a state (conflicting attribute set) */ + return false; + } + return true; +} + /* Check if there are conflicting attribute settings */ static exit_code_t check_conflicting_settings(struct setting_list *list) { @@ -1182,6 +1213,8 @@ static exit_code_t check_conflicting_settings(struct setting_list *list) util_list_iterate(&list->list, s) { if (s->removed) continue; + if (!conflict_setting_active(s)) + continue; t = get_attr_group_type(s); if (t == group_bridge && (!bridge || !bridge->specified)) bridge = s; -- 2.21.3 From e2916bdb493e9565d74b0334fa45f17c6f0f730c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 19 Nov 2018 11:38:30 +0100 Subject: [PATCH 11/56] qethqoat: add OSA-Express7S support (#1644384) Description: qethqoat: add OSA-Express7S support Symptom: qethqoat fails to report HW generation and link speed for OSA-Express7S network card. Problem: Missing identifiers for new values in the QUERY OAT data. Solution: Add identifiers for card generation and link speed. Reproduction: Run qethqoat on an OSA-Express7S, check the output for 'OSA Generation' and 'Port speed/mode'. Upstream-ID: 20145b6d06debd47944bff0a471d17e5eba07010 --- qethqoat/qethqoat.c | 6 ++++++ qethqoat/qethqoat.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/qethqoat/qethqoat.c b/qethqoat/qethqoat.c index 6a30d1d..7110c28 100644 --- a/qethqoat/qethqoat.c +++ b/qethqoat/qethqoat.c @@ -208,6 +208,9 @@ static void print_physical(struct qeth_qoat_physical *phdr) case OAT_OSA_GEN_OSAE6S: osagen = "OSA-Express6S"; break; + case OAT_OSA_GEN_OSAE7S: + osagen = "OSA-Express7S"; + break; default: sprintf(tmp, "unknown (0x%x)", phdr->osa_gen); osagen = tmp; @@ -239,6 +242,9 @@ static void print_physical(struct qeth_qoat_physical *phdr) case OAT_PORT_SPEED_10gbs_full: speed = "10 Gb/s / full duplex"; break; + case OAT_PORT_SPEED_25gbs_full: + speed = "25 Gb/s / full duplex"; + break; case OAT_PORT_SPEED_UNKNOWN: speed = "unknown / unknown"; break; diff --git a/qethqoat/qethqoat.h b/qethqoat/qethqoat.h index dd7e992..e692937 100644 --- a/qethqoat/qethqoat.h +++ b/qethqoat/qethqoat.h @@ -58,6 +58,7 @@ struct qeth_qoat_physical { #define OAT_OSA_GEN_OSAE4S 0x02 #define OAT_OSA_GEN_OSAE5S 0x03 #define OAT_OSA_GEN_OSAE6S 0x04 +#define OAT_OSA_GEN_OSAE7S 0x05 __u8 osa_gen; #define OAT_PORT_SPEED_UNKNOWN 0x00 #define OAT_PORT_SPEED_10mbs_half 0x01 @@ -68,6 +69,7 @@ struct qeth_qoat_physical { #define OAT_PORT_SPEED_1000mbs_full 0x06 #define OAT_PORT_SPEED_NA 0x07 #define OAT_PORT_SPEED_10gbs_full 0x08 +#define OAT_PORT_SPEED_25gbs_full 0x0A __u8 port_speed; #define OAT_PORT_MEDIA_COPPER 0x01 #define OAT_PORT_MEDIA_MULTI_MODE 0x02 -- 2.21.3 From ba7eb5cd9d858f2ff8bd29e0b610337896587b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 19 Nov 2018 11:39:35 +0100 Subject: [PATCH 12/56] zcryptctl: add zcryptctl to manage multiple zcrypt nodes (#1646354) Summary: zcryptctl: add zcryptctl to manage multiple zcrypt nodes Description: There is a new zcrypt kernel feature which provides multiple customizable device nodes for the zcrypt device driver. Here is the userspace part of this which adds a new application zcryptctl for user friendly management of this feature. Upstream-ID: f05f7d656b13c3904f0c55e86ebe9e9b19fcd222 --- zconf/zcrypt/Makefile | 7 +- zconf/zcrypt/zcryptctl.8 | 147 ++++++ zconf/zcrypt/zcryptctl.c | 1030 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1182 insertions(+), 2 deletions(-) create mode 100644 zconf/zcrypt/zcryptctl.8 create mode 100644 zconf/zcrypt/zcryptctl.c diff --git a/zconf/zcrypt/Makefile b/zconf/zcrypt/Makefile index 698e148..d075f34 100644 --- a/zconf/zcrypt/Makefile +++ b/zconf/zcrypt/Makefile @@ -1,21 +1,24 @@ include ../../common.mak -all: chzcrypt lszcrypt +all: chzcrypt lszcrypt zcryptctl libs = $(rootdir)/libutil/libutil.a chzcrypt: chzcrypt.o misc.o $(libs) lszcrypt: lszcrypt.o misc.o $(libs) +zcryptctl: zcryptctl.o misc.o $(libs) install: all $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 chzcrypt $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 lszcrypt $(DESTDIR)$(BINDIR) + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zcryptctl $(DESTDIR)$(BINDIR) $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 644 -c chzcrypt.8 $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 644 -c lszcrypt.8 $(DESTDIR)$(MANDIR)/man8 + $(INSTALL) -m 644 -c zcryptctl.8 $(DESTDIR)$(MANDIR)/man8 clean: - rm -f *.o chzcrypt lszcrypt + rm -f *.o chzcrypt lszcrypt zcryptctl .PHONY: all install clean diff --git a/zconf/zcrypt/zcryptctl.8 b/zconf/zcrypt/zcryptctl.8 new file mode 100644 index 0000000..5d098da --- /dev/null +++ b/zconf/zcrypt/zcryptctl.8 @@ -0,0 +1,147 @@ +.\" zcryptctl.8 +.\" +.\" Copyright 2018 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" +.\" use +.\" groff -man -Tutf8 zcryptctl.8 +.\" or +.\" nroff -man zcryptctl.8 +.\" to process this source +.\" +.TH ZCRYPTCTL 8 "AUG 2018" "s390-tools" +.SH NAME +zcryptctl \- display information and administrate zcrypt multiple device nodes +.SH SYNOPSIS +.TP 8 +.B zcryptctl list +.TP +.B zcryptctl create +.R [ +.I node-name +.R ] +.TP +.B zcryptctl destroy +.I node-name +.TP +.B zcryptctl addap +.R | +.B delap +.I node-name adapter-nr +.TP +.B zcryptctl adddom +.R | +.B deldom +.I node-name domain-nr +.TP +.B zcryptctl addioctl +.R | +.B delioctl +.I node-name ioctl-term +.TP +.B zcryptctl config +.I config-file +.TP +.B zcryptctl listconfig +.SH DESCRIPTION +The +.B zcryptctl +command displays information and maintains the multi device node +extension for the zcrypt device driver. +.P +With the multi device node extension you can create and configure +additional zcrypt device nodes which can be used as alternate device +nodes to access the crypto hardware provided by the zcrypt device +driver. Each zcrypt device node can be restricted in terms of crypto +cards, domains, and available ioctls. Such a device node can be used +as a base for container solutions like Docker to control and restrict +the access to crypto resources. +.SH COMMANDS +.TP 8 +.B zcryptctl list +Show all the additional device nodes that are currently active. +.TP +.B zcryptctl create +.R [ +.I node-name +.R ] +Create a new zcrypt device node. The \fInode-name\fP might be given +and needs to be unique and not in use. If there is no node name +provided, the zcrypt device driver will create a new one with pattern +zcrypt_\fIx\fP, with \fIx\fP being the next free number. Up to 256 +additional device nodes can be created. The newly created additional +device node appears in /dev and has read and write permissions enabled +only for root. By default all adapters, domains and ioctls are +initially disabled on this new device node. +.TP +.B zcryptctl destroy +.I node-name +Destroy an additional zcrypt device node. The device node is only +marked for disposal and destroyed when it is no longer used. +.TP +.B zcryptctl addap +.R | +.B delap +.I node-name adapter-nr +Update the filter for the specified zcrypt device node and add or +delete a crypto adapter to be accessible via this node. The symbol +\fBALL\fP can be used to enable or disable all adapters. +.TP +.B zcryptctl adddom +.R | +.B deldom +.I node-name domain-nr +Update the filter for the specified zcrypt device node and add or +delete a domain to be accessible through this node. The symbol +\fBALL\fP can be used to enable or disable all domains. +.TP +.B zcryptctl addioctl +.R | +.B delioctl +.I node-name ioctl-term +Update the filter for the specified zcrypt device node and add or +delete an ioctl. The ioctl might be specified as symbolic string (one +of \fBICARSAMODEXPO\fP, \fBICARSACRT\fP, \fBZSECSENDCPRB\fP, +\fBZSENDEP11CPRB\fP, \fBZCRYPT_DEVICE_STATUS\fP, +\fBZCRYPT_STATUS_MASK\fP, \fBZCRYPT_QDEPTH_MASK\fP, +\fBZCRYPT_PERDEV_REQCNT\fP) or numeric value in the range 0-255 and +the symbol \fBALL\fP can be used to include all ioctls. +.TP +.B zcryptctl config +.I config-file +Process a config file. The given configuration file is read line by +line and the settings are applied. Syntax is simple: +.RS +.IP "node=" +.IP "aps=" +.IP "doms=" +.IP "ioctls=" +.LP +Empty lines are ignored and the '#' marks the rest of the +line as comment. +.LP +The \fBnode=\fP line creates a new zcrypt device node, the \fBaps=\fP, +\fBdoms=\fP and \fBioctls=\fP lines customize the previously created +node. The symbol \fBALL\fP is also recognized for aps, doms, and +ioctls. +.LP +Each action must fit into one line, spreading over multiple lines is +not supported. But you can use more than one \fBaps=\fP, \fBdoms=\fP +and \fBioctls=\fP lines to customize the very same node. +.LP +Processing stops when a line cannot be parsed or the current action +fails. In this case the exit status is non zero but the successful +actions until the failure occurs are not rolled back. +.RE +.TP +.B zcryptctl listconfig +List the current configuration in a form suitable for input to the +\fBzcryptctl config\fP command. +.LP +.SH EXIT STATUS +On successful completion of the command the exit status is 0. A non +zero return code (and some kind of failure message) is emitted if the +processing could not complete successful. +.SH SEE ALSO +\fBlszcrypt\fR(8) diff --git a/zconf/zcrypt/zcryptctl.c b/zconf/zcrypt/zcryptctl.c new file mode 100644 index 0000000..8326a08 --- /dev/null +++ b/zconf/zcrypt/zcryptctl.c @@ -0,0 +1,1030 @@ +/* + * zcryptctl - Maintain zcrypt multi device nodes. + * + * by Harald Freudenberger + * Copyright IBM Corp. 2018 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_base.h" +#include "lib/util_file.h" +#include "lib/util_opt.h" +#include "lib/util_panic.h" +#include "lib/util_path.h" +#include "lib/util_prg.h" +#include "lib/util_proc.h" +#include "lib/util_rec.h" +#include "lib/util_scandir.h" +#include "lib/zt_common.h" + +#define MAX_ZDEV_IOCTLS 256 +#define ZCRYPT_NAME "zcrypt" +#define MAX_ZDEV_CARDIDS_EXT 256 +#define MAX_ZDEV_DOMAINS_EXT 256 +#define ZCRYPTDEVICE "/dev/z90crypt" +#define _UNUSED_ __attribute__((unused)) + +/* + * Currently known commands + */ +#define CMD_LIST 0x0001 +#define CMD_CREATE 0x0002 +#define CMD_DESTROY 0x0003 +#define CMD_ADD_AP 0x0004 +#define CMD_DEL_AP 0x0005 +#define CMD_ADD_DOM 0x0006 +#define CMD_DEL_DOM 0x0007 +#define CMD_ADD_IOCTL 0x0008 +#define CMD_DEL_IOCTL 0x0009 +#define CMD_CONFIG 0x000A +#define CMD_LISTCONFIG 0x000B + +/* + * Program configuration + */ +static const struct util_prg prg = { + .args = "", + .command_args = "COMMAND [COMMAND-PARAMS]", + .desc = "Display and administrate zcrypt multiple device nodes.", + .copyright_vec = { + { + .owner = "IBM Corp.", + .pub_first = 2018, + .pub_last = 2018, + }, + UTIL_PRG_COPYRIGHT_END + } +}; + +static struct util_opt opt_vec[] = { + UTIL_OPT_HELP, + UTIL_OPT_VERSION, + UTIL_OPT_END +}; + +/* + * List of currently known and supported ioctls + */ +static struct zcryptctl_ioctls_s { + int nr; + const char *name; +} zcryptctl_ioctls[] = { + { + .name = "ICARSAMODEXPO", + .nr = 0x05, + }, + { + .name = "ICARSACRT", + .nr = 0x06, + }, + { + .name = "ZSECSENDCPRB", + .nr = 0x81, + }, + { + .name = "ZSENDEP11CPRB", + .nr = 0x04, + }, + { + .name = "ZCRYPT_DEVICE_STATUS", + .nr = 0x5f, + }, + { + .name = "ZCRYPT_STATUS_MASK", + .nr = 0x58, + }, + { + .name = "ZCRYPT_QDEPTH_MASK", + .nr = 0x59, + }, + { + .name = "ZCRYPT_PERDEV_REQCNT", + .nr = 0x5a, + }, + { + .name = NULL, + .nr = 0, + }, +}; + +static int ioctlstr2value(const char *str) +{ + int i; + + for (i = 0; zcryptctl_ioctls[i].name; i++) + if (strcasecmp(str, zcryptctl_ioctls[i].name) == 0) + return zcryptctl_ioctls[i].nr; + + return -1; +} + +static const char *value2ioctlstr(int value) +{ + int i; + + for (i = 0; zcryptctl_ioctls[i].name; i++) + if (value == zcryptctl_ioctls[i].nr) + return zcryptctl_ioctls[i].name; + + return NULL; +} + +static int check_nodename(const char *nodename) +{ + struct stat sb; + const char *node; + char pathname[PATH_MAX]; + + node = strrchr(nodename, '/'); + node = node ? node + 1 : nodename; + snprintf(pathname, sizeof(pathname), "/dev/%s", node); + pathname[sizeof(pathname) - 1] = '\0'; + if (stat(pathname, &sb) != 0) + return -1; + if (!S_ISCHR(sb.st_mode)) + return -2; + + return 0; +} + +static int check_zcrypt_class_dir(void) +{ + int rc = 0; + char *afile; + + afile = util_path_sysfs("class/%s", ZCRYPT_NAME); + if (!util_path_is_dir(afile)) + rc = -1; + + free(afile); + return rc; +} + +static int fetch_major_minor(const char *nodename, int *major, int *minor) +{ + FILE *f; + int rc = 0; + char *afile; + const char *node; + + node = strrchr(nodename, '/'); + node = node ? node + 1 : nodename; + afile = util_path_sysfs("class/%s/%s/dev", ZCRYPT_NAME, node); + f = fopen(afile, "r"); + if (!f) { + rc = -1; + goto out; + } + if (fscanf(f, "%i:%i", major, minor) != 2) { + fclose(f); + rc = -2; + goto out; + } + fclose(f); + +out: + free(afile); + return rc; +} + +static int write_dn_attr(const char *nodename, const char *attr, + const char *value) +{ + FILE *f; + int rc = 0; + char *afile; + const char *node; + + if (nodename) { + node = strrchr(nodename, '/'); + node = node ? node + 1 : nodename; + afile = util_path_sysfs("class/%s/%s/%s", + ZCRYPT_NAME, node, attr); + } else + afile = util_path_sysfs("class/%s/%s", ZCRYPT_NAME, attr); + f = fopen(afile, "w"); + if (!f) { + rc = -1; + goto out; + } + if (fprintf(f, "%s\n", value) < 0) { + fclose(f); + rc = -2; + goto out; + } + fflush(f); + if (ferror(f)) { + fclose(f); + rc = -2; + goto out; + } + + fclose(f); + +out: + free(afile); + return rc; +} + +static int read_dn_attr(const char *nodename, const char *attr, + char *value, int valuelen) +{ + int rc; + FILE *f; + char *afile; + const char *node; + + node = strrchr(nodename, '/'); + node = node ? node + 1 : nodename; + afile = util_path_sysfs("class/%s/%s/%s", ZCRYPT_NAME, node, attr); + f = fopen(afile, "r"); + if (!f) { + rc = -1; + goto out; + } + value = fgets(value, valuelen, f); + fclose(f); + rc = value ? 0 : -2; + +out: + free(afile); + return rc; +} + +static int test_bit(int n, const char *hexbytestr) +{ + char c; + int v, i = 0; + + if (strncmp(hexbytestr, "0x", 2) == 0) + i += 2; + 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 + errx(EXIT_FAILURE, + "Could not parse hex digit '%c'", c); + + return v & (1 << (3 - (n % 4))); +} + +static int cmd_list(int cmd, + const char *node _UNUSED_, + const char *arg _UNUSED_) +{ + DIR *dir; + char *dirname; + const char *p; + struct dirent *de; + int i, n, major, minor, count = 0; + char buf[80], tab = (cmd == CMD_LISTCONFIG ? ' ' : '\t'); + + dirname = util_path_sysfs("class/%s", ZCRYPT_NAME); + dir = opendir(dirname); + if (!dir) + errx(EXIT_FAILURE, + "Could not read directory '%s' errno=%d (%s)", + dirname, errno, strerror(errno)); + while ((de = readdir(dir)) != NULL) { + if (de->d_name[0] == '.' || de->d_type == DT_REG) + continue; + if (fetch_major_minor(de->d_name, &major, &minor) != 0) + errx(EXIT_FAILURE, + "Could not fetch major/minor from sysfs for zcrypt node '%s'", + de->d_name); + if (cmd == CMD_LISTCONFIG) { + printf("node = %s\n", de->d_name); + printf(" aps ="); + } else { + printf("zcrypt node name:\t%s\n", de->d_name); + printf(" device node:\t/dev/%s\n", de->d_name); + printf(" major:minor:\t%d:%d\n", major, minor); + printf(" adapter:"); + } + if (read_dn_attr(de->d_name, "apmask", buf, sizeof(buf)) != 0) + errx(EXIT_FAILURE, + "Could not fetch apmask attribute from sysfs for zcrypt node '%s'", + de->d_name); + for (i = n = 0; i < MAX_ZDEV_CARDIDS_EXT; i++) + if (test_bit(i, buf)) + printf("%c%d", n++ == 0 ? tab : ',', i); + putchar('\n'); + if (cmd == CMD_LISTCONFIG) + printf(" doms ="); + else + printf(" domains:"); + if (read_dn_attr(de->d_name, "aqmask", buf, sizeof(buf)) != 0) + errx(EXIT_FAILURE, + "Could not fetch aqmask attribute from sysfs for zcrypt node '%s'", + de->d_name); + for (i = n = 0; i < MAX_ZDEV_DOMAINS_EXT; i++) + if (test_bit(i, buf)) + printf("%c%d", n++ == 0 ? tab : ',', i); + putchar('\n'); + if (cmd == CMD_LISTCONFIG) + printf(" ioctls ="); + else + printf(" ioctls:"); + if (read_dn_attr(de->d_name, "ioctlmask", + buf, sizeof(buf)) != 0) + errx(EXIT_FAILURE, + "Could not fetch ioctlmask attribute from sysfs for zcrypt node '%s'", + de->d_name); + for (i = n = 0; i < MAX_ZDEV_IOCTLS; i++) { + if (test_bit(i, buf)) { + p = value2ioctlstr(i); + if (p) + printf("%c%s", + n++ == 0 ? tab : ',', p); + else + printf("%c%d", + n++ == 0 ? tab : ',', i); + } + } + putchar('\n'); + count++; + } + closedir(dir); + + if (count == 0) + printf("No additional zcrypt device nodes defined\n"); + + return 0; +} + +static int cmd_create(int cmd _UNUSED_, + const char *nodename, + const char *arg _UNUSED_) +{ + int rc; + const char *node; + char buf[PATH_MAX]; + + if (nodename) { + node = strrchr(nodename, '/'); + node = node ? node + 1 : nodename; + strncpy(buf, node, sizeof(buf) - 1); + } else + strncpy(buf, "\n", sizeof(buf) - 1); + buf[sizeof(buf) - 1] = 0; + + rc = write_dn_attr(NULL, "create", buf); + if (rc != 0) + errx(EXIT_FAILURE, + "Could not write into sysfs entry to create zdev node"); + + printf("Device node created\n"); + + return 0; +} + +static int cmd_destroy(int cmd _UNUSED_, + const char *nodename, + const char *arg _UNUSED_) +{ + int rc; + struct stat sb; + const char *node; + char pathname[PATH_MAX]; + + node = strrchr(nodename, '/'); + node = node ? node + 1 : nodename; + snprintf(pathname, sizeof(pathname), "/dev/%s", node); + pathname[sizeof(pathname) - 1] = '\0'; + rc = stat(pathname, &sb); + if (rc != 0) + errx(EXIT_FAILURE, + "Could not check status for '%s'", pathname); + if (!S_ISCHR(sb.st_mode)) + errx(EXIT_FAILURE, + "File '%s' is not a character device node", pathname); + + rc = write_dn_attr(NULL, "destroy", node); + if (rc != 0) + errx(EXIT_FAILURE, + "Could not write into sysfs entry to destroy zdev node '%s'", + node); + + printf("Device node '%s' marked for destruction\n", node); + + return 0; +} + +static void add_del_ap(int cmd, const char *node, int ap) +{ + int rc; + char buf[PATH_MAX]; + + if (cmd == CMD_ADD_AP) + sprintf(buf, "+%d", ap); + else + sprintf(buf, "-%d", ap); + rc = write_dn_attr(node, "apmask", buf); + if (rc != 0) + errx(EXIT_FAILURE, + "Could not write into sysfs entry to %s adapter %d for zdev node '%s'", + cmd == CMD_ADD_AP ? "add" : "remove", ap, node); +} + +static int cmd_add_del_ap(int cmd, const char *node, const char *arg) +{ + int ap, all = 0; + + if (strcasecmp(arg, "ALL") == 0) { + all = 1; + } else { + if (sscanf(arg, "%i", &ap) != 1) + errx(EXIT_FAILURE, + "Invalid adapter argument '%s'", arg); + if (ap < 0 || ap >= MAX_ZDEV_CARDIDS_EXT) + errx(EXIT_FAILURE, + "Adapter argument '%s' out of range [0..%d]", + arg, MAX_ZDEV_CARDIDS_EXT - 1); + } + + if (!all) { + add_del_ap(cmd, node, ap); + printf("Adapter %d %s\n", ap, + (cmd == CMD_ADD_AP ? "added" : "removed")); + } else { + for (ap = 0; ap < MAX_ZDEV_CARDIDS_EXT; ap++) + add_del_ap(cmd, node, ap); + printf("All adapters %s\n", + (cmd == CMD_ADD_AP ? "added" : "removed")); + } + + return 0; +} + +static void add_del_dom(int cmd, const char *node, int dom) +{ + int rc; + char buf[PATH_MAX]; + + if (cmd == CMD_ADD_DOM) + sprintf(buf, "+%d", dom); + else + sprintf(buf, "-%d", dom); + rc = write_dn_attr(node, "aqmask", buf); + if (rc != 0) + errx(EXIT_FAILURE, + "Could not write into sysfs entry to %s domain %d for zdev node '%s'", + cmd == CMD_ADD_DOM ? "add" : "remove", dom, node); +} + +static int cmd_add_del_dom(int cmd, const char *node, const char *arg) +{ + int dom, all = 0; + + if (strcasecmp(arg, "ALL") == 0) { + all = 1; + } else { + if (sscanf(arg, "%i", &dom) != 1) + errx(EXIT_FAILURE, + "Invalid domain argument '%s'", arg); + if (dom < 0 || dom >= MAX_ZDEV_DOMAINS_EXT) + errx(EXIT_FAILURE, + "Domain argument '%s' out of range [0..%d]", + arg, MAX_ZDEV_DOMAINS_EXT - 1); + } + + if (!all) { + add_del_dom(cmd, node, dom); + printf("Domain %d %s\n", dom, + (cmd == CMD_ADD_DOM ? "added" : "removed")); + } else { + for (dom = 0; dom < MAX_ZDEV_DOMAINS_EXT; dom++) + add_del_dom(cmd, node, dom); + printf("All domains %s\n", + (cmd == CMD_ADD_DOM ? "added" : "removed")); + } + + return 0; +} + +static void add_del_ioctl(int cmd, const char *node, int ioctlnr) +{ + int rc; + char buf[PATH_MAX]; + + if (cmd == CMD_ADD_IOCTL) + sprintf(buf, "+%d", ioctlnr); + else + sprintf(buf, "-%d", ioctlnr); + rc = write_dn_attr(node, "ioctlmask", buf); + if (rc != 0) + errx(EXIT_FAILURE, + "Could not write into sysfs entry to %s ioctl %d for zdev node '%s'", + cmd == CMD_ADD_IOCTL ? "add" : "remove", ioctlnr, node); +} + +static int cmd_add_del_ioctl(int cmd, const char *node, const char *arg) +{ + int ioctlnr, all = 0; + + if (strcasecmp(arg, "ALL") == 0) { + all = 1; + } else { + ioctlnr = ioctlstr2value(arg); + if (ioctlnr < 0) + if (sscanf(arg, "%i", &ioctlnr) != 1) + errx(EXIT_FAILURE, + "Invalid ioctl argument '%s'", arg); + if (ioctlnr < 0 || ioctlnr >= MAX_ZDEV_IOCTLS) + errx(EXIT_FAILURE, + "Ioctl argument '%s' out of range [0..%d]", + arg, MAX_ZDEV_IOCTLS - 1); + } + + if (!all) { + add_del_ioctl(cmd, node, ioctlnr); + printf("Ioctl %s %s\n", arg, + (cmd == CMD_ADD_IOCTL ? "added" : "removed")); + } else { + for (ioctlnr = 0; ioctlnr < MAX_ZDEV_IOCTLS; ioctlnr++) + add_del_ioctl(cmd, node, ioctlnr); + printf("All Ioctls %s\n", + (cmd == CMD_ADD_IOCTL ? "added" : "removed")); + } + + return 0; +} + +static int _match_keyword(char **p, const char *keyword) +{ + int n = strlen(keyword); + + if (strncmp(*p, keyword, n) == 0) { + *p += n; + return n; + } + + return 0; +} + +static int _match_character(char **p, char c) +{ + char *q = *p; + + while (isblank(*q)) + q++; + if (*q != c) + return 0; + q++; + while (isblank(*q)) + q++; + *p = q; + + return 1; +} + +static int _match_string(char **p, char *buf) +{ + int n = 0; + char *q = *p; + + while (isblank(*q)) + q++; + while (*q && *q != ',' && !isspace(*q)) { + buf[n++] = *q; + q++; + } + while (isblank(*q)) + q++; + + if (n > 0) { + buf[n] = '\0'; + *p = q; + } + + return n; +} + +static int cmd_config(int cmd _UNUSED_, + const char *nodename _UNUSED_, + const char *arg) +{ + ssize_t n; + size_t linesize = 0; + int nr = 0, havenode = 0; + FILE *f = fopen(arg, "r"); + char *p, *line = NULL, node[128], buf[128]; + + if (!f) + errx(EXIT_FAILURE, + "Could not open file '%s'", arg); + + while ((n = getline(&line, &linesize, f)) != -1) { + nr++; + p = line; + while (isspace(*p)) + p++; + if (*p == '\0' || *p == '#') + continue; + if (_match_keyword(&p, "node")) { + if (!_match_character(&p, '=')) + errx(EXIT_FAILURE, + "Missing '=' at '%-8.8s...' in line %d '%s'", + p, nr, line); + if (!_match_string(&p, node)) + errx(EXIT_FAILURE, + "Missing node name at '%-8.8s...' in line %d '%s'", + p, nr, line); + cmd_create(CMD_CREATE, node, NULL); + havenode = 1; + } else if (_match_keyword(&p, "aps")) { + if (!havenode) + errx(EXIT_FAILURE, + "Missing node=... before processing any aps=... statements in line %d '%s'", + nr, line); + if (!_match_character(&p, '=')) + errx(EXIT_FAILURE, + "Missing '=' at '%-8.8s...' in line %d '%s'", + p, nr, line); + while (1) { + while (isspace(*p)) + p++; + if (*p == '\0' || *p == '#') + break; + if (!_match_string(&p, buf)) + errx(EXIT_FAILURE, + "Missing argument(s) for aps=... at '%-8.8s...' in line %d '%s'", + p, nr, line); + cmd_add_del_ap(CMD_ADD_AP, node, buf); + while (isblank(*p) || *p == ',') + p++; + } + } else if (_match_keyword(&p, "doms")) { + if (!havenode) + errx(EXIT_FAILURE, + "Missing node=... before processing any doms=... statements in line %d '%s'", + nr, line); + if (!_match_character(&p, '=')) + errx(EXIT_FAILURE, + "Missing '=' at '%-8.8s...' in line %d '%s'", + p, nr, line); + while (1) { + while (isspace(*p)) + p++; + if (*p == '\0' || *p == '#') + break; + if (!_match_string(&p, buf)) + errx(EXIT_FAILURE, + "Missing argument(s) for aps=... at '%-8.8s...' in line %d '%s'", + p, nr, line); + cmd_add_del_dom(CMD_ADD_DOM, node, buf); + while (isblank(*p) || *p == ',') + p++; + } + } else if (_match_keyword(&p, "ioctls")) { + if (!havenode) + errx(EXIT_FAILURE, + "Missing node=... before processing any ioctls=... statements in line %d '%s'", + nr, line); + if (!_match_character(&p, '=')) + errx(EXIT_FAILURE, + "Missing '=' at '%-8.8s...' in line %d '%s'", + p, nr, line); + while (1) { + while (isspace(*p)) + p++; + if (*p == '\0' || *p == '#') + break; + if (!_match_string(&p, buf)) + errx(EXIT_FAILURE, + "Missing argument(s) for aps=... at '%-8.8s...' in line %d '%s'", + p, nr, line); + cmd_add_del_ioctl(CMD_ADD_IOCTL, node, buf); + while (isblank(*p) || *p == ',') + p++; + } + } else + errx(EXIT_FAILURE, + "Unknown keyword '%-8.8s...' in line %d '%s'", + p, nr, line); + } + + free(line); + fclose(f); + + return 0; +} + +static struct zcryptctl_cmds_s { + int cmd; + const char *usage; + const char *command; + const char *description; + int (*function)(int cmd, const char *node, const char *arg); +} zcryptctl_cmds[] = { + { + .cmd = CMD_LIST, + .command = "list", + .function = cmd_list, + .usage = "zcryptctl list", + .description = + "List all currently known additional zcrypt device nodes.", + }, + { + .cmd = CMD_CREATE, + .command = "create", + .function = cmd_create, + .usage = "zcryptctl create [nodename]", + .description = + "Create a new zcrypt device node.\n" + "The node-name might be given and needs to be unique and not\n" + "in use. If there is no node name provided, the zcrypt device\n" + "driver will create a new one with pattern zcrypt_\n" + "with being the next free number. By default all\n" + "adapters, domains and ioctls are initially disabled on this\n" + "new device node." + }, + { + .cmd = CMD_DESTROY, + .command = "destroy", + .function = cmd_destroy, + .usage = "zcryptctl destroy ", + .description = + "Destroy an additional zcrypt device node.\n" + "Mark the given zcrypt device node as disposable. The removal\n" + "will take place when it is no longer used.", + }, + { + .cmd = CMD_ADD_AP, + .command = "addap", + .function = cmd_add_del_ap, + .usage = "zcryptctl addap ", + .description = + "Update the filter for the specified zcrypt device node and\n" + "add an crypto adapter to be accessible via this node. The\n" + "adapter argument may be a number in the range 0-255 or the\n" + "symbol ALL.", + }, + { + .cmd = CMD_DEL_AP, + .command = "delap", + .function = cmd_add_del_ap, + .usage = "zcryptctl delap ", + .description = + "Update the filter for the specified zcrypt device node and\n" + "remove a crypto adapter from the allowed adapters list. The\n" + "adapter argument may be a number in the range 0-255 or the\n" + "symbol ALL.", + }, + { + .cmd = CMD_ADD_DOM, + .command = "adddom", + .function = cmd_add_del_dom, + .usage = "zcryptctl adddom ", + .description = + "Update the filter for the specified zcrypt device node and\n" + "add a crypto domain to be accessible via this node. The\n" + "domain argument may be a number in the range 0-255 or the\n" + "symbol ALL.", + }, + { + .cmd = CMD_DEL_DOM, + .command = "deldom", + .function = cmd_add_del_dom, + .usage = "zcryptctl deldom ", + .description = + "Update the filter for the specified zcrypt device node and\n" + "remove a crypto domain from the allowed domains list. The\n" + "domain argument may be a number in the range 0-255 or the\n" + "symbol ALL.", + }, + { + .cmd = CMD_ADD_IOCTL, + .command = "addioctl", + .function = cmd_add_del_ioctl, + .usage = "zcryptctl addioctl ", + .description = + "Update the filter for the specified zcrypt device node and\n" + "add an ioctl number to be accessible via this node. The\n" + "ioctlexp argument may be one of symbols ICARSAMODEXPO,\n" + "ICARSACRT, ZSECSENDCPRB, ZSENDEP11CPRB, ZCRYPT_DEVICE_STATUS\n" + "ZCRYPT_STATUS_MASK, ZCRYPT_QDEPTH_MASK, ZCRYPT_PERDEV_REQCNT\n" + "or a number in the range 0-255 or the symbol ALL.", + }, + { + .cmd = CMD_DEL_IOCTL, + .command = "delioctl", + .function = cmd_add_del_ioctl, + .usage = "zcryptctl delioctl ", + .description = + "Update the filter for the specified zcrypt device node and\n" + "remove an ioctl number from the allowed ioctls list. The\n" + "ioctlexp argument may be one of symbols ICARSAMODEXPO,\n" + "ICARSACRT, ZSECSENDCPRB, ZSENDEP11CPRB, ZCRYPT_DEVICE_STATUS\n" + "ZCRYPT_STATUS_MASK, ZCRYPT_QDEPTH_MASK, ZCRYPT_PERDEV_REQCNT\n" + "or a number in the range 0-255 or the symbol ALL.", + }, + { + .cmd = CMD_CONFIG, + .command = "config", + .function = cmd_config, + .usage = "zcryptctl config ", + .description = + "Process a config file. The given config file is read line by\n" + "line and the settings are applied. Syntax is simple:\n" + " node=\n" + " aps=\n" + " doms=\n" + " ioctls=\n" + "Empty lines are ignored and the '#' marks the rest of the\n" + "line as comment.\n" + "The node= line creates a new zcrypt device node, the\n" + "aps=, doms= and ioctls= lines customize the previously\n" + "created node. The symbol ALL is also recognized for aps,\n" + "doms, and ioctls.\n" + "Each action must fit into one line, spreading over multiple\n" + "lines is not supported. But you can use more than one\n" + "aps=, doms= and ioctls= lines to customize the very same\n" + "node.\n" + "Processing stops when a line cannot be parsed or the\n" + "current action fails. When the config file has been\n" + "processed successful, the zcryptctl return code is 0. A non\n" + "zero return code (and some kind of failure message) is\n" + "emitted on partial completion.", + }, + { + .cmd = CMD_LISTCONFIG, + .command = "listconfig", + .function = cmd_list, + .usage = "zcryptctl listconfig", + .description = + "List all currently known additional zcrypt device nodes\n" + "in a format suitable for the 'config' command.", + }, + { + .command = NULL, + .cmd = 0, + } +}; + +static int get_command_index(const char *cmdstr) +{ + int i; + + for (i = 0; zcryptctl_cmds[i].command; i++) + if (!strcmp(zcryptctl_cmds[i].command, cmdstr)) + return i; + + return -1; +} + +static void commands_print_help(void) +{ + int i; + + for (i = 0; zcryptctl_cmds[i].command; i++) + if (zcryptctl_cmds[i].usage) + printf(" %s\n", zcryptctl_cmds[i].usage); +} + +int main(int argc, char *argv[]) +{ + int c, cmdindex = -1; + int rc = EXIT_SUCCESS; + + util_prg_init(&prg); + util_opt_init(opt_vec, NULL); + + for (c = 1; c < argc; c++) { + cmdindex = get_command_index(argv[c]); + if (cmdindex >= 0) + break; + } + + while (1) { + c = util_opt_getopt_long(argc, argv); + if (c == -1) + break; + switch (c) { + case 'h': + if (cmdindex < 0) { + util_prg_print_help(); + commands_print_help(); + util_opt_print_help(); + } else { + printf("Usage: %s\n", + zcryptctl_cmds[cmdindex].usage); + printf("%s\n", + zcryptctl_cmds[cmdindex].description); + } + return EXIT_SUCCESS; + case 'v': + util_prg_print_version(); + return EXIT_SUCCESS; + default: + util_opt_print_parse_error(c, argv); + return EXIT_FAILURE; + } + } + + if (cmdindex < 0) + errx(EXIT_FAILURE, "Missing or invalid command argument"); + + if (check_zcrypt_class_dir() != 0) + errx(EXIT_FAILURE, + "Directory class/%s is missing in sysfs.\n" + "Multiple zcrypt node support is not available", + ZCRYPT_NAME); + + c = zcryptctl_cmds[cmdindex].cmd; + switch (c) { + case CMD_LIST: + case CMD_LISTCONFIG: + rc = zcryptctl_cmds[cmdindex].function(c, NULL, NULL); + break; + case CMD_CREATE: + rc = zcryptctl_cmds[cmdindex].function(c, + optind + 1 < argc ? + argv[optind + 1] : NULL, + NULL); + break; + case CMD_DESTROY: + if (optind + 1 >= argc) + errx(EXIT_FAILURE, "Missing node name argument"); + if (check_nodename(argv[optind + 1]) != 0) + errx(EXIT_FAILURE, "Invalid or unknown nodename '%s'", + argv[optind + 1]); + rc = zcryptctl_cmds[cmdindex].function(c, + argv[optind + 1], NULL); + break; + case CMD_ADD_AP: + case CMD_DEL_AP: + if (optind + 1 >= argc) + errx(EXIT_FAILURE, "Missing node name argument"); + if (optind + 2 >= argc) + errx(EXIT_FAILURE, "Missing adapter argument"); + if (check_nodename(argv[optind + 1]) != 0) + errx(EXIT_FAILURE, "Invalid or unknown nodename '%s'", + argv[optind + 1]); + rc = zcryptctl_cmds[cmdindex].function(c, + argv[optind + 1], + argv[optind + 2]); + break; + case CMD_ADD_DOM: + case CMD_DEL_DOM: + if (optind + 1 >= argc) + errx(EXIT_FAILURE, "Missing node name argument"); + if (optind + 2 >= argc) + errx(EXIT_FAILURE, "Missing domain argument"); + if (check_nodename(argv[optind + 1]) != 0) + errx(EXIT_FAILURE, "Invalid or unknown nodename '%s'", + argv[optind + 1]); + rc = zcryptctl_cmds[cmdindex].function(c, + argv[optind + 1], + argv[optind + 2]); + break; + case CMD_ADD_IOCTL: + case CMD_DEL_IOCTL: + if (optind + 1 >= argc) + errx(EXIT_FAILURE, "Missing node name argument"); + if (optind + 2 >= argc) + errx(EXIT_FAILURE, "Missing ioctl argument"); + if (check_nodename(argv[optind + 1]) != 0) + errx(EXIT_FAILURE, "Invalid or unknown nodename '%s'", + argv[optind + 1]); + rc = zcryptctl_cmds[cmdindex].function(c, + argv[optind + 1], + argv[optind + 2]); + break; + case CMD_CONFIG: + if (optind + 1 >= argc) + errx(EXIT_FAILURE, "Missing filename argument"); + rc = zcryptctl_cmds[cmdindex].function(c, NULL, + argv[optind + 1]); + break; + default: + errx(EXIT_FAILURE, "Unknown command %d", c); + } + + return rc; +} -- 2.21.3 From d5aab30ab6dd79a47d821b39e9f803d4afe8ed7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 19 Nov 2018 11:40:35 +0100 Subject: [PATCH 13/56] lszcrypt: support for alternate zcrypt device drivers (#1646355) Summary: lszcrypt: support for alternate zcrypt device drivers Description: With kernel 4.19 there comes an extension to the existing AP bus which supports alternate zcrypt drivers. For details about this see kernel patch "s390/zcrypt: AP bus support for alternate driver(s)". So now lszcrypt displays the driver name in verbose mode. As some of the information displayed by lszcrypt was based on sysfs attributes, which are only available when the default zcrypt driver is bound to the device, this also needed some rework. If a sysfs attribute is not available because of an alternate driver binding (or no driver) a question mark is printed into the field. Upstream-ID: 0a0b4c382693cded5652404e8fa2c0e483aa33df --- zconf/zcrypt/lszcrypt.8 | 4 +- zconf/zcrypt/lszcrypt.c | 163 +++++++++++++++++++++++++++------------- 2 files changed, 112 insertions(+), 55 deletions(-) diff --git a/zconf/zcrypt/lszcrypt.8 b/zconf/zcrypt/lszcrypt.8 index 7196806..826e109 100644 --- a/zconf/zcrypt/lszcrypt.8 +++ b/zconf/zcrypt/lszcrypt.8 @@ -54,8 +54,8 @@ status. .B -V, --verbose The verbose level for cryptographic device information. With this verbose level additional information like hardware card type, -hardware queue depth, pending request queue count, outstanding -request queue count, and installed function facilities are displayed. +hardware queue depth, pending requests count, installed function +facilities and driver binding is displayed. .TP 8 .B Specifies a cryptographic device to display. A cryptographic device can be diff --git a/zconf/zcrypt/lszcrypt.c b/zconf/zcrypt/lszcrypt.c index eb3cd6e..580407f 100644 --- a/zconf/zcrypt/lszcrypt.c +++ b/zconf/zcrypt/lszcrypt.c @@ -1,7 +1,7 @@ /** * lszcrypt - Display zcrypt devices and configuration settings * - * Copyright IBM Corp. 2008, 2017 + * Copyright IBM Corp. 2008, 2018 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. @@ -56,6 +56,25 @@ struct lszcrypt_l *lszcrypt_l = &l; #define MASK_CLASS_STATELESS 0x00400000 #define CLASS_STATELESS "restricted function set" +/* + * facility bits + */ +#define MAX_FAC_BITS 9 +static struct fac_bits_s { + int mask; + char c; +} fac_bits[MAX_FAC_BITS] = { + { 0x80000000, 'S' }, + { 0x40000000, 'M' }, + { 0x20000000, 'C' }, + { 0x10000000, 'D' }, + { 0x08000000, 'A' }, + { 0x04000000, 'X' }, + { 0x02000000, 'N' }, + { 0x00800000, 'F' }, + { 0x00400000, 'R' }, +}; + /* * Program configuration */ @@ -66,7 +85,7 @@ const struct util_prg prg = { { .owner = "IBM Corp.", .pub_first = 2008, - .pub_last = 2017, + .pub_last = 2018, }, UTIL_PRG_COPYRIGHT_END } @@ -255,7 +274,8 @@ static void show_capability(const char *id_str) /* Skip devices, which are not supported by zcrypt layer */ if (!util_path_is_readable("%s/type", dev) || !util_path_is_readable("%s/online", dev)) { - printf("Detailed capability information for %s (hardware type %ld) is not available.\n", card, hwtype); + printf("Detailed capability information for %s (hardware type %ld) is not available.\n", + card, hwtype); return; } cbuf[0] = '\0'; @@ -299,11 +319,13 @@ static void show_capability(const char *id_str) } else if (func_val & MASK_EP11) { printf("%s", CAP_EP11); } else { - printf("Detailed capability information for %s (hardware type %ld) is not available.", card, hwtype); + printf("Detailed capability information for %s (hardware type %ld) is not available.", + card, hwtype); } break; default: - printf("Detailed capability information for %s (hardware type %ld) is not available.", card, hwtype); + printf("Detailed capability information for %s (hardware type %ld) is not available.", + card, hwtype); break; } printf("\n"); @@ -315,17 +337,22 @@ static void show_capability(const char *id_str) static void read_subdev_rec_default(struct util_rec *rec, const char *grp_dev, const char *sub_dev) { - unsigned long facility; char buf[256]; + unsigned long facility; - util_file_read_line(buf, sizeof(buf), "%s/type", grp_dev); - util_rec_set(rec, "type", buf); + if (util_file_read_line(buf, sizeof(buf), "%s/type", grp_dev)) + util_rec_set(rec, "type", "-"); + else + util_rec_set(rec, "type", buf); - util_file_read_line(buf, sizeof(buf), "%s/%s/online", grp_dev, sub_dev); - if (strcmp(buf, "0") == 0) - util_rec_set(rec, "online", "offline"); + if (util_file_read_line(buf, sizeof(buf), "%s/%s/online", + grp_dev, sub_dev)) + util_rec_set(rec, "online", "-"); else - util_rec_set(rec, "online", "online"); + if (strcmp(buf, "0") == 0) + util_rec_set(rec, "online", "offline"); + else + util_rec_set(rec, "online", "online"); util_file_read_ul(&facility, 16, "%s/ap_functions", grp_dev); if (facility & MASK_COPRO) @@ -339,7 +366,7 @@ static void read_subdev_rec_default(struct util_rec *rec, const char *grp_dev, util_file_read_line(buf, sizeof(buf), "%s/%s/request_count", grp_dev, sub_dev); - util_rec_set(rec, "request_count", buf); + util_rec_set(rec, "requests", buf); } /* @@ -348,20 +375,19 @@ static void read_subdev_rec_default(struct util_rec *rec, const char *grp_dev, static void read_subdev_rec_verbose(struct util_rec *rec, const char *grp_dev, const char *sub_dev) { + int i; unsigned long facility; - char buf[256]; - long depth; + char buf[256], afile[PATH_MAX]; + long depth, pending1, pending2; if (l.verbose == 0) return; - util_file_read_line(buf, sizeof(buf), "%s/%s/pendingq_count", - grp_dev, sub_dev); - util_rec_set(rec, "pendingq_count", buf); - - util_file_read_line(buf, sizeof(buf), "%s/%s/requestq_count", - grp_dev, sub_dev); - util_rec_set(rec, "requestq_count", buf); + util_file_read_l(&pending1, 10, "%s/%s/pendingq_count", + grp_dev, sub_dev); + util_file_read_l(&pending2, 10, "%s/%s/requestq_count", + grp_dev, sub_dev); + util_rec_set(rec, "pending", "%ld", pending1 + pending2); util_file_read_line(buf, sizeof(buf), "%s/hwtype", grp_dev); util_rec_set(rec, "hwtype", buf); @@ -370,7 +396,18 @@ static void read_subdev_rec_verbose(struct util_rec *rec, const char *grp_dev, util_rec_set(rec, "depth", "%02d", depth + 1); util_file_read_ul(&facility, 16, "%s/ap_functions", grp_dev); - util_rec_set(rec, "facility", "0x%08x", facility); + for (i = 0; i < MAX_FAC_BITS; i++) + buf[i] = facility & fac_bits[i].mask ? fac_bits[i].c : '-'; + buf[i] = '\0'; + util_rec_set(rec, "facility", buf); + + snprintf(afile, sizeof(afile), "%s/%s/driver", grp_dev, sub_dev); + afile[sizeof(afile) - 1] = '\0'; + memset(buf, 0, sizeof(buf)); + if (readlink(afile, buf, sizeof(buf)) > 0) + util_rec_set(rec, "driver", strrchr(buf, '/') + 1); + else + util_rec_set(rec, "driver", "-no-driver-"); } /* @@ -382,9 +419,13 @@ static void show_subdevice(struct util_rec *rec, const char *grp_dev, if (!util_path_is_dir("%s/%s", grp_dev, sub_dev)) errx(EXIT_FAILURE, "Error - cryptographic device %s/%s does not exist.", grp_dev, sub_dev); - /* Skip devices, which are not supported by zcrypt layer */ - if (!util_path_is_readable("%s/type", grp_dev) || - !util_path_is_readable("%s/%s/online", grp_dev, sub_dev)) + /* + * If not verbose mode, skip devices which are not supported + * by the zcrypt layer. + */ + if (l.verbose == 0 && + (!util_path_is_readable("%s/type", grp_dev) || + !util_path_is_readable("%s/%s/online", grp_dev, sub_dev))) return; util_rec_set(rec, "card", sub_dev); @@ -414,11 +455,13 @@ static void show_subdevices(struct util_rec *rec, const char *grp_dev) */ static void read_rec_default(struct util_rec *rec, const char *grp_dev) { - unsigned long facility; char buf[256]; + unsigned long facility; - util_file_read_line(buf, sizeof(buf), "%s/type", grp_dev); - util_rec_set(rec, "type", buf); + if (util_file_read_line(buf, sizeof(buf), "%s/type", grp_dev)) + util_rec_set(rec, "type", "-"); + else + util_rec_set(rec, "type", buf); util_file_read_ul(&facility, 16, "%s/ap_functions", grp_dev); if (facility & MASK_COPRO) @@ -430,14 +473,16 @@ static void read_rec_default(struct util_rec *rec, const char *grp_dev) else util_rec_set(rec, "mode", "Unknown"); - util_file_read_line(buf, sizeof(buf), "%s/online", grp_dev); - if (strcmp(buf, "0") == 0) - util_rec_set(rec, "online", "offline"); + if (util_file_read_line(buf, sizeof(buf), "%s/online", grp_dev)) + util_rec_set(rec, "online", "-"); else - util_rec_set(rec, "online", "online"); + if (strcmp(buf, "0") == 0) + util_rec_set(rec, "online", "offline"); + else + util_rec_set(rec, "online", "online"); util_file_read_line(buf, sizeof(buf), "%s/request_count", grp_dev); - util_rec_set(rec, "request_count", buf); + util_rec_set(rec, "requests", buf); } /* @@ -445,18 +490,17 @@ static void read_rec_default(struct util_rec *rec, const char *grp_dev) */ static void read_rec_verbose(struct util_rec *rec, const char *grp_dev) { + int i; unsigned long facility; - char buf[256]; - long depth; + char buf[256], afile[PATH_MAX]; + long depth, pending1, pending2; if (l.verbose == 0) return; - util_file_read_line(buf, sizeof(buf), "%s/pendingq_count", grp_dev); - util_rec_set(rec, "pendingq_count", buf); - - util_file_read_line(buf, sizeof(buf), "%s/requestq_count", grp_dev); - util_rec_set(rec, "requestq_count", buf); + util_file_read_l(&pending1, 10, "%s/pendingq_count", grp_dev); + util_file_read_l(&pending2, 10, "%s/requestq_count", grp_dev); + util_rec_set(rec, "pending", "%ld", pending1 + pending2); util_file_read_line(buf, sizeof(buf), "%s/hwtype", grp_dev); util_rec_set(rec, "hwtype", buf); @@ -465,7 +509,18 @@ static void read_rec_verbose(struct util_rec *rec, const char *grp_dev) util_rec_set(rec, "depth", "%02d", depth + 1); util_file_read_ul(&facility, 16, "%s/ap_functions", grp_dev); - util_rec_set(rec, "facility", "0x%08x", facility); + for (i = 0; i < MAX_FAC_BITS; i++) + buf[i] = facility & fac_bits[i].mask ? fac_bits[i].c : '-'; + buf[i] = '\0'; + util_rec_set(rec, "facility", buf); + + snprintf(afile, sizeof(afile), "%s/driver", grp_dev); + afile[sizeof(afile) - 1] = '\0'; + memset(buf, 0, sizeof(buf)); + if (readlink(afile, buf, sizeof(buf)) > 0) + util_rec_set(rec, "driver", strrchr(buf, '/') + 1); + else + util_rec_set(rec, "driver", "-no-driver-"); } /* @@ -481,9 +536,14 @@ static void show_device(struct util_rec *rec, const char *device) grp_dev = util_path_sysfs("devices/ap/%s", device); if (!util_path_is_dir(grp_dev)) errx(EXIT_FAILURE, "Error - cryptographic device %s does not exist.", device); - /* Skip devices, which are not supported by zcrypt layer */ - if (!util_path_is_readable("%s/type", grp_dev) || - !util_path_is_readable("%s/online", grp_dev)) { + + /* + * If not verbose mode, skip devices which are not supported + * by the zcrypt layer. + */ + if (l.verbose == 0 && + (!util_path_is_readable("%s/type", grp_dev) || + !util_path_is_readable("%s/online", grp_dev))) { goto out_free; } util_rec_set(rec, "card", card); @@ -506,8 +566,7 @@ static void define_rec_default(struct util_rec *rec) util_rec_def(rec, "type", UTIL_REC_ALIGN_LEFT, 5, "TYPE"); util_rec_def(rec, "mode", UTIL_REC_ALIGN_LEFT, 11, "MODE"); util_rec_def(rec, "online", UTIL_REC_ALIGN_LEFT, 7, "STATUS"); - util_rec_def(rec, "request_count", UTIL_REC_ALIGN_RIGHT, 11, - "REQUEST_CNT"); + util_rec_def(rec, "requests", UTIL_REC_ALIGN_RIGHT, 8, "REQUESTS"); } /* @@ -517,13 +576,11 @@ static void define_rec_verbose(struct util_rec *rec) { if (l.verbose == 0) return; - util_rec_def(rec, "pendingq_count", UTIL_REC_ALIGN_RIGHT, 12, - "PENDINGQ_CNT"); - util_rec_def(rec, "requestq_count", UTIL_REC_ALIGN_RIGHT, 12, - "REQUESTQ_CNT"); - util_rec_def(rec, "hwtype", UTIL_REC_ALIGN_RIGHT, 7, "HW_TYPE"); - util_rec_def(rec, "depth", UTIL_REC_ALIGN_RIGHT, 7, "Q_DEPTH"); + util_rec_def(rec, "pending", UTIL_REC_ALIGN_RIGHT, 8, "PENDING"); + util_rec_def(rec, "hwtype", UTIL_REC_ALIGN_RIGHT, 6, "HWTYPE"); + util_rec_def(rec, "depth", UTIL_REC_ALIGN_RIGHT, 6, "QDEPTH"); util_rec_def(rec, "facility", UTIL_REC_ALIGN_LEFT, 10, "FUNCTIONS"); + util_rec_def(rec, "driver", UTIL_REC_ALIGN_LEFT, 11, "DRIVER"); } /* -- 2.21.3 From 47e15616b0c81f85918794a0af2073aac6455826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Tue, 11 Dec 2018 09:46:40 +0100 Subject: [PATCH 14/56] zkey: Fails to run commands generated by 'zkey cryptsetup' (#1650628) Description: zkey: Fails to run commands generated by 'zkey cryptsetup' Symptom: Fails to run commands generated by 'zkey cryptsetup'. Problem: When using 'zkey cryptsetup' with --run option the execution of the generated commands may fail, when the executable to be run is located in '/sbin'. Solution: Include /sbin into PATH when executing commands. Reproduction: Use 'zkey cryptsetup' with option --run on a distribution where 'cryptsetup' is located in '/sbin'. Upstream-ID: 9100327092c470c2e5b5819087c8094822a1c751 --- zkey/keystore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zkey/keystore.c b/zkey/keystore.c index 11f555c..a4ad634 100644 --- a/zkey/keystore.c +++ b/zkey/keystore.c @@ -3235,7 +3235,7 @@ static int _keystore_execute_cmd(const char *cmd, { int rc; - rc = setenv("PATH", "/bin:/usr/bin:/usr/sbin", 1); + rc = setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin", 1); if (rc < 0) return rc; -- 2.21.3 From 3328fadb3147a24bae10495259fa7fd00b973eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Tue, 11 Dec 2018 10:14:05 +0100 Subject: [PATCH 15/56] zkey: Enhance error message about missing CCA library (#1655134) Description: zkey: Enhance error message about missing CCA library. Symptom: "zkey-cryptsetup reencipher" fails with missing library and confusing error message. Problem: The "zkey reencipher" command as well as the "zkey-cryptsetup reencipher" command requires the IBM CCA Host Libraries and Tools package to be installed. This is a closed source library that is not distributed by the distributions, but must be downloaded separately from an IBM web page. Solution: Enhance the error message to point to the web page where the package can be downloaded. Reproduction: Run the "zkey-cryptsetup reencipher" or "zkey reencipher" command without having installed the IBM CCA Host Libraries and Tools package. --- zkey/pkey.c | 13 +++++++++---- zkey/zkey-cryptsetup.1 | 3 ++- zkey/zkey.1 | 3 ++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/zkey/pkey.c b/zkey/pkey.c index fe43d02..15e606a 100644 --- a/zkey/pkey.c +++ b/zkey/pkey.c @@ -48,6 +48,7 @@ * Definitions for the CCA library */ #define CCA_LIBRARY_NAME "libcsulcca.so" +#define CCA_WEB_PAGE "http://www.ibm.com/security/cryptocards" #define DEFAULT_KEYBITS 256 @@ -71,16 +72,20 @@ int load_cca_library(void **lib_csulcca, t_CSNBKTC *dll_CSNBKTC, bool verbose) /* Load the CCA library */ *lib_csulcca = dlopen(CCA_LIBRARY_NAME, RTLD_GLOBAL | RTLD_NOW); if (*lib_csulcca == NULL) { - warnx("%s\nEnsure that the IBM CCA Host Libraries and " - "Tools are installed properly", dlerror()); + pr_verbose(verbose, "%s", dlerror()); + warnx("The command requires the IBM CCA Host Libraries and " + "Tools.\nFor the supported environments and downloads, " + "see:\n%s", CCA_WEB_PAGE); return -ELIBACC; } /* Get the Key Token Change function */ *dll_CSNBKTC = (t_CSNBKTC)dlsym(*lib_csulcca, "CSNBKTC"); if (*dll_CSNBKTC == NULL) { - warnx("%s\nEnsure that the IBM CCA Host Libraries and " - "Tools are installed properly", dlerror()); + pr_verbose(verbose, "%s", dlerror()); + warnx("The command requires the IBM CCA Host Libraries and " + "Tools.\nFor the supported environments and downloads, " + "see:\n%s", CCA_WEB_PAGE); dlclose(*lib_csulcca); *lib_csulcca = NULL; return -ELIBACC; diff --git a/zkey/zkey-cryptsetup.1 b/zkey/zkey-cryptsetup.1 index bf92e21..988ef76 100644 --- a/zkey/zkey-cryptsetup.1 +++ b/zkey/zkey-cryptsetup.1 @@ -182,7 +182,8 @@ behave in the same way as with \fBcryptsetup\fP. .PP .B Note: The \fBreencipher\fP command requires the CCA host library (libcsulcca.so) -to be installed. +to be installed. For the supported environments and downloads, see: +\fIhttp://www.ibm.com/security/cryptocards\fP . . . diff --git a/zkey/zkey.1 b/zkey/zkey.1 index c9f7906..0837b27 100644 --- a/zkey/zkey.1 +++ b/zkey/zkey.1 @@ -282,7 +282,8 @@ a staged re-enciphering for the \fBOLD\fP to \fBCURRENT\fP case. .PP .B Note: The \fBreencipher\fP command requires the CCA host library (libcsulcca.so) -to be installed. +to be installed. For the supported environments and downloads, see: +\fIhttp://www.ibm.com/security/cryptocards\fP . .SS "Import existing AES secure keys into the secure key repository" . -- 2.21.3 From ba7d612c055c887d5cbb596a369d32f00c47711d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Tue, 29 Jan 2019 13:06:21 +0100 Subject: [PATCH 16/56] zfcpdump: add install script for zfcpdump kernel (#1600480) Summary: zfcpdump: add install script for zfcpdump kernel Description: zipl expects to find the zfcpdump kernel at a specific location (usually /usr/lib/s390-tools/zfcpdump/zfcpdump-image). During kernel installation however, the kernels are installed to /boot. In order for zipl to find the image add a script that manages a link to the latest installed zfcpdump kernel at said location. --- .gitignore | 3 +- common.mak | 7 +- zfcpdump/10-zfcpdump.install.in | 114 ++++++++++++++++++++++++++++++++ zfcpdump/Makefile | 22 ++++-- zipl/include/zipl.h | 7 -- zipl/src/Makefile | 7 +- zipl/src/job.c | 10 +-- 7 files changed, 143 insertions(+), 27 deletions(-) create mode 100644 zfcpdump/10-zfcpdump.install.in diff --git a/.gitignore b/.gitignore index 042233f..6cbec8c 100644 --- a/.gitignore +++ b/.gitignore @@ -76,7 +76,8 @@ zdsfs/zdsfs zdump/zgetdump zfcpdump/cpioinit zfcpdump/zfcpdump_part -zfcpdump/zfcpdump_part.rd +zfcpdump/zfcpdump-initrd +zfcpdump/10-zfcpdump.install ziomon/ziomon_mgr ziomon/ziomon_util ziomon/ziomon_zfcpdd diff --git a/common.mak b/common.mak index 6f0990e..6029687 100644 --- a/common.mak +++ b/common.mak @@ -182,9 +182,10 @@ GROUP = $(shell id -gn) export INSTALLDIR BINDIR LIBDIR MANDIR OWNER GROUP # Special defines for zfcpdump -ZFCPDUMP_PART_IMAGE = zfcpdump_part.image -ZFCPDUMP_PART_RD = zfcpdump_part.rd -export ZFCPDUMP_DIR ZFCPDUMP_PART_IMAGE ZFCPDUMP_PART_RD +ZFCPDUMP_IMAGE = zfcpdump-image +ZFCPDUMP_INITRD = zfcpdump-initrd +ZFCPDUMP_FLAVOR = zfcpdump +export ZFCPDUMP_DIR ZFCPDUMP_IMAGE ZFCPDUMP_INITRD ZFCPDUMP_FLAVOR CFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS) HOSTCFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS) diff --git a/zfcpdump/10-zfcpdump.install.in b/zfcpdump/10-zfcpdump.install.in new file mode 100644 index 0000000..37fde3b --- /dev/null +++ b/zfcpdump/10-zfcpdump.install.in @@ -0,0 +1,114 @@ +#!/bin/bash +# +# 10-zfcpdump.install - Installation script to handle zfcpdump kernels +# +# Copyright IBM Corp. 2018 +# +# s390-tools is free software; you can redistribute it and/or modify +# it under the terms of the MIT license. See LICENSE for details. +# +# +# This script supports two modes: +# +# 1) Installing the images to /boot// +# subdirectories, i.e. BOOT_DIR_ABS, as recommended by the BLS. +# In this case file names are taken over from the original files. +# +# 2) Installing the images directly to /boot. In this case the files are +# renamed to -. +# +# The existence of BOOT_DIR_ABS is taken as trigger to switch between both +# modes. +# +# The KERNEL_VERSION is assumed to contain '@flavor@' to identify the image +# as a zfcpdump kernel. + +COMMAND="$1" +KERNEL_VERSION="$2" +BOOT_DIR_ABS="$3" +KERNEL_IMAGE="$4" + +# Location zipl looks for the zfcpdump kernel +ZFCPDUMP_IMAGE='@zfcpdump_image@' + +# Only handle zfcpdump kernels +echo "$KERNEL_VERSION" | grep -q '@flavor@' || exit 0 + +case "$COMMAND" in + add) + KERNEL_DIR="$(dirname $KERNEL_IMAGE)" + KERNEL_NAME="$(basename $KERNEL_IMAGE)" + + for f in \ + "$KERNEL_IMAGE" \ + "$KERNEL_DIR"/System.map \ + "$KERNEL_DIR"/config \ + "$KERNEL_DIR"/zImage.stub + do + test -e "$f" || continue + test -d "$BOOT_DIR_ABS" \ + && DEST="$BOOT_DIR_ABS/$(basename $f)" \ + || DEST="/boot/$(basename $f)-$KERNEL_VERSION" + + cp -aT "$f" "$DEST" + test $(command -v restorecon) && restorecon -R "$DEST" + done + + # hmac file need special treatment + f="$KERNEL_DIR/.$KERNEL_NAME.hmac" + if [ -e "$f" ]; then + test -d "$BOOT_DIR_ABS" \ + && DEST="$BOOT_DIR_ABS/$(basename $f)" \ + || DEST="/boot/.$KERNEL_NAME-$KERNEL_VERSION.hmac" + + cp -aT "$f" "$DEST" + test $(command -v restorecon) && restorecon -R "$DEST" + fi + + # Set link so zipl finds the kernel + test -d "$BOOT_DIR_ABS" \ + && TARGET="$BOOT_DIR_ABS/$KERNEL_NAME" \ + || TARGET="/boot/$KERNEL_NAME-$KERNEL_VERSION" + ln -sf "$TARGET" "$ZFCPDUMP_IMAGE" + ;; + + remove) + # On removal + # $KERNEL_IMAGE is empty -> $KERNEL_NAME is empty -> rebuild it + KERNEL_NAME="$(basename $(readlink $ZFCPDUMP_IMAGE))" + if [ -d "$BOOT_DIR_ABS" ]; then + INSTALL_DIR="$(dirname $BOOT_DIR_ABS)" + else + INSTALL_DIR="/boot/" + KERNEL_NAME="$(echo $KERNEL_NAME \ + | sed -e "s#\(.*\)-$KERNEL_VERSION#\1#")" + fi + + for f in $(find "$INSTALL_DIR" -name "*$KERNEL_VERSION*"); do + rm -rf "$f" + done + + # Update link to latest remaining zfcpdump kernel. + if [ $(readlink "$ZFCPDUMP_IMAGE" | grep "$KERNEL_VERSION") ] + then + NEXT_IMAGE=$( \ + find "$INSTALL_DIR" -type f \ + | grep '@flavor@' \ + | grep "$KERNEL_NAME" \ + | grep -v "hmac" \ + | sort -V \ + | tail -n1 ) + + test $NEXT_IMAGE \ + && ln -sf "$NEXT_IMAGE" "$ZFCPDUMP_IMAGE" \ + || rm -f "$ZFCPDUMP_IMAGE" + fi + ;; + *) + ;; +esac + +# Prevent execution of all other scripts. +# The zfcpdump kernel is stripped down to the bare minimum needed for +# dumping. It is not supposed to be used for any other purpose. +exit 77 diff --git a/zfcpdump/Makefile b/zfcpdump/Makefile index 5fcb073..cb2fd41 100644 --- a/zfcpdump/Makefile +++ b/zfcpdump/Makefile @@ -1,6 +1,7 @@ include ../common.mak CPIOINIT = $(call echocmd," CPIOINI ",/$@)./cpioinit +INSTALL_SCRIPTS = 10-zfcpdump.install ifeq (${HAVE_LIBC_STATIC},0) @@ -20,7 +21,7 @@ check_dep: "HAVE_LIBC_STATIC=0", \ "-static") -all: check_dep $(ZFCPDUMP_PART_RD) +all: check_dep $(ZFCPDUMP_INITRD) scripts cpioinit: cpioinit.c $(HOSTCC) $(HOSTCFLAGS) -o $@ $^ @@ -29,17 +30,26 @@ zfcpdump_part: zfcpdump.o zfcpdump_part.o $(LINK) $(ALL_LDFLAGS) $^ -static -o $@ $(STRIP) -s $@ -$(ZFCPDUMP_PART_RD): cpioinit zfcpdump_part +$(ZFCPDUMP_INITRD): cpioinit zfcpdump_part $(CPIOINIT) zfcpdump_part > $@.tmp $(GZIP) -f $@.tmp - $(MV) $@.tmp.gz $(ZFCPDUMP_PART_RD) + $(MV) $@.tmp.gz $(ZFCPDUMP_INITRD) + +scripts: $(INSTALL_SCRIPTS) + chmod +x $(INSTALL_SCRIPTS) install: all - $(INSTALL) -m 611 $(ZFCPDUMP_PART_RD) $(DESTDIR)$(ZFCPDUMP_DIR) + $(INSTALL) -m 611 $(ZFCPDUMP_INITRD) $(DESTDIR)$(ZFCPDUMP_DIR) +%: %.in + zfcpdump_image=$(ZFCPDUMP_DIR)/$(ZFCPDUMP_IMAGE); \ + $(SED) -e "s#@zfcpdump_image@#$$zfcpdump_image#g" \ + -e "s#@flavor@#$(ZFCPDUMP_FLAVOR)#g" \ + < $< > $@ endif clean: - rm -f *.o *.gz *.tmp *~ zfcpdump_part cpioinit $(ZFCPDUMP_PART_RD) + rm -f *.o *.gz *.tmp *~ zfcpdump_part cpioinit $(ZFCPDUMP_INITRD) \ + $(INSTALL_SCRIPTS) -.PHONY: all clean install check_dep +.PHONY: all clean install check_dep scripts diff --git a/zipl/include/zipl.h b/zipl/include/zipl.h index 2aed54f..770801e 100644 --- a/zipl/include/zipl.h +++ b/zipl/include/zipl.h @@ -48,13 +48,6 @@ #define MENU_DEFAULT_PROMPT 0 #define MENU_DEFAULT_TIMEOUT 0 -#define FSDUMP_IMAGE STRINGIFY(ZFCPDUMP_DIR) "/" STRINGIFY(ZFCPDUMP_FS_IMAGE) -#define FSDUMP_RAMDISK STRINGIFY(ZFCPDUMP_DIR) "/" STRINGIFY(ZFCPDUMP_FS_RD) -#define FSDUMP_PART_IMAGE STRINGIFY(ZFCPDUMP_DIR) "/" \ - STRINGIFY(ZFCPDUMP_PART_IMAGE) -#define FSDUMP_PART_RAMDISK STRINGIFY(ZFCPDUMP_DIR) "/" \ - STRINGIFY(ZFCPDUMP_PART_RD) - #define MAX_DUMP_VOLUMES 32 /* Internal component load address type */ diff --git a/zipl/src/Makefile b/zipl/src/Makefile index 1634c0d..be14fce 100644 --- a/zipl/src/Makefile +++ b/zipl/src/Makefile @@ -2,11 +2,8 @@ include ../../common.mak ALL_CPPFLAGS += -I../include -I../boot \ - -DZFCPDUMP_DIR=$(ZFCPDUMP_DIR) \ - -DZFCPDUMP_FS_IMAGE=$(ZFCPDUMP_FS_IMAGE) \ - -DZFCPDUMP_FS_RD=$(ZFCPDUMP_FS_RD) \ - -DZFCPDUMP_PART_IMAGE=$(ZFCPDUMP_PART_IMAGE) \ - -DZFCPDUMP_PART_RD=$(ZFCPDUMP_PART_RD) \ + -DZFCPDUMP_IMAGE="STRINGIFY($(ZFCPDUMP_DIR)/$(ZFCPDUMP_IMAGE))" \ + -DZFCPDUMP_INITRD="STRINGIFY($(ZFCPDUMP_DIR)/$(ZFCPDUMP_INITRD))" \ -D_FILE_OFFSET_BITS=64 $(NO_PIE_CFLAGS) ALL_LDFLAGS += -Wl,-z,noexecstack $(NO_PIE_LDFLAGS) diff --git a/zipl/src/job.c b/zipl/src/job.c index e6d2981..22d2549 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -874,22 +874,22 @@ check_job_dump_images(struct job_dump_data* dump, char* name) { int rc; /* Add data needed to convert fs dump job to IPL job */ - rc = misc_check_readable_file(FSDUMP_PART_IMAGE); + rc = misc_check_readable_file(ZFCPDUMP_IMAGE); if (rc) { error_text("Need external file '%s' for partition dump", - FSDUMP_PART_IMAGE); + ZFCPDUMP_IMAGE); return rc; } - dump->image = misc_strdup(FSDUMP_PART_IMAGE); + dump->image = misc_strdup(ZFCPDUMP_IMAGE); if (dump->image == NULL) return -1; dump->image_addr = DEFAULT_IMAGE_ADDRESS; /* Ramdisk is no longer required with new initramfs dump system */ - if (misc_check_readable_file(FSDUMP_PART_RAMDISK)) + if (misc_check_readable_file(ZFCPDUMP_INITRD)) dump->ramdisk = NULL; else { - dump->ramdisk = misc_strdup(FSDUMP_PART_RAMDISK); + dump->ramdisk = misc_strdup(ZFCPDUMP_INITRD); if (dump->ramdisk == NULL) return -1; dump->ramdisk_addr = UNSPECIFIED_ADDRESS; -- 2.21.3 From eef1d70b8bd0ec96aa90f5dad9795196c85382ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Wed, 9 Jan 2019 13:58:10 +0100 Subject: [PATCH 17/56] pkey: Support autoloading kernel pkey module (#1664632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The module is loaded automatically based on CPU features, but it's still too late in some use cases. Thus allow distros to use explicit loading. See also: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=63c19be095d0f8eb8992674991e44b4228bd4179 Closes: https://github.com/ibm-s390-tools/s390-tools/pull/51 Signed-off-by: Dan Horák Reviewed-by: Ingo Franzki Signed-off-by: Jan Höppner (cherry picked from commit dffd41943e5c01be2f343da7726edabf9d2ec05e) --- etc/modules-load.d/s390-pkey.conf | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 etc/modules-load.d/s390-pkey.conf diff --git a/etc/modules-load.d/s390-pkey.conf b/etc/modules-load.d/s390-pkey.conf new file mode 100644 index 0000000..972a099 --- /dev/null +++ b/etc/modules-load.d/s390-pkey.conf @@ -0,0 +1,2 @@ +# Load protected key support module on s390 early at boot +pkey -- 2.21.3 From 7a3ba6fb39f015da0ee10e1dc0e14ae9fcf6347f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Tue, 21 May 2019 13:39:08 +0200 Subject: [PATCH 18/56] zipl: Secure Boot support for SCSI IPL (#1659401) Description: The Secure Boot firmware feature ensures that only signed and verified code is executed during IPL. This feature adds support for secure boot to the zipl tool and internal loader. Upstream-ID: e764f460c457ab2a6000acb5f2eb7169866ce192 Upstream-ID: 6825645a21a3221629e7d5aa9d5cfbf6e3bc4188 Upstream-ID: 0c1a63ce5ec6059374d9f8dd55bffbb42ef9bd45 Upstream-ID: 7c7e10ed8fb048efc4e0cd91b0f6fa704fba128e Upstream-ID: 58a7462f65d85726168303bd58e7472bd4c82731 Upstream-ID: 75d4317f208da1e25afd520fe43a1786333388ad Upstream-ID: dc2e4395506956a99819484d8938e877c4c2cd88 Upstream-ID: 7e7a77675d2b88b3d516d60aa64f2b1264b25117 Upstream-ID: 331b54d573c38c7eed6617eef69cf296c2e86d6d Upstream-ID: 6ea645b3453264a0f1a60955c4476dab54035f88 --- zipl/boot/Makefile | 26 ++- zipl/boot/error.h | 3 + zipl/boot/libc.c | 20 ++ zipl/boot/libc.h | 1 + zipl/boot/s390.h | 5 + zipl/boot/stage2.c | 9 +- zipl/boot/stage2.h | 3 +- zipl/boot/stage2.lds | 5 +- zipl/boot/stage3.c | 114 ++++++++--- zipl/boot/stage3.h | 142 ++++++++++++++ zipl/boot/stage3.lds | 38 ++-- zipl/include/boot.h | 11 +- zipl/include/bootmap.h | 31 +++ zipl/include/job.h | 1 + zipl/include/misc.h | 1 + zipl/include/scan.h | 3 +- zipl/include/zipl.h | 15 +- zipl/man/zipl.8 | 15 ++ zipl/man/zipl.conf.5 | 27 +++ zipl/src/Makefile | 7 +- zipl/src/boot.c | 33 ++-- zipl/src/bootmap.c | 417 +++++++++++++++++++++++++++++++++-------- zipl/src/job.c | 44 ++++- zipl/src/misc.c | 1 - zipl/src/scan.c | 34 ++-- 25 files changed, 837 insertions(+), 169 deletions(-) diff --git a/zipl/boot/Makefile b/zipl/boot/Makefile index 52b3a23..da7e95f 100644 --- a/zipl/boot/Makefile +++ b/zipl/boot/Makefile @@ -13,10 +13,9 @@ FILES = fba0.bin fba1b.bin fba2.bin \ eckd0_ldl.bin eckd0_cdl.bin \ eckd1.bin eckd1b.bin eckd2.bin \ tape0.bin \ - eckd2dump_sv.bin tape2dump.bin fba2dump.bin eckd2dump_mv.bin \ - stage3.bin + eckd2dump_sv.bin tape2dump.bin fba2dump.bin eckd2dump_mv.bin -all: data.o data.h tape0.bin +all: data.o data.h tape0.bin stage3.bin # Prevent make from using some default rules... %: %.S @@ -44,7 +43,7 @@ eckd2.exec: head.o stage2.o cio.o eckd2.o libc.o menu.o sclp.o \ fba2.exec: head.o stage2.o cio.o fba2.o libc.o menu.o sclp.o \ kdump2.o kdump.o entry.o stage3.exec: head.o stage3.o kdump3.o libc.o sclp.o sclp_stage3.o \ - kdump.o entry.o + kdump.o entry.o stage3.lds %.exec: %.o @STAGE=$$( \ @@ -77,6 +76,22 @@ stage3.exec: head.o stage3.o kdump3.o libc.o sclp.o sclp_stage3.o \ --only-section=.fixup \ $< $@ +stage3.bin: stage3.exec + $(OBJCOPY) -O binary \ + --only-section=.stage2.head \ + --only-section=.text.dummy \ + --only-section=.text.start \ + --only-section=.text \ + --only-section=.ex_table \ + --only-section=.data \ + --only-section=.rodata.str1.2 \ + --only-section=.rodata \ + --only-section=.stage2dump.tail \ + --only-section=.eckd2dump_mv.tail \ + --only-section=.fixup \ + --pad-to=0xf000 \ + $< $@ + data.o: $(FILES) $(LD) $(NO_PIE_LDFLAGS) -r -b binary -o data.o $(FILES) @@ -86,6 +101,7 @@ data.h: data.o echo "extern char $$SYMBOL;" >>data.h; done clean: - rm -f *.o *.exec *.bin $(FILES) data.o data.h tape0.bin *.xxx *.yyy + rm -f *.o *.exec *.bin $(FILES) data.o data.h tape0.bin *.xxx *.yyy \ + stage3.bin .PHONY: all clean diff --git a/zipl/boot/error.h b/zipl/boot/error.h index 93fd748..715973e 100644 --- a/zipl/boot/error.h +++ b/zipl/boot/error.h @@ -31,6 +31,9 @@ /* Internal error */ #define EINTERNAL 0x00004511 +/* Secure IPL error */ +#define ESECUREBOOT 0x00004512 + /* kdump: No operating system information was found */ #define EOS_INFO_MISSING 0x00004520 diff --git a/zipl/boot/libc.c b/zipl/boot/libc.c index f86f62a..5944c4e 100644 --- a/zipl/boot/libc.c +++ b/zipl/boot/libc.c @@ -58,6 +58,26 @@ void *memcpy(void *dest, const void *src, unsigned long n) return dest; } +/* + * Move @n bytes of memory from @src to @dest. The memory regions may overlap. + */ +void *memmove(void *dest, const void *src, unsigned long n) +{ + const char *s = src; + char *d = dest; + + if (s < d) { + d += n; + s += n; + while (n--) + *--d = *--s; + } else { + while (n--) + *d++ = *s++; + } + return dest; +} + /* * Copy string */ diff --git a/zipl/boot/libc.h b/zipl/boot/libc.h index b05a116..44097fa 100644 --- a/zipl/boot/libc.h +++ b/zipl/boot/libc.h @@ -49,6 +49,7 @@ typedef unsigned char uint8_t; void printf(const char *, ...); void sprintf(char *, const char *, ...); void *memcpy(void *, const void *, unsigned long); +void *memmove(void *, const void *, unsigned long); void *memset(void *, int c, unsigned long); char *strcat(char *, const char *); int strncmp(const char *, const char *, unsigned long); diff --git a/zipl/boot/s390.h b/zipl/boot/s390.h index 279d16b..6e5a7ae 100644 --- a/zipl/boot/s390.h +++ b/zipl/boot/s390.h @@ -35,6 +35,11 @@ struct psw_t { uint64_t addr; } __aligned(8); +struct psw32_t { + uint32_t mask; + uint32_t addr; +} __aligned(8); + void load_wait_psw(uint64_t, struct psw_t *); struct _lowcore { diff --git a/zipl/boot/stage2.c b/zipl/boot/stage2.c index 02d644e..99e1bd1 100644 --- a/zipl/boot/stage2.c +++ b/zipl/boot/stage2.c @@ -115,8 +115,13 @@ void start(void) /* skip header */ entry = (struct component_entry *) (load_address + sizeof(struct component_header)); - - while (entry->type == COMPONENT_LOAD) { + while (entry->type == COMPONENT_LOAD || + entry->type == COMPONENT_SIGNATURE) { + if (entry->type == COMPONENT_SIGNATURE) { + /* Skip unhandled signature components */ + entry++; + continue; + } load_address = (void *)(unsigned long) entry->address.load_address[1]; load_blocklist(entry, subchannel_id, load_address); diff --git a/zipl/boot/stage2.h b/zipl/boot/stage2.h index b79c798..8fd5bd3 100644 --- a/zipl/boot/stage2.h +++ b/zipl/boot/stage2.h @@ -61,7 +61,8 @@ struct component_entry { typedef enum { COMPONENT_EXECUTE = 0x01, - COMPONENT_LOAD = 0x02 + COMPONENT_LOAD = 0x02, + COMPONENT_SIGNATURE = 0x03 } component_type; struct stage2_descr { diff --git a/zipl/boot/stage2.lds b/zipl/boot/stage2.lds index befe8e2..41efa79 100644 --- a/zipl/boot/stage2.lds +++ b/zipl/boot/stage2.lds @@ -9,7 +9,8 @@ * 0x2000-0x4fff Sections (load): head, text, data, rodata, rodata.str * 0x5000-0x51ff eckd2dump_mv parameter block (426 bytes) * 0x5200-0x5fff Sections: bss - * 0x6000-0x9fff Memory allocation (heap) + * 0x6000-0x8fff Memory allocation (heap) + * 0x9000-0x9fff Memory to load stage3 parameter to * 0xa000-0xdfff Memory to load stage3 to * 0xe000-0xffff Stack * @@ -52,7 +53,7 @@ SECTIONS . = 0x6000; __heap_start = .; - . = 0xa000; + . = 0x9000; __heap_stop = .; . = 0xf000; diff --git a/zipl/boot/stage3.c b/zipl/boot/stage3.c index eb02627..f7a9597 100644 --- a/zipl/boot/stage3.c +++ b/zipl/boot/stage3.c @@ -12,35 +12,15 @@ #include "libc.h" #include "s390.h" #include "stage3.h" +#include "error.h" -/* - * 48 Byte dummy space for external symbols - * _parm_addr; address of parmline - * _initrd_addr; address of initrd - * _initrd_len; length of initrd - * _load_psw; load psw of kernel - * _extra_parm; use extra parm line mechanism? - * stage3_flags; flags (e.g. STAGE3_FLAG_KDUMP) - * - * needed to blow up the binary and leave room - */ -__attribute__ ((section(".text.dummy"))) void _dummy(void) -{ - asm volatile( - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ".long 0x00000000\n" - ); -} +#define for_each_rb_entry(entry, rb) \ + for (entry = rb->entries; \ + (void *) entry + sizeof(*entry) <= (void *) rb + rb->len; \ + entry++) + +static const char *msg_sipl_inval = "Secure boot failure: invalid load address"; +static const char *msg_sipl_unverified = "Secure boot failure: unverified load address"; static unsigned char ebc_037[256] = { /* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */ @@ -221,6 +201,64 @@ start_kernel(void) : [psw] "a" (psw) ); } +unsigned int +is_verified_address(unsigned long image_addr) +{ + struct ipl_rb_component_entry *comp; + struct ipl_rb_components *comps; + struct ipl_pl_hdr *pl_hdr; + struct ipl_rl_hdr *rl_hdr; + struct ipl_rb_hdr *rb_hdr; + unsigned long tmp; + void *rl_end; + + /* + * There is an IPL report, to find it load the pointer to the + * IPL parameter information block from lowcore and skip past + * the IPL parameter list, then align the address to a double + * word boundary. + */ + tmp = (unsigned long) S390_lowcore.ipl_parmblock_ptr; + pl_hdr = (struct ipl_pl_hdr *) tmp; + tmp = (tmp + pl_hdr->len + 7) & -8UL; + rl_hdr = (struct ipl_rl_hdr *) tmp; + /* Walk through the IPL report blocks in the IPL Report list */ + comps = NULL; + rl_end = (void *) rl_hdr + rl_hdr->len; + rb_hdr = (void *) rl_hdr + sizeof(*rl_hdr); + while ((void *) rb_hdr + sizeof(*rb_hdr) < rl_end && + (void *) rb_hdr + rb_hdr->len <= rl_end) { + switch (rb_hdr->rbt) { + case IPL_RBT_COMPONENTS: + comps = (struct ipl_rb_components *) rb_hdr; + break; + default: + break; + } + + rb_hdr = (void *) rb_hdr + rb_hdr->len; + } + for_each_rb_entry(comp, comps) { + if (image_addr == comp->addr && + comp->flags & IPL_RB_COMPONENT_FLAG_SIGNED && + comp->flags & IPL_RB_COMPONENT_FLAG_VERIFIED) + return 1; + } + return 0; +} + +unsigned int +secure_boot_enabled() +{ + struct ipl_pl_hdr *pl_hdr; + unsigned long tmp; + + tmp = (unsigned long) S390_lowcore.ipl_parmblock_ptr; + pl_hdr = (struct ipl_pl_hdr *) tmp; + + return pl_hdr->flags & IPL_FLAG_SECURE; +} + void start(void) { unsigned int subchannel_id; @@ -228,6 +266,26 @@ void start(void) unsigned char *command_line = (unsigned char *)COMMAND_LINE; unsigned int begin = 0, end = 0, length = 0; + /* + * IPL process is secure we have to use default IPL values and + * check if the psw jump address is within at the start of a + * verified component. If it is not IPL is aborted. + */ + if (secure_boot_enabled()) { + if (_image_addr != DEFAULT_IMAGE_ADDR || + _load_psw != DEFAULT_PSW_LOAD) + panic(ESECUREBOOT, "%s", msg_sipl_inval); + + if (!is_verified_address(_load_psw & PSW_ADDR_MASK)) + panic(ESECUREBOOT, "%s", msg_sipl_unverified); + } + /* + * cut the kernel header + */ + memmove((void *)_image_addr, + (void *)_image_addr + KERNEL_HEADER_SIZE, + _image_len - KERNEL_HEADER_SIZE); + /* store subchannel ID into low core and into new kernel space */ subchannel_id = S390_lowcore.subchannel_id; *(unsigned int *)__LC_IPLDEV = subchannel_id; diff --git a/zipl/boot/stage3.h b/zipl/boot/stage3.h index cdc0d77..91477b1 100644 --- a/zipl/boot/stage3.h +++ b/zipl/boot/stage3.h @@ -27,14 +27,156 @@ #define STAGE3_FLAG_SCSI 0x0001000000000000ULL #define STAGE3_FLAG_KDUMP 0x0002000000000000ULL +#define IPL_FLAG_SECURE 0x40 + +#define DEFAULT_IMAGE_ADDR 0x10000 +#define DEFAULT_PSW_LOAD 0x0008000080010000L +#define PSW_ADDR_MASK 0x000000007FFFFFFFL +#define KERNEL_HEADER_SIZE 65536 + #define UNSPECIFIED_ADDRESS -1ULL + +/* IPL Parameter List header */ +struct ipl_pl_hdr { + uint32_t len; + uint8_t flags; + uint8_t reserved1[2]; + uint8_t version; +} __packed; + +/* IPL Parameter Block header */ +struct ipl_pb_hdr { + uint32_t len; + uint8_t pbt; +} __packed; + +/* IPL Parameter Block 0 with common fields */ +struct ipl_pb0_common { + uint32_t len; + uint8_t pbt; + uint8_t flags; + uint8_t reserved1[2]; + uint8_t loadparm[8]; + uint8_t reserved2[84]; +} __packed; + +/* IPL Parameter Block 0 for FCP */ +struct ipl_pb0_fcp { + uint32_t len; + uint8_t pbt; + uint8_t reserved1[3]; + uint8_t loadparm[8]; + uint8_t reserved2[304]; + uint8_t opt; + uint8_t reserved3[3]; + uint8_t cssid; + uint8_t reserved4[1]; + uint8_t devno; + uint8_t reserved5[4]; + uint64_t wwpn; + uint64_t lun; + uint32_t bootprog; + uint8_t reserved6[12]; + uint64_t br_lba; + uint32_t scp_data_len; + uint8_t reserved7[260]; + uint8_t scp_data[]; +} __packed; + +/* IPL Parameter Block 0 for CCW */ +struct ipl_pb0_ccw { + uint32_t len; + uint8_t pbt; + uint8_t flags; + uint8_t reserved1[2]; + uint8_t loadparm[8]; + uint8_t reserved2[84]; + uint16_t reserved3 : 13; + uint8_t ssid : 3; + uint16_t devno; + uint8_t vm_flags; + uint8_t reserved4[3]; + uint32_t vm_parm_len; + uint8_t nss_name[8]; + uint8_t vm_parm[64]; + uint8_t reserved5[8]; +} __packed; + +struct ipl_parameter_block { + struct ipl_pl_hdr hdr; + union { + struct ipl_pb_hdr pb0_hdr; + struct ipl_pb0_common common; + struct ipl_pb0_fcp fcp; + struct ipl_pb0_ccw ccw; + char raw[PAGE_SIZE - sizeof(struct ipl_pl_hdr)]; + }; +} __packed __aligned(PAGE_SIZE); + +/* IPL Report List header */ +struct ipl_rl_hdr { + uint32_t len; + uint8_t flags; + uint8_t reserved1[2]; + uint8_t version; + uint8_t reserved2[8]; +} __packed; + +/* IPL Report Block header */ +struct ipl_rb_hdr { + uint32_t len; + uint8_t rbt; + uint8_t reserved1[11]; +} __packed; + +/* IPL Report Block types */ +enum ipl_rbt { + IPL_RBT_CERTIFICATES = 1, + IPL_RBT_COMPONENTS = 2, +}; + +/* IPL Report Block for the certificate list */ +struct ipl_rb_certificate_entry { + uint64_t addr; + uint64_t len; +} __packed; + +struct ipl_rb_certificates { + uint32_t len; + uint8_t rbt; + uint8_t reserved1[11]; + struct ipl_rb_certificate_entry entries[]; +} __packed; + +/* IPL Report Block for the component list */ +struct ipl_rb_component_entry { + uint64_t addr; + uint64_t len; + uint8_t flags; + uint8_t reserved1[5]; + uint16_t certificate_index; + uint8_t reserved2[8]; +}; + +#define IPL_RB_COMPONENT_FLAG_SIGNED 0x80 +#define IPL_RB_COMPONENT_FLAG_VERIFIED 0x40 + +struct ipl_rb_components { + uint32_t len; + uint8_t rbt; + uint8_t reserved1[11]; + struct ipl_rb_component_entry entries[]; +} __packed; + extern unsigned long long _parm_addr; /* address of parmline */ extern unsigned long long _initrd_addr; /* address of initrd */ extern unsigned long long _initrd_len; /* length of initrd */ extern unsigned long long _load_psw; /* load psw of kernel */ extern unsigned long long _extra_parm; /* use extra parm line mechanism? */ extern unsigned long long stage3_flags; /* flags (e.g. STAGE3_FLAG_KDUMP) */ +extern unsigned long long _image_len; /* length of kernel */ +extern unsigned long long _image_addr; /* target address of kernel */ extern void kdump_stage3(); #endif /* STAGE3_H */ diff --git a/zipl/boot/stage3.lds b/zipl/boot/stage3.lds index 638fb25..c67bc32 100644 --- a/zipl/boot/stage3.lds +++ b/zipl/boot/stage3.lds @@ -1,33 +1,47 @@ /* * Memory layout for stage 3 + * ========================= + * + * General memory layout + * --------------------- + * + * 0x0000-0x1fff Lowcore + * 0x2000-0x5fff Memory allocation (heap) + * 0x6000-0x8fff free + * 0x9000-0x9fff Stage3 parameter + * 0xa000-0xdfff Stage3 code + data + * 0xe000-0xffff Stack */ SECTIONS { . = 0x0; - . = 0x7000; + . = 0x2000; __heap_start = .; - . = 0xa000; + . = 0x6000; __heap_stop = .; - . = 0xa000; + + /* stage 3 parameter */ + . = 0x9000; _parm_addr = .; - . = 0xa008; + . = 0x9008; _initrd_addr = .; - . = 0xa010; + . = 0x9010; _initrd_len = .; - . = 0xa018; + . = 0x9018; _load_psw = .; - . = 0xa020; + . = 0x9020; _extra_parm = .; - . = 0xa028; + . = 0x9028; stage3_flags =.; + . = 0x9030; + _image_len = .; + . = 0x9038; + _image_addr = .; . = 0xa000; - .text.dummy : { *(.text.dummy) } - - . = 0xa050; .text.start : { *(.text.start) } .text : { *(.text) } __ex_table_start = .; @@ -35,11 +49,9 @@ SECTIONS __ex_table_stop = .; .eh_frame : { *(.eh_frame) } - . = 0xc000; __bss_start = .; .bss : { *(.bss) } __bss_stop = .; .rodata : {*(.rodata) } .data : { *(.data) } - } diff --git a/zipl/include/boot.h b/zipl/include/boot.h index f21ab59..6bd2a9b 100644 --- a/zipl/include/boot.h +++ b/zipl/include/boot.h @@ -248,6 +248,9 @@ struct boot_stage3_params { uint64_t load_psw; uint64_t extra_parm; uint16_t flags; + uint16_t reserved[3]; + uint64_t image_len; + uint64_t image_addr; } __attribute__ ((packed)); #define STAGE3_FLAG_SCSI 0x0001 @@ -305,10 +308,10 @@ int boot_init_fba_stage1b(struct boot_fba_stage1b *stage1b, disk_blockptr_t *stage2_list, blocknum_t stage2_count); int boot_get_eckd_stage2(void** data, size_t* size, struct job_data* job); -size_t get_stage3_size(); -int boot_get_stage3(void** buffer, size_t* bytecount, address_t parm_addr, - address_t initrd_addr, size_t initrd_len, - address_t image_addr, int extra_parm, uint16_t flags); +int boot_get_stage3_parms(void **buffer, size_t *bytecount, address_t parm_addr, + address_t initrd_addr, size_t initrd_len, + address_t load_addr, int extra_parm, uint16_t flags, + size_t image_len); int boot_get_tape_ipl(void** data, size_t* size, address_t parm_addr, address_t initrd_addr, address_t image_addr); int boot_get_tape_dump(void** data, size_t* size, uint64_t mem); diff --git a/zipl/include/bootmap.h b/zipl/include/bootmap.h index c03c041..4a0bd04 100644 --- a/zipl/include/bootmap.h +++ b/zipl/include/bootmap.h @@ -16,6 +16,37 @@ #include "job.h" #include "zipl.h" +#define SIGNATURE_MAGIC "~Module signature appended~\n" +#define PKCS7_FORMAT 0x01 + +struct signature_header { + uint8_t format; + uint8_t reserved[3]; + uint32_t length; +} __attribute((packed)); + +typedef union { + uint64_t load_address; + uint64_t load_psw; + struct signature_header sig_head; +} component_data; + +/* + * The file_signature structure and the PKEY_ID definition + * are based on linux/scripts/sign-file.c + */ +struct file_signature { + u8 algorithm; + u8 hash; + u8 id_type; + u8 signer_len; + u8 key_id_len; + u8 __pad[3]; + u32 sig_len; + char magic[28]; +}; + +#define PKEY_ID_PKCS7 0x02 int bootmap_create(struct job_data* job, disk_blockptr_t* program_table, disk_blockptr_t *scsi_dump_sb_blockptr, diff --git a/zipl/include/job.h b/zipl/include/job.h index 4c9253f..fce811c 100644 --- a/zipl/include/job.h +++ b/zipl/include/job.h @@ -121,6 +121,7 @@ struct job_data { int add_files; int dry_run; int command_line; + int is_secure; }; diff --git a/zipl/include/misc.h b/zipl/include/misc.h index 37a8a87..5a349a7 100644 --- a/zipl/include/misc.h +++ b/zipl/include/misc.h @@ -51,6 +51,7 @@ int misc_check_readable_file(const char* filename); int misc_check_writable_device(const char* devno, int blockdev, int chardev); void misc_ebcdic_to_ascii(unsigned char *from, unsigned char *to); void misc_ascii_to_ebcdic(unsigned char *from, unsigned char *to); +unsigned int misc_check_secure_boot(void); #define ROUNDUP(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) diff --git a/zipl/include/scan.h b/zipl/include/scan.h index 0fe415c..4dcee0b 100644 --- a/zipl/include/scan.h +++ b/zipl/include/scan.h @@ -16,7 +16,7 @@ #define SCAN_SECTION_NUM 9 -#define SCAN_KEYWORD_NUM 21 +#define SCAN_KEYWORD_NUM 22 #define SCAN_KEYWORD_ONLY_NUM 1 #define SCAN_AUTOMENU_NAME "zipl-automatic-menu" @@ -51,6 +51,7 @@ enum scan_keyword_id { scan_keyword_targetoffset = 18, scan_keyword_defaultauto = 19, scan_keyword_kdump = 20, + scan_keyword_secure = 21, }; enum scan_section_type { diff --git a/zipl/include/zipl.h b/zipl/include/zipl.h index 770801e..6f2d115 100644 --- a/zipl/include/zipl.h +++ b/zipl/include/zipl.h @@ -20,10 +20,11 @@ #define DISK_LAYOUT_ID 0x00000001 #define ZIPL_STAGE2_LOAD_ADDRESS 0x2000 -#define ZIPL_STAGE3_ENTRY_ADDRESS 0xa050LL +#define ZIPL_STAGE3_ENTRY_ADDRESS 0xa000LL #define DEFAULT_IMAGE_ADDRESS 0x10000LL #define KDUMP_IMAGE_ADDRESS 0x10010LL #define DEFAULT_STAGE3_ADDRESS 0xa000LL +#define DEFAULT_STAGE3_PARAMS_ADDRESS 0x9000LL #define MINIMUM_ADDRESS 0x10000LL #define ADDRESS_LIMIT 0x80000000LL #define ADDRESS_LIMIT_KDUMP 0x2000000UL /* HSA size: 32 MiB */ @@ -31,11 +32,15 @@ #define MAXIMUM_PARMLINE_SIZE 0x380 #define MAXIMUM_PHYSICAL_BLOCKSIZE 0x1000 +#define STAGE3_HEAP_SIZE 0x4000 +#define STAGE3_HEAP_ADDRESS 0x2000 +#define STAGE3_STACK_SIZE 0x1000 +#define STAGE3_STACK_ADDRESS 0xF000 + #define PSW_ADDRESS_MASK 0x000000007fffffffLL #define PSW_LOAD 0x0008000080000000LL #define PSW_DISABLED_WAIT 0x000a000000000000LL -#define KERNEL_HEADER_SIZE 65536 #define BOOTMAP_FILENAME "bootmap" #define BOOTMAP_TEMPLATE_FILENAME "bootmap_temp.XXXXXX" @@ -44,12 +49,18 @@ #define ZIPL_CONF_VAR "ZIPLCONF" #define ZIPL_DEFAULT_CONF "/etc/zipl.conf" #define ZIPL_DEFAULT_BLSDIR "/boot/loader/entries" +#define ZIPL_STAGE3_PATH TOOLS_LIBDIR "/stage3.bin" +#define ZIPL_SIPL_PATH "/sys/firmware/ipl/has_secure" #define MENU_DEFAULT_PROMPT 0 #define MENU_DEFAULT_TIMEOUT 0 #define MAX_DUMP_VOLUMES 32 +#define SECURE_BOOT_DISABLED 0 +#define SECURE_BOOT_ENABLED 1 +#define SECURE_BOOT_AUTO 2 + /* Internal component load address type */ typedef uint64_t address_t; diff --git a/zipl/man/zipl.8 b/zipl/man/zipl.8 index 9891975..2f873f3 100644 --- a/zipl/man/zipl.8 +++ b/zipl/man/zipl.8 @@ -352,6 +352,21 @@ whether they contain a dump signature or not. This option can only be used together with .BR \-\-mvdump . +.TP +.BR "\-S " " or " "\-\-secure " +Control the zIPL secure boot support. + can take one of three values: + + auto (default) + Write signatures if available and supported by the system. + 1 + Signatures are written independent of support indicated by the local + system. Also missing signatures for stage 3 and kernel IPL files + will result in an error. + 0 + No signatures will be written. + + .SH EXAMPLE 1. Scenario: prepare disk for booting a Linux kernel image using the following parameters: diff --git a/zipl/man/zipl.conf.5 b/zipl/man/zipl.conf.5 index d4877d8..71d1252 100644 --- a/zipl/man/zipl.conf.5 +++ b/zipl/man/zipl.conf.5 @@ -82,6 +82,8 @@ below). .br defaultmenu = menu1 .br +secure = auto +.br [linux] .br @@ -517,6 +519,31 @@ An optional hexadecimal address may be provided to load the kernel to a non-default memory location. .PP +.B secure += +.IR auto / 1 / 0 +(configuration only) +.IP +.B Configuration section: +.br +Control the zIPL secure boot support. +Set this option to one of the following: +.IP " - " 12 +.BR auto: +Write signatures if available and supported by the system. +.IP " - " 12 +.BR 1: +Signatures are written independent of support indicated by the local system. +Also missing signatures for stage 3 and kernel IPL files will result in an error. +.IP " - " 12 +.BR 0: +No signatures will be written. + +The default value for +.B 'secure' +is auto. +.PP + .B segment = .IR segment\-file , address diff --git a/zipl/src/Makefile b/zipl/src/Makefile index be14fce..bed970c 100644 --- a/zipl/src/Makefile +++ b/zipl/src/Makefile @@ -15,8 +15,9 @@ objects = misc.o error.o scan.o job.o boot.o bootmap.o disk.o \ zipl_helpers = $(basename $(wildcard zipl_helper.*.c)) chreipl_helpers = $(subst zipl_,chreipl_, $(zipl_helpers)) +zipl_stage3 = ../boot/stage3.bin -all: zipl $(chreipl_helpers) +all: zipl $(chreipl_helpers) $(zipl_stage3) zipl: $(objects) $(libs) @@ -33,6 +34,7 @@ install: all $(INSTALL) -m 755 $(zipl_helpers) $(chreipl_helpers) \ $(DESTDIR)$(TOOLS_LIBDIR) $(CP) --no-dereference $(chreipl_helpers) $(DESTDIR)$(TOOLS_LIBDIR) + $(CP) --no-dereference $(zipl_stage3) $(DESTDIR)$(TOOLS_LIBDIR) clean: rm -f *.o $(zipl_helpers) $(chreipl_helpers) zipl @@ -46,3 +48,6 @@ clean: ../boot/data.o: make -C ../boot data.o + +../boot/stage3.bin: + make -C ../boot stage3.bin diff --git a/zipl/src/boot.c b/zipl/src/boot.c index 279d246..b13bcb7 100644 --- a/zipl/src/boot.c +++ b/zipl/src/boot.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "../boot/data.h" #include "boot.h" @@ -68,20 +70,17 @@ boot_check_data(void) return 0; } -/* Export stage 3 size for partition dump with dump kernel */ -size_t -get_stage3_size() -{ - return DATA_SIZE(stage3); -} - -/* Create a stage 3 loader in memory. +/* + * Create a stage 3 parameter block in memory. * Upon success, return 0 and set BUFFER to point to the data buffer and set - * BYTECOUNT to contain the loader size in bytes. Return non-zero otherwise. */ + * BYTECOUNT to contain the parameter block size in bytes. + * Return non-zero otherwise. + */ int -boot_get_stage3(void** buffer, size_t* bytecount, address_t parm_addr, - address_t initrd_addr, size_t initrd_len, address_t image_addr, - int extra_parm, uint16_t flags) +boot_get_stage3_parms(void **buffer, size_t *bytecount, address_t parm_addr, + address_t initrd_addr, size_t initrd_len, + address_t image_addr, int extra_parm, uint16_t flags, + size_t image_len) { struct boot_stage3_params params; void* data; @@ -92,9 +91,10 @@ boot_get_stage3(void** buffer, size_t* bytecount, address_t parm_addr, return -1; } /* Get memory */ - data = misc_malloc(DATA_SIZE(stage3)); + data = misc_malloc(sizeof(params)); if (data == NULL) return -1; + memset(data, 0, sizeof(params)); /* Prepare params section */ params.parm_addr = (uint64_t) parm_addr; params.initrd_addr = (uint64_t) initrd_addr; @@ -102,11 +102,12 @@ boot_get_stage3(void** buffer, size_t* bytecount, address_t parm_addr, params.load_psw = (uint64_t)(image_addr | PSW_LOAD); params.extra_parm = (uint64_t) extra_parm; params.flags = flags; + params.image_len = (uint64_t) image_len; + params.image_addr = (uint64_t) image_addr; /* Initialize buffer */ - memcpy(data, DATA_ADDR(stage3), DATA_SIZE(stage3)); - memcpy(data, ¶ms, sizeof(struct boot_stage3_params)); + memcpy(data, ¶ms, sizeof(params)); *buffer = data; - *bytecount = DATA_SIZE(stage3); + *bytecount = sizeof(params); return 0; } diff --git a/zipl/src/bootmap.c b/zipl/src/bootmap.c index 1b71b03..456c2ef 100644 --- a/zipl/src/bootmap.c +++ b/zipl/src/bootmap.c @@ -19,6 +19,7 @@ #include #include "lib/util_part.h" +#include "lib/util_path.h" #include "boot.h" #include "bootmap.h" @@ -117,6 +118,22 @@ check_menu_positions(struct job_menu_data* menu, char* name, return 0; } +static bool +check_secure_boot_support(void) +{ + unsigned int val; + FILE *fp; + + fp = fopen(ZIPL_SIPL_PATH, "r"); + if (!fp) + return false; + + fscanf(fp, "%d", &val); + fclose(fp); + + return val ? true : false; +} + /* Write COUNT elements of the blocklist specified by LIST as a linked list * of segment table blocks to the file identified by file descriptor FD. Upon @@ -199,24 +216,21 @@ add_program_table(int fd, disk_blockptr_t* table, int entries, return rc; } - struct component_entry { uint8_t data[23]; uint8_t type; - union { - uint64_t load_address; - uint64_t load_psw; - } address; + component_data compdat; } __attribute((packed)); typedef enum { component_execute = 0x01, - component_load = 0x02 + component_load = 0x02, + component_signature = 0x03 } component_type; static void create_component_entry(void* buffer, disk_blockptr_t* pointer, - component_type type, uint64_t address, + component_type type, component_data data, struct disk_info* info) { struct component_entry* entry; @@ -228,10 +242,15 @@ create_component_entry(void* buffer, disk_blockptr_t* pointer, case component_load: bootmap_store_blockptr(&entry->data, pointer, info); - entry->address.load_address = address; + entry->compdat.load_address = data.load_address; break; case component_execute: - entry->address.load_psw = address; + entry->compdat.load_psw = data.load_psw; + break; + case component_signature: + bootmap_store_blockptr(&entry->data, pointer, + info); + entry->compdat.sig_head = data.sig_head; break; } } @@ -267,7 +286,7 @@ struct component_loc { static int add_component_file(int fd, const char* filename, address_t load_address, - off_t offset, void* component, int add_files, + size_t trailer, void *component, int add_files, struct disk_info* info, struct job_target_data* target, struct component_loc *location) { @@ -279,8 +298,6 @@ add_component_file(int fd, const char* filename, address_t load_address, size_t size; blocknum_t count; int rc; - int from; - unsigned int to; if (add_files) { /* Read file to buffer */ @@ -289,17 +306,10 @@ add_component_file(int fd, const char* filename, address_t load_address, error_text("Could not read file '%s'", filename); return rc; } - /* Ensure minimum size */ - if (size <= (size_t) offset) { - error_reason("File '%s' is too small (has to be " - "greater than %ld bytes)", filename, - (long) offset); - free(buffer); - return -1; - } + size -= trailer; /* Write buffer */ - count = disk_write_block_buffer(fd, 0, buffer + offset, - size - offset, &list, info); + count = disk_write_block_buffer(fd, 0, buffer, + size, &list, info); free(buffer); if (count == 0) { error_text("Could not write to bootmap file"); @@ -321,20 +331,7 @@ add_component_file(int fd, const char* filename, address_t load_address, disk_free_info(file_info); if (count == 0) return -1; - if (count * info->phy_block_size <= (size_t) offset) { - error_reason("File '%s' is too small (has to be " - "greater than %ld bytes)", filename, - (long) offset); - free(list); - return -1; - } - if (offset > 0) { - /* Shorten list by offset */ - from = offset / info->phy_block_size; - count -= from; - for (to=0; to < count; to++, from++) - list[to] = list[from]; - } + count -= DIV_ROUND_UP(trailer, info->phy_block_size); } /* Fill in component location */ loc.addr = load_address; @@ -346,7 +343,7 @@ add_component_file(int fd, const char* filename, address_t load_address, free(list); if (rc == 0) { create_component_entry(component, &segment, component_load, - load_address, info); + (component_data) load_address, info); /* Return location if requested */ if (location != NULL) *location = loc; @@ -354,11 +351,10 @@ add_component_file(int fd, const char* filename, address_t load_address, return rc; } - static int -add_component_buffer(int fd, void* buffer, size_t size, address_t load_address, +add_component_buffer(int fd, void* buffer, size_t size, component_data data, void* component, struct disk_info* info, - struct component_loc *location) + struct component_loc *location, int type) { struct component_loc loc; disk_blockptr_t segment; @@ -372,17 +368,21 @@ add_component_buffer(int fd, void* buffer, size_t size, address_t load_address, error_text("Could not write to bootmap file"); return -1; } - /* Fill in component location */ - loc.addr = load_address; - loc.size = count * info->phy_block_size; + if (type == component_load) { + /* Fill in component location */ + loc.addr = data.load_address; + loc.size = count * info->phy_block_size; + } else { + loc.addr = 0; + loc.size = 0; + } /* Try to compact list */ count = disk_compact_blocklist(list, count, info); /* Write segment table */ rc = add_segment_table(fd, list, count, &segment, info); free(list); if (rc == 0) { - create_component_entry(component, &segment, component_load, - load_address, info); + create_component_entry(component, &segment, type, data, info); /* Return location if requested */ if (location != NULL) *location = loc; @@ -391,6 +391,30 @@ add_component_buffer(int fd, void* buffer, size_t size, address_t load_address, } +static int +add_dummy_buffer(int fd, size_t size, address_t addr, void *component, + struct disk_info *info, struct component_loc *comp_loc) +{ + char *buffer; + int rc; + + buffer = misc_malloc(size); + if (buffer == NULL) + return -1; + + memset(buffer, 0, size); + rc = add_component_buffer(fd, buffer, size, + (component_data) (uint64_t) addr, + component, info, comp_loc, component_load); + if (rc) { + free(buffer); + return rc; + } + free(buffer); + return 0; +} + + static void print_components(const char *name[], struct component_loc *loc, int num) { @@ -409,23 +433,86 @@ print_components(const char *name[], struct component_loc *loc, int num) } } +static int +extract_signature(char *filename, void **ret_signature, + struct signature_header *sig_head) +{ + struct file_signature *file_sig; + size_t signature_size = 0; + void *signature; + char *buffer; + size_t size; + + if (misc_read_file(filename, &buffer, &size, 0)) + return 0; + + file_sig = (void *) buffer + size - sizeof(*file_sig); + if (memcmp(file_sig->magic, SIGNATURE_MAGIC, sizeof(file_sig->magic)) + != 0) + goto out; + + signature = misc_malloc(file_sig->sig_len); + if (signature == NULL) + goto out; + signature_size = file_sig->sig_len; + + memcpy(signature, buffer + size - signature_size - sizeof(*file_sig), + signature_size); + + *ret_signature = signature; + sig_head->length = signature_size; + + switch (file_sig->id_type) { + case PKEY_ID_PKCS7: + sig_head->format = PKCS7_FORMAT; + break; + default: + error_text("Unsupported signature type %02x", + file_sig->id_type); + signature_size = 0; + goto out; + } + /* return size of signature and corresponding header */ + signature_size += sizeof(*file_sig); +out: + free(buffer); + return signature_size; +} + +static void +check_remaining_filesize(size_t filesize, size_t signature_size, + struct disk_info *info, char *filename) +{ + if ((filesize - signature_size) % info->phy_block_size) { + fprintf(stderr, + "Warning: Size of signed file %s is not a multiple of the disk block size\n", + filename); + } +} static int add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, int verbose, int add_files, component_header_type type, - struct disk_info* info, struct job_target_data* target) + struct disk_info* info, struct job_target_data* target, + int is_secure) { + struct component_loc comp_loc[10]; + struct signature_header sig_head; + size_t ramdisk_size, image_size; + bool secure_boot_supported; + size_t stage3_params_size; + const char *comp_name[10]; + size_t signature_size; + int offset, flags = 0; + void *stage3_params; struct stat stats; - void* table; - void* stage3; - size_t stage3_size; - const char *comp_name[4] = {"kernel image", "parmline", - "initial ramdisk", "internal loader"}; - struct component_loc comp_loc[4]; + void *signature; + int comp_nr = 0; + void *table; int rc; - int offset, flags = 0; memset(comp_loc, 0, sizeof(comp_loc)); + memset(&sig_head, 0, sizeof(sig_head)); table = misc_malloc(info->phy_block_size); if (table == NULL) return -1; @@ -456,55 +543,189 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, return -1; } } - if (info->type == disk_type_scsi) + ramdisk_size = stats.st_size; + if (info->type == disk_type_scsi) { flags |= STAGE3_FLAG_SCSI; + /* + * Add dummy components for stage 3 heap and stack to block the + * associated memory areas against firmware use. + */ + rc = add_dummy_buffer(fd, STAGE3_HEAP_SIZE, STAGE3_HEAP_ADDRESS, + VOID_ADD(table, offset), info, + &comp_loc[comp_nr]); + if (rc) { + error_text("Could not add stage3 HEAP dummy"); + free(table); + return rc; + } + comp_name[comp_nr] = "heap area"; + offset += sizeof(struct component_entry); + comp_nr++; + rc = add_dummy_buffer(fd, STAGE3_STACK_SIZE, + STAGE3_STACK_ADDRESS, + VOID_ADD(table, offset), info, + &comp_loc[comp_nr]); + if (rc) { + error_text("Could not add stage3 STACK dummy"); + free(table); + return rc; + } + comp_name[comp_nr] = "stack area"; + offset += sizeof(struct component_entry); + comp_nr++; + } if (ipl->is_kdump) flags |= STAGE3_FLAG_KDUMP; + /* Get kernel file size */ + if (stat(ipl->image, &stats)) { + error_reason(strerror(errno)); + error_text("Could not get information for file '%s'", + ipl->image); + free(table); + return -1; + } + image_size = stats.st_size; + secure_boot_supported = check_secure_boot_support(); + signature_size = extract_signature(ZIPL_STAGE3_PATH, &signature, + &sig_head); + if (signature_size && + (is_secure == SECURE_BOOT_ENABLED || + (is_secure == SECURE_BOOT_AUTO && secure_boot_supported))) { + if (verbose) + printf(" signature for.....: %s\n", ZIPL_STAGE3_PATH); + + rc = add_component_buffer(fd, signature, sig_head.length, + (component_data)sig_head, + VOID_ADD(table, offset), info, + &comp_loc[comp_nr], + component_signature); + if (rc) { + error_text("Could not add stage3 signature"); + free(table); + return rc; + } + comp_name[comp_nr] = "loader signature"; + offset += sizeof(struct component_entry); + comp_nr++; + free(signature); + } else if (is_secure == SECURE_BOOT_ENABLED) { + /* + * If secure boot is forced and we have failed to extract a + * signature for the stage 3 loader zipl will abort with an + * error message + */ + error_text("Could not install Secure Boot IPL records"); + error_reason("Missing signature in internal loader file %s", + ZIPL_STAGE3_PATH); + free(table); + return -1; + } + /* Add stage 3 loader to bootmap */ - rc = boot_get_stage3(&stage3, &stage3_size, ipl->parm_addr, - ipl->ramdisk_addr, (size_t) stats.st_size, - ipl->is_kdump ? ipl->image_addr + 0x10 : - ipl->image_addr, - (info->type == disk_type_scsi) ? 0 : 1, - flags); + rc = add_component_file(fd, ZIPL_STAGE3_PATH, DEFAULT_STAGE3_ADDRESS, + signature_size, VOID_ADD(table, offset), 1, + info, target, &comp_loc[comp_nr]); if (rc) { + error_text("Could not add internal loader file '%s'", + ZIPL_STAGE3_PATH); free(table); return rc; } - rc = add_component_buffer(fd, stage3, stage3_size, - DEFAULT_STAGE3_ADDRESS, - VOID_ADD(table, offset), info, &comp_loc[3]); - free(stage3); + offset += sizeof(struct component_entry); + comp_name[comp_nr] = "internal loader"; + comp_nr++; + + /* Add stage 3 parameter to bootmap */ + rc = boot_get_stage3_parms(&stage3_params, &stage3_params_size, + ipl->parm_addr, ipl->ramdisk_addr, + ramdisk_size, + ipl->is_kdump ? ipl->image_addr + 0x10 : + ipl->image_addr, + (info->type == disk_type_scsi) ? 0 : 1, + flags, image_size); if (rc) { - error_text("Could not add stage 3 boot loader"); + free(table); + return rc; + } + rc = add_component_buffer(fd, stage3_params, stage3_params_size, + (component_data) (uint64_t) + DEFAULT_STAGE3_PARAMS_ADDRESS, + VOID_ADD(table, offset), info, + &comp_loc[comp_nr], component_load); + free(stage3_params); + if (rc) { + error_text("Could not add parameters"); free(table); return -1; } offset += sizeof(struct component_entry); + comp_name[comp_nr] = "parameters"; + comp_nr++; + /* Add kernel image */ if (verbose) { printf(" kernel image......: %s\n", ipl->image); } + signature_size = extract_signature(ipl->image, &signature, &sig_head); + if (signature_size && + (is_secure == SECURE_BOOT_ENABLED || + (is_secure == SECURE_BOOT_AUTO && secure_boot_supported))) { + if (verbose) + printf(" signature for.....: %s\n", ipl->image); + + rc = add_component_buffer(fd, signature, sig_head.length, + (component_data)sig_head, + VOID_ADD(table, offset), info, + &comp_loc[comp_nr], + component_signature); + if (rc) { + error_text("Could not add image signature"); + free(table); + return rc; + } + comp_name[comp_nr] = "image signature"; + offset += sizeof(struct component_entry); + comp_nr++; + free(signature); + check_remaining_filesize(image_size, signature_size, info, + ipl->image); + } else if (is_secure == SECURE_BOOT_ENABLED) { + /* + * If secure boot is forced and we have failed to extract a + * signature for the kernel image zipl will abort with an + * error message + */ + error_text("Could not install Secure Boot IPL records"); + error_reason("Missing signature in image file %s", + ipl->image); + free(table); + return -1; + } + rc = add_component_file(fd, ipl->image, ipl->image_addr, - KERNEL_HEADER_SIZE, VOID_ADD(table, offset), - add_files, info, target, &comp_loc[0]); + signature_size, VOID_ADD(table, offset), + add_files, info, target, &comp_loc[comp_nr]); if (rc) { error_text("Could not add image file '%s'", ipl->image); free(table); return rc; } offset += sizeof(struct component_entry); + comp_name[comp_nr] = "kernel image"; + comp_nr++; + + /* Add kernel parmline */ if (ipl->parmline != NULL) { - /* Add kernel parmline */ if (verbose) { printf(" kernel parmline...: '%s'\n", ipl->parmline); } rc = add_component_buffer(fd, ipl->parmline, strlen(ipl->parmline) + 1, - ipl->parm_addr, + (component_data) ipl->parm_addr, VOID_ADD(table, offset), - info, &comp_loc[1]); + info, &comp_loc[comp_nr], + component_load); if (rc) { error_text("Could not add parmline '%s'", ipl->parmline); @@ -512,14 +733,45 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, return -1; } offset += sizeof(struct component_entry); + comp_name[comp_nr] = "parmline"; + comp_nr++; } /* finally add ramdisk */ if (ipl->ramdisk != NULL) { + signature_size = extract_signature(ipl->ramdisk, &signature, + &sig_head); + if (signature_size && + (is_secure == SECURE_BOOT_ENABLED || + (is_secure == SECURE_BOOT_AUTO && + secure_boot_supported))) { + if (verbose) { + printf(" signature for.....: %s\n", + ipl->ramdisk); + } + rc = add_component_buffer(fd, signature, + sig_head.length, + (component_data)sig_head, + VOID_ADD(table, offset), info, + &comp_loc[comp_nr], + component_signature); + if (rc) { + error_text("Could not add ramdisk signature"); + free(table); + return rc; + } + comp_name[comp_nr] = "ramdisk signature"; + offset += sizeof(struct component_entry); + comp_nr++; + free(signature); + check_remaining_filesize(ramdisk_size, signature_size, + info, ipl->ramdisk); + } rc = add_component_file(fd, ipl->ramdisk, - ipl->ramdisk_addr, 0, + ipl->ramdisk_addr, signature_size, VOID_ADD(table, offset), - add_files, info, target, &comp_loc[2]); + add_files, info, target, + &comp_loc[comp_nr]); if (rc) { error_text("Could not add ramdisk '%s'", ipl->ramdisk); @@ -527,13 +779,16 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, return -1; } offset += sizeof(struct component_entry); + comp_name[comp_nr] = "initial ramdisk"; + comp_nr++; } if (verbose) - print_components(comp_name, comp_loc, 4); + print_components(comp_name, comp_loc, comp_nr); /* Terminate component table */ create_component_entry(VOID_ADD(table, offset), NULL, component_execute, - ZIPL_STAGE3_ENTRY_ADDRESS | PSW_LOAD, + (component_data) (uint64_t) + (ZIPL_STAGE3_ENTRY_ADDRESS | PSW_LOAD), info); /* Write component table */ rc = disk_write_block_aligned(fd, table, info->phy_block_size, @@ -584,7 +839,8 @@ if (rc) { print_components(comp_name, comp_loc, 1); /* Terminate component table */ create_component_entry(VOID_ADD(table, offset), NULL, - component_execute, PSW_DISABLED_WAIT, info); + component_execute, (component_data) (uint64_t) + PSW_DISABLED_WAIT, info); /* Write component table */ rc = disk_write_block_aligned(fd, table, info->phy_block_size, program, info); @@ -674,7 +930,7 @@ add_dump_program(int fd, struct job_dump_data* dump, return rc; ipl.parm_addr = dump->parm_addr; return add_ipl_program(fd, &ipl, program, verbose, 1, - type, info, target); + type, info, target, SECURE_BOOT_DISABLED); } @@ -711,7 +967,7 @@ build_program_table(int fd, struct job_data* job, disk_blockptr_t* pointer, rc = add_ipl_program(fd, &job->data.ipl, &table[0], verbose || job->command_line, job->add_files, component_header, - info, &job->target); + info, &job->target, job->is_secure); break; case job_segment: if (job->command_line) @@ -762,7 +1018,8 @@ build_program_table(int fd, struct job_data* job, disk_blockptr_t* pointer, &table[job->data.menu.entry[i].pos], verbose || job->command_line, job->add_files, component_header, - info, &job->target); + info, &job->target, + job->is_secure); break; case job_print_usage: case job_print_version: @@ -913,7 +1170,9 @@ bootmap_create(struct job_data *job, disk_blockptr_t *program_table, ulong size; ulong unused_size; - size = DIV_ROUND_UP(get_stage3_size(), info->phy_block_size); + /* Use approximated stage 3 size as starting point */ + size = MINIMUM_ADDRESS; + /* Ramdisk */ if (job->data.dump.ramdisk != NULL) { if (stat(job->data.dump.ramdisk, &st)) diff --git a/zipl/src/job.c b/zipl/src/job.c index 22d2549..2d8de8f 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -52,11 +52,12 @@ static struct option options[] = { { "dry-run", no_argument, NULL, '0'}, { "force", no_argument, NULL, 'f'}, { "kdump", required_argument, NULL, 'k'}, + { "secure", required_argument, NULL, 'S'}, { NULL, 0, NULL, 0 } }; /* Command line option abbreviations */ -static const char option_string[] = "-c:b:t:i:r:p:P:d:D:M:s:m:hHnVvaT:fk:"; +static const char option_string[] = "-c:b:t:i:r:p:P:d:D:M:s:S:m:hHnVvaT:fk:"; struct command_line { char* data[SCAN_KEYWORD_NUM]; @@ -215,6 +216,11 @@ get_command_line(int argc, char* argv[], struct command_line* line) } else cmdline.menu = optarg; break; + case 'S': + is_keyword = 1; + rc = store_option(&cmdline, scan_keyword_secure, + optarg); + break; case 'h': cmdline.help = 1; break; @@ -511,7 +517,7 @@ get_ipl_components(struct job_ipl_data *ipl, struct component_loc **clp, /* Fill in component data */ num = 0; rc = set_cl_element(&cl[num++], "kernel image", ipl->image, - &ipl->image_addr, 0, 0x10000, + &ipl->image_addr, 0, 0, MAXIMUM_PHYSICAL_BLOCKSIZE); if (rc) goto error; @@ -1263,6 +1269,26 @@ type_from_target(char *target, disk_type_t *type) } } +static int +set_secure_ipl(char *keyword, struct job_data *job) +{ + if (strcmp(keyword, "auto") == 0) { + job->is_secure = SECURE_BOOT_AUTO; + } else if (strcmp(keyword, "0") == 0) { + job->is_secure = SECURE_BOOT_DISABLED; + } else if (strcmp(keyword, "1") == 0) { + if (job->target.targettype != disk_type_scsi) { + error_reason("Secure boot forced for non-SCSI disk type"); + return -1; + } + job->is_secure = SECURE_BOOT_ENABLED; + } else { + error_reason("Invalid secure boot setting '%s'", + keyword); + return -1; + } + return 0; +} static int get_job_from_section_data(char* data[], struct job_data* job, char* section) @@ -1345,6 +1371,13 @@ get_job_from_section_data(char* data[], struct job_data* job, char* section) return -1; } } + /* Fill in secure boot */ + if (data[(int) scan_keyword_secure] != NULL) { + rc = set_secure_ipl(data[(int) scan_keyword_secure], + job); + if (rc) + return rc; + } break; case section_ipl_tape: /* Tape IPL job */ @@ -1502,6 +1535,13 @@ get_menu_job(struct scan_token* scan, char* menu, struct job_data* job) job->data.menu.timeout = atol(scan[i].content.keyword.value); break; + case scan_keyword_secure: + rc = set_secure_ipl( + scan[i].content.keyword.value, + job); + if (rc) + return rc; + break; case scan_keyword_target: job->target.bootmap_dir = misc_strdup( scan[i].content.keyword.value); diff --git a/zipl/src/misc.c b/zipl/src/misc.c index fd16810..057c9a0 100644 --- a/zipl/src/misc.c +++ b/zipl/src/misc.c @@ -22,7 +22,6 @@ #include "error.h" #include "misc.h" - /* Allocate SIZE bytes of memory. Upon success, return pointer to memory. * Return NULL otherwise. */ void * diff --git a/zipl/src/scan.c b/zipl/src/scan.c index fe72e9a..e57361e 100644 --- a/zipl/src/scan.c +++ b/zipl/src/scan.c @@ -45,45 +45,45 @@ enum scan_key_state scan_key_table[SCAN_SECTION_NUM][SCAN_KEYWORD_NUM] = { * ult to tofs e mete file isk ent et pt out ultm dump * rs enu * - * targ targ targ targ targ defa kdum - * etba etty etge etbl etof ulta p + * targ targ targ targ targ defa kdum secu + * etba etty etge etbl etof ulta p re * se pe omet ocks fset uto * ry ize */ /* default auto */ {opt, inv, inv, inv, inv, inv, inv, inv, req, opt, opt, inv, inv, inv, - opt, opt, opt, opt, opt, opt, inv}, + opt, opt, opt, opt, opt, opt, inv, opt}, /* default menu */ {inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req, inv, inv, - inv, inv, inv, inv, inv, inv, inv}, + inv, inv, inv, inv, inv, inv, inv, opt}, /* default section */ {req, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, - inv, inv, inv, inv, inv, inv, inv}, + inv, inv, inv, inv, inv, inv, inv, opt}, /* ipl */ {inv, inv, inv, req, opt, opt, opt, inv, req, inv, inv, inv, inv, inv, - opt, opt, opt, opt, opt, inv, opt}, + opt, opt, opt, opt, opt, inv, opt, opt}, /* segment load */ {inv, inv, inv, inv, inv, inv, inv, req, req, inv, inv, inv, inv, inv, - inv, inv, inv, inv, inv, inv, inv}, + inv, inv, inv, inv, inv, inv, inv, inv}, /* part dump */ {inv, req, inv, inv, inv, inv, inv, inv, opt, inv, inv, inv, inv, inv, - inv, inv, inv, inv, inv, inv, inv}, + inv, inv, inv, inv, inv, inv, inv, inv}, /* fs dump */ {inv, inv, req, inv, opt, opt, inv, inv, req, inv, inv, inv, inv, inv, - inv, inv, inv, inv, inv, inv, inv}, + inv, inv, inv, inv, inv, inv, inv, inv}, /* ipl tape */ {inv, inv, inv, req, opt, opt, opt, inv, inv, inv, inv, inv, req, inv, - inv, inv, inv, inv, inv, inv, inv}, + inv, inv, inv, inv, inv, inv, inv, inv}, /* multi volume dump */ {inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req, - inv, inv, inv, inv, inv, inv, inv} + inv, inv, inv, inv, inv, inv, inv, inv} }; /* Determines which keyword may be present in a menu section */ enum scan_key_state scan_menu_key_table[SCAN_KEYWORD_NUM] = { /* menu section */ opt, inv, inv, inv, inv, inv, inv, inv, req, opt, opt, inv, inv, inv, - opt, opt, opt, opt, opt, inv, inv + opt, opt, opt, opt, opt, inv, inv, opt }; /* Mapping of keyword IDs to strings */ @@ -111,6 +111,7 @@ static const struct { { "timeout", scan_keyword_timeout}, { "tape", scan_keyword_tape}, { "kdump", scan_keyword_kdump}, + { "secure", scan_keyword_secure}, }; /* List of keywords that are used without an assignment */ @@ -1863,6 +1864,7 @@ scan_build_automenu(struct scan_token* scan) /* defaultmenu */ 1 + /* menu heading */ 1 + /* keyword default,prompt,timeout */ 3 + + /* keyword secure */ 1 + /* target keywords*/ num_targets + /* missing target definitions */ num_sections * num_targets + /* number assigment */ num_sections; @@ -1978,6 +1980,14 @@ scan_build_automenu(struct scan_token* scan) db_keyword[i])) goto err; } + /* secure= */ + i = (int) scan_keyword_secure; + if (db_keyword[i]) { + if (scan_append_keyword_assignment(new_scan, &i_new, + scan_keyword_secure, + db_keyword[i])) + goto err; + } /* target= */ /* targetbase= */ /* targetgeometry= */ -- 2.21.3 From 100c89a273fb4bcc1deb93aaf77ac9264d9ac3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 2 May 2019 15:46:39 +0200 Subject: [PATCH 19/56] zipl: update stage3 objcopy command for gcc9 (#1659401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The objcopy command for stage3.bin needs to take the new rodata section into account. See also PR #60. Signed-off-by: Dan Horák --- zipl/boot/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/zipl/boot/Makefile b/zipl/boot/Makefile index da7e95f..a049797 100644 --- a/zipl/boot/Makefile +++ b/zipl/boot/Makefile @@ -85,6 +85,7 @@ stage3.bin: stage3.exec --only-section=.ex_table \ --only-section=.data \ --only-section=.rodata.str1.2 \ + --only-section=.rodata.cst8 \ --only-section=.rodata \ --only-section=.stage2dump.tail \ --only-section=.eckd2dump_mv.tail \ -- 2.21.3 From 78f0479055e025ba849766aa2d60c22a7bcdbfba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Tue, 21 May 2019 13:49:09 +0200 Subject: [PATCH 20/56] s390-tools: Add zcryptstats tool (#1658756) Description: The zcryptstats tool displays usage statistics of IBM Crypto Express adapters. It obtains cryptographic performance measurement data periodically and displays the data for each cryptographic device for each interval. A cryptographic device can be either a card device or queue device (APQN). Upstream-ID: 5f2ddad6a83e04f6a64ea39619148c80e56c0814 Upstream-ID: b165500b6987f3fcbbb6daee6d84d66af7fbd41f Upstream-ID: 58189b878697fef3bf405fdd864bf40f2149ccc0 Upstream-ID: 7b4a05e5a36fd36ba1866047f6b4814f127ec846 Upstream-ID: 8be43a1f16923da037d5f5bffca73c433ca1231b Upstream-ID: 43fcb694bfd2e4515ce131ea3aba2f81e8437757 --- README.md | 1 + include/lib/util_rec.h | 3 + libutil/util_rec.c | 89 +- libutil/util_rec_example.c | 7 + zconf/zcrypt/Makefile | 7 +- zconf/zcrypt/zcryptstats.8 | 247 ++++ zconf/zcrypt/zcryptstats.c | 2418 ++++++++++++++++++++++++++++++++++++ 7 files changed, 2768 insertions(+), 4 deletions(-) create mode 100644 zconf/zcrypt/zcryptstats.8 create mode 100644 zconf/zcrypt/zcryptstats.c diff --git a/README.md b/README.md index 08c27d7..af1024e 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Package contents or show encryption state of attached LUNs. - lszcrypt: Show Information about zcrypt devices and configuration. - chzcrypt: Modify the zcrypt configuration. + - zcryptstats: Display usage statistics of IBM Crypto Express adapters. - znetconf: List and configure network devices for s390 network adapters. - cio_ignore: Query and modify the contents of the CIO device driver blacklist. diff --git a/include/lib/util_rec.h b/include/lib/util_rec.h index a44461a..7a0c2f6 100644 --- a/include/lib/util_rec.h +++ b/include/lib/util_rec.h @@ -68,5 +68,8 @@ const char *util_rec_get(struct util_rec *rec, const char *key); void util_rec_print_hdr(struct util_rec *rec); void util_rec_print(struct util_rec *rec); +void util_rec_print_separator(struct util_rec *rec); + +void util_rec_set_indent(struct util_rec *rec, int indent); #endif /** LIB_UTIL_REC_H @} */ diff --git a/libutil/util_rec.c b/libutil/util_rec.c index 621bebc..c216cf9 100644 --- a/libutil/util_rec.c +++ b/libutil/util_rec.c @@ -64,6 +64,7 @@ struct rec_fmt { int argz_sep; } csv_p; } d; + int indent; }; /* @@ -122,9 +123,50 @@ struct util_rec *util_rec_new_wide(const char *hdr_sep) rec->fmt.type = REC_FMT_WIDE; rec->fmt.d.wide_p.hdr_sep = util_strdup(hdr_sep); rec->fmt.d.wide_p.argz_sep = ','; + rec->fmt.indent = 0; return rec; } +/* + * Print the indentation characters + */ +static inline void rec_print_indention(int indent) +{ + if (indent <= 0) + return; + + printf("%*s", indent, ""); +} + +/* + * Print record separator in "wide" output format + */ +static void rec_print_wide_separator(struct util_rec *rec) +{ + const char *hdr_sep = rec->fmt.d.wide_p.hdr_sep; + int size = 0, field_count = 0; + struct util_rec_fld *fld; + char *buf; + + if (!hdr_sep) + return; + + util_list_iterate(rec->list, fld) { + if (fld->hdr) { + size += fld->width; + field_count++; + } + } + + size += field_count - 1; + buf = util_malloc(size + 1); + memset(buf, (int)hdr_sep[0], size); + buf[size] = 0; + rec_print_indention(rec->fmt.indent); + printf("%s\n", buf); + free(buf); +} + /* * Print record header in "wide" output format */ @@ -135,6 +177,7 @@ static void rec_print_wide_hdr(struct util_rec *rec) struct util_rec_fld *fld; char *buf; + rec_print_indention(rec->fmt.indent); util_list_iterate(rec->list, fld) { if (col_nr) printf(" "); @@ -156,6 +199,7 @@ static void rec_print_wide_hdr(struct util_rec *rec) buf = util_malloc(size + 1); memset(buf, (int)hdr_sep[0], size); buf[size] = 0; + rec_print_indention(rec->fmt.indent); printf("%s\n", buf); free(buf); } @@ -172,6 +216,7 @@ void rec_print_wide(struct util_rec *rec) int fld_count = 0; char *entry; + rec_print_indention(rec->fmt.indent); util_list_iterate(rec->list, fld) { if (!fld->hdr) continue; @@ -225,6 +270,7 @@ struct util_rec *util_rec_new_long(const char *hdr_sep, const char *col_sep, rec->fmt.d.long_p.key_size = key_size; rec->fmt.d.long_p.val_size = val_size; rec->fmt.d.long_p.argz_sep = ' '; + rec->fmt.indent = 0; return rec; } @@ -241,6 +287,7 @@ static void rec_print_long_hdr(struct util_rec *rec) fld = rec_get_fld(rec, p->key); util_assert(fld != NULL, "Record not found\n"); util_assert(fld->hdr != NULL, "Header for field not found\n"); + rec_print_indention(rec->fmt.indent); if (p->col_sep) { printf("%-*s %s %-*s\n", p->key_size, fld->hdr, p->col_sep, fld->width, fld->val); @@ -255,6 +302,7 @@ static void rec_print_long_hdr(struct util_rec *rec) buf = util_malloc(len + 1); memset(buf, p->hdr_sep[0], len); buf[len] = 0; + rec_print_indention(rec->fmt.indent); printf("%s\n", buf); free(buf); } @@ -277,19 +325,24 @@ static void rec_print_long(struct util_rec *rec) continue; if (!fld->val) continue; + rec_print_indention(rec->fmt.indent); item = argz_next(fld->val, fld->len, item); if (p->col_sep) { printf(" %-*s %s %s\n", p->key_size - 8, fld->hdr, p->col_sep, item); - while ((item = argz_next(fld->val, fld->len, item))) + while ((item = argz_next(fld->val, fld->len, item))) { + rec_print_indention(rec->fmt.indent); printf(" %-*s %c %s\n", p->key_size - 8, "", p->argz_sep, item); + } } else { printf(" %-*s %s\n", p->key_size - 8, fld->hdr, fld->val); - while ((item = argz_next(fld->val, fld->len, item))) + while ((item = argz_next(fld->val, fld->len, item))) { + rec_print_indention(rec->fmt.indent); printf(" %-*s %s\n", p->key_size - 8, "", item); + } } } printf("\n"); @@ -320,6 +373,7 @@ struct util_rec *util_rec_new_csv(const char *col_sep) rec->fmt.type = REC_FMT_CSV; rec->fmt.d.csv_p.col_sep = util_strdup(col_sep); rec->fmt.d.csv_p.argz_sep = ' '; + rec->fmt.indent = 0; return rec; } @@ -332,6 +386,7 @@ void rec_print_csv_hdr(struct util_rec *rec) struct util_rec_fld *fld; int fld_count = 0; + rec_print_indention(rec->fmt.indent); util_list_iterate(rec->list, fld) { if (fld_count) printf("%c", *col_sep); @@ -354,6 +409,7 @@ void rec_print_csv(struct util_rec *rec) int fld_count = 0; char *item = NULL; + rec_print_indention(rec->fmt.indent); util_list_iterate(rec->list, fld) { item = argz_next(fld->val, fld->len, item); if (fld_count) @@ -470,6 +526,24 @@ void util_rec_print_hdr(struct util_rec *rec) } } +/** + * Print record separator according to output format + * + * @param[in] rec Record pointer + */ +void util_rec_print_separator(struct util_rec *rec) +{ + switch (rec->fmt.type) { + case REC_FMT_WIDE: + rec_print_wide_separator(rec); + break; + case REC_FMT_LONG: + break; + case REC_FMT_CSV: + break; + } +} + /** * Set a field value to an argz vector * @@ -537,3 +611,14 @@ const char *util_rec_get(struct util_rec *rec, const char *key) return (fld != NULL) ? fld->val : NULL; } + +/** + * Sets the indentation of the record + * + * @param[in] rec Record pointer + * @param[in] indent Number of characters to indent + */ +void util_rec_set_indent(struct util_rec *rec, int indent) +{ + rec->fmt.indent = indent; +} diff --git a/libutil/util_rec_example.c b/libutil/util_rec_example.c index bc38bb4..801f909 100644 --- a/libutil/util_rec_example.c +++ b/libutil/util_rec_example.c @@ -42,6 +42,8 @@ static void print_records(const char *format, struct util_rec *rec) /* Print the record */ util_rec_print(rec); } + /* Print a separator line (is a nop for long and csv format) */ + util_rec_print_separator(rec); printf("\n"); } @@ -72,6 +74,11 @@ int main(void) print_records("Wide format", rec); util_rec_free(rec); + rec = util_rec_new_wide("-"); + util_rec_set_indent(rec, 4); + print_records("Wide format with indentation", rec); + util_rec_free(rec); + rec = util_rec_new_long("-", ":", "number", 30, 20); print_records("Long format", rec); util_rec_free(rec); diff --git a/zconf/zcrypt/Makefile b/zconf/zcrypt/Makefile index d075f34..f679775 100644 --- a/zconf/zcrypt/Makefile +++ b/zconf/zcrypt/Makefile @@ -1,24 +1,27 @@ include ../../common.mak -all: chzcrypt lszcrypt zcryptctl +all: chzcrypt lszcrypt zcryptctl zcryptstats libs = $(rootdir)/libutil/libutil.a chzcrypt: chzcrypt.o misc.o $(libs) lszcrypt: lszcrypt.o misc.o $(libs) zcryptctl: zcryptctl.o misc.o $(libs) +zcryptstats: zcryptstats.o $(libs) install: all $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 chzcrypt $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 lszcrypt $(DESTDIR)$(BINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zcryptctl $(DESTDIR)$(BINDIR) + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zcryptstats $(DESTDIR)$(BINDIR) $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 644 -c chzcrypt.8 $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 644 -c lszcrypt.8 $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 644 -c zcryptctl.8 $(DESTDIR)$(MANDIR)/man8 + $(INSTALL) -m 644 -c zcryptstats.8 $(DESTDIR)$(MANDIR)/man8 clean: - rm -f *.o chzcrypt lszcrypt zcryptctl + rm -f *.o chzcrypt lszcrypt zcryptctl zcryptstats .PHONY: all install clean diff --git a/zconf/zcrypt/zcryptstats.8 b/zconf/zcrypt/zcryptstats.8 new file mode 100644 index 0000000..8c31c69 --- /dev/null +++ b/zconf/zcrypt/zcryptstats.8 @@ -0,0 +1,247 @@ +.\" Copyright IBM Corp. 2019 +.\" 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 ZCRYPTSTATS 1 "January 2019" "s390-tools" +.SH NAME +zcryptstats \- Display usage statistics of IBM Crypto Express adapters +. +. +.SH SYNOPSIS +.B zcryptstats +.RI [ OPTIONS ] +.RI [ DEVICE_ID +[...] ] +. +.PP +.B zcryptstats +.BR \-\-help | \-h +.br +.B zcryptstats +.BR \-\-version | \-v +. +. +. +.SH DESCRIPTION +. +Use \fBzcryptstats\fP to display usage statistics of IBM Crypto Express +adapters. +.PP +\fBzcryptstats\fP obtains cryptographic performance measurement data +periodically and displays the data for each cryptographic device for each +interval. +A cryptographic device can be either a card device or a queue device (APQN). +\fBzcryptstats\fP runs forever unless you limit the number of intervals with +the \fB\-\-count\fP option. The default interval time is 10 seconds. +Use the \fB\-\-interval\fP option to specify a different interval time. +.PP +By default, all available cryptographic devices are monitored. +You can optionally specify the device IDs of the devices to be monitored. +The card device representation and the queue device are both in hexadecimal +notation. +.PP +Use the \fB\-\-no-apqn\fP option to omit the performance measurement data of +the queues. If the system does not support obtaining cryptographic +performance measurement data on the queue devices, only the card devices +are monitored. +.PP +For each device, a set of counters is displayed. The amount and meaning of the +counters are dependent on the device type and mode, see the COUNTERS section. +For each counter and interval, the following values are displayed: +.RS 2 +.IP "\(bu" 2 +Number of measured operations. +.IP "\(bu" 2 +Rate of the measured operation in operations per second. +.IP "\(bu" 2 +Utilization of the device in percent. +.IP "\(bu" 2 +Average duration of the operations. +.RE +.PP +The sum of all operations is displayed in a separate \fBtotals\fP line. +Use the \fB\-\-only-totals\fP option to omit the individual counters and +display the totals only. Use the \fB\-\-no\-totals\fP option to omit the +totals. +.PP + +.B Note: +The utilization value of a counter can exceed 100%. This value is caused by +the parallel execution of cryptographic operations. +.PP +Cryptographic performance measurement data might not be available when Linux +is running as guest under z/VM or under KVM. \fBzcryptstats\fP then displays an +error message and exits. +. +. +. +.SH OPTIONS +. +.TP +.BR DEVICE_ID +Specifies a cryptographic device for which statistics are displayed. +A device ID can either be a card device ID +(\fI\fP) or a queue device (APQN) ID (\fI.\fP). +To filter all devices by domain, provide \fI.\fP. +If no IDs are given, statistics are displayed for all available devices. +. +.TP +.BR \-i ", " \-\-interval\~\fIINTERVAL\fP +Specifies the interval time in seconds. If this option is omitted, then the +default interval time of 10 seconds is used. +. +.TP +.BR \-c ", " \-\-count\~\fICOUNT\fP +Specifies the number of reports that are generated at \fIINTERVAL\fP seconds +apart. If this option is omitted, the \fBzcryptstats\fP command generates +reports continuously, until it is stopped with control-C. +. +.TP +.BR \-o ", " \-\-output\~\fIJSON\fP|\fITABLE\fP|\fICSV\fP +Displays the statistics in the specified format. If this option is omitted, a +comprehensive report is displayed. Supported output formats are: +.RS 8 +.IP "\(bu" 2 +\fBJSON:\fP Displays the statistics in Javascript Object Notation (JSON) format. +JSON output field order is undefined, and new fields might be added in the +future. +.IP "\(bu" 2 +\fBTABLE:\fP Displays the statistics in a human readable simple table format. +The individual counters are omitted, and only the totals are displayed. +This output format implies option \fB\-\-only-totals\fP. +.IP "\(bu" 2 +\fBCSV:\fP Displays the statistics in comma-separated values format. The values +are separated with a semicolon. The individual counters are omitted, and only +the totals are displayed. This output format implies option +\fB\-\-only-totals\fP. +.RE +. +.TP +.BR \-t ", " \-\-no\-totals +Excludes the totals of all counters of a card device or queue device +(APQN). This option cannot be specified together with option +\fB\-\-only\-totals\fP or option \fB\-\-output\fP \fITABLE\fP|\fICSV\fP. +. +.TP +.BR \-T ", " \-\-only\-totals +Displays only the totals of all counters of a card device or a queue device +(APQN), but not the individual counters. This option is implied with +option \fB\-\-output\fP \fITABLE\fP|\fICSV\fP. +. +.TP +.BR \-a ", " \-\-no\-apqn +Displays only the counters of the card device, but omits the counters of the +queue device (APQN). If the system does not support obtaining cryptographic +performance measurement data on the queue devices, this option is implied. +. +.TP +.BR \-M ", " \-\-map\-type\~\fIMAPPING\fP +Maps unknown cryptographic device types and modes to known types and modes. +This option should only be used when new, so far unknown cryptographic devices +are found. You can then map them to known devices and modes, provided that the +new cryptographic devices report the same counters as the known cryptographic +device to which it is mapped. +The mapping specification consists of a comma-separated list of +\fIFROM\-TYPE\fP:\fIFROM\-MODE\fP=\fITO\-TYPE\fP:\fITO\-MODE\fP specifications. +The type and mode values must be specified in decimal notation. +. +.TP +.BR \-A ", " \-\-all +Displays all cards devices and queue devices (APQNs), not only those that are +available to the Linux instance. Using this option additional cryptographic +devices that are available in the CEC, but not available to the Linux system +are also monitored. +This option cannot be specified together with option \fB\-\-only-online\fP. +. +.TP +.BR \-O ", " \-\-only\-online +Displays only online cards devices and queue devices (APQNs). This option +cannot be specified together with option \fB\-\-all\fP. +. +.TP +.BR \-V ", " \-\-verbose +Displays additional information messages during processing. +.TP +.BR \-h ", " \-\-help +Displays help text and exits. +.TP +.BR \-v ", " \-\-version +Displays version information and exits. +. +. +. +.SH COUNTERS +. +.PP +.B IBM Crypto Express adapter in accelerator mode: +.RS 4 +.TP +.B All +All operations on the adapter +.TP +.B RSA Key-gen +RSA-key-generation operations (also included in \fBAll\fP). +.RE +.PP +.B IBM Crypto Express adapter in CCA co-processor mode: +.RS 4 +.TP +.B RSA 1024 ME +1024-bit ME-format RSA operations. +.TP +.B RSA 2048 ME +2048-bit ME-format RSA operations. +.TP +.B RSA 1024 CRT +1024-bit CRT-format RSA operations. +.TP +.B RSA 2048 CRT +2048-bit CRT-format RSA operations. +.TP +.B RSA 4096 ME +4096-bit ME-format RSA operations. +.TP +.B RSA 4096 CTR +4096-bit CRT-format RSA operations. +.RE +.PP +.B IBM Crypto Express adapter in EP11 co-processor mode: +.RS 4 +.TP +.B Asym. Slow +Slow asymmetric-key functions. +.TP +.B Asym. Fast +Fast asymmetric-key functions. +.TP +.B Symm. Partial +Symmetric-key functions that return partial or incremental results. +.TP +.B Symm. Complete +Symmetric-key functions that return a complete or final result. +.TP +.B Asym. Key-gen +asymmetric-key generation function. +.RE +.PP +. +. +. +.SH EXAMPLES +.TP +.B zcryptstats 02 +Display statistics for all cryptographic devices with card ID \fB02\fP. +.TP +.B zcryptstats 02.0005 --interval 5 +Display statistics for cryptographic devices with card ID \fB02\fP and domain +ID \fB0005\fP in a 5 second interval. +.TP +.B zcryptstats .0005 --count 10 +Display statistics for cryptographic devices with domain ID \fB0005\fP with the +default interval time of 10 seconds, for 10 intervals. +.TP +.B zcryptstats 02 --output JSON +Display statistics for all cryptographic devices with card ID \fB02\fP in +\fBJSON\fP output format. +.TP + diff --git a/zconf/zcrypt/zcryptstats.c b/zconf/zcrypt/zcryptstats.c new file mode 100644 index 0000000..136d5ba --- /dev/null +++ b/zconf/zcrypt/zcryptstats.c @@ -0,0 +1,2418 @@ +/* + * zcryptstats - Show usage statistics of IBM Crypto Express adapters + * + * Copyright IBM Corp. 2019 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_base.h" +#include "lib/util_file.h" +#include "lib/util_libc.h" +#include "lib/util_opt.h" +#include "lib/util_path.h" +#include "lib/util_prg.h" +#include "lib/util_rec.h" +#include "lib/util_scandir.h" +#include "lib/zt_common.h" + +#ifndef offsetof + #define offsetof(type, member) ((size_t) &((type *)0)->member) +#endif +#ifndef offsetofend + #define offsetofend(type, member) \ + (offsetof(type, member) + sizeof(((type *)0)->member)) +#endif + +#define SYSFS_DEVICES_AP_PATH "devices/ap" +#define SYSFS_DEVICES_CARD "devices/ap/card%02x" +#define SYSFS_DEVICES_APQN "devices/ap/card%02x/%02x.%04x" +#define SYSFS_DEVICES_CARD_ONLINE "devices/ap/card%02x/online" +#define SYSFS_DEVICES_APQN_ONLINE "devices/ap/card%02x/%02x.%04x/online" +#define CHSC_DEVICE "/dev/chsc" +#define NUM_CARDS_OLD 64 +#define NUM_CARDS 256 +#define NUM_DOMAINS 256 + +#define MASK_WORD_BITS (sizeof(uint32_t) * 8) +#define MASK_WORD_NO(n) ((n) / MASK_WORD_BITS) +#define MASK_BIT_IN_WORD(n) ((n) % MASK_WORD_BITS) +#define MASK_BIT(n) (0x80000000 >> MASK_BIT_IN_WORD(n)) + +struct chsc_apdn { + uint8_t ap_index; + uint8_t domain_index; +} __packed; + +struct chsc_scdmd_request { + struct chsc_header header; + struct chsc_apdn first_drid; + struct chsc_apdn last_drid; + uint32_t s:1; + uint32_t reserved1:31; + uint32_t reserved2; + uint32_t apsm[8]; + uint32_t dsm[8]; +} __packed; + +struct chsc_scdmd_response { + struct chsc_header header; + uint32_t reserved1; + uint16_t p:1; + uint16_t reserved2:15; + struct chsc_apdn crid; + uint32_t reserved3; +} __packed; + +struct chsc_scdmd_area { + struct chsc_scdmd_request request; + struct chsc_scdmd_response response; + uint8_t response_data[CHSC_SIZE - sizeof(struct chsc_scdmd_request) - + sizeof(struct chsc_scdmd_response)]; +} __packed; + +struct chsc_scmd_request { + struct chsc_header header; + uint8_t reserved1; + uint8_t zeros1:6; + uint8_t one:1; + uint8_t zero:1; + uint8_t fcs; + uint8_t lcs; + uint32_t reserved2; + uint32_t reserved3; +} __packed; + +struct chsc_scmd_response { + struct chsc_header header; + uint32_t reserved1; + uint32_t p:1; + uint32_t reserved2:31; + uint32_t reserved3; +} __packed; + +struct chsc_scmd_area { + struct chsc_scmd_request request; + struct chsc_scmd_response response; + uint8_t response_data[CHSC_SIZE - sizeof(struct chsc_scmd_request) - + sizeof(struct chsc_scmd_response)]; +} __packed; + +struct chsc_cmb_header { + uint8_t reserved1; + uint8_t ct; /* AP_DEVICE_TYPE_xxx values */ + uint8_t format; + uint8_t ax; + float s; + uint32_t v; + uint8_t dx; + uint8_t mt; + uint16_t l4; +} __packed; + +struct chsc_cmb_entry { + u64 t; + u64 c; +} __packed; + +struct chsc_cmb_area { + struct chsc_cmb_header header; + struct chsc_cmb_entry entries[32]; +} __packed; + +#define CRYPTO_TYPE_PCICC 3 +#define CRYPTO_TYPE_PCICA 4 +#define CRYPTO_TYPE_PCIXCC 5 +#define CRYPTO_TYPE_CEX2A 6 +#define CRYPTO_TYPE_CEX2C 7 +#define CRYPTO_TYPE_CEX3A 8 +#define CRYPTO_TYPE_CEX3C 9 +#define CRYPTO_TYPE_CEX4S 10 +#define CRYPTO_TYPE_CEX5S 11 +#define CRYPTO_TYPE_CEX6S 12 + +#define CRYPTO_TYPE_TOLERATION CRYPTO_TYPE_CEX6S + +struct crypto_counter { + const char *name; + bool is_totals; +}; + +struct crypto_mode { + const char *name; + char indicatior_char; + unsigned int num_counters; + const struct crypto_counter *counters; +}; + +struct crypto_type { + const char *name; + unsigned int num_modes; + const struct crypto_mode *modes; +}; + +#define NUM_COPROC_COUNTERS 2 +const struct crypto_counter counter_coproc[NUM_COPROC_COUNTERS] = { + { .name = "All", .is_totals = true }, + { .name = "RSA Key-gen" }, +}; + +#define NUM_ACCEL_COUNTERS 6 +const struct crypto_counter counter_accel[NUM_ACCEL_COUNTERS] = { + { .name = "RSA 1024 ME" }, + { .name = "RSA 2048 ME" }, + { .name = "RSA 1024 CRT" }, + { .name = "RSA 2048 CRT" }, + { .name = "RSA 4096 ME" }, + { .name = "RSA 4096 CTR" }, +}; + +#define NUM_EP11_COUNTERS 5 +const struct crypto_counter counter_ep11[NUM_EP11_COUNTERS] = { + { .name = "Asym. Slow" }, + { .name = "Asym. Fast" }, + { .name = "Symm. Partial" }, + { .name = "Symm. Complete" }, + { .name = "Asym. Key-gen" }, +}; + +#define NUM_PCICA_COUNTERS 20 +const struct crypto_counter counter_pcica[NUM_PCICA_COUNTERS] = { + { .name = "RSA 1024 ME (E0)" }, + { .name = "RSA 2048 ME (E0)" }, + { .name = "RSA 1024 CRT (E0)" }, + { .name = "RSA 2048 CRT (E0)" }, + { .name = "RSA 1024 ME (E1)" }, + { .name = "RSA 2048 ME (E1)" }, + { .name = "RSA 1024 CRT (E1)" }, + { .name = "RSA 2048 CRT (E1)" }, + { .name = "RSA 1024 ME (E2)" }, + { .name = "RSA 2048 ME (E2)" }, + { .name = "RSA 1024 CRT (E2)" }, + { .name = "RSA 2048 CRT (E2)" }, + { .name = "RSA 1024 ME (E3)" }, + { .name = "RSA 2048 ME (E3)" }, + { .name = "RSA 1024 CRT (E3)" }, + { .name = "RSA 2048 CRT (E3)" }, + { .name = "RSA 1024 ME (E4)" }, + { .name = "RSA 2048 ME (E4)" }, + { .name = "RSA 1024 CRT (E4)" }, + { .name = "RSA 2048 CRT (E4)" }, +}; + +#define NUM_COPROC_MODES 1 +const struct crypto_mode mode_coproc[1] = { + { .num_counters = NUM_COPROC_COUNTERS, + .counters = counter_coproc}, +}; + +#define NUM_ACCEL_MODES 1 +const struct crypto_mode mode_accel[1] = { + { .num_counters = NUM_ACCEL_COUNTERS, + .counters = counter_accel }, +}; + +#define NUM_PCICA_MODES 1 +const struct crypto_mode mode_pcica[1] = { + { .num_counters = NUM_PCICA_COUNTERS, + .counters = counter_pcica }, +}; + +#define NUM_CEX456_MODES 11 +const struct crypto_mode mode_cex456[NUM_CEX456_MODES] = { + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { .name = "Accelerator", .indicatior_char = 'A', + .num_counters = NUM_ACCEL_COUNTERS, + .counters = counter_accel }, + { .name = "CCA co-processor", .indicatior_char = 'C', + .num_counters = NUM_COPROC_COUNTERS, + .counters = counter_coproc}, + { .name = "EP11 co-processor", .indicatior_char = 'P', + .num_counters = NUM_EP11_COUNTERS, + .counters = counter_ep11 }, +}; + +#define NUM_CRYPTO_TYPES 13 +const struct crypto_type crypto_types[NUM_CRYPTO_TYPES] = { + { 0 }, + { 0 }, + { 0 }, + { .name = "PCICC", .num_modes = NUM_COPROC_MODES, + .modes = mode_coproc }, + { .name = "PCICA", .num_modes = NUM_PCICA_MODES, + .modes = mode_pcica }, + { .name = "PCIXCC", .num_modes = NUM_COPROC_MODES, + .modes = mode_coproc}, + { .name = "CEX2A", .num_modes = NUM_ACCEL_MODES, + .modes = mode_accel }, + { .name = "CEX2C", .num_modes = NUM_COPROC_MODES, + .modes = mode_coproc }, + { .name = "CEX3A", .num_modes = NUM_ACCEL_MODES, + .modes = mode_accel }, + { .name = "CEX3C", .num_modes = NUM_COPROC_MODES, + .modes = mode_coproc }, + { .name = "CEX4", .num_modes = NUM_CEX456_MODES, + .modes = mode_cex456 }, + { .name = "CEX5", .num_modes = NUM_CEX456_MODES, + .modes = mode_cex456 }, + { .name = "CEX6", .num_modes = NUM_CEX456_MODES, + .modes = mode_cex456 }, +}; + + +struct type_mapping { + uint8_t from_type; + uint8_t from_mode; + uint8_t to_type; + uint8_t to_mode; + struct type_mapping *next; +}; + +struct device_selection { + int card; + int domain; /* -1 if not specified */ + struct device_selection *next; +}; + +struct interval_data { + bool current_valid; + bool previous_valid; + struct chsc_cmb_area current; + struct chsc_cmb_area previous; +}; + +struct card_data { + struct interval_data data; + struct interval_data *domains[NUM_DOMAINS]; +}; + +struct interval_values { + u64 count; + double rate; + double utilization; + double duration; +}; + +struct print_func { + int (*print_initialize)(void); + int (*print_terminate)(void); + int (*print_header)(void); + int (*print_footer)(void); + int (*print_interval_header)(unsigned long interval_count, + const char *timestamp); + int (*print_interval_footer)(void); + int (*print_device_header)(bool is_apqn, uint8_t card, uint8_t domain, + const char *type, const char *timestamp); + int (*print_device_footer)(void); + int (*print_counter_data)(bool is_apqn, uint8_t card, uint8_t domain, + const char *type, const char *timestamp, + const char *name, + struct interval_values *vals); + int (*print_counter_separator)(void); +}; + +#define pr_call(func) g.print_funcs->func == NULL ? 0 : g.print_funcs->func + +static int default_print_initialize(void); +static int default_print_terminate(void); +static int default_print_header(void); +static int default_print_footer(void); +static int default_print_interval_header(unsigned long interval_count, + const char *timestamp); +static int default_print_device_header(bool is_apqn, uint8_t card, + uint8_t domain, const char *type, + const char *timestamp); +static int default_print_device_footer(void); +static int default_print_counter_data(bool is_apqn, uint8_t card, + uint8_t domain, const char *type, + const char *timestamp, const char *name, + struct interval_values *vals); +static int default_print_counter_separator(void); + +static const struct print_func default_print = { + .print_initialize = default_print_initialize, + .print_terminate = default_print_terminate, + .print_header = default_print_header, + .print_footer = default_print_footer, + .print_interval_header = default_print_interval_header, + .print_device_header = default_print_device_header, + .print_device_footer = default_print_device_footer, + .print_counter_data = default_print_counter_data, + .print_counter_separator = default_print_counter_separator, +}; + +static int json_print_initialize(void); +static int json_print_header(void); +static int json_print_footer(void); +static int json_print_interval_header(unsigned long interval_count, + const char *timestamp); +static int json_print_interval_footer(void); +static int json_print_device_header(bool is_apqn, uint8_t card, + uint8_t domain, const char *type, + const char *timestamp); +static int json_print_device_footer(void); +static int json_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain, + const char *type, const char *timestamp, + const char *name, + struct interval_values *vals); + +static const struct print_func json_print = { + .print_initialize = json_print_initialize, + .print_header = json_print_header, + .print_footer = json_print_footer, + .print_interval_header = json_print_interval_header, + .print_interval_footer = json_print_interval_footer, + .print_device_header = json_print_device_header, + .print_device_footer = json_print_device_footer, + .print_counter_data = json_print_counter_data, +}; + + +static int table_print_initialize(void); +static int table_print_terminate(void); +static int table_print_header(void); +static int table_print_interval_footer(void); +static int table_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain, + const char *type, const char *timestamp, + const char *name, + struct interval_values *vals); + +static const struct print_func table_print = { + .print_initialize = table_print_initialize, + .print_terminate = table_print_terminate, + .print_header = table_print_header, + .print_interval_footer = table_print_interval_footer, + .print_counter_data = table_print_counter_data, +}; + +static int csv_print_initialize(void); +static int csv_print_terminate(void); +static int csv_print_header(void); +static int csv_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain, + const char *type, const char *timestamp, + const char *name, + struct interval_values *vals); + +static const struct print_func csv_print = { + .print_initialize = csv_print_initialize, + .print_terminate = csv_print_terminate, + .print_header = csv_print_header, + .print_counter_data = csv_print_counter_data, +}; + +/* + * Program configuration + */ +const struct util_prg prg = { + .desc = "Display usage statistics of IBM Crypto Express adapters", + .args = "[DEVICE_IDS]", + .copyright_vec = { + { + .owner = "IBM Corp.", + .pub_first = 2019, + .pub_last = 2019, + }, + UTIL_PRG_COPYRIGHT_END + } +}; + +/* + * Global variables for program options + */ +static struct zcryptstats_globals { + long interval; + unsigned long count; + bool no_totals; + bool only_totals; + bool no_apqn; + char *map_type; + char **device_ids; + bool all; + bool only_online; + bool verbose; + int chsc_fd; + uint8_t max_card_used; + uint32_t card_mask[8]; + uint8_t min_card; + uint8_t max_card; + uint32_t domain_mask[8]; + uint8_t min_domain; + uint8_t max_domain; + struct device_selection *dev_selection; + struct type_mapping *type_mapping; + struct card_data *cards[NUM_CARDS]; + const struct print_func *print_funcs; + struct util_rec *device_rec; + struct util_rec *counter_rec; + bool first_device; + bool first_counter; +} g = { + .interval = 10, + .chsc_fd = -1, + .print_funcs = &default_print, +}; + + +static volatile bool quit; + +/* + * Configuration of command line options + */ +static struct util_opt opt_vec[] = { + /***********************************************************/ + { + .flags = UTIL_OPT_FLAG_SECTION, + .desc = "OPTIONS", + }, + { + .option = {"interval", required_argument, NULL, 'i'}, + .argument = "INTERVAL", + .desc = "Specifies the interval time in seconds. If omitted, a " + "default interval of 10 seconds is used", + }, + { + .option = {"count", required_argument, NULL, 'c'}, + .argument = "COUNT", + .desc = "Specifies the number of reports that are generated " + "at INTERVAL seconds apart. If omitted, reports are " + "generated continuously, until stopped with control-C", + }, + { + .option = {"output", required_argument, NULL, 'o'}, + .argument = "JSON|TABLE|CSV", + .desc = "Displays the statistics in the specified format. If " + "this option is omitted, a comprehensive report is " + "displayed. Supported output formats are: JSON, TABLE, " + "CSV. With TABLE and CSV the display of the individual " + "counters are omitted, and only the totals are " + "displayed. CSV and TABLE output formats imply option " + "--only-totals", + }, + { + .option = {"no-totals", 0, NULL, 't'}, + .desc = "Excludes the totals of all counters of a card " + "device or queue device (APQN). It can not be " + "specified together with option --only-totals or " + "option --output TABLE|CSV", + }, + { + .option = {"only-totals", 0, NULL, 'T'}, + .desc = "Displays only the totals of all counters of a card " + "device or a queue device (APQN), but not the " + "individual counters. This option is implied with " + "option --output TABLE|CSV", + }, + { + .option = {"no-apqn", 0, NULL, 'a'}, + .desc = "Displays only the counters of the card device, but " + "omits the counters of the queue device (APQN). If the " + "system does not support obtaining cryptographic " + "performance measurement data on the queue devices, " + "then this option is implied", + }, + { + .option = {"map-type", required_argument, NULL, 'M'}, + .argument = "MAPPING", + .desc = "Maps unknown cryptographic device types and modes to " + "known types and modes. This option should only be " + "used when new, so far unknown cryptographic devices " + "are found. You can then map them to known devices and " + "modes, provided that the new cryptographic devices " + "report the same counters as the known cryptographic " + "device to which it is mapped. The mapping " + "specification consists of a comma-separated list of " + "FROM-TYPE:FROM-MODE=TO-TYPE:TO-MODE specifications. " + "The type and mode values must be specified in decimal " + "notation", + }, + { + .option = {"all", 0, NULL, 'A'}, + .desc = "Displays all cards devices and queue devices (APQNs), " + "not only those that are available to the Linux " + "system. Using this option additional cryptographic " + "devices that are available in the CEC, but not " + "available to the Linux system are also monitored. " + "This option can not be specified together with option " + "--only-online", + }, + { + .option = {"only-online", 0, NULL, 'O'}, + .desc = "Displays only online cards devices and queue devices " + "(APQNs). This option can not be specified together " + "with option --all" + }, + { + .option = {"verbose", 0, NULL, 'V'}, + .desc = "Prints additional information messages during " + "processing", + }, + UTIL_OPT_HELP, + UTIL_OPT_VERSION, + UTIL_OPT_END +}; + +#define pr_verbose(fmt...) do { \ + if (g.verbose) \ + warnx(fmt); \ + } while (0) + +/* + * Describe adapter ids + */ +static void print_adapter_id_help(void) +{ + printf("\n"); + printf("DEVICE_IDS\n"); + util_print_indented(" List of cryptographic device IDs separated by " + "blanks for which statistics are displayed. " + "DEVICE_ID can either be a card device ID " + "('') or a queue device ID (." + "'). To filter all devices by domain, " + "provide '.'. If no IDs are given, " + "statistics are displayed for all available " + "devices.", 2); + printf("\n"); + printf("EXAMPLE:\n"); + util_print_indented(" Display statistics for all cryptographic " + "devices with card ID '02.", 2); + printf(" # zcryptstats 02\n"); + printf("\n"); + util_print_indented(" Display statistics for cryptographic devices " + "with card ID '02' and domain ID '0005'.", 2); + printf(" # zcryptstats 02.0005\n"); + printf("\n"); +} + +static struct type_mapping *find_type_mapping(uint8_t from_type, + uint8_t from_mode) +{ + struct type_mapping *map = g.type_mapping; + + while (map != NULL) { + if (map->from_type == from_type && map->from_mode == from_mode) + return map; + map = map->next; + } + return NULL; +} + +/* + * Get the name of the card for a crypto type and mode. + * Note: This function might return the address of a static string variable. + * It is only valid until this function is called again. + */ +static const char *get_card_name(uint8_t type, uint8_t mode) +{ + const struct crypto_type *ct; + const struct crypto_mode *m; + static char temp_name[250]; + struct type_mapping *map; + + map = find_type_mapping(type, mode); + if (map != NULL) { + type = map->to_type; + mode = map->to_mode; + } else if (type >= NUM_CRYPTO_TYPES) { + type = CRYPTO_TYPE_TOLERATION; + } + + if (type >= NUM_CRYPTO_TYPES) + return "UNKNOWN ADAPTER TYPE"; + + ct = &crypto_types[type]; + if (ct->name == NULL || ct->modes == NULL || ct->num_modes == 0) + return "UNKNOWN ADAPTER TYPE"; + + if (mode >= ct->num_modes) + return ct->name; + + m = &ct->modes[mode]; + snprintf(temp_name, sizeof(temp_name) - 1, "%s%c (%s)", ct->name, + m->indicatior_char, m->name != NULL ? m->name : ""); + return temp_name; +} + +/* + * Get the name of a counter for a crypto type, mode and index. + * Note: This function might return the address of a static string variable. + * It is only valid until this function is called again. + */ +static const char *get_counter_name(uint8_t type, uint8_t mode, uint8_t index) +{ + const struct crypto_type *ct; + const struct crypto_mode *m; + static char temp_name[250]; + struct type_mapping *map; + + map = find_type_mapping(type, mode); + if (map != NULL) { + type = map->to_type; + mode = map->to_mode; + } else if (type >= NUM_CRYPTO_TYPES) { + type = CRYPTO_TYPE_TOLERATION; + } + + if (type >= NUM_CRYPTO_TYPES) + goto generic; + + ct = &crypto_types[type]; + if (ct->name == NULL || ct->modes == NULL || ct->num_modes == 0) + goto generic; + + if (mode >= ct->num_modes) + goto generic; + + m = &ct->modes[mode]; + if (m->counters == NULL || m->num_counters == 0) + goto generic; + + if (index >= m->num_counters) + goto generic; + + return m->counters[index].name; + +generic: + snprintf(temp_name, sizeof(temp_name) - 1, "COUNTER %u", index); + return temp_name; +} + +/* + * Returns true if a counter for a crypto type, mode and index represents the + * total number of operations. + */ +static bool is_counter_totals(uint8_t type, uint8_t mode, uint8_t index) +{ + const struct crypto_type *ct; + const struct crypto_mode *m; + struct type_mapping *map; + + map = find_type_mapping(type, mode); + if (map != NULL) { + type = map->to_type; + mode = map->to_mode; + } else if (type >= NUM_CRYPTO_TYPES) { + type = CRYPTO_TYPE_TOLERATION; + } + + if (type >= NUM_CRYPTO_TYPES) + return false; + + ct = &crypto_types[type]; + if (ct->name == NULL || ct->modes == NULL || ct->num_modes == 0) + return false; + + if (mode >= ct->num_modes) + return false; + + m = &ct->modes[mode]; + if (m->counters == NULL || m->num_counters == 0) + return false; + + if (index >= m->num_counters) + return false; + + return m->counters[index].is_totals; +} + +/* + * Returns true if the card is available + */ +static bool is_card_available(uint8_t card) +{ + char *path; + bool ret; + + path = util_path_sysfs(SYSFS_DEVICES_CARD, card); + ret = util_path_is_dir(path); + free(path); + + return ret; +} + +/* + * Returns true if the APQN is available + */ +static bool is_apqn_available(uint8_t card, uint8_t domain) +{ + char *path; + bool ret; + + path = util_path_sysfs(SYSFS_DEVICES_APQN, card, card, domain); + ret = util_path_is_dir(path); + free(path); + + return ret; +} + +/* + * Returns true if the card is online + */ +static bool is_card_online(uint8_t card) +{ + unsigned long online; + char *path; + int rc; + + path = util_path_sysfs(SYSFS_DEVICES_CARD_ONLINE, card); + rc = util_file_read_ul(&online, 10, path); + free(path); + + return rc == 0 && online != 0; +} + +/* + * Returns true if the APQN is online + */ +static bool is_apqn_online(uint8_t card, uint8_t domain) +{ + unsigned long online; + char *path; + int rc; + + path = util_path_sysfs(SYSFS_DEVICES_APQN_ONLINE, card, card, domain); + rc = util_file_read_ul(&online, 10, path); + free(path); + + if (rc != 0) + return false; + + return online != 0; +} + +/* + * Updates the APQNs data with data for the current interval. + */ +static void update_apqn_data(uint8_t card, uint8_t domain, + struct chsc_cmb_area *cmb, size_t cmb_len) +{ + struct card_data *cd = g.cards[card]; + struct interval_data *dd; + + if (cd == NULL) { + cd = util_malloc(sizeof(struct card_data)); + memset(cd, 0, sizeof(struct card_data)); + g.cards[card] = cd; + + pr_verbose("Card %02x added", card); + } + + dd = cd->domains[domain]; + if (dd == NULL) { + dd = util_malloc(sizeof(struct interval_data)); + memset(dd, 0, sizeof(struct interval_data)); + cd->domains[domain] = dd; + + pr_verbose("APQN %02x.%04x added", card, domain); + } else { + if (!dd->current_valid) { + dd->previous = dd->current; + dd->previous_valid = true; + } + } + + memset(&dd->current, 0, sizeof(struct chsc_cmb_area)); + memcpy(&dd->current, cmb, cmb_len); + dd->current_valid = true; +} + +/* + * Updates the card's data with data for the current interval. + */ +static void update_card_data(uint8_t card, struct chsc_cmb_area *cmb, + size_t cmb_len) +{ + struct card_data *cd = g.cards[card]; + + if (cd == NULL) { + cd = util_malloc(sizeof(struct card_data)); + memset(cd, 0, sizeof(struct card_data)); + g.cards[card] = cd; + + pr_verbose("Card %02x added", card); + } else { + if (!cd->data.current_valid) { + cd->data.previous = cd->data.current; + cd->data.previous_valid = true; + } + }; + + memset(&cd->data.current, 0, sizeof(struct chsc_cmb_area)); + memcpy(&cd->data.current, cmb, cmb_len); + cd->data.current_valid = true; +} + +/* + * Frees the interval data of a card + */ +static void free_card_data(struct card_data *cd) +{ + struct interval_data *dd; + int domain; + + if (cd == NULL) + return; + + for (domain = 0; domain < NUM_DOMAINS; domain++) { + dd = cd->domains[domain]; + if (dd == NULL) + continue; + + free(dd); + cd->domains[domain] = NULL; + } + + free(cd); +} + +/* + * Frees the interval data + */ +static void free_interval_data(void) +{ + struct card_data *cd; + int card; + + for (card = 0; card < NUM_CARDS; card++) { + cd = g.cards[card]; + if (cd == NULL) + continue; + + free_card_data(cd); + g.cards[card] = NULL; + } +} + +/* + * Returns the highest card index used by the system. + * If there is a card with an index > 0x3f, then the returned number is 0xff, + * else 0x3f is returned. + */ +static int get_max_card_index(uint8_t *max_index) +{ + struct dirent **dev_vec = NULL; + int i, count, card, rc = 0; + char *path; + + path = util_path_sysfs(SYSFS_DEVICES_AP_PATH); + if (!util_path_is_dir(path)) { + warnx("Crypto device driver is not available"); + rc = -ENODEV; + goto out; + } + + count = util_scandir(&dev_vec, NULL, path, "card[0-9a-fA-F]+"); + if (count < 1) { + warnx("No crypto card devices found"); + rc = -ENODEV; + goto out; + } + + *max_index = NUM_CARDS_OLD - 1; + for (i = 0; i < count; i++) { + if (sscanf(dev_vec[i]->d_name, "card%x", &card) != 1) + continue; + if (card >= NUM_CARDS_OLD) + *max_index = NUM_CARDS - 1; + } + + pr_verbose("Max card index used: %u", *max_index); + +out: + free(path); + if (dev_vec != NULL) + free(dev_vec); + return rc; +} + +/* + * Returns the size of the CMB. The size is either contained in l4 field + * of the cmb, or a fix length dependent on the crypto type, if l4 is zero. + */ +static size_t get_cmb_length(struct chsc_cmb_area *cmb) +{ + size_t len = cmb->header.l4; + + if (len != 0) + return len; + + switch (cmb->header.ct) { + case CRYPTO_TYPE_PCICC: + case CRYPTO_TYPE_PCIXCC: + case CRYPTO_TYPE_CEX2C: + case CRYPTO_TYPE_CEX3C: + return 64; + case CRYPTO_TYPE_PCICA: + return 336; + case CRYPTO_TYPE_CEX2A: + case CRYPTO_TYPE_CEX3A: + return 80; + default: + warnx("Zero length value in CMB"); + return 0; + } +} + +/* + * Return true if the device is in the device selection list and mask + */ +static bool filter_device(uint8_t card, uint8_t domain, bool is_apqn) +{ + struct device_selection *dev; + bool found; + + /* Check for selection mask */ + if ((g.card_mask[MASK_WORD_NO(card)] & + MASK_BIT(card)) == 0) { + pr_verbose("Skipping card %02x (mask)", card); + return false; + } + if (is_apqn) { + if ((g.domain_mask[MASK_WORD_NO(domain)] & + MASK_BIT(domain)) == 0) { + pr_verbose("Skipping APQN %02x.%04x (mask)", card, + domain); + return false; + } + } + + /* Check for device selection list */ + if (g.dev_selection != NULL) { + dev = g.dev_selection; + found = false; + while (dev != NULL) { + if (is_apqn == false) { + /* Its a card */ + if (card == (dev->card >= 0 ? dev->card : + card)) { + found = true; + break; + } + } else { + /* Its an APQN */ + if (card == (dev->card >= 0 ? dev->card : + card) && + domain == (dev->domain >= 0 ? dev->domain : + domain)) { + found = true; + break; + } + } + + dev = dev->next; + } + + if (!found) { + if (is_apqn) + pr_verbose("Skipping APQN %02x.%04x " + "(selection)", card, domain); + else + pr_verbose("Skipping card %02x (selection)", + card); + return false; + } + } + + if (g.all) + return true; + + /* Check if card/APQN is available in the system (SYSFS) */ + if (!is_card_available(card)) { + pr_verbose("Skipping card %02x (not available)", card); + return false; + } + if (is_apqn && !is_apqn_available(card, domain)) { + pr_verbose("Skipping APQN %02x.%04x (not available)", + card, domain); + return false; + } + + if (g.only_online) { + /* Check if card/APQN is online */ + if (!is_card_online(card)) { + pr_verbose("Skipping card %02x (not online)", card); + return false; + } + if (is_apqn && !is_apqn_online(card, domain)) { + pr_verbose("Skipping APQN %02x.%04x (not online)", + card, domain); + return false; + } + } + + return true; +} + +/* + * Process a crypto measurement block. + * Passes back the actual length of the CMB processed and its card number. + * Returns -ENODEV when the CMB is skipped. + */ +static int process_cmb(struct chsc_cmb_area *cmb, size_t size, size_t *length, + uint8_t *card) +{ + size_t len; + + len = get_cmb_length(cmb); + if (len == 0) + return -EINVAL; + + if (len > size) { + warnx("Length value in CMB exceeds size of CMB"); + return -EINVAL; + } + + if (card != NULL) + *card = cmb->header.ax; + if (length != NULL) + *length = len; + + if (filter_device(cmb->header.ax, cmb->header.dx, + cmb->header.format == 1) == false) + return -ENODEV; + + if (cmb->header.format == 1) + update_apqn_data(cmb->header.ax, cmb->header.dx, cmb, len); + else + update_card_data(cmb->header.ax, cmb, len); + + return 0; +} + +/* + * Translate the CHSC response code to an error (0 or negative errno) + */ +static int chsc_error_from_response(int response) +{ + if (response != 0x0001) + pr_verbose("CHSC Response code: %04x", response); + + switch (response) { + case 0x0001: + return 0; + case 0x0002: + return -EOPNOTSUPP; + case 0x0003: + case 0x0006: + case 0x0007: + case 0x0008: + case 0x000a: + case 0x0103: + case 0x0104: + return -EINVAL; + case 0x0004: + return -EOPNOTSUPP; + case 0x000b: + return -EBUSY; + case 0x0102: + return -ENOMEM; + case 0x0105: + return -EACCES; + case 0x0100: + case 0x0107: + return -ENODEV; + default: + return -EIO; + } +} + +/* + * Process the APQN measurement data and extract the CMBs + */ +static int process_apqn_measurement_data(struct chsc_scdmd_area *scdmd_area) +{ + size_t size = scdmd_area->response.header.length - + sizeof(struct chsc_scdmd_response); + size_t len, ofs = 0; + int rc; + + while (ofs < size) { + rc = process_cmb((struct chsc_cmb_area *) + &scdmd_area->response_data[ofs], + size - ofs, &len, NULL); + if (rc != 0 && rc != -ENODEV) + return rc; + ofs += len; + + } + + return 0; +} + +/* + * Get Crypto Measurement data on the APQN level + */ +static int get_apqn_measurement_data(uint8_t card) +{ + struct chsc_scdmd_area scdmd_area; + int rc; + + memset(&scdmd_area, 0, sizeof(scdmd_area)); + do { + scdmd_area.request.header.code = 0x102d; + scdmd_area.request.header.length = + sizeof(struct chsc_scdmd_request); + scdmd_area.request.first_drid.ap_index = card; + scdmd_area.request.first_drid.domain_index = g.min_domain; + scdmd_area.request.last_drid.ap_index = card; + scdmd_area.request.last_drid.domain_index = g.max_domain; + scdmd_area.request.s = 1; + scdmd_area.request.apsm[MASK_WORD_NO(card)] |= MASK_BIT(card); + memcpy(scdmd_area.request.dsm, g.domain_mask, + sizeof(scdmd_area.request.dsm)); + + rc = ioctl(g.chsc_fd, CHSC_START_SYNC, &scdmd_area); + if (rc != 0) { + rc = -errno; + warnx("Failed to get APQN measurement data for card " + "%02x: %s", card, strerror(errno)); + break; + } + + rc = chsc_error_from_response(scdmd_area.response.header.code); + if (rc != 0) { + if (rc != -EOPNOTSUPP && rc != -ENODEV) { + warnx("Failed to get APQN crypto measurement " + "data for card %02x: %s", card, + strerror(-rc)); + } else { + pr_verbose("Failed to get APQN crypto " + "measurement data for card %02x: %s", + card, strerror(-rc)); + /* + * ignore return code other than -EOPNOTSUPP + * and -ENODEV + */ + rc = 0; + } + break; + } + + rc = process_apqn_measurement_data(&scdmd_area); + if (rc != 0) + break; + + if (scdmd_area.response.p) + scdmd_area.request.first_drid = + scdmd_area.response.crid; + } while (scdmd_area.response.p); + + return rc; +} + +/* + * Process the card measurement data and extract the CMBs + */ +static int process_card_measurement_data(struct chsc_scmd_area *scmd_area, + uint8_t *last_card) +{ + size_t size = scmd_area->response.header.length - + sizeof(struct chsc_scmd_response); + size_t len, ofs = 0; + int rc; + + while (ofs < size) { + rc = process_cmb((struct chsc_cmb_area *) + &scmd_area->response_data[ofs], + size - ofs, &len, last_card); + if (rc != 0 && rc != -ENODEV) + return rc; + ofs += len; + + if (rc == -ENODEV) + continue; + + if (!g.no_apqn) { + rc = get_apqn_measurement_data(*last_card); + if (rc != 0) + return rc; + } + } + + return 0; +} + +/* + * Get Crypto Measurement data on the card level + */ +static int get_card_measurement_data(void) +{ + struct chsc_scmd_area scmd_area; + uint8_t last_card = 0; + int rc; + + memset(&scmd_area, 0, sizeof(scmd_area)); + do { + scmd_area.request.header.code = 0x102e; + scmd_area.request.header.length = + sizeof(struct chsc_scmd_request); + scmd_area.request.one = 1; + scmd_area.request.fcs = g.min_card; + scmd_area.request.lcs = g.max_card; + + rc = ioctl(g.chsc_fd, CHSC_START_SYNC, &scmd_area); + if (rc != 0) { + rc = -errno; + warnx("Failed to get card measurement data: %s", + strerror(errno)); + break; + } + + rc = chsc_error_from_response(scmd_area.response.header.code); + if (rc != 0) { + warnx("Failed to get card crypto measurement data: %s", + strerror(-rc)); + break; + } + + rc = process_card_measurement_data(&scmd_area, &last_card); + if (rc != 0) + break; + + if (scmd_area.response.p) + scmd_area.request.fcs = last_card + 1; + } while (scmd_area.response.p && last_card < g.max_card); + + return rc; +} + +/* + * Signal handler for SIGALRM + */ +static void alarm_handler(int UNUSED(sig)) +{ + if (!quit) + alarm(g.interval); +} + +/* + * Signal handler for SIGINT and SIGTERM + */ +static void int_handler(int UNUSED(sig)) +{ + quit = true; + raise(SIGALRM); +} + +/* + * Calculates the time difference between tv1 and tv2 in seconds + */ +static float time_diff(struct timeval *tv1, struct timeval *tv2) +{ + struct timeval tv_diff; + + tv_diff.tv_sec = tv2->tv_sec - tv1->tv_sec; + tv_diff.tv_usec = tv2->tv_usec - tv1->tv_usec; + if (tv_diff.tv_usec < 0) { + tv_diff.tv_sec--; + tv_diff.tv_usec += 1000000; + } + + return (float)tv_diff.tv_sec + (float)(0.000001 * tv_diff.tv_usec); +} + +/* + * Initialize the default print format + */ +static int default_print_initialize(void) +{ + g.device_rec = util_rec_new_wide("-"); + util_rec_def(g.device_rec, "device", UTIL_REC_ALIGN_LEFT, 7, + "DEVICE"); + util_rec_def(g.device_rec, "kind", UTIL_REC_ALIGN_LEFT, 5, ""); + util_rec_def(g.device_rec, "type", UTIL_REC_ALIGN_LEFT, 33, + "TYPE"); + util_rec_def(g.device_rec, "time", UTIL_REC_ALIGN_LEFT, 20, + "TIMESTAMP"); + + g.counter_rec = util_rec_new_wide("-"); + util_rec_def(g.counter_rec, "name", UTIL_REC_ALIGN_LEFT, 18, + "COUNTER"); + util_rec_def(g.counter_rec, "ops", UTIL_REC_ALIGN_RIGHT, 10, + "OPS"); + util_rec_def(g.counter_rec, "rate", UTIL_REC_ALIGN_RIGHT, 12, + "RATE"); + util_rec_def(g.counter_rec, "utilization", UTIL_REC_ALIGN_RIGHT, + 12, "UTILIZATION"); + util_rec_def(g.counter_rec, "duration", UTIL_REC_ALIGN_RIGHT, + 15, "AVG.DURATION"); + + return 0; +} + +/* + * Terminate the default print format + */ +static int default_print_terminate(void) +{ + util_rec_free(g.counter_rec); + util_rec_free(g.device_rec); + + return 0; +} + +/* + * Print the header lines for the default print format + */ +static int default_print_header(void) +{ + char timestamp[64]; + struct utsname un; + struct tm *tm; + time_t t; + + time(&t); + tm = localtime(&t); + strftime(timestamp, sizeof(timestamp), "%x", tm); + + if (uname(&un) != 0) + return -errno; + + printf("%s %s (%s) \t%s\t%s\n\n", un.sysname, un.release, + un.nodename, timestamp, un.machine); + + return 0; +} + +/* + * Print the footer lines for the default print format + */ +static int default_print_footer(void) +{ + printf("\n"); + return 0; +} + +/* + * Print the interval header lines for the default print format + */ +static int default_print_interval_header(unsigned long interval_count, + const char *timestamp) +{ + + printf("*****************************************************" + "***************\n"); + printf("TIME: %s\t\tINTERVAL: %lu\n\n", timestamp, interval_count); + return 0; +} + +/* + * Prints the separator lines in front of a device for the default print format + */ +static int default_print_device_header(bool is_apqn, uint8_t card, + uint8_t domain, const char *type, + const char *timestamp) +{ + if (is_apqn) + util_rec_set(g.device_rec, "device", "%02x.%04x", card, + domain); + else + util_rec_set(g.device_rec, "device", "%02x", card); + util_rec_set(g.device_rec, "kind", "%s", + is_apqn ? "APQN" : "CARD"); + util_rec_set(g.device_rec, "type", "%s", type); + util_rec_set(g.device_rec, "time", "%s", timestamp); + + util_rec_print_hdr(g.device_rec); + util_rec_print(g.device_rec); + printf("\n"); + + util_rec_set_indent(g.counter_rec, is_apqn ? 8 : 4); + util_rec_print_hdr(g.counter_rec); + + return 0; +} + +/* + * Prints the separator lines after a device for the default print format + */ +static int default_print_device_footer(void) +{ + + printf("\n"); + return 0; +} + + +/** + * Prints the counter data for the default print format + */ +static int default_print_counter_data(bool UNUSED(is_apqn), + uint8_t UNUSED(card), + uint8_t UNUSED(domain), + const char *UNUSED(type), + const char *UNUSED(timestamp), + const char *name, + struct interval_values *vals) +{ + util_rec_set(g.counter_rec, "name", "%s", name); + util_rec_set(g.counter_rec, "ops", "%llu", vals->count); + util_rec_set(g.counter_rec, "rate", "%.2f", vals->rate); + util_rec_set(g.counter_rec, "utilization", "%.2f %%", + vals->utilization * 100); + if (vals->duration >= 1) + util_rec_set(g.counter_rec, "duration", "%.3f sec ", + vals->duration); + else if (vals->duration >= 0.001) + util_rec_set(g.counter_rec, "duration", "%.3f msec", + vals->duration * 1000); + else + util_rec_set(g.counter_rec, "duration", "%.3f usec", + vals->duration * 1000000); + util_rec_print(g.counter_rec); + + return 0; +} + +/* + * Prints a separator between the counter lines and the totals line for the + * default print format + */ +static int default_print_counter_separator(void) +{ + util_rec_print_separator(g.counter_rec); + return 0; +} + +/* + * Initialize the JSON print format + */ +static int json_print_initialize(void) +{ + /* Use a decimal point to make JSON code compliant with RFC7159 */ + setlocale(LC_NUMERIC, "C"); + return 0; +} + +/* + * Print the header lines for the JSON print format + */ +static int json_print_header(void) +{ + char timestamp[64]; + struct utsname un; + struct tm *tm; + time_t t; + + time(&t); + tm = localtime(&t); + strftime(timestamp, sizeof(timestamp), "%x", tm); + + if (uname(&un) != 0) + return -errno; + + printf("{\"zcryptstats\": {\n"); + printf("\t\"host\": {\n"); + printf("\t\t\"nodename\": \"%s\",\n", un.nodename); + printf("\t\t\"sysname\": \"%s\",\n", un.sysname); + printf("\t\t\"release\": \"%s\",\n", un.release); + printf("\t\t\"machine\": \"%s\",\n", un.machine); + printf("\t\t\"date\": \"%s\",\n", timestamp); + printf("\t\t\"statistics\": [\n"); + + return 0; +} + +/* + * Print the footer lines for the JSON print format + */ +static int json_print_footer(void) +{ + printf("\n\t\t]\n"); + printf("\t}\n"); + printf("}}\n"); + + return 0; +} + +/* + * Print the interval header lines for the JSON print format + */ +static int json_print_interval_header(unsigned long interval_count, + const char *timestamp) +{ + if (interval_count > 1) + printf(",\n"); + printf("\t\t\t{\n"); + printf("\t\t\t\t\"interval\": %lu, \"timestamp\": \"%s\"," + " \"devices\": [\n", interval_count, timestamp); + + return 0; +} + +/* + * Print the interval footer lines for the JSON print format + */ +static int json_print_interval_footer(void) +{ + if (!g.first_device) + printf("\n"); + printf("\t\t\t\t]\n"); + printf("\t\t\t}"); + + return 0; +} + +/* + * Prints the separator lines in front of a device for the JSON print format + */ +static int json_print_device_header(bool is_apqn, uint8_t card, + uint8_t domain, const char *type, + const char *UNUSED(timestamp)) +{ + if (!g.first_device) + printf(",\n"); + printf("\t\t\t\t\t{"); + if (is_apqn) + printf("\"device\": \"%02x.%04x\"", card, + domain); + else + printf("\"device\": \"%02x\"", card); + printf(", \"type\": \"%s\",\n", type); + printf("\t\t\t\t\t \"counters\": [\n"); + + return 0; +} + +/* + * Prints the separator lines after a device for the JSON print format + */ +static int json_print_device_footer(void) +{ + if (!g.first_counter) + printf("\n"); + printf("\t\t\t\t\t ]}"); + + return 0; +} + + +/** + * Prints the counter data for the JSON print format + */ +static int json_print_counter_data(bool UNUSED(is_apqn), + uint8_t UNUSED(card), + uint8_t UNUSED(domain), + const char *UNUSED(type), + const char *UNUSED(timestamp), + const char *name, + struct interval_values *vals) +{ + if (!g.first_counter) + printf(",\n"); + printf("\t\t\t\t\t\t{\"counter\": \"%s\", \"ops\": %llu, " + "\"rate\": %.2f, \"utilization\": %.2f, \"duration\": %.9f}", + name, vals->count, vals->rate, vals->utilization * 100, + vals->duration); + + return 0; +} + +static int table_print_initialize(void) +{ + g.counter_rec = util_rec_new_wide("-"); + util_rec_def(g.counter_rec, "time", UTIL_REC_ALIGN_LEFT, 20, + "TIMESTAMP"); + util_rec_def(g.counter_rec, "device", UTIL_REC_ALIGN_LEFT, 7, + "DEVICE"); + util_rec_def(g.counter_rec, "ops", UTIL_REC_ALIGN_RIGHT, 10, + "OPS"); + util_rec_def(g.counter_rec, "rate", UTIL_REC_ALIGN_RIGHT, 12, + "RATE"); + util_rec_def(g.counter_rec, "utilization", UTIL_REC_ALIGN_RIGHT, + 12, "UTILIZATION"); + util_rec_def(g.counter_rec, "duration", UTIL_REC_ALIGN_RIGHT, + 15, "AVG.DURATION"); + return 0; +} + +static int table_print_terminate(void) +{ + util_rec_free(g.counter_rec); + return 0; +} + +static int table_print_header(void) +{ + int rc; + + rc = default_print_header(); + if (rc != 0) + return rc; + + util_rec_print_hdr(g.counter_rec); + return 0; +} + +static int table_print_interval_footer(void) +{ + util_rec_print_separator(g.counter_rec); + return 0; +} + +static int table_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain, + const char *UNUSED(type), + const char *timestamp, + const char *UNUSED(name), + struct interval_values *vals) +{ + if (is_apqn) + util_rec_set(g.counter_rec, "device", "%02x.%04x", card, + domain); + else + util_rec_set(g.counter_rec, "device", "%02x", card); + util_rec_set(g.counter_rec, "time", "%s", timestamp); + + util_rec_set(g.counter_rec, "ops", "%llu", vals->count); + util_rec_set(g.counter_rec, "rate", "%.2f", vals->rate); + util_rec_set(g.counter_rec, "utilization", "%.2f %%", + vals->utilization * 100); + if (vals->duration >= 1) + util_rec_set(g.counter_rec, "duration", "%.3f sec ", + vals->duration); + else if (vals->duration >= 0.001) + util_rec_set(g.counter_rec, "duration", "%.3f msec", + vals->duration * 1000); + else + util_rec_set(g.counter_rec, "duration", "%.3f usec", + vals->duration * 1000000); + + util_rec_print(g.counter_rec); + + return 0; +} + +static int csv_print_initialize(void) +{ + /* Use a decimal point to not conflict with the colon separator char */ + setlocale(LC_NUMERIC, "C"); + + g.counter_rec = util_rec_new_csv(","); + util_rec_def(g.counter_rec, "time", UTIL_REC_ALIGN_LEFT, 20, + "TIMESTAMP"); + util_rec_def(g.counter_rec, "device", UTIL_REC_ALIGN_LEFT, 7, + "DEVICE"); + util_rec_def(g.counter_rec, "ops", UTIL_REC_ALIGN_RIGHT, 10, + "OPS"); + util_rec_def(g.counter_rec, "rate", UTIL_REC_ALIGN_RIGHT, 12, + "RATE"); + util_rec_def(g.counter_rec, "utilization", UTIL_REC_ALIGN_RIGHT, + 12, "UTILIZATION"); + util_rec_def(g.counter_rec, "duration", UTIL_REC_ALIGN_RIGHT, + 15, "AVG.DURATION"); + return 0; +} + +static int csv_print_terminate(void) +{ + util_rec_free(g.counter_rec); + return 0; +} + +static int csv_print_header(void) +{ + util_rec_print_hdr(g.counter_rec); + return 0; +} + + +static int csv_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain, + const char *UNUSED(type), + const char *timestamp, + const char *UNUSED(name), + struct interval_values *vals) +{ + if (is_apqn) + util_rec_set(g.counter_rec, "device", "%02x.%04x", card, + domain); + else + util_rec_set(g.counter_rec, "device", "%02x", card); + util_rec_set(g.counter_rec, "time", "%s", timestamp); + util_rec_set(g.counter_rec, "ops", "%llu", vals->count); + util_rec_set(g.counter_rec, "rate", "%.2f", vals->rate); + util_rec_set(g.counter_rec, "utilization", "%.2f %%", + vals->utilization * 100); + util_rec_set(g.counter_rec, "duration", "%.9f", vals->duration); + + util_rec_print(g.counter_rec); + + return 0; +} + +/* + * Calculates number of ops, utilization, duration and rate of an + * interval from the timer values, scale and interval time. + */ +static void calc_interval_values(struct chsc_cmb_entry *current, + struct chsc_cmb_entry *previous, + float scale, + float interval_time, + struct interval_values *result) +{ + u64 tdiff; + + tdiff = current->t - previous->t; + result->count = current->c - previous->c; + result->utilization = (double)(tdiff) * scale / interval_time; + if (result->count > 0) + result->duration = (double)(tdiff) * scale / result->count; + else + result->duration = 0; + result->rate = (double)result->count / interval_time; +} + +/* + * Print the measurement data of an interval + */ +static int print_interval_data(struct interval_data *data, + const char *timestamp, float interval_time) +{ + struct chsc_cmb_entry total_current; + struct chsc_cmb_entry total_previous; + struct interval_values vals; + const char *type, *counter; + uint32_t mask = 0x80000000; + bool totals_found = false; + size_t len; + int i, rc; + + len = get_cmb_length(&data->current); + type = get_card_name(data->current.header.ct, data->current.header.mt); + + rc = pr_call(print_device_header)(data->current.header.format == 1, + data->current.header.ax, + data->current.header.dx, type, + timestamp); + if (rc != 0) + return rc; + + memset(&total_current, 0, sizeof(total_current)); + memset(&total_previous, 0, sizeof(total_previous)); + + g.first_counter = true; + + for (i = 0; i < 32 && + offsetofend(struct chsc_cmb_area, entries[i]) <= len; i++) { + if (data->current.header.v & mask) { + if (is_counter_totals(data->current.header.ct, + data->current.header.mt, i)) { + total_current.t = data->current.entries[i].t; + total_current.c = data->current.entries[i].c; + total_previous.t = data->previous.entries[i].t; + total_previous.c = data->previous.entries[i].c; + totals_found = true; + } else if (!totals_found) { + total_current.t += data->current.entries[i].t; + total_current.c += data->current.entries[i].c; + total_previous.t += data->previous.entries[i].t; + total_previous.c += data->previous.entries[i].c; + } + + if (g.only_totals) + continue; + + calc_interval_values(&data->current.entries[i], + &data->previous.entries[i], + data->current.header.s, + interval_time, + &vals); + + counter = get_counter_name(data->current.header.ct, + data->current.header.mt, i); + + rc = pr_call(print_counter_data)( + data->current.header.format == 1, + data->current.header.ax, + data->current.header.dx, type, + timestamp, counter, &vals); + if (rc != 0) + break; + + g.first_counter = false; + } + mask >>= 1; + } + + if (!g.no_totals) { + rc = pr_call(print_counter_separator)(); + if (rc != 0) + return rc; + + calc_interval_values(&total_current, &total_previous, + data->current.header.s, interval_time, + &vals); + + rc = pr_call(print_counter_data)( + data->current.header.format == 1, + data->current.header.ax, + data->current.header.dx, type, + timestamp, "Total", &vals); + if (rc != 0) + return rc; + } + + rc = pr_call(print_device_footer)(); + if (rc != 0) + return rc; + + return 0; +} + +/* + * Print the measured data + */ +static int print_measurement_data(unsigned long interval_count, + float interval_time, const char *timestamp) +{ + bool header_printed = false; + struct interval_data *dd; + struct card_data *cd; + int card, domain, rc; + + g.first_device = true; + for (card = 0; card < NUM_CARDS; card++) { + cd = g.cards[card]; + if (cd == NULL) + continue; + + if (!cd->data.current_valid) { + /* Not update in last interval -> free it */ + free_card_data(cd); + g.cards[card] = NULL; + + pr_verbose("Card %02x removed", card); + continue; + } + + if (cd->data.previous_valid) { + if (memcmp(&cd->data.current.header, + &cd->data.previous.header, + sizeof(struct chsc_cmb_header)) != 0) { + free_card_data(cd); + g.cards[card] = NULL; + + pr_verbose("CMB header mismatch, card %02x " + "removed", card); + continue; + } + + if (!header_printed) { + rc = pr_call(print_interval_header)( + interval_count, timestamp); + if (rc != 0) + return rc; + header_printed = true; + } + + rc = print_interval_data(&cd->data, timestamp, + interval_time); + if (rc != 0) + return rc; + + g.first_device = false; + } + + cd->data.current_valid = false; + + for (domain = 0; domain < NUM_DOMAINS; domain++) { + dd = cd->domains[domain]; + if (dd == NULL) + continue; + + if (!dd->current_valid) { + /* Not update in last interval -> free it */ + free(dd); + cd->domains[domain] = NULL; + + pr_verbose("APQN %02x.%04x removed", card, + domain); + } + + if (dd->previous_valid) { + if (memcmp(&dd->current.header, + &dd->previous.header, + sizeof(struct chsc_cmb_header))) { + free(dd); + cd->domains[domain] = NULL; + + pr_verbose("CMB header mismatch, APQN " + "%02x.%04x removed", card, + domain); + continue; + } + + rc = print_interval_data(dd, timestamp, + interval_time); + if (rc != 0) + return rc; + } + + dd->current_valid = false; + } + } + + if (header_printed) { + rc = pr_call(print_interval_footer)(); + if (rc != 0) + return rc; + } else if (interval_count > 0) { + pr_verbose("No data was reported in this interval"); + warnx("Failed to get card crypto measurement data: %s", + strerror(ENODEV)); + return -ENODEV; + } + + return 0; +} + +/* + * Perform the measurement in intervals + */ +static int perform_measurement(void) +{ + struct timeval tv_current, tv_previous; + struct sigaction alrm_act, int_act; + unsigned long interval_count = 0; + float interval_time; + char timestamp[64]; + struct tm *tm; + int rc; + + /* Set a handler for SIGINT/SIGTERM */ + memset(&int_act, 0, sizeof(int_act)); + int_act.sa_handler = int_handler; + sigaction(SIGINT, &int_act, NULL); + sigaction(SIGTERM, &int_act, NULL); + + /* Set a handler for SIGALRM */ + memset(&alrm_act, 0, sizeof(alrm_act)); + alrm_act.sa_handler = alarm_handler; + sigaction(SIGALRM, &alrm_act, NULL); + + rc = pr_call(print_initialize)(); + if (rc != 0) + return rc; + + rc = pr_call(print_header)(); + if (rc != 0) + return 0; + + alarm(g.interval); + + memset(&tv_current, 0, sizeof(tv_current)); + while (!quit) { + pr_verbose("Interval %lu", interval_count); + + tv_previous = tv_current; + rc = gettimeofday(&tv_current, NULL); + if (rc != 0) + break; + + tm = localtime(&tv_current.tv_sec); + if (tm == NULL) + break; + strftime(timestamp, sizeof(timestamp), "%x %X", tm); + interval_time = time_diff(&tv_previous, &tv_current); + + rc = get_card_measurement_data(); + if (rc != 0) + break; + + rc = print_measurement_data(interval_count, interval_time, + timestamp); + if (rc != 0) + break; + + if (g.count > 0 && interval_count >= g.count) { + pr_verbose("Interval limit reached"); + break; + } + interval_count++; + + if (quit) + break; + + pause(); + } + + if (quit) + pr_verbose("Measurement stopped by user"); + + alarm(0); + memset(&alrm_act, 0, sizeof(alrm_act)); + alrm_act.sa_handler = SIG_DFL; + sigaction(SIGALRM, &alrm_act, NULL); + + rc = pr_call(print_footer)(); + if (rc != 0) + return 0; + + rc = pr_call(print_terminate)(); + if (rc != 0) + return rc; + + return 0; +} + +/* + * Parse the type mapping specification: + * TYPE:MODE=TYPE:MODE[,TYPE:MODE=TYPE:MODE[,...]] + */ +static int parse_type_mapping(char *mapping) +{ + unsigned int from_type, to_type, from_mode, to_mode; + struct type_mapping *map; + char *tok; + + tok = strtok(mapping, ","); + while (tok != NULL) { + if (sscanf(tok, "%u:%u=%u:%u", &from_type, &from_mode, + &to_type, &to_mode) != 4) { + warnx("Invalid type mapping: %s", tok); + return -EINVAL; + } + + pr_verbose("from_type: %u from_mode: %u to_type: %u " + "to_mode: %u", from_type, from_mode, to_type, + to_mode); + + if (from_type < NUM_CRYPTO_TYPES && + crypto_types[from_type].name != NULL && + from_mode < crypto_types[from_type].num_modes && + crypto_types[from_type].modes[from_mode].counters != NULL) { + warnx("Cannot map a known type/mode to another " + "type/mode: %s", tok); + return -EINVAL; + } + + if (to_type >= NUM_CRYPTO_TYPES || + crypto_types[to_type].name == NULL || + to_mode >= crypto_types[to_type].num_modes || + crypto_types[to_type].modes[to_mode].counters == NULL) { + warnx("Cannot map a type/mode to an unknown " + "type/mode: %s", tok); + return -EINVAL; + } + + map = util_malloc(sizeof(struct type_mapping)); + map->from_type = from_type; + map->from_mode = from_mode; + map->to_type = to_type; + map->to_mode = to_mode; + + map->next = g.type_mapping; + g.type_mapping = map; + + tok = strtok(NULL, ","); + } + + return 0; +} + +static void free_type_mapping(void) +{ + struct type_mapping *map = g.type_mapping; + struct type_mapping *next; + + while (map != NULL) { + next = map->next; + free(map); + map = next; + } +} + +static int add_device_selection(const char *device_id) +{ + int card = -1, domain = -1; + struct device_selection *dev; + + pr_verbose("device_id: '%s'", device_id); + + /* check for 'card[.domain]' specification */ + if (sscanf(device_id, "%x.%x", &card, &domain) >= 1) { + pr_verbose("card: %d domain: %d", card, domain); + if (card < 0 || card > g.max_card_used) { + warnx("Invalid card specified: %s", device_id); + return -EINVAL; + } + g.card_mask[MASK_WORD_NO(card)] |= MASK_BIT(card); + g.min_card = MIN(g.min_card, card); + g.max_card = MAX(g.max_card, card); + + if (domain >= 0) { + if (domain > NUM_DOMAINS) { + warnx("Invalid domain specified: %s", + device_id); + return -EINVAL; + } + g.domain_mask[MASK_WORD_NO(domain)] |= + MASK_BIT(domain); + g.min_domain = MIN(g.max_domain, domain); + g.max_domain = MAX(g.max_domain, domain); + } else { + memset(g.domain_mask, 0xff, sizeof(g.domain_mask)); + g.min_domain = 0; + g.max_domain = NUM_DOMAINS - 1; + } + + dev = util_malloc(sizeof(struct device_selection)); + dev->card = card; + dev->domain = domain; + dev->next = g.dev_selection; + g.dev_selection = dev; + + return 0; + } + /* check for '.domain' specification */ + if (device_id[0] == '.' && + sscanf(device_id + 1, "%x", &domain) == 1) { + pr_verbose("domain: %d", domain); + if (domain < 0 || domain > NUM_DOMAINS) { + warnx("Invalid domain specified: %s", device_id); + return -EINVAL; + } + + g.domain_mask[MASK_WORD_NO(domain)] |= MASK_BIT(domain); + g.min_domain = MIN(g.max_domain, domain); + g.max_domain = MAX(g.max_domain, domain); + memset(g.card_mask, 0xff, sizeof(g.card_mask)); + g.min_card = 0; + g.max_card = g.max_card_used; + + dev = util_malloc(sizeof(struct device_selection)); + dev->card = -1; + dev->domain = domain; + dev->next = g.dev_selection; + g.dev_selection = dev; + + return 0; + } + + warnx("Invalid device ID specified: %s", device_id); + return -EINVAL; +} + +/* + * Frees the device selection list + */ +static void free_device_selection(void) +{ + struct device_selection *dev = g.dev_selection; + struct device_selection *next; + + while (dev != NULL) { + next = dev->next; + free(dev); + + dev = next; + } +} + +/* + * Build the AP and domain selection mask from the specified device ID's. + */ +static int parse_device_selection(void) +{ + int i, rc; + + g.min_card = NUM_CARDS - 1; + g.max_card = 0; + g.min_domain = NUM_DOMAINS - 1; + g.max_domain = 0; + + for (i = 0; g.device_ids[i] != NULL; i++) { + rc = add_device_selection(g.device_ids[i]); + if (rc != 0) + return rc; + } + + if (i == 0) { + /* No device-IDs specified */ + memset(g.card_mask, 0xff, sizeof(g.card_mask)); + g.min_card = 0; + g.max_card = g.max_card_used; + memset(g.domain_mask, 0xff, sizeof(g.domain_mask)); + g.min_domain = 0; + g.max_domain = NUM_DOMAINS - 1; + } + + pr_verbose("Min card: %u, max card: %u", g.min_card, g.max_card); + pr_verbose("Min domain: %u, max domain: %u", g.min_domain, + g.max_domain); + + return 0; +} + +/* + * Entry point + */ +int main(int argc, char *argv[]) +{ + char *endp; + int c, rc; + + util_prg_init(&prg); + util_opt_init(opt_vec, NULL); + + while (1) { + c = util_opt_getopt_long(argc, argv); + if (c == -1) + break; + switch (c) { + case 'i': + g.interval = strtoll(optarg, &endp, 0); + if (*optarg == '\0' || *endp != '\0' || + g.interval <= 0 || + (g.interval == LLONG_MAX && errno == ERANGE)) { + warnx("Invalid value for '--interval'|" + "'-i': '%s'", optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; + case 'c': + g.count = strtoull(optarg, &endp, 0); + if (*optarg == '\0' || *endp != '\0' || g.count == 0 || + (g.count == LLONG_MAX && errno == ERANGE)) { + warnx("Invalid value for '--count'|" + "'-c': '%s'", optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; + case 'o': + if (strcasecmp(optarg, "JSON") == 0) { + g.print_funcs = &json_print; + } else if (strcasecmp(optarg, "TABLE") == 0) { + g.only_totals = true; + g.print_funcs = &table_print; + } else if (strcasecmp(optarg, "CSV") == 0) { + g.only_totals = true; + g.print_funcs = &csv_print; + } else { + warnx("Invalid value for '--output'|" + "'-o': '%s'", optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; + case 't': + g.no_totals = true; + break; + case 'T': + g.only_totals = true; + break; + case 'a': + g.no_apqn = true; + break; + case 'M': + g.map_type = optarg; + break; + case 'A': + g.all = true; + break; + case 'O': + g.only_online = true; + break; + case 'V': + g.verbose = true; + break; + case 'h': + util_prg_print_help(); + util_opt_print_help(); + print_adapter_id_help(); + return EXIT_SUCCESS; + case 'v': + util_prg_print_version(); + return EXIT_SUCCESS; + default: + util_opt_print_parse_error(c, argv); + return EXIT_FAILURE; + } + } + + /* remaining positional args are device IDs */ + g.device_ids = &argv[optind]; + + if (g.only_totals && g.no_totals) { + warnx("Either --no-totals or --only-totals can be specified, " + "but not both"); + return EXIT_FAILURE; + } + + if (g.only_online && g.all) { + warnx("Either --only-online or --all can be specified, " + "but not both"); + return EXIT_FAILURE; + } + + pr_verbose("Interval: %ld Count: %ld", g.interval, g.count); + + rc = get_max_card_index(&g.max_card_used); + if (rc != 0) { + rc = EXIT_FAILURE; + goto out; + } + + rc = parse_device_selection(); + if (rc != 0) { + rc = EXIT_FAILURE; + goto out; + } + + if (g.map_type != NULL) { + rc = parse_type_mapping(g.map_type); + if (rc != 0) { + rc = EXIT_FAILURE; + goto out; + } + } + + g.chsc_fd = open(CHSC_DEVICE, O_RDWR); + if (g.chsc_fd < 0) { + warnx("File '%s:' %s", CHSC_DEVICE, strerror(errno)); + return EXIT_FAILURE; + } + pr_verbose("Device '%s' has been opened successfully", CHSC_DEVICE); + + /* Don't buffer data if redirected to a pipe */ + setbuf(stdout, NULL); + + rc = perform_measurement(); + if (rc != 0) { + rc = EXIT_FAILURE; + goto out; + } +out: + if (g.chsc_fd >= 0) + close(g.chsc_fd); + free_device_selection(); + free_type_mapping(); + free_interval_data(); + + return rc; +} + -- 2.21.3 From c0670b62ccd7a9f1f1b3a8a49114c2e3c673ecd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Mon, 1 Apr 2019 09:53:06 +0200 Subject: [PATCH 21/56] zpcictl: Check for regular directory (#1695001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In case a regular directory was specified, rather than a device node, the check if the device exists will pass. The following code paths then assume a slot id was specified. This in turn may lead to a buffer overflow when the device data is copied to to the zpci_device struct. Check if the specified path is a regular directory and prevent a possible later buffer overflow and copying wrong data respectively. Signed-off-by: Jan Höppner (cherry picked from commit c7a255fcd9433bba6dd516f6b28a139bbc5351d3) --- zpcictl/zpcictl.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zpcictl/zpcictl.c b/zpcictl/zpcictl.c index 7cfa0d3..f2a55ad 100644 --- a/zpcictl/zpcictl.c +++ b/zpcictl/zpcictl.c @@ -249,8 +249,12 @@ static int device_exists(char *dev) char *path; int rc = 0; + /* In case a device node is specified, this will be sufficiant */ + if (util_path_exists(dev) && !util_path_is_dir(dev)) + return 1; + path = util_path_sysfs("bus/pci/devices/%s", dev); - if (util_path_exists(path) || util_path_exists(dev)) + if (util_path_exists(path)) rc = 1; free(path); -- 2.21.3 From 9f0fe2e3d5bcc079577700d778e3f1a58fced510 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Wed, 10 Jul 2019 13:01:09 +0200 Subject: [PATCH 22/56] cpumf: Add support for CPU-Measurement Facility counters SVN 6 (#1683276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for CPU-Measurement facility counter second version number 6. This adds some more counters to the crypto counter set. Extended counter set is the same as for z14. Signed-off-by: Thomas Richter Signed-off-by: Jan Höppner (cherry picked from commit f110a77b68d46f10cab8847cf73fe92686ae7d11) --- cpumf/Makefile | 2 +- cpumf/bin/cpumf_helper.in | 7 +++- ...svn-generic.ctr => cpum-cf-csvn-12345.ctr} | 0 cpumf/data/cpum-cf-csvn-6.ctr | 33 +++++++++++++++++++ cpumf/data/cpum-cf-hw-counter.map | 6 +++- 5 files changed, 45 insertions(+), 3 deletions(-) rename cpumf/data/{cpum-cf-csvn-generic.ctr => cpum-cf-csvn-12345.ctr} (100%) create mode 100644 cpumf/data/cpum-cf-csvn-6.ctr diff --git a/cpumf/Makefile b/cpumf/Makefile index 3fc4c0f..2e11810 100644 --- a/cpumf/Makefile +++ b/cpumf/Makefile @@ -6,7 +6,7 @@ include ../common.mak CPUMF_DATADIR = $(TOOLS_DATADIR)/cpumf DATA_FILES = cpum-cf-hw-counter.map \ cpum-cf-cfvn-1.ctr cpum-cf-cfvn-3.ctr \ - cpum-cf-csvn-generic.ctr \ + cpum-cf-csvn-12345.ctr cpum-cf-csvn-6.ctr \ cpum-cf-extended-z10.ctr cpum-cf-extended-z196.ctr \ cpum-cf-extended-zEC12.ctr cpum-sf-modes.ctr \ cpum-cf-extended-z13.ctr cpum-cf-extended-z14.ctr diff --git a/cpumf/bin/cpumf_helper.in b/cpumf/bin/cpumf_helper.in index 4757725..e1da017 100755 --- a/cpumf/bin/cpumf_helper.in +++ b/cpumf/bin/cpumf_helper.in @@ -266,7 +266,12 @@ sub cpumf_load_ctrdef($;$) # List of "generic" counter sets my @def = (); push @def, "cfvn-" . $version->{cfvn}; - push @def, "csvn-generic"; + if ($version->{csvn} >= 1 && $version->{csvn} <= 6) { + push @def, "csvn-12345"; + } + if ($version->{csvn} == 6) { + push @def, "csvn-6"; + } my $h = {}; # Load counter set definition diff --git a/cpumf/data/cpum-cf-csvn-generic.ctr b/cpumf/data/cpum-cf-csvn-12345.ctr similarity index 100% rename from cpumf/data/cpum-cf-csvn-generic.ctr rename to cpumf/data/cpum-cf-csvn-12345.ctr diff --git a/cpumf/data/cpum-cf-csvn-6.ctr b/cpumf/data/cpum-cf-csvn-6.ctr new file mode 100644 index 0000000..4ea3b44 --- /dev/null +++ b/cpumf/data/cpum-cf-csvn-6.ctr @@ -0,0 +1,33 @@ +Counter: 80 Name:ECC_FUNCTION_COUNT +Short-Description:ECC Function Count +Description: +This counter counts the +total number of the elliptic-curve cryptography (ECC) +functions issued by the CPU. +. +Counter: 81 Name:ECC_CYCLES_COUNT +Short-Description:ECC Cycles Count +Description: +This counter counts the total +number of CPU cycles when the ECC coprocessor is +busy performing the elliptic-curve cryptography +(ECC) functions issued by the CPU. +. +Counter: 82 Name:ECC_BLOCKED_FUNCTION_COUNT +Short-Description:Ecc Blocked Function Count +Description: +This counter +counts the total number of the elliptic-curve +cryptography (ECC) functions that are issued by the CPU +and are blocked because the ECC coprocessor is +busy performing a function issued by another CPU. +. +Counter: 83 Name:ECC_BLOCKED_CYCLES_COUNT +Short-Description:ECC Blocked Cycles Count +Description: +This counter counts +the total number of CPU cycles blocked for the elliptic-curve +cryptography (ECC) functions issued by the +CPU because the ECC coprocessor is busy perform- +ing a function issued by another CPU. +. diff --git a/cpumf/data/cpum-cf-hw-counter.map b/cpumf/data/cpum-cf-hw-counter.map index a8a2846..5729b95 100644 --- a/cpumf/data/cpum-cf-hw-counter.map +++ b/cpumf/data/cpum-cf-hw-counter.map @@ -14,7 +14,8 @@ 'cfvn-3' => 'cpum-cf-cfvn-3.ctr', # CSVN - 'csvn-generic' => 'cpum-cf-csvn-generic.ctr', + 'csvn-12345' => 'cpum-cf-csvn-12345.ctr', + 'csvn-6' => 'cpum-cf-csvn-6.ctr', # Extended counters 2097 => 'cpum-cf-extended-z10.ctr', @@ -27,4 +28,7 @@ 2965 => 'cpum-cf-extended-z13.ctr', 3906 => 'cpum-cf-extended-z14.ctr', 3907 => 'cpum-cf-extended-z14.ctr', + # Identical with z14 + 8561 => 'cpum-cf-extended-z14.ctr', + 8562 => 'cpum-cf-extended-z14.ctr', }; -- 2.21.3 From f58ee5580cd6e357dce488d9cd3d999e7ad61f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Fri, 19 Jul 2019 09:41:08 +0200 Subject: [PATCH 23/56] ziomon: fix utilization recording with multi-digit scsi hosts (#1731203) Description: ziomon: fix utilization recording with multi-digit scsi hosts Symptom: During running of ziomon script, user receives ziomon error: "ziomon: Number of LUNs does not match number of devices: 2 devices and 1 LUNs". Also user receives ziomon_util error: "ziomon_util: Path does not exist: /sys/class/scsi_host/host0/utilization - correct kernel version?". After collection of records, user receives error during using of ziorep_utilization or ziorep_traffic: "ziorep_traffic: Could not retrieve initial data - data files corrupted or broken, or the .agg file is missing." Problem: s390-tools-1.9.0 introduced a new way of recognizing of multipath device paths with using of sed command invocation in ziomon script. With this new way of recognizing, if there are paths, related to multipath device, with SCSI host ID longer, than one digit, ziomon incorrectly parses the multipath -l command output. It erroneously cuts off all but the least significant digit of the SCSI host ID (H) of paths in H:B:T:L format (Host:Bus:Target:Lun). This leads to passing of hosts (-a) and paths (-l) with wrong SCSI host ID to ziomon_util. In turn ziomon_util cannot recognize hosts with non-existing SCSI host ID and issues an error. Also, wrong sed command invocation could lead to receiving of duplicate LUNs by ziomon after parsing of multipath -l command output. Then ziomon excludes duplicates from WRP_LUNS, which leads to mismatch between number of LUNs and number of detected block devices and issues ziomon script error, without starting ziomon_util and without writing to specified log file. Solution: The regular expression to match a path in H:B:T:L format started with a greedy ".*", which erroneously consumed parts of the SCSI host ID (H). The solution is to replace the greedy ".*" by "[^0-9]*", so that sed command does not consume parts of the SCSI host ID any more. Reproduction: Create such multipath device, that its related path contains LUN with SCSI host ID longer, than one digit. Example for wrong SCSI host ID: $ multipath -l mpathc (36005076307ffc5e300000000000083f5) dm-2 IBM ... size=20G features='1 queue_if_no_path' hwhandler='0'... `-+- policy='service-time 0' prio=0 status=active |- 10:0:0:1089814659 sdb 8:16 active undef running `- 11:0:0:1089814659 sdf 8:80 active undef running Example for duplicate SCSI host ID: $ multipath -l mpathd (36005076307ffc5e300000000000083f4) dm-3 IBM ... size=20G features='1 queue_if_no_path' hwhandler='0'... `-+- policy='service-time 0' prio=0 status=active |- 10:0:0:1089749123 sda 8:0 active undef running `- 0:0:0:1089749123 sdd 8:48 active undef running Use ziomon tool with one of the described multipath device as input. Upstream-ID: f2dee9f542439cf07e00df4296b05a47b81e2469 --- ziomon/ziomon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziomon/ziomon b/ziomon/ziomon index 87c4bc4..26fa269 100755 --- a/ziomon/ziomon +++ b/ziomon/ziomon @@ -514,7 +514,7 @@ function check_for_multipath_devices() { (( i+=2 )); while [[ `echo "${mp_arr[$i]:0:1}" | grep -ve "[0-9a-zA-Z]"` ]] && [ $i -lt ${#mp_arr[@]} ]; do if [ `echo ${mp_arr[$i]} | grep -e "[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}" | wc -l` -ne 0 ]; then - line="`echo ${mp_arr[$i]} | sed 's/.*\([0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}\)/\1/'`"; + line="`echo ${mp_arr[$i]} | sed 's/[^0-9]*\([0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}:[0-9]\{1,\}\)/\1/'`"; checked_devs[${#checked_devs[@]}]=`echo $line | awk '{print "/dev/"$2}'`; ddebug " adding ${checked_devs[${#checked_devs[@]}-1]}"; WRP_HOST_ADAPTERS[${#WRP_HOST_ADAPTERS[@]}]="host${line%%:*}"; -- 2.21.3 From ef93298c3f0a8d318c0568f3bec10b76279bb15b Mon Sep 17 00:00:00 2001 From: Peter Oberparleiter Date: Tue, 12 Mar 2019 14:05:17 +0100 Subject: [PATCH 24/56] zdev: Do not export inacceptable attribute values (#1731960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For some device driver SysFS attributes, values read may not be acceptable input values for that attribute. An example would be the group of qeth VNICC attributes that return "n/a" when VNICC setup is not supported, but only accept "0" and "1" as valid values that can be written to it. This leads to errors such as the following when data for such attributes is imported: # chzdev f500 --import test.conf Importing configuration data from test.conf QETH device 0.0.f500:0.0.f501:0.0.f502 configure failed Error: Invalid value for qeth attribute: vnicc/flooding=n/a (*) Acceptable values: - Integers in the range 0 - 1 Use 'chzdev qeth --help-attribute vnicc/flooding' for more information Note: You can use --force to override safety checks (*) To fix this, change chzdev's --export function to skip any attribute value that is not acceptable for that attribute. Fixes: e831269e7433 ("zdev: Add support for VNIC Characteristics") Signed-off-by: Peter Oberparleiter Signed-off-by: Jan Höppner (cherry picked from commit 4ff6519961965deb278093c83ea0f46fb7c7c205) --- zdev/src/export.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zdev/src/export.c b/zdev/src/export.c index f1acb8a..a66db0b 100644 --- a/zdev/src/export.c +++ b/zdev/src/export.c @@ -78,6 +78,10 @@ static bool is_exportable(struct setting *s, config_t config) /* Skip values that cannot be determined. */ return false; } + if (!attrib_check_value(a, s->value)) { + /* Skip values that are not acceptable input values. */ + return false; + } if (!attrib_match_default(s->attrib, s->value)) { /* All non-default values should be exported. */ return true; -- 2.21.3 From de7f4178f2178268ed65292bf5e1f42894832d9a Mon Sep 17 00:00:00 2001 From: Stefan Haberland Date: Wed, 17 Jul 2019 17:26:40 +0200 Subject: [PATCH 25/56] zipl: do not overwrite BOOT_IMAGE entry (#1728677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The zipl internal loader adds a variable BOOT_IMAGE= to the commandline so that it is visible in the operating system which menu entry has been chosen. This entry was overwritten by the stage3 parameter page. Fix by re-arranging the internal memory layout and putting the command line extra param, which contains the BOOT_IMAGE entry, at 0xe000. This location is available because less than one page is used for the stack. Fixes: https://github.com/ibm-s390-tools/s390-tools/issues/67 Signed-off-by: Stefan Haberland Signed-off-by: Jan Höppner (cherry picked from commit 8bf5f8d0e293b294c9b7aaebea80e92634c7564d) --- zipl/boot/Makefile | 4 ++-- zipl/boot/menu.h | 2 +- zipl/boot/stage2.lds | 3 ++- zipl/boot/stage3.h | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/zipl/boot/Makefile b/zipl/boot/Makefile index a049797..0faf3ec 100644 --- a/zipl/boot/Makefile +++ b/zipl/boot/Makefile @@ -6,7 +6,7 @@ CFLAGS_BOOT = $(NO_PIE_CFLAGS) -Os -g -I../include -D__ASSEMBLY__ \ -fno-builtin -ffreestanding -fno-asynchronous-unwind-tables \ -fno-delete-null-pointer-checks \ -fexec-charset=IBM1047 -m64 -mpacked-stack \ - -mstack-size=8192 -mstack-guard=128 -msoft-float \ + -mstack-size=4096 -mstack-guard=128 -msoft-float \ -W -Wall -Wformat-security FILES = fba0.bin fba1b.bin fba2.bin \ @@ -90,7 +90,7 @@ stage3.bin: stage3.exec --only-section=.stage2dump.tail \ --only-section=.eckd2dump_mv.tail \ --only-section=.fixup \ - --pad-to=0xf000 \ + --pad-to=0xe000 \ $< $@ data.o: $(FILES) diff --git a/zipl/boot/menu.h b/zipl/boot/menu.h index 83338a8..1b103a8 100644 --- a/zipl/boot/menu.h +++ b/zipl/boot/menu.h @@ -15,7 +15,7 @@ #include "stage2.h" /* address of extra command line */ -#define COMMAND_LINE_EXTRA (0xA000-0x400) +#define COMMAND_LINE_EXTRA 0xE000 /* max command line length */ #define COMMAND_LINE_SIZE 896 diff --git a/zipl/boot/stage2.lds b/zipl/boot/stage2.lds index 41efa79..34ed014 100644 --- a/zipl/boot/stage2.lds +++ b/zipl/boot/stage2.lds @@ -12,7 +12,8 @@ * 0x6000-0x8fff Memory allocation (heap) * 0x9000-0x9fff Memory to load stage3 parameter to * 0xa000-0xdfff Memory to load stage3 to - * 0xe000-0xffff Stack + * 0xe000-0xe3ff command line extra + * 0xe400-0xffff Stack * * Special memory locations * ------------------------ diff --git a/zipl/boot/stage3.h b/zipl/boot/stage3.h index 91477b1..3a02001 100644 --- a/zipl/boot/stage3.h +++ b/zipl/boot/stage3.h @@ -22,7 +22,7 @@ #define OLDMEM_SIZE 0x10420 #define COMMAND_LINE 0x10480 #define COMMAND_LINE_SIZE 896 -#define COMMAND_LINE_EXTRA (0xA000-0x400) +#define COMMAND_LINE_EXTRA 0xE000 #define STAGE3_FLAG_SCSI 0x0001000000000000ULL #define STAGE3_FLAG_KDUMP 0x0002000000000000ULL -- 2.21.3 From d25f635ebdb0f2faa8ca018859c3223c3dac4d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Wed, 31 Jul 2019 15:25:00 +0200 Subject: [PATCH 26/56] fdasd: Fix exit status in error cases (#1734816) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In error cases, fdasd returns -1 which results in the return value 255. This is due to the fact that only the low-order 8 bits are used for the status value. See 2.13 Status Information [1] in the POSIX standard and the exit() POSIX man page [2] for more details. Instead of returning -1, use the EXIT_FAILURE constant to indicate unsuccessful termination properly. [1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html [2]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/exit.html Signed-off-by: Jan Höppner (cherry picked from commit 80443ab629a234e2af55dd9bcceb6fe150c6c79e) --- fdasd/fdasd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fdasd/fdasd.c b/fdasd/fdasd.c index 085d2be..5de7955 100644 --- a/fdasd/fdasd.c +++ b/fdasd/fdasd.c @@ -397,7 +397,7 @@ static void fdasd_error(fdasd_anchor_t *anc, enum fdasd_failure why, char *str) fputc('\n', stderr); fputs(err_str, stderr); - fdasd_exit(anc, -1); + fdasd_exit(anc, EXIT_FAILURE); } /* @@ -3040,5 +3040,5 @@ int main(int argc, char *argv[]) } } - return -1; + return EXIT_FAILURE; } -- 2.21.3 From 89ed5688c2b351f487e75e3035948aadcab73a8f Mon Sep 17 00:00:00 2001 From: Stefan Haberland Date: Wed, 7 Aug 2019 16:59:20 +0200 Subject: [PATCH 27/56] zipl: fix zfcp dump image location (#1730707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The zfcp dumper fails with the following error: MLOPDM003I: Machine loader finished, moving data to final storage location. uncompression error --- System halted HCPGIR450W CP entered; disabled wait PSW 00020001 80000000 00000000 DEADBEEF The zipl command shows overlapping components when installing the dumper to a zfcp disk: zipl -d /dev/sda1 Building bootmap directly on partition '/dev/sda1' Adding dump section kernel image......: /lib/s390-tools/zfcpdump/zfcpdump-image kernel parmline...: 'root=/dev/ram0 dump_mem=1 possible_cpus=1 cgroup_disable=memory ' component address: heap area.......: 0x00002000-0x00005fff stack area......: 0x0000f000-0x0000ffff internal loader.: 0x0000a000-0x0000dfff parameters......: 0x00009000-0x000091ff kernel image....: 0x00010000-0x005761ff ^^^^^^ parmline........: 0x00567000-0x005671ff ^^^^^^ Preparing boot device: sda. Done. With the secure IPL patchset the offset of the kernel image has been removed for the normal IPL case but it has not been removed for the dump image which leads to the overlap of 0x10000. Fix by removing the offset for the dump case. Signed-off-by: Stefan Haberland Reviewd-by: Mikhail Zaslonko Tested-by: Mikhail Zaslonko Signed-off-by: Jan Höppner (cherry picked from commit a9f4ae2db3fa513941244956e323cb6ce214ca5f) --- zipl/src/job.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zipl/src/job.c b/zipl/src/job.c index 2d8de8f..e68d1b7 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -559,7 +559,7 @@ get_dump_components(struct job_dump_data *dump, /* Fill in component data */ num = 0; rc = set_cl_element(&cl[num++], "kernel image", dump->image, - &dump->image_addr, 0, 0x10000, + &dump->image_addr, 0, 0x0, MAXIMUM_PHYSICAL_BLOCKSIZE); if (rc) goto error; -- 2.21.3 From 9e5da1e34622725dd9fb7a681f99552bcdabe414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 7 Nov 2019 12:23:26 +0100 Subject: [PATCH 28/56] dasdfmt/lsdasd: Add Thin provisioning base support (#1651733) Summary: dasdfmt/lsdasd: Add Thin provisioning base support Description: Make dasdfmt and lsdasd aware of thinly provisioned (Extent Space Efficient (ESE)) DASD volumes. If an ESE volume is recognised a QUICK format is performed, formatting only the first two tracks. The mode can always be overwritten by --mode. Previously allocated space is always released before formatting, if not specified otherwise. The option --no-discard (-D) is provided to omit the space release. The ESE information is added to the discipline output of lsdasd and the extended output additionally lists information about logical capacity, allocated space, and the extent size. --- Makefile | 2 +- common.mak | 4 - dasdfmt/Makefile | 1 - dasdfmt/dasdfmt.8 | 6 + dasdfmt/dasdfmt.c | 60 +++++++- dasdfmt/dasdfmt.h | 48 +----- dasdinfo/Makefile | 3 +- dasdview/Makefile | 1 - dasdview/dasdview.c | 4 +- dasdview/dasdview.h | 3 +- fdasd/Makefile | 1 - fdasd/fdasd.c | 2 +- include/lib/dasd_base.h | 36 +++++ include/lib/dasd_sys.h | 3 +- include/lib/u2s.h | 21 --- include/lib/util_sys.h | 17 ++ libdasd/dasd_ioctl.c | 20 +++ libdasd/dasd_sys.c | 114 ++++++++++++-- libu2s/Makefile | 14 -- libu2s/misc.c | 27 ---- libu2s/misc.h | 15 -- libu2s/u2s.c | 334 ---------------------------------------- libutil/Makefile | 3 +- libutil/util_sys.c | 78 ++++++++++ libzds/libzds.c | 4 +- tunedasd/src/Makefile | 1 - zconf/lsdasd | 30 +++- zdsfs/Makefile | 5 +- zipl/src/Makefile | 3 +- zipl/src/install.c | 4 +- 30 files changed, 354 insertions(+), 510 deletions(-) delete mode 100644 include/lib/u2s.h create mode 100644 include/lib/util_sys.h delete mode 100644 libu2s/Makefile delete mode 100644 libu2s/misc.c delete mode 100644 libu2s/misc.h delete mode 100644 libu2s/u2s.c create mode 100644 libutil/util_sys.c diff --git a/Makefile b/Makefile index bb2b900..8a07a4d 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ ARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ -e s/arm.*/ar # Include common definitions include common.mak -LIB_DIRS = libvtoc libu2s libutil libzds libdasd libvmdump libccw libvmcp +LIB_DIRS = libvtoc libutil libzds libdasd libvmdump libccw libvmcp 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 \ diff --git a/common.mak b/common.mak index 6029687..6f6acbb 100644 --- a/common.mak +++ b/common.mak @@ -331,10 +331,6 @@ $(rootdir)/libzds/libzds.a: $(rootdir)/libzds $(MAKE) -C $(rootdir)/libzds/ libzds.a .PHONY: $(rootdir)/libzds -$(rootdir)/libu2s/libu2s.a: $(rootdir)/libu2s - $(MAKE) -C $(rootdir)/libu2s/ libu2s.a -.PHONY: $(rootdir)/libu2s - $(rootdir)/libvmdump/libvmdump.a: $(rootdir)/libvmdump $(MAKE) -C $(rootdir)/libvmdump/ libvmdump.a .PHONY: $(rootdir)/libvmdump diff --git a/dasdfmt/Makefile b/dasdfmt/Makefile index 03877b3..98d9d3e 100644 --- a/dasdfmt/Makefile +++ b/dasdfmt/Makefile @@ -4,7 +4,6 @@ all: dasdfmt libs = $(rootdir)/libdasd/libdasd.a \ $(rootdir)/libvtoc/libvtoc.a \ - $(rootdir)/libu2s/libu2s.a \ $(rootdir)/libutil/libutil.a dasdfmt: dasdfmt.o $(libs) diff --git a/dasdfmt/dasdfmt.8 b/dasdfmt/dasdfmt.8 index 99da9ed..c3e98fa 100644 --- a/dasdfmt/dasdfmt.8 +++ b/dasdfmt/dasdfmt.8 @@ -122,6 +122,8 @@ Format the first two tracks and write label and partition information. Only use this option if you are sure that the target DASD already contains a regular format with the specified blocksize. A blocksize can optionally be specified using \fB-b\fR (\fB--blocksize\fR). +.br +For thin-provisioned DASD ESE volumes this is the default mode. .IP expand Format all unformatted tracks at the end of the target DASD. This mode assumes that tracks at the beginning of the DASD volume have already been correctly @@ -136,6 +138,10 @@ using \fB-b\fR (\fB--blocksize\fR). Perform a complete format check on a DASD volume. A blocksize can be specified with \fB-b\fR (\fB--blocksize\fR). +.TP +\fB--no-discard\fR +Omit a full space release when formatting a thin-provisioned DASD ESE volume. + .TP \fB-r\fR \fIcylindercount\fR or \fB--requestsize\fR=\fIcylindercount\fR Number of cylinders to be processed in one formatting step. diff --git a/dasdfmt/dasdfmt.c b/dasdfmt/dasdfmt.c index a34d79b..c78080c 100644 --- a/dasdfmt/dasdfmt.c +++ b/dasdfmt/dasdfmt.c @@ -53,6 +53,7 @@ static const struct util_prg prg = { /* Defines for options with no short command */ #define OPT_CHECK 128 #define OPT_NOZERO 129 +#define OPT_NODISCARD 130 static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("FORMAT ACTIONS"), @@ -105,6 +106,11 @@ static struct util_opt opt_vec[] = { .desc = "Prevent storage server from modifying record 0", .flags = UTIL_OPT_FLAG_NOSHORT, }, + { + .option = { "no-discard", no_argument, NULL, OPT_NODISCARD }, + .desc = "Do not discard space before formatting", + .flags = UTIL_OPT_FLAG_NOSHORT, + }, { .option = { NULL, no_argument, NULL, 'y' }, .desc = "Start formatting without further user-confirmation", @@ -921,6 +927,8 @@ static void dasdfmt_print_info(dasdfmt_info_t *info, char *devname, printf("Drive Geometry: %d Cylinders * %d Heads = %d Tracks\n", cylinders, heads, (cylinders * heads)); + printf("Device Type: %s Provisioned\n", + info->ese ? "Thinly" : "Fully"); printf("\nI am going to format the device "); printf("%s in the following way:\n", devname); printf(" Device number of device : 0x%x\n", info->dasd_info.devno); @@ -939,7 +947,10 @@ static void dasdfmt_print_info(dasdfmt_info_t *info, char *devname, (p->intensity & DASD_FMT_INT_COMPAT) ? "yes" : "no"); printf(" Blocksize : %d\n", p->blksize); printf(" Mode : %s\n", mode_str[mode]); - + if (info->ese) { + printf(" Full Space Release : %s\n", + (info->no_discard || mode == FULL) ? "no" : "yes"); + } if (info->testmode) printf("Test mode active, omitting ioctl.\n"); } @@ -1234,6 +1245,26 @@ static void dasdfmt_format(dasdfmt_info_t *info, unsigned int cylinders, process_tracks(info, cylinders, heads, format_params); } +static void dasdfmt_release_space(dasdfmt_info_t *info) +{ + format_data_t r = { + .start_unit = 0, + .stop_unit = 0, + .intensity = DASD_FMT_INT_ESE_FULL, + }; + int err = 0; + + if (!info->ese || info->no_discard) + return; + + printf("Releasing space for the entire device...\n"); + err = dasd_release_space(dev_filename, &r); + if (err) { + ERRMSG_EXIT(EXIT_FAILURE, "%s: Could not release space (%s)\n", + prog_name, strerror(err)); + } +} + static void dasdfmt_prepare_and_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *p) @@ -1329,6 +1360,8 @@ static void dasdfmt_quick_format(dasdfmt_info_t *info, unsigned int cylinders, if (info->force) { printf("Skipping format check due to --force.\n"); + } else if (info->ese) { + printf("Skipping format check due to thin-provisioned device.\n"); } else { check_blocksize(info, p->blksize); @@ -1394,7 +1427,7 @@ static void do_format_dasd(dasdfmt_info_t *info, char *devname, if ((info->verbosity > 0) || !info->withoutprompt || info->testmode) dasdfmt_print_info(info, devname, vlabel, cylinders, heads, p); - count = u2s_get_host_access_count(devname); + count = dasd_get_host_access_count(devname); if (info->force_host) { if (count > 1) { ERRMSG_EXIT(EXIT_FAILURE, @@ -1438,6 +1471,7 @@ static void do_format_dasd(dasdfmt_info_t *info, char *devname, dasdfmt_prepare_and_format(info, cylinders, heads, p); break; case QUICK: + dasdfmt_release_space(info); dasdfmt_quick_format(info, cylinders, heads, p); break; case EXPAND: @@ -1461,6 +1495,19 @@ static void do_format_dasd(dasdfmt_info_t *info, char *devname, } } +static void eval_format_mode(dasdfmt_info_t *info) +{ + if (!info->force && info->mode_specified && info->ese && mode == EXPAND) { + ERRMSG_EXIT(EXIT_FAILURE, + "WARNING: The specified device is thin-provisioned\n" + "Format mode 'expand' is not feasible.\n" + "Use --mode=full or --mode=quick to perform a clean format\n"); + } + + if (!info->mode_specified) + mode = info->ese ? QUICK : FULL; +} + int main(int argc, char *argv[]) { dasdfmt_info_t info = { @@ -1496,8 +1543,6 @@ int main(int argc, char *argv[]) format_params.blksize = DEFAULT_BLOCKSIZE; format_params.intensity = DASD_FMT_INT_COMPAT; - mode = FULL; - /*************** parse parameters **********************/ while (1) { @@ -1601,6 +1646,10 @@ int main(int argc, char *argv[]) "invalid. Consult the man page for " "more information.\n", prog_name, optarg); + info.mode_specified = 1; + break; + case OPT_NODISCARD: + info.no_discard = 1; break; case OPT_CHECK: info.check = 1; @@ -1645,6 +1694,9 @@ int main(int argc, char *argv[]) "device information failed (%s).\n", prog_name, strerror(rc)); + info.ese = dasd_sys_ese(dev_filename); + eval_format_mode(&info); + /* Either let the user specify the blksize or get it from the kernel */ if (!info.blksize_specified) { if (!(mode == FULL || diff --git a/dasdfmt/dasdfmt.h b/dasdfmt/dasdfmt.h index 5470c90..2f53a0d 100644 --- a/dasdfmt/dasdfmt.h +++ b/dasdfmt/dasdfmt.h @@ -27,10 +27,6 @@ #include #include -/**************************************************************************** - * SECTION: Definition needed for DASD-API (see dasd.h) * - ****************************************************************************/ - /* * Represents possible format modes that can be specified when formatting * a DASD. @@ -45,57 +41,16 @@ static const char mode_str[3][10] = { "Full", "Quick", "Expand" }; -/* - * values to be used for format_data_t.intensity - * 0/8: normal format - * 1/9: also write record zero - * 3/11: also write home address - * 4/12: invalidate track - */ -#define DASD_FMT_INT_FMT_R0 1 /* write record zero */ -#define DASD_FMT_INT_FMT_HA 2 /* write home address, also set FMT_R0 ! */ -#define DASD_FMT_INT_INVAL 4 /* invalidate tracks */ -#define DASD_FMT_INT_COMPAT 8 /* use OS/390 compatible disk layout */ -#define DASD_FMT_INT_FMT_NOR0 16 /* remove permission to write record zero */ - -/* - * values to be used in format_check_t for indicating - * possible format errors - */ -#define DASD_FMT_ERR_TOO_FEW_RECORDS 1 -#define DASD_FMT_ERR_TOO_MANY_RECORDS 2 -#define DASD_FMT_ERR_BLKSIZE 3 -#define DASD_FMT_ERR_RECORD_ID 4 -#define DASD_FMT_ERR_KEY_LENGTH 5 - -/* - * values to be used for dasd_information2_t.format - * 0x00: NOT formatted - * 0x01: Linux disc layout - * 0x02: Common disc layout - */ -#define DASD_FORMAT_NONE 0 -#define DASD_FORMAT_LDL 1 -#define DASD_FORMAT_CDL 2 - -/**************************************************************************** - * SECTION: DASDFMT internal types * - ****************************************************************************/ - #define DASD_PARTN_BITS 2 #define PARTN_MASK ((1 << DASD_PARTN_BITS) - 1) #define EXIT_MISUSE 1 #define EXIT_BUSY 2 -#define LABEL_LENGTH 14 -#define VLABEL_CHARS 84 -#define LINE_LENGTH 80 #define ERR_LENGTH 90 #define DEFAULT_BLOCKSIZE 4096 /* requestsize - number of cylinders in one format step */ #define DEFAULT_REQUESTSIZE 10 -#define USABLE_PARTITIONS ((1 << DASD_PARTN_BITS) - 1) #define ERRMSG(x...) {fflush(stdout);fprintf(stderr,x);} #define ERRMSG_EXIT(ec,x...) {fflush(stdout);fprintf(stderr,x);exit(ec);} @@ -137,6 +92,9 @@ typedef struct dasdfmt_info { int force_host; int layout_specified; int check; + int mode_specified; + int ese; + int no_discard; } dasdfmt_info_t; diff --git a/dasdinfo/Makefile b/dasdinfo/Makefile index ea45bf1..ed81c3f 100644 --- a/dasdinfo/Makefile +++ b/dasdinfo/Makefile @@ -1,7 +1,6 @@ include ../common.mak -libs = $(rootdir)/libu2s/libu2s.a \ - $(rootdir)/libutil/libutil.a \ +libs = $(rootdir)/libutil/libutil.a \ $(rootdir)/libdasd/libdasd.a all: dasdinfo diff --git a/dasdview/Makefile b/dasdview/Makefile index b7bdba3..59fda17 100644 --- a/dasdview/Makefile +++ b/dasdview/Makefile @@ -7,7 +7,6 @@ all: dasdview libs = $(rootdir)/libdasd/libdasd.a \ $(rootdir)/libzds/libzds.a \ $(rootdir)/libvtoc/libvtoc.a \ - $(rootdir)/libu2s/libu2s.a \ $(rootdir)/libutil/libutil.a dasdview: dasdview.o $(libs) diff --git a/dasdview/dasdview.c b/dasdview/dasdview.c index d773b02..3cf5629 100644 --- a/dasdview/dasdview.c +++ b/dasdview/dasdview.c @@ -28,10 +28,10 @@ #include "lib/dasd_base.h" #include "lib/dasd_sys.h" #include "lib/libzds.h" -#include "lib/u2s.h" #include "lib/util_base.h" #include "lib/util_opt.h" #include "lib/util_prg.h" +#include "lib/util_sys.h" #include "lib/vtoc.h" #include "lib/zt_common.h" @@ -183,7 +183,7 @@ dasdview_get_info(dasdview_info_t *info) else info->hw_cylinders = characteristics->no_cyl; - if (u2s_getbusid(info->device, info->busid) == -1) + if (util_sys_get_dev_addr(info->device, info->busid) != 0) info->busid_valid = 0; else info->busid_valid = 1; diff --git a/dasdview/dasdview.h b/dasdview/dasdview.h index 50dd3fe..35f2c54 100644 --- a/dasdview/dasdview.h +++ b/dasdview/dasdview.h @@ -11,7 +11,6 @@ #define DASDVIEW_H #include -#include "lib/u2s.h" /******************************************************************************** * SECTION: Definitions needed for DASD-API (see dasd.h) @@ -109,7 +108,7 @@ typedef struct dasdview_info int f8c; int f9c; - char busid[U2S_BUS_ID_SIZE]; + char busid[DASD_BUS_ID_SIZE]; int busid_valid; int raw_track_access; struct zdsroot *zdsroot; diff --git a/fdasd/Makefile b/fdasd/Makefile index 360d81f..facd89e 100644 --- a/fdasd/Makefile +++ b/fdasd/Makefile @@ -3,7 +3,6 @@ include ../common.mak libs = $(rootdir)/libvtoc/libvtoc.a \ $(rootdir)/libzds/libzds.a \ $(rootdir)/libdasd/libdasd.a \ - $(rootdir)/libu2s/libu2s.a \ $(rootdir)/libutil/libutil.a all: fdasd diff --git a/fdasd/fdasd.c b/fdasd/fdasd.c index 5de7955..569cc78 100644 --- a/fdasd/fdasd.c +++ b/fdasd/fdasd.c @@ -935,7 +935,7 @@ static void fdasd_verify_device(fdasd_anchor_t *anc, char *name) fdasd_error(anc, device_verification_failed, err_str); } - count = u2s_get_host_access_count(name); + count = dasd_get_host_access_count(name); if (anc->force_host) { if (count > 1) { snprintf(err_str, ERROR_STRING_SIZE, diff --git a/include/lib/dasd_base.h b/include/lib/dasd_base.h index cf7be2e..abd17f5 100644 --- a/include/lib/dasd_base.h +++ b/include/lib/dasd_base.h @@ -20,6 +20,9 @@ #include #include +/* A bus id of a DASD is 8 characters long. E.g. 0.0.4711 */ +#define DASD_BUS_ID_SIZE 9 + typedef struct dasd_information2_t { unsigned int devno; /* S/390 devno */ unsigned int real_devno; /* for aliases */ @@ -51,6 +54,16 @@ typedef struct dasd_information2_t { unsigned int reserved7; /* reserved for further use ,... */ } dasd_information2_t; +/* + * values to be used for dasd_information2_t.format + * 0x00: NOT formatted + * 0x01: Linux disc layout + * 0x02: Common disc layout + */ +#define DASD_FORMAT_NONE 0 +#define DASD_FORMAT_LDL 1 +#define DASD_FORMAT_CDL 2 + struct dasd_eckd_characteristics { unsigned short cu_type; struct { @@ -136,6 +149,16 @@ typedef struct format_data_t { unsigned int intensity; } format_data_t; +/* + * values to be used for format_data_t.intensity + */ +#define DASD_FMT_INT_FMT_R0 1 /* write record zero */ +#define DASD_FMT_INT_FMT_HA 2 /* write home address, also set FMT_R0 ! */ +#define DASD_FMT_INT_INVAL 4 /* invalidate tracks */ +#define DASD_FMT_INT_COMPAT 8 /* use OS/390 compatible disk layout */ +#define DASD_FMT_INT_FMT_NOR0 16 /* remove permission to write record zero */ +#define DASD_FMT_INT_ESE_FULL 32 /* release space for entire volume */ + /* * struct format_check_t * represents all data necessary to evaluate the format of @@ -154,6 +177,16 @@ typedef struct format_check_t { unsigned int key_length; /* Key length of first record in error */ } format_check_t; +/* + * values to be used in format_check_t for indicating + * possible format errors + */ +#define DASD_FMT_ERR_TOO_FEW_RECORDS 1 +#define DASD_FMT_ERR_TOO_MANY_RECORDS 2 +#define DASD_FMT_ERR_BLKSIZE 3 +#define DASD_FMT_ERR_RECORD_ID 4 +#define DASD_FMT_ERR_KEY_LENGTH 5 + #ifndef __linux__ /* definition from hdreg.h */ struct hd_geometry { @@ -174,6 +207,8 @@ struct hd_geometry { #define BIODASDINFO2 _IOR(DASD_IOCTL_LETTER, 3, dasd_information2_t) /* #define BIODASDFORMAT _IOW(IOCTL_LETTER,0,format_data_t) , deprecated */ #define BIODASDFMT _IOW(DASD_IOCTL_LETTER, 1, format_data_t) +/* Release Allocated Space */ +#define BIODASDRAS _IOW(DASD_IOCTL_LETTER, 3, format_data_t) /* Check device format according to format_data_t */ #define BIODASDCHECKFMT _IOWR(DASD_IOCTL_LETTER, 2, format_check_t) @@ -197,6 +232,7 @@ int dasd_check_format(const char *device, format_check_t *p); int dasd_format_disk(int fd, format_data_t *p); int dasd_disk_disable(const char *device, int *fd); int dasd_disk_enable(int fd); +int dasd_release_space(const char *device, format_data_t *r); int dasd_get_blocksize(const char *device, unsigned int *blksize); int dasd_get_blocksize_in_bytes(const char *device, unsigned long long *blksize); int dasd_get_geo(const char *device, struct hd_geometry *geo); diff --git a/include/lib/dasd_sys.h b/include/lib/dasd_sys.h index 5438172..75b186e 100644 --- a/include/lib/dasd_sys.h +++ b/include/lib/dasd_sys.h @@ -13,9 +13,10 @@ #define LIB_DASD_SYS_H #include -#include "u2s.h" int dasd_sys_raw_track_access(char *); +int dasd_sys_ese(char *); int dasd_reset_chpid(char *, char *); +int dasd_get_host_access_count(char *device); #endif /* LIB_DASD_SYS_H */ diff --git a/include/lib/u2s.h b/include/lib/u2s.h deleted file mode 100644 index fe5d673..0000000 --- a/include/lib/u2s.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * - * Copyright IBM Corp. 2004, 2017 - * - * s390-tools is free software; you can redistribute it and/or modify - * it under the terms of the MIT license. See LICENSE for details. - * - * History of changes (starts July 2004) - * 2004-07-02 initial - */ - -#ifndef LIB_U2S_H -#define LIB_U2S_H - -#define U2S_BUS_ID_SIZE 32 - -int u2s_getbusid(char *, char *); -int u2s_read_attribute(char *, char *, char *, size_t); -int u2s_get_host_access_count(char *); - -#endif /* LIB_U2S_H */ diff --git a/include/lib/util_sys.h b/include/lib/util_sys.h new file mode 100644 index 0000000..e0f078b --- /dev/null +++ b/include/lib/util_sys.h @@ -0,0 +1,17 @@ +/* + * @defgroup util_sys_h util_sys: SysFS interface + * @{ + * @brief Work with SysFS + * + * Copyright IBM Corp. 2019 + * + * 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_SYS_H +#define LIB_UTIL_SYS_H + +int util_sys_get_dev_addr(const char *dev, char *addr); + +#endif /** LIB_UTIL_SYS_H @} */ diff --git a/libdasd/dasd_ioctl.c b/libdasd/dasd_ioctl.c index bbfb384..ef1605e 100644 --- a/libdasd/dasd_ioctl.c +++ b/libdasd/dasd_ioctl.c @@ -228,6 +228,26 @@ int dasd_check_format(const char *device, format_check_t *p) return 0; } +/* + * Release Allocated Space + * + * @param[in] fd device node's file descriptor + * @param[in] r format options + * + * @retval 0 in case of success + * @retval errno in case of failure + */ +int dasd_release_space(const char *device, format_data_t *r) +{ + int fd; + + fd = dasd_open_device(device, O_RDONLY); + RUN_IOCTL(fd, BIODASDRAS, r); + dasd_close_device(fd); + + return 0; +} + /* * Reread partition table * diff --git a/libdasd/dasd_sys.c b/libdasd/dasd_sys.c index f2fd826..94c2d12 100644 --- a/libdasd/dasd_sys.c +++ b/libdasd/dasd_sys.c @@ -12,7 +12,11 @@ #include #include +#include "lib/dasd_base.h" #include "lib/dasd_sys.h" +#include "lib/util_file.h" +#include "lib/util_path.h" +#include "lib/util_sys.h" /** * Get raw-track access mode status @@ -31,39 +35,80 @@ */ int dasd_sys_raw_track_access(char *devnode) { - char busid[9]; - char path[47]; + char busid[DASD_BUS_ID_SIZE]; + char *path; FILE *fp; int rc; - if (u2s_getbusid(devnode, busid)) + if (util_sys_get_dev_addr(devnode, busid) != 0) return 0; - sprintf(path, "/sys/bus/ccw/devices/%s/raw_track_access", busid); - + path = util_path_sysfs("bus/ccw/devices/%s/raw_track_access", busid); fp = fopen(path, "r"); - if (!fp) + if (!fp) { + free(path); return 0; + } rc = fgetc(fp) - '0'; fclose(fp); + free(path); return (rc == 1) ? 1 : 0; } +/** + * Is volume extent space efficient a.k.a. thin-provisioned + * + * The "devnode" parameter can be any valid relative or absolute path to + * a DASD device node, for example: + * + * - /dev/dasda + * - /dev/disk/by-path/ccw-0.0.bf20 + * + * @param[in] devnode Device node of interest + * + * @retval 1 Volume is extent space efficient + * @retval 0 Volume is not extent space efficient or + * cannot be determined + */ +int dasd_sys_ese(char *devnode) +{ + char busid[DASD_BUS_ID_SIZE]; + char *path; + FILE *fp; + int rc; + + if (util_sys_get_dev_addr(devnode, busid) != 0) + return 0; + + path = util_path_sysfs("bus/ccw/devices/%s/ese", busid); + fp = fopen(path, "r"); + if (!fp) { + free(path); + return 0; + } + + rc = fgetc(fp) - '0'; + fclose(fp); + + return (rc == 1) ? 1 : 0; +} int dasd_get_pm_from_chpid(char *busid, unsigned int chpid, int *mask) { unsigned int val; - char path[40]; int count, i; + char *path; FILE *fp; - sprintf(path, "/sys/bus/ccw/devices/%s/../chpids", busid); + path = util_path_sysfs("bus/ccw/devices/%s/../chpids", busid); *mask = 0; fp = fopen(path, "r"); - if (!fp) + if (!fp) { + free(path); return ENODEV; + } for (i = 0; i < 8; i++) { count = fscanf(fp, " %x", &val); @@ -75,6 +120,7 @@ int dasd_get_pm_from_chpid(char *busid, unsigned int chpid, int *mask) *mask = 0x80 >> i; } fclose(fp); + free(path); return 0; } @@ -102,22 +148,25 @@ int dasd_get_pm_from_chpid(char *busid, unsigned int chpid, int *mask) int dasd_reset_chpid(char *devnode, char *chpid_char) { unsigned int chpid; - char path[41]; - char busid[9]; + char busid[DASD_BUS_ID_SIZE]; int mask, rc; char *endptr; + char *path; FILE *fp; - if (u2s_getbusid(devnode, busid)) + if (util_sys_get_dev_addr(devnode, busid) != 0) return ENODEV; if (!chpid_char) { - sprintf(path, "/sys/bus/ccw/devices/%s/path_reset", busid); + path = util_path_sysfs("bus/ccw/devices/%s/path_reset", busid); fp = fopen(path, "w"); - if (!fp) + if (!fp) { + free(path); return ENODEV; + } fprintf(fp, "%s", "all\n"); fclose(fp); + free(path); return 0; } @@ -132,12 +181,45 @@ int dasd_reset_chpid(char *devnode, char *chpid_char) if (!mask) return ENOENT; - sprintf(path, "/sys/bus/ccw/devices/%s/path_reset", busid); + path = util_path_sysfs("bus/ccw/devices/%s/path_reset", busid); fp = fopen(path, "w"); - if (!fp) + if (!fp) { + free(path); return ENODEV; + } fprintf(fp, "%02x", mask); fclose(fp); + free(path); return 0; } + +/** + * Read amount of host with access to \p device + * + * The \p device can be any valid relative or absolute path to a DASD device + * node, for example: + * + * - /dev/dasda + * - /dev/disk/by-path/ccw-0.0.bf20 + * + * @param[in] device Device node of interest + * + * @retval n Number of hosts with access to \p device + * @retval 0 Value could not be determined + */ +int dasd_get_host_access_count(char *device) +{ + char busid[9]; + char *path; + long value; + + if (!util_sys_get_dev_addr(device, busid)) + return 0; + + path = util_path_sysfs("bus/ccw/devices/%s/host_access_count", busid); + util_file_read_l(&value, 10, path); + free(path); + + return value; +} diff --git a/libu2s/Makefile b/libu2s/Makefile deleted file mode 100644 index 523282b..0000000 --- a/libu2s/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -include ../common.mak - -lib = libu2s.a - -all: $(lib) - -objects = u2s.o misc.o - -$(lib): $(objects) - -install: all - -clean: - rm -f *.o $(lib) diff --git a/libu2s/misc.c b/libu2s/misc.c deleted file mode 100644 index d29784c..0000000 --- a/libu2s/misc.c +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Misc - Local helper functions - * - * Copyright IBM Corp. 2016, 2017 - * - * 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 -#include "lib/util_base.h" - -/* - * Helper function that copies a string safely - */ -size_t misc_strlcpy(char *dest, const char *src, size_t size) -{ - size_t str_len = strlen(src); - size_t len; - - if (size) { - len = MIN(size - 1, str_len); - memcpy(dest, src, len); - dest[len] = '\0'; - } - return str_len; -} diff --git a/libu2s/misc.h b/libu2s/misc.h deleted file mode 100644 index 53b7356..0000000 --- a/libu2s/misc.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Misc - Local helper functions - * - * Copyright IBM Corp. 2016, 2017 - * - * 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 MISC_H -#define MISC_H - -size_t misc_strlcpy(char *, const char *, size_t); - -#endif /* MISC_H */ diff --git a/libu2s/u2s.c b/libu2s/u2s.c deleted file mode 100644 index 9ffafa7..0000000 --- a/libu2s/u2s.c +++ /dev/null @@ -1,334 +0,0 @@ -/* - * - * Copyright IBM Corp. 2004, 2017 - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "lib/u2s.h" -#include "misc.h" - -#define DEV_BUFFER_LENGTH 20 -#define PATH_BUFFER_LENGTH 256 -#define BUSIDSIZE 9 - -#define BLOCKPATH "/sys/block/" -#define DEVICE_LINK "device" -#define DEV_ATTRIBUTE "dev" - - -/* - * Helper function that expects a file name and returns 1 if this - * is a directory or 0 otherwise. - */ -static int isdir(char *name) { - - struct stat statbuf; - - if (stat(name, &statbuf) < 0) - return 0; - return S_ISDIR(statbuf.st_mode); -} - -/* - * Helper function that expects a directory name in sysfs of the form - * /sys/block// or /sys/block///. - * It will try to read the file "dev" in this directory and compare - * it's contents with the given dev string of the form :. - * Trailing white space (newline) is ignored. - * The buffer name is expected to be long enough to hold the additional "dev". - * Returns 1 if the directory matches dev, 0 otherwise. - */ -static int check_directory(char *name, char *dev) { - - char buffer[DEV_BUFFER_LENGTH]; - char *end; - int fd; - ssize_t count; - int dev_attr_len, dev_parm_len; - unsigned int namelen; - - namelen = strlen(name); - if ((PATH_BUFFER_LENGTH - namelen) < sizeof(DEV_ATTRIBUTE)) - return 0; - end = name + namelen; - strcpy(end, DEV_ATTRIBUTE); - fd = open(name, O_RDONLY); - *end = 0; - if (fd < 0) - return 0; - count = read(fd, buffer, DEV_BUFFER_LENGTH); - close(fd); - if (count < 0) - return 0; - dev_attr_len = strspn(buffer, "1234567890:"); - dev_parm_len = strlen(dev); - if (dev_attr_len != dev_parm_len ) - return 0; - return (strncmp(dev, buffer, dev_parm_len) == 0); -} - -/* - * Helper function that expects a directory name in sysfs of the form - * /sys/block//. It will try to read a link "device" - * in this directory and extract the busid, which is the last part - * of that link. The buffer name is expected to be long enough - * to hold the additional "device". - * name: block device path in sysfs. - * busid: buffer in which the busid string will be returned - * returns 0 for successful operation and -1 in case of an error. - */ -static int extract_busid(char *name, char *busid) { - - int count; - unsigned int namelen; - char linkbuffer[PATH_BUFFER_LENGTH]; - char *start, *end; - size_t len; - - namelen = strlen(name); - if ((PATH_BUFFER_LENGTH - namelen) < sizeof(DEVICE_LINK)) - return 0; - end = name + namelen; - strcpy(end, DEVICE_LINK); - count = readlink(name, linkbuffer, PATH_BUFFER_LENGTH - 1); - if (count < 0) - return -1; - linkbuffer[count] = 0; - start = strrchr(linkbuffer, '/'); - if (!start) - return -1; - start++; - len = misc_strlcpy(busid, start, BUSIDSIZE); - if (len >= BUSIDSIZE) - return -1; - - return 0; -}; - -/* - * Helper function that makes some basic checks on a directory entry. - * The function checks if there is still enough space left in the buffer - * for the new string, excludes '.' and '..', and verifies that the entry - * is actually a directory. - * buffer: the beginning of the name buffer - * oldend: the current end of the string in the name buffer - * dir: the dirent in question - * returns: a pointer to the new end of the string in buffer or NULL if - * one of the checks failed - */ - -static char *append_if_directory(char *buffer, char *oldend, struct dirent *dir) { - - char *newend; - int oldlength, dirlength; - - if (strcmp(dir->d_name, ".") == 0 || - strcmp(dir->d_name, "..") == 0) - return NULL; - oldlength = strlen(buffer); - dirlength = strlen(dir->d_name); - if (PATH_BUFFER_LENGTH < oldlength + dirlength + 2) - return NULL; - strcpy(oldend, dir->d_name); - if (!isdir(buffer)) { - *oldend = 0; - return NULL; - } - newend = oldend + dirlength; - strcpy(newend, "/"); - newend++; - - return newend; -} - -/* - * helper function that searches for a specific block device and returns - * it's busid - * dev: : of the device - * busid: buffer in which the busid string will be returned - * returns 0 for successful operation and -1 in case of an error. - */ -static int find_busid_in_sysfs(char *dev, char *busid) { - - DIR *blockdir, *diskdir; - struct dirent *blockde, *diskde; - int found = 0; - char namebuffer[PATH_BUFFER_LENGTH]; - char *blockend, *diskend = NULL, *partend; - - /* everything, including the other helper functions, works on the - * same buffer area 'namebuffer'. The pointers blockend, diskend - * and partend point to the end of the various names. - * Example: - * "/sys/block/dasda/dasda1/" - * ^ blockend - * ^ diskend - * ^ partend - */ - - strcpy(namebuffer,BLOCKPATH); - blockdir = opendir(namebuffer); - if (!blockdir) - return -1; - blockend = namebuffer + strlen(namebuffer); - /* check each entry in /sys/block */ - while ((blockde = readdir(blockdir))) { - diskend = append_if_directory(namebuffer, blockend, blockde); - if (!diskend) - continue; - found = check_directory(namebuffer, dev); - if (found) - break; - diskdir = opendir(namebuffer); - if (!diskdir) - continue; - /* check each entry in /sys/block/ */ - while ((diskde = readdir(diskdir))) { - partend = append_if_directory( - namebuffer, diskend, diskde); - if (!partend) - continue; - found = check_directory(namebuffer, dev); - if (found) - break; - } - closedir(diskdir); - if (found) - break; - } - closedir(blockdir); - if (found) { - *diskend = 0; /* remove partition directory from name */ - return extract_busid(namebuffer, busid); - } else - return -1; -} - -/* - * helper function that searches for a specific block device in - * /proc/dasd/devices and returns it's bus-ID - * maja, mina: , of the device - * busid: buffer in which the bus-ID string will be returned - * returns 0 for successful operation and -1 in case of an error - * e.g. /proc/dasd/devices does not exist. - * - * An entry looks like: - * 0.0.XXXX(DISCIPLINE) at ( MAJ: MIN) is dasdX : - * active at blocksize: BLOCKSIZE, BLOCKS blocks, SIZE MB - */ -static int find_busid_in_proc(int maja, int mina, char *busid) -{ - FILE *filp; - char bus[BUSIDSIZE]; - int majb, minb, rc; - size_t len; - - rc = -1; - - filp = fopen("/proc/dasd/devices", "r"); - if (!filp) - return rc; - while (fscanf(filp, "%[^(] %*[^)] ) at ( %d : %d %*[^\n]\n", - bus, &majb, &minb) != EOF) { - if ((maja == majb) && (mina == minb)) { - len = misc_strlcpy(busid, bus, BUSIDSIZE); - if (len < BUSIDSIZE) - rc = 0; - break; - } - } - - fclose(filp); - return rc; -} - -/* - * Return the busid of a given device node. - * Works only for block devices. - * devicenode: path to the device node - * busid: buffer in which the busid string will be returned - * returns 0 for successful operation and -1 in case of an error. - */ -int u2s_getbusid(char *devicenode, char *busid) -{ - int maj, min, rc; - struct stat stat_buf; - char dev_string[DEV_BUFFER_LENGTH]; - - /* - * Get major and minor information of the device special file - * and combine them to a : string, as returned by - * the dev attributes in sysfs - */ - if (stat(devicenode, &stat_buf)) - return -1; - if (!S_ISBLK(stat_buf.st_mode)) - return -1; - maj = major(stat_buf.st_rdev); - min = minor(stat_buf.st_rdev); - - rc = find_busid_in_proc(maj, min, busid); - if (rc) { - snprintf(dev_string, DEV_BUFFER_LENGTH, "%u:%u", maj, min); - rc = find_busid_in_sysfs(dev_string, busid); - } - - return rc; -} - -/* - * Attempts to find the sysfs entry for the given busid and reads - * the contents of a specified attribute to the buffer - */ -int u2s_read_attribute(char *busid, char *attribute, char *buffer, - size_t count) -{ - char path[100]; - int rc, fd; - ssize_t rcount; - - rc = 0; - snprintf(path, sizeof(path), "/sys/bus/ccw/devices/%s/%s", - busid, attribute); - fd = open(path, O_RDONLY); - if (fd < 0) - return errno; - rcount = read(fd, buffer, count); - if (rcount < 0) - rc = errno; - close(fd); - return rc; -} - -int u2s_get_host_access_count(char *devicenode) -{ - char busid[BUSIDSIZE]; - unsigned long value; - char buffer[10]; - char *endp; - - u2s_getbusid(devicenode, busid); - u2s_read_attribute(busid, "host_access_count", buffer, sizeof(buffer)); - - value = strtoul(buffer, &endp, 0); - - if (endp == buffer) - return -EINVAL; - - return value; -} diff --git a/libutil/Makefile b/libutil/Makefile index fd5e213..8a11db2 100644 --- a/libutil/Makefile +++ b/libutil/Makefile @@ -26,7 +26,8 @@ objects = util_base.o \ util_part.o \ util_prg.o \ util_proc.o \ - util_rec.o + util_rec.o \ + util_sys.o util_base_example: util_base_example.o $(lib) util_panic_example: util_panic_example.o $(lib) diff --git a/libutil/util_sys.c b/libutil/util_sys.c new file mode 100644 index 0000000..05e3653 --- /dev/null +++ b/libutil/util_sys.c @@ -0,0 +1,78 @@ +/* + * util - Utility function library + * + * SysFS helper functions + * + * Copyright IBM Corp. 2019 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_path.h" +#include "lib/util_sys.h" + +/* lstat() doesn't work for sysfs files, a fixed size is therefore inevitable */ +#define READLINK_SIZE 256 + +/** + * Identify device address + * + * Identifying the device address with this function works for almost any + * character and block device (e.g. NVMe, SCSI, DASD, etc). + * The user must provide a buffer that is large enough for the desired device + * address to be read into \p addr. + * + * @param[in] dev Device node of interest + * @param[out] addr Identified device address + * + * @retval 0 Success + * @retval -1 Error while reading device information or + * constructed path + */ +int util_sys_get_dev_addr(const char *dev, char *addr) +{ + char device[READLINK_SIZE], *result; + unsigned int maj, min; + struct stat s; + ssize_t len; + char *path; + + if (stat(dev, &s) != 0) + return -1; + + maj = major(s.st_rdev); + min = minor(s.st_rdev); + + if (S_ISBLK(s.st_mode)) + path = util_path_sysfs("dev/block/%u:%u/device", maj, min); + else if (S_ISCHR(s.st_mode)) + path = util_path_sysfs("dev/char/%u:%u/device", maj, min); + else + return -1; + + len = readlink(path, device, READLINK_SIZE - 1); + free(path); + if (len != -1) + device[len] = '\0'; + else + return -1; + + result = strrchr(device, '/'); + if (result) + result++; + else + result = device; + strcpy(addr, result); + + return 0; +} diff --git a/libzds/libzds.c b/libzds/libzds.c index 2430962..affb30c 100644 --- a/libzds/libzds.c +++ b/libzds/libzds.c @@ -20,8 +20,8 @@ #include #include "lib/dasd_base.h" +#include "lib/dasd_sys.h" #include "lib/libzds.h" -#include "lib/u2s.h" #include "lib/util_base.h" #include "lib/util_list.h" #include "lib/vtoc.h" @@ -3727,7 +3727,7 @@ int lzds_analyse_open_count(struct zdsroot *root, int warn) int rc = 0; util_list_iterate(root->dasdlist, dasd) { - value = u2s_get_host_access_count(dasd->device); + value = dasd_get_host_access_count(dasd->device); if (value < 0) { fprintf(stderr, diff --git a/tunedasd/src/Makefile b/tunedasd/src/Makefile index 959b8eb..c79a235 100644 --- a/tunedasd/src/Makefile +++ b/tunedasd/src/Makefile @@ -3,7 +3,6 @@ include ../../common.mak ALL_CPPFLAGS += -I../include -I../boot libs = $(rootdir)/libdasd/libdasd.a \ - $(rootdir)/libu2s/libu2s.a \ $(rootdir)/libutil/libutil.a all: tunedasd diff --git a/zconf/lsdasd b/zconf/lsdasd index a0af6ea..792efc0 100755 --- a/zconf/lsdasd +++ b/zconf/lsdasd @@ -164,6 +164,7 @@ function gatherDeviceData() { read DEV_UID 2> /dev/null < $DEVPATH/uid || continue read READONLY 2> /dev/null < $DEVPATH/readonly || continue read DISCIPLINE 2> /dev/null < $DEVPATH/discipline || continue + read ESE 2> /dev/null < $DEVPATH/ese # Block device specific information is only available for # devices that are online and not a PAV alias @@ -244,7 +245,7 @@ function newoutput() #-------------------------------------------# if [[ "$ONLINE" == 0 ]]; then - printf "%s:%s:%-8s offline\n" \ + printf "%s:%s:%-8s offline\n" \ "$SORTKEYLEN" "$SORTKEY" \ "$BUSID" ; return @@ -252,7 +253,7 @@ function newoutput() if [[ "$ALIAS" == 1 ]]; then if [[ "$BASEONLY" == "false" ]]; then - printf "%s:%s:%-8s alias %28s\n" \ + printf "%s:%s:%-8s alias %26s\n" \ "$SORTKEYLEN" "$SORTKEY" \ "$BUSID" \ "$DISCIPLINE" @@ -286,7 +287,11 @@ function newoutput() ACTIVE="active" fi - printf "%s:%s:%-8s %-6s%-4s %-8s %-2s:%-2s %-4s %-4s %-8s %s\n" \ + if [[ "$ESE" == 1 ]]; then + DISCIPLINE="${DISCIPLINE} (ESE)" + fi + + printf "%s:%s:%-8s %-6s%-2s %-8s %-2s:%-2s %-11s %-4s %-8s %s\n" \ "$SORTKEYLEN" "$SORTKEY" \ "$BUSID" \ "$ACTIVE" \ @@ -378,6 +383,10 @@ function extended() read OPM NPPM CABLEPM CUIRPM HPFPM IFCCPM 2> /dev/null < $DEVPATH/path_masks read -a C 2> /dev/null < $DEVPATH/../chpids read PIM PAM POM 2> /dev/null < $DEVPATH/../pimpampom + read ESE 2> /dev/null < $DEVPATH/ese + read EXTSZ 2> /dev/null < $DEVPATH/extent_pool/extent_size + read CAPACITY 2> /dev/null < $DEVPATH/capacity/logical_capacity + read ALLOCATED 2> /dev/null < $DEVPATH/capacity/space_allocated # convert to hexadecimal values PIM=0x$PIM @@ -550,7 +559,11 @@ function extended() COLON=":" fi - printf "%s:%s:%s/%s/%s%s%s# status:\t\t\t\t%s# type: \t\t\t\t%s# blksz:\t\t\t\t%s# size: \t\t\t\t%s# blocks:\t\t\t\t%s# use_diag:\t\t\t\t%s# readonly:\t\t\t\t%s# eer_enabled:\t\t\t\t%s# erplog:\t\t\t\t%s# hpf:\t\t\t\t\t%s# uid: \t\t\t\t%s# paths_installed: \t\t\t%s %s %s %s %s %s %s %s# paths_in_use: \t\t\t%s %s %s %s %s %s %s %s# paths_non_preferred: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_cabling: \t\t%s %s %s %s %s %s %s %s# paths_cuir_quiesced: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_hpf_characteristics: \t%s %s %s %s %s %s %s %s# paths_error_threshold_exceeded: \t%s %s %s %s %s %s %s %s#\n" \ + if [[ "$ESE" == 1 ]]; then + DISCIPLINE="${DISCIPLINE} (ESE)" + fi + + printf "%s:%s:%s/%s/%s%s%s# status:\t\t\t\t%s# type: \t\t\t\t%s# blksz:\t\t\t\t%s# size: \t\t\t\t%s# blocks:\t\t\t\t%s# extent_size:\t\t\t\t%s# logical_capacity:\t\t\t%s# space_allocated:\t\t\t%s# use_diag:\t\t\t\t%s# readonly:\t\t\t\t%s# eer_enabled:\t\t\t\t%s# erplog:\t\t\t\t%s# hpf:\t\t\t\t\t%s# uid: \t\t\t\t%s# paths_installed: \t\t\t%s %s %s %s %s %s %s %s# paths_in_use: \t\t\t%s %s %s %s %s %s %s %s# paths_non_preferred: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_cabling: \t\t%s %s %s %s %s %s %s %s# paths_cuir_quiesced: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_hpf_characteristics: \t%s %s %s %s %s %s %s %s# paths_error_threshold_exceeded: \t%s %s %s %s %s %s %s %s#\n" \ "$SORTKEYLEN" "$SORTKEY" \ "$BUSID" \ "$BLOCKNAME" \ @@ -562,6 +575,9 @@ function extended() "$SSIZE" \ "$MBSIZE" \ "$BLOCKCOUNT" \ + "$EXTSZ" \ + "$CAPACITY" \ + "$ALLOCATED" \ "$DIAG" \ "$READONLY" \ "$EER" \ @@ -787,10 +803,10 @@ fi if [[ "$PRINTUID" == "true" ]] && [[ "$OUTPUT" != "old" ]]; then printf "Bus-ID Name UID\n" - printf "==============================================================================\n" + printf "================================================================================\n" elif [[ "$OUTPUT" == "new" ]]; then - printf "Bus-ID Status Name Device Type BlkSz Size Blocks\n" - printf "==============================================================================\n" + printf "Bus-ID Status Name Device Type BlkSz Size Blocks\n" + printf "================================================================================\n" elif [[ "$OUTPUT" == "extended" ]]; then PROCESSING=" $PROCESSING | sed 's/#/\n/g' " fi diff --git a/zdsfs/Makefile b/zdsfs/Makefile index 579ce4b..07d7658 100644 --- a/zdsfs/Makefile +++ b/zdsfs/Makefile @@ -2,9 +2,8 @@ include ../common.mak libs = $(rootdir)/libzds/libzds.a \ $(rootdir)/libvtoc/libvtoc.a \ - $(rootdir)/libu2s/libu2s.a \ - $(rootdir)/libutil/libutil.a \ - $(rootdir)/libdasd/libdasd.a + $(rootdir)/libdasd/libdasd.a \ + $(rootdir)/libutil/libutil.a ifeq (${HAVE_FUSE},0) diff --git a/zipl/src/Makefile b/zipl/src/Makefile index bed970c..fd77670 100644 --- a/zipl/src/Makefile +++ b/zipl/src/Makefile @@ -7,8 +7,7 @@ ALL_CPPFLAGS += -I../include -I../boot \ -D_FILE_OFFSET_BITS=64 $(NO_PIE_CFLAGS) ALL_LDFLAGS += -Wl,-z,noexecstack $(NO_PIE_LDFLAGS) -libs = $(rootdir)/libutil/libutil.a \ - $(rootdir)/libu2s/libu2s.a +libs = $(rootdir)/libutil/libutil.a objects = misc.o error.o scan.o job.o boot.o bootmap.o disk.o \ install.o zipl.o $(rootdir)/zipl/boot/data.o diff --git a/zipl/src/install.c b/zipl/src/install.c index a6c4333..20b53f5 100644 --- a/zipl/src/install.c +++ b/zipl/src/install.c @@ -23,7 +23,7 @@ #include #include -#include "lib/u2s.h" +#include "lib/util_sys.h" #include "boot.h" #include "bootmap.h" @@ -1132,7 +1132,7 @@ install_mvdump(char* const device[], struct job_target_data* target, int count, parm.param[i].num_heads = info[i]->geo.heads; parm.param[i].blocksize = info[i]->phy_block_size >> 8; parm.param[i].devno = info[i]->devno; - if (u2s_getbusid(device[i], busid)) { + if (util_sys_get_dev_addr(device[i], busid) != 0) { error_text("Could not find bus-ID for '%s'", device[i]); rc = -1; goto out; -- 2.21.3 From 836d4b9f0f4387a3e35125be8ee50563501d6ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 7 Nov 2019 12:29:42 +0100 Subject: [PATCH 29/56] zdsfs: add online vtoc refresh (#1685536) Description: Enable zdsfs to access datasets that were created after zdsfs was mounted without the need to remount zdsfs. This is done by re-reading the VTOC with every readdir system call. To ensure a consistent VTOC state the DASD device is reserved for every VTOC read and released afterwards. Upstream-ID: bc053a975af6bd8eecd31269656f305d4c744fbe --- dasdview/dasdview.c | 2 +- include/lib/dasd_base.h | 6 ++ include/lib/libzds.h | 83 ++++++++++++++++-- libdasd/dasd_ioctl.c | 39 +++++++++ libzds/libzds.c | 184 +++++++++++++++++----------------------- zdsfs/zdsfs.c | 97 +++++++++++++++++---- 6 files changed, 280 insertions(+), 131 deletions(-) diff --git a/dasdview/dasdview.c b/dasdview/dasdview.c index 3cf5629..3dc5890 100644 --- a/dasdview/dasdview.c +++ b/dasdview/dasdview.c @@ -1741,7 +1741,7 @@ static void dasdview_print_vtoc_raw(dasdview_info_t *info) " rc=%d\n", rc); exit(-1); } - rc = lzds_dasd_read_rawvtoc(info->dasd); + rc = lzds_dasd_alloc_rawvtoc(info->dasd); if (rc == EINVAL) { zt_error_print("dasdview: Cannot read VTOC because disk does" " not contain valid VOL1 label.\n", diff --git a/include/lib/dasd_base.h b/include/lib/dasd_base.h index abd17f5..b12e1f9 100644 --- a/include/lib/dasd_base.h +++ b/include/lib/dasd_base.h @@ -203,6 +203,10 @@ struct hd_geometry { #define BIODASDDISABLE _IO(DASD_IOCTL_LETTER, 0) /* Enable the volume (for Linux) */ #define BIODASDENABLE _IO(DASD_IOCTL_LETTER, 1) +/* Reserve the device for the current LPAR */ +#define BIODASDRSRV _IO(DASD_IOCTL_LETTER, 2) +/* Release the device for the current LPAR */ +#define BIODASDRLSE _IO(DASD_IOCTL_LETTER, 3) /* Get information on a dasd device (enhanced) */ #define BIODASDINFO2 _IOR(DASD_IOCTL_LETTER, 3, dasd_information2_t) /* #define BIODASDFORMAT _IOW(IOCTL_LETTER,0,format_data_t) , deprecated */ @@ -239,5 +243,7 @@ int dasd_get_geo(const char *device, struct hd_geometry *geo); int dasd_get_info(const char *device, dasd_information2_t *info); int dasd_is_ro(const char *device, bool *ro); int dasd_reread_partition_table(const char *device, int ntries); +int dasd_disk_reserve(const char *device); +int dasd_disk_release(const char *device); #endif /* LIB_DASD_BASE_H */ diff --git a/include/lib/libzds.h b/include/lib/libzds.h index 355bf77..6ae767b 100644 --- a/include/lib/libzds.h +++ b/include/lib/libzds.h @@ -112,6 +112,8 @@ */ #define LIB_LIBZDS_H +#include "lib/util_base.h" +#include "lib/util_list.h" #include "vtoc.h" @@ -328,11 +330,74 @@ struct pds_member_entry { */ struct zdsroot; +/** + * @struct raw_vtoc + * @brief The VTOC is a directory of data sets on one DASD + * + * As the VTOC is the data area on the DASD that describes all data sets, + * this library will often have to refer to the various records in the VTOC. + * To make this more efficient, we will read the whole VTOC once and identify + * all elements (DSCBs). The raw data of the VTOC tracks and the index to the + * DSCBs is stored. + */ +struct raw_vtoc { + /** @brief The raw track data */ + char *rawdata; + /** @brief This size of the raw track data in bytes */ + unsigned long long rawdatasize; + /** @brief An array with pointers to the various DSCBs in the rawdata */ + char **vtocindex; + /** @brief Number of entries in the index */ + unsigned int vtocindexcount; + /** @brief Number of records per VTOC track + * + * @note While the DS4DEVDT field in the format 4 DSCB names the number + * if DSCBs per VTOC track, we count the records, which is DS4DEVDT + 1 + * for record 0. + */ + unsigned int vtoc_rec_per_track; + /** @brief The track number at which the vtoc begins on the DASD */ + unsigned int vtoctrackoffset; + /** @brief Start record of VTOC. + * + * The rawdata contains full tracks. This is the number of the first + * record that actually belongs to the VTOC + */ + unsigned int vtocrecno; + /** @brief The DASD this vtoc was read from */ + struct dasd *dasd; + /** @brief Detailed error messages in case of a problem */ + struct errorlog *log; +}; + /** * @struct dasd * @brief Represents one physical device, may have a vtoc */ -struct dasd; +struct dasd { + /** @brief List head used to store a list of DASDs in struct zdsroot */ + struct util_list_node list; + /** @brief Name of the block device, e.g. /dev/dasde */ + char *device; + /** @brief File descriptor for the block device. + * + * The device is kept open for as along as the library uses it. + * This lets the system know that the device is still in use. + */ + int inusefd; + /* @brief where to find the volume label */ + unsigned int label_block; + /** @brief Device geometry. How many cylinders does the DASD have. */ + unsigned int cylinders; + /** @brief Device geometry. How many heads does the DASD have. */ + unsigned int heads; + /** @brief The VTOC data that has been read from this device */ + struct raw_vtoc *rawvtoc; + /** @brief The volume label that has been read from this device */ + volume_label_t *vlabel; + /** @brief Detailed error messages in case of a problem */ + struct errorlog *log; +}; /** * @struct dasditerator @@ -350,12 +415,6 @@ struct dasditerator; */ struct dasdhandle; -/** - * @struct raw_vtoc - * @brief The VTOC is a directory of data sets on one dasd - */ -struct raw_vtoc; - /** * @struct dscbiterator * @brief allows to iterate over all DSCBs in a vtoc @@ -572,7 +631,13 @@ int lzds_dasd_get_vlabel(struct dasd *dasd, struct volume_label **vlabel); * @brief Read the vtoc data from device. The data as stored as part * of the struct dasd. */ -int lzds_dasd_read_rawvtoc(struct dasd *dasd); +int lzds_dasd_read_rawvtoc(struct dasd *dasd, struct raw_vtoc *vtoc); + +/** + * @brief Read the vtoc data from device. The data as stored as part + * of the struct dasd. + */ +int lzds_dasd_alloc_rawvtoc(struct dasd *dasd); /** * @brief Get the previously read raw_vtoc data. @@ -787,6 +852,8 @@ int lzds_zdsroot_extract_datasets_from_dasd(struct zdsroot *root, struct dasd *dasd); +void lzds_dslist_free(struct zdsroot *root); + /** @} */ /* end of group libzds_functions_high */ diff --git a/libdasd/dasd_ioctl.c b/libdasd/dasd_ioctl.c index ef1605e..22e6d21 100644 --- a/libdasd/dasd_ioctl.c +++ b/libdasd/dasd_ioctl.c @@ -281,3 +281,42 @@ int dasd_reread_partition_table(const char *device, int ntries) return err; } + +/* + * Reserve DASD disk. + * + * @param[in] device node device node's name + * + * @retval 0 in case of success + * @retval errno in case of failure + * + */ +int dasd_disk_reserve(const char *device) +{ + int fd; + + fd = dasd_open_device(device, O_RDONLY); + RUN_IOCTL(fd, BIODASDRSRV, NULL); + dasd_close_device(fd); + + return 0; +} + +/* + * Release DASD disk + * + * @param[in] device node device node's name + * + * @retval 0 in case of success + * @retval errno in case of failure + */ +int dasd_disk_release(const char *device) +{ + int fd; + + fd = dasd_open_device(device, O_RDONLY); + RUN_IOCTL(fd, BIODASDRLSE, NULL); + dasd_close_device(fd); + + return 0; +} diff --git a/libzds/libzds.c b/libzds/libzds.c index affb30c..c444769 100644 --- a/libzds/libzds.c +++ b/libzds/libzds.c @@ -22,8 +22,6 @@ #include "lib/dasd_base.h" #include "lib/dasd_sys.h" #include "lib/libzds.h" -#include "lib/util_base.h" -#include "lib/util_list.h" #include "lib/vtoc.h" /** @cond PRIVATE */ @@ -70,42 +68,6 @@ struct errormsg { char text[ERRORMSG]; }; -/** - * As the VTOC is the data area on the DASD that describes all data sets, - * this library will often have to refer to the various records in the VTOC. - * To make this more efficiant, we will read the whole VTOC once and identify - * all elements (DSCBs). The raw data of the VTOC tracks and the index to the - * DSCBs is stored. - */ -struct raw_vtoc { - /** @brief The raw track data */ - char *rawdata; - /** @brief This size of the raw track data in bytes */ - unsigned long long rawdatasize; - /** @brief An array with pointers to the various DSCBs in the rawdata */ - char **vtocindex; - /** @brief Number of entries in the index */ - unsigned int vtocindexcount; - /** @brief Number of records per VTOC track - * - * @note While the DS4DEVDT field in the format 4 DSCB names the number - * if DSCBs per VTOC track, we count the records, which is DS4DEVDT + 1 - * for record 0. - */ - unsigned int vtoc_rec_per_track; - /** @brief The track number in which the vtoc begins on the DASD */ - unsigned int vtoctrackoffset; - /** @brief Start record of VTOC. - * - * The rawdata contains full tracks. This is the number of the first - * record that actually belongs to the VTOC */ - unsigned int vtocrecno; - /** @brief The DASD this vtoc was read from */ - struct dasd *dasd; - /** @brief Detailed error messages in case of a problem */ - struct errorlog *log; -}; - struct dscbiterator { /** @brief The raw_vtoc this iterator refers to */ struct raw_vtoc *rawvtoc; @@ -113,31 +75,6 @@ struct dscbiterator { unsigned int i; }; -struct dasd { - /** @brief List head used to store a list of DASDs in struct zdsroot */ - struct util_list_node list; - /** @brief Name of the block device, e.g. /dev/dasde */ - char *device; - /** @brief File descriptor for the block device. - * - * The device is kept open for as along as the library uses it. - * This lets the system know that the device is still in use. - */ - int inusefd; - /* @brief where to find the volume label */ - unsigned int label_block; - /** @brief Device geometry. How many cylinders does the DASD have. */ - unsigned int cylinders; - /** @brief Device geometry. How many heads does the DASD have. */ - unsigned int heads; - /** @brief The VTOC data that has been read from this device */ - struct raw_vtoc *rawvtoc; - /** @brief The volume label that has been read from this device */ - volume_label_t *vlabel; - /** @brief Detailed error messages in case of a problem */ - struct errorlog *log; -}; - struct dasdhandle { /** @brief The struct dasd this context relates to */ struct dasd *dasd; @@ -412,6 +349,7 @@ int lzds_zdsroot_alloc(struct zdsroot **root) return 0; } + /** * It should be noted that this frees all structures that are owned by the * root structure as well. For example, a pointer to a struct dasd that @@ -419,21 +357,11 @@ int lzds_zdsroot_alloc(struct zdsroot **root) * * @param[in] root Reference to the zdsroot structure that is to be freed. */ -void lzds_zdsroot_free(struct zdsroot *root) +void lzds_dslist_free(struct zdsroot *root) { - struct dasd *dasd, *nextdasd; struct dataset *ds, *nextds; int i; - if (!root) - return; - - util_list_iterate_safe(root->dasdlist, dasd, nextdasd) { - util_list_remove(root->dasdlist, dasd); - dasd_free(dasd); - } - util_list_free(root->dasdlist); - util_list_iterate_safe(root->datasetlist, ds, nextds) { util_list_remove(root->datasetlist, ds); dataset_free_memberlist(ds); @@ -442,6 +370,28 @@ void lzds_zdsroot_free(struct zdsroot *root) errorlog_free(ds->log); free(ds); } +} + +/** + * It should be noted that this frees all structures that are owned by the + * root structure as well. For example, a pointer to a struct dasd that + * has been returned by lzds_zdsroot_add_device is not valid anymore. + * + * @param[in] root Reference to the zdsroot structure that is to be freed. + */ +void lzds_zdsroot_free(struct zdsroot *root) +{ + struct dasd *dasd, *nextdasd; + + if (!root) + return; + + util_list_iterate_safe(root->dasdlist, dasd, nextdasd) { + util_list_remove(root->dasdlist, dasd); + dasd_free(dasd); + } + util_list_free(root->dasdlist); + lzds_dslist_free(root); util_list_free(root->datasetlist); errorlog_free(root->log); free(root); @@ -1443,7 +1393,7 @@ int lzds_raw_vtoc_get_dscb_from_cchhb(struct raw_vtoc *rv, cchhb_t *p, * - EPROTO The VTOC data is not in a valid format. * - EIO Other I/O error */ -int lzds_dasd_read_rawvtoc(struct dasd *dasd) +int lzds_dasd_read_rawvtoc(struct dasd *dasd, struct raw_vtoc *rawvtoc) { unsigned long long vtoctrckno, vtocrecno; unsigned int vtoctrack_start, vtoctrack_end, vtocindexsize; @@ -1455,26 +1405,11 @@ int lzds_dasd_read_rawvtoc(struct dasd *dasd) format4_label_t *f4; unsigned long long rawvtocsize; - struct raw_vtoc *rawvtoc = NULL; volume_label_t *vlabel = NULL; char *trackdata = NULL; char vol1[] = {0xe5, 0xd6, 0xd3, 0xf1, 0x00}; /* "VOL1" in EBCDIC */ errorlog_clear(dasd->log); - /* cleanup the old rawvtoc structures before we read new ones */ - rawvtoc = dasd->rawvtoc; - dasd->rawvtoc = NULL; - if (rawvtoc) { - free(rawvtoc->rawdata); - free(rawvtoc->vtocindex); - free(rawvtoc); - } - - rawvtoc = malloc(sizeof(*rawvtoc)); - if (!rawvtoc) - return ENOMEM; - memset(rawvtoc, 0, sizeof(*rawvtoc)); - rawvtoc->dasd = dasd; rc = lzds_dasd_get_vlabel(dasd, &vlabel); if (rc) { @@ -1611,13 +1546,49 @@ int lzds_dasd_read_rawvtoc(struct dasd *dasd) ++i; } - dasd->rawvtoc = rawvtoc; return 0; cleanup: - free(rawvtoc->vtocindex); free(trackdata); - free(rawvtoc); + return rc; +} + +/** + * @param[in] dasd The struct dasd that represents the device we want to read + * the VTOC from. + * @return 0 on success, otherwise one of the following error codes: + * - ENOMEM Could not allocate internal structure due to lack of memory. + * - EINVAL The volume label has not yet been read or it is not valid. + * - EPROTO The VTOC data is not in a valid format. + * - EIO Other I/O error + */ +int lzds_dasd_alloc_rawvtoc(struct dasd *dasd) +{ + struct raw_vtoc *rawvtoc = NULL; + int rc; + + /* cleanup the old rawvtoc structures before we read new ones */ + rawvtoc = dasd->rawvtoc; + dasd->rawvtoc = NULL; + if (rawvtoc) { + free(rawvtoc->rawdata); + free(rawvtoc->vtocindex); + free(rawvtoc); + } + + rawvtoc = malloc(sizeof(*rawvtoc)); + if (!rawvtoc) + return ENOMEM; + memset(rawvtoc, 0, sizeof(*rawvtoc)); + rawvtoc->dasd = dasd; + + rc = lzds_dasd_read_rawvtoc(dasd, rawvtoc); + if (rc) { + free(rawvtoc->vtocindex); + free(rawvtoc); + } else { + dasd->rawvtoc = rawvtoc; + } return rc; } @@ -2261,6 +2232,7 @@ out1: static int dataset_merge_dataset(struct dataset *baseds, struct dataset *newds) { int k, l, dspcount; + for (k = 0; k < MAXVOLUMESPERDS; ++k) { /* if both datasets have a part in position k, * then something is wrong */ @@ -2280,18 +2252,20 @@ static int dataset_merge_dataset(struct dataset *baseds, struct dataset *newds) * Since dsp[0] may not be set yet, we loop over the * base dsp array until we find an entry. */ - for (l = 0; l < MAXVOLUMESPERDS; ++l) - if (baseds->dsp[l]) { - if (memcmp(baseds->dsp[l]->f1->DS1DSSN, - newds->dsp[k]->f1->DS1DSSN, - MAXVOLSER)) - return errorlog_add_message( - &baseds->log, NULL, EPROTO, - "merge dataset: part %d has incompatible" - " base volume serial\n", k); - else - break; - } + for (l = 0; l < MAXVOLUMESPERDS; ++l) { + if (!baseds->dsp[l]) + continue; + if (memcmp(baseds->dsp[l]->f1->DS1DSSN, + newds->dsp[k]->f1->DS1DSSN, + MAXVOLSER)) + return errorlog_add_message( + &baseds->log, NULL, EPROTO, + "merge dataset: part %d has incompatible base volume serial\n", + k); + else + break; + } + baseds->dsp[k] = newds->dsp[k]; baseds->dspcount++; diff --git a/zdsfs/zdsfs.c b/zdsfs/zdsfs.c index 462fda7..d7a83ee 100644 --- a/zdsfs/zdsfs.c +++ b/zdsfs/zdsfs.c @@ -52,6 +52,8 @@ struct zdsfs_info { }; static struct zdsfs_info zdsfsinfo; +static int zdsfs_create_meta_data_buffer(struct zdsfs_info *); +static int zdsfs_verify_datasets(void); struct zdsfs_file_info { struct dshandle *dsh; @@ -194,6 +196,48 @@ static int zdsfs_getattr(const char *path, struct stat *stbuf) return 0; } +static void zdsfs_read_device(struct dasd *newdasd, const char *device) +{ + struct errorlog *log; + int rc; + + rc = dasd_disk_reserve(device); + if (rc) { + fprintf(stderr, "error when reserving device %s: %s\n", + device, strerror(rc)); + lzds_dasd_get_errorlog(newdasd, &log); + lzds_errorlog_fprint(log, stderr); + exit(1); + } + rc = lzds_dasd_alloc_rawvtoc(newdasd); + if (rc) { + fprintf(stderr, "error when reading VTOC from device %s: %s\n", + device, strerror(rc)); + lzds_dasd_get_errorlog(newdasd, &log); + lzds_errorlog_fprint(log, stderr); + exit(1); + } + rc = lzds_zdsroot_extract_datasets_from_dasd(zdsfsinfo.zdsroot, + newdasd); + if (rc) { + fprintf(stderr, + "error when extracting data sets from dasd %s: %s\n", + device, strerror(rc)); + lzds_zdsroot_get_errorlog(zdsfsinfo.zdsroot, &log); + lzds_errorlog_fprint(log, stderr); + exit(1); + } + rc = dasd_disk_release(device); + if (rc) { + fprintf(stderr, "error when releasing device %s: %s\n", + device, strerror(rc)); + lzds_dasd_get_errorlog(newdasd, &log); + lzds_errorlog_fprint(log, stderr); + exit(1); + } +} + + static int zdsfs_statfs(const char *UNUSED(path), struct statvfs *statvfs) { struct dasditerator *dasdit; @@ -240,6 +284,33 @@ static int zdsfs_statfs(const char *UNUSED(path), struct statvfs *statvfs) return 0; } + +static int zdsfs_update_vtoc(void) +{ + struct dasditerator *dasdit; + struct dasd *dasd; + int rc; + + lzds_dslist_free(zdsfsinfo.zdsroot); + rc = lzds_zdsroot_alloc_dasditerator(zdsfsinfo.zdsroot, &dasdit); + if (rc) + return -ENOMEM; + + while (!lzds_dasditerator_get_next_dasd(dasdit, &dasd)) + zdsfs_read_device(dasd, dasd->device); + + lzds_dasditerator_free(dasdit); + rc = zdsfs_verify_datasets(); + if (rc) + return rc; + + rc = zdsfs_create_meta_data_buffer(&zdsfsinfo); + if (rc) + return rc; + + return 0; +} + static int zdsfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t UNUSED(offset), struct fuse_file_info *UNUSED(fi)) { @@ -253,6 +324,10 @@ static int zdsfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, int rc; int ispds, issupported; + rc = zdsfs_update_vtoc(); + if (rc) + return rc; + /* we have two type of directories * type one: the root directory contains all data sets */ @@ -329,6 +404,9 @@ static int zdsfs_open(const char *path, struct fuse_file_info *fi) goto error1; if (strcmp(path, "/"METADATAFILE) == 0) { + rc = zdsfs_update_vtoc(); + if (rc) + return rc; zfi->dsh = NULL; zfi->is_metadata_file = 1; zfi->metaread = 0; @@ -596,6 +674,7 @@ static int zdsfs_verify_datasets(void) if (rc) return ENOMEM; while (!lzds_dsiterator_get_next_dataset(dsit, &ds)) { + lzds_dataset_get_name(ds, &dsname); lzds_dataset_get_is_complete(ds, &iscomplete); if (!iscomplete) { lzds_dataset_get_name(ds, &dsname); @@ -822,23 +901,7 @@ static void zdsfs_process_device(const char *device) lzds_errorlog_fprint(log, stderr); exit(1); } - rc = lzds_dasd_read_rawvtoc(newdasd); - if (rc) { - fprintf(stderr, "error when reading VTOC from device %s:" - " %s\n", device, strerror(rc)); - lzds_dasd_get_errorlog(newdasd, &log); - lzds_errorlog_fprint(log, stderr); - exit(1); - } - rc = lzds_zdsroot_extract_datasets_from_dasd(zdsfsinfo.zdsroot, - newdasd); - if (rc) { - fprintf(stderr, "error when extracting data sets from dasd %s:" - " %s\n", device, strerror(rc)); - lzds_zdsroot_get_errorlog(zdsfsinfo.zdsroot, &log); - lzds_errorlog_fprint(log, stderr); - exit(1); - } + zdsfs_read_device(newdasd, device); } static void zdsfs_process_device_file(const char *devfile) -- 2.21.3 From db8ece16f1c2b4e514d39505397bc4d70052617b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 7 Nov 2019 12:31:54 +0100 Subject: [PATCH 30/56] zdev: add zfcp dix parameter handling (#1723852) Description: The zfcp kernel module was changed to introduce separate parameters for selecting DIF and DIF&DIX. This patch implements the corresponding changes in chzdev and lszdev. Upstream-ID: 2c4c210ca8c641ad6e051eea65493202a16a5f4a --- zdev/src/zfcp.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/zdev/src/zfcp.c b/zdev/src/zfcp.c index 03aa252..82dac57 100644 --- a/zdev/src/zfcp.c +++ b/zdev/src/zfcp.c @@ -75,16 +75,28 @@ static struct attrib zfcp_tattr_allow_lun_scan = { static struct attrib zfcp_tattr_dif = { .name = "dif", - .title = "Enable DIF/DIX data consistency checking", + .title = "Enable DIF data consistency checking", .desc = - "Control the use of the end-to-end data consistency checking\n" - "mechanism (DIF/DIX):\n" + "Control the use of the DIF data consistency checking\n" + "mechanism:\n" " 0: DIF is disabled\n" " 1: DIF is enabled when supported by the FCP device hardware\n", .defval = "0", .accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)), }; +static struct attrib zfcp_tattr_dix = { + .name = "dix", + .title = "Enable DIF&DIX data consistency checking", + .desc = + "Control the use of the end-to-end data consistency checking\n" + "mechanism (DIF&DIX):\n" + " 0: DIF&DIX is disabled\n" + " 1: DIF&DIX is enabled when supported by the FCP device hardware\n", + .defval = "0", + .accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)), +}; + static struct attrib zfcp_tattr_datarouter = { .name = "datarouter", .title = "Enable hardware data routing", @@ -160,6 +172,7 @@ static void all_bools_to_num(struct setting_list *list) util_list_iterate(&list->list, s) { if (s->attrib == &zfcp_tattr_allow_lun_scan || s->attrib == &zfcp_tattr_dif || + s->attrib == &zfcp_tattr_dix || s->attrib == &zfcp_tattr_datarouter || s->attrib == &zfcp_tattr_no_auto_port_rescan) { /* Convert Y to 1 and N to 0. */ @@ -295,6 +308,7 @@ struct devtype zfcp_devtype = { &zfcp_tattr_queue_depth, &zfcp_tattr_allow_lun_scan, &zfcp_tattr_dif, + &zfcp_tattr_dix, &zfcp_tattr_datarouter, &zfcp_tattr_no_auto_port_rescan, &zfcp_tattr_port_scan_ratelimit, -- 2.21.3 From 41bb1d87abfe5f4b856ba885109749d42959a812 Mon Sep 17 00:00:00 2001 From: Philipp Rudo Date: Wed, 21 Aug 2019 12:34:43 +0200 Subject: [PATCH 31/56] zipl: Fix error message printed with --dumptofs (#1750307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The option --dump does not exist. Adjust it to --dumpto. Signed-off-by: Philipp Rudo Signed-off-by: Jan Höppner --- zipl/src/job.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zipl/src/job.c b/zipl/src/job.c index e68d1b7..5dcebaf 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -114,7 +114,7 @@ get_command_line(int argc, char* argv[], struct command_line* line) break; case 'D': error_reason("dumptofs has been deprecated, use " - "--dump instead"); + "--dumpto instead"); rc = -1; break; case 'M': -- 2.21.3 From 47f3a82078811bd4dfad2726ed3ba38cc0fe2e3f Mon Sep 17 00:00:00 2001 From: Stefan Haberland Date: Wed, 4 Sep 2019 13:01:53 +0200 Subject: [PATCH 32/56] zipl: set correct secure IPL default value (#1750326) Set secure IPL to auto as default value to match documented behavior. (cherry picked from commit 367598b18738d26e92302812deb5fdbdc2617ba6) --- zipl/src/job.c | 1 + 1 file changed, 1 insertion(+) diff --git a/zipl/src/job.c b/zipl/src/job.c index 5dcebaf..1178a7d 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -1880,6 +1880,7 @@ job_get(int argc, char* argv[], struct job_data** data) job->add_files = cmdline.add_files; job->data.mvdump.force = cmdline.force; job->dry_run = cmdline.dry_run; + job->is_secure = SECURE_BOOT_AUTO; /* Get job data from user input */ if (cmdline.help) { job->command_line = 1; -- 2.21.3 From d64cb3431d7cd51a4b4c24ed3e0a2e50a024b50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 7 Nov 2019 12:49:06 +0100 Subject: [PATCH 33/56] zkey: various enhancements (#1725881) Description: Enhancements to zkey in response to first (customer) experiences Upstream-ID: 9561a0b98385180441b2f5aa338fb583a94d4c40 Upstream-ID: a648dbb0149513885eba4ced84ca9e1e65c6caca Upstream-ID: d95dc6d698b5a666920201e39f3ccd0b1afb918c Upstream-ID: b26dbfe8320c9ddb72b86efbe532b616243e0e3f Upstream-ID: a69470d7e02af2d7d6a4c781eef57727c0672fc0 Upstream-ID: 79ce12055369009be3d5d0a170e8d6edde0e96f2 Upstream-ID: ed6e3b727059e0533b5ccf7bb697a7fe1a5f151b Upstream-ID: 951bd1ae54a074cf59a4719909d6f6122f5c0448 Upstream-ID: ec9c67189e1b841a922a627a9066d17222b51a01 Upstream-ID: 90dc65659fa868ab446db07cac82235fa8a184a5 Upstream-ID: b0c7965234c80af83b0419dc3781aff323cd04d4 Upstream-ID: 5a0c93443c6a1d31d238444c798272979f0c5bf3 Upstream-ID: 3ed8ab4e2a071180cfdce1a673d5101b7a09e171 Upstream-ID: 11bfa1d3c8bdec60c5d0b04e93431e55c264d5ef Upstream-ID: e693173eef75b9c67ad5516c2e1bc3d5ded5bbee Upstream-ID: e4cd42900f1922b25d99af1134e2695799d14248 Upstream-ID: f97d048643ef105e2c4dcdcf656d4dee5b2d7a15 --- zkey/keystore.c | 315 ++++++++++++++++++++++++++++++----------- zkey/keystore.h | 24 ++-- zkey/pkey.c | 10 +- zkey/zkey-cryptsetup.1 | 36 +++++ zkey/zkey-cryptsetup.c | 81 ++++++++++- zkey/zkey.1 | 244 +++++++++++++++++++++++++++++-- zkey/zkey.c | 282 ++++++++++++++++++++++++++++++++++-- 7 files changed, 866 insertions(+), 126 deletions(-) diff --git a/zkey/keystore.c b/zkey/keystore.c index a4ad634..33c8f93 100644 --- a/zkey/keystore.c +++ b/zkey/keystore.c @@ -259,13 +259,13 @@ static int _keystore_set_file_permission(struct keystore *keystore, if (chmod(filename, keystore->mode) != 0) { rc = -errno; - warnx("chmod faild on file '%s': %s", filename, strerror(-rc)); + warnx("chmod failed on file '%s': %s", filename, strerror(-rc)); return rc; } if (chown(filename, geteuid(), keystore->owner) != 0) { rc = -errno; - warnx("chown faild on file '%s': %s", filename, strerror(-rc)); + warnx("chown failed on file '%s': %s", filename, strerror(-rc)); return rc; } @@ -369,7 +369,7 @@ out: return 0; } -typedef int (*check_association_t)(const char *value, bool remove, +typedef int (*check_association_t)(const char *value, bool remove, bool set, char **normalized, void *private); /** @@ -407,7 +407,7 @@ static int _keystore_set_association(struct properties *key_props, for (i = 0; newvals[i] != NULL; i++) { if (check_func != NULL) { - rc = check_func(newvals[i], 0, &normalized, + rc = check_func(newvals[i], 0, 1, &normalized, check_private); if (rc != 0) goto out; @@ -488,7 +488,7 @@ static int _keystore_add_association(struct properties *key_props, for (i = 0; newvals[i] != NULL; i++) { if (check_func != NULL) { - rc = check_func(newvals[i], 0, &normalized, + rc = check_func(newvals[i], 0, 0, &normalized, check_private); if (rc != 0) goto out; @@ -567,7 +567,7 @@ static int _keystore_remove_association(struct properties *key_props, for (i = 0; delvals[i] != NULL; i++) { if (check_func != NULL) { - rc = check_func(delvals[i], 1, &normalized, + rc = check_func(delvals[i], 1, 0, &normalized, check_private); if (rc != 0) goto out; @@ -1085,20 +1085,27 @@ out: return rc; } +struct apqn_check { + bool noonlinecheck; + bool nomsg; +}; + /** * Checks an APQN value for its syntax. This is a callback function for * function _keystore_change_association(). * * @param[in] apqn the APQN value to check * @param[in] remove if true the apqn is removed + * @param[in] set if true the apqn is set (not used here) * @param[out] normalized normalized value on return or NULL if no change - * @param[in] private private data (not used here) + * @param[in] private private data (struct apqn_check) * * @returns 0 if successful, a negative errno value otherwise */ -static int _keystore_apqn_check(const char *apqn, bool remove, - char **normalized, void *UNUSED(private)) +static int _keystore_apqn_check(const char *apqn, bool remove, bool UNUSED(set), + char **normalized, void *private) { + struct apqn_check *info = (struct apqn_check *)private; int rc, card, domain; regmatch_t pmatch[1]; regex_t reg_buf; @@ -1124,15 +1131,16 @@ static int _keystore_apqn_check(const char *apqn, bool remove, util_asprintf(normalized, "%02x.%04x", card, domain); - if (remove) { + if (remove || info->noonlinecheck) { rc = 0; goto out; } rc = _keystore_is_apqn_online(card, domain); if (rc != 1) { - warnx("The APQN %02x.%04x is %s", card, domain, - rc == -1 ? "not a CCA card" : "not online"); + if (info->nomsg == 0) + warnx("The APQN %02x.%04x is %s", card, domain, + rc == -1 ? "not a CCA card" : "not online"); rc = -EIO; goto out; } else { @@ -1149,6 +1157,7 @@ struct volume_check { struct keystore *keystore; const char *name; const char *volume; + bool set; }; /** @@ -1173,6 +1182,11 @@ static int _keystore_volume_check_process(struct keystore *UNUSED(keystore), { struct volume_check *info = (struct volume_check *)private; + if (info->set) { + if (strcmp(name, info->name) == 0) + return 0; + } + warnx("Key '%s' is already associated with volume '%s'", name, info->volume); return -EINVAL; @@ -1204,12 +1218,13 @@ static int _keystore_is_block_device(const char *volume) * * @param[in] volume the Volume value to check * @param[in] remove if true the volume is removed + * @param[in] set if true the volume is set * @param[out] normalized normalized value on return or NULL if no change * @param[in] private private data: struct volume_check * * @returns 0 if successful, a negative errno value otherwise */ -static int _keystore_volume_check(const char *volume, bool remove, +static int _keystore_volume_check(const char *volume, bool remove, bool set, char **normalized, void *private) { struct volume_check *info = (struct volume_check *)private; @@ -1250,6 +1265,7 @@ static int _keystore_volume_check(const char *volume, bool remove, goto out; } + info->set = set; rc = _keystore_process_filtered(info->keystore, NULL, info->volume, NULL, NULL, _keystore_volume_check_process, info); @@ -1359,7 +1375,7 @@ struct keystore *keystore_new(const char *directory, bool verbose) util_assert(directory != NULL, "Internal error: directory is NULL"); if (stat(directory, &sb) != 0) { - warnx("'%s' does not exist", directory); + warnx("Can not access '%s': %s", directory, strerror(errno)); return NULL; } if (!(sb.st_mode & S_IFDIR)) { @@ -1543,6 +1559,8 @@ static int _keystore_set_default_properties(struct properties *key_props) * key (optional, can be NULL) * @param[in] apqns a comma separated list of APQNs associated with this * key (optional, can be NULL) + * @param[in] noapqncheck if true, the specified APQN(s) are not checked for + * existence and type. * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system @@ -1554,10 +1572,14 @@ static int _keystore_create_info_file(struct keystore *keystore, const struct key_filenames *filenames, const char *description, const char *volumes, const char *apqns, + bool noapqncheck, size_t sector_size, const char *volume_type) { - struct volume_check vol_check = { .keystore = keystore, .name = name }; + struct volume_check vol_check = { .keystore = keystore, .name = name, + .set = 0 }; + struct apqn_check apqn_check = { .noonlinecheck = noapqncheck, + .nomsg = 0 }; struct properties *key_props; char temp[10]; int rc; @@ -1583,7 +1605,8 @@ static int _keystore_create_info_file(struct keystore *keystore, rc = _keystore_change_association(key_props, PROP_NAME_APQNS, apqns != NULL ? apqns : "", - "APQN", _keystore_apqn_check, NULL); + "APQN", _keystore_apqn_check, + &apqn_check); if (rc != 0) goto out; @@ -1642,15 +1665,18 @@ out: } /** - * Extracts a card/domain pair from the specified APQns, or uses AUTOSELECT - * if no APQNs are specified. + * Extracts an online card/domain pair from the specified APQns. If none of the + * specified APQNs are online, then -ENODEV is returned. + * If no APQNs are specified at all, then it uses AUTOSELECT and returns zero. */ static int _keystore_get_card_domain(const char *apqns, unsigned int *card, unsigned int *domain) { + struct apqn_check apqn_check = { .noonlinecheck = 0, .nomsg = 1 }; char **apqn_list; char *normalized = NULL; int rc = 0; + int i; *card = AUTOSELECT; *domain = AUTOSELECT; @@ -1662,17 +1688,23 @@ static int _keystore_get_card_domain(const char *apqns, unsigned int *card, if (apqn_list[0] == NULL) goto out; - rc = _keystore_apqn_check(apqn_list[0], 0, &normalized, NULL); - if (normalized != NULL) - free(normalized); - if (rc != 0) - goto out; + for (i = 0; apqn_list[i] != NULL; i++) { + rc = _keystore_apqn_check(apqn_list[i], 0, 0, &normalized, + &apqn_check); + if (normalized != NULL) + free(normalized); + if (rc == -EINVAL) + goto out; + if (rc != 0) + continue; - if (sscanf(apqn_list[0], "%x.%x", card, domain) != 2) { - rc = -EINVAL; - goto out; + if (sscanf(apqn_list[i], "%x.%x", card, domain) == 2) + goto found; } + warnx("None of the specified APQNs is online or of type CCA"); + rc = -ENODEV; +found: out: str_list_free_string_array(apqn_list); return rc; @@ -1688,6 +1720,8 @@ out: * key (optional, can be NULL) * @param[in] apqns a comma separated list of APQNs associated with this * key (optional, can be NULL) + * @param[in] noapqncheck if true, the specified APQN(s) are not checked for + * existence and type. * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system @@ -1704,9 +1738,10 @@ out: */ int keystore_generate_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, - const char *apqns, size_t sector_size, - size_t keybits, bool xts, const char *clear_key_file, - const char *volume_type, int pkey_fd) + const char *apqns, bool noapqncheck, + size_t sector_size, size_t keybits, bool xts, + const char *clear_key_file, const char *volume_type, + int pkey_fd) { struct key_filenames file_names = { NULL, NULL, NULL }; struct properties *key_props = NULL; @@ -1748,7 +1783,7 @@ int keystore_generate_key(struct keystore *keystore, const char *name, rc = _keystore_create_info_file(keystore, name, &file_names, description, volumes, apqns, - sector_size, volume_type); + noapqncheck, sector_size, volume_type); if (rc != 0) goto out_free_props; @@ -1781,6 +1816,8 @@ out_free_key_filenames: * key (optional, can be NULL) * @param[in] apqns a comma separated list of APQNs associated with this * key (optional, can be NULL) + * @param[in] noapqncheck if true, the specified APQN(s) are not checked for + * existence and type. * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system @@ -1792,7 +1829,7 @@ out_free_key_filenames: */ int keystore_import_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, - const char *apqns, size_t sector_size, + const char *apqns, bool noapqncheck, size_t sector_size, const char *import_file, const char *volume_type) { struct key_filenames file_names = { NULL, NULL, NULL }; @@ -1832,7 +1869,7 @@ int keystore_import_key(struct keystore *keystore, const char *name, rc = _keystore_create_info_file(keystore, name, &file_names, description, volumes, apqns, - sector_size, volume_type); + noapqncheck, sector_size, volume_type); if (rc != 0) goto out_free_props; @@ -1870,6 +1907,8 @@ out_free_key_filenames: * key, or an APQN prefixed with '+' or '-' to add or * remove that APQN respectively. If NULL then the APQNs * are not changed. + * @param[in] noapqncheck if true, the specified APQN(s) are not checked for + * existence and type. * @param[in] sector_size the sector size to use with dm-crypt. It must be power * of two and in range 512 - 4096 bytes. 0 means that * the sector size is not specified and the system @@ -1883,10 +1922,13 @@ out_free_key_filenames: */ int keystore_change_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, - const char *apqns, long int sector_size, - const char *volume_type) + const char *apqns, bool noapqncheck, + long int sector_size, const char *volume_type) { - struct volume_check vol_check = { .keystore = keystore, .name = name }; + struct volume_check vol_check = { .keystore = keystore, .name = name, + .set = 0 }; + struct apqn_check apqn_check = { .noonlinecheck = noapqncheck, + .nomsg = 0 }; struct key_filenames file_names = { NULL, NULL, NULL }; struct properties *key_props = NULL; char temp[30]; @@ -1931,7 +1973,8 @@ int keystore_change_key(struct keystore *keystore, const char *name, if (apqns != NULL) { rc = _keystore_change_association(key_props, PROP_NAME_APQNS, apqns, "APQN", - _keystore_apqn_check, NULL); + _keystore_apqn_check, + &apqn_check); if (rc != 0) goto out; } @@ -1982,10 +2025,6 @@ int keystore_change_key(struct keystore *keystore, const char *name, goto out; } - rc = _keystore_set_file_permission(keystore, file_names.info_filename); - if (rc != 0) - goto out; - pr_verbose(keystore, "Successfully changed key '%s'", name); out: @@ -2269,6 +2308,7 @@ static void _keystore_print_record(struct util_rec *rec, struct validate_info { struct util_rec *rec; int pkey_fd; + bool noapqncheck; unsigned long int num_valid; unsigned long int num_invalid; unsigned long int num_warnings; @@ -2422,8 +2462,9 @@ static int _keystore_process_validate(struct keystore *keystore, "master key\n", 0); info->num_warnings++; } - if (_keystore_display_apqn_status(properties, name) != 0) - info->num_warnings++; + if (info->noapqncheck == 0) + if (_keystore_display_apqn_status(properties, name) != 0) + info->num_warnings++; if (_keystore_display_volume_status(properties, name) != 0) info->num_warnings++; @@ -2439,11 +2480,16 @@ out: * * @param[in] keystore the key store * @param[in] name_filter the name filter to select the key (can be NULL) + * @param[in] apqn_filter the APQN filter to select the key (can be NULL) + * @param[in] noapqncheck if true, the specified APQN(s) are not checked for + * existence and type. + * @param[in] pkey_fd the file descriptor of /dev/pkey * * @returns 0 for success or a negative errno in case of an error */ int keystore_validate_key(struct keystore *keystore, const char *name_filter, - const char *apqn_filter, int pkey_fd) + const char *apqn_filter, bool noapqncheck, + int pkey_fd) { struct validate_info info; struct util_rec *rec; @@ -2454,6 +2500,7 @@ int keystore_validate_key(struct keystore *keystore, const char *name_filter, rec = _keystore_setup_record(1); info.pkey_fd = pkey_fd; + info.noapqncheck = noapqncheck; info.rec = rec; info.num_valid = 0; info.num_invalid = 0; @@ -2683,10 +2730,6 @@ static int _keystore_process_reencipher(struct keystore *keystore, if (rc != 0) goto out; - rc = _keystore_set_file_permission(keystore, out_file); - if (rc != 0) - goto out; - if (params.complete || params.inplace == 1) { rc = _keystore_set_timestamp_property(properties, PROP_NAME_REENC_TIME); @@ -2712,11 +2755,6 @@ static int _keystore_process_reencipher(struct keystore *keystore, goto out; } - rc = _keystore_set_file_permission(keystore, - file_names->info_filename); - if (rc != 0) - goto out; - util_asprintf(&temp, "The following LUKS2 volumes are " "encrypted with key '%s'. You should also " "re-encipher the volume key of those volumes " @@ -2846,7 +2884,7 @@ int keystore_copy_key(struct keystore *keystore, const char *name, const char *newname, const char *volumes) { struct volume_check vol_check = { .keystore = keystore, - .name = newname }; + .name = newname, .set = 0 }; struct key_filenames file_names = { NULL, NULL, NULL }; struct key_filenames new_names = { NULL, NULL, NULL }; struct properties *key_prop = NULL; @@ -3255,6 +3293,13 @@ static int _keystore_execute_cmd(const char *cmd, struct crypt_info { bool execute; + bool batch_mode; + const char *keyfile; + size_t keyfile_offset; + size_t keyfile_size; + size_t tries; + bool open; + bool format; char **volume_filter; int (*process_func)(struct keystore *keystore, const char *volume, @@ -3293,16 +3338,46 @@ static int _keystore_process_cryptsetup(struct keystore *keystore, const char *volume_type, struct crypt_info *info) { + char *keyfile_opt = NULL, *offset_opt = NULL; + char *size_opt = NULL, *tries_opt = NULL; + char *common_passphrase_options; + size_t common_len; char temp[100]; int rc = 0; char *cmd; sprintf(temp, "--sector-size %lu ", sector_size); + if (info->keyfile) { + util_asprintf(&keyfile_opt, "--key-file '%s' ", info->keyfile); + if (info->keyfile_offset > 0) + util_asprintf(&offset_opt, "--keyfile-offset %lu ", + info->keyfile_offset); + if (info->keyfile_size > 0) + util_asprintf(&size_opt, "--keyfile-size %lu ", + info->keyfile_size); + } + if (info->tries > 0) + util_asprintf(&tries_opt, "--tries %lu ", info->tries); + util_asprintf(&common_passphrase_options, "%s%s%s%s", + keyfile_opt != NULL ? keyfile_opt : "", + offset_opt != NULL ? offset_opt : "", + size_opt != NULL ? size_opt : "", + tries_opt != NULL ? tries_opt : ""); + common_len = strlen(common_passphrase_options); + free(keyfile_opt); + free(offset_opt); + free(size_opt); + free(tries_opt); + if (strcasecmp(volume_type, VOLUME_TYPE_PLAIN) == 0) { + if (info->format) + return 0; + util_asprintf(&cmd, - "cryptsetup plainOpen %s--key-file '%s' " + "cryptsetup plainOpen %s%s--key-file '%s' " "--key-size %lu --cipher %s %s%s %s", + info->batch_mode ? "-q " : "", keystore->verbose ? "-v " : "", key_file_name, key_file_size * 8, cipher_spec, sector_size > 0 ? temp : "", volume, dmname); @@ -3314,39 +3389,69 @@ static int _keystore_process_cryptsetup(struct keystore *keystore, printf("%s\n", cmd); } } else if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) == 0) { - util_asprintf(&cmd, - "cryptsetup luksFormat %s--type luks2 " - "--master-key-file '%s' --key-size %lu " - "--cipher %s %s%s", - keystore->verbose ? "-v " : "", key_file_name, - key_file_size * 8, cipher_spec, - sector_size > 0 ? temp : "", volume); - - if (info->execute) { - printf("Executing: %s\n", cmd); - rc = _keystore_execute_cmd(cmd, "cryptsetup"); + if (info->open) { + util_asprintf(&cmd, + "cryptsetup luksOpen %s%s%s%s %s", + info->batch_mode ? "-q " : "", + keystore->verbose ? "-v " : "", + common_len > 0 ? + common_passphrase_options : "", + volume, dmname); + + if (info->execute) { + printf("Executing: %s\n", cmd); + rc = _keystore_execute_cmd(cmd, "cryptsetup"); + } else { + printf("%s\n", cmd); + } } else { - printf("%s\n", cmd); - } - - free(cmd); - if (rc != 0) - return rc; - - util_asprintf(&cmd, - "zkey-cryptsetup setvp %s%s", volume, - keystore->verbose ? " -V " : ""); + /* + * Use PBKDF2 as key derivation function for LUKS2 + * volumes. LUKS2 uses Argon2i as default, but this + * might cause out-of-memory errors when multiple LUKS2 + * volumes are opened automatically via /etc/crypttab + */ + util_asprintf(&cmd, + "cryptsetup luksFormat %s%s--type luks2 " + "--master-key-file '%s' --key-size %lu " + "--cipher %s --pbkdf pbkdf2 %s%s%s", + info->batch_mode ? "-q " : "", + keystore->verbose ? "-v " : "", + key_file_name, key_file_size * 8, + cipher_spec, common_len > 0 ? + common_passphrase_options : "", + sector_size > 0 ? temp : "", volume); + + if (info->execute) { + printf("Executing: %s\n", cmd); + rc = _keystore_execute_cmd(cmd, "cryptsetup"); + } else { + printf("%s\n", cmd); + } - if (info->execute) { - printf("Executing: %s\n", cmd); - rc = _keystore_execute_cmd(cmd, "zkey-cryptsetup"); - } else { - printf("%s\n", cmd); + free(cmd); + if (rc != 0) + return rc; + + util_asprintf(&cmd, + "zkey-cryptsetup setvp %s %s%s", volume, + common_len > 0 ? + common_passphrase_options : "", + keystore->verbose ? "-V" : ""); + + if (info->execute) { + printf("Executing: %s\n", cmd); + rc = _keystore_execute_cmd(cmd, + "zkey-cryptsetup"); + } else { + printf("%s\n", cmd); + } } } else { return -EINVAL; } + free(common_passphrase_options); free(cmd); return rc; } @@ -3376,7 +3481,7 @@ static int _keystore_process_crypttab(struct keystore *UNUSED(keystore), size_t key_file_size, size_t sector_size, const char *volume_type, - struct crypt_info *UNUSED(info)) + struct crypt_info *info) { char temp[1000]; @@ -3395,11 +3500,22 @@ static int _keystore_process_crypttab(struct keystore *UNUSED(keystore), } sprintf(temp, ",sector-size=%lu", sector_size); - printf("%s\t%s\t%s\tplain,cipher=%s,size=%lu,hash=plain%s\n", + printf("%s\t%s\t%s\tplain,cipher=%s,size=%lu%s\n", dmname, volume, key_file_name, cipher_spec, key_file_size * 8, sector_size > 0 ? temp : ""); } else if (strcasecmp(volume_type, VOLUME_TYPE_LUKS2) == 0) { - printf("%s\t%s\n", dmname, volume); + printf("%s\t%s\t%s\tluks", dmname, volume, + info->keyfile != NULL ? info->keyfile : "none"); + if (info->keyfile != NULL) { + if (info->keyfile_offset > 0) + printf(",keyfile-offset=%lu", + info->keyfile_offset); + if (info->keyfile_size > 0) + printf(",keyfile-size=%lu", info->keyfile_size); + } + if (info->tries > 0) + printf(",tries=%lu", info->tries); + printf("\n"); } else { return -EINVAL; } @@ -3584,11 +3700,21 @@ out: * @param[in] execute If TRUE the cryptsetup command is executed, * otherwise it is printed to stdout * @param[in] volume_type the type of volume to generate cryptsetup cmds for - * * + * @param[in] keyfile If non-NULL, specifies the name of the file to + * read the passphrase from. + * @param[in] keyfile_offset the offset in bytes for reading from keyfile + * @param[in] keyfile_size the size in bytes for reading from keyfile + * @param[in] tries the number of tries for passphrase entry + * @param[in] batch_mode If TRUE, suppress cryptsetup confirmation questions + * @param[in] open If TRUE, generate luksOpen/plainOpen commands + * @param[in] format If TRUE, generate luksFormat commands * @returns 0 for success or a negative errno in case of an error */ int keystore_cryptsetup(struct keystore *keystore, const char *volume_filter, - bool execute, const char *volume_type) + bool execute, const char *volume_type, + const char *keyfile, size_t keyfile_offset, + size_t keyfile_size, size_t tries, bool batch_mode, + bool open, bool format) { struct crypt_info info = { 0 }; int rc; @@ -3605,6 +3731,13 @@ int keystore_cryptsetup(struct keystore *keystore, const char *volume_filter, } info.execute = execute; + info.open = open; + info.format = format; + info.batch_mode = batch_mode; + info.keyfile = keyfile; + info.keyfile_offset = keyfile_offset; + info.keyfile_size = keyfile_size; + info.tries = tries; info.volume_filter = str_list_split(volume_filter); info.process_func = _keystore_process_cryptsetup; @@ -3636,11 +3769,17 @@ int keystore_cryptsetup(struct keystore *keystore, const char *volume_filter, * for the volume filter. If not specified, the filter * checks the volume part only. * @param[in] volume_type the type of volume to generate crypttab entries for + * @param[in] keyfile If non-NULL, specifies the name of the file to + * read the passphrase from. + * @param[in] keyfile_offset the offset in bytes for reading from keyfile + * @param[in] keyfile_size the size in bytes for reading from keyfile + * @param[in] tries the number of tries for passphrase entry * * @returns 0 for success or a negative errno in case of an error */ int keystore_crypttab(struct keystore *keystore, const char *volume_filter, - const char *volume_type) + const char *volume_type, const char *keyfile, + size_t keyfile_offset, size_t keyfile_size, size_t tries) { struct crypt_info info = { 0 }; int rc; @@ -3656,6 +3795,10 @@ int keystore_crypttab(struct keystore *keystore, const char *volume_filter, return -EINVAL; } + info.keyfile = keyfile; + info.keyfile_offset = keyfile_offset; + info.keyfile_size = keyfile_size; + info.tries = tries; info.volume_filter = str_list_split(volume_filter); info.process_func = _keystore_process_crypttab; diff --git a/zkey/keystore.h b/zkey/keystore.h index 11a2103..16ecc62 100644 --- a/zkey/keystore.h +++ b/zkey/keystore.h @@ -28,25 +28,27 @@ struct keystore *keystore_new(const char *directory, bool verbose); int keystore_generate_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, - const char *apqns, size_t sector_size, - size_t keybits, bool xts, const char *clear_key_file, - const char *volume_type, int pkey_fd); + const char *apqns, bool noapqncheck, + size_t sector_size, size_t keybits, bool xts, + const char *clear_key_file, const char *volume_type, + int pkey_fd); int keystore_import_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, - const char *apqns, size_t sector_size, + const char *apqns, bool noapqncheck, size_t sector_size, const char *import_file, const char *volume_type); int keystore_change_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, - const char *apqns, long int sector_size, - const char *volume_type); + const char *apqns, bool noapqncheck, + long int sector_size, const char *volume_type); int keystore_rename_key(struct keystore *keystore, const char *name, const char *newname); int keystore_validate_key(struct keystore *keystore, const char *name_filter, - const char *apqn_filter, int pkey_fd); + const char *apqn_filter, bool noapqncheck, + int pkey_fd); int keystore_reencipher_key(struct keystore *keystore, const char *name_filter, const char *apqn_filter, @@ -68,10 +70,14 @@ int keystore_list_keys(struct keystore *keystore, const char *name_filter, const char *volume_type); int keystore_cryptsetup(struct keystore *keystore, const char *volume_filter, - bool execute, const char *volume_type); + bool execute, const char *volume_type, + const char *keyfile, size_t keyfile_offset, + size_t keyfile_size, size_t tries, bool batch_mode, + bool open, bool format); int keystore_crypttab(struct keystore *keystore, const char *volume_filter, - const char *volume_type); + const char *volume_type, const char *keyfile, + size_t keyfile_offset, size_t keyfile_size, size_t tries); void keystore_free(struct keystore *keystore); diff --git a/zkey/pkey.c b/zkey/pkey.c index 15e606a..a88c4e9 100644 --- a/zkey/pkey.c +++ b/zkey/pkey.c @@ -163,8 +163,8 @@ u8 *read_secure_key(const char *keyfile, size_t *secure_key_size, buf = util_malloc(size); count = fread(buf, 1, size, fp); - if (count <= 0) { - msg = feof(fp) ? "File is too small" : strerror(errno); + if (count != size) { + msg = ferror(fp) ? strerror(errno) : "File is too small"; warnx("File '%s': %s", keyfile, msg); free(buf); buf = NULL; @@ -211,7 +211,7 @@ int write_secure_key(const char *keyfile, const u8 *secure_key, } count = fwrite(secure_key, 1, secure_key_size, fp); - if (count <= 0) { + if (count != secure_key_size) { warnx("File '%s': %s", keyfile, strerror(errno)); fclose(fp); return -EIO; @@ -301,8 +301,8 @@ static u8 *read_clear_key(const char *keyfile, size_t keybits, bool xts, buf = util_malloc(size); count = fread(buf, 1, size, fp); - if (count <= 0) { - msg = feof(fp) ? "File is too small" : strerror(errno); + if (count != size) { + msg = ferror(fp) ? strerror(errno) : "File is too small"; warnx("File '%s': %s", keyfile, msg); free(buf); buf = NULL; diff --git a/zkey/zkey-cryptsetup.1 b/zkey/zkey-cryptsetup.1 index 988ef76..e93b5f0 100644 --- a/zkey/zkey-cryptsetup.1 +++ b/zkey/zkey-cryptsetup.1 @@ -102,6 +102,7 @@ behave in the same way as with \fBcryptsetup\fP. .IR bytes ] .RB [ \-\-tries | \-T .IR number ] +.RB [ \-\-batch\-mode | \-q ] .RB [ \-\-verbose | \-V ] .RB [ \-\-debug | \-D ] .PP @@ -180,6 +181,20 @@ and to control which part of the key file is used as passphrase. These options behave in the same way as with \fBcryptsetup\fP. .PP +The +.B reencipher +command creates a new key slot with the re-enciphered secure AES volume key. +The new key slot uses +.B PBKDF2 +as password based key derivation function. LUKS2 volumes typically default to +.B Argon2i +as password based key derivation function, +but this might cause out-of-memory errors when multiple encrypted volumes are +unlocked automatically at boot through /etc/crypttab. Because PAES +uses secure AES keys as volume keys, the security of the key derivation +function used to encrypt the volume key in the LUKS key slots is of less +relevance. +.PP .B Note: The \fBreencipher\fP command requires the CCA host library (libcsulcca.so) to be installed. For the supported environments and downloads, see: @@ -247,6 +262,7 @@ behave in the same way as with \fBcryptsetup\fP. .IR bytes ] .RB [ \-\-tries | \-T .IR number ] +.RB [ \-\-batch\-mode | \-q ] .RB [ \-\-verbose | \-V ] .RB [ \-\-debug | \-D ] .PP @@ -288,6 +304,20 @@ and .B \-\-keyfile\-size to control which part of the key file is used as passphrase. These options behave in the same way the same as with \fBcryptsetup\fP. +.PP +The +.B setkey +command creates a new key slot with the re-enciphered secure AES volume key. +The new key slot uses +.B PBKDF2 +as password based key derivation function. LUKS2 volumes typically default to +.B Argon2i +as password based key derivation function, +but this might cause out-of-memory errors when multiple encrypted volumes are +unlocked automatically at boot through /etc/crypttab. Because PAES +uses secure AES keys as volume keys, the security of the key derivation +function used to encrypt the volume key in the LUKS key slots is of less +relevance. . . . @@ -319,6 +349,9 @@ has been set (made active). When completing the staged re-enciphering, the (unbound) key slot containing the re-enciphered secure volume key becomes the active key slot and, optionally, all key slots containing the old secure volume key are removed. +.TP +.BR \-q ", " \-\-batch\-mode +Suppresses all confirmation questions. Use with care! . . . @@ -327,6 +360,9 @@ volume key are removed. .BR \-m ", " \-\-master\-key\-file\~\fIfile\-name\fP Specifies the name of a file containing the secure AES key that is set as the new volume key. +.TP +.BR \-q ", " \-\-batch\-mode +Suppresses all confirmation questions. Use with care! . . . diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c index f7098f8..8180b4a 100644 --- a/zkey/zkey-cryptsetup.c +++ b/zkey/zkey-cryptsetup.c @@ -35,6 +35,11 @@ #include "misc.h" #include "pkey.h" +/* Detect if cryptsetup 2.1 or later is available */ +#ifdef CRYPT_LOG_DEBUG_JSON +#define HAVE_CRYPT_KEYSLOT_GET_PBKDF +#endif + #define MAX_KEY_SIZE (8 * 1024 * 1024) #define MAX_PASSWORD_SIZE 512 #define KEYFILE_BUFLEN 4096 @@ -93,6 +98,7 @@ static struct zkey_cryptsetup_globals { bool inplace; bool staged; char *master_key_file; + bool batch_mode; bool debug; bool verbose; void *lib_csulcca; @@ -176,6 +182,11 @@ static struct util_opt opt_vec[] = { .command = COMMAND_REENCIPHER, }, OPT_PASSPHRASE_ENTRY(COMMAND_REENCIPHER), + { + .option = {"batch-mode", 0, NULL, 'q'}, + .desc = "Suppresses all confirmation questions. Use with care!", + .command = COMMAND_REENCIPHER, + }, /***********************************************************/ { .flags = UTIL_OPT_FLAG_SECTION, @@ -204,6 +215,11 @@ static struct util_opt opt_vec[] = { .command = COMMAND_SETKEY, }, OPT_PASSPHRASE_ENTRY(COMMAND_SETKEY), + { + .option = {"batch-mode", 0, NULL, 'q'}, + .desc = "Suppresses all confirmation questions. Use with care!", + .command = COMMAND_SETKEY, + }, /***********************************************************/ { .flags = UTIL_OPT_FLAG_SECTION, @@ -414,8 +430,13 @@ static void cryptsetup_log(int level, const char *msg, fprintf(stderr, "%s: %s", program_invocation_short_name, msg); break; case CRYPT_LOG_DEBUG: - fprintf(stderr, "%s: # %s", program_invocation_short_name, msg); + fprintf(stderr, "%s: # %s\n", program_invocation_short_name, msg); + break; +#ifdef CRYPT_DEBUG_JSON + case CRYPT_DEBUG_JSON: + fprintf(stderr, "%s\n", msg); break; +#endif default: warnx("Internal error on logging class for msg: %s", msg); break; @@ -1096,7 +1117,11 @@ static int put_vp_token(struct crypt_device *cd, int token, */ static int open_device(const char *device, struct crypt_device **cd) { - const struct crypt_pbkdf_type *pbkdf; + const struct crypt_pbkdf_type pbkdf2 = { + .type = CRYPT_KDF_PBKDF2, + .hash = "sha256", + .time_ms = 2000, + }; struct crypt_device *cdev = NULL; int rc; @@ -1128,10 +1153,14 @@ static int open_device(const char *device, struct crypt_device **cd) goto out; } - pbkdf = crypt_get_pbkdf_type(cdev); - rc = crypt_set_pbkdf_type(cdev, pbkdf); + /* + * Set PBKDF2 as default key derivation function. LUKS2 uses + * Argon2i as default, but this might cause out-of-memory errors when + * multiple LUKS2 volumes are opened automatically via /etc/crypttab + */ + rc = crypt_set_pbkdf_type(cdev, &pbkdf2); if (rc != 0) { - warnx("Failed to set the PBKDF-type for device '%s': %s", + warnx("Failed to set the PBKDF for device '%s': %s", device, strerror(-rc)); goto out; } @@ -1155,6 +1184,12 @@ static bool prompt_for_yes(void) { char str[20]; + if (g.batch_mode) { + printf("(yes implied because '--batch-mode' | '-q' option is " + "specified)\n"); + return true; + } + if (fgets(str, sizeof(str), stdin) == NULL) return false; @@ -1311,6 +1346,9 @@ static int open_keyslot(int keyslot, char **key, size_t *keysize, char **password, size_t *password_len, const char *prompt) { +#ifdef HAVE_CRYPT_KEYSLOT_GET_PBKDF + struct crypt_pbkdf_type pbkdf; +#endif char *vkey = NULL; char *pw = NULL; long long tries; @@ -1362,6 +1400,30 @@ static int open_keyslot(int keyslot, char **key, size_t *keysize, keyslot = rc; pr_verbose("Volume key obtained from key slot %d", keyslot); +#ifdef HAVE_CRYPT_KEYSLOT_GET_PBKDF + /* + * Get PBKDF of the key slot that was opened, and use its PBKDF for + * new key slots. + */ + memset(&pbkdf, 0, sizeof(pbkdf)); + rc = crypt_keyslot_get_pbkdf(g.cd, keyslot, &pbkdf); + if (rc != 0) { + warnx("Failed to get the PBKDF for key slot %d: %s", + keyslot, strerror(-rc)); + goto out; + } + + /* Reuse already benchmarked number of iterations */ + pbkdf.flags |= CRYPT_PBKDF_NO_BENCHMARK; + + rc = crypt_set_pbkdf_type(g.cd, &pbkdf); + if (rc != 0) { + warnx("Failed to set the PBKDF for new key slots: %s", + strerror(-rc)); + goto out; + } +#endif + if (key != NULL) *key = vkey; else @@ -2191,6 +2253,9 @@ int main(int argc, char *argv[]) case 'm': g.master_key_file = optarg; break; + case 'q': + g.batch_mode = true; + break; case 'D': g.debug = true; g.verbose = true; @@ -2238,7 +2303,11 @@ int main(int argc, char *argv[]) crypt_set_log_callback(NULL, cryptsetup_log, NULL); if (g.debug) - crypt_set_debug_level(-1); +#ifdef CRYPT_DEBUG_JSON + crypt_set_debug_level(CRYPT_DEBUG_JSON); +#else + crypt_set_debug_level(CRYPT_DEBUG_ALL); +#endif if (command->open_device) { if (g.pos_arg == NULL) { diff --git a/zkey/zkey.1 b/zkey/zkey.1 index 0837b27..c7cea83 100644 --- a/zkey/zkey.1 +++ b/zkey/zkey.1 @@ -92,6 +92,7 @@ key repository. .IR volume1:dmname1[,volume2:dmname2[,...]] ] .RB [ \-\-apqns | \-a .IR card1.domain1[,card2.domain2[,...]] ] +.RB [ \-\-no\-apqn\-check ] .RB [ \-\-sector-size | \-S .IR bytes ] .RB [ \-\-volume-type | \-t @@ -141,6 +142,9 @@ options. .BR validate | val .RB [ \-\-name | \-N .IR key-name ] +.RB [ \-\-apqns | \-a +.IR card1.domain1[,card2.domain2[,...]] ] +.RB [ \-\-no\-apqn\-check ] .RB [ \-\-verbose | \-V ] .PP Use the @@ -160,8 +164,21 @@ contained in the secure key repository, specify the name of the key or a pattern containing wildcards using the .B \-\-name option. When wildcards are used you must quote the value. -If neither option \fIsecure\-key\-file\fP nor option +You can also specify the +.B \-\-apqns +option to validate those secure keys which are associated with the specified +cryptographic adapters (APQNs). You can use wildcards for the APQN +specification. When wildcards are used you must quote the value. +If both option +.B \-\-name +and option +.B \-\-apqns +are specified then all secure keys contained in the key repository that match +both patterns are validated. +If neither option \fIsecure\-key\-file\fP nor options .B \-\-name +or +.B \-\-apqns are specified, then all secure keys contained in the key repository are validated. . @@ -259,7 +276,7 @@ and option .B \-\-apqns are specified then all secure keys contained in the key repository that match both patterns are re-enciphered. -If all both options are omitted, then all secure keys contained in the key +If both options are omitted, then all secure keys contained in the key repository are re-enciphered. .PP Re-enciphering a secure key contained in the secure key repository can be @@ -298,6 +315,7 @@ to be installed. For the supported environments and downloads, see: .IR volume1:dmname1[,volume2:dmname2[,...]] ] .RB [ \-\-apqns | \-a .IR card1.domain1[,card2.domain2[,...]] ] +.RB [ \-\-no\-apqn\-check ] .RB [ \-\-sector-size | \-S .IR bytes ] .RB [ \-\-volume-type | \-t @@ -409,6 +427,7 @@ secure key. .IR [+|-]volume1:dmname1[,volume2:dmname2[,...]] ] .RB [ \-\-apqns | \-a .IR [+|-]card1.domain1[,card2.domain2[,...]] ] +.RB [ \-\-no\-apqn\-check ] .RB [ \-\-sector-size | \-S .IR bytes ] .RB [ \-\-volume-type | \-t @@ -477,8 +496,8 @@ option. You cannot use wildcards. .B copy | co .RB \-\-name | \-N .IR key-name -.B \-\-new-key-name | \-w -.IR new-name +.B \-\-new\-name | \-w +.IR new-key-name .RB [ \-\-volumes | \-l .IR volume1:dmname1[,volume2:dmname2[,...]] ] .RB [ \-\-verbose | \-V ] @@ -509,6 +528,14 @@ volumes afterwards. .IR volume1[:dmname1][,volume2[:dmname2][,...]] ] .RB [ \-\-volume-type | \-t .IR type ] +.RB [ \-\-key\-file +.IR file-name ] +.RB [ \-\-keyfile\-offset +.IR bytes ] +.RB [ \-\-keyfile\-size +.IR bytes ] +.RB [ \-\-tries +.IR number ] .RB [ \-\-verbose | \-V ] . .PP @@ -527,6 +554,23 @@ name are selected. Specify the .B \-\-volume-type option to generate crypttab entries for the specified volume type only. +.P +For LUKS2 volumes, a passphrase is required. You are prompted for the +passphrase during system startup when crypttab is evaluated, unless option +.B \-\-key\-file +is specified. Option +.B \-\-tries +specifies how often a passphrase can be re-entered. When option +.B \-\-key\-file +is specified, the passphrase is read from the specified file. You can specify +options +.B \-\-keyfile\-offset +and +.B \-\-keyfile\-size +to control which part of the key file is used as passphrase. These options are +passed to the generated crypttab entries and are only available if +.B zkey +has been compiled with LUKS2 support enabled. . .SS "Generate cryptsetup commands for volumes associated with secure AES keys" . @@ -537,14 +581,24 @@ option to generate crypttab entries for the specified volume type only. .RB [ \-\-volume-type | \-t .IR type ] .RB [ \-\-run | \-r ] +.RB [ \-\-open ] +.RB [ \-\-format ] +.RB [ \-\-key\-file +.IR file-name ] +.RB [ \-\-keyfile\-offset +.IR bytes ] +.RB [ \-\-keyfile\-size +.IR bytes ] +.RB [ \-\-tries +.IR number ] .RB [ \-\-verbose | \-V ] . .PP Use the .B cryptsetup -command to generate \fBcryptsetup plainOpen\fP or \fBcryptsetup luksFormat\fP -commands for volumes that are associated with secure keys contained in the -secure key repository. Specify the +command to generate \fBcryptsetup plainOpen\fP, \fBcryptsetup luksOpen\fP, or +\fBcryptsetup luksFormat\fP commands for volumes that are associated with +secure keys contained in the secure key repository. Specify the .B \-\-volumes option to limit the list of volumes where cryptsetup commands are generated for. You can use wildcards. @@ -556,7 +610,44 @@ name are selected. Specify the option to generate cryptsetup commands for the specified volume type only. Specify the .B \-\-run -option to run the generated cryptsetup commands. +option to run the generated cryptsetup commands. Specify the +.B \-\-open +to generate \fBcryptsetup plainOpen\fP or \fBcryptsetup luksOpen\fP commands. +For the plain volume type, this is the default. Specify the +.B \-\-format +option to generate \fBcryptsetup luksFormat\fP commands. For the LUKS2 volume +type, this is the default. If specified for the plain volume type, then no +command is generated. +.P +For LUKS2 volumes, the generated \fBcryptsetup luksFormat\fP contains +option \fB\-\-pbkdf pbkdf2\fP to set \fBPBKDF2\fP as password based key +derivation function. LUKS2 volumes typically default to \fBArgon2i\fP as +password based key derivation function, but this might cause out-of-memory +errors when multiple encrypted volumes are unlocked automatically at boot +through /etc/crypttab. Because PAES uses secure AES keys as volume keys, the +security of the key derivation function used to encrypt the volume key in the +LUKS key slots is of less relevance. +.P +For LUKS2 volumes, a passphrase is required. You are prompted for the +passphrase when running the generated commands, unless option +.B \-\-key\-file +is specified. Option +.B \-\-tries +specifies how often a passphrase can be re-entered. When option +.B \-\-key\-file +is specified, the passphrase is read from the specified file. You can specify +options +.B \-\-keyfile\-offset +and +.B \-\-keyfile\-size +to control which part of the key file is used as passphrase. These options are +only available if +.B zkey +has been compiled with LUKS2 support enabled. To avoid cryptsetup confirmation +questions, you can specify the +.B \-\-batch\-mode +option. These options are passed to the generated command(s) and behave in the +same way as with \fBcryptsetup\fP. . . . @@ -603,8 +694,14 @@ Specifies a comma-separated list of cryptographic adapters in CCA coprocessor mode (APQN) which are associated with the secure AES key in the repository. Each APQN association specifies a card and domain number separated by a period (like lszcrypt displays it). When at least one APQN is specified, -then the first one is used to generate the key. If no APQNs are specified, -then an APQN is selected automatically. All specified APQNs must be online. +then the first online APQN is used to generate the key. If no APQNs are +specified, then an APQN is selected automatically. All specified APQNs must be +online, unless the \fB\-\-no\-apqn\-check\fP option is specified. +This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-\-no\-apqn\-check +Do not check if the specified APQNs are available. Use this option to +associate APQNs with a secure AES key that are currently not available. This option is only used for secure keys contained in the secure key repository. .TP .BR \-S ", " \-\-sector-size\~\fIbytes\fP @@ -631,6 +728,19 @@ Specifies the name of the secure key in the secure key repository. You can use wildcards to select multiple secure keys in the secure key repository. When wildcards are used you must quote the value. This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-a ", " \-\-apqns\~\fIcard1.domain1[,card2.domain2[,...]]\fP +Specifies a comma-separated list of cryptographic adapters in CCA +coprocessor mode (APQNs). You can use wildcards in the APQN specification. +All secure keys contained in the secure key repository +which are associated with the specified APQNs are validated. +Each APQN specifies a card and domain number separated by a period (like +lszcrypt displays it). +This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-\-no\-apqn\-check +Do not check if the associated APQNs are available. +This option is only used for secure keys contained in the secure key repository. . . . @@ -714,7 +824,13 @@ This option is only used for secure keys contained in the secure key repository. Specifies a comma-separated list of cryptographic adapters in CCA coprocessor mode (APQN) which are associated with the secure AES key in the repository. Each APQN association specifies a card and domain number separated -by a period (like lszcrypt displays it). All specified APQNs must be online. +by a period (like lszcrypt displays it). All specified APQNs must be online, +unless option \fB\-\-no\-apqn\-check\fP is specified. +This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-\-no\-apqn\-check +Do not check if the specified APQNs are available. Use this option to +associate APQNs with a secure AES key that are currently not available. This option is only used for secure keys contained in the secure key repository. .TP .BR \-S ", " \-\-sector-size\~\fIbytes\fP @@ -833,7 +949,13 @@ To remove an APQN from the associated APQNs, prefix the APQN with a \fI-\fP. To set (replace) the APQN association do not specify a prefix. You cannot mix \fI+\fP and \fI-\fP in one specification. You can either add or remove (or set) the associations with one command. -All APQNs being added or set (replaced) must be online. +All APQNs being added or set (replaced) must be online, unless option +\fB\-\-no\-apqn\-check\fP is specified. +This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-\-no\-apqn\-check +Do not check if the specified APQNs are available. Use this option to +associate APQNs with a secure AES key that are currently not available. This option is only used for secure keys contained in the secure key repository. .TP .BR \-S ", " \-\-sector-size\~\fIbytes\fP @@ -908,6 +1030,46 @@ This option is only available if .B zkey has been compiled with LUKS2 support enabled. This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-\-key\-file\~\fIfile\-name\fP +Reads the passphrase from the specified file. If this option is omitted, then +you are prompted to enter the passphrase interactively during system startup. +This option is passed to the generated crypttab entries for LUKS2 volumes, and +is only available if +.B zkey +has been compiled with LUKS2 support enabled. +.TP +.BR \-\-keyfile\-offset\~\fIbytes\fP +Specifies the number of bytes to skip before starting to read in the file +specified with option \fB\-\-key\-file\fP. If omitted, the file is read +from the beginning. When option \fB\-\-key\-file\fP is not specified, this +option is ignored. This option is passed to the generated crypttab entries +for LUKS2 volumes, and is only available if +.B zkey +has been compiled with LUKS2 support enabled. Not all distributions support the +.B keyfile-offset +option in crypttab entries. +.TP +.BR \-\-keyfile\-size\~\fIbytes\fP +Specifies the number of bytes to be read from the beginning of the file +specified with option \fB\-\-key\-file\fP. If omitted, the file is read +until the end. When \fB\-\-keyfile\-offset\fP is also specified, reading starts +at the offset. When option \fB\-\-key\-file\fP is not specified, this option is +ignored. This option is passed to the generated crypttab entries for LUKS2 +volumes, and is only available if +.B zkey +has been compiled with LUKS2 support enabled. Not all distributions support the +.B keyfile-size +option in crypttab entries. +.TP +.BR \-\-tries\~\fInumber\fP +Specifies how often the interactive input of the passphrase can be re-entered +during system startup. The default is 3 times. When option \fB\-\-key\-file\fP +is specified, this option is ignored, and the passphrase is read only once from +the file. This option is passed to the generated crypttab entries for LUKS2 +volumes, and is only available if +.B zkey +has been compiled with LUKS2 support enabled. . . . @@ -937,6 +1099,64 @@ This option is only used for secure keys contained in the secure key repository. Runs the generated cryptsetup commands. When one of the cryptsetup command fail, no further cryptsetup commands are run, and zkey ends with an error. This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-\-open +Generates \fBcryptsetup luksOpen\fP or \fBcryptsetup plainOpen\fP commands. +For a plain volume type, this is the default. This option can not be specified +together with the +.BR \-\-format +option, and is only available if +.B zkey +has been compiled with LUKS2 support enabled. +.TP +.BR \-\-format +Generates \fBcryptsetup luksFormat\fP commands. For a LUKS2 volume type, this +is the default. If specified for a plain volume type, then no command is +generated. This option can not be specified together with the +.BR \-\-open +option, and is only available if +.B zkey +has been compiled with LUKS2 support enabled. +.TP +.BR \-\-key\-file\~\fIfile\-name\fP +Reads the passphrase from the specified file. If this option is omitted, +or if the file\-name is \fI-\fP (a dash), then you are prompted to enter the +passphrase interactively. This option is passed to the generated command(s) +for LUKS2 volumes, and is only available if +.B zkey +has been compiled with LUKS2 support enabled. +.TP +.BR \-\-keyfile\-offset\~\fIbytes\fP +Specifies the number of bytes to skip before starting to read in the file +specified with option \fB\-\-key\-file\fP. If omitted, the file is read +from the beginning. When option \fB\-\-key\-file\fP is not specified, this +option is ignored. This option is passed to the generated command(s) +for LUKS2 volumes, and is only available if +.B zkey +has been compiled with LUKS2 support enabled. +.TP +.BR \-\-keyfile\-size\~\fIbytes\fP +Specifies the number of bytes to be read from the beginning of the file +specified with option \fB\-\-key\-file\fP. If omitted, the file is read +until the end. When \fB\-\-keyfile\-offset\fP is also specified, reading starts +at the offset. When option \fB\-\-key\-file\fP is not specified, this option is +ignored. This option is passed to the generated command(s) for LUKS2 volumes, +and is only available if +.B zkey +has been compiled with LUKS2 support enabled. +.TP +.BR \-\-tries\~\fInumber\fP +Specifies how often the interactive input of the passphrase can be re-entered. +The default is 3 times. When option \fB\-\-key\-file\fP is specified, this +option is ignored, and the passphrase is read only once from the file. +This option is passed to the generated command(s) for LUKS2 volumes, and is +only available if +.B zkey +has been compiled with LUKS2 support enabled. +.TP +.BR \-q ", " \-\-batch\-mode +Suppress cryptsetup confirmation questions. This option is passed to the generated +cryptsetup command(s). . . . diff --git a/zkey/zkey.c b/zkey/zkey.c index 3a8909a..2ecbb90 100644 --- a/zkey/zkey.c +++ b/zkey/zkey.c @@ -67,11 +67,19 @@ static struct zkey_globals { char *description; char *volumes; char *apqns; + bool noapqncheck; long int sector_size; char *volume_type; char *newname; bool run; + bool batch_mode; + char *keyfile; + long long keyfile_offset; + long long keyfile_size; + long long tries; bool force; + bool open; + bool format; void *lib_csulcca; t_CSNBKTC dll_CSNBKTC; int pkey_fd; @@ -102,6 +110,14 @@ static struct zkey_globals { #define ENVVAR_ZKEY_REPOSITORY "ZKEY_REPOSITORY" #define DEFAULT_KEYSTORE "/etc/zkey/repository" +#define OPT_CRYPTSETUP_KEYFILE 256 +#define OPT_CRYPTSETUP_KEYFILE_OFFSET 257 +#define OPT_CRYPTSETUP_KEYFILE_SIZE 258 +#define OPT_CRYPTSETUP_TRIES 259 +#define OPT_CRYPTSETUP_OPEN 260 +#define OPT_CRYPTSETUP_FORMAT 261 +#define OPT_NO_APQN_CHECK 262 + /* * Configuration of command line options */ @@ -172,6 +188,14 @@ static struct util_opt opt_vec[] = { "repository", .command = COMMAND_GENERATE, }, + { + .option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK}, + .desc = "Do not check if the specified APQN(s) are available. " + "Use this option to associate APQN(s) with a secure " + "AES key that are currently not available.", + .command = COMMAND_GENERATE, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, { .option = { "sector-size", required_argument, NULL, 'S'}, .argument = "bytes", @@ -281,6 +305,12 @@ static struct util_opt opt_vec[] = { "associated with specific crypto cards", .command = COMMAND_VALIDATE, }, + { + .option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK}, + .desc = "Do not check if the associated APQN(s) are available", + .command = COMMAND_VALIDATE, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, /***********************************************************/ { .flags = UTIL_OPT_FLAG_SECTION, @@ -316,6 +346,14 @@ static struct util_opt opt_vec[] = { "repository", .command = COMMAND_IMPORT, }, + { + .option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK}, + .desc = "Do not check if the specified APQN(s) are available. " + "Use this option to associate APQN(s) with a secure " + "AES key that are currently not available.", + .command = COMMAND_IMPORT, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, { .option = { "sector-size", required_argument, NULL, 'S'}, .argument = "512|4096", @@ -453,6 +491,14 @@ static struct util_opt opt_vec[] = { "specify '-CARD.DOMAIN[,...]'", .command = COMMAND_CHANGE, }, + { + .option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK}, + .desc = "Do not check if the specified APQN(s) are available. " + "Use this option to associate APQN(s) with a secure " + "AES key that are currently not available.", + .command = COMMAND_CHANGE, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, { .option = { "sector-size", required_argument, NULL, 'S'}, .argument = "0|512|4096", @@ -547,6 +593,53 @@ static struct util_opt opt_vec[] = { "entry is to be generated", .command = COMMAND_CRYPTTAB, }, + { + .option = {"key-file", required_argument, NULL, + OPT_CRYPTSETUP_KEYFILE}, + .argument = "FILE-NAME", + .desc = "Read the passphrase from the specified file. " + "The specified file is passed to the generated " + "crypttab entry for LUKS2 volumes", + .command = COMMAND_CRYPTTAB, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = {"keyfile-offset", required_argument, NULL, + OPT_CRYPTSETUP_KEYFILE_OFFSET}, + .argument = "BYTES", + .desc = "Specifies the number of bytes to skip in the file " + "specified with option '--key-file'. " + "The specified offset is passed to the generated " + "crypttab entry for LUKS2 volumes. Not all " + "distributions support the 'keyfile-offset' option in " + "crypttab entries", + .command = COMMAND_CRYPTTAB, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = {"keyfile-size", required_argument, NULL, + OPT_CRYPTSETUP_KEYFILE_SIZE}, + .argument = "BYTES", + .desc = "Specifies the number of bytes to read from the file " + "specified with option '--key-file'. " + "The specified size is passed to the generated " + "crypttab entry for LUKS2 volumes. Not all " + "distributions support the 'keyfile-size' option in " + "crypttab entries", + .command = COMMAND_CRYPTTAB, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = {"tries", required_argument, NULL, + OPT_CRYPTSETUP_TRIES}, + .argument = "NUMBER", + .desc = "Specifies how often the interactive input of the " + "passphrase can be retried. " + "The specified number is passed to the generated " + "crypttab entry for LUKS2 volumes", + .command = COMMAND_CRYPTTAB, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, #endif /***********************************************************/ { @@ -582,6 +675,75 @@ static struct util_opt opt_vec[] = { .desc = "Runs the generated cryptsetup command", .command = COMMAND_CRYPTSETUP, }, +#ifdef HAVE_LUKS2_SUPPORT + { + .option = {"key-file", required_argument, NULL, + OPT_CRYPTSETUP_KEYFILE}, + .argument = "FILE-NAME", + .desc = "Read the passphrase from the specified file. " + "This option is passed to the generated command(s) for " + "LUKS2 volumes", + .command = COMMAND_CRYPTSETUP, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = {"keyfile-offset", required_argument, NULL, + OPT_CRYPTSETUP_KEYFILE_OFFSET}, + .argument = "BYTES", + .desc = "Specifies the number of bytes to skip in the file " + "specified with option '--key-file'. " + "This option is passed to the generated command(s) for " + "LUKS2 volumes", + .command = COMMAND_CRYPTSETUP, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = {"keyfile-size", required_argument, NULL, + OPT_CRYPTSETUP_KEYFILE_SIZE}, + .argument = "BYTES", + .desc = "Specifies the number of bytes to read from the file " + "specified with option '--key-file'. " + "This option is passed to the generated command(s) for " + "LUKS2 volumes", + .command = COMMAND_CRYPTSETUP, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = {"tries", required_argument, NULL, + OPT_CRYPTSETUP_TRIES}, + .argument = "NUMBER", + .desc = "Specifies how often the interactive input of the " + "passphrase can be retried. " + "This option is passed to the generated command(s) for " + "LUKS2 volumes", + .command = COMMAND_CRYPTSETUP, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, +#endif + { + .option = {"batch-mode", 0, NULL, 'q'}, + .desc = "Suppresses cryptsetup confirmation questions. " + "This option is passed to the generated cryptsetup " + "command(s)", + .command = COMMAND_CRYPTSETUP, + }, +#ifdef HAVE_LUKS2_SUPPORT + { + .option = {"open", 0, NULL, OPT_CRYPTSETUP_OPEN}, + .desc = "Generates luksOpen or plainOpen commands. For the " + "plain volume type, this is the default", + .command = COMMAND_CRYPTSETUP, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = {"format", 0, NULL, OPT_CRYPTSETUP_FORMAT}, + .desc = "Generates luksFormat commands. For the LUKS2 volume " + "type, this is the default. If specified for the " + "plain volume type, then no command is generated", + .command = COMMAND_CRYPTSETUP, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, +#endif /***********************************************************/ { .flags = UTIL_OPT_FLAG_SECTION, @@ -883,8 +1045,9 @@ static int command_generate_repository(void) g.sector_size = 0; rc = keystore_generate_key(g.keystore, g.name, g.description, g.volumes, - g.apqns, g.sector_size, g.keybits, g.xts, - g.clearkeyfile, g.volume_type, g.pkey_fd); + g.apqns, g.noapqncheck, g.sector_size, + g.keybits, g.xts, g.clearkeyfile, + g.volume_type, g.pkey_fd); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -903,6 +1066,12 @@ static int command_generate(void) util_prg_print_parse_error(); return EXIT_FAILURE; } + if (g.apqns == NULL && g.noapqncheck) { + warnx("Option '--noapqncheck' is only valid together with " + "the '--apqns|-a' option"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } if (g.name != NULL) return command_generate_repository(); if (g.pos_arg != NULL) { @@ -918,6 +1087,12 @@ static int command_generate(void) util_prg_print_parse_error(); return EXIT_FAILURE; } + if (g.noapqncheck) { + warnx("Option '--noapqncheck' is not valid for " + "generating a key outside of the repository"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } if (g.description != NULL) { warnx("Option '--description|-d' is not valid for " "generating a key outside of the repository"); @@ -1141,6 +1316,12 @@ static int command_validate_file(void) util_prg_print_parse_error(); return EXIT_FAILURE; } + if (g.noapqncheck) { + warnx("Option '--noapqncheck' is not valid for " + "validating a key outside of the repository"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } /* Read the secure key to be re-enciphered */ secure_key = read_secure_key(g.pos_arg, &secure_key_size, g.verbose); @@ -1194,7 +1375,15 @@ static int command_validate_repository(void) { int rc; - rc = keystore_validate_key(g.keystore, g.name, g.apqns, g.pkey_fd); + if (g.apqns == NULL && g.noapqncheck) { + warnx("Option '--noapqncheck' is only valid together with " + "the '--apqns|-a' option"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + + rc = keystore_validate_key(g.keystore, g.name, g.apqns, g.noapqncheck, + g.pkey_fd); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1231,9 +1420,16 @@ static int command_import(void) if (g.sector_size < 0) g.sector_size = 0; + if (g.apqns == NULL && g.noapqncheck) { + warnx("Option '--noapqncheck' is only valid together with " + "the '--apqns|-a' option"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + rc = keystore_import_key(g.keystore, g.name, g.description, g.volumes, - g.apqns, g.sector_size, g.pos_arg, - g.volume_type); + g.apqns, g.noapqncheck, g.sector_size, + g.pos_arg, g.volume_type); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1304,9 +1500,16 @@ static int command_change(void) misc_print_required_parm("--name/-N"); return EXIT_FAILURE; } + if (g.apqns == NULL && g.noapqncheck) { + warnx("Option '--noapqncheck' is only valid together with " + "the '--apqns|-a' option"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } rc = keystore_change_key(g.keystore, g.name, g.description, g.volumes, - g.apqns, g.sector_size, g.volume_type); + g.apqns, g.noapqncheck, g.sector_size, + g.volume_type); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1366,7 +1569,8 @@ static int command_crypttab(void) { int rc; - rc = keystore_crypttab(g.keystore, g.volumes, g.volume_type); + rc = keystore_crypttab(g.keystore, g.volumes, g.volume_type, g.keyfile, + g.keyfile_offset, g.keyfile_size, g.tries); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1380,7 +1584,16 @@ static int command_cryptsetup(void) { int rc; - rc = keystore_cryptsetup(g.keystore, g.volumes, g.run, g.volume_type); + if (g.open && g.format) { + warnx("Either '--open' or '--format' can be specified, but " + "not both"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + + rc = keystore_cryptsetup(g.keystore, g.volumes, g.run, g.volume_type, + g.keyfile, g.keyfile_offset, g.keyfile_size, + g.tries, g.batch_mode, g.open, g.format); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1546,6 +1759,9 @@ int main(int argc, char *argv[]) case 'a': g.apqns = optarg; break; + case OPT_NO_APQN_CHECK: + g.noapqncheck = 1; + break; case 'S': g.sector_size = strtol(optarg, &endp, 0); if (*optarg == '\0' || *endp != '\0' || @@ -1574,6 +1790,56 @@ int main(int argc, char *argv[]) case 'V': g.verbose = 1; break; +#ifdef HAVE_LUKS2_SUPPORT + case OPT_CRYPTSETUP_KEYFILE: + g.keyfile = optarg; + break; + case OPT_CRYPTSETUP_KEYFILE_OFFSET: + g.keyfile_offset = strtoll(optarg, &endp, 0); + if (*optarg == '\0' || *endp != '\0' || + g.keyfile_offset < 0 || + (g.keyfile_offset == LLONG_MAX && + errno == ERANGE)) { + warnx("Invalid value for '--keyfile-offset': " + "'%s'", optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; + case OPT_CRYPTSETUP_KEYFILE_SIZE: + g.keyfile_size = strtoll(optarg, &endp, 0); + if (*optarg == '\0' || *endp != '\0' || + g.keyfile_size <= 0 || + (g.keyfile_size == LLONG_MAX && errno == ERANGE)) { + warnx("Invalid value for '--keyfile-size': " + "'%s'", optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; + case OPT_CRYPTSETUP_TRIES: + g.tries = strtoll(optarg, &endp, 0); + if (*optarg == '\0' || *endp != '\0' || + g.tries <= 0 || + (g.tries == LLONG_MAX && errno == ERANGE)) { + warnx("Invalid value for '--tries': '%s'", + optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; +#endif + case 'q': + g.batch_mode = 1; + break; +#ifdef HAVE_LUKS2_SUPPORT + case OPT_CRYPTSETUP_OPEN: + g.open = 1; + break; + case OPT_CRYPTSETUP_FORMAT: + g.format = 1; + break; +#endif case 'h': print_help(command); return EXIT_SUCCESS; -- 2.21.3 From 86c3031204f6e52455bba1dfc89d619ce26debe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 7 Nov 2019 13:16:36 +0100 Subject: [PATCH 34/56] zkey: check master key consistency (#1753153) Description: Enhances the zkey tool to perform a cross check whether the APQNs associated with a secure key have the same master key. Display the master key verification pattern of a secure key during the zkey validate command. This helps to better identify which master key is the correct one, in case of master key inconsistencies. Select an appropriate APQN when re-enciphering a secure key. Re-enciphering is done using the CCA host library. Special handling is required to select an appropriate APQN for use with the CCA host library. Upstream-ID: a6507b330d05a3033837768a54ce9959c2a84870 Upstream-ID: 8d1a7fecd04e620138a3828d58900d35a396c33a Upstream-ID: 7e6d782699adf681eaaf63fd43d65e6c0627ee6e Upstream-ID: 819513ab385fa37306cff3f9ac32e3be7f225793 Upstream-ID: 5dbe73403f455cb880aa256598b9527a465beb39 Upstream-ID: 99ab2db6ad3f79c4e3a7287f4c724d1c635ebd2f Upstream-ID: 7d4c8c27813c792237f92db5038b9dc4bea2fa7c Upstream-ID: 94227b44d5e5538b221b7f33ae6e627b86d07593 Upstream-ID: 139dff3525e7140a1d2e6aefdc1d35930d05c6d2 Upstream-ID: 95c7258ea783c5bd6aa12fc0e3d5fbe65647af03 Upstream-ID: 696e8458f0c117e3a084e1a083de89ec19baaff9 Upstream-ID: a84d1c5d58fa4a0c9e087357eec009803ea06ef2 Upstream-ID: bf8872e94a2dc4810df388d1539560b00b1acf6e Upstream-ID: 625b81130ab2c9d184aaede2749f1fd776f51062 Upstream-ID: bfc3dd018c4f0cc17f8463d8bd6be16aab8de4a4 Upstream-ID: b32c0314746ddee69e59f892f105acd720d06452 Upstream-ID: ea7cc9ea606dd879e4cdfae06a6f13d8fa3afff4 Upstream-ID: c2244a57950f4eb35e3209151dcf48de66828df1 Upstream-ID: a5b58038a0dbf1c3eb202a6933265f0d2e57e130 Upstream-ID: 7f8e31e8619b32297b432a4882d78af79de37a58 Upstream-ID: d854aed4b8154e7420def8749db2106a049dd80a Upstream-ID: 0b4cbf00412f27456d28ff7f86ec5335a39e3416 Upstream-ID: 016a0a56fcb3dd0bf8bed693e5d64873f6288995 Upstream-ID: 1091b0bf65328aff94055a2e333aff2c737b6744 Upstream-ID: 552a915465301b768268cddc7ccb65a6d167e432 Upstream-ID: a0ed6709cf3c62b1fc9dfa28358e70215c1da55a --- zkey/Makefile | 12 +- zkey/cca.c | 628 +++++++++++++++++++++++++++++++++++ zkey/cca.h | 95 ++++++ zkey/keystore.c | 306 +++++++++-------- zkey/keystore.h | 3 +- zkey/pkey.c | 165 ++-------- zkey/pkey.h | 21 +- zkey/properties.c | 28 +- zkey/properties.h | 3 + zkey/utils.c | 725 +++++++++++++++++++++++++++++++++++++++++ zkey/utils.h | 54 +++ zkey/zkey-cryptsetup.1 | 49 ++- zkey/zkey-cryptsetup.c | 198 +++++++++-- zkey/zkey.c | 122 +++++-- 14 files changed, 2034 insertions(+), 375 deletions(-) create mode 100644 zkey/cca.c create mode 100644 zkey/cca.h create mode 100644 zkey/utils.c create mode 100644 zkey/utils.h diff --git a/zkey/Makefile b/zkey/Makefile index a44b14b..f92de4f 100644 --- a/zkey/Makefile +++ b/zkey/Makefile @@ -64,18 +64,20 @@ zkey-cryptsetup-skip-jsonc: all: $(BUILD_TARGETS) -zkey.o: zkey.c pkey.h misc.h +zkey.o: zkey.c pkey.h cca.h misc.h pkey.o: pkey.c pkey.h +cca.o: cca.c cca.h pkey.h utils.h +utils.o: utils.h properties.o: check-dep-zkey properties.c properties.h -keystore.o: keystore.c keystore.h properties.h -zkey-cryptsetup.o: check-dep-zkey-cryptsetup zkey-cryptsetup.c pkey.h misc.h +keystore.o: keystore.c keystore.h properties.h pkey.h cca.h utils.h +zkey-cryptsetup.o: check-dep-zkey-cryptsetup zkey-cryptsetup.c pkey.h cca.h misc.h zkey: LDLIBS = -ldl -lcrypto -zkey: zkey.o pkey.o properties.o keystore.o $(libs) +zkey: zkey.o pkey.o cca.o properties.o keystore.o utils.o $(libs) $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ zkey-cryptsetup: LDLIBS = -ldl -lcryptsetup -ljson-c -zkey-cryptsetup: zkey-cryptsetup.o pkey.o $(libs) +zkey-cryptsetup: zkey-cryptsetup.o pkey.o cca.o utils.o $(libs) $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ install-common: diff --git a/zkey/cca.c b/zkey/cca.c new file mode 100644 index 0000000..2669c75 --- /dev/null +++ b/zkey/cca.c @@ -0,0 +1,628 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * Copyright IBM Corp. 2019 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_base.h" +#include "lib/util_libc.h" +#include "lib/util_panic.h" + +#include "cca.h" +#include "pkey.h" +#include "utils.h" + +#define pr_verbose(verbose, fmt...) do { \ + if (verbose) \ + warnx(fmt); \ + } while (0) + +/* + * Definitions for the CCA library + */ +#define CCA_LIBRARY_NAME "libcsulcca.so" +#define CCA_WEB_PAGE "http://www.ibm.com/security/cryptocards" +#define CCA_DOMAIN_ENVAR "CSU_DEFAULT_DOMAIN" +#define CCA_ADAPTER_ENVAR "CSU_DEFAULT_ADAPTER" + +/** + * Prints CCA return and reason code information for certain known CCA + * error situations. + * + * @param return_code the CCA return code + * @param reason_code the CCA reason code + */ +static void print_CCA_error(int return_code, int reason_code) +{ + switch (return_code) { + case 8: + switch (reason_code) { + case 48: + warnx("The secure key has a CCA master key " + "verification pattern that is not valid"); + break; + } + break; + case 12: + switch (reason_code) { + case 764: + warnx("The CCA master key is not loaded and " + "therefore a secure key cannot be enciphered"); + break; + } + break; + } +} + +/** + * Returns the version, release and modification number of the used CCA library. + * + * @param[in] cca the CCA library structure + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error + */ +static int get_cca_version(struct cca_lib *cca, bool verbose) +{ + unsigned char exit_data[4] = { 0, }; + unsigned char version_data[20]; + long return_code, reason_code; + long version_data_length; + long exit_data_len = 0; + char date[20]; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + + memset(version_data, 0, sizeof(version_data)); + version_data_length = sizeof(version_data); + cca->dll_CSUACFV(&return_code, &reason_code, + &exit_data_len, exit_data, + &version_data_length, version_data); + pr_verbose(verbose, "CSUACFV (Cryptographic Facility Version) " + "returned: return_code: %ld, reason_code: %ld", return_code, + reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + + version_data[sizeof(version_data) - 1] = '\0'; + pr_verbose(verbose, "CCA Version string: %s", version_data); + + if (sscanf((char *)version_data, "%u.%u.%uz%s", &cca->version.ver, + &cca->version.rel, &cca->version.mod, date) != 4) { + warnx("CCA library version is invalid: %s", version_data); + return -EINVAL; + } + + return 0; +} + +/** + * Loads the CCA library and provides the entry point of the CSNBKTC function. + * + * @param[out] cca on return this contains the address of the CCA + * library and certain CCA symbols. dlclose() should + * be used to free the library when no longer needed. + * @param verbose if true, verbose messages are printed + * + * @returns 0 on success, -ELIBACC in case of library load errors + */ +int load_cca_library(struct cca_lib *cca, bool verbose) +{ + util_assert(cca != NULL, "Internal error: caa is NULL"); + + /* Load the CCA library */ + cca->lib_csulcca = dlopen(CCA_LIBRARY_NAME, RTLD_GLOBAL | RTLD_NOW); + if (cca->lib_csulcca == NULL) { + pr_verbose(verbose, "%s", dlerror()); + warnx("The command requires the IBM CCA Host Libraries and " + "Tools.\nFor the supported environments and downloads, " + "see:\n%s", CCA_WEB_PAGE); + return -ELIBACC; + } + + /* Get the Cryptographic Facility Version function */ + cca->dll_CSUACFV = (t_CSUACFV)dlsym(cca->lib_csulcca, "CSUACFV"); + + /* Get the Key Token Change function */ + cca->dll_CSNBKTC = (t_CSNBKTC)dlsym(cca->lib_csulcca, "CSNBKTC"); + + /* Get the Cryptographic Facility Query function */ + cca->dll_CSUACFQ = (t_CSUACFQ)dlsym(cca->lib_csulcca, "CSUACFQ"); + + /* Get the Cryptographic Resource Allocate function */ + cca->dll_CSUACRA = (t_CSUACRA)dlsym(cca->lib_csulcca, "CSUACRA"); + + /* Cryptographic Resource Deallocate function */ + cca->dll_CSUACRD = (t_CSUACRD)dlsym(cca->lib_csulcca, "CSUACRD"); + + if (cca->dll_CSUACFV == NULL || + cca->dll_CSNBKTC == NULL || + cca->dll_CSUACFQ == NULL || + cca->dll_CSUACRA == NULL || + cca->dll_CSUACRD == NULL) { + pr_verbose(verbose, "%s", dlerror()); + warnx("The command requires the IBM CCA Host Libraries and " + "Tools.\nFor the supported environments and downloads, " + "see:\n%s", CCA_WEB_PAGE); + dlclose(cca->lib_csulcca); + cca->lib_csulcca = NULL; + return -ELIBACC; + } + + pr_verbose(verbose, "CCA library '%s' has been loaded successfully", + CCA_LIBRARY_NAME); + + return get_cca_version(cca, verbose); +} + +/** + * Re-enciphers a secure key. + * + * @param[in] cca the CCA libraray structure + * @param[in] secure_key a buffer containing the secure key + * @param[in] secure_key_size the size of the secure key + * @param[in] method the re-enciphering method. METHOD_OLD_TO_CURRENT + * or METHOD_CURRENT_TO_NEW. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, -EIO in case of an error + */ +int key_token_change(struct cca_lib *cca, + u8 *secure_key, unsigned int secure_key_size, + char *method, bool verbose) +{ + long exit_data_len = 0, rule_array_count; + unsigned char rule_array[2 * 8] = { 0, }; + unsigned char exit_data[4] = { 0, }; + long return_code, reason_code; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + util_assert(secure_key_size > 0, + "Internal error: secure_key_size is 0"); + util_assert(method != NULL, "Internal error: method is NULL"); + + memcpy(rule_array, method, 8); + memcpy(rule_array + 8, "AES ", 8); + rule_array_count = 2; + + cca->dll_CSNBKTC(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + secure_key); + + pr_verbose(verbose, "CSNBKTC (Key Token Change) with '%s' returned: " + "return_code: %ld, reason_code: %ld", method, return_code, + reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + + if (secure_key_size == 2 * SECURE_KEY_SIZE) { + cca->dll_CSNBKTC(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + secure_key + SECURE_KEY_SIZE); + + pr_verbose(verbose, "CSNBKTC (Key Token Change) with '%s' " + "returned: return_code: %ld, reason_code: %ld", + method, return_code, reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + } + return 0; +} + +/** + * Queries the number of adapters known by the CCA host library + * + * @param[in] cca the CCA library structure + * @param[out] adapters the number of adapters + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. + */ +static int get_number_of_cca_adapters(struct cca_lib *cca, + unsigned int *adapters, bool verbose) +{ + long exit_data_len = 0, rule_array_count, verb_data_length = 0; + unsigned char rule_array[16 * 8] = { 0, }; + unsigned char exit_data[4] = { 0, }; + long return_code, reason_code; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + util_assert(adapters != NULL, "Internal error: adapters is NULL"); + + memset(rule_array, 0, sizeof(rule_array)); + memcpy(rule_array, "STATCRD2", 8); + rule_array_count = 1; + + cca->dll_CSUACFQ(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &verb_data_length, NULL); + + pr_verbose(verbose, "CSUACFQ (Cryptographic Facility Query) returned: " + "return_code: %ld, reason_code: %ld", return_code, + reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + + rule_array[8] = '\0'; + if (sscanf((char *)rule_array, "%u", adapters) != 1) { + pr_verbose(verbose, "Unparsable output: %s", rule_array); + return -EIO; + } + + pr_verbose(verbose, "Number of CCA adapters: %u", *adapters); + return 0; +} + +/** + * Allocate a specific CCA adapter. + * + * @param[in] cca the CCA library structure + * @param[in] adapter the adapter number, starting at 1. If 0 is + * specified, then the AUTOSELECT option is + * enabled. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. -ENODEV is + * returned if the adapter is not available. + */ +static int allocate_cca_adapter(struct cca_lib *cca, unsigned int adapter, + bool verbose) +{ + long exit_data_len = 0, rule_array_count; + unsigned char rule_array[8] = { 0, }; + unsigned char exit_data[4] = { 0, }; + long return_code, reason_code; + char res_name[9]; + long res_name_len; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + + if (adapter > 0) + memcpy(rule_array, "DEVICE ", 8); + else + memcpy(rule_array, "DEV-ANY ", 8); + rule_array_count = 1; + + sprintf(res_name, "CRP%02d", adapter); + res_name_len = strlen(res_name); + + cca->dll_CSUACRA(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &res_name_len, (unsigned char *)res_name); + + pr_verbose(verbose, "CSUACRA (Cryptographic Resource Allocate) " + "returned: return_code: %ld, reason_code: %ld", return_code, + reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -ENODEV; + } + + pr_verbose(verbose, "Adapter %u (%s) allocated", adapter, res_name); + return 0; +} + +/** + * Deallocate a specific CCA adapter. + * + * @param[in] cca the CCA library structure + * @param[in] adapter the adapter number, starting at 1. If 0 is + * specified, then the AUTOSELECT option is + * disabled. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. -ENODEV is + * returned if the adapter is not available. + */ +static int deallocate_cca_adapter(struct cca_lib *cca, unsigned int adapter, + bool verbose) +{ + long exit_data_len = 0, rule_array_count; + unsigned char rule_array[8] = { 0, }; + unsigned char exit_data[4] = { 0, }; + long return_code, reason_code; + char res_name[9]; + long res_name_len; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + + if (adapter > 0) + memcpy(rule_array, "DEVICE ", 8); + else + memcpy(rule_array, "DEV-ANY ", 8); + rule_array_count = 1; + + sprintf(res_name, "CRP%02d", adapter); + res_name_len = strlen(res_name); + + cca->dll_CSUACRD(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &res_name_len, (unsigned char *)res_name); + + pr_verbose(verbose, "CSUACRD (Cryptographic Resource Deallocate) " + "returned: return_code: %ld, reason_code: %ld", return_code, + reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -ENODEV; + } + + pr_verbose(verbose, "Adapter %u (%s) deallocated", adapter, res_name); + return 0; +} + +/** + * Queries the serial number of the current CCA adapter + * + * @param[in] cca the CCA library structure + * @param[out] serialnr the buffer where the serial number is returned + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. + */ +static int get_cca_adapter_serialnr(struct cca_lib *cca, char serialnr[9], + bool verbose) +{ + long exit_data_len = 0, rule_array_count, verb_data_length = 0; + unsigned char rule_array[16 * 8] = { 0, }; + unsigned char exit_data[4] = { 0, }; + long return_code, reason_code; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + + memset(rule_array, 0, sizeof(rule_array)); + memcpy(rule_array, "STATCRD2", 8); + rule_array_count = 1; + + cca->dll_CSUACFQ(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &verb_data_length, NULL); + + pr_verbose(verbose, "CSUACFQ (Cryptographic Facility Query) returned: " + "return_code: %ld, reason_code: %ld", return_code, + reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + + memcpy(serialnr, rule_array+14*8, 8); + serialnr[8] = '\0'; + + pr_verbose(verbose, "Serial number of CCA adapter: %s", serialnr); + return 0; +} + +/** + * Selects the specified APQN to be used for the CCA host library. + * + * @param[in] cca the CCA library structure + * @param[in] card the card number + * @param[in] domain the domain number + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. -ENOTSUP is + * returned when the serialnr sysfs attribute is not available, + * because the zcrypt kernel module is on an older level. -ENODEV is + * returned if the APQN is not available. + */ +int select_cca_adapter(struct cca_lib *cca, int card, int domain, bool verbose) +{ + unsigned int adapters, adapter; + char adapter_serialnr[9]; + char apqn_serialnr[9]; + char temp[10]; + int rc, found = 0; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + + pr_verbose(verbose, "Select %02x.%04x for the CCA host library", card, + domain); + + rc = sysfs_get_serialnr(card, apqn_serialnr, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to get the serial number: %s", + strerror(-rc)); + return rc; + } + + sprintf(temp, "%u", domain); + if (setenv(CCA_DOMAIN_ENVAR, temp, 1) != 0) { + rc = -errno; + pr_verbose(verbose, "Failed to set the %s environment variable:" + " %s", CCA_DOMAIN_ENVAR, strerror(-rc)); + return rc; + } + unsetenv(CCA_ADAPTER_ENVAR); + + /* + * Unload and reload the CCA host library so that it recognizes the + * changed CSU_DEFAULT_DOMAIN environment variable value. + */ + if (cca->lib_csulcca != NULL) + dlclose(cca->lib_csulcca); + memset(cca, 0, sizeof(struct cca_lib)); + + rc = load_cca_library(cca, verbose); + if (rc != 0) + return rc; + + rc = get_number_of_cca_adapters(cca, &adapters, verbose); + if (rc != 0) + return rc; + + /* Disable the AUTOSELECT option */ + rc = deallocate_cca_adapter(cca, 0, verbose); + if (rc != 0) + return rc; + + for (adapter = 1; adapter <= adapters; adapter++) { + rc = allocate_cca_adapter(cca, adapter, verbose); + if (rc != 0) + return rc; + + rc = get_cca_adapter_serialnr(cca, adapter_serialnr, verbose); + if (rc == 0) { + if (memcmp(apqn_serialnr, adapter_serialnr, 8) == 0) { + found = 1; + break; + } + } + + rc = deallocate_cca_adapter(cca, adapter, verbose); + if (rc != 0) + return rc; + } + + if (!found) + return -ENODEV; + + pr_verbose(verbose, "Selected adapter %u (CRP%02d)", adapter, adapter); + return 0; +} + +struct find_mkvp_info { + u64 mkvp; + unsigned int flags; + bool found; + int card; + int domain; + bool verbose; +}; + +static int find_mkvp(int card, int domain, void *handler_data) +{ + struct find_mkvp_info *info = (struct find_mkvp_info *)handler_data; + struct mk_info mk_info; + bool found = false; + int rc; + + rc = sysfs_get_mkvps(card, domain, &mk_info, info->verbose); + if (rc == -ENODEV) + return 0; + if (rc != 0) + return rc; + + if (info->flags & FLAG_SEL_CCA_MATCH_CUR_MKVP) + if (mk_info.cur_mk.mk_state == MK_STATE_VALID && + mk_info.cur_mk.mkvp == info->mkvp) + found = true; + + if (info->flags & FLAG_SEL_CCA_MATCH_OLD_MKVP) + if (mk_info.old_mk.mk_state == MK_STATE_VALID && + mk_info.old_mk.mkvp == info->mkvp) + found = true; + + if (info->flags & FLAG_SEL_CCA_NEW_MUST_BE_SET) + if (mk_info.new_mk.mk_state != MK_STATE_FULL) + found = false; + + + if (found) { + info->card = card; + info->domain = domain; + info->found = true; + + pr_verbose(info->verbose, "%02x.%04x has the desired mkvp%s", + card, domain, + info->flags & FLAG_SEL_CCA_NEW_MUST_BE_SET ? + " and NEW MK set" : ""); + + return 1; + } + + return 0; +} + +/** + * Selects an APQN to be used for the CCA host library that has the specified + * master key verification pattern + * + * @param[in] cca the CCA library structure + * @param[in] mkvp the master key verification pattern to search for + * @param[in] apqns a comma separated list of APQNs. If NULL is specified, + * or an empty string, then all online CCA APQNs are + * checked. + * @param[in] flags Flags that control the MKVM matching and NEW register + * checking. Multiple flags can be combined. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. -ENOTSUP is + * returned when the serialnr sysfs attribute is not available, + * because the zcrypt kernel module is on an older level. -ENODEV is + * returned if no APQN is available with the desired mkvp. + */ +int select_cca_adapter_by_mkvp(struct cca_lib *cca, u64 mkvp, const char *apqns, + unsigned int flags, bool verbose) +{ + struct find_mkvp_info info; + int rc; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + + pr_verbose(verbose, "Select mkvp %016llx in APQNs %s for the CCA host " + "library", mkvp, apqns == 0 ? "ANY" : apqns); + + info.mkvp = mkvp; + info.flags = flags; + info.found = false; + info.card = 0; + info.domain = 0; + info.verbose = verbose; + + rc = handle_apqns(apqns, find_mkvp, &info, verbose); + if (rc < 0) + return rc; + + if (!info.found) + return -ENODEV; + + rc = select_cca_adapter(cca, info.card, info.domain, verbose); + return rc; +} + +void print_msg_for_cca_envvars(const char *key_name) +{ + char *msg; + + util_asprintf(&msg, "WARNING: You must set environment variables " + "%s and %s to the desired card and domain that is " + "set up with the AES master key used by this %s. " + "%s specifies the domain as decimal number. %s " + "specifies the adapter number as 'CRPnn', where " + "'nn' is the adapter number. See the CCA " + "documentation for more details.\n", + CCA_DOMAIN_ENVAR, CCA_ADAPTER_ENVAR, key_name, + CCA_DOMAIN_ENVAR, CCA_ADAPTER_ENVAR); + util_print_indented(msg, 0); + free(msg); +} diff --git a/zkey/cca.h b/zkey/cca.h new file mode 100644 index 0000000..a79e2ee --- /dev/null +++ b/zkey/cca.h @@ -0,0 +1,95 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * This header file defines the interface to the CCA host library. + * + * Copyright IBM Corp. 2019 + * + * 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 CCA_H +#define CCA_H + +#include "lib/zt_common.h" + +#define METHOD_OLD_TO_CURRENT "RTCMK " +#define METHOD_CURRENT_TO_NEW "RTNMK " + +typedef void (*t_CSNBKTC)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + unsigned char *key_identifier); + +typedef void (*t_CSUACFV)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *version_data_length, + unsigned char *version_data); + +typedef void (*t_CSUACFQ)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *verb_data_length, + unsigned char *verb_data); + +typedef void (*t_CSUACRA)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *ressource_name_length, + unsigned char *ressource_name); + +typedef void (*t_CSUACRD)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *ressource_name_length, + unsigned char *ressource_name); + +struct cca_version { + unsigned int ver; + unsigned int rel; + unsigned int mod; +}; + +struct cca_lib { + void *lib_csulcca; + t_CSNBKTC dll_CSNBKTC; + t_CSUACFV dll_CSUACFV; + t_CSUACFQ dll_CSUACFQ; + t_CSUACRA dll_CSUACRA; + t_CSUACRD dll_CSUACRD; + struct cca_version version; +}; + +int load_cca_library(struct cca_lib *cca, bool verbose); + +int key_token_change(struct cca_lib *cca, + u8 *secure_key, unsigned int secure_key_size, + char *method, bool verbose); + +int select_cca_adapter(struct cca_lib *cca, int card, int domain, bool verbose); + +#define FLAG_SEL_CCA_MATCH_CUR_MKVP 0x01 +#define FLAG_SEL_CCA_MATCH_OLD_MKVP 0x02 +#define FLAG_SEL_CCA_NEW_MUST_BE_SET 0x80 + +int select_cca_adapter_by_mkvp(struct cca_lib *cca, u64 mkvp, const char *apqns, + unsigned int flags, bool verbose); + +void print_msg_for_cca_envvars(const char *key_name); + +#endif diff --git a/zkey/keystore.c b/zkey/keystore.c index 33c8f93..957ebb0 100644 --- a/zkey/keystore.c +++ b/zkey/keystore.c @@ -3,7 +3,7 @@ * * Keystore handling functions * - * Copyright IBM Corp. 2018 + * Copyright IBM Corp. 2018, 2019 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. @@ -25,7 +25,6 @@ #include #include "lib/util_base.h" -#include "lib/util_file.h" #include "lib/util_libc.h" #include "lib/util_panic.h" #include "lib/util_path.h" @@ -33,7 +32,9 @@ #include "keystore.h" #include "pkey.h" +#include "cca.h" #include "properties.h" +#include "utils.h" struct key_filenames { char *skey_filename; @@ -133,14 +134,7 @@ static int _keystore_get_key_filenames(struct keystore *keystore, */ static int _keystore_reencipher_key_exists(struct key_filenames *file_names) { - struct stat sb; - int rc; - - rc = stat(file_names->renc_filename, &sb); - if (rc == 0 && !S_ISREG(sb.st_mode)) - rc = 1; - - return !rc; + return util_path_is_reg_file("%s", file_names->renc_filename); } /** @@ -153,20 +147,14 @@ static int _keystore_reencipher_key_exists(struct key_filenames *file_names) */ static int _keystore_exists_keyfiles(struct key_filenames *file_names) { - struct stat sb_skey, sb_info; - int rc_skey, rc_info; - - rc_skey = stat(file_names->skey_filename, &sb_skey); - if (rc_skey == 0 && !S_ISREG(sb_skey.st_mode)) - rc_skey = 1; + bool rc_skey, rc_info; - rc_info = stat(file_names->info_filename, &sb_info); - if (rc_info == 0 && !S_ISREG(sb_info.st_mode)) - rc_info = 1; + rc_skey = util_path_is_reg_file("%s", file_names->skey_filename); + rc_info = util_path_is_reg_file("%s", file_names->info_filename); - if (rc_skey == 0 && rc_info == 0) + if (rc_skey && rc_info) return 1; - if (rc_skey != 0 && rc_info != 0 && + if (!rc_skey && !rc_info && _keystore_reencipher_key_exists(file_names) == 0) return 0; return -1; @@ -1022,69 +1010,6 @@ free: return rc; } -/** - * Checks if the specified APQN is of type CCA and is online - * - * @param[in] card card number - * @param[in] domain the domain - * - * @returns 1 if its a CCA card and is online, 0 if offline and -1 if its - * not a CCA card. - */ -static int _keystore_is_apqn_online(int card, int domain) -{ - long int online; - char *dev_path; - char type[20]; - int rc = 1; - - dev_path = util_path_sysfs("bus/ap/devices/card%02x", card); - if (!util_path_is_dir(dev_path)) { - rc = 0; - goto out; - } - if (util_file_read_l(&online, 10, "%s/online", dev_path) != 0) { - rc = 0; - goto out; - } - if (online == 0) { - rc = 0; - goto out; - } - if (util_file_read_line(type, sizeof(type), "%s/type", dev_path) != 0) { - rc = 0; - goto out; - } - if (strncmp(type, "CEX", 3) != 0 || strlen(type) < 5) { - rc = 0; - goto out; - } - if (type[4] != 'C') { - rc = -1; - goto out; - } - free(dev_path); - - dev_path = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x", card, - card, domain); - if (!util_path_is_dir(dev_path)) { - rc = 0; - goto out; - } - if (util_file_read_l(&online, 10, "%s/online", dev_path) != 0) { - rc = 0; - goto out; - } - if (online == 0) { - rc = 0; - goto out; - } - -out: - free(dev_path); - return rc; -} - struct apqn_check { bool noonlinecheck; bool nomsg; @@ -1136,7 +1061,7 @@ static int _keystore_apqn_check(const char *apqn, bool remove, bool UNUSED(set), goto out; } - rc = _keystore_is_apqn_online(card, domain); + rc = sysfs_is_apqn_online(card, domain); if (rc != 1) { if (info->nomsg == 0) warnx("The APQN %02x.%04x is %s", card, domain, @@ -1378,7 +1303,7 @@ struct keystore *keystore_new(const char *directory, bool verbose) warnx("Can not access '%s': %s", directory, strerror(errno)); return NULL; } - if (!(sb.st_mode & S_IFDIR)) { + if (!S_ISDIR(sb.st_mode)) { warnx("'%s' is not a directory", directory); return NULL; } @@ -1630,7 +1555,8 @@ static int _keystore_create_info_file(struct keystore *keystore, rc = -EINVAL; goto out; } - rc = properties_set(key_props, PROP_NAME_VOLUME_TYPE, volume_type); + rc = properties_set2(key_props, PROP_NAME_VOLUME_TYPE, volume_type, + true); if (rc != 0) { warnx("Invalid characters in volume-type"); goto out; @@ -1759,6 +1685,14 @@ int keystore_generate_key(struct keystore *keystore, const char *name, if (rc != 0) goto out_free_key_filenames; + rc = cross_check_apqns(apqns, 0, true, keystore->verbose); + if (rc == -EINVAL) + goto out_free_key_filenames; + if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { + warnx("Your master key setup is improper"); + goto out_free_key_filenames; + } + rc = _keystore_get_card_domain(apqns, &card, &domain); if (rc != 0) goto out_free_key_filenames; @@ -1836,6 +1770,7 @@ int keystore_import_key(struct keystore *keystore, const char *name, struct properties *key_props = NULL; size_t secure_key_size; u8 *secure_key; + u64 mkvp; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); @@ -1857,9 +1792,26 @@ int keystore_import_key(struct keystore *keystore, const char *name, goto out_free_key_filenames; } + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + &mkvp, keystore->verbose); + if (rc != 0) { + warnx("Failed to get the master key verification pattern: %s", + strerror(-rc)); + goto out_free_key; + } + + rc = cross_check_apqns(apqns, mkvp, true, keystore->verbose); + if (rc == -EINVAL) + goto out_free_key; + if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { + warnx("Your master key setup is improper"); + goto out_free_key; + } + rc = write_secure_key(file_names.skey_filename, secure_key, secure_key_size, keystore->verbose); free(secure_key); + secure_key = NULL; if (rc != 0) goto out_free_props; @@ -1877,6 +1829,9 @@ int keystore_import_key(struct keystore *keystore, const char *name, "Successfully imported a secure key in '%s' and key info in '%s'", file_names.skey_filename, file_names.info_filename); +out_free_key: + if (secure_key != NULL) + free(secure_key); out_free_props: if (key_props != NULL) properties_free(key_props); @@ -1931,7 +1886,11 @@ int keystore_change_key(struct keystore *keystore, const char *name, .nomsg = 0 }; struct key_filenames file_names = { NULL, NULL, NULL }; struct properties *key_props = NULL; + size_t secure_key_size; + char *apqns_prop; + u8 *secure_key; char temp[30]; + u64 mkvp; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); @@ -1977,6 +1936,33 @@ int keystore_change_key(struct keystore *keystore, const char *name, &apqn_check); if (rc != 0) goto out; + + secure_key = read_secure_key(file_names.skey_filename, + &secure_key_size, + keystore->verbose); + if (secure_key == NULL) { + rc = -ENOENT; + goto out; + } + + rc = get_master_key_verification_pattern(secure_key, + secure_key_size, + &mkvp, + keystore->verbose); + free(secure_key); + if (rc) + goto out; + + apqns_prop = properties_get(key_props, PROP_NAME_APQNS); + rc = cross_check_apqns(apqns_prop, mkvp, true, + keystore->verbose); + free(apqns_prop); + if (rc == -ENOTSUP) + rc = 0; + if (rc != 0 && noapqncheck == 0) { + warnx("Your master key setup is improper"); + goto out; + } } if (sector_size >= 0) { @@ -2002,8 +1988,8 @@ int keystore_change_key(struct keystore *keystore, const char *name, goto out; } - rc = properties_set(key_props, PROP_NAME_VOLUME_TYPE, - volume_type); + rc = properties_set2(key_props, PROP_NAME_VOLUME_TYPE, + volume_type, true); if (rc != 0) { warnx("Invalid characters in volume-type"); goto out; @@ -2181,7 +2167,7 @@ static void _keystore_print_record(struct util_rec *rec, bool validation, const char *skey_filename, size_t secure_key_size, size_t clear_key_bitsize, bool valid, - bool is_old_mk, bool reenc_pending) + bool is_old_mk, bool reenc_pending, u64 mkvp) { char temp_vp[VERIFICATION_PATTERN_LEN + 2]; char *volumes_argz = NULL; @@ -2243,10 +2229,11 @@ static void _keystore_print_record(struct util_rec *rec, if (validation) { if (valid) util_rec_set(rec, REC_MASTERKEY, - is_old_mk ? "OLD CCA master key" : - "CURRENT CCA master key"); + "%s CCA master key (MKVP: %016llx)", + is_old_mk ? "OLD" : "CURRENT", mkvp); else - util_rec_set(rec, REC_MASTERKEY, "(unknown)"); + util_rec_set(rec, REC_MASTERKEY, + "(unknown, MKVP: %016llx)", mkvp); } if (volumes_argz != NULL) util_rec_set_argz(rec, REC_VOLUMES, volumes_argz, @@ -2317,43 +2304,32 @@ struct validate_info { /** * Displays the status of the associated APQNs. * + * @param[in] keystore the key store * @param[in] properties the properties of the key - * @param[in] name the name of the key + * @param[in] mkvp the master key verification pattern of the key * * @returns 0 in case of success, 1 if at least one of the APQNs is not - * available + * available or has a master key mismatch */ -static int _keystore_display_apqn_status(struct properties *properties, - const char *name) +static int _keystore_display_apqn_status(struct keystore *keystore, + struct properties *properties, + u64 mkvp) { - int i, rc, card, domain, warning = 0; - char **apqn_list; + int rc, warning = 0; char *apqns; apqns = properties_get(properties, PROP_NAME_APQNS); if (apqns == NULL) return 0; - apqn_list = str_list_split(apqns); - for (i = 0; apqn_list[i] != NULL; i++) { - - if (sscanf(apqn_list[i], "%x.%x", &card, &domain) != 2) - continue; - - rc = _keystore_is_apqn_online(card, domain); - if (rc != 1) { - printf("WARNING: The APQN %02x.%04x associated with " - "key '%s' is %s\n", card, domain, name, - rc == -1 ? "not a CCA card" : "not online"); - warning = 1; - } - } + rc = cross_check_apqns(apqns, mkvp, true, keystore->verbose); + if (rc != 0 && rc != -ENOTSUP) + warning = 1; if (warning) printf("\n"); free(apqns); - str_list_free_string_array(apqn_list); return warning; } /** @@ -2424,6 +2400,7 @@ static int _keystore_process_validate(struct keystore *keystore, u8 *secure_key; int is_old_mk; int rc, valid; + u64 mkvp; rc = _keystore_ensure_keyfiles_exist(file_names, name); if (rc != 0) @@ -2447,12 +2424,18 @@ static int _keystore_process_validate(struct keystore *keystore, info->num_valid++; valid = 1; } + + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + &mkvp, keystore->verbose); free(secure_key); + if (rc) + goto out; _keystore_print_record(info->rec, name, properties, 1, file_names->skey_filename, secure_key_size, clear_key_bitsize, valid, is_old_mk, - _keystore_reencipher_key_exists(file_names)); + _keystore_reencipher_key_exists(file_names), + mkvp); if (valid && is_old_mk) { util_print_indented("WARNING: The secure key is currently " @@ -2463,7 +2446,8 @@ static int _keystore_process_validate(struct keystore *keystore, info->num_warnings++; } if (info->noapqncheck == 0) - if (_keystore_display_apqn_status(properties, name) != 0) + if (_keystore_display_apqn_status(keystore, properties, + mkvp) != 0) info->num_warnings++; if (_keystore_display_volume_status(properties, name) != 0) info->num_warnings++; @@ -2534,7 +2518,7 @@ struct reencipher_params { struct reencipher_info { struct reencipher_params params; int pkey_fd; - t_CSNBKTC dll_CSNBKTC; + struct cca_lib *cca; unsigned long num_reenciphered; unsigned long num_failed; unsigned long num_skipped; @@ -2545,23 +2529,33 @@ struct reencipher_info { * * @param[in] keystore the keystore * @param[in] name the name of the key - * @param[in] dll_CSNBKTC the CCA key token change function + * @param[in] cca the CCA library struct * @param[in] params reenciphering parameters * @param[in] secure_key a buffer containing the secure key * @param[in] secure_key_size the size of the secure key * @param[in] is_old_mk if true the key is currently re-enciphered with the * OLD master key + * @param[in] apqns the associated APQNs (or NULL if none) * @returns 0 if the re-enciphering is successful, a negative errno value * otherwise, 1 if it was skipped */ static int _keystore_perform_reencipher(struct keystore *keystore, const char *name, - t_CSNBKTC dll_CSNBKTC, + struct cca_lib *cca, struct reencipher_params *params, u8 *secure_key, size_t secure_key_size, - bool is_old_mk) + bool is_old_mk, const char *apqns) { - int rc; + int rc, selected = 1; + u64 mkvp; + + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + &mkvp, keystore->verbose); + if (rc != 0) { + warnx("Failed to get the master key verification pattern: %s", + strerror(-rc)); + return rc; + } if (!params->from_old && !params->to_new) { /* Autodetect reencipher mode */ @@ -2583,12 +2577,6 @@ static int _keystore_perform_reencipher(struct keystore *keystore, } if (params->from_old) { - if (!is_old_mk) { - printf("The secure key '%s' is already enciphered " - "with the CURRENT CCA master key\n", name); - return 1; - } - if (params->inplace == -1) params->inplace = 1; @@ -2596,13 +2584,27 @@ static int _keystore_perform_reencipher(struct keystore *keystore, "Secure key '%s' will be re-enciphered from OLD " "to the CURRENT CCA master key", name); - rc = key_token_change(dll_CSNBKTC, - secure_key, secure_key_size, + rc = select_cca_adapter_by_mkvp(cca, mkvp, apqns, + FLAG_SEL_CCA_MATCH_OLD_MKVP, + keystore->verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + warnx("No APQN found that is suitable for " + "re-enciphering this secure AES key"); + return rc; + } + + rc = key_token_change(cca, secure_key, secure_key_size, METHOD_OLD_TO_CURRENT, keystore->verbose); if (rc != 0) { warnx("Failed to re-encipher '%s' from OLD to " "CURRENT CCA master key", name); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); return rc; } } @@ -2614,13 +2616,30 @@ static int _keystore_perform_reencipher(struct keystore *keystore, if (params->inplace == -1) params->inplace = 0; - rc = key_token_change(dll_CSNBKTC, - secure_key, secure_key_size, + rc = select_cca_adapter_by_mkvp(cca, mkvp, apqns, + FLAG_SEL_CCA_MATCH_CUR_MKVP | + FLAG_SEL_CCA_NEW_MUST_BE_SET, + keystore->verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + util_print_indented("No APQN found that is suitable " + "for re-enciphering this secure " + "AES key and has the NEW master " + "key loaded", 0); + return rc; + } + + rc = key_token_change(cca, secure_key, secure_key_size, METHOD_CURRENT_TO_NEW, keystore->verbose); if (rc != 0) { warnx("Failed to re-encipher '%s' from CURRENT to " "NEW CCA master key", name); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); return rc; } } @@ -2708,10 +2727,11 @@ static int _keystore_process_reencipher(struct keystore *keystore, if (!params.complete) { printf("Re-enciphering key '%s'\n", name); - rc = _keystore_perform_reencipher(keystore, name, - info->dll_CSNBKTC, ¶ms, - secure_key, secure_key_size, - is_old_mk); + rc = _keystore_perform_reencipher(keystore, name, info->cca, + ¶ms, secure_key, + secure_key_size, is_old_mk, + properties_get(properties, + PROP_NAME_APQNS)); if (rc < 0) goto out; if (rc > 0) { @@ -2814,6 +2834,8 @@ out: * @param[in] inplace if true, the key will be re-enciphere in-place * @param[in] staged if true, the key will be re-enciphere not in-place * @param[in] complete if true, a pending re-encipherment is completed + * @param[in] pkey_fd the file descriptor of /dev/pkey + * @param[in] cca the CCA library struct * Note: if both from Old and toNew are FALSE, then the reencipherement mode is * detected automatically. If both are TRUE then the key is reenciphered * from the OLD to the NEW CCA master key. @@ -2826,7 +2848,7 @@ int keystore_reencipher_key(struct keystore *keystore, const char *name_filter, const char *apqn_filter, bool from_old, bool to_new, bool inplace, bool staged, bool complete, int pkey_fd, - t_CSNBKTC dll_CSNBKTC) + struct cca_lib *cca) { struct reencipher_info info; int rc; @@ -2842,7 +2864,7 @@ int keystore_reencipher_key(struct keystore *keystore, const char *name_filter, info.params.inplace = 0; info.params.complete = complete; info.pkey_fd = pkey_fd; - info.dll_CSNBKTC = dll_CSNBKTC; + info.cca = cca; info.num_failed = 0; info.num_reenciphered = 0; info.num_skipped = 0; @@ -3063,7 +3085,7 @@ out: * @returnd 0 if the user confirmed the deletion, a negative errno value * otherwise */ -static int _keystore_propmp_for_remove(struct keystore *keystore, +static int _keystore_prompt_for_remove(struct keystore *keystore, const char *name, struct key_filenames *file_names) { @@ -3084,7 +3106,8 @@ static int _keystore_propmp_for_remove(struct keystore *keystore, _keystore_msg_for_volumes(msg, key_prop, VOLUME_TYPE_PLAIN); free(msg); - printf("%s: Remove key '%s'? ", program_invocation_short_name, name); + printf("%s: Remove key '%s' [y/N]? ", program_invocation_short_name, + name); if (fgets(str, sizeof(str), stdin) == NULL) { rc = -EIO; goto out; @@ -3093,6 +3116,7 @@ static int _keystore_propmp_for_remove(struct keystore *keystore, str[strlen(str) - 1] = '\0'; pr_verbose(keystore, "Prompt reply: '%s'", str); if (strcasecmp(str, "y") != 0 && strcasecmp(str, "yes") != 0) { + warnx("Operation aborted"); rc = -ECANCELED; goto out; } @@ -3129,7 +3153,7 @@ int keystore_remove_key(struct keystore *keystore, const char *name, goto out; if (!quiet) { - if (_keystore_propmp_for_remove(keystore, name, + if (_keystore_prompt_for_remove(keystore, name, &file_names) != 0) goto out; } @@ -3204,7 +3228,7 @@ static int _keystore_display_key(struct keystore *keystore, IS_XTS(secure_key_size) ? secure_key->bitsize * 2 : secure_key->bitsize, 0, 0, - _keystore_reencipher_key_exists(file_names)); + _keystore_reencipher_key_exists(file_names), 0); out: free(secure_key); diff --git a/zkey/keystore.h b/zkey/keystore.h index 16ecc62..8e888d1 100644 --- a/zkey/keystore.h +++ b/zkey/keystore.h @@ -14,6 +14,7 @@ #include +#include "cca.h" #include "pkey.h" struct keystore { @@ -54,7 +55,7 @@ int keystore_reencipher_key(struct keystore *keystore, const char *name_filter, const char *apqn_filter, bool from_old, bool to_new, bool inplace, bool staged, bool complete, int pkey_fd, - t_CSNBKTC dll_CSNBKTC); + struct cca_lib *cca); int keystore_copy_key(struct keystore *keystore, const char *name, const char *newname, const char *volumes); diff --git a/zkey/pkey.c b/zkey/pkey.c index a88c4e9..8471f3d 100644 --- a/zkey/pkey.c +++ b/zkey/pkey.c @@ -44,57 +44,7 @@ #define MAX_CIPHER_LEN 32 -/* - * Definitions for the CCA library - */ -#define CCA_LIBRARY_NAME "libcsulcca.so" -#define CCA_WEB_PAGE "http://www.ibm.com/security/cryptocards" - -#define DEFAULT_KEYBITS 256 - -/** - * Loads the CCA library and provides the entry point of the CSNBKTC function. - * - * @param[out] lib_csulcca on return this contains the address of the CCA - * library. dlclose() should be used to free this - * when no longer needed. - * @param[out] dll_CSNBKTC on return this contains the address of the - * CSNBKTC function. - * @param verbose if true, verbose messages are printed - * - * @returns 0 on success, -ELIBACC in case of library load errors - */ -int load_cca_library(void **lib_csulcca, t_CSNBKTC *dll_CSNBKTC, bool verbose) -{ - util_assert(lib_csulcca != NULL, "Internal error: lib_csulcca is NULL"); - util_assert(dll_CSNBKTC != NULL, "Internal error: dll_CSNBKTC is NULL"); - - /* Load the CCA library */ - *lib_csulcca = dlopen(CCA_LIBRARY_NAME, RTLD_GLOBAL | RTLD_NOW); - if (*lib_csulcca == NULL) { - pr_verbose(verbose, "%s", dlerror()); - warnx("The command requires the IBM CCA Host Libraries and " - "Tools.\nFor the supported environments and downloads, " - "see:\n%s", CCA_WEB_PAGE); - return -ELIBACC; - } - - /* Get the Key Token Change function */ - *dll_CSNBKTC = (t_CSNBKTC)dlsym(*lib_csulcca, "CSNBKTC"); - if (*dll_CSNBKTC == NULL) { - pr_verbose(verbose, "%s", dlerror()); - warnx("The command requires the IBM CCA Host Libraries and " - "Tools.\nFor the supported environments and downloads, " - "see:\n%s", CCA_WEB_PAGE); - dlclose(*lib_csulcca); - *lib_csulcca = NULL; - return -ELIBACC; - } - - pr_verbose(verbose, "CCA library '%s' has been loaded successfully", - CCA_LIBRARY_NAME); - return 0; -} +#define DEFAULT_KEYBITS 256 /** * Opens the pkey device and returns its file descriptor. @@ -267,7 +217,7 @@ static u8 *read_clear_key(const char *keyfile, size_t keybits, bool xts, return NULL; } } else { - keybits = DOUBLE_KEYSIZE_FOR_XTS(size * 8, xts); + keybits = HALF_KEYSIZE_FOR_XTS(size * 8, xts); } switch (keybits) { @@ -522,96 +472,6 @@ out: return rc; } -/** - * Prints CCA return and reason code information for certain known CCA - * error situations. - * - * @param return_code the CCA return code - * @param reason_code the CCA reason code - */ -static void print_CCA_error(int return_code, int reason_code) -{ - switch (return_code) { - case 8: - switch (reason_code) { - case 48: - warnx("The secure key has a CCA master key " - "verification pattern that is not valid"); - break; - } - break; - case 12: - switch (reason_code) { - case 764: - warnx("The CCA master key is not loaded and " - "therefore a secure key cannot be enciphered"); - break; - } - break; - } -} - -/** - * Re-enciphers a secure key. - * - * @param[in] dll_CSNBKTC the address of the CCA CSNBKTC function - * @param[in] secure_key a buffer containing the secure key - * @param[in] secure_key_size the size of the secure key - * @param[in] method the re-enciphering method. METHOD_OLD_TO_CURRENT - * or METHOD_CURRENT_TO_NEW. - * @param[in] verbose if true, verbose messages are printed - * - * @returns 0 on success, -EIO in case of an error - */ -int key_token_change(t_CSNBKTC dll_CSNBKTC, - u8 *secure_key, unsigned int secure_key_size, - char *method, bool verbose) -{ - long exit_data_len = 0, rule_array_count; - unsigned char rule_array[2 * 80] = { 0, }; - unsigned char exit_data[4] = { 0, }; - long return_code, reason_code; - - util_assert(dll_CSNBKTC != NULL, "Internal error: dll_CSNBKTC is NULL"); - util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); - util_assert(secure_key_size > 0, - "Internal error: secure_key_size is 0"); - util_assert(method != NULL, "Internal error: method is NULL"); - - memcpy(rule_array, method, 8); - memcpy(rule_array + 8, "AES ", 8); - rule_array_count = 2; - - dll_CSNBKTC(&return_code, &reason_code, - &exit_data_len, exit_data, - &rule_array_count, rule_array, - secure_key); - - pr_verbose(verbose, "CSNBKTC (Key Token Change) with '%s' returned: " - "return_code: %ld, reason_code: %ld", method, return_code, - reason_code); - if (return_code != 0) { - print_CCA_error(return_code, reason_code); - return -EIO; - } - - if (secure_key_size == 2 * SECURE_KEY_SIZE) { - dll_CSNBKTC(&return_code, &reason_code, - &exit_data_len, exit_data, - &rule_array_count, rule_array, - secure_key + SECURE_KEY_SIZE); - - pr_verbose(verbose, "CSNBKTC (Key Token Change) with '%s' " - "returned: return_code: %ld, reason_code: %ld", - method, return_code, reason_code); - if (return_code != 0) { - print_CCA_error(return_code, reason_code); - return -EIO; - } - } - return 0; -} - /** * Validates an XTS secure key (the second part) * @@ -909,3 +769,24 @@ out: return rc; } + +int get_master_key_verification_pattern(const u8 *secure_key, + size_t secure_key_size, u64 *mkvp, + bool verbose) +{ + struct secaeskeytoken *token = (struct secaeskeytoken *)secure_key; + + util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + util_assert(mkvp != NULL, "Internal error: mkvp is NULL"); + + if (secure_key_size < SECURE_KEY_SIZE) { + pr_verbose(verbose, "Size of secure key is too small: " + "%lu expected %lu", secure_key_size, + SECURE_KEY_SIZE); + return -EINVAL; + } + + *mkvp = token->mkvp; + + return 0; +} diff --git a/zkey/pkey.h b/zkey/pkey.h index 3c524cd..c0aac2e 100644 --- a/zkey/pkey.h +++ b/zkey/pkey.h @@ -82,23 +82,10 @@ struct pkey_verifykey { #define PKEY_VERIFYKEY _IOWR(PKEY_IOCTL_MAGIC, 0x07, struct pkey_verifykey) -#define METHOD_OLD_TO_CURRENT "RTCMK " -#define METHOD_CURRENT_TO_NEW "RTNMK " - -typedef void (*t_CSNBKTC)(long *return_code, - long *reason_code, - long *exit_data_length, - unsigned char *exit_data, - long *rule_array_count, - unsigned char *rule_array, - unsigned char *key_identifier); - #define PAES_BLOCK_SIZE 16 #define ENC_ZERO_LEN (2 * PAES_BLOCK_SIZE) #define VERIFICATION_PATTERN_LEN (2 * ENC_ZERO_LEN + 1) -int load_cca_library(void **lib_csulcca, t_CSNBKTC *dll_CSNBKTC, bool verbose); - int open_pkey_device(bool verbose); int generate_secure_key_random(int pkey_fd, const char *keyfile, @@ -122,11 +109,11 @@ int validate_secure_key(int pkey_fd, size_t *clear_key_bitsize, int *is_old_mk, bool verbose); -int key_token_change(t_CSNBKTC dll_CSNBKTC, - u8 *secure_key, unsigned int secure_key_size, - char *method, bool verbose); - int generate_key_verification_pattern(const char *key, size_t key_size, char *vp, size_t vp_len, bool verbose); +int get_master_key_verification_pattern(const u8 *secure_key, + size_t secure_key_size, u64 *mkvp, + bool verbose); + #endif diff --git a/zkey/properties.c b/zkey/properties.c index c20e51b..147bcc5 100644 --- a/zkey/properties.c +++ b/zkey/properties.c @@ -9,6 +9,7 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include #include #include #include @@ -183,14 +184,16 @@ static struct property *properties_find(struct properties *properties, * @param[in] properties the properties object * @param[in] name the name of the property * @param[in] value the value of the property + * @param[in] uppercase if true the value is set all uppercase * * @returns 0 on success, * -EINVAL if the name or value contains invalid characters */ -int properties_set(struct properties *properties, - const char *name, const char *value) +int properties_set2(struct properties *properties, + const char *name, const char *value, bool uppercase) { struct property *property; + int i; util_assert(properties != NULL, "Internal error: properties is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); @@ -211,9 +214,30 @@ int properties_set(struct properties *properties, property->value = util_strdup(value); util_list_add_tail(&properties->list, property); } + if (uppercase) { + for (i = 0; property->value[i] != '\0'; i++) + property->value[i] = toupper(property->value[i]); + } + return 0; } +/** + * Adds or updates a property + * + * @param[in] properties the properties object + * @param[in] name the name of the property + * @param[in] value the value of the property + * + * @returns 0 on success, + * -EINVAL if the name or value contains invalid characters + */ +int properties_set(struct properties *properties, + const char *name, const char *value) +{ + return properties_set2(properties, name, value, false); +} + /** * Gets a property * diff --git a/zkey/properties.h b/zkey/properties.h index 234948e..f2c8a15 100644 --- a/zkey/properties.h +++ b/zkey/properties.h @@ -23,6 +23,9 @@ void properties_free(struct properties *properties); int properties_set(struct properties *properties, const char *name, const char *value); +int properties_set2(struct properties *properties, + const char *name, const char *value, bool uppercase); + char *properties_get(struct properties *properties, const char *name); int properties_remove(struct properties *properties, const char *name); diff --git a/zkey/utils.c b/zkey/utils.c new file mode 100644 index 0000000..9dc4af7 --- /dev/null +++ b/zkey/utils.c @@ -0,0 +1,725 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * Copyright IBM Corp. 2019 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_path.h" +#include "lib/util_file.h" +#include "lib/util_scandir.h" +#include "lib/util_libc.h" +#include "lib/util_rec.h" +#include "lib/util_base.h" + +#include "utils.h" +#include "properties.h" + +#define pr_verbose(verbose, fmt...) do { \ + if (verbose) \ + warnx(fmt); \ + } while (0) + +/** + * Checks if the specified card is of type CCA and is online + * + * @param[in] card card number + * + * @returns 1 if its a CCA card and is online, 0 if offline and -1 if its + * not a CCA card. + */ +int sysfs_is_card_online(int card) +{ + long int online; + char *dev_path; + char type[20]; + int rc = 1; + + dev_path = util_path_sysfs("bus/ap/devices/card%02x", card); + if (!util_path_is_dir(dev_path)) { + rc = 0; + goto out; + } + if (util_file_read_l(&online, 10, "%s/online", dev_path) != 0) { + rc = 0; + goto out; + } + if (online == 0) { + rc = 0; + goto out; + } + if (util_file_read_line(type, sizeof(type), "%s/type", dev_path) != 0) { + rc = 0; + goto out; + } + if (strncmp(type, "CEX", 3) != 0 || strlen(type) < 5) { + rc = 0; + goto out; + } + if (type[4] != 'C') { + rc = -1; + goto out; + } + +out: + free(dev_path); + return rc; +} + +/** + * Checks if the specified APQN is of type CCA and is online + * + * @param[in] card card number + * @param[in] domain the domain + * + * @returns 1 if its a CCA card and is online, 0 if offline and -1 if its + * not a CCA card. + */ +int sysfs_is_apqn_online(int card, int domain) +{ + long int online; + char *dev_path; + int rc = 1; + + rc = sysfs_is_card_online(card); + if (rc != 1) + return rc; + + dev_path = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x", card, + card, domain); + if (!util_path_is_dir(dev_path)) { + rc = 0; + goto out; + } + if (util_file_read_l(&online, 10, "%s/online", dev_path) != 0) { + rc = 0; + goto out; + } + if (online == 0) { + rc = 0; + goto out; + } + +out: + free(dev_path); + return rc; +} + +/** + * Gets the 8 character ASCII serial number string of an card from the sysfs. + * + * @param[in] card card number + * @param[out] serialnr Result buffer + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 if the serial number was returned. -ENODEV if the APQN is not + * available, or is not a CCA card. -ENOTSUP if the serialnr sysfs + * attribute is not available, because the zcrypt kernel module is + * on an older level. + */ +int sysfs_get_serialnr(int card, char serialnr[9], bool verbose) +{ + char *dev_path; + int rc = 0; + + if (serialnr == NULL) + return -EINVAL; + + if (sysfs_is_card_online(card) != 1) + return -ENODEV; + + dev_path = util_path_sysfs("bus/ap/devices/card%02x", card); + if (!util_path_is_dir(dev_path)) { + rc = -ENODEV; + goto out; + } + if (util_file_read_line(serialnr, 9, "%s/serialnr", dev_path) != 0) { + rc = -ENOTSUP; + goto out; + } + + if (strlen(serialnr) == 0) { + rc = -ENODEV; + goto out; + } + + pr_verbose(verbose, "Serial number of %02x: %s", card, serialnr); +out: + if (rc != 0) + pr_verbose(verbose, "Failed to get serial number for " + "%02x: %s", card, strerror(-rc)); + + free(dev_path); + return rc; +} + +static int parse_mk_info(char *line, struct mk_info *mk_info) +{ + struct mk_info_reg *mk_reg; + char *save; + char *tok; + + tok = strtok_r(line, " ", &save); + if (tok == NULL) + return -EIO; + + if (strcasecmp(tok, "AES") != 0) + return 0; + + tok = strtok_r(NULL, " ", &save); + if (tok == NULL) + return -EIO; + + if (strcasecmp(tok, "NEW:") == 0) + mk_reg = &mk_info->new_mk; + else if (strcasecmp(tok, "CUR:") == 0) + mk_reg = &mk_info->cur_mk; + else if (strcasecmp(tok, "OLD:") == 0) + mk_reg = &mk_info->old_mk; + else + return -EIO; + + tok = strtok_r(NULL, " ", &save); + if (tok == NULL) + return -EIO; + + if (strcasecmp(tok, "empty") == 0) + mk_reg->mk_state = MK_STATE_EMPTY; + else if (strcasecmp(tok, "partial") == 0) + mk_reg->mk_state = MK_STATE_PARTIAL; + else if (strcasecmp(tok, "full") == 0) + mk_reg->mk_state = MK_STATE_FULL; + else if (strcasecmp(tok, "valid") == 0) + mk_reg->mk_state = MK_STATE_VALID; + else if (strcasecmp(tok, "invalid") == 0) + mk_reg->mk_state = MK_STATE_INVALID; + else + mk_reg->mk_state = MK_STATE_UNKNOWN; + + tok = strtok_r(NULL, " ", &save); + if (tok == NULL) + return -EIO; + + if (sscanf(tok, "%llx", &mk_reg->mkvp) != 1) + return -EIO; + + return 0; +} + +/** + * Gets the master key states and verification patterns of an APQN from the + * sysfs. + * + * @param[in] card card number + * @param[in] domain the domain + * @param[out] mk_info structure is filled on return with master key infos + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 if the master key info was returned. -ENODEV if the APQN is not + * available, or is not a CCA card. -ENOTSUP if the mkvps sysfs + * attribute is not available, because the zcrypt kernel module is + * on an older level. + */ +int sysfs_get_mkvps(int card, int domain, struct mk_info *mk_info, bool verbose) +{ + char *dev_path; + char *p, *end; + char buf[100]; + int rc = 0; + FILE *fp; + + if (mk_info == NULL) + return -EINVAL; + + memset(mk_info, 0, sizeof(struct mk_info)); + mk_info->new_mk.mk_state = MK_STATE_UNKNOWN; + mk_info->cur_mk.mk_state = MK_STATE_UNKNOWN; + mk_info->old_mk.mk_state = MK_STATE_UNKNOWN; + + if (sysfs_is_apqn_online(card, domain) != 1) + return -ENODEV; + + dev_path = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x/mkvps", + card, card, domain); + if (!util_path_is_reg_file(dev_path)) { + rc = -ENOTSUP; + goto out; + } + + fp = fopen(dev_path, "r"); + if (fp == NULL) { + rc = -ENOTSUP; + goto out; + } + + /* + * Expected contents: + * AES NEW: + * AES CUR: + * AES OLD: + * with + * : 'empty' or 'partial' or 'full' + * , : 'valid' or 'invalid' + * , , new_mk.mk_state == MK_STATE_UNKNOWN && + mk_info->cur_mk.mk_state == MK_STATE_UNKNOWN && + mk_info->old_mk.mk_state == MK_STATE_UNKNOWN) + rc = -EIO; +out: + if (rc != 0) + pr_verbose(verbose, "Failed to get mkvps for %02x.%04x: %s", + card, domain, strerror(-rc)); + + free(dev_path); + return rc; +} + +static int scan_for_domains(int card, apqn_handler_t handler, + void *handler_data, bool verbose) +{ + struct dirent **namelist; + char fname[290]; + int i, n, domain, rc = 0; + + sprintf(fname, "/sys/devices/ap/card%02x/", card); + n = util_scandir(&namelist, alphasort, fname, + "[0-9a-fA-F]+\\.[0-9a-fA-F]+"); + + if (n < 0) + return -EIO; + + for (i = 0; i < n; i++) { + if (sscanf(namelist[i]->d_name, "%x.%x", &card, &domain) != 2) + continue; + + pr_verbose(verbose, "Found %02x.%04x", card, domain); + + if (sysfs_is_apqn_online(card, domain) != 1) { + pr_verbose(verbose, "APQN %02x.%04x is offline or not " + "CCA", card, domain); + continue; + } + + rc = handler(card, domain, handler_data); + if (rc != 0) + break; + } + + util_scandir_free(namelist, n); + return rc; +} + + +static int scan_for_apqns(apqn_handler_t handler, void *handler_data, + bool verbose) +{ + struct dirent **namelist; + int i, n, card, rc = 0; + + if (handler == NULL) + return -EINVAL; + + n = util_scandir(&namelist, alphasort, "/sys/devices/ap/", + "card[0-9a-fA-F]+"); + if (n < 0) + return -EIO; + + for (i = 0; i < n; i++) { + if (sscanf(namelist[i]->d_name, "card%x", &card) != 1) + continue; + + pr_verbose(verbose, "Found card %02x", card); + + if (sysfs_is_card_online(card) != 1) { + pr_verbose(verbose, "Card %02x is offline or not CCA", + card); + continue; + } + + rc = scan_for_domains(card, handler, handler_data, verbose); + if (rc != 0) + break; + } + + util_scandir_free(namelist, n); + return rc; +} + +/** + * Calls the handler for all APQNs specified in the apqns parameter, or of this + * is NULL, for all online CCA APQNs found in sysfs. In case sysfs is inspected, + * the cards and domains are processed in alphabetical order. + * + * @param[in] apqns a comma separated list of APQNs. If NULL is specified, + * or an empty string, then all online CCA APQNs are + * handled. + * @param[in] handler a handler function that is called for each APQN + * @param[in] handler_data private data that is passed to the handler + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error + */ +int handle_apqns(const char *apqns, apqn_handler_t handler, void *handler_data, + bool verbose) +{ + int card, domain; + char *copy, *tok; + char *save; + int rc = 0; + + if (apqns == NULL || (apqns != NULL && strlen(apqns) == 0)) { + rc = scan_for_apqns(handler, handler_data, verbose); + } else { + copy = util_strdup(apqns); + tok = strtok_r(copy, ",", &save); + while (tok != NULL) { + + if (sscanf(tok, "%x.%x", &card, &domain) != 2) { + warnx("the APQN '%s' is not valid", + tok); + rc = -EINVAL; + break; + } + + pr_verbose(verbose, "Specified: %02x.%04x", card, + domain); + rc = handler(card, domain, handler_data); + if (rc != 0) + break; + + tok = strtok_r(NULL, ",", &save); + } + free(copy); + } + + return rc; +} + +struct print_apqn_info { + struct util_rec *rec; + bool verbose; +}; + +static int print_apqn_mk_info(int card, int domain, void *handler_data) +{ + struct print_apqn_info *info = (struct print_apqn_info *)handler_data; + struct mk_info mk_info; + int rc; + + rc = sysfs_get_mkvps(card, domain, &mk_info, info->verbose); + if (rc == -ENOTSUP) + return rc; + + util_rec_set(info->rec, "APQN", "%02x.%04x", card, domain); + + if (rc == 0) { + if (mk_info.new_mk.mk_state == MK_STATE_FULL) + util_rec_set(info->rec, "NEW", "%016llx", + mk_info.new_mk.mkvp); + else if (mk_info.new_mk.mk_state == MK_STATE_PARTIAL) + util_rec_set(info->rec, "NEW", "partially loaded"); + else + util_rec_set(info->rec, "NEW", "-"); + + if (mk_info.cur_mk.mk_state == MK_STATE_VALID) + util_rec_set(info->rec, "CUR", "%016llx", + mk_info.cur_mk.mkvp); + else + util_rec_set(info->rec, "CUR", "-"); + + if (mk_info.old_mk.mk_state == MK_STATE_VALID) + util_rec_set(info->rec, "OLD", "%016llx", + mk_info.old_mk.mkvp); + else + util_rec_set(info->rec, "OLD", "-"); + } else { + util_rec_set(info->rec, "NEW", "?"); + util_rec_set(info->rec, "CUR", "?"); + util_rec_set(info->rec, "OLD", "?"); + } + + util_rec_print(info->rec); + + return 0; +} + +/** + * Prints master key information for all specified APQNs + * + * @param[in] apqns a comma separated list of APQNs. If NULL is specified, + * or an empty string, then all online CCA APQNs are + * printed. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. -ENOTSUP is + * returned when the mkvps sysfs attribute is not available, because + * the zcrypt kernel module is on an older level. + */ +int print_mk_info(const char *apqns, bool verbose) +{ + struct print_apqn_info info; + int rc; + + info.verbose = verbose; + info.rec = util_rec_new_wide("-"); + + util_rec_def(info.rec, "APQN", UTIL_REC_ALIGN_LEFT, 11, "CARD.DOMAIN"); + util_rec_def(info.rec, "NEW", UTIL_REC_ALIGN_LEFT, 16, "NEW MK"); + util_rec_def(info.rec, "CUR", UTIL_REC_ALIGN_LEFT, 16, "CURRENT MK"); + util_rec_def(info.rec, "OLD", UTIL_REC_ALIGN_LEFT, 16, "OLD MK"); + util_rec_print_hdr(info.rec); + + rc = handle_apqns(apqns, print_apqn_mk_info, &info, verbose); + + util_rec_free(info.rec); + return rc; +} + +struct cross_check_info { + u64 mkvp; + u64 new_mkvp; + bool key_mkvp; + u32 num_cur_match; + u32 num_old_match; + u32 num_new_match; + bool mismatch; + bool print_mks; + int num_checked; + bool verbose; +}; + +static int cross_check_mk_info(int card, int domain, void *handler_data) +{ + struct cross_check_info *info = (struct cross_check_info *)handler_data; + struct mk_info mk_info; + char temp[200]; + int rc; + + rc = sysfs_get_mkvps(card, domain, &mk_info, info->verbose); + if (rc == -ENODEV) { + info->print_mks = 1; + printf("WARNING: APQN %02x.%04x: Not available or not of " + "type CCA\n", card, domain); + return 0; + } + if (rc != 0) + return rc; + + info->num_checked++; + + if (mk_info.new_mk.mk_state == MK_STATE_PARTIAL) { + info->print_mks = 1; + sprintf(temp, "INFO: APQN %02x.%04x: The NEW master key " + "register is only partially loaded.", card, domain); + util_print_indented(temp, 0); + } + + if (info->new_mkvp == 0 && + mk_info.new_mk.mk_state == MK_STATE_FULL) + info->new_mkvp = mk_info.new_mk.mkvp; + + if (mk_info.new_mk.mk_state == MK_STATE_FULL && + mk_info.new_mk.mkvp != info->new_mkvp) { + info->print_mks = 1; + sprintf(temp, "WARNING: APQN %02x.%04x: The NEW master key " + "register contains a different master key than " + "the NEW register of other APQNs.", card, + domain); + util_print_indented(temp, 0); + } + + if (mk_info.cur_mk.mk_state != MK_STATE_VALID) { + info->print_mks = 1; + info->mismatch = 1; + printf("WARNING: APQN %02x.%04x: No master key is set.\n", card, + domain); + return 0; + } + + if (mk_info.old_mk.mk_state == MK_STATE_VALID && + mk_info.old_mk.mkvp == mk_info.cur_mk.mkvp) { + info->print_mks = 1; + sprintf(temp, "INFO: APQN %02x.%04x: The OLD master key " + "register contains the same master key as the CURRENT " + "master key register.", card, domain); + util_print_indented(temp, 0); + } + if (mk_info.new_mk.mk_state == MK_STATE_FULL && + mk_info.new_mk.mkvp == mk_info.cur_mk.mkvp) { + info->print_mks = 1; + sprintf(temp, "INFO: APQN %02x.%04x: The NEW master key " + "register contains the same master key as the CURRENT " + "master key register.", card, domain); + util_print_indented(temp, 0); + } + if (mk_info.new_mk.mk_state == MK_STATE_FULL && + mk_info.old_mk.mk_state == MK_STATE_VALID && + mk_info.new_mk.mkvp == mk_info.old_mk.mkvp) { + info->print_mks = 1; + sprintf(temp, "INFO: APQN %02x.%04x: The NEW master key " + "register contains the same master key as the OLD " + "master key register.", card, domain); + util_print_indented(temp, 0); + } + + if (info->mkvp == 0) + info->mkvp = mk_info.cur_mk.mkvp; + + if (info->key_mkvp) { + if (mk_info.cur_mk.mk_state == MK_STATE_VALID && + mk_info.cur_mk.mkvp == info->mkvp) + info->num_cur_match++; + + if (mk_info.old_mk.mk_state == MK_STATE_VALID && + mk_info.old_mk.mkvp == info->mkvp) + info->num_old_match++; + + if (mk_info.new_mk.mk_state == MK_STATE_FULL && + mk_info.new_mk.mkvp == info->mkvp) + info->num_new_match++; + } + + if (mk_info.cur_mk.mkvp != info->mkvp) { + + if (info->key_mkvp) { + if (mk_info.old_mk.mk_state == MK_STATE_VALID && + mk_info.old_mk.mkvp == info->mkvp) { + info->print_mks = 1; + sprintf(temp, "INFO: APQN %02x.%04x: The master" + " key has been changed to a new " + "master key, but the secure key has " + "not yet been re-enciphered.", card, + domain); + util_print_indented(temp, 0); + } else if (mk_info.new_mk.mk_state == MK_STATE_FULL && + mk_info.new_mk.mkvp == info->mkvp) { + info->print_mks = 1; + sprintf(temp, "INFO: APQN %02x.%04x: The master" + " key has been changed but is not " + "yet been set (made active).", card, + domain); + util_print_indented(temp, 0); + } else { + info->print_mks = 1; + info->mismatch = 1; + sprintf(temp, "WARNING: APQN %02x.%04x: The " + "CURRENT master key register contains " + "a master key that is different from " + "the one used by the secure key.", card, + domain); + util_print_indented(temp, 0); + } + } else { + info->print_mks = 1; + info->mismatch = 1; + } + } + + return 0; +} + +/** + * Cross checks the master key information for all specified APQNs. It checks + * if all specified APQNs have the same current master key, and if it matches + * the master key specified by the mkvp parameter (optional). If not, it prints + * out an information message about the APQNs that have a different master key. + * + * @param[in] apqns a comma separated list of APQNs. If NULL is specified, + * or an empty string, then all online CCA APQNs are + * checked. + * @param[in] mkvp The master key verification pattern of a secure key. + * If this is all zero, then the master keys are not + * matched against it. + * @param[in] print_mks if true, then a the full master key info of all + * specified APQns is printed, in case of a mismatch. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 for success or a negative errno in case of an error. -ENODEV is + * returned if at least one APQN has a mismatching master key. + * -ENOTSUP is returned when the mkvps sysfs attribute is not + * available, because the zcrypt kernel module is on an older level. + */ +int cross_check_apqns(const char *apqns, u64 mkvp, bool print_mks, bool verbose) +{ + struct cross_check_info info; + char temp[200]; + int rc; + + memset(&info, 0, sizeof(info)); + info.key_mkvp = mkvp != 0; + info.mkvp = mkvp; + info.verbose = verbose; + + pr_verbose(verbose, "Cross checking APQNs with mkvp 0x%016llx: %s", + mkvp, apqns != NULL ? apqns : "ANY"); + + rc = handle_apqns(apqns, cross_check_mk_info, &info, verbose); + if (rc != 0) + return rc; + + if (info.mismatch) { + if (info.key_mkvp) + printf("WARNING: Not all APQNs have the correct master " + "key (%016llx).\n", mkvp); + else + printf("WARNING: Not all APQNs have the same master " + "key.\n"); + + rc = -ENODEV; + } + if (info.num_checked == 0) { + printf("WARNING: None of the APQNs is available or of " + "type CCA\n"); + rc = -ENODEV; + } + if (info.num_old_match > 0 && info.num_new_match > 0) { + sprintf(temp, "WARNING: On %u APQNs the OLD master key " + "register contains the master key use by the secure " + "key, and on %u APQNs the NEW master key register " + "contains the master key use by the secure key.", + info.num_old_match, info.num_new_match); + util_print_indented(temp, 0); + info.print_mks = 1; + rc = -ENODEV; + } + + if (print_mks && info.print_mks) { + printf("\n"); + print_mk_info(apqns, verbose); + printf("\n"); + } + + return rc; +} diff --git a/zkey/utils.h b/zkey/utils.h new file mode 100644 index 0000000..c4dfb2b --- /dev/null +++ b/zkey/utils.h @@ -0,0 +1,54 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * This header file defines the interface to the CCA host library. + * + * Copyright IBM Corp. 2019 + * + * 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 UTILS_H +#define UTILS_H + +#include "lib/zt_common.h" + +int sysfs_is_card_online(int card); + +int sysfs_is_apqn_online(int card, int domain); + +int sysfs_get_serialnr(int card, char serialnr[9], bool verbose); + +#define MK_STATE_EMPTY 0 +#define MK_STATE_PARTIAL 1 +#define MK_STATE_FULL 2 +#define MK_STATE_VALID 3 +#define MK_STATE_INVALID 4 +#define MK_STATE_UNKNOWN -1 + +struct mk_info_reg { + int mk_state; + u64 mkvp; +}; + +struct mk_info { + struct mk_info_reg new_mk; + struct mk_info_reg cur_mk; + struct mk_info_reg old_mk; +}; + +int sysfs_get_mkvps(int card, int domain, struct mk_info *mk_info, + bool verbose); + +typedef int(*apqn_handler_t) (int card, int domain, void *handler_data); + +int handle_apqns(const char *apqns, apqn_handler_t handler, void *handler_data, + bool verbose); + +int print_mk_info(const char *apqns, bool verbose); + +int cross_check_apqns(const char *apqns, u64 mkvp, bool print_mks, + bool verbose); + +#endif diff --git a/zkey/zkey-cryptsetup.1 b/zkey/zkey-cryptsetup.1 index e93b5f0..35e3591 100644 --- a/zkey/zkey-cryptsetup.1 +++ b/zkey/zkey-cryptsetup.1 @@ -91,6 +91,8 @@ behave in the same way as with \fBcryptsetup\fP. .B zkey\-cryptsetup .BR reencipher | re .I device +.RB [ \-\-to\-new | \-N ] +.RB [ \-\-from\-old | \-O ] .RB [ \-\-staged | \-s ] .RB [ \-\-in\-place | \-i ] .RB [ \-\-complete | \-c ] @@ -128,17 +130,36 @@ register can still be used until the master key is changed again. The \fBNEW\fP register contains the new master key to be set. The master key in the \fBNEW\fP register cannot be used until it is made the current master key. You can pro-actively re-encipher a secure key with the -\fBNEW\fP master key before this key is made the \fBCURRENT\fP key. +\fBNEW\fP master key before this key is made the \fBCURRENT\fP key. Use the +.B \-\-to-new +option to do this. .RE .PP -\fBzkey\-cryptsetup\fP automatically detects whether the secure volume key -is currently enciphered with the master key in the \fBOLD\fP register or with -the master key in the \fBCURRENT\fP register. If currently enciphered with the -master key in the \fBOLD\fP register, it is re-enciphered with the master key -in the \fBCURRENT\fP register. If it is currently enciphered with the master -key in the \fBCURRENT\fP register, it is re-enciphered with the master key in -the \fBNEW\fP register. If for this case the \fBNEW\fP register does not -contain a valid master key, then the re-encipher operation fails. +Use the +.B \-\-from\-old +option to re-encipher a secure volume key that is currently enciphered with +the master key in the \fBOLD\fP register with the master key in the +\fBCURRENT\fP register. +.PP +.PP +If both the +.B \-\-from-old +and +.B \-\-to-new +options are specified, a secure volume key that is currently enciphered +with the master key in the \fBOLD\fP register is re-enciphered with the +master key in the \fBNEW\fP register. +.RE +.PP +If both options are omitted, \fBzkey-cryptsetup\fP automatically detects whether +the secure volume key is currently enciphered with the master key in the +\fBOLD\fP register or with the master key in the \fBCURRENT\fP register. +If currently enciphered with the master key in the \fBOLD\fP register, +it is re-enciphered with the master key in the \fBCURRENT\fP register. +If it is currently enciphered with the master key in the \fBCURRENT\fP +register, it is re-enciphered with the master key in the \fBNEW\fP register. +If for this case the \fBNEW\fP register does not contain a valid master key, +then the re-encipher operation fails. .PP Re-enciphering a secure volume key of a volume encrypted with \fBLUKS2\fP and the \fBpaes\fP cipher can be performed \fBin-place\fP, or in @@ -326,6 +347,16 @@ relevance. . .SS "Options for the reencipher command" .TP +.BR \-N ", " \-\-to\-new +Re-enciphers a secure volume key in the LUKS2 header that is currently +enciphered with the master key in the CURRENT register with the master key in +the NEW register. +.TP +.BR \-O ", " \-\-from\-old +Re-enciphers a secure volume key in the LUKS2 header that is currently +enciphered with the master key in the OLD register with the master key in the +CURRENT register. +.TP .BR \-i ", " \-\-in-place Forces an in-place re-enciphering of a secure volume key in the LUKS2 header. This option immediately replaces the secure volume key in the LUKS2 diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c index 8180b4a..c683fd4 100644 --- a/zkey/zkey-cryptsetup.c +++ b/zkey/zkey-cryptsetup.c @@ -34,6 +34,7 @@ #include "misc.h" #include "pkey.h" +#include "cca.h" /* Detect if cryptsetup 2.1 or later is available */ #ifdef CRYPT_LOG_DEBUG_JSON @@ -94,6 +95,8 @@ static struct zkey_cryptsetup_globals { long long keyfile_offset; long long keyfile_size; long long tries; + bool tonew; + bool fromold; bool complete; bool inplace; bool staged; @@ -101,8 +104,7 @@ static struct zkey_cryptsetup_globals { bool batch_mode; bool debug; bool verbose; - void *lib_csulcca; - t_CSNBKTC dll_CSNBKTC; + struct cca_lib cca; int pkey_fd; struct crypt_device *cd; } g = { @@ -162,6 +164,22 @@ static struct util_opt opt_vec[] = { .desc = "OPTIONS", .command = COMMAND_REENCIPHER, }, + { + .option = {"to-new", 0, NULL, 'N'}, + .desc = "Re-enciphers a secure volume key in the LUKS2 header " + "that is currently enciphered with the master key in " + "the CURRENT register with the master key in the NEW " + "register", + .command = COMMAND_REENCIPHER, + }, + { + .option = {"from-old", 0, NULL, 'O'}, + .desc = "Re-enciphers a secure volume key in the LUKS2 header " + "that is currently enciphered with the master key in " + "the OLD register with the master key in the CURRENT " + "register", + .command = COMMAND_REENCIPHER, + }, { .option = {"staged", 0, NULL, 's'}, .desc = "Forces that the re-enciphering of a secure volume " @@ -1275,7 +1293,7 @@ static int activate_unbound_keyslot(int token, int keyslot, const char *key, util_print_indented(complete_msg, 0); util_print_indented("All key slots containing the old volume key are " "now in unbound state. Do you want to remove " - "these key slots?", 0); + "these key slots [y/N]?", 0); if (!prompt_for_yes()) return 0; @@ -1514,17 +1532,19 @@ static int reencipher_prepare(int token) char *password = NULL; size_t password_len; char *key = NULL; + int selected = 1; size_t keysize; int is_old_mk; char *prompt; char *msg; + u64 mkvp; int rc; if (token >= 0) { util_asprintf(&msg, "Staged volume key re-enciphering is " "already initiated for device '%s'. Do you want to " "cancel the pending re-enciphering and start a " - "new re-enciphering process?", g.pos_arg); + "new re-enciphering process [y/N]?", g.pos_arg); util_print_indented(msg, 0); free(msg); @@ -1570,25 +1590,97 @@ static int reencipher_prepare(int token) if (rc < 0) goto out; - util_asprintf(&msg, "The secure volume key of device '%s' is " - "enciphered with the %s CCA master key and is being " - "re-enciphered with the %s CCA master key.", - g.pos_arg, is_old_mk ? "OLD" : "CURRENT", - is_old_mk ? "CURRENT" : "NEW"); - util_print_indented(msg, 0); - free(msg); + if (!g.fromold && !g.tonew) { + /* Autodetect reencipher mode */ + if (is_old_mk) { + g.fromold = 1; + util_asprintf(&msg, "The secure volume key of device " + "'%s' is enciphered with the OLD CCA " + "master key and is being re-enciphered " + "with the CURRENT CCA master key.", + g.pos_arg); + util_print_indented(msg, 0); + free(msg); + } else { + g.tonew = 1; + util_asprintf(&msg, "The secure volume key of device " + "'%s' is enciphered with the CURRENT CCA " + "master key and is being re-enciphered " + "with the NEW CCA master key.", + g.pos_arg); + util_print_indented(msg, 0); + free(msg); + } + } - rc = key_token_change(g.dll_CSNBKTC, (u8 *)key, keysize, - is_old_mk ? METHOD_OLD_TO_CURRENT : - METHOD_CURRENT_TO_NEW, - g.verbose); + rc = get_master_key_verification_pattern((u8 *)key, keysize, &mkvp, + g.verbose); if (rc != 0) { - warnx("Failed to re-encipher the secure volume key of device " - "'%s'", g.pos_arg); - rc = -EINVAL; + warnx("Failed to get the master key verification pattern: %s", + strerror(-rc)); goto out; } + if (g.fromold) { + rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, + FLAG_SEL_CCA_MATCH_OLD_MKVP, + g.verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + util_print_indented("No APQN found that is suitable " + "for re-enciphering the secure AES " + "volume key from the OLD to the " + "CURRENT CCA master key.", 0); + goto out; + } + + rc = key_token_change(&g.cca, (u8 *)key, keysize, + METHOD_OLD_TO_CURRENT, g.verbose); + if (rc != 0) { + warnx("Failed to re-encipher the secure volume key of " + "device '%s'\n", g.pos_arg); + if (!selected) + print_msg_for_cca_envvars( + "secure AES volume key"); + rc = -EINVAL; + goto out; + } + } + + if (g.tonew) { + rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, + FLAG_SEL_CCA_MATCH_CUR_MKVP | + FLAG_SEL_CCA_NEW_MUST_BE_SET, + g.verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + util_print_indented("No APQN found that is suitable " + "for re-enciphering the secure AES " + "volume key from the CURRENT to " + "the NEW CCA master key.", 0); + goto out; + } + + rc = key_token_change(&g.cca, (u8 *)key, keysize, + METHOD_CURRENT_TO_NEW, + g.verbose); + if (rc != 0) { + warnx("Failed to re-encipher the secure volume key of " + "device '%s'\n", g.pos_arg); + if (!selected) + print_msg_for_cca_envvars( + "secure AES volume key"); + rc = -EINVAL; + goto out; + } + } + rc = crypt_keyslot_add_by_key(g.cd, CRYPT_ANY_SLOT, key, keysize, password, password_len, CRYPT_VOLUME_KEY_NO_SEGMENT); @@ -1651,10 +1743,12 @@ static int reencipher_complete(int token) char *password = NULL; size_t password_len; char *key = NULL; + int selected = 1; size_t keysize; int is_old_mk; char *prompt; char *msg; + u64 mkvp; int rc; rc = get_reencipher_token(g.cd, token, &tok, true); @@ -1690,7 +1784,7 @@ static int reencipher_complete(int token) "was completed.\n" "Do you want to re-encipher the secure key with " "the CCA master key in the CURRENT master key " - "register?", g.pos_arg); + "register [y/N]?", g.pos_arg); util_print_indented(msg, 0); free(msg); @@ -1700,11 +1794,38 @@ static int reencipher_complete(int token) goto out; } - rc = key_token_change(g.dll_CSNBKTC, (u8 *)key, keysize, + rc = get_master_key_verification_pattern((u8 *)key, keysize, + &mkvp, g.verbose); + if (rc != 0) { + warnx("Failed to get the master key verification " + "pattern: %s", + strerror(-rc)); + goto out; + } + + rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, + FLAG_SEL_CCA_MATCH_OLD_MKVP, + g.verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + util_print_indented("No APQN found that is suitable " + "for re-enciphering the secure AES " + "volume key from the OLD to the " + "CURRENT CCA master key.", 0); + goto out; + } + + rc = key_token_change(&g.cca, (u8 *)key, keysize, METHOD_OLD_TO_CURRENT, g.verbose); if (rc != 0) { warnx("Failed to re-encipher the secure volume key for " - "device '%s'", g.pos_arg); + "device '%s'\n", g.pos_arg); + if (!selected) + print_msg_for_cca_envvars( + "secure AES volume key"); rc = -EINVAL; goto out; } @@ -1834,6 +1955,7 @@ static int command_validate(void) char *prompt; char *msg; int token; + u64 mkvp; int rc; util_asprintf(&prompt, "Enter passphrase for '%s': ", g.pos_arg); @@ -1864,6 +1986,14 @@ static int command_validate(void) vp_tok_avail = 1; } + rc = get_master_key_verification_pattern((u8 *)key, keysize, + &mkvp, g.verbose); + if (rc != 0) { + warnx("Failed to get the master key verification pattern: %s", + strerror(-rc)); + goto out; + } + printf("Validation of secure volume key of device '%s':\n", g.pos_arg); printf(" Status: %s\n", is_valid ? "Valid" : "Invalid"); printf(" Secure key size: %lu bytes\n", keysize); @@ -1871,11 +2001,12 @@ static int command_validate(void) keysize > SECURE_KEY_SIZE ? "Yes" : "No"); if (is_valid) { printf(" Clear key size: %lu bits\n", clear_keysize); - printf(" Enciphered with: %s CCA master key\n", - is_old_mk ? "OLD" : "CURRENT"); + printf(" Enciphered with: %s CCA master key (MKVP: " + "%016llx)\n", is_old_mk ? "OLD" : "CURRENT", mkvp); } else { printf(" Clear key size: (unknown)\n"); - printf(" Enciphered with: (unknown)\n"); + printf(" Enciphered with: (unknown, MKVP: %016llx)\n", + mkvp); } if (vp_tok_avail) print_verification_pattern(vp_tok.verification_pattern); @@ -2014,12 +2145,13 @@ static int command_setkey(void) util_asprintf(&msg, "The secure key in file '%s' is " "enciphered with the CCA master key in the OLD " "master key register. Do you want to set this " - "key as the new volume key anyway?", + "key as the new volume key anyway [y/N]?", g.master_key_file); util_print_indented(msg, 0); free(msg); if (!prompt_for_yes()) { + warnx("Device '%s' is left unchanged", g.pos_arg); rc = -EINVAL; goto out; } @@ -2078,12 +2210,13 @@ static int command_setkey(void) "be correct. You will lose all data on the " "volume if you set the wrong volume key!\n" "Are you sure that the key in file '%s' is the " - "correct volume key for volume '%s'?", + "correct volume key for volume '%s' [y/N]?", g.master_key_file, g.pos_arg); util_print_indented(msg, 0); free(msg); if (!prompt_for_yes()) { + warnx("Device '%s' is left unchanged", g.pos_arg); rc = -EINVAL; goto out; } @@ -2204,6 +2337,12 @@ int main(int argc, char *argv[]) if (c == -1) break; switch (c) { + case 'N': + g.tonew = 1; + break; + case 'O': + g.fromold = 1; + break; case 'c': g.complete = 1; break; @@ -2286,8 +2425,7 @@ int main(int argc, char *argv[]) } if (command->need_cca_library) { - rc = load_cca_library(&g.lib_csulcca, &g.dll_CSNBKTC, - g.verbose); + rc = load_cca_library(&g.cca, g.verbose); if (rc != 0) { rc = EXIT_FAILURE; goto out; @@ -2329,8 +2467,8 @@ int main(int argc, char *argv[]) rc = command->function(); out: - if (g.lib_csulcca) - dlclose(g.lib_csulcca); + if (g.cca.lib_csulcca) + dlclose(g.cca.lib_csulcca); if (g.pkey_fd >= 0) close(g.pkey_fd); if (g.cd) diff --git a/zkey/zkey.c b/zkey/zkey.c index 2ecbb90..a0dbc0b 100644 --- a/zkey/zkey.c +++ b/zkey/zkey.c @@ -27,9 +27,11 @@ #include "lib/util_prg.h" #include "lib/zt_common.h" +#include "cca.h" #include "keystore.h" #include "misc.h" #include "pkey.h" +#include "utils.h" /* * Program configuration @@ -80,8 +82,7 @@ static struct zkey_globals { bool force; bool open; bool format; - void *lib_csulcca; - t_CSNBKTC dll_CSNBKTC; + struct cca_lib cca; int pkey_fd; struct keystore *keystore; } g = { @@ -832,7 +833,7 @@ static struct zkey_command zkey_commands[] = { .need_pkey_device = 1, .short_desc = "Validate an existing secure AES key", .long_desc = "Validate an existing secure AES key that is " - "either contained in SECURE-KEY-FILE or is stored" + "either contained in SECURE-KEY-FILE or is stored " "in the repository and print information about " "the key", .has_options = 1, @@ -1060,6 +1061,8 @@ static int command_generate_repository(void) */ static int command_generate(void) { + int rc; + if (g.pos_arg != NULL && g.name != NULL) { warnx(" Option '--name|-N' is not valid for generating a key " "outside of the repository"); @@ -1067,7 +1070,7 @@ static int command_generate(void) return EXIT_FAILURE; } if (g.apqns == NULL && g.noapqncheck) { - warnx("Option '--noapqncheck' is only valid together with " + warnx("Option '--no-apqn-check' is only valid together with " "the '--apqns|-a' option"); util_prg_print_parse_error(); return EXIT_FAILURE; @@ -1088,7 +1091,7 @@ static int command_generate(void) return EXIT_FAILURE; } if (g.noapqncheck) { - warnx("Option '--noapqncheck' is not valid for " + warnx("Option '--no-apqn-check' is not valid for " "generating a key outside of the repository"); util_prg_print_parse_error(); return EXIT_FAILURE; @@ -1100,6 +1103,14 @@ static int command_generate(void) return EXIT_FAILURE; } + rc = cross_check_apqns(NULL, 0, true, g.verbose); + if (rc == -EINVAL) + return EXIT_FAILURE; + if (rc != 0 && rc != -ENOTSUP) { + warnx("Your master key setup is improper"); + return EXIT_FAILURE; + } + return g.clearkeyfile ? command_generate_clear() : command_generate_random(); } @@ -1117,7 +1128,9 @@ static int command_reencipher_file(void) { size_t secure_key_size; int rc, is_old_mk; + int selected = 1; u8 *secure_key; + u64 mkvp; if (g.name != NULL) { warnx("Option '--name|-N' is not valid for " @@ -1163,6 +1176,15 @@ static int command_reencipher_file(void) goto out; } + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + &mkvp, g.verbose); + if (rc != 0) { + warnx("Failed to get the master key verification pattern: %s", + strerror(-rc)); + rc = EXIT_FAILURE; + goto out; + } + if (!g.fromold && !g.tonew) { /* Autodetect reencipher option */ if (is_old_mk) { @@ -1194,13 +1216,28 @@ static int command_reencipher_file(void) pr_verbose("Secure key will be re-enciphered from OLD to the " "CURRENT CCA master key"); - rc = key_token_change(g.dll_CSNBKTC, - secure_key, secure_key_size, + rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, + FLAG_SEL_CCA_MATCH_OLD_MKVP, + g.verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + warnx("No APQN found that is suitable for " + "re-enciphering the secure AES volume key"); + rc = EXIT_FAILURE; + goto out; + } + + rc = key_token_change(&g.cca, secure_key, secure_key_size, METHOD_OLD_TO_CURRENT, g.verbose); if (rc != 0) { warnx("Re-encipher from OLD to CURRENT CCA " - "master key has failed"); + "master key has failed\n"); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); rc = EXIT_FAILURE; goto out; } @@ -1209,12 +1246,30 @@ static int command_reencipher_file(void) pr_verbose("Secure key will be re-enciphered from CURRENT " "to the NEW CCA master key"); - rc = key_token_change(g.dll_CSNBKTC, - secure_key, secure_key_size, + rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, + FLAG_SEL_CCA_MATCH_CUR_MKVP | + FLAG_SEL_CCA_NEW_MUST_BE_SET, + g.verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + util_print_indented("No APQN found that is suitable " + "for re-enciphering this secure " + "AES key and has the NEW master " + "key loaded", 0); + rc = EXIT_FAILURE; + goto out; + } + + rc = key_token_change(&g.cca, secure_key, secure_key_size, METHOD_CURRENT_TO_NEW, g.verbose); if (rc != 0) { warnx("Re-encipher from CURRENT to NEW CCA " - "master key has failed"); + "master key has failed\n"); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); rc = EXIT_FAILURE; goto out; } @@ -1270,7 +1325,7 @@ static int command_reencipher_repository(void) rc = keystore_reencipher_key(g.keystore, g.name, g.apqns, g.fromold, g.tonew, g.inplace, g.staged, g.complete, - g.pkey_fd, g.dll_CSNBKTC); + g.pkey_fd, &g.cca); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1302,6 +1357,7 @@ static int command_validate_file(void) size_t clear_key_size; u8 *secure_key; int is_old_mk; + u64 mkvp; int rc; if (g.name != NULL) { @@ -1317,7 +1373,7 @@ static int command_validate_file(void) return EXIT_FAILURE; } if (g.noapqncheck) { - warnx("Option '--noapqncheck' is not valid for " + warnx("Option '--no-apqn-check' is not valid for " "validating a key outside of the repository"); util_prg_print_parse_error(); return EXIT_FAILURE; @@ -1348,19 +1404,37 @@ static int command_validate_file(void) goto out; } + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + &mkvp, g.verbose); + if (rc != 0) { + warnx("Failed to get the master key verification pattern: %s", + strerror(-rc)); + rc = EXIT_FAILURE; + goto out; + } + printf("Validation of secure key in file '%s':\n", g.pos_arg); printf(" Status: Valid\n"); printf(" Secure key size: %lu bytes\n", secure_key_size); printf(" Clear key size: %lu bits\n", clear_key_size); printf(" XTS type key: %s\n", secure_key_size > SECURE_KEY_SIZE ? "Yes" : "No"); - printf(" Enciphered with: %s CCA master key\n", - is_old_mk ? "OLD" : "CURRENT"); + printf(" Enciphered with: %s CCA master key (MKVP: %016llx)\n", + is_old_mk ? "OLD" : "CURRENT", mkvp); printf(" Verification pattern: %.*s\n", VERIFICATION_PATTERN_LEN / 2, vp); printf(" %.*s\n", VERIFICATION_PATTERN_LEN / 2, &vp[VERIFICATION_PATTERN_LEN / 2]); + rc = cross_check_apqns(NULL, mkvp, true, g.verbose); + if (rc == -EINVAL) + return EXIT_FAILURE; + if (rc != 0 && rc != -ENOTSUP) { + warnx("Your master key setup is improper"); + rc = EXIT_FAILURE; + goto out; + } + out: free(secure_key); return rc; @@ -1375,13 +1449,6 @@ static int command_validate_repository(void) { int rc; - if (g.apqns == NULL && g.noapqncheck) { - warnx("Option '--noapqncheck' is only valid together with " - "the '--apqns|-a' option"); - util_prg_print_parse_error(); - return EXIT_FAILURE; - } - rc = keystore_validate_key(g.keystore, g.name, g.apqns, g.noapqncheck, g.pkey_fd); @@ -1421,7 +1488,7 @@ static int command_import(void) g.sector_size = 0; if (g.apqns == NULL && g.noapqncheck) { - warnx("Option '--noapqncheck' is only valid together with " + warnx("Option '--no-apqn-check' is only valid together with " "the '--apqns|-a' option"); util_prg_print_parse_error(); return EXIT_FAILURE; @@ -1501,7 +1568,7 @@ static int command_change(void) return EXIT_FAILURE; } if (g.apqns == NULL && g.noapqncheck) { - warnx("Option '--noapqncheck' is only valid together with " + warnx("Option '--no-apqn-check' is only valid together with " "the '--apqns|-a' option"); util_prg_print_parse_error(); return EXIT_FAILURE; @@ -1874,8 +1941,7 @@ int main(int argc, char *argv[]) } if (command->need_cca_library) { - rc = load_cca_library(&g.lib_csulcca, &g.dll_CSNBKTC, - g.verbose); + rc = load_cca_library(&g.cca, g.verbose); if (rc != 0) { rc = EXIT_FAILURE; goto out; @@ -1894,8 +1960,8 @@ int main(int argc, char *argv[]) rc = command->function(); out: - if (g.lib_csulcca) - dlclose(g.lib_csulcca); + if (g.cca.lib_csulcca) + dlclose(g.cca.lib_csulcca); if (g.pkey_fd >= 0) close(g.pkey_fd); if (g.keystore) -- 2.21.3 From c5e94c1cf3b03d290752227d21540b8786df8131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 7 Nov 2019 13:23:55 +0100 Subject: [PATCH 35/56] zkey: Add support for CCA AES CIPHER keys (#1719623) Description: With CCA 5 there is a new secure key type, the so called variable length symmetric cipher key token. This token format can hold AES keys with size 128, 192 and 256 bits together with additional attributes cryptographic bound to the key token. The attributes may limit the usage of the key, for example restrict export or usability scope. So this key type is considered to be even more secure than the traditional secure key token. This key token type is also called "CCA AES CIPHER key", where the formerly used key token is called "CCA AES DATA key". The zkey as well as the zkey-cryptsetup tools are enhanced to support AES CIPHER keys. That is, zkey can manage AES DATA keys, as well as AES CIPHER keys. The key type must be specified at key generation time, the default is to generate AED DATA keys. Upstream-ID: 9de85f42951e0b1a3d083363d7000b1950aebcd7 Upstream-ID: 91c35543ca7fd25691487c61ec2e308f2903a6b8 Upstream-ID: b47007b8ac8b446eb94b06e7ed3050b3df3e80e8 Upstream-ID: 298fab68fee86cb9b1862d60ca274971d4c39638 Upstream-ID: ddde3f354f3506521877a4e2a6082c4d597629cb Upstream-ID: d4027e6506963fbf995992e32490d56a6f7ea587 Upstream-ID: 663d362ff3b1036476bfce9e2563272bab087013 Upstream-ID: b56c74fe7b6100b9a2ef17b8847cd850309cf487 Upstream-ID: 0fab6bdf2aa01e093f8a4f3d86c9183889a587fe Upstream-ID: 560b672bfad3c7bb6631ed4e1676a8b2c836e030 Upstream-ID: b7bb90c552f9b62c0b4ddc1295e76769149ee6bb Upstream-ID: b0cc0e47378de9cd82b0cd14228b26be4d615ffc Upstream-ID: 7d4b1e18b6195f48414f42b4655f900872fed1e7 Upstream-ID: e7d79d5c5c0928c1bdbd6b669a6e70b8fd3352a5 Upstream-ID: 7fede7021ece58b9960532e17d963f844fe0de02 Upstream-ID: 0d9e42264db9935e28f663802c5b95795af79160 Upstream-ID: a86e41a51827b524c5f88db5e24282166df9b3c8 Upstream-ID: bc987c8d18ddeb6fec46113a7fe7588555b592e7 Upstream-ID: 9894e391ef26fd31e2995f19965b7ce5bf006caa --- zkey/cca.c | 385 ++++++++++- zkey/cca.h | 51 ++ zkey/keystore.c | 605 +++++++++++++----- zkey/keystore.h | 11 +- zkey/pkey.c | 1385 ++++++++++++++++++++++++++++++++++------ zkey/pkey.h | 193 +++++- zkey/utils.c | 104 ++- zkey/utils.h | 8 +- zkey/zkey-cryptsetup.c | 44 +- zkey/zkey.1 | 142 +++- zkey/zkey.c | 308 ++++++++- 11 files changed, 2809 insertions(+), 427 deletions(-) diff --git a/zkey/cca.c b/zkey/cca.c index 2669c75..01f7bfd 100644 --- a/zkey/cca.c +++ b/zkey/cca.c @@ -55,6 +55,14 @@ static void print_CCA_error(int return_code, int reason_code) warnx("The secure key has a CCA master key " "verification pattern that is not valid"); break; + case 90: + warnx("The operation has been rejected due to access " + "control checking"); + break; + case 2143: + warnx("The operation has been rejected due to key " + "export restrictions of the secure key"); + break; } break; case 12: @@ -142,6 +150,9 @@ int load_cca_library(struct cca_lib *cca, bool verbose) /* Get the Key Token Change function */ cca->dll_CSNBKTC = (t_CSNBKTC)dlsym(cca->lib_csulcca, "CSNBKTC"); + /* Get the Key Token Change 2 function */ + cca->dll_CSNBKTC2 = (t_CSNBKTC2)dlsym(cca->lib_csulcca, "CSNBKTC2"); + /* Get the Cryptographic Facility Query function */ cca->dll_CSUACFQ = (t_CSUACFQ)dlsym(cca->lib_csulcca, "CSUACFQ"); @@ -151,11 +162,20 @@ int load_cca_library(struct cca_lib *cca, bool verbose) /* Cryptographic Resource Deallocate function */ cca->dll_CSUACRD = (t_CSUACRD)dlsym(cca->lib_csulcca, "CSUACRD"); + /* Get the Key Translate 2 function */ + cca->dll_CSNBKTR2 = (t_CSNBKTR2)dlsym(cca->lib_csulcca, "CSNBKTR2"); + + /* Get the Restrict Key Attribute function */ + cca->dll_CSNBRKA = (t_CSNBRKA)dlsym(cca->lib_csulcca, "CSNBRKA"); + if (cca->dll_CSUACFV == NULL || cca->dll_CSNBKTC == NULL || + cca->dll_CSNBKTC2 == NULL || cca->dll_CSUACFQ == NULL || cca->dll_CSUACRA == NULL || - cca->dll_CSUACRD == NULL) { + cca->dll_CSUACRD == NULL || + cca->dll_CSNBKTR2 == NULL || + cca->dll_CSNBRKA == NULL) { pr_verbose(verbose, "%s", dlerror()); warnx("The command requires the IBM CCA Host Libraries and " "Tools.\nFor the supported environments and downloads, " @@ -187,10 +207,13 @@ int key_token_change(struct cca_lib *cca, u8 *secure_key, unsigned int secure_key_size, char *method, bool verbose) { + struct aescipherkeytoken *cipherkey = + (struct aescipherkeytoken *)secure_key; long exit_data_len = 0, rule_array_count; unsigned char rule_array[2 * 8] = { 0, }; unsigned char exit_data[4] = { 0, }; long return_code, reason_code; + long key_token_length; util_assert(cca != NULL, "Internal error: cca is NULL"); util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); @@ -202,33 +225,77 @@ int key_token_change(struct cca_lib *cca, memcpy(rule_array + 8, "AES ", 8); rule_array_count = 2; - cca->dll_CSNBKTC(&return_code, &reason_code, - &exit_data_len, exit_data, - &rule_array_count, rule_array, - secure_key); - - pr_verbose(verbose, "CSNBKTC (Key Token Change) with '%s' returned: " - "return_code: %ld, reason_code: %ld", method, return_code, - reason_code); - if (return_code != 0) { - print_CCA_error(return_code, reason_code); - return -EIO; - } - - if (secure_key_size == 2 * SECURE_KEY_SIZE) { + if (is_cca_aes_data_key(secure_key, secure_key_size)) { cca->dll_CSNBKTC(&return_code, &reason_code, &exit_data_len, exit_data, &rule_array_count, rule_array, - secure_key + SECURE_KEY_SIZE); + secure_key); pr_verbose(verbose, "CSNBKTC (Key Token Change) with '%s' " "returned: return_code: %ld, reason_code: %ld", method, return_code, reason_code); + } else if (is_cca_aes_cipher_key(secure_key, secure_key_size)) { + key_token_length = cipherkey->length; + cca->dll_CSNBKTC2(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &key_token_length, + (unsigned char *)cipherkey); + + pr_verbose(verbose, "CSNBKTC2 (Key Token Change2) with '%s' " + "returned: return_code: %ld, reason_code: %ld", + method, return_code, reason_code); + + pr_verbose(verbose, "key_token_length: %lu", key_token_length); + } else { + warnx("Invalid key type specified"); + return -EINVAL; + } + + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + + if (is_xts_key(secure_key, secure_key_size)) { + if (is_cca_aes_data_key(secure_key, secure_key_size)) { + cca->dll_CSNBKTC(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + secure_key + AESDATA_KEY_SIZE); + + pr_verbose(verbose, "CSNBKTC (Key Token Change) with " + "'%s' returned: return_code: %ld, " + "reason_code: %ld", method, return_code, + reason_code); + } else if (is_cca_aes_cipher_key(secure_key, secure_key_size)) { + cipherkey = (struct aescipherkeytoken *)(secure_key + + AESCIPHER_KEY_SIZE); + key_token_length = cipherkey->length; + cca->dll_CSNBKTC2(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &key_token_length, + (unsigned char *)cipherkey); + + pr_verbose(verbose, "CSNBKTC2 (Key Token Change2) with " + "'%s' returned: return_code: %ld, " + "reason_code: %ld", method, return_code, + reason_code); + + pr_verbose(verbose, "key_token_length: %lu", + key_token_length); + } else { + warnx("Invalid key type specified"); + return -EINVAL; + } + if (return_code != 0) { print_CCA_error(return_code, reason_code); return -EIO; } } + return 0; } @@ -422,6 +489,58 @@ static int get_cca_adapter_serialnr(struct cca_lib *cca, char serialnr[9], return 0; } +/** + * Queries the firmware version of the current CCA adapter + * + * @param[in] cca the CCA library structure + * @param[out] version the struct where the version is returned + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. + */ +static int get_cca_adapter_version(struct cca_lib *cca, + struct cca_version *version, + bool verbose) +{ + long exit_data_len = 0, rule_array_count, verb_data_length = 0; + unsigned char rule_array[6 * 8] = { 0, }; + unsigned char exit_data[4] = { 0, }; + long return_code, reason_code; + char version_data[9]; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + + memset(rule_array, 0, sizeof(rule_array)); + memcpy(rule_array, "STATCCA ", 8); + rule_array_count = 1; + + cca->dll_CSUACFQ(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &verb_data_length, NULL); + + pr_verbose(verbose, "CSUACFQ (Cryptographic Facility Query) returned: " + "return_code: %ld, reason_code: %ld", return_code, + reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + + memcpy(version_data, rule_array+3*8, 8); + version_data[8] = '\0'; + + pr_verbose(verbose, "CCA firmware version string: %s", version_data); + + if (sscanf((char *)version_data, "%u.%u.%uz", &version->ver, + &version->rel, &version->mod) != 3) { + warnx("CCA formware version is invalid: %s", version_data); + return -EINVAL; + } + + return 0; +} + /** * Selects the specified APQN to be used for the CCA host library. * @@ -626,3 +745,237 @@ void print_msg_for_cca_envvars(const char *key_name) util_print_indented(msg, 0); free(msg); } + +/* + * Convert a secure key of type CCA-AESDATA into a secure key of type + * CCA-AESCIPHER. + * + * @param[in] cca the CCA library structure + * @param[in] input_key the secure key to convert + * @param[in] input_key_size the size of the secure key to convert + * @param[in] output_key buffer for the converted secure key + * @param[in/out] output_key_size on input: size of the output buffer. + * on exit: size of the converted secure key + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. + */ +int convert_aes_data_to_cipher_key(struct cca_lib *cca, + u8 *input_key, unsigned int input_key_size, + u8 *output_key, + unsigned int *output_key_size, + bool verbose) +{ + long input_token_size, output_token_size, zero = 0; + long exit_data_len = 0, rule_array_count = 0; + unsigned char *input_token, *output_token; + unsigned char rule_array[8 * 2] = { 0, }; + unsigned char null_token[64] = { 0, }; + long null_token_len = sizeof(null_token); + unsigned char exit_data[4] = { 0, }; + struct aescipherkeytoken *cipherkey; + long return_code, reason_code; + struct cca_version version; + unsigned char buffer[800]; + int rc; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + util_assert(input_key != NULL, "Internal error: input_key is NULL"); + util_assert(output_key != NULL, "Internal error: output_key is NULL"); + util_assert(output_key_size != NULL, + "Internal error: output_key_size is NULL"); + + if (is_cca_aes_cipher_key(input_key, input_key_size)) { + warnx("Invalid key-type specified"); + return -EINVAL; + } + + if (*output_key_size < (is_xts_key(input_key, input_key_size) ? + 2 * AESCIPHER_KEY_SIZE : AESCIPHER_KEY_SIZE)) + return -EINVAL; + + /* + * We need a CCA firmware version 6.3.27 or later to support + * conversion of secure keys that are exportable to CPACF protected keys + */ + rc = get_cca_adapter_version(cca, &version, verbose); + if (rc != 0) + return rc; + if (version.ver < 6 || + (version.ver == 6 && version.rel < 3) || + (version.ver == 6 && version.rel < 3 && version.mod < 27)) { + util_print_indented("The used CCA firmware version does not " + "support converting a secure key that can " + "be used with the PAES cipher. The " + "required CCA firmware version is 6.3.27 " + "or later. For the supported environments " + "and updates, see: " CCA_WEB_PAGE, 0); + return -ENOTSUP; + } + + input_token = input_key; + input_token_size = AESDATA_KEY_SIZE; + output_token = buffer; + output_token_size = sizeof(buffer); + memset(buffer, 0, sizeof(buffer)); + + memcpy(rule_array, "AES ", 8); + memcpy(rule_array + 8, "REFORMAT", 8); + rule_array_count = 2; + + cca->dll_CSNBKTR2(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &input_token_size, input_token, + &null_token_len, null_token, + &zero, NULL, + &output_token_size, output_token); + + pr_verbose(verbose, "CSNBKTR2 (Key Translate2) " + "returned: return_code: %ld, reason_code: %ld", return_code, + reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + + pr_verbose(verbose, "output_token_size: %lu", output_token_size); + if (output_token_size > (long)AESCIPHER_KEY_SIZE) { + pr_verbose(verbose, "Output key token too large"); + return -EINVAL; + } + + /* + * Check if the converted key allows export to CPACF protected key. + * If not, then the CCA host library or firmware code level is too low. + */ + cipherkey = (struct aescipherkeytoken *)buffer; + if ((cipherkey->kmf1 & 0x0800) == 0) { + util_print_indented("The used CCA firmware version does not " + "support converting a secure key that can " + "be used with the PAES cipher. The " + "required CCA firmware version is 6.3.27 " + "or later. For the supported environments " + "and updates, see: " CCA_WEB_PAGE, 0); + return -ENOTSUP; + } + + memset(output_key, 0, *output_key_size); + memcpy(output_key, buffer, output_token_size); + *output_key_size = AESCIPHER_KEY_SIZE; + + if (is_xts_key(input_key, input_key_size)) { + input_token = input_key + AESDATA_KEY_SIZE; + input_token_size = AESDATA_KEY_SIZE; + output_token = buffer; + output_token_size = sizeof(buffer); + memset(buffer, 0, sizeof(buffer)); + + cca->dll_CSNBKTR2(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &input_token_size, input_token, + &null_token_len, null_token, + &zero, NULL, + &output_token_size, output_token); + + pr_verbose(verbose, "CSNBKTR2 (Key Translate2) " + "returned: return_code: %ld, reason_code: %ld", + return_code, reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + + pr_verbose(verbose, "output_token_size: %lu", + output_token_size); + if (output_token_size > (long)AESCIPHER_KEY_SIZE) { + pr_verbose(verbose, "Output key token too large"); + return -EINVAL; + } + + memcpy(output_key + AESCIPHER_KEY_SIZE, buffer, + output_token_size); + *output_key_size += AESCIPHER_KEY_SIZE; + } + + return 0; +} + +/* + * Restrict the exportability of an AES CIPHER key. It restricts export by means + * of NOEX-AES, NOEX-DES, NOEX-RSA, NOEX-SYM, NOEXUASY, NOEXAASY, NOEX-RAW + * keywords. + * When this function is called with an AES DATA key, it does nothing and + * returns 0. AES DATA keys can not be export restricted. + * + * @param[in] cca the CCA library structure + * @param[in] secure_key the secure key to restrict + * @param[in] secure_key_size the size of the secure key to restrict + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. + */ +int restrict_key_export(struct cca_lib *cca, u8 *secure_key, + unsigned int secure_key_size, bool verbose) +{ + struct aescipherkeytoken *cipherkey = + (struct aescipherkeytoken *)secure_key; + long exit_data_len = 0, rule_array_count = 0; + unsigned char rule_array[8 * 8] = { 0, }; + unsigned char exit_data[4] = { 0, }; + long return_code, reason_code; + long token_length, zero = 0; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + + if (!is_cca_aes_cipher_key(secure_key, secure_key_size)) + return 0; + + memcpy(rule_array, "AES ", 8); + memcpy(rule_array + 8, "NOEX-AES", 8); + memcpy(rule_array + 16, "NOEX-DES", 8); + memcpy(rule_array + 24, "NOEX-RSA", 8); + memcpy(rule_array + 32, "NOEX-SYM", 8); + memcpy(rule_array + 40, "NOEXUASY", 8); + memcpy(rule_array + 48, "NOEXAASY", 8); + memcpy(rule_array + 56, "NOEX-RAW", 8); + rule_array_count = 8; + + token_length = cipherkey->length; + cca->dll_CSNBRKA(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &token_length, (unsigned char *)secure_key, + &zero, NULL, &zero, NULL, &zero, NULL); + + pr_verbose(verbose, "CSNBRKA (Restrict Key Attribute) " + "returned: return_code: %ld, reason_code: %ld", return_code, + reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + + if (is_xts_key(secure_key, secure_key_size)) { + cipherkey = (struct aescipherkeytoken *)(secure_key + + AESCIPHER_KEY_SIZE); + token_length = cipherkey->length; + cca->dll_CSNBRKA(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &token_length, (unsigned char *)cipherkey, + &zero, NULL, &zero, NULL, &zero, NULL); + + pr_verbose(verbose, "CSNBRKA (Restrict Key Attribute) " + "returned: return_code: %ld, reason_code: %ld", + return_code, reason_code); + if (return_code != 0) { + print_CCA_error(return_code, reason_code); + return -EIO; + } + } + + return 0; +} diff --git a/zkey/cca.h b/zkey/cca.h index a79e2ee..2b248ec 100644 --- a/zkey/cca.h +++ b/zkey/cca.h @@ -25,6 +25,15 @@ typedef void (*t_CSNBKTC)(long *return_code, unsigned char *rule_array, unsigned char *key_identifier); +typedef void (*t_CSNBKTC2)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *key_identifier_length, + unsigned char *key_identifier); + typedef void (*t_CSUACFV)(long *return_code, long *reason_code, long *exit_data_length, @@ -59,6 +68,36 @@ typedef void (*t_CSUACRD)(long *return_code, long *ressource_name_length, unsigned char *ressource_name); +typedef void (*t_CSNBKTR2)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *input_key_token_length, + unsigned char *input_key_token, + long *input_KEK_key_identifier_length, + unsigned char *input_KEK_key_identifier, + long *output_KEK_key_identifier_length, + unsigned char *output_KEK_key_identifier, + long *output_key_token_length, + unsigned char *output_key_token); + +typedef void (*t_CSNBRKA)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *key_identifier_length, + unsigned char *key_identifier, + long *ey_encrypting_key_identifier_length, + unsigned char *ey_encrypting_key_identifier, + long *opt_parameter1_length, + unsigned char *opt_parameter1, + long *opt_parameter2_length, + unsigned char *opt_parameter2); + struct cca_version { unsigned int ver; unsigned int rel; @@ -68,10 +107,13 @@ struct cca_version { struct cca_lib { void *lib_csulcca; t_CSNBKTC dll_CSNBKTC; + t_CSNBKTC2 dll_CSNBKTC2; t_CSUACFV dll_CSUACFV; t_CSUACFQ dll_CSUACFQ; t_CSUACRA dll_CSUACRA; t_CSUACRD dll_CSUACRD; + t_CSNBKTR2 dll_CSNBKTR2; + t_CSNBRKA dll_CSNBRKA; struct cca_version version; }; @@ -92,4 +134,13 @@ int select_cca_adapter_by_mkvp(struct cca_lib *cca, u64 mkvp, const char *apqns, void print_msg_for_cca_envvars(const char *key_name); +int convert_aes_data_to_cipher_key(struct cca_lib *cca, + u8 *input_key, unsigned int input_key_size, + u8 *output_key, + unsigned int *output_key_size, + bool verbose); + +int restrict_key_export(struct cca_lib *cca, u8 *secure_key, + unsigned int secure_key_size, bool verbose); + #endif diff --git a/zkey/keystore.c b/zkey/keystore.c index 957ebb0..af67721 100644 --- a/zkey/keystore.c +++ b/zkey/keystore.c @@ -70,13 +70,12 @@ struct key_filenames { #define DEFAULT_VOLUME_TYPE VOLUME_TYPE_PLAIN #endif -#define IS_XTS(secure_key_size) (secure_key_size > SECURE_KEY_SIZE ? 1 : 0) - #define REC_KEY "Key" #define REC_DESCRIPTION "Description" #define REC_SEC_KEY_SIZE "Secure key size" #define REC_CLR_KEY_SIZE "Clear key size" #define REC_XTS "XTS type key" +#define REC_KEY_TYPE "Key type" #define REC_VOLUMES "Volumes" #define REC_APQNS "APQNs" #define REC_KEY_FILE "Key file name" @@ -313,6 +312,41 @@ static char *_keystore_get_volume_type(struct properties *properties) return type; } +/** + * Returns the key type contained in the properties. If no key type + * property is contained, then 'CCA-AESDATA' is assumed (for backward + * compatibility). + * + * @returns a string containing the key type. Must be freed by the caller. + */ +static char *_keystore_get_key_type(struct properties *properties) +{ + char *type; + + type = properties_get(properties, PROP_NAME_KEY_TYPE); + if (type == NULL) + type = util_strdup(KEY_TYPE_CCA_AESDATA); + + return type; +} + +/** + * Checks if the key type is supported. + * + * @param[in] key_type the key type + * + * @returns 1 if the key type is valid, 0 otherwise + */ +static int _keystore_valid_key_type(const char *key_type) +{ + if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0) + return 1; + if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) + return 1; + + return 0; +} + /** * Prints a message followed by a list of associated volumes, if volumes are * associated and the volume-type matches (if specified) @@ -816,6 +850,33 @@ static int _keystore_match_volume_type_property(struct properties *properties, return rc; } +/** + * Checks if the key type property matches the specified key type. + * If the properties do not contain a key type property, then the default + * key type is assumed. + * + * @param[in] properties a properties object + * @param[in] key_type the key type to match. Can be NULL. In this case + * it always matches. + * + * @returns 1 for a match, 0 for not matched + */ +static int _keystore_match_key_type_property(struct properties *properties, + const char *key_type) +{ + char *type; + int rc = 0; + + if (key_type == NULL) + return 1; + + type = _keystore_get_key_type(properties); + if (strcasecmp(type, key_type) == 0) + rc = 1; + + free(type); + return rc; +} /** * Checks if a key name matches a name filter @@ -881,6 +942,7 @@ typedef int (*process_key_t)(struct keystore *keystore, * mutliple APQN filters separated by commas. * NULL means no APQN filter. * @param[in] volume_type If not NULL, specifies the volume type. + * @param[in] key_type The key type. NULL means no key type filter. * @param[in] process_func the callback function called for a matching key * @param[in/out] process_private private data passed to the process_func * @@ -893,6 +955,7 @@ static int _keystore_process_filtered(struct keystore *keystore, const char *volume_filter, const char *apqn_filter, const char *volume_type, + const char *key_type, process_key_t process_func, void *process_private) { @@ -984,6 +1047,15 @@ static int _keystore_process_filtered(struct keystore *keystore, goto free_prop; } + rc = _keystore_match_key_type_property(key_props, + key_type); + if (rc == 0) { + pr_verbose(keystore, + "Key '%s' filtered out due to key type", + name); + goto free_prop; + } + rc = process_func(keystore, name, key_props, &file_names, process_private); if (rc != 0) { @@ -1192,7 +1264,7 @@ static int _keystore_volume_check(const char *volume, bool remove, bool set, info->set = set; rc = _keystore_process_filtered(info->keystore, NULL, info->volume, - NULL, NULL, + NULL, NULL, NULL, _keystore_volume_check_process, info); out: free((void *)info->volume); @@ -1368,7 +1440,7 @@ static int _keystore_generate_verification_pattern(struct keystore *keystore, if (key == NULL) return -EIO; - rc = generate_key_verification_pattern((const char *)key, key_size, + rc = generate_key_verification_pattern(key, key_size, vp, vp_len, keystore->verbose); free(key); @@ -1453,10 +1525,6 @@ static int _keystore_set_default_properties(struct properties *key_props) { int rc; - rc = properties_set(key_props, PROP_NAME_KEY_TYPE, "CCA-AESDATA"); - if (rc != 0) - return rc; - rc = properties_set(key_props, PROP_NAME_CIPHER, "paes"); if (rc != 0) return rc; @@ -1491,6 +1559,7 @@ static int _keystore_set_default_properties(struct properties *key_props) * the sector size is not specified and the system * default is used. * @param[in] volume_type the type of volume + * @param[in] key_type the type of the key */ static int _keystore_create_info_file(struct keystore *keystore, const char *name, @@ -1499,7 +1568,8 @@ static int _keystore_create_info_file(struct keystore *keystore, const char *volumes, const char *apqns, bool noapqncheck, size_t sector_size, - const char *volume_type) + const char *volume_type, + const char *key_type) { struct volume_check vol_check = { .keystore = keystore, .name = name, .set = 0 }; @@ -1521,6 +1591,12 @@ static int _keystore_create_info_file(struct keystore *keystore, goto out; } + rc = properties_set2(key_props, PROP_NAME_KEY_TYPE, key_type, true); + if (rc != 0) { + warnx("Invalid characters in key-type"); + goto out; + } + rc = _keystore_change_association(key_props, PROP_NAME_VOLUMES, volumes != NULL ? volumes : "", "volume", _keystore_volume_check, @@ -1590,52 +1666,6 @@ out: return rc; } -/** - * Extracts an online card/domain pair from the specified APQns. If none of the - * specified APQNs are online, then -ENODEV is returned. - * If no APQNs are specified at all, then it uses AUTOSELECT and returns zero. - */ -static int _keystore_get_card_domain(const char *apqns, unsigned int *card, - unsigned int *domain) -{ - struct apqn_check apqn_check = { .noonlinecheck = 0, .nomsg = 1 }; - char **apqn_list; - char *normalized = NULL; - int rc = 0; - int i; - - *card = AUTOSELECT; - *domain = AUTOSELECT; - - if (apqns == NULL) - return 0; - - apqn_list = str_list_split(apqns); - if (apqn_list[0] == NULL) - goto out; - - for (i = 0; apqn_list[i] != NULL; i++) { - rc = _keystore_apqn_check(apqn_list[i], 0, 0, &normalized, - &apqn_check); - if (normalized != NULL) - free(normalized); - if (rc == -EINVAL) - goto out; - if (rc != 0) - continue; - - if (sscanf(apqn_list[i], "%x.%x", card, domain) == 2) - goto found; - } - - warnx("None of the specified APQNs is online or of type CCA"); - rc = -ENODEV; -found: -out: - str_list_free_string_array(apqn_list); - return rc; -} - /** * Generates a secure key by random and adds it to the key store * @@ -1658,6 +1688,7 @@ out: * clear key contained in the file denoted here. * if NULL, the secure key is generated by random. * @param[in] volume_type the type of volume + * @param[in] key_type the type of the key * @param[in] pkey_fd the file descriptor of /dev/pkey * * @returns 0 for success or a negative errno in case of an error @@ -1667,15 +1698,21 @@ int keystore_generate_key(struct keystore *keystore, const char *name, const char *apqns, bool noapqncheck, size_t sector_size, size_t keybits, bool xts, const char *clear_key_file, const char *volume_type, - int pkey_fd) + const char *key_type, int pkey_fd) { struct key_filenames file_names = { NULL, NULL, NULL }; struct properties *key_props = NULL; - unsigned int card, domain; + char **apqn_list = NULL; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); + util_assert(key_type != NULL, "Internal error: key_type is NULL"); + + if (!_keystore_valid_key_type(key_type)) { + warnx("Invalid key-type specified"); + return -EINVAL; + } rc = _keystore_get_key_filenames(keystore, name, &file_names); if (rc != 0) @@ -1685,7 +1722,9 @@ int keystore_generate_key(struct keystore *keystore, const char *name, if (rc != 0) goto out_free_key_filenames; - rc = cross_check_apqns(apqns, 0, true, keystore->verbose); + rc = cross_check_apqns(apqns, 0, + get_min_card_level_for_keytype(key_type), true, + keystore->verbose); if (rc == -EINVAL) goto out_free_key_filenames; if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { @@ -1693,20 +1732,21 @@ int keystore_generate_key(struct keystore *keystore, const char *name, goto out_free_key_filenames; } - rc = _keystore_get_card_domain(apqns, &card, &domain); - if (rc != 0) - goto out_free_key_filenames; + if (apqns != NULL) + apqn_list = str_list_split(apqns); if (clear_key_file == NULL) rc = generate_secure_key_random(pkey_fd, file_names.skey_filename, - keybits, xts, card, domain, + keybits, xts, key_type, + (const char **)apqn_list, keystore->verbose); else rc = generate_secure_key_clear(pkey_fd, file_names.skey_filename, keybits, xts, clear_key_file, - card, domain, + key_type, + (const char **)apqn_list, keystore->verbose); if (rc != 0) goto out_free_props; @@ -1717,7 +1757,8 @@ int keystore_generate_key(struct keystore *keystore, const char *name, rc = _keystore_create_info_file(keystore, name, &file_names, description, volumes, apqns, - noapqncheck, sector_size, volume_type); + noapqncheck, sector_size, volume_type, + key_type); if (rc != 0) goto out_free_props; @@ -1727,6 +1768,8 @@ int keystore_generate_key(struct keystore *keystore, const char *name, file_names.info_filename); out_free_props: + if (apqn_list != NULL) + str_list_free_string_array(apqn_list); if (key_props != NULL) properties_free(key_props); if (rc != 0) @@ -1758,17 +1801,21 @@ out_free_key_filenames: * default is used. * @param[in] import_file The name of a secure key containing the key to import * @param[in] volume_type the type of volume + * @param[in] cca the CCA library struct * * @returns 0 for success or a negative errno in case of an error */ int keystore_import_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, const char *apqns, bool noapqncheck, size_t sector_size, - const char *import_file, const char *volume_type) + const char *import_file, const char *volume_type, + struct cca_lib *cca) { struct key_filenames file_names = { NULL, NULL, NULL }; struct properties *key_props = NULL; size_t secure_key_size; + const char *key_type; + int selected = 1; u8 *secure_key; u64 mkvp; int rc; @@ -1792,6 +1839,14 @@ int keystore_import_key(struct keystore *keystore, const char *name, goto out_free_key_filenames; } + key_type = get_key_type(secure_key, secure_key_size); + if (key_type == NULL) { + warnx("Key '%s' is not a valid secure key", name); + free(secure_key); + rc = -EINVAL; + goto out_free_key_filenames; + } + rc = get_master_key_verification_pattern(secure_key, secure_key_size, &mkvp, keystore->verbose); if (rc != 0) { @@ -1800,7 +1855,9 @@ int keystore_import_key(struct keystore *keystore, const char *name, goto out_free_key; } - rc = cross_check_apqns(apqns, mkvp, true, keystore->verbose); + rc = cross_check_apqns(apqns, mkvp, + get_min_card_level_for_keytype(key_type), true, + keystore->verbose); if (rc == -EINVAL) goto out_free_key; if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { @@ -1808,6 +1865,51 @@ int keystore_import_key(struct keystore *keystore, const char *name, goto out_free_key; } + if (is_cca_aes_cipher_key(secure_key, secure_key_size)) { + if (cca->lib_csulcca == NULL) { + rc = load_cca_library(cca, keystore->verbose); + if (rc != 0) + goto out_free_key; + } + + rc = select_cca_adapter_by_mkvp(cca, mkvp, apqns, + FLAG_SEL_CCA_MATCH_CUR_MKVP | + FLAG_SEL_CCA_MATCH_OLD_MKVP, + keystore->verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + warnx("No APQN found that is suitable for " + "working with the secure AES key '%s'", name); + rc = 0; + goto out_free_key; + } + + rc = restrict_key_export(cca, secure_key, secure_key_size, + keystore->verbose); + if (rc != 0) { + warnx("Failed to export-restrict the imported secure " + "key: %s", strerror(-rc)); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); + goto out_free_key; + } + + rc = check_aes_cipher_key(secure_key, secure_key_size); + if (rc != 0) { + warnx("The secure key to import might not be secure"); + printf("%s: Do you want to import it anyway [y/N]? ", + program_invocation_short_name); + if (!prompt_for_yes(keystore->verbose)) { + warnx("Operation aborted"); + rc = -ECANCELED; + goto out_free_key; + } + } + } + rc = write_secure_key(file_names.skey_filename, secure_key, secure_key_size, keystore->verbose); free(secure_key); @@ -1821,7 +1923,8 @@ int keystore_import_key(struct keystore *keystore, const char *name, rc = _keystore_create_info_file(keystore, name, &file_names, description, volumes, apqns, - noapqncheck, sector_size, volume_type); + noapqncheck, sector_size, volume_type, + key_type); if (rc != 0) goto out_free_props; @@ -1886,8 +1989,8 @@ int keystore_change_key(struct keystore *keystore, const char *name, .nomsg = 0 }; struct key_filenames file_names = { NULL, NULL, NULL }; struct properties *key_props = NULL; + char *apqns_prop, *key_type; size_t secure_key_size; - char *apqns_prop; u8 *secure_key; char temp[30]; u64 mkvp; @@ -1954,9 +2057,12 @@ int keystore_change_key(struct keystore *keystore, const char *name, goto out; apqns_prop = properties_get(key_props, PROP_NAME_APQNS); - rc = cross_check_apqns(apqns_prop, mkvp, true, - keystore->verbose); + key_type = properties_get(key_props, PROP_NAME_KEY_TYPE); + rc = cross_check_apqns(apqns_prop, mkvp, + get_min_card_level_for_keytype(key_type), + true, keystore->verbose); free(apqns_prop); + free(key_type); if (rc == -ENOTSUP) rc = 0; if (rc != 0 && noapqncheck == 0) { @@ -2140,6 +2246,7 @@ static struct util_rec *_keystore_setup_record(bool validation) util_rec_def(rec, REC_CLR_KEY_SIZE, UTIL_REC_ALIGN_LEFT, 20, REC_CLR_KEY_SIZE); util_rec_def(rec, REC_XTS, UTIL_REC_ALIGN_LEFT, 3, REC_XTS); + util_rec_def(rec, REC_KEY_TYPE, UTIL_REC_ALIGN_LEFT, 54, REC_KEY_TYPE); if (validation) util_rec_def(rec, REC_MASTERKEY, UTIL_REC_ALIGN_LEFT, 54, REC_MASTERKEY); @@ -2165,7 +2272,7 @@ static void _keystore_print_record(struct util_rec *rec, const char *name, struct properties *properties, bool validation, const char *skey_filename, - size_t secure_key_size, + size_t secure_key_size, bool is_xts, size_t clear_key_bitsize, bool valid, bool is_old_mk, bool reenc_pending, u64 mkvp) { @@ -2178,6 +2285,7 @@ static void _keystore_print_record(struct util_rec *rec, char *description; char *volume_type; char *reencipher; + char *key_type; char *creation; char *volumes; char *change; @@ -2212,6 +2320,7 @@ static void _keystore_print_record(struct util_rec *rec, reencipher = properties_get(properties, PROP_NAME_REENC_TIME); vp = properties_get(properties, PROP_NAME_KEY_VP); volume_type = _keystore_get_volume_type(properties); + key_type = properties_get(properties, PROP_NAME_KEY_TYPE); util_rec_set(rec, REC_KEY, name); if (validation) @@ -2219,13 +2328,13 @@ static void _keystore_print_record(struct util_rec *rec, util_rec_set(rec, REC_DESCRIPTION, description != NULL ? description : ""); util_rec_set(rec, REC_SEC_KEY_SIZE, "%lu bytes", secure_key_size); - if (!validation || valid) + if ((!validation || valid) && clear_key_bitsize != 0) util_rec_set(rec, REC_CLR_KEY_SIZE, "%lu bits", clear_key_bitsize); else util_rec_set(rec, REC_CLR_KEY_SIZE, "(unknown)"); - util_rec_set(rec, REC_XTS, - IS_XTS(secure_key_size) ? "Yes" : "No"); + util_rec_set(rec, REC_XTS, is_xts ? "Yes" : "No"); + util_rec_set(rec, REC_KEY_TYPE, key_type); if (validation) { if (valid) util_rec_set(rec, REC_MASTERKEY, @@ -2290,6 +2399,8 @@ static void _keystore_print_record(struct util_rec *rec, free(vp); if (volume_type != NULL) free(volume_type); + if (key_type != NULL) + free(key_type); } struct validate_info { @@ -2317,12 +2428,17 @@ static int _keystore_display_apqn_status(struct keystore *keystore, { int rc, warning = 0; char *apqns; + char *key_type; apqns = properties_get(properties, PROP_NAME_APQNS); if (apqns == NULL) return 0; - rc = cross_check_apqns(apqns, mkvp, true, keystore->verbose); + apqns = properties_get(properties, PROP_NAME_APQNS); + key_type = properties_get(properties, PROP_NAME_KEY_TYPE); + rc = cross_check_apqns(apqns, mkvp, + get_min_card_level_for_keytype(key_type), true, + keystore->verbose); if (rc != 0 && rc != -ENOTSUP) warning = 1; @@ -2330,6 +2446,7 @@ static int _keystore_display_apqn_status(struct keystore *keystore, printf("\n"); free(apqns); + free(key_type); return warning; } /** @@ -2395,8 +2512,10 @@ static int _keystore_process_validate(struct keystore *keystore, void *private) { struct validate_info *info = (struct validate_info *)private; + char **apqn_list = NULL; size_t clear_key_bitsize; size_t secure_key_size; + char *apqns = NULL; u8 *secure_key; int is_old_mk; int rc, valid; @@ -2413,9 +2532,13 @@ static int _keystore_process_validate(struct keystore *keystore, goto out; } + apqns = properties_get(properties, PROP_NAME_APQNS); + if (apqns != NULL) + apqn_list = str_list_split(apqns); + rc = validate_secure_key(info->pkey_fd, secure_key, secure_key_size, &clear_key_bitsize, &is_old_mk, - keystore->verbose); + (const char **)apqn_list, keystore->verbose); if (rc != 0) { valid = 0; info->num_invalid++; @@ -2433,6 +2556,7 @@ static int _keystore_process_validate(struct keystore *keystore, _keystore_print_record(info->rec, name, properties, 1, file_names->skey_filename, secure_key_size, + is_xts_key(secure_key, secure_key_size), clear_key_bitsize, valid, is_old_mk, _keystore_reencipher_key_exists(file_names), mkvp); @@ -2453,6 +2577,10 @@ static int _keystore_process_validate(struct keystore *keystore, info->num_warnings++; out: + if (apqns != NULL) + free(apqns); + if (apqn_list != NULL) + str_list_free_string_array(apqn_list); if (rc != 0) pr_verbose(keystore, "Failed to validate key '%s': %s", name, strerror(-rc)); @@ -2491,7 +2619,7 @@ int keystore_validate_key(struct keystore *keystore, const char *name_filter, info.num_warnings = 0; rc = _keystore_process_filtered(keystore, name_filter, NULL, - apqn_filter, NULL, + apqn_filter, NULL, NULL, _keystore_process_validate, &info); util_rec_free(rec); @@ -2669,7 +2797,9 @@ static int _keystore_process_reencipher(struct keystore *keystore, struct reencipher_params params = info->params; size_t clear_key_bitsize; size_t secure_key_size; + char **apqn_list = NULL; u8 *secure_key = NULL; + char *apqns = NULL; char *out_file; int is_old_mk; char *temp; @@ -2706,9 +2836,13 @@ static int _keystore_process_reencipher(struct keystore *keystore, goto out; } + apqns = properties_get(properties, PROP_NAME_APQNS); + if (apqns != NULL) + apqn_list = str_list_split(apqns); + rc = validate_secure_key(info->pkey_fd, secure_key, secure_key_size, &clear_key_bitsize, &is_old_mk, - keystore->verbose); + (const char **)apqn_list, keystore->verbose); if (rc != 0) { if (params.complete) { warnx("Key '%s' is not valid, re-enciphering is not " @@ -2807,6 +2941,10 @@ static int _keystore_process_reencipher(struct keystore *keystore, info->num_reenciphered++; out: + if (apqns != NULL) + free(apqns); + if (apqn_list != NULL) + str_list_free_string_array(apqn_list); if (secure_key != NULL) free(secure_key); @@ -2870,7 +3008,7 @@ int keystore_reencipher_key(struct keystore *keystore, const char *name_filter, info.num_skipped = 0; rc = _keystore_process_filtered(keystore, name_filter, NULL, - apqn_filter, NULL, + apqn_filter, NULL, NULL, _keystore_process_reencipher, &info); if (rc != 0) { @@ -3090,7 +3228,6 @@ static int _keystore_prompt_for_remove(struct keystore *keystore, struct key_filenames *file_names) { struct properties *key_prop; - char str[20]; char *msg; int rc; @@ -3108,14 +3245,7 @@ static int _keystore_prompt_for_remove(struct keystore *keystore, printf("%s: Remove key '%s' [y/N]? ", program_invocation_short_name, name); - if (fgets(str, sizeof(str), stdin) == NULL) { - rc = -EIO; - goto out; - } - if (str[strlen(str) - 1] == '\n') - str[strlen(str) - 1] = '\0'; - pr_verbose(keystore, "Prompt reply: '%s'", str); - if (strcasecmp(str, "y") != 0 && strcasecmp(str, "yes") != 0) { + if (!prompt_for_yes(keystore->verbose)) { warnx("Operation aborted"); rc = -ECANCELED; goto out; @@ -3205,29 +3335,29 @@ static int _keystore_display_key(struct keystore *keystore, void *private) { struct util_rec *rec = (struct util_rec *)private; - struct secaeskeytoken *secure_key; - size_t secure_key_size; + u8 *secure_key; + size_t secure_key_size, clear_key_bitsize = 0; int rc = 0; - secure_key = (struct secaeskeytoken *) - read_secure_key(file_names->skey_filename, + secure_key = read_secure_key(file_names->skey_filename, &secure_key_size, keystore->verbose); if (secure_key == NULL) return -EIO; - if (secure_key_size < SECURE_KEY_SIZE) { + if (secure_key_size < MIN_SECURE_KEY_SIZE) { pr_verbose(keystore, "Size of secure key is too small: %lu expected %lu", - secure_key_size, SECURE_KEY_SIZE); + secure_key_size, MIN_SECURE_KEY_SIZE); rc = -EIO; goto out; } + get_key_bit_size(secure_key, secure_key_size, &clear_key_bitsize); + _keystore_print_record(rec, name, properties, 0, file_names->skey_filename, secure_key_size, - IS_XTS(secure_key_size) ? secure_key->bitsize * 2 - : secure_key->bitsize, - 0, 0, + is_xts_key(secure_key, secure_key_size), + clear_key_bitsize, 0, 0, _keystore_reencipher_key_exists(file_names), 0); out: @@ -3251,12 +3381,13 @@ out: * mutliple APQN filters separated by commas. * NULL means no APQN filter. * @param[in] volume_type The volume type. NULL means no volume type filter. + * @param[in] key_type The key type. NULL means no key type filter. * * @returns 0 for success or a negative errno in case of an error */ int keystore_list_keys(struct keystore *keystore, const char *name_filter, const char *volume_filter, const char *apqn_filter, - const char *volume_type) + const char *volume_type, const char *key_type) { struct util_rec *rec; int rc; @@ -3269,10 +3400,16 @@ int keystore_list_keys(struct keystore *keystore, const char *name_filter, return -EINVAL; } + if (key_type != NULL && + !_keystore_valid_key_type(key_type)) { + warnx("Invalid key-type specified"); + return -EINVAL; + } + rec = _keystore_setup_record(0); rc = _keystore_process_filtered(keystore, name_filter, volume_filter, - apqn_filter, volume_type, + apqn_filter, volume_type, key_type, _keystore_display_key, rec); util_rec_free(rec); @@ -3582,37 +3719,6 @@ out: return cipher_spec; } -/** - * Returns the size of the secure key file - * - * @param[in] keystore the keystore - * @param[in] skey_filename the file name of the secure key - * - * @returns the size of the secure key, or -1 in case of an error - */ -static size_t _keystore_get_key_file_size(struct keystore *keystore, - const char *skey_filename) -{ - size_t secure_key_size; - struct stat sb; - - if (stat(skey_filename, &sb)) { - pr_verbose(keystore, "Key file '%s': %s", - skey_filename, strerror(errno)); - return -1; - } - - secure_key_size = sb.st_size; - if (secure_key_size < SECURE_KEY_SIZE) { - pr_verbose(keystore, - "Size of secure key is too small: %lu expected %lu", - secure_key_size, SECURE_KEY_SIZE); - return -1; - } - - return secure_key_size; -} - /** * Processing function for the cryptsetup and crypttab functions. * Extracts the required information and calls the secondary processing function @@ -3639,6 +3745,7 @@ static int _keystore_process_crypt(struct keystore *keystore, size_t secure_key_size; size_t sector_size = 0; char *volumes = NULL; + u8 *secure_key = NULL; char *dmname; char *temp; int rc = 0; @@ -3646,18 +3753,14 @@ static int _keystore_process_crypt(struct keystore *keystore, char *ch; int i; - secure_key_size = _keystore_get_key_file_size(keystore, - file_names->skey_filename); - if (secure_key_size < SECURE_KEY_SIZE) { - pr_verbose(keystore, - "Size of secure key is too small: %lu expected %lu", - secure_key_size, SECURE_KEY_SIZE); - rc = -EIO; - goto out; - } + secure_key = read_secure_key(file_names->skey_filename, + &secure_key_size, keystore->verbose); + if (secure_key == NULL) + return -EIO; cipher_spec = _keystore_build_cipher_spec(properties, - IS_XTS(secure_key_size)); + is_xts_key(secure_key, + secure_key_size)); if (cipher_spec == NULL) { rc = -EINVAL; goto out; @@ -3709,6 +3812,8 @@ out: free(cipher_spec); if (volume_type != NULL) free(volume_type); + if (secure_key != NULL) + free(secure_key); return rc; } @@ -3766,8 +3871,8 @@ int keystore_cryptsetup(struct keystore *keystore, const char *volume_filter, info.process_func = _keystore_process_cryptsetup; rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL, - volume_type, _keystore_process_crypt, - &info); + volume_type, NULL, + _keystore_process_crypt, &info); str_list_free_string_array(info.volume_filter); @@ -3827,8 +3932,8 @@ int keystore_crypttab(struct keystore *keystore, const char *volume_filter, info.process_func = _keystore_process_crypttab; rc = _keystore_process_filtered(keystore, NULL, volume_filter, NULL, - volume_type, _keystore_process_crypt, - &info); + volume_type, NULL, + _keystore_process_crypt, &info); str_list_free_string_array(info.volume_filter); @@ -3841,6 +3946,218 @@ int keystore_crypttab(struct keystore *keystore, const char *volume_filter, return rc; } +/** + * Converts a secure keys in the keystore + * + * @param[in] keystore the key store + * @param[in] name the name of the key to convert + * @param[in] key_type the type of the key to convert it to + * @param[in] noapqncheck if true, the specified APQN(s) are not checked for + * existence and type. + * @param[in] pkey_fd the file descriptor of /dev/pkey + * @param[in] cca the CCA library struct + * + * @returns 0 for success or a negative errno in case of an error + */ +int keystore_convert_key(struct keystore *keystore, const char *name, + const char *key_type, bool noapqncheck, bool quiet, + int pkey_fd, struct cca_lib *cca) +{ + struct key_filenames file_names = { NULL, NULL, NULL }; + u8 output_key[2 * MAX_SECURE_KEY_SIZE]; + struct properties *properties = NULL; + int rc, min_level, selected = 1; + unsigned int output_key_size; + char *cur_key_type = NULL; + char **apqn_list = NULL; + size_t secure_key_size; + u8 *secure_key = NULL; + char *apqns = NULL; + char *temp; + u64 mkvp; + + util_assert(keystore != NULL, "Internal error: keystore is NULL"); + util_assert(name != NULL, "Internal error: name is NULL"); + + rc = _keystore_get_key_filenames(keystore, name, &file_names); + if (rc != 0) + goto out; + + rc = _keystore_ensure_keyfiles_exist(&file_names, name); + if (rc != 0) + goto out; + + properties = properties_new(); + rc = properties_load(properties, file_names.info_filename, 1); + if (rc != 0) { + warnx("Key '%s' does not exist or is invalid", name); + goto out; + } + + cur_key_type = _keystore_get_key_type(properties); + if (strcasecmp(cur_key_type, key_type) == 0) { + warnx("The secure key '%s' is already of type %s", name, + cur_key_type); + rc = 0; + goto out; + } + if (strcasecmp(cur_key_type, KEY_TYPE_CCA_AESDATA) != 0) { + warnx("Only secure keys of type %s can " + "be converted. The secure key '%s' is of type %s", + KEY_TYPE_CCA_AESDATA, name, cur_key_type); + rc = 0; + goto out; + } + + secure_key = read_secure_key(file_names.skey_filename, + &secure_key_size, keystore->verbose); + if (secure_key == NULL) { + rc = -ENOENT; + goto out; + } + + min_level = get_min_card_level_for_keytype(key_type); + if (min_level < 0) { + warnx("Invalid key-type specified: %s", key_type); + rc = -EINVAL; + goto out; + } + + apqns = properties_get(properties, PROP_NAME_APQNS); + if (apqns != NULL) + apqn_list = str_list_split(apqns); + + rc = cross_check_apqns(apqns, 0, min_level, true, keystore->verbose); + if (rc == -EINVAL) + goto out; + if (rc != 0 && rc != -ENOTSUP && !noapqncheck) { + warnx("Your master key setup is improper for converting key " + "'%s'", name); + goto out; + } + + rc = validate_secure_key(pkey_fd, secure_key, secure_key_size, + NULL, NULL, (const char **)apqn_list, + keystore->verbose); + if (rc != 0) + goto out; + + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + &mkvp, keystore->verbose); + if (rc) + goto out; + + rc = select_cca_adapter_by_mkvp(cca, mkvp, NULL, + FLAG_SEL_CCA_MATCH_CUR_MKVP, + keystore->verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + warnx("No APQN found that is suitable for " + "converting the secure AES key '%s'", name); + goto out; + } + + if (!quiet) { + util_print_indented("ATTENTION: Converting a secure key is " + "irreversible, and might have an effect " + "on the volumes encrypted with it!", 0); + _keystore_msg_for_volumes("The following volumes are encrypted " + "with this key:", properties, NULL); + printf("%s: Convert key '%s [y/N]'? ", + program_invocation_short_name, name); + if (!prompt_for_yes(keystore->verbose)) { + warnx("Operation aborted"); + rc = -ECANCELED; + goto out; + } + } + + memset(output_key, 0, sizeof(output_key)); + output_key_size = sizeof(output_key); + rc = convert_aes_data_to_cipher_key(cca, secure_key, + secure_key_size, output_key, + &output_key_size, + keystore->verbose); + if (rc != 0) { + warnx("Converting the secure key '%s' from %s to %s has failed", + name, KEY_TYPE_CCA_AESDATA, key_type); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); + goto out; + } + + rc = restrict_key_export(cca, output_key, output_key_size, + keystore->verbose); + if (rc != 0) { + warnx("Export restricting the converted secure key '%s' has " + "failed", name); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); + goto out; + } + + rc = properties_set2(properties, PROP_NAME_KEY_TYPE, key_type, true); + if (rc != 0) { + warnx("Invalid characters in key-type"); + goto out; + } + + rc = properties_save(properties, file_names.info_filename, 1); + if (rc != 0) { + pr_verbose(keystore, + "Failed to write key info file '%s': %s", + file_names.info_filename, strerror(-rc)); + goto out; + } + + rc = write_secure_key(file_names.skey_filename, output_key, + output_key_size, keystore->verbose); + if (rc != 0) + goto out; + + pr_verbose(keystore, "Secure key '%s' was converted successfully", + name); + + util_asprintf(&temp, "The following LUKS2 volumes are " + "encrypted with key '%s'. These volumes still contain " + "the secure AES volume key of type CCA-AESDATA. To " + "change the secure AES volume key in the LUKS2 header, " + "run command 'zkey-cryptsetup setkey " + "--master-key-file %s':", name, + file_names.skey_filename); + _keystore_msg_for_volumes(temp, properties, VOLUME_TYPE_LUKS2); + free(temp); + util_asprintf(&temp, "The following plain mode volumes are " + "encrypted with key '%s'. You must adapt the crypttab " + "entries for this volumes and change the key size " + "parameter to 'size=%u' or run command 'zkey crypttab " + "--volumes ' for each volume to re-generate the " + "crypttab entries:", name, output_key_size * 8, name); + _keystore_msg_for_volumes(temp, properties, VOLUME_TYPE_PLAIN); + free(temp); + +out: + _keystore_free_key_filenames(&file_names); + if (properties != NULL) + properties_free(properties); + if (secure_key != NULL) + free(secure_key); + if (apqns != NULL) + free(apqns); + if (apqn_list != NULL) + str_list_free_string_array(apqn_list); + if (cur_key_type != NULL) + free(cur_key_type); + + if (rc != 0) + pr_verbose(keystore, "Failed to convert key '%s': %s", + name, strerror(-rc)); + return rc; +} + /** * Frees a keystore object * diff --git a/zkey/keystore.h b/zkey/keystore.h index 8e888d1..b17a575 100644 --- a/zkey/keystore.h +++ b/zkey/keystore.h @@ -32,12 +32,13 @@ int keystore_generate_key(struct keystore *keystore, const char *name, const char *apqns, bool noapqncheck, size_t sector_size, size_t keybits, bool xts, const char *clear_key_file, const char *volume_type, - int pkey_fd); + const char *key_type, int pkey_fd); int keystore_import_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, const char *apqns, bool noapqncheck, size_t sector_size, - const char *import_file, const char *volume_type); + const char *import_file, const char *volume_type, + struct cca_lib *cca); int keystore_change_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, @@ -68,7 +69,7 @@ int keystore_remove_key(struct keystore *keystore, const char *name, int keystore_list_keys(struct keystore *keystore, const char *name_filter, const char *volume_filter, const char *apqn_filter, - const char *volume_type); + const char *volume_type, const char *key_type); int keystore_cryptsetup(struct keystore *keystore, const char *volume_filter, bool execute, const char *volume_type, @@ -80,6 +81,10 @@ int keystore_crypttab(struct keystore *keystore, const char *volume_filter, const char *volume_type, const char *keyfile, size_t keyfile_offset, size_t keyfile_size, size_t tries); +int keystore_convert_key(struct keystore *keystore, const char *name, + const char *key_type, bool noapqncheck, bool quiet, + int pkey_fd, struct cca_lib *cca); + void keystore_free(struct keystore *keystore); diff --git a/zkey/pkey.c b/zkey/pkey.c index 8471f3d..462f9fe 100644 --- a/zkey/pkey.c +++ b/zkey/pkey.c @@ -46,6 +46,8 @@ #define DEFAULT_KEYBITS 256 +#define INITIAL_APQN_ENTRIES 16 + /** * Opens the pkey device and returns its file descriptor. * @@ -98,10 +100,8 @@ u8 *read_secure_key(const char *keyfile, size_t *secure_key_size, } size = sb.st_size; - if (size != SECURE_KEY_SIZE && size != 2*SECURE_KEY_SIZE) { - warnx("File '%s' has an invalid size, %lu or %lu bytes " - "expected", keyfile, SECURE_KEY_SIZE, - 2 * SECURE_KEY_SIZE); + if (size < MIN_SECURE_KEY_SIZE || size > 2 * MAX_SECURE_KEY_SIZE) { + warnx("File '%s' has an invalid size: %lu", keyfile, size); return NULL; } @@ -271,6 +271,615 @@ out: return buf; } +/** + * Returns the PKEY_KEYTYPE_xxx value for the specified key size. + * + * @param[in] keysize the key size in bits + * + * @returns the PKEY_KEYTYPE_xxx value or 0 for an unknown key size + */ +static u32 keysize_to_keytype(enum pkey_key_size keysize) +{ + switch (keysize) { + case PKEY_SIZE_AES_128: + return PKEY_KEYTYPE_AES_128; + case PKEY_SIZE_AES_192: + return PKEY_KEYTYPE_AES_192; + case PKEY_SIZE_AES_256: + return PKEY_KEYTYPE_AES_256; + default: + return 0; + } +} + +/** + * Returns the PKEY_SIZE_xxx value for the specified keybits. + * + * @param[in] keybits the key size in bits + * + * @returns thePKEY_SIZE_xxx value or 0 for an unknown key size + */ +static enum pkey_key_size keybits_to_keysize(u32 keybits) +{ + switch (keybits) { + case 128: + return PKEY_SIZE_AES_128; + case 192: + return PKEY_SIZE_AES_192; + case 256: + return PKEY_SIZE_AES_256; + default: + return PKEY_SIZE_UNKNOWN; + } +} + +/* + * Wrapper for the PKEY_GENSECK/PKEY_GENSECK2 IOCTL to generate a secure + * key of any type by random. If the newer PKEY_GENSECK2 IOCTL is not supported + * by the pkey device, then it falls back to the older PKEY_GENSECK IOCTL + * + * @param[in] pkey_fd the pkey file descriptor + * @param[in/out] genseck info about key to generate + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error + */ +static int pkey_genseck2(int pkey_fd, struct pkey_genseck2 *genseck2, + bool verbose) +{ + struct pkey_genseck genseck; + int rc; + u32 i; + + util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); + util_assert(genseck2 != NULL, "Internal error: genseck2 is NULL"); + + rc = ioctl(pkey_fd, PKEY_GENSECK2, genseck2); + if (rc != 0 && errno != ENOTTY) + return -errno; + if (rc == 0) + return 0; + + /* New IOCTL is not available, fall back to old one */ + pr_verbose(verbose, "ioctl PKEY_GENSECK2 not supported, fall back to " + "PKEY_GENSECK"); + + if (genseck2->type != PKEY_TYPE_CCA_DATA) { + warnx("Key-type is not supported"); + return -ENOTSUP; + } + + if (genseck2->keylen < AESDATA_KEY_SIZE) + return -EINVAL; + + memset(&genseck, 0, sizeof(genseck)); + + genseck.keytype = keysize_to_keytype(genseck2->size); + if (genseck.keytype == 0) + return -EINVAL; + + for (i = 0; i < genseck2->apqn_entries; i++) { + genseck.cardnr = genseck2->apqns[i].card; + genseck.domain = genseck2->apqns[i].domain; + + rc = ioctl(pkey_fd, PKEY_GENSECK, &genseck); + if (rc != 0) + continue; + + memcpy(genseck2->key, &genseck.seckey.seckey, AESDATA_KEY_SIZE); + genseck2->keylen = AESDATA_KEY_SIZE; + return 0; + } + + return -errno; +} + +/* + * Wrapper for the PKEY_CLR2SECK/PKEY_CLR2SECK2 IOCTL to generate a secure + * key of any type from a clear key. If the newer PKEY_CLR2SECK2 IOCTL is not + * supported by the pkey device, then it falls back to the older PKEY_CLR2SECK + * IOCTL + * + * @param[in] pkey_fd the pkey file descriptor + * @param[in/out] clr2seck2 info about key to generate + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error + */ +static int pkey_clr2seck2(int pkey_fd, struct pkey_clr2seck2 *clr2seck2, + bool verbose) +{ + struct pkey_clr2seck clr2seck; + int rc; + u32 i; + + util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); + util_assert(clr2seck2 != NULL, "Internal error: clr2seck2 is NULL"); + + rc = ioctl(pkey_fd, PKEY_CLR2SECK2, clr2seck2); + if (rc != 0 && errno != ENOTTY) + return -errno; + if (rc == 0) + return 0; + + /* New IOCTL is not available, fall back to old one */ + pr_verbose(verbose, "ioctl PKEY_CLR2SECK2 not supported, fall back to " + "PKEY_CLR2SECK"); + + if (clr2seck2->type != PKEY_TYPE_CCA_DATA) { + warnx("Key-type is not supported"); + return -ENOTSUP; + } + + if (clr2seck2->keylen < AESDATA_KEY_SIZE) + return -EINVAL; + + memset(&clr2seck, 0, sizeof(clr2seck)); + clr2seck.clrkey = clr2seck2->clrkey; + + clr2seck.keytype = keysize_to_keytype(clr2seck2->size); + if (clr2seck.keytype == 0) + return -EINVAL; + + for (i = 0; i < clr2seck2->apqn_entries; i++) { + clr2seck.cardnr = clr2seck2->apqns[i].card; + clr2seck.domain = clr2seck2->apqns[i].domain; + + rc = ioctl(pkey_fd, PKEY_CLR2SECK, &clr2seck); + if (rc != 0) + continue; + + memcpy(clr2seck2->key, &clr2seck.seckey.seckey, + AESDATA_KEY_SIZE); + clr2seck2->keylen = AESDATA_KEY_SIZE; + return 0; + } + + return -errno; +} + +/* + * Wrapper for the PKEY_VERIFYKEY/PKEY_VERIFYKEY2 IOCTL to verify a secure + * key of any type. If the newer PKEY_VERIFYKEY2 IOCTL is not supported + * by the pkey device, then it falls back to the older PKEY_VERIFYKEY IOCTL + * + * @param[in] pkey_fd the pkey file descriptor + * @param[in/out] verifykey2 info about key to verify + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error + */ +static int pkey_verifyseck2(int pkey_fd, struct pkey_verifykey2 *verifykey2, + bool verbose) +{ + struct pkey_verifykey verifykey; + int rc; + + util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); + util_assert(verifykey2 != NULL, "Internal error: verifyseck2 is NULL"); + + rc = ioctl(pkey_fd, PKEY_VERIFYKEY2, verifykey2); + if (rc != 0 && errno != ENOTTY) + return -errno; + if (rc == 0) + return 0; + + /* New IOCTL is not available, fall back to old one */ + pr_verbose(verbose, "ioctl PKEY_VERIFYKEY2 not supported, fall back to " + "PKEY_VERIFYKEY"); + + if (!is_cca_aes_data_key(verifykey2->key, verifykey2->keylen)) + return -ENODEV; + + memset(&verifykey, 0, sizeof(verifykey)); + memcpy(&verifykey.seckey, verifykey2->key, sizeof(verifykey.seckey)); + + /* + * Note: the old IOCTL does not support to check a specific card and + * domain. If falling back to the old IOCTL, this input is silently + * ignored, and all APQNs currently available in the system are used. + */ + rc = ioctl(pkey_fd, PKEY_VERIFYKEY, &verifykey); + if (rc != 0) + return -errno; + + if ((verifykey.attributes & PKEY_VERIFY_ATTR_AES) == 0) + return -ENODEV; + + verifykey2->type = PKEY_TYPE_CCA_DATA; + verifykey2->cardnr = verifykey.cardnr; + verifykey2->domain = verifykey.domain; + verifykey2->size = keybits_to_keysize(verifykey.keysize); + + if (verifykey.attributes & PKEY_VERIFY_ATTR_OLD_MKVP) + verifykey2->flags = PKEY_FLAGS_MATCH_ALT_MKVP; + else + verifykey2->flags = PKEY_FLAGS_MATCH_CUR_MKVP; + + return 0; +} + +/** + * Print a list of APQNs if verbose is set + */ +static void pr_verbose_apqn_list(bool verbose, struct pkey_apqn *list, u32 num) +{ + u32 i; + + if (!verbose) + return; + + for (i = 0; i < num ; i++) + warnx(" APQN: %02x.%04x", list[i].card, list[i].domain); +} + +/** + * Filter a n array list of APQNs (struct pkey_apqn) by a list of APQN strings. + * + * @param[in] apqn_list a zero terminated array of pointers to C-strings + * @param[in/out] apqns A list of APQNs as array of struct pkey_apqn to + * filter. The list is modified during filtering. + * @param[in/out] apqn_entries Number of entries in the list of APQNs. The + * number is modified during filtering. + * + * @returns 0 on success, a negative errno in case of an error + */ +static int filter_apqn_list(const char **apqn_list, struct pkey_apqn **apqns, + u32 *apqn_entries) +{ + unsigned int count, i, k, card, domain; + struct pkey_apqn *list = *apqns; + bool found; + + if (apqn_list == NULL) + return 0; + + for (count = 0; apqn_list[count] != NULL; count++) + ; + if (count == 0) + return 0; + + for (i = 0; i < *apqn_entries; i++) { + found = false; + for (k = 0; apqn_list[k] != NULL; k++) { + if (sscanf(apqn_list[k], "%x.%x", &card, &domain) != 2) + return -EINVAL; + + if (list[i].card == card && list[i].domain == domain) { + found = true; + break; + } + } + + if (!found) { + if (i < *apqn_entries - 1) + memmove(&list[i], &list[i+1], + (*apqn_entries - i - 1) * + sizeof(struct pkey_apqn)); + (*apqn_entries)--; + i--; + } + } + + return 0; +} + +/** + * Build a list of APQNs in the form accepted by the pkey IOCTLs from the + * List of APQNs as zero terminated array of pointers to C-strings that + * are usable for the CCA-AESDATA key type. + * + * @param[in] apqn_list a zero terminated array of pointers to C-strings + * @param[out] apqns A list of APQNs as array of struct pkey_apqn. The + * list must be freed by the caller using free(). + * @param[out] apqn_entries Number of entries in the list of APQNs + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error + */ +static int build_apqn_list_for_aes_data(const char **apqn_list, + struct pkey_apqn **apqns, + u32 *apqn_entries, bool verbose) +{ + unsigned int card, domain, count = 0; + struct pkey_apqn *list = NULL; + u32 list_entries = 0; + int i; + + pr_verbose(verbose, "Build a list of APQNs for CCA-AESDATA"); + + if (apqn_list != NULL) + for (count = 0; apqn_list[count] != NULL; count++) + ; + + if (count > 0) { + list = util_malloc(count * sizeof(struct pkey_apqn)); + list_entries = count; + + for (i = 0; apqn_list[i] != NULL; i++) { + if (sscanf(apqn_list[i], "%x.%x", &card, &domain) != 2) + return -EINVAL; + + list[i].card = card; + list[i].domain = domain; + } + + } else { + /* + * Although the new pkey IOCTLs do not support APQN entries + * with ANY indication, build an ANY-list here. If we get here, + * then the new IOCTLs are not available, and it will fall back + * to the old IOCTL which do support ANY specifications. + */ + list = util_malloc(sizeof(struct pkey_apqn)); + list_entries = 1; + + list[0].card = AUTOSELECT; + list[0].domain = AUTOSELECT; + } + + *apqns = list; + *apqn_entries = list_entries; + + pr_verbose(verbose, "%u APQNs found", list_entries); + pr_verbose_apqn_list(verbose, list, list_entries); + return 0; +} + +/** + * Build a list of APQNs in the form accepted by the pkey IOCTLs from the + * List of APQNs as zero terminated array of pointers to C-strings that + * are usable for the specified key type. + * + * @param[in] pkey_fd the pkey file descriptor + * @param[in] type the key type + * @param[in] apqn_list a zero terminated array of pointers to C-strings + * @param[out] apqns A list of APQNs as array of struct pkey_apqn. The + * list must be freed by the caller using free(). + * @param[out] apqn_entries Number of entries in the list of APQNs + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error + */ +static int build_apqn_list_for_key_type(int pkey_fd, enum pkey_key_type type, + const char **apqn_list, + struct pkey_apqn **apqns, + u32 *apqn_entries, bool verbose) +{ + struct pkey_apqns4keytype apqns4keytype; + int rc; + + util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); + util_assert(apqns != NULL, "Internal error: apqns is NULL"); + util_assert(apqn_entries != NULL, + "Internal error: apqn_entries is NULL"); + + pr_verbose(verbose, "Build a list of APQNs for key type %d", type); + + memset(&apqns4keytype, 0, sizeof(apqns4keytype)); + apqns4keytype.type = type; + apqns4keytype.apqn_entries = INITIAL_APQN_ENTRIES; + apqns4keytype.apqns = (struct pkey_apqn *)util_malloc( + apqns4keytype.apqn_entries * sizeof(struct pkey_apqn)); + + do { + rc = ioctl(pkey_fd, PKEY_APQNS4KT, &apqns4keytype); + if (rc == 0) + break; + rc = -errno; + pr_verbose(verbose, "ioctl PKEY_APQNS4KT rc: %s", + strerror(-rc)); + + switch (rc) { + case -ENOSPC: + free(apqns4keytype.apqns); + apqns4keytype.apqns = (struct pkey_apqn *) + util_malloc(apqns4keytype.apqn_entries * + sizeof(struct pkey_apqn)); + continue; + case -ENOTTY: + /* + * New IOCTL is not available: build the list + * manually (Key type CCA-AESDATA only) + */ + free(apqns4keytype.apqns); + + if (type != PKEY_TYPE_CCA_DATA) + return -ENOTSUP; + + rc = build_apqn_list_for_aes_data(apqn_list, apqns, + apqn_entries, + verbose); + return rc; + default: + goto out; + } + } while (rc != 0); + + if (apqns4keytype.apqn_entries == 0) { + pr_verbose(verbose, "No APQN available for key type %d", type); + rc = -ENODEV; + goto out; + } + + rc = filter_apqn_list(apqn_list, &apqns4keytype.apqns, + &apqns4keytype.apqn_entries); + if (rc != 0) + goto out; + + if (apqns4keytype.apqn_entries == 0) { + pr_verbose(verbose, "No APQN available for key type %d", type); + rc = -ENODEV; + goto out; + } + + pr_verbose(verbose, "%u APQNs found", apqns4keytype.apqn_entries); + pr_verbose_apqn_list(verbose, apqns4keytype.apqns, + apqns4keytype.apqn_entries); + +out: + if (rc == 0) { + *apqns = apqns4keytype.apqns; + *apqn_entries = apqns4keytype.apqn_entries; + } else { + *apqns = NULL; + *apqn_entries = 0; + free(apqns4keytype.apqns); + } + + return rc; +} + +/** + * Build a list of APQNs in the form accepted by the pkey IOCTLs from the + * List of APQNs as zero terminated array of pointers to C-strings that are + * usable for the specufied key. + * + * @param[in] pkey_fd the pkey file descriptor + * @param[in] key the key + * @param[in] keylen the length of the key + * @param[in] flags PKEY_FLAGS_MATCH_xxx flags + * @param[in] apqn_list a zero terminated array of pointers to C-strings + * @param[out] apqns A list of APQNs as array of struct pkey_apqn. The + * list must be freed by the caller using free(). + * @param[out] apqn_entries Number of entries in the list of APQNs + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error + */ +static int build_apqn_list_for_key(int pkey_fd, u8 *key, u32 keylen, u32 flags, + const char **apqn_list, + struct pkey_apqn **apqns, + u32 *apqn_entries, bool verbose) +{ + struct pkey_apqns4key apqns4key; + u64 mkvp; + int rc; + + util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); + util_assert(key != NULL, "Internal error: key is NULL"); + util_assert(apqns != NULL, "Internal error: apqns is NULL"); + util_assert(apqn_entries != NULL, + "Internal error: apqn_entries is NULL"); + + pr_verbose(verbose, "Build a list of APQNs for the key"); + + memset(&apqns4key, 0, sizeof(apqns4key)); + apqns4key.key = key; + apqns4key.keylen = keylen; + apqns4key.flags = flags; + apqns4key.apqn_entries = INITIAL_APQN_ENTRIES; + apqns4key.apqns = (struct pkey_apqn *)util_malloc( + apqns4key.apqn_entries * sizeof(struct pkey_apqn)); + + do { + rc = ioctl(pkey_fd, PKEY_APQNS4K, &apqns4key); + if (rc == 0) + break; + rc = -errno; + pr_verbose(verbose, "ioctl PKEY_APQNS4K rc: %s", strerror(-rc)); + + switch (rc) { + case -ENOSPC: + free(apqns4key.apqns); + apqns4key.apqns = (struct pkey_apqn *) + util_malloc(apqns4key.apqn_entries * + sizeof(struct pkey_apqn)); + continue; + case -ENOTTY: + /* + * New IOCTL is not available: build the list manually + * (Key type CCA-AESDATA only) + */ + free(apqns4key.apqns); + + if (!is_cca_aes_data_key(key, keylen)) + return -ENOTSUP; + + rc = get_master_key_verification_pattern(key, keylen, + &mkvp, + verbose); + if (rc != 0) + return rc; + + rc = build_apqn_list_for_aes_data(apqn_list, apqns, + apqn_entries, + verbose); + return rc; + default: + goto out; + } + } while (rc != 0); + + if (apqns4key.apqn_entries == 0) { + pr_verbose(verbose, "No APQN available for the key"); + rc = -ENODEV; + goto out; + } + + rc = filter_apqn_list(apqn_list, &apqns4key.apqns, + &apqns4key.apqn_entries); + if (rc != 0) + goto out; + + if (apqns4key.apqn_entries == 0) { + pr_verbose(verbose, "No APQN available for the key"); + rc = -ENODEV; + goto out; + } + + pr_verbose(verbose, "%u APQNs found", apqns4key.apqn_entries); + pr_verbose_apqn_list(verbose, apqns4key.apqns, apqns4key.apqn_entries); + +out: + if (rc == 0) { + *apqns = apqns4key.apqns; + *apqn_entries = apqns4key.apqn_entries; + } else { + *apqns = NULL; + *apqn_entries = 0; + free(apqns4key.apqns); + } + + return rc; +} + +/** + * Convert the key type string into the pkey enumeration + * + * @param[in] key_type the type of the key + * + * @returns the pkey key type or 0 for an u known key type + */ +static enum pkey_key_type key_type_to_pkey_type(const char *key_type) +{ + if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0) + return PKEY_TYPE_CCA_DATA; + if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) + return PKEY_TYPE_CCA_CIPHER; + + return 0; +} + +/** + * Return the size of a key blob for a specific type + * + * @param[in] type the type of the key + * + * @returns the size of the key or 0 for an invalid key type + */ +static size_t key_size_for_type(enum pkey_key_type type) +{ + switch (type) { + case PKEY_TYPE_CCA_DATA: + return AESDATA_KEY_SIZE; + case PKEY_TYPE_CCA_CIPHER: + return AESCIPHER_KEY_SIZE; + default: + return 0; + } +} + /** * Generate a secure key by random * @@ -278,80 +887,110 @@ out: * @param[in] keyfile the file name of the secure key to generate * @param[in] keybits the cryptographic size of the key in bits * @param[in] xts if true an XTS key is generated - * @param[in] card the card number to use (or AUTOSELECT) - * @param[in] domain the domain number to use (or AUTOSELECT) + * @param[in] key_type the type of the key + * @param[in] apqns a zero terminated array of pointers to APQN-strings, + * or NULL for AUTOSELECT * @param[in] verbose if true, verbose messages are printed * * @returns 0 on success, a negative errno in case of an error */ int generate_secure_key_random(int pkey_fd, const char *keyfile, - size_t keybits, bool xts, u16 card, u16 domain, - bool verbose) + size_t keybits, bool xts, const char *key_type, + const char **apqns, bool verbose) { - struct pkey_genseck gensec; - size_t secure_key_size; - u8 *secure_key; + struct pkey_genseck2 genseck2; + size_t secure_key_size, size; + u8 *secure_key = NULL; int rc; util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); util_assert(keyfile != NULL, "Internal error: keyfile is NULL"); + util_assert(key_type != NULL, "Internal error: key_type is NULL"); if (keybits == 0) keybits = DEFAULT_KEYBITS; - secure_key_size = DOUBLE_KEYSIZE_FOR_XTS(SECURE_KEY_SIZE, xts); - secure_key = util_malloc(secure_key_size); + pr_verbose(verbose, "Generate secure key by random"); - pr_verbose(verbose, "Generate key on card %02x.%04x", card, domain); + memset(&genseck2, 0, sizeof(genseck2)); - gensec.cardnr = card; - gensec.domain = domain; - switch (keybits) { - case 128: - gensec.keytype = PKEY_KEYTYPE_AES_128; - break; - case 192: - if (xts) { - warnx("Invalid value for '--keybits'|'-c' " - "for XTS: '%lu'", keybits); - rc = -EINVAL; - goto out; - } - gensec.keytype = PKEY_KEYTYPE_AES_192; - break; - case 256: - gensec.keytype = PKEY_KEYTYPE_AES_256; - break; - default: + genseck2.type = key_type_to_pkey_type(key_type); + if (genseck2.type == 0) { + warnx("Key-type not supported; %s", key_type); + return -ENOTSUP; + } + + genseck2.size = keybits_to_keysize(keybits); + if (genseck2.size == 0) { warnx("Invalid value for '--keybits'/'-c': '%lu'", keybits); - rc = -EINVAL; - goto out; + return -EINVAL; + } + if (keybits == 192 && xts) { + warnx("Invalid value for '--keybits'|'-c' " + "for XTS: '%lu'", keybits); + return -EINVAL; } - rc = ioctl(pkey_fd, PKEY_GENSECK, &gensec); - if (rc < 0) { - rc = -errno; - warnx("Failed to generate a secure key: %s", strerror(errno)); - warnx("Make sure that all available CCA crypto adapters are " - "setup with the same master key"); - goto out; + rc = build_apqn_list_for_key_type(pkey_fd, genseck2.type, apqns, + &genseck2.apqns, + &genseck2.apqn_entries, verbose); + if (rc != 0) { + if (rc == -ENODEV || rc == -ENOTSUP) + warnx("No APQN is available that can generate a secure " + "key of type %s", key_type); + else + warnx("Failed to build a list of APQNs that can " + "generate a secure key of type %s: %s", key_type, + strerror(-rc)); + return rc; } - memcpy(secure_key, &gensec.seckey, SECURE_KEY_SIZE); + size = key_size_for_type(genseck2.type); + secure_key_size = DOUBLE_KEYSIZE_FOR_XTS(size, xts); + secure_key = util_zalloc(secure_key_size); + + genseck2.key = secure_key; + genseck2.keylen = size; + + rc = pkey_genseck2(pkey_fd, &genseck2, verbose); + if (rc != 0) { + warnx("Failed to generate a secure key: %s", strerror(-rc)); + goto out; + } if (xts) { - rc = ioctl(pkey_fd, PKEY_GENSECK, &gensec); - if (rc < 0) { - rc = -errno; - warnx("Failed to generate a secure key: %s", - strerror(errno)); - warnx("Make sure that all available CCA crypto " - "adapters are setup with the same master key"); + free(genseck2.apqns); + genseck2.apqns = NULL; + genseck2.apqn_entries = 0; + + /* + * Ensure to generate 2nd key with an APQN that has the same + * master key that is used by the 1st key. + */ + rc = build_apqn_list_for_key(pkey_fd, secure_key, size, + PKEY_FLAGS_MATCH_CUR_MKVP, apqns, + &genseck2.apqns, + &genseck2.apqn_entries, verbose); + if (rc != 0) { + if (rc == -ENODEV || rc == -ENOTSUP) + warnx("No APQN is available that can generate " + "a secure key of type %s", key_type); + else + warnx("Failed to build a list of APQNs that " + "can generate a secure key of type %s: " + "%s", key_type, strerror(-rc)); goto out; } - memcpy(secure_key + SECURE_KEY_SIZE, &gensec.seckey, - SECURE_KEY_SIZE); + genseck2.key = secure_key + size; + genseck2.keylen = size; + + rc = pkey_genseck2(pkey_fd, &genseck2, verbose); + if (rc != 0) { + warnx("Failed to generate a secure key: %s", + strerror(-rc)); + goto out; + } } pr_verbose(verbose, "Successfully generated a secure key"); @@ -359,6 +998,7 @@ int generate_secure_key_random(int pkey_fd, const char *keyfile, rc = write_secure_key(keyfile, secure_key, secure_key_size, verbose); out: + free(genseck2.apqns); free(secure_key); return rc; } @@ -374,89 +1014,125 @@ out: * determines the keybits. * @param[in] xts if true an XTS key is generated * @param[in] clearkeyfile the file name of the clear key to read - * @param[in] card the card number to use (or AUTOSELECT) - * @param[in] domain the domain number to use (or AUTOSELECT) + * @param[in] key_type the type of the key + * @param[in] apqns a zero terminated array of pointers to APQN-strings, + * or NULL for AUTOSELECT * @param[in] verbose if true, verbose messages are printed * * @returns 0 on success, a negative errno in case of an error */ int generate_secure_key_clear(int pkey_fd, const char *keyfile, size_t keybits, bool xts, - const char *clearkeyfile, - u16 card, u16 domain, - bool verbose) + const char *clearkeyfile, const char *key_type, + const char **apqns, bool verbose) { - struct pkey_clr2seck clr2sec; + struct pkey_clr2seck2 clr2seck2; size_t secure_key_size; size_t clear_key_size; u8 *secure_key; u8 *clear_key; + size_t size; int rc; util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); util_assert(keyfile != NULL, "Internal error: keyfile is NULL"); util_assert(clearkeyfile != NULL, "Internal error: clearkeyfile is NULL"); + util_assert(key_type != NULL, "Internal error: key_type is NULL"); - secure_key_size = DOUBLE_KEYSIZE_FOR_XTS(SECURE_KEY_SIZE, xts); - secure_key = util_malloc(secure_key_size); + pr_verbose(verbose, "Generate secure key from a clear key"); clear_key = read_clear_key(clearkeyfile, keybits, xts, &clear_key_size, verbose); if (clear_key == NULL) return -EINVAL; - pr_verbose(verbose, "Generate key on card %02x.%04x", card, domain); + memset(&clr2seck2, 0, sizeof(clr2seck2)); - clr2sec.cardnr = card; - clr2sec.domain = domain; - switch (HALF_KEYSIZE_FOR_XTS(clear_key_size * 8, xts)) { - case 128: - clr2sec.keytype = PKEY_KEYTYPE_AES_128; - break; - case 192: - clr2sec.keytype = PKEY_KEYTYPE_AES_192; - break; - case 256: - clr2sec.keytype = PKEY_KEYTYPE_AES_256; - break; - default: + memcpy(&clr2seck2.clrkey, clear_key, + HALF_KEYSIZE_FOR_XTS(clear_key_size, xts)); + + clr2seck2.type = key_type_to_pkey_type(key_type); + if (clr2seck2.type == 0) { + warnx("Key-type not supported; %s", key_type); + return -ENOTSUP; + } + + clr2seck2.size = keybits_to_keysize(HALF_KEYSIZE_FOR_XTS( + clear_key_size * 8, xts)); + if (clr2seck2.size == 0) { warnx("Invalid clear key size: '%lu' bytes", clear_key_size); - rc = -EINVAL; - goto out; + return -EINVAL; + } + if (keybits == 192 && xts) { + warnx("Invalid clear key size for XTS: '%lu' bytes", + clear_key_size); + return -EINVAL; } - memcpy(&clr2sec.clrkey, clear_key, - HALF_KEYSIZE_FOR_XTS(clear_key_size, xts)); + rc = build_apqn_list_for_key_type(pkey_fd, clr2seck2.type, apqns, + &clr2seck2.apqns, + &clr2seck2.apqn_entries, verbose); + if (rc != 0) { + if (rc == -ENODEV || rc == -ENOTSUP) + warnx("No APQN is available that can generate a secure " + "key of type %s", key_type); + else + warnx("Failed to build a list of APQNs that can " + "generate a secure key of type %s: %s", key_type, + strerror(-rc)); + return rc; + } - rc = ioctl(pkey_fd, PKEY_CLR2SECK, &clr2sec); - if (rc < 0) { - rc = -errno; - warnx("Failed to generate a secure key from a " - "clear key: %s", strerror(errno)); - warnx("Make sure that all available CCA crypto adapters are " - "setup with the same master key"); + size = key_size_for_type(clr2seck2.type); + secure_key_size = DOUBLE_KEYSIZE_FOR_XTS(size, xts); + secure_key = util_zalloc(secure_key_size); + + clr2seck2.key = secure_key; + clr2seck2.keylen = size; + + rc = pkey_clr2seck2(pkey_fd, &clr2seck2, verbose); + if (rc != 0) { + warnx("Failed to generate a secure key: %s", strerror(-rc)); goto out; } - memcpy(secure_key, &clr2sec.seckey, SECURE_KEY_SIZE); - if (xts) { - memcpy(&clr2sec.clrkey, clear_key + clear_key_size / 2, + free(clr2seck2.apqns); + clr2seck2.apqns = NULL; + clr2seck2.apqn_entries = 0; + + memcpy(&clr2seck2.clrkey, clear_key + clear_key_size / 2, clear_key_size / 2); - rc = ioctl(pkey_fd, PKEY_CLR2SECK, &clr2sec); - if (rc < 0) { - rc = -errno; - warnx("Failed to generate a secure key from " - "a clear key: %s", strerror(errno)); - warnx("Make sure that all available CCA crypto " - "adapters are setup with the same master key"); + /* + * Ensure to generate 2nd key with an APQN that has the same + * master key that is used by the 1st key. + */ + rc = build_apqn_list_for_key(pkey_fd, secure_key, size, + PKEY_FLAGS_MATCH_CUR_MKVP, apqns, + &clr2seck2.apqns, + &clr2seck2.apqn_entries, verbose); + if (rc != 0) { + if (rc == -ENODEV || rc == -ENOTSUP) + warnx("No APQN is available that can generate " + "a secure key of type %s", key_type); + else + warnx("Failed to build a list of APQNs that " + "can generate a secure key of type %s: " + "%s", key_type, strerror(-rc)); goto out; } - memcpy(secure_key+SECURE_KEY_SIZE, &clr2sec.seckey, - SECURE_KEY_SIZE); + clr2seck2.key = secure_key + size; + clr2seck2.keylen = size; + + rc = pkey_clr2seck2(pkey_fd, &clr2seck2, verbose); + if (rc != 0) { + warnx("Failed to generate a secure key: %s", + strerror(-rc)); + goto out; + } } pr_verbose(verbose, @@ -465,10 +1141,11 @@ int generate_secure_key_clear(int pkey_fd, const char *keyfile, rc = write_secure_key(keyfile, secure_key, secure_key_size, verbose); out: - memset(&clr2sec, 0, sizeof(clr2sec)); + memset(&clr2seck2, 0, sizeof(clr2seck2)); memset(clear_key, 0, clear_key_size); free(clear_key); free(secure_key); + free(clr2seck2.apqns); return rc; } @@ -476,84 +1153,58 @@ out: * Validates an XTS secure key (the second part) * * @param[in] pkey_fd the pkey file descriptor + * @param[in] apqn the APQN to verify the key with * @param[in] secure_key a buffer containing the secure key * @param[in] secure_key_size the secure key size * @param[in] part1_keysize the key size of the first key part - * @param[in] part1_attributes the attributes of the first key part + * @param[in] part1_flags the flags of the first key part * @param[out] clear_key_bitsize on return , the cryptographic size of the * clear key * @param[in] verbose if true, verbose messages are printed * * @returns 0 on success, a negative errno in case of an error */ -static int validate_secure_xts_key(int pkey_fd, +static int validate_secure_xts_key(int pkey_fd, struct pkey_apqn *apqn, u8 *secure_key, size_t secure_key_size, - u16 part1_keysize, u32 part1_attributes, - size_t *clear_key_bitsize, bool verbose) + enum pkey_key_size part1_keysize, + u32 part1_flags, size_t *clear_key_bitsize, + bool verbose) { - struct secaeskeytoken *token = (struct secaeskeytoken *)secure_key; - struct pkey_verifykey verifykey; - struct secaeskeytoken *token2; + struct pkey_verifykey2 verifykey2; int rc; util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + util_assert(apqn != NULL, "Internal error: apqn is NULL"); - /* XTS uses 2 secure key tokens concatenated to each other */ - token2 = (struct secaeskeytoken *)(secure_key + SECURE_KEY_SIZE); - - if (secure_key_size != 2 * SECURE_KEY_SIZE) { - pr_verbose(verbose, "Size of secure key is too small: " - "%lu expected %lu", secure_key_size, - 2 * SECURE_KEY_SIZE); - return -EINVAL; - } - - if (token->bitsize != token2->bitsize) { - pr_verbose(verbose, "XTS secure key contains 2 clear keys of " - "different sizes"); - return -EINVAL; - } - if (token->keysize != token2->keysize) { - pr_verbose(verbose, "XTS secure key contains 2 keys of " - "different sizes"); - return -EINVAL; - } - if (memcmp(&token->mkvp, &token2->mkvp, sizeof(token->mkvp)) != 0) { - pr_verbose(verbose, "XTS secure key contains 2 keys using " - "different CCA master keys"); - return -EINVAL; - } - - memcpy(&verifykey.seckey, token2, sizeof(verifykey.seckey)); + memset(&verifykey2, 0, sizeof(verifykey2)); + verifykey2.key = secure_key + (secure_key_size / 2); + verifykey2.keylen = secure_key_size / 2; + verifykey2.cardnr = apqn->card; + verifykey2.domain = apqn->domain; - rc = ioctl(pkey_fd, PKEY_VERIFYKEY, &verifykey); + rc = pkey_verifyseck2(pkey_fd, &verifykey2, verbose); if (rc < 0) { - rc = -errno; - pr_verbose(verbose, "Failed to validate a secure key: %s", - strerror(-rc)); + pr_verbose(verbose, "Failed to validate the 2nd part of the " + "XTS secure key on APQN %02x.%04x: %s", apqn->card, + apqn->domain, strerror(-rc)); return rc; } - if ((verifykey.attributes & PKEY_VERIFY_ATTR_AES) == 0) { - pr_verbose(verbose, "Secure key is not an AES key"); - return -EINVAL; - } - - if (verifykey.keysize != part1_keysize) { + if (verifykey2.size != part1_keysize) { pr_verbose(verbose, "XTS secure key contains 2 keys using " "different key sizes"); return -EINVAL; } - if (verifykey.attributes != part1_attributes) { + if (verifykey2.flags != part1_flags) { pr_verbose(verbose, "XTS secure key contains 2 keys using " - "different attributes"); + "different master keys"); return -EINVAL; } - if (clear_key_bitsize) - *clear_key_bitsize += verifykey.keysize; + if (clear_key_bitsize && verifykey2.size != PKEY_SIZE_UNKNOWN) + *clear_key_bitsize += verifykey2.size; return 0; } @@ -568,6 +1219,8 @@ static int validate_secure_xts_key(int pkey_fd, * clear key * @param[out] is_old_mk in return set to 1 to indicate if the secure key * is currently enciphered by the OLD CCA master key + * @param[in] apqns a zero terminated array of pointers to APQN-strings, + * or NULL for AUTOSELECT * @param[in] verbose if true, verbose messages are printed * * @returns 0 on success, a negative errno in case of an error @@ -575,59 +1228,89 @@ static int validate_secure_xts_key(int pkey_fd, int validate_secure_key(int pkey_fd, u8 *secure_key, size_t secure_key_size, size_t *clear_key_bitsize, int *is_old_mk, - bool verbose) + const char **apqns, bool verbose) { - struct secaeskeytoken *token = (struct secaeskeytoken *)secure_key; - struct pkey_verifykey verifykey; + struct pkey_verifykey2 verifykey2; + struct pkey_apqn *list = NULL; + u32 i, list_entries = 0; + bool xts, valid; int rc; util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); - if (secure_key_size < SECURE_KEY_SIZE) { - pr_verbose(verbose, "Size of secure key is too small: " - "%lu expected %lu", secure_key_size, - SECURE_KEY_SIZE); - return -EINVAL; - } - - memcpy(&verifykey.seckey, token, sizeof(verifykey.seckey)); + xts = is_xts_key(secure_key, secure_key_size); - rc = ioctl(pkey_fd, PKEY_VERIFYKEY, &verifykey); - if (rc < 0) { - rc = -errno; - pr_verbose(verbose, "Failed to validate a secure key: %s", - strerror(-rc)); + rc = build_apqn_list_for_key(pkey_fd, secure_key, + HALF_KEYSIZE_FOR_XTS(secure_key_size, xts), + PKEY_FLAGS_MATCH_CUR_MKVP | + PKEY_FLAGS_MATCH_ALT_MKVP, + apqns, &list, &list_entries, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to build a list of APQNs that can " + "validate this secure key: %s", strerror(-rc)); return rc; } - if ((verifykey.attributes & PKEY_VERIFY_ATTR_AES) == 0) { - pr_verbose(verbose, "Secure key is not an AES key"); - return -EINVAL; - } + if (is_old_mk != NULL) + *is_old_mk = true; + if (clear_key_bitsize != NULL) + *clear_key_bitsize = 0; - if (clear_key_bitsize) - *clear_key_bitsize = verifykey.keysize; + valid = false; + for (i = 0; i < list_entries; i++) { + memset(&verifykey2, 0, sizeof(verifykey2)); + verifykey2.key = secure_key; + verifykey2.keylen = HALF_KEYSIZE_FOR_XTS(secure_key_size, xts); + verifykey2.cardnr = list[i].card; + verifykey2.domain = list[i].domain; - /* XTS uses 2 secure key tokens concatenated to each other */ - if (secure_key_size > SECURE_KEY_SIZE) { - rc = validate_secure_xts_key(pkey_fd, - secure_key, secure_key_size, - verifykey.keysize, - verifykey.attributes, - clear_key_bitsize, - verbose); - if (rc != 0) - return rc; + rc = pkey_verifyseck2(pkey_fd, &verifykey2, verbose); + if (rc < 0) { + pr_verbose(verbose, "Failed to validate the secure key " + "on APQN %02x.%04x: %s", list[i].card, + list[i].domain, strerror(-rc)); + continue; + } + + if (is_xts_key(secure_key, secure_key_size)) { + rc = validate_secure_xts_key(pkey_fd, &list[i], + secure_key, + secure_key_size, + verifykey2.size, + verifykey2.flags, + clear_key_bitsize, + verbose); + if (rc != 0) + continue; + + } + + valid = true; + + if (clear_key_bitsize) { + if (verifykey2.size != PKEY_SIZE_UNKNOWN) + *clear_key_bitsize += verifykey2.size; + clear_key_bitsize = NULL; /* Set it only once */ + } + + /* + * If at least one of the APQNs have a matching current MK, + * then don't report OLD, even if some match the old MK. + */ + if (is_old_mk && + (verifykey2.flags & PKEY_FLAGS_MATCH_CUR_MKVP)) + *is_old_mk = false; } - if (is_old_mk) - *is_old_mk = (verifykey.attributes & - PKEY_VERIFY_ATTR_OLD_MKVP) != 0; + if (!valid) + return -ENODEV; pr_verbose(verbose, "Secure key validation completed successfully"); - return 0; + if (list != NULL) + free(list); + return rc; } /** @@ -642,7 +1325,7 @@ int validate_secure_key(int pkey_fd, * * @returns 0 on success, a negative errno in case of an error */ -int generate_key_verification_pattern(const char *key, size_t key_size, +int generate_key_verification_pattern(const u8 *key, size_t key_size, char *vp, size_t vp_len, bool verbose) { int tfmfd = -1, opfd = -1, rc = 0; @@ -677,7 +1360,7 @@ int generate_key_verification_pattern(const char *key, size_t key_size, } snprintf((char *)sa.salg_name, sizeof(sa.salg_name), "%s(paes)", - key_size > SECURE_KEY_SIZE ? "xts" : "cbc"); + is_xts_key(key, key_size) ? "xts" : "cbc"); tfmfd = socket(AF_ALG, SOCK_SEQPACKET, 0); if (tfmfd < 0) { @@ -770,23 +1453,337 @@ out: return rc; } -int get_master_key_verification_pattern(const u8 *secure_key, - size_t secure_key_size, u64 *mkvp, - bool verbose) +int get_master_key_verification_pattern(const u8 *key, size_t key_size, + u64 *mkvp, bool UNUSED(verbose)) { - struct secaeskeytoken *token = (struct secaeskeytoken *)secure_key; + struct aesdatakeytoken *datakey = (struct aesdatakeytoken *)key; + struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; - util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + util_assert(key != NULL, "Internal error: secure_key is NULL"); util_assert(mkvp != NULL, "Internal error: mkvp is NULL"); - if (secure_key_size < SECURE_KEY_SIZE) { - pr_verbose(verbose, "Size of secure key is too small: " - "%lu expected %lu", secure_key_size, - SECURE_KEY_SIZE); + if (is_cca_aes_data_key(key, key_size)) + *mkvp = datakey->mkvp; + else if (is_cca_aes_cipher_key(key, key_size)) + memcpy(mkvp, cipherkey->kvp, sizeof(*mkvp)); + else return -EINVAL; + + return 0; +} + +/** + * Check if the specified key is a CCA AESDATA key token. + * + * @param[in] key the secure key token + * @param[in] key_size the size of the secure key + * + * @returns true if the key is an CCA AESDATA token type + */ +bool is_cca_aes_data_key(const u8 *key, size_t key_size) +{ + struct tokenheader *hdr = (struct tokenheader *)key; + + if (key == NULL || key_size < AESDATA_KEY_SIZE) + return false; + + if (hdr->type != TOKEN_TYPE_CCA_INTERNAL) + return false; + if (hdr->version != TOKEN_VERSION_AESDATA) + return false; + + return true; +} + +/** + * Check if the specified key is a CCA AESCIPHER key token. + * + * @param[in] key the secure key token + * @param[in] key_size the size of the secure key + * + * @returns true if the key is an CCA AESCIPHER token type + */ +bool is_cca_aes_cipher_key(const u8 *key, size_t key_size) +{ + struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; + + if (key == NULL || key_size < AESCIPHER_KEY_SIZE) + return false; + + if (cipherkey->type != TOKEN_TYPE_CCA_INTERNAL) + return false; + if (cipherkey->version != TOKEN_VERSION_AESCIPHER) + return false; + if (cipherkey->length > key_size) + return false; + + if (cipherkey->kms != 0x03) /* key wrapped by master key */ + return false; + if (cipherkey->kwm != 0x02) /* key wrapped using AESKW */ + return false; + if (cipherkey->pfv != 0x00 && cipherkey->pfv != 0x01) /* V0 or V1 */ + return false; + if (cipherkey->adv != 0x01) /* Should have ass. data sect. version 1 */ + return false; + if (cipherkey->at != 0x02) /* Algorithm: AES */ + return false; + if (cipherkey->kt != 0x0001) /* Key type: CIPHER */ + return false; + if (cipherkey->adl != 26) /* Ass. data section length should be 26 */ + return false; + if (cipherkey->kll != 0) /* Should have no key label */ + return false; + if (cipherkey->eadl != 0) /* Should have no ext associated data */ + return false; + if (cipherkey->uadl != 0) /* Should have no user associated data */ + return false; + if (cipherkey->kufc != 2) /* Should have 2 KUFs */ + return false; + if (cipherkey->kmfc != 3) /* Should have 3 KMFs */ + return false; + + return true; +} + +/** + * Check if the specified key is an XTS type key + * + * @param[in] key the secure key token + * @param[in] key_size the size of the secure key + * + * @returns true if the key is an XTS key type + */ +bool is_xts_key(const u8 *key, size_t key_size) +{ + if (is_cca_aes_data_key(key, key_size)) { + if (key_size == 2 * AESDATA_KEY_SIZE && + is_cca_aes_data_key(key + AESDATA_KEY_SIZE, + key_size - AESDATA_KEY_SIZE)) + return true; + } else if (is_cca_aes_cipher_key(key, key_size)) { + if (key_size == 2 * AESCIPHER_KEY_SIZE && + is_cca_aes_cipher_key(key + AESCIPHER_KEY_SIZE, + key_size - AESCIPHER_KEY_SIZE)) + return true; } - *mkvp = token->mkvp; + return false; +} + +/** + * Gets the size in bits of the effective key of the specified secure key + * + * @param[in] key the secure key token + * @param[in] key_size the size of the secure key + * @param[out] bitsize On return, contains the size in bits of the key. + * If the key size can not be determined, then 0 is + * passed back as bitsize. + * + * @returns 0 on success, a negative errno in case of an error + */ +int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) +{ + struct aesdatakeytoken *datakey = (struct aesdatakeytoken *)key; + struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; + + util_assert(bitsize != NULL, "Internal error: bitsize is NULL"); + + if (is_cca_aes_data_key(key, key_size)) { + *bitsize = datakey->bitsize; + if (key_size == 2 * AESDATA_KEY_SIZE) { + datakey = (struct aesdatakeytoken *)key + + AESDATA_KEY_SIZE; + *bitsize += datakey->bitsize; + } + } else if (is_cca_aes_cipher_key(key, key_size)) { + if (cipherkey->pfv == 0x00) /* V0 payload */ + *bitsize = cipherkey->pl - 384; + else + *bitsize = 0; /* Unknown */ + if (key_size > cipherkey->length) { + cipherkey = (struct aescipherkeytoken *)key + + cipherkey->length; + if (cipherkey->pfv == 0x00) /* V0 payload */ + *bitsize += cipherkey->pl - 384; + } + } else { + return -EINVAL; + } return 0; } + +/** + * Returns the type of the key + * + * @param[in] key the secure key token + * @param[in] key_size the size of the secure key + * + * @returns a static string on success, NULL in case of an error + */ +const char *get_key_type(const u8 *key, size_t key_size) +{ + if (is_cca_aes_data_key(key, key_size)) + return KEY_TYPE_CCA_AESDATA; + if (is_cca_aes_cipher_key(key, key_size)) + return KEY_TYPE_CCA_AESCIPHER; + + return NULL; +} + +/** + * Returns the minimum card level for a specific key type + * + * @param[in] key_type the type of the key + * + * @returns the minimum card level, or -1 for unknown key types + */ +int get_min_card_level_for_keytype(const char *key_type) +{ + if (key_type == NULL) + return -1; + + if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0) + return 3; + if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) + return 6; + + return -1; +} + +/** + * Performs extended checks on an AES CIPHER key. It checks the key usage + * fields (KUFs) and key management fields (KMFs) of the key. The function + * returns -EINVAL and issues warning messages if a mismatch is detected. + * + * @param[in] key the secure key token + * @param[in] key_size the size of the secure key + * + * @returns 0 on success, a negative errno in case of an error + */ +int check_aes_cipher_key(const u8 *key, size_t key_size) +{ + struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; + bool mismatch = false; + + if (!is_cca_aes_cipher_key(key, key_size)) { + warnx("The key is not of type '"KEY_TYPE_CCA_AESCIPHER"'"); + return -EINVAL; + } + + if ((cipherkey->kuf1 & 0x8000) == 0) { + printf("WARNING: The secure key can not be used for " + "encryption\n"); + mismatch = true; + } + if ((cipherkey->kuf1 & 0x4000) == 0) { + printf("WARNING: The secure key can not be used for " + "decryption\n"); + mismatch = true; + } + if (cipherkey->kuf1 & 0x2000) { + printf("INFO: The secure key can be used for data translate\n"); + mismatch = true; + } + if (cipherkey->kuf1 & 0x1000) { + printf("WARNING: The secure key can only be used in UDXs\n"); + mismatch = true; + } + + if (cipherkey->kmf1 & 0x8000) { + printf("WARNING: The secure key can be exported using a " + "symmetric key\n"); + mismatch = true; + } + if (cipherkey->kmf1 & 0x4000) { + printf("WARNING: The secure key can be exported using an " + "unauthenticated asymmetric key\n"); + mismatch = true; + } + if (cipherkey->kmf1 & 0x2000) { + printf("WARNING: The secure key can be exported using an " + "authenticated asymmetric key\n"); + mismatch = true; + } + if (cipherkey->kmf1 & 0x1000) { + printf("WARNING: The secure key can be exported using a RAW " + "key\n"); + mismatch = true; + } + if ((cipherkey->kmf1 & 0x0800) == 0) { + printf("WARNING: The secure key can not be transformed into a " + "CPACF protected key\n"); + mismatch = true; + } + if ((cipherkey->kmf1 & 0x0080) == 0) { + printf("WARNING: The secure key can be exported using a DES " + "key\n"); + mismatch = true; + } + if ((cipherkey->kmf1 & 0x0040) == 0) { + printf("WARNING: The secure key can be exported using an AES " + "key\n"); + mismatch = true; + } + if ((cipherkey->kmf1 & 0x0008) == 0) { + printf("WARNING: The secure key can be exported using an RSA " + "key\n"); + mismatch = true; + } + + if (cipherkey->kmf2 & 0xC000) { + printf("WARNING: The secure key is incomplete\n"); + mismatch = true; + } + if (cipherkey->kmf2 & 0x0010) { + printf("WARNING: The secure key was previously encrypted with " + "an untrusted KEK\n"); + mismatch = true; + } + if (cipherkey->kmf2 & 0x0008) { + printf("WARNING: The secure key was previously in a format " + "without type or usage attributes\n"); + mismatch = true; + } + if (cipherkey->kmf2 & 0x0004) { + printf("WARNING: The secure key was previously encrypted with " + "a key weaker than itself\n"); + mismatch = true; + } + if (cipherkey->kmf2 & 0x0002) { + printf("WARNING: The secure key was previously in a non-CCA " + "format\n"); + mismatch = true; + } + if (cipherkey->kmf2 & 0x0001) { + printf("WARNING: The secure key was previously encrypted in " + "ECB mode\n"); + mismatch = true; + } + + if ((cipherkey->kmf3 & 0xFF00) == 0x0000 || + (cipherkey->kmf3 & 0x00FF) == 0x0000) { + printf("WARNING: The secure key was created by an unknown " + "method\n"); + mismatch = true; + } + if ((cipherkey->kmf3 & 0xFF00) == 0x0400 || + (cipherkey->kmf3 & 0x00FF) == 0x0004) { + printf("WARNING: The secure key was created from cleartext key " + "components\n"); + mismatch = true; + } + if ((cipherkey->kmf3 & 0xFF00) == 0x0500 || + (cipherkey->kmf3 & 0x00FF) == 0x0005) { + printf("WARNING: The secure key was entered as a cleartext key " + "value\n"); + mismatch = true; + } + if ((cipherkey->kmf3 & 0x00FF) == 0x0012) { + printf("WARNING: The secure key was converted from a CCA " + "key-token that had no export control attributes\n"); + mismatch = true; + } + + return mismatch ? -EINVAL : 0; +} diff --git a/zkey/pkey.h b/zkey/pkey.h index c0aac2e..3dfd588 100644 --- a/zkey/pkey.h +++ b/zkey/pkey.h @@ -18,10 +18,23 @@ /* * Definitions for the /dev/pkey kernel module interface */ -struct secaeskeytoken { - u8 type; /* 0x01 for internal key token */ +struct tokenheader { + u8 type; u8 res0[3]; - u8 version; /* should be 0x04 */ + u8 version; + u8 res1[3]; +} __packed; + +#define TOKEN_TYPE_NON_CCA 0x00 +#define TOKEN_TYPE_CCA_INTERNAL 0x01 + +#define TOKEN_VERSION_AESDATA 0x04 +#define TOKEN_VERSION_AESCIPHER 0x05 + +struct aesdatakeytoken { + u8 type; /* TOKEN_TYPE_INTERNAL (0x01) for internal key token */ + u8 res0[3]; + u8 version; /* should be TOKEN_VERSION_AESDATA (0x04) */ u8 res1[1]; u8 flag; /* key flags */ u8 res2[1]; @@ -33,22 +46,60 @@ struct secaeskeytoken { u8 tvv[4]; /* token validation value */ } __packed; -#define SECURE_KEY_SIZE sizeof(struct secaeskeytoken) +struct aescipherkeytoken { + u8 type; /* TOKEN_TYPE_INTERNAL (0x01) for internal key token */ + u8 res0; + u16 length; /* length of token */ + u8 version; /* should be TOKEN_VERSION_CIPHER (0x05) */ + u8 res1[3]; + u8 kms; /* key material state, should be 0x03 */ + u8 kvptype; /* key verification pattern type */ + u8 kvp[16]; /* key verification pattern */ + u8 kwm; /* key wrapping method, should be 0x02 */ + u8 kwh; /* key wrapping hash algorithm */ + u8 pfv; /* payload format version, should be 0x00*/ + u8 res2; + u8 adv; /* associated data section version */ + u8 res3; + u16 adl; /* associated data length */ + u8 kll; /* length of optional key label */ + u8 eadl; /* extended associated data length */ + u8 uadl; /* user associated data length */ + u8 res4; + u16 pl; /* payload bit length */ + u8 res5; + u8 at; /* algorithm type, should be 0x02 (AES) */ + u16 kt; /* key type, should be 0x001 (CIPHER) */ + u8 kufc; /* key usage field count */ + u16 kuf1; /* key usage field 1 */ + u16 kuf2; /* key usage field 2 */ + u8 kmfc; /* key management field count */ + u16 kmf1; /* key management field 1 */ + u16 kmf2; /* key management field 2 */ + u16 kmf3; /* key management field 3 */ + u8 varpart[80]; /* variable part */ +} __packed; + +#define AESDATA_KEY_SIZE sizeof(struct aesdatakeytoken) +#define AESCIPHER_KEY_SIZE sizeof(struct aescipherkeytoken) + +#define MAX_SECURE_KEY_SIZE MAX(AESDATA_KEY_SIZE, AESCIPHER_KEY_SIZE) +#define MIN_SECURE_KEY_SIZE MIN(AESDATA_KEY_SIZE, AESCIPHER_KEY_SIZE) struct pkey_seckey { - u8 seckey[SECURE_KEY_SIZE]; /* the secure key blob */ + u8 seckey[AESDATA_KEY_SIZE]; /* the secure key blob */ }; struct pkey_clrkey { u8 clrkey[32]; /* 16, 24, or 32 byte clear key value */ }; -#define PKEY_IOCTL_MAGIC 'p' -#define AUTOSELECT 0xFFFF -#define PKEYDEVICE "/dev/pkey" -#define PKEY_KEYTYPE_AES_128 1 -#define PKEY_KEYTYPE_AES_192 2 -#define PKEY_KEYTYPE_AES_256 3 +#define PKEY_IOCTL_MAGIC 'p' +#define AUTOSELECT 0xFFFF +#define PKEYDEVICE "/dev/pkey" +#define PKEY_KEYTYPE_AES_128 1 +#define PKEY_KEYTYPE_AES_192 2 +#define PKEY_KEYTYPE_AES_256 3 struct pkey_genseck { u16 cardnr; /* in: card to use or FFFF for any */ @@ -82,6 +133,100 @@ struct pkey_verifykey { #define PKEY_VERIFYKEY _IOWR(PKEY_IOCTL_MAGIC, 0x07, struct pkey_verifykey) +enum pkey_key_type { + PKEY_TYPE_CCA_DATA = (u32) 1, + PKEY_TYPE_CCA_CIPHER = (u32) 2, +}; + +enum pkey_key_size { + PKEY_SIZE_AES_128 = (u32) 128, + PKEY_SIZE_AES_192 = (u32) 192, + PKEY_SIZE_AES_256 = (u32) 256, + PKEY_SIZE_UNKNOWN = (u32) 0xFFFFFFFF, +}; + +#define PKEY_FLAGS_MATCH_CUR_MKVP 0x00000002 +#define PKEY_FLAGS_MATCH_ALT_MKVP 0x00000004 + +#define PKEY_KEYGEN_XPRT_SYM 0x00008000 +#define PKEY_KEYGEN_XPRT_UASY 0x00004000 +#define PKEY_KEYGEN_XPRT_AASY 0x00002000 +#define PKEY_KEYGEN_XPRT_RAW 0x00001000 +#define PKEY_KEYGEN_XPRT_CPAC 0x00000800 +#define PKEY_KEYGEN_XPRT_DES 0x00000080 +#define PKEY_KEYGEN_XPRT_AES 0x00000040 +#define PKEY_KEYGEN_XPRT_RSA 0x00000008 + +struct pkey_apqn { + u16 card; + u16 domain; +}; + +struct pkey_genseck2 { + struct pkey_apqn *apqns; /* in: ptr to list of apqn targets */ + u32 apqn_entries; /* in: # of apqn target list entries */ + enum pkey_key_type type; /* in: key type to generate */ + enum pkey_key_size size; /* in: key size to generate */ + u32 keygenflags; /* in: key generation flags */ + u8 *key; /* in: pointer to key blob buffer */ + u32 keylen; /* in: available key blob buffer size */ + /* out: actual key blob size */ +}; + +#define PKEY_GENSECK2 _IOWR(PKEY_IOCTL_MAGIC, 0x11, struct pkey_genseck2) + +struct pkey_clr2seck2 { + struct pkey_apqn *apqns; /* in: ptr to list of apqn targets */ + u32 apqn_entries; /* in: # of apqn target list entries */ + enum pkey_key_type type; /* in: key type to generate */ + enum pkey_key_size size; /* in: key size to generate */ + u32 keygenflags; /* in: key generation flags */ + struct pkey_clrkey clrkey; /* in: the clear key value */ + u8 *key; /* in: pointer to key blob buffer */ + u32 keylen; /* in: available key blob buffer size */ + /* out: actual key blob size */ +}; + +#define PKEY_CLR2SECK2 _IOWR(PKEY_IOCTL_MAGIC, 0x12, struct pkey_clr2seck2) + +struct pkey_verifykey2 { + u8 *key; /* in: pointer to key blob */ + u32 keylen; /* in: key blob size */ + u16 cardnr; /* in/out: card number */ + u16 domain; /* in/out: domain number */ + enum pkey_key_type type; /* out: the key type */ + enum pkey_key_size size; /* out: the key size */ + u32 flags; /* out: additional key info flags */ +}; + +#define PKEY_VERIFYKEY2 _IOWR(PKEY_IOCTL_MAGIC, 0x17, struct pkey_verifykey2) + +struct pkey_apqns4key { + u8 *key; /* in: pointer to key blob */ + u32 keylen; /* in: key blob size */ + u32 flags; /* in: match controlling flags */ + struct pkey_apqn *apqns; /* in/out: ptr to list of apqn targets*/ + u32 apqn_entries; /* in: max # of apqn entries in list */ + /* out: # apqns stored into the list */ +}; + +#define PKEY_APQNS4K _IOWR(PKEY_IOCTL_MAGIC, 0x1B, struct pkey_apqns4key) + +struct pkey_apqns4keytype { + enum pkey_key_type type; /* in: key type */ + u8 cur_mkvp[32]; /* in: current mkvp */ + u8 alt_mkvp[32]; /* in: alternate mkvp */ + u32 flags; /* in: match controlling flags */ + struct pkey_apqn *apqns; /* in/out: ptr to list of apqn targets*/ + u32 apqn_entries; /* in: max # of apqn entries in list */ + /* out: # apqns stored into the list */ +}; + +#define PKEY_APQNS4KT _IOWR(PKEY_IOCTL_MAGIC, 0x1C, struct pkey_apqns4keytype) + +#define KEY_TYPE_CCA_AESDATA "CCA-AESDATA" +#define KEY_TYPE_CCA_AESCIPHER "CCA-AESCIPHER" + #define PAES_BLOCK_SIZE 16 #define ENC_ZERO_LEN (2 * PAES_BLOCK_SIZE) #define VERIFICATION_PATTERN_LEN (2 * ENC_ZERO_LEN + 1) @@ -89,14 +234,13 @@ struct pkey_verifykey { int open_pkey_device(bool verbose); int generate_secure_key_random(int pkey_fd, const char *keyfile, - size_t keybits, bool xts, u16 card, u16 domain, - bool verbose); + size_t keybits, bool xts, const char *key_type, + const char **apqns, bool verbose); int generate_secure_key_clear(int pkey_fd, const char *keyfile, size_t keybits, bool xts, - const char *clearkeyfile, - u16 card, u16 domain, - bool verbose); + const char *clearkeyfile, const char *key_type, + const char **apqns, bool verbose); u8 *read_secure_key(const char *keyfile, size_t *secure_key_size, bool verbose); @@ -107,13 +251,20 @@ int write_secure_key(const char *keyfile, const u8 *secure_key, int validate_secure_key(int pkey_fd, u8 *secure_key, size_t secure_key_size, size_t *clear_key_bitsize, int *is_old_mk, - bool verbose); + const char **apqns, bool verbose); -int generate_key_verification_pattern(const char *key, size_t key_size, +int generate_key_verification_pattern(const u8 *key, size_t key_size, char *vp, size_t vp_len, bool verbose); -int get_master_key_verification_pattern(const u8 *secure_key, - size_t secure_key_size, u64 *mkvp, - bool verbose); +int get_master_key_verification_pattern(const u8 *key, size_t key_size, + u64 *mkvp, bool verbose); + +bool is_cca_aes_data_key(const u8 *key, size_t key_size); +bool is_cca_aes_cipher_key(const u8 *key, size_t key_size); +bool is_xts_key(const u8 *key, size_t key_size); +int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize); +const char *get_key_type(const u8 *key, size_t key_size); +int get_min_card_level_for_keytype(const char *key_type); +int check_aes_cipher_key(const u8 *key, size_t key_size); #endif diff --git a/zkey/utils.c b/zkey/utils.c index 9dc4af7..e70ebcd 100644 --- a/zkey/utils.c +++ b/zkey/utils.c @@ -118,6 +118,49 @@ out: return rc; } +/** + * Returns the level of the card. For a CEX3C 3 is returned, for a CEX4C 4, + * and so on. + * + * @param[in] card card number + * + * @returns The card level, or -1 of the level can not be determined. + */ +int sysfs_get_card_level(int card) +{ + char *dev_path; + char type[20]; + int rc; + + dev_path = util_path_sysfs("bus/ap/devices/card%02x", card); + if (!util_path_is_dir(dev_path)) { + rc = -1; + goto out; + } + if (util_file_read_line(type, sizeof(type), "%s/type", dev_path) != 0) { + rc = -1; + goto out; + } + if (strncmp(type, "CEX", 3) != 0 || strlen(type) < 5) { + rc = -1; + goto out; + } + if (type[4] != 'C') { + rc = -1; + goto out; + } + if (type[3] < '1' || type[3] > '9') { + rc = -1; + goto out; + } + + rc = type[3] - '0'; + +out: + free(dev_path); + return rc; +} + /** * Gets the 8 character ASCII serial number string of an card from the sysfs. * @@ -436,12 +479,14 @@ static int print_apqn_mk_info(int card, int domain, void *handler_data) { struct print_apqn_info *info = (struct print_apqn_info *)handler_data; struct mk_info mk_info; - int rc; + int rc, level; rc = sysfs_get_mkvps(card, domain, &mk_info, info->verbose); if (rc == -ENOTSUP) return rc; + level = sysfs_get_card_level(card); + util_rec_set(info->rec, "APQN", "%02x.%04x", card, domain); if (rc == 0) { @@ -470,6 +515,11 @@ static int print_apqn_mk_info(int card, int domain, void *handler_data) util_rec_set(info->rec, "OLD", "?"); } + if (level > 0) + util_rec_set(info->rec, "TYPE", "CEX%dC", level); + else + util_rec_set(info->rec, "TYPE", "?"); + util_rec_print(info->rec); return 0; @@ -499,6 +549,7 @@ int print_mk_info(const char *apqns, bool verbose) util_rec_def(info.rec, "NEW", UTIL_REC_ALIGN_LEFT, 16, "NEW MK"); util_rec_def(info.rec, "CUR", UTIL_REC_ALIGN_LEFT, 16, "CURRENT MK"); util_rec_def(info.rec, "OLD", UTIL_REC_ALIGN_LEFT, 16, "OLD MK"); + util_rec_def(info.rec, "TYPE", UTIL_REC_ALIGN_LEFT, 6, "TYPE"); util_rec_print_hdr(info.rec); rc = handle_apqns(apqns, print_apqn_mk_info, &info, verbose); @@ -511,6 +562,7 @@ struct cross_check_info { u64 mkvp; u64 new_mkvp; bool key_mkvp; + int min_level; u32 num_cur_match; u32 num_old_match; u32 num_new_match; @@ -525,7 +577,7 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) struct cross_check_info *info = (struct cross_check_info *)handler_data; struct mk_info mk_info; char temp[200]; - int rc; + int rc, level; rc = sysfs_get_mkvps(card, domain, &mk_info, info->verbose); if (rc == -ENODEV) { @@ -539,6 +591,19 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) info->num_checked++; + if (info->min_level >= 0) { + level = sysfs_get_card_level(card); + + if (level < info->min_level) { + info->print_mks = 1; + info->mismatch = 1; + sprintf(temp, "WARNING: APQN %02x.%04x: The card level " + "is less than CEX%dC.", card, domain, + info->min_level); + util_print_indented(temp, 0); + } + } + if (mk_info.new_mk.mk_state == MK_STATE_PARTIAL) { info->print_mks = 1; sprintf(temp, "INFO: APQN %02x.%04x: The NEW master key " @@ -662,6 +727,8 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) * @param[in] mkvp The master key verification pattern of a secure key. * If this is all zero, then the master keys are not * matched against it. + * @param[in] min_level The minimum card level required. If min_level is -1 then + * the card level is not checked. * @param[in] print_mks if true, then a the full master key info of all * specified APQns is printed, in case of a mismatch. * @param[in] verbose if true, verbose messages are printed @@ -671,7 +738,8 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) * -ENOTSUP is returned when the mkvps sysfs attribute is not * available, because the zcrypt kernel module is on an older level. */ -int cross_check_apqns(const char *apqns, u64 mkvp, bool print_mks, bool verbose) +int cross_check_apqns(const char *apqns, u64 mkvp, int min_level, + bool print_mks, bool verbose) { struct cross_check_info info; char temp[200]; @@ -680,10 +748,12 @@ int cross_check_apqns(const char *apqns, u64 mkvp, bool print_mks, bool verbose) memset(&info, 0, sizeof(info)); info.key_mkvp = mkvp != 0; info.mkvp = mkvp; + info.min_level = min_level; info.verbose = verbose; - pr_verbose(verbose, "Cross checking APQNs with mkvp 0x%016llx: %s", - mkvp, apqns != NULL ? apqns : "ANY"); + pr_verbose(verbose, "Cross checking APQNs with mkvp 0x%016llx and " + "min-level %d: %s", mkvp, min_level, + apqns != NULL ? apqns : "ANY"); rc = handle_apqns(apqns, cross_check_mk_info, &info, verbose); if (rc != 0) @@ -723,3 +793,27 @@ int cross_check_apqns(const char *apqns, u64 mkvp, bool print_mks, bool verbose) return rc; } + +/* + * Prompts for yes or no. Returns true if 'y' or 'yes' was entered. + * + * @param[in] verbose if true, verbose messages are printed + * + * @returns true if 'y' or 'yes' was entered (case insensitive). Returns false + * otherwise. + */ +bool prompt_for_yes(bool verbose) +{ + char str[20]; + + if (fgets(str, sizeof(str), stdin) == NULL) + return false; + + if (str[strlen(str) - 1] == '\n') + str[strlen(str) - 1] = '\0'; + pr_verbose(verbose, "Prompt reply: '%s'", str); + if (strcasecmp(str, "y") == 0 || strcasecmp(str, "yes") == 0) + return true; + + return false; +} diff --git a/zkey/utils.h b/zkey/utils.h index c4dfb2b..2a915eb 100644 --- a/zkey/utils.h +++ b/zkey/utils.h @@ -18,6 +18,8 @@ int sysfs_is_card_online(int card); int sysfs_is_apqn_online(int card, int domain); +int sysfs_get_card_level(int card); + int sysfs_get_serialnr(int card, char serialnr[9], bool verbose); #define MK_STATE_EMPTY 0 @@ -48,7 +50,9 @@ int handle_apqns(const char *apqns, apqn_handler_t handler, void *handler_data, int print_mk_info(const char *apqns, bool verbose); -int cross_check_apqns(const char *apqns, u64 mkvp, bool print_mks, - bool verbose); +int cross_check_apqns(const char *apqns, u64 mkvp, int min_level, + bool print_mks, bool verbose); + +bool prompt_for_yes(bool verbose); #endif diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c index c683fd4..7ba2842 100644 --- a/zkey/zkey-cryptsetup.c +++ b/zkey/zkey-cryptsetup.c @@ -1329,22 +1329,25 @@ static int activate_unbound_keyslot(int token, int keyslot, const char *key, return rc; } -static int check_keysize_and_cipher_mode(size_t keysize) +static int check_keysize_and_cipher_mode(const u8 *key, size_t keysize) { - if (keysize == 0) { + if (keysize < MIN_SECURE_KEY_SIZE || + keysize > 2 * MAX_SECURE_KEY_SIZE) { warnx("Invalid volume key size"); return -EINVAL; } if (strncmp(crypt_get_cipher_mode(g.cd), "xts", 3) == 0) { - if (keysize != 2 * SECURE_KEY_SIZE) { + if (keysize < 2 * MIN_SECURE_KEY_SIZE || + (key != NULL && !is_xts_key(key, keysize))) { warnx("The volume key size %lu is not valid for the " "cipher mode '%s'", keysize, crypt_get_cipher_mode(g.cd)); return -EINVAL; } } else { - if (keysize != SECURE_KEY_SIZE) { + if (keysize > MAX_SECURE_KEY_SIZE || + (key != NULL && is_xts_key(key, keysize))) { warnx("The volume key size %lu is not valid for the " "cipher mode '%s'", keysize, crypt_get_cipher_mode(g.cd)); @@ -1377,7 +1380,7 @@ static int open_keyslot(int keyslot, char **key, size_t *keysize, vkeysize = crypt_get_volume_key_size(g.cd); pr_verbose("Volume key size: %lu", vkeysize); - rc = check_keysize_and_cipher_mode(vkeysize); + rc = check_keysize_and_cipher_mode(NULL, vkeysize); if (rc != 0) return rc; @@ -1489,7 +1492,7 @@ static int validate_keyslot(int keyslot, char **key, size_t *keysize, keyslot = rc; rc = validate_secure_key(g.pkey_fd, (u8 *)vkey, vkeysize, clear_keysize, - &is_old, g.verbose); + &is_old, NULL, g.verbose); if (rc != 0) { if (invalid_msg != NULL) warnx("%s", invalid_msg); @@ -1571,7 +1574,7 @@ static int reencipher_prepare(int token) if (rc != 0) goto out; - rc = generate_key_verification_pattern(key, keysize, + rc = generate_key_verification_pattern((u8 *)key, keysize, reenc_tok.verification_pattern, sizeof(reenc_tok.verification_pattern), g.verbose); @@ -1851,8 +1854,8 @@ static int reencipher_complete(int token) } - rc = generate_key_verification_pattern(key, keysize, vp, sizeof(vp), - g.verbose); + rc = generate_key_verification_pattern((u8 *)key, keysize, vp, + sizeof(vp), g.verbose); if (rc != 0) { warnx("Failed to generate the verification pattern: %s", strerror(-rc)); @@ -1969,7 +1972,7 @@ static int command_validate(void) goto out; rc = validate_secure_key(g.pkey_fd, (u8 *)key, keysize, &clear_keysize, - &is_old_mk, g.verbose); + &is_old_mk, NULL, g.verbose); is_valid = (rc == 0); token = find_token(g.cd, PAES_REENC_TOKEN_NAME); @@ -1998,7 +2001,9 @@ static int command_validate(void) printf(" Status: %s\n", is_valid ? "Valid" : "Invalid"); printf(" Secure key size: %lu bytes\n", keysize); printf(" XTS type key: %s\n", - keysize > SECURE_KEY_SIZE ? "Yes" : "No"); + is_xts_key((u8 *)key, keysize) ? "Yes" : "No"); + printf(" Key type: %s\n", + get_key_type((u8 *)key, keysize)); if (is_valid) { printf(" Clear key size: %lu bits\n", clear_keysize); printf(" Enciphered with: %s CCA master key (MKVP: " @@ -2074,7 +2079,7 @@ static int command_setvp(void) token = find_token(g.cd, PAES_VP_TOKEN_NAME); - rc = generate_key_verification_pattern(key, keysize, + rc = generate_key_verification_pattern((const u8 *)key, keysize, vp_tok.verification_pattern, sizeof(vp_tok.verification_pattern), g.verbose); @@ -2129,12 +2134,12 @@ static int command_setkey(void) if (newkey == NULL) return EXIT_FAILURE; - rc = check_keysize_and_cipher_mode(newkey_size); + rc = check_keysize_and_cipher_mode(newkey, newkey_size); if (rc != 0) goto out; rc = validate_secure_key(g.pkey_fd, newkey, newkey_size, NULL, - &is_old_mk, g.verbose); + &is_old_mk, NULL, g.verbose); if (rc != 0) { warnx("The secure key in file '%s' is not valid", g.master_key_file); @@ -2164,21 +2169,14 @@ static int command_setkey(void) if (rc < 0) goto out; - if (keysize != newkey_size) { - warnx("The secure key in file '%s' has an invalid size", - g.master_key_file); - rc = -EINVAL; - goto out; - } - - if (memcmp(newkey, key, keysize) == 0) { + if (keysize == newkey_size && memcmp(newkey, key, keysize) == 0) { warnx("The secure key in file '%s' is equal to the current " "volume key, setkey is ignored", g.master_key_file); rc = 0; goto out; } - rc = generate_key_verification_pattern((char *)newkey, newkey_size, vp, + rc = generate_key_verification_pattern(newkey, newkey_size, vp, sizeof(vp), g.verbose); if (rc != 0) { warnx("Failed to generate the verification pattern: %s", diff --git a/zkey/zkey.1 b/zkey/zkey.1 index c7cea83..31fff43 100644 --- a/zkey/zkey.1 +++ b/zkey/zkey.1 @@ -62,7 +62,8 @@ in size. The \fBzkey\fP tool can operate in two modes. When argument .I secure\-key\-file is specified then it operates on the secure key contained in the specified file. -This applies to commands \fBgenerate\fP, \fBvalidate\fP, and \fBreencipher\fP. +This applies to commands \fBgenerate\fP, \fBvalidate\fP, \fBreencipher\fP, and +\fBconvert\fP. When the .B \-\-name option is specified then it operates on a secure key contained in the secure @@ -79,6 +80,8 @@ key repository. .RB [ \-\-xts | \-x ] .RB [ \-\-clearkey | \-c .IR clear\-key\-file ] +.RB [ \-\-key-type | \-K +.IR type ] .RB [ \-\-verbose | \-V ] . .PP @@ -102,6 +105,8 @@ key repository. .RB [ \-\-xts | \-x ] .RB [ \-\-clearkey | \-c .IR clear\-key\-file ] +.RB [ \-\-key-type | \-K +.IR type ] .RB [ \-\-verbose | \-V ] .PP Use the @@ -129,6 +134,15 @@ additional information can be associated with a secure key using the , or the .B \-\-sector-size options. +.PP +You can generate different types of secure keys: \fBCCA-AESDATA\fP keys, and +\fBCCA-AESCIPHER\fP keys. Specify the type of the secure key using the +.B \-\-key\-type +option. The default key type is CCA-AESDATA. +.PP +.B Note: +Secure keys of type \fBCCA-AESCIPHER\fP require an IBM cryptographic +adapter in CCA coprocessor mode of version 6 or later, e.g. a CEX6C. . .SS "Validating secure AES keys" . @@ -336,6 +350,12 @@ additional information can be associated with a secure key using the , or the .B \-\-sector-size options. +.PP +.B Note: +The \fBimport\fP command requires the CCA host library (libcsulcca.so) +to be installed when secure keys of type \fBCCA-AESCIPHER\fP are imported. +For the supported environments and downloads, see: +\fIhttp://www.ibm.com/security/cryptocards\fP . .SS "Export AES secure keys from the secure key repository" . @@ -354,7 +374,6 @@ to a file in the file system. Specify the name of the key that is to be exported using the .B \-\-name option. You cannot use wildcards. -When wildcards are used you must quote the value. The exported secure key also remains in the secure key repository. . .SS "List AES secure keys contained in the secure key repository" @@ -369,6 +388,8 @@ The exported secure key also remains in the secure key repository. .IR card1.domain1[,card2.domain2[,...]] ] .RB [ \-\-volume-type | \-t .IR type ] +.RB [ \-\-key-type | \-K +.IR type ] .RB [ \-\-verbose | \-V ] . .PP @@ -383,7 +404,7 @@ listed that are associated with the specified volume and device-mapper name. .PP The .B list -command displays the attributes of the secure keys, such as key sizes, +command displays the attributes of the secure keys, such as key sizes, key type, whether it is a secure key that can be used for the XTS cipher mode, the textual description, associated cryptographic adapters (APQNs) and volumes, the sector size, the key verification pattern, and timestamps for key creation, last @@ -411,9 +432,11 @@ option. .PP .B Note: When removing a secure key that is associated with one or multiple volumes, +and the key's volume type is \fBplain\fP, a message informs you about the associated volumes. When the secure key is removed, these volumes can no longer be used, unless you have a backup of the -secure key. +secure key. For keys with volume type \fBluks2\fP no such message is issued, +because the secure key is contained in the LUKS2 header. . .SS "Change existing AES secure keys contained the secure key repository" . @@ -490,6 +513,15 @@ option and the new name using the .B \-\-new-name option. You cannot use wildcards. . +.B Note: +When renaming a secure key that is associated with one or multiple volumes and +the key's volume type is \fBplain\fP, a message informs you about the +associated volumes. When the secure key is renamed, these volumes can no +longer be used, unless you change the name of the secure key in the 'cryptsetup +plainOpen' commands and in the '/etc/crypttab' entries. +For keys with volume type \fBluks2\fP no such message is issued, because the +secure key is contained in the LUKS2 header. +. .SS "Copy (duplicate) existing AES secure keys in the secure key repository" . .B zkey @@ -649,6 +681,72 @@ questions, you can specify the option. These options are passed to the generated command(s) and behave in the same way as with \fBcryptsetup\fP. . +.SS "Convert existing AES secure keys from one key type to another type" +. +.B zkey +.BR convert | con +.I secure\-key\-file +.RB \-\-key-type | \-K +.IR type +.RB [ \-\-no\-apqn\-check ] +.RB [ \-\-force | \-F ] +.RB [ \-\-verbose | \-V ] +. +.PP +.B zkey +.BR convert | con +.B \-\-name | \-N +.IR key-name +.RB \-\-key-type | \-K +.IR type +.RB [ \-\-no\-apqn\-check ] +.RB [ \-\-force | \-F ] +.RB [ \-\-verbose | \-V ] +. +.PP +Use the +.B convert +command to convert an existing secure key from one key type to another type. +You can convert secure keys of type CCA-AESDATA to type CCA-AESCIPHER only. + +.B Note: +Secure keys converted to type \fBCCA-AESCIPHER\fP require an IBM cryptographic +adapter in CCA coprocessor mode of version 6 or later, e.g. a CEX6C. + +The secure key can either be contained in a file in the file system, or in a +secure key repository. To convert a secure key contained in a file, specify +the file name with option \fIsecure\-key\-file\fP. To convert a secure key +contained in the secure key repository, specify the name of the key +that is to be converted using the +.B \-\-name +option. You cannot use wildcards. The convert command prompts for +a confirmation, unless you specify the +.B \-\-force +option. +.PP +.B Note: +Converting a secure key is irreversible! +When converting a secure key that is associated with one or multiple volumes, +a message informs you about the associated volumes. When the secure key is +converted, this might have an effect on these volumes. +.P +For volumes with volume type \fBplain\fP, you must adapt the crypttab entries +and change the key size parameter to \fBsize=\fP or run +command \fBzkey crypttab --volumes \fP for each associated volume to +re-generate the crypttab entries. +.P +Associated volumes of type \fLUKS2\fP still contain the secure AES volume key of +the original type. To change the secure AES volume key in the LUKS2 header, +run command \fBzkey-cryptsetup setkey --master-key-file +\fP for each associated volume. +. +.P +.B Note: +The \fBconvert\fP command requires the CCA host library (libcsulcca.so) +to be installed. The required CCA IBM cryptographic adapter firmware version +is 6.3.27 or later. For the supported environments and downloads, see: +\fIhttp://www.ibm.com/security/cryptocards\fP +. . . . @@ -718,6 +816,13 @@ This option is only available if has been compiled with LUKS2 support enabled. If LUKS2 support is not enabled, the default volume type is \fBplain\fP. This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-K ", " \-\-key-type\~\fItype\fP +Specifies the key type of the secure key. Possible values are \fBCCA-AESDATA\fP +and \fBCCA-AESCIPHER\fP. If this option is omitted, then a secure key of type +CCA-AESDATA is generated. Secure keys of type \fBCCA-AESCIPHER\fP require an +IBM cryptographic adapter in CCA coprocessor mode of version 6 or later, e.g. +a CEX6C. . . . @@ -897,6 +1002,11 @@ This option is only available if .B zkey has been compiled with LUKS2 support enabled. This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-K ", " \-\-key-type\~\fItype\fP +Specifies the key type of the secure key. Possible values are \fBCCA-AESDATA\fP +and \fBCCA-AESCIPHER\fP. Only keys with the specified key type are listed. +This option is only used for secure keys contained in the secure key repository. . . . @@ -1160,6 +1270,30 @@ cryptsetup command(s). . . . +.SS "Options for the convert command" +.TP +.BR \-N ", " \-\-name\~\fIkey-name\fP +Specifies the name of the secure key in the secure key repository. You cannot +use wildcards. +This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-K ", " \-\-key-type\~\fItype\fP +Specifies the key type to which the secure key shall be converted to. +Possible values are \fBCCA-AESCIPHER\fP. Secure keys of type \fBCCA-AESCIPHER\fP +require an IBM cryptographic adapter in CCA coprocessor mode of version 6 or +later, e.g. a CEX6C. +.TP +.BR \-\-no\-apqn\-check +Do not check if the associated APQNs are available and capable of converting +the secure key to type CCA-AESCIPHER. +This option is only used for secure keys contained in the secure key repository. +.TP +.BR \-F ", " \-\-force\fP +The user is prompted to confirm the convertion of a secure key. Use this option +to convert a secure key without prompting for a confirmation. +. +. +. .SS "General options" .TP .BR \-V ", " \-\-verbose diff --git a/zkey/zkey.c b/zkey/zkey.c index a0dbc0b..3ae9a9b 100644 --- a/zkey/zkey.c +++ b/zkey/zkey.c @@ -73,6 +73,7 @@ static struct zkey_globals { long int sector_size; char *volume_type; char *newname; + char *key_type; bool run; bool batch_mode; char *keyfile; @@ -105,6 +106,7 @@ static struct zkey_globals { #define COMMAND_COPY "copy " #define COMMAND_CRYPTTAB "crypttab" #define COMMAND_CRYPTSETUP "cryptsetup" +#define COMMAND_CONVERT "convert" #define ZKEY_COMMAND_MAX_LEN 10 @@ -216,6 +218,15 @@ static struct util_opt opt_vec[] = { .command = COMMAND_GENERATE, }, #endif + { + .option = { "key-type", required_argument, NULL, 'K'}, + .argument = "type", + .desc = "The type of the key. Possible values are '" + KEY_TYPE_CCA_AESDATA"' and '"KEY_TYPE_CCA_AESCIPHER"'. " + "When this option is omitted, the default is '" + KEY_TYPE_CCA_AESDATA"'", + .command = COMMAND_GENERATE, + }, /***********************************************************/ { .flags = UTIL_OPT_FLAG_SECTION, @@ -432,6 +443,15 @@ static struct util_opt opt_vec[] = { .command = COMMAND_LIST, }, #endif + { + .option = { "key-type", required_argument, NULL, 'K'}, + .argument = "type", + .desc = "The type of the key. Possible values are '" + KEY_TYPE_CCA_AESDATA"' and '"KEY_TYPE_CCA_AESCIPHER"'. " + "Use this option to list all keys with the specified " + "key type.", + .command = COMMAND_LIST, + }, /***********************************************************/ { .flags = UTIL_OPT_FLAG_SECTION, @@ -745,6 +765,38 @@ static struct util_opt opt_vec[] = { .flags = UTIL_OPT_FLAG_NOSHORT, }, #endif + /***********************************************************/ + { + .flags = UTIL_OPT_FLAG_SECTION, + .desc = "OPTIONS", + .command = COMMAND_CONVERT, + }, + { + .option = { "name", required_argument, NULL, 'N'}, + .argument = "NAME", + .desc = "Name of the secure AES key in the repository that is " + "to be converted", + .command = COMMAND_CONVERT, + }, + { + .option = { "key-type", required_argument, NULL, 'K'}, + .argument = "type", + .desc = "The type of the key to convert the secure key to. " + "Possible values are '"KEY_TYPE_CCA_AESCIPHER"'. ", + .command = COMMAND_CONVERT, + }, + { + .option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK}, + .desc = "Do not check if the associated APQN(s) are available", + .command = COMMAND_CONVERT, + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + { + .option = {"force", 0, NULL, 'F'}, + .desc = "Do not prompt for a confirmation when converting a " + "key", + .command = COMMAND_CONVERT, + }, /***********************************************************/ { .flags = UTIL_OPT_FLAG_SECTION, @@ -793,6 +845,7 @@ static int command_rename(void); static int command_copy(void); static int command_crypttab(void); static int command_cryptsetup(void); +static int command_convert(void); static struct zkey_command zkey_commands[] = { { @@ -927,6 +980,21 @@ static struct zkey_command zkey_commands[] = { .has_options = 1, .need_keystore = 1, }, + { + .command = COMMAND_CONVERT, + .abbrev_len = 3, + .function = command_convert, + .need_cca_library = 1, + .need_pkey_device = 1, + .short_desc = "Convert a secure AES key", + .long_desc = "Convert an existing secure AES key that is " + "either contained in SECURE-KEY-FILE or is stored " + "in the repository from one key type to another " + "type.", + .has_options = 1, + .pos_arg = "[SECURE-KEY-FILE]", + .pos_arg_optional = 1, + }, { .command = NULL } }; @@ -1009,9 +1077,8 @@ static int command_generate_clear(void) rc = generate_secure_key_clear(g.pkey_fd, g.pos_arg, g.keybits, g.xts, - g.clearkeyfile, - AUTOSELECT, AUTOSELECT, - g.verbose); + g.clearkeyfile, g.key_type, + NULL, g.verbose); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1026,9 +1093,8 @@ static int command_generate_random(void) int rc; rc = generate_secure_key_random(g.pkey_fd, g.pos_arg, - g.keybits, g.xts, - AUTOSELECT, AUTOSELECT, - g.verbose); + g.keybits, g.xts, g.key_type, + NULL, g.verbose); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1048,7 +1114,7 @@ static int command_generate_repository(void) rc = keystore_generate_key(g.keystore, g.name, g.description, g.volumes, g.apqns, g.noapqncheck, g.sector_size, g.keybits, g.xts, g.clearkeyfile, - g.volume_type, g.pkey_fd); + g.volume_type, g.key_type, g.pkey_fd); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1075,6 +1141,8 @@ static int command_generate(void) util_prg_print_parse_error(); return EXIT_FAILURE; } + if (g.key_type == NULL) + g.key_type = KEY_TYPE_CCA_AESDATA; if (g.name != NULL) return command_generate_repository(); if (g.pos_arg != NULL) { @@ -1103,7 +1171,9 @@ static int command_generate(void) return EXIT_FAILURE; } - rc = cross_check_apqns(NULL, 0, true, g.verbose); + rc = cross_check_apqns(NULL, 0, + get_min_card_level_for_keytype(g.key_type), + true, g.verbose); if (rc == -EINVAL) return EXIT_FAILURE; if (rc != 0 && rc != -ENOTSUP) { @@ -1169,7 +1239,7 @@ static int command_reencipher_file(void) return EXIT_FAILURE; rc = validate_secure_key(g.pkey_fd, secure_key, secure_key_size, NULL, - &is_old_mk, g.verbose); + &is_old_mk, NULL, g.verbose); if (rc != 0) { warnx("The secure key in file '%s' is not valid", g.pos_arg); rc = EXIT_FAILURE; @@ -1385,14 +1455,14 @@ static int command_validate_file(void) return EXIT_FAILURE; rc = validate_secure_key(g.pkey_fd, secure_key, secure_key_size, - &clear_key_size, &is_old_mk, g.verbose); + &clear_key_size, &is_old_mk, NULL, g.verbose); if (rc != 0) { warnx("The secure key in file '%s' is not valid", g.pos_arg); rc = EXIT_FAILURE; goto out; } - rc = generate_key_verification_pattern((char *)secure_key, + rc = generate_key_verification_pattern(secure_key, secure_key_size, vp, sizeof(vp), g.verbose); if (rc != 0) { @@ -1416,9 +1486,11 @@ static int command_validate_file(void) printf("Validation of secure key in file '%s':\n", g.pos_arg); printf(" Status: Valid\n"); printf(" Secure key size: %lu bytes\n", secure_key_size); + printf(" Key type: %s\n", + get_key_type(secure_key, secure_key_size)); printf(" Clear key size: %lu bits\n", clear_key_size); printf(" XTS type key: %s\n", - secure_key_size > SECURE_KEY_SIZE ? "Yes" : "No"); + is_xts_key(secure_key, secure_key_size) ? "Yes" : "No"); printf(" Enciphered with: %s CCA master key (MKVP: %016llx)\n", is_old_mk ? "OLD" : "CURRENT", mkvp); printf(" Verification pattern: %.*s\n", VERIFICATION_PATTERN_LEN / 2, @@ -1426,7 +1498,10 @@ static int command_validate_file(void) printf(" %.*s\n", VERIFICATION_PATTERN_LEN / 2, &vp[VERIFICATION_PATTERN_LEN / 2]); - rc = cross_check_apqns(NULL, mkvp, true, g.verbose); + rc = cross_check_apqns(NULL, mkvp, + get_min_card_level_for_keytype( + get_key_type(secure_key, secure_key_size)), + true, g.verbose); if (rc == -EINVAL) return EXIT_FAILURE; if (rc != 0 && rc != -ENOTSUP) { @@ -1496,7 +1571,7 @@ static int command_import(void) rc = keystore_import_key(g.keystore, g.name, g.description, g.volumes, g.apqns, g.noapqncheck, g.sector_size, - g.pos_arg, g.volume_type); + g.pos_arg, g.volume_type, &g.cca); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1530,7 +1605,7 @@ static int command_list(void) int rc; rc = keystore_list_keys(g.keystore, g.name, g.volumes, g.apqns, - g.volume_type); + g.volume_type, g.key_type); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1665,6 +1740,206 @@ static int command_cryptsetup(void) return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } +/* + * Command handler for 'convert'. + * + * Converts secure keys from one key type to another + */ +static int command_convert_file(void) +{ + u8 output_key[2 * MAX_SECURE_KEY_SIZE]; + unsigned int output_key_size; + size_t secure_key_size; + int rc, is_old_mk; + int selected = 1; + u8 *secure_key; + int min_level; + u64 mkvp; + + if (g.name != NULL) { + warnx("Option '--name|-N' is not valid for " + "re-enciphering a key outside of the repository"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + if (g.noapqncheck) { + warnx("Option '--no-apqn-check' is not valid for " + "converting a key outside of the repository"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + + min_level = get_min_card_level_for_keytype(g.key_type); + if (min_level < 0) { + warnx("Invalid key-type specified: %s", g.key_type); + return EXIT_FAILURE; + } + + rc = cross_check_apqns(NULL, 0, min_level, true, g.verbose); + if (rc == -EINVAL) + return EXIT_FAILURE; + if (rc != 0 && rc != -ENOTSUP) { + warnx("Your master key setup is improper"); + return EXIT_FAILURE; + } + + /* Read the secure key to be re-enciphered */ + secure_key = read_secure_key(g.pos_arg, &secure_key_size, g.verbose); + if (secure_key == NULL) + return EXIT_FAILURE; + + rc = validate_secure_key(g.pkey_fd, secure_key, secure_key_size, NULL, + &is_old_mk, NULL, g.verbose); + if (rc != 0) { + warnx("The secure key in file '%s' is not valid", g.pos_arg); + rc = EXIT_FAILURE; + goto out; + } + + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + &mkvp, g.verbose); + if (rc != 0) { + warnx("Failed to get the master key verification pattern: %s", + strerror(-rc)); + rc = EXIT_FAILURE; + goto out; + } + + if (strcasecmp(get_key_type(secure_key, secure_key_size), + g.key_type) == 0) { + warnx("The secure key in file '%s' is already of type %s", + g.pos_arg, get_key_type(secure_key, secure_key_size)); + rc = EXIT_FAILURE; + goto out; + } + + if (is_cca_aes_data_key(secure_key, secure_key_size)) { + if (strcasecmp(g.key_type, KEY_TYPE_CCA_AESCIPHER) != 0) { + warnx("The secure key in file '%s' can not be " + "converted into type %s", g.pos_arg, g.key_type); + rc = EXIT_FAILURE; + goto out; + } + } else if (is_cca_aes_cipher_key(secure_key, secure_key_size)) { + warnx("The secure key in file '%s' is already of type %s", + g.pos_arg, KEY_TYPE_CCA_AESCIPHER); + rc = EXIT_FAILURE; + goto out; + } else { + warnx("The secure key in file '%s' has an unsupported key type", + g.pos_arg); + rc = EXIT_FAILURE; + goto out; + } + + rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, + FLAG_SEL_CCA_MATCH_CUR_MKVP, + g.verbose); + if (rc == -ENOTSUP) { + rc = 0; + selected = 0; + } + if (rc != 0) { + warnx("No APQN found that is suitable for " + "converting the secure AES key in file '%s'", g.pos_arg); + rc = EXIT_FAILURE; + goto out; + } + + if (!g.force) { + util_print_indented("ATTENTION: Converting a secure key is " + "irreversible, and might have an effect " + "on the volumes encrypted with it!", 0); + printf("%s: Convert key in file '%s' [y/N]? ", + program_invocation_short_name, g.pos_arg); + if (!prompt_for_yes(g.verbose)) { + warnx("Operation aborted"); + rc = EXIT_FAILURE; + goto out; + } + } + + memset(output_key, 0, sizeof(output_key)); + output_key_size = sizeof(output_key); + rc = convert_aes_data_to_cipher_key(&g.cca, secure_key, secure_key_size, + output_key, &output_key_size, + g.verbose); + if (rc != 0) { + warnx("Converting the secure key from %s to %s has failed", + get_key_type(secure_key, secure_key_size), g.key_type); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); + rc = EXIT_FAILURE; + goto out; + } + + rc = restrict_key_export(&g.cca, output_key, output_key_size, + g.verbose); + if (rc != 0) { + warnx("Export restricting the converted secure key has failed"); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); + rc = EXIT_FAILURE; + goto out; + } + + pr_verbose("Secure key was converted successfully"); + + /* Write the converted secure key */ + rc = write_secure_key(g.outputfile ? g.outputfile : g.pos_arg, + output_key, output_key_size, g.verbose); + if (rc != 0) + rc = EXIT_FAILURE; +out: + free(secure_key); + return rc; +} + +/* + * Command handler for 'convert in repository'. + * + * Converts secure keys from one key type to another + */ +static int command_convert_repository(void) +{ + int rc; + + if (g.name == NULL) { + misc_print_required_parm("--name/-N"); + return EXIT_FAILURE; + } + + rc = keystore_convert_key(g.keystore, g.name, g.key_type, g.noapqncheck, + g.force, g.pkey_fd, &g.cca); + + return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +/* + * Command handler for 'convert'. + * + * Converts secure keys from one key type to another + */ +static int command_convert(void) +{ + if (g.key_type == NULL) { + misc_print_required_parm("--key-type/-K"); + return EXIT_FAILURE; + } + if (strcasecmp(g.key_type, KEY_TYPE_CCA_AESCIPHER) != 0) { + warnx("Secure keys can only be converted into key type %s", + KEY_TYPE_CCA_AESCIPHER); + return EXIT_FAILURE; + } + + if (g.pos_arg != NULL) + return command_convert_file(); + else + return command_convert_repository(); + + return EXIT_SUCCESS; +} + /** * Opens the keystore. The keystore directory is either the * default directory or as specified in an environment variable @@ -1851,6 +2126,9 @@ int main(int argc, char *argv[]) case 'r': g.run = 1; break; + case 'K': + g.key_type = optarg; + break; case 'F': g.force = 1; break; -- 2.21.3 From a9fe5d1477161d658bcade0f85309eefe9fcb0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 7 Nov 2019 13:29:28 +0100 Subject: [PATCH 36/56] zcrypt: CEX7S exploitation support (#1723837) Description: CEX7S exploitation support to lszcrypt, chzcrypt and zcryptstats. Upstream-ID: b631c74df57e9ea52f54b4d6be79b63bc89e5eee Upstream-ID: e9c030f2026b1b8e0399679600845c298aeb508d Upstream-ID: 784ac7d190080a3cf230995eedd3743bddfa4b5c Upstream-ID: 4fc0c3cfefb8fb23a83ef629ac3f4a967fc0e77f Upstream-ID: e15e8a1bfa15e2179f30c6fe2e937ddd1a5e53c1 --- zconf/zcrypt/chzcrypt.8 | 4 +-- zconf/zcrypt/chzcrypt.c | 4 +-- zconf/zcrypt/lszcrypt.8 | 66 ++++++++++++++++++++++++++++++++++++-- zconf/zcrypt/lszcrypt.c | 20 +++++++----- zconf/zcrypt/zcryptstats.8 | 5 +++ zconf/zcrypt/zcryptstats.c | 27 ++++++++++------ 6 files changed, 102 insertions(+), 24 deletions(-) diff --git a/zconf/zcrypt/chzcrypt.8 b/zconf/zcrypt/chzcrypt.8 index 2021645..2697c96 100644 --- a/zconf/zcrypt/chzcrypt.8 +++ b/zconf/zcrypt/chzcrypt.8 @@ -1,8 +1,8 @@ -.\" Copyright 2017 IBM Corp. +.\" Copyright 2019 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 CHZCRYPT 8 "OCT 2017" "s390-tools" +.TH CHZCRYPT 8 "AUG 2019" "s390-tools" .SH NAME chzcrypt \- modify zcrypt configuration .SH SYNOPSIS diff --git a/zconf/zcrypt/chzcrypt.c b/zconf/zcrypt/chzcrypt.c index 6521056..2fc8441 100644 --- a/zconf/zcrypt/chzcrypt.c +++ b/zconf/zcrypt/chzcrypt.c @@ -1,7 +1,7 @@ /* * chzcrypt - Tool to modify zcrypt configuration * - * Copyright IBM Corp. 2008, 2017 + * Copyright IBM Corp. 2008, 2019 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. @@ -47,7 +47,7 @@ const struct util_prg prg = { { .owner = "IBM Corp.", .pub_first = 2008, - .pub_last = 2017, + .pub_last = 2019, }, UTIL_PRG_COPYRIGHT_END } diff --git a/zconf/zcrypt/lszcrypt.8 b/zconf/zcrypt/lszcrypt.8 index 826e109..f9ce4a5 100644 --- a/zconf/zcrypt/lszcrypt.8 +++ b/zconf/zcrypt/lszcrypt.8 @@ -1,6 +1,6 @@ .\" lszcrypt.8 .\" -.\" Copyright 2017 IBM Corp. +.\" Copyright 2019 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. .\" @@ -10,7 +10,7 @@ .\" nroff -man lszcrypt.8 .\" to process this source .\" -.TH LSZCRYPT 8 "OCT 2017" "s390-tools" +.TH LSZCRYPT 8 "AUG 2019" "s390-tools" .SH NAME lszcrypt \- display zcrypt device and configuration information .SH SYNOPSIS @@ -111,6 +111,68 @@ Displays help text and exits. .TP 8 .B -v, --version Displays version information and exits. +.SH VERBOSE LISTING DETAILS +Some of the columns showing up in verbose listing mode may need some +explanation: +.TP +.B TYPE and HWTYPE +The HWTYPE is a numeric value showing which type of hardware the zcrypt +device driver presumes that this crypto card is. The currently known values +are 7=CEX3C, 8=CEX3A, 10=CEX4, 11=CEX5, 12=CEX6 and 13=CEX7. +.br +The TYPE is a human readable value showing the hardware type and the basic +function type (A=Accelerator, C=CCA Coprocessor, P=EP11 Coprocessor). So +for example CEX6P means a CEX6 card in EP11 Coprocessor mode. +.TP +.B REQUESTS +This is the counter value of successful processed requests on card or queue +level. Successful here means the request was processed without any failure +in the whole processing chain. +.TP +.B PENDING +The underlying firmware and hardware layer usually provide some queuing +space for requests. When this queue is already filled up, the zcrypt device +driver maintains a software queue of pending requests. The sum of these +both values is displayed here and shows the amount of requests waiting for +processing on card or queue level. +.TP +.B FUNCTIONS +This column shows firmware and hardware function details: +.br +S - APSC available: card/queue can handle requests with the special bit +enabled. +.br +M - Accelerator card/queue with support for RSA ME with up to 4k key size. +.br +C - Accelerator card/queue with support for RSA CRT with up to 4k key size. +.br +D - Card/queue is providing CCA functions (this is the CCA Coprocessor mode). +.br +A - Card/queue is providing Accelerator functions (this is the Accelerator mode). +.br +X - Card/queue is providing EP11 functions (this is the EP11 Coprocessor mode). +.br +N - APXA available (ability to address more than 16 crypto cards and domains). +.br +F - Full function support (opposed to restricted function support, see below). +.br +R - Restricted function support. The F and R flag both reflect if a +hypervisor is somehow restricting this crypto resource in a virtual +environment. Dependent on the hypervisor configuration the crypto requests +may be filtered by the hypervisor to allow only a subset of functions +within the virtual runtime environment. For example a shared CCA +Coprocessor may be restricted by the hypervisor to allow only clear key +operations within the guests. +.TP +.B DRIVER +.br +Shows which card or queue device driver currently handles this crypto +resource. Currently known drivers are cex4card/cex4queue (CEX4-CEX7 +hardware), cex2card/cex2cqueue (CEX2C and CEX3C hardware), +cex2acard/cex2aqueue (CEX2A and CEX3A hardware) and vfio_ap (queue reserved +for use by kvm hypervisor for kvm guests and not accessible to host +applications). It is also valid to have no driver handling a queue which is +shown as a -no-driver- entry. .SH EXAMPLES .TP .B lszcrypt diff --git a/zconf/zcrypt/lszcrypt.c b/zconf/zcrypt/lszcrypt.c index 580407f..1b2befe 100644 --- a/zconf/zcrypt/lszcrypt.c +++ b/zconf/zcrypt/lszcrypt.c @@ -1,7 +1,7 @@ /** * lszcrypt - Display zcrypt devices and configuration settings * - * Copyright IBM Corp. 2008, 2018 + * Copyright IBM Corp. 2008, 2019 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. @@ -12,6 +12,7 @@ #include "lib/util_base.h" #include "lib/util_file.h" +#include "lib/util_libc.h" #include "lib/util_opt.h" #include "lib/util_panic.h" #include "lib/util_path.h" @@ -85,7 +86,7 @@ const struct util_prg prg = { { .owner = "IBM Corp.", .pub_first = 2008, - .pub_last = 2018, + .pub_last = 2019, }, UTIL_PRG_COPYRIGHT_END } @@ -301,9 +302,10 @@ static void show_capability(const char *id_str) printf("%s\n", CAP_CCA); printf("%s", CAP_RNG); break; - case 10: - case 11: - case 12: + case 10: /* CEX4S */ + case 11: /* CEX5S */ + case 12: /* CEX6S */ + case 13: /* CEX7S */ if (func_val & MASK_ACCEL) { if (func_val & MASK_RSA4K) printf("%s", CAP_RSA4K); @@ -620,7 +622,7 @@ static void show_devices_argv(char *argv[]) { struct util_rec *rec = util_rec_new_wide("-"); struct dirent **dev_vec, **subdev_vec; - char *ap, *grp_dev, *path, card[16], sub_dev[16]; + char *ap, *grp_dev, *path, *card, *sub_dev; int id, dom, i, n, dev_cnt, sub_cnt; /* check if ap driver is available */ @@ -639,14 +641,16 @@ static void show_devices_argv(char *argv[]) if (sscanf(argv[i], "%x.%x", &id, &dom) >= 1) { /* at least the id field was valid */ if (id >= 0 && dom >= 0) { /* single subdevice */ - sprintf(sub_dev, "%02x.%04x", id, dom); + util_asprintf(&sub_dev, "%02x.%04x", id, dom); grp_dev = util_path_sysfs("devices/ap/card%02x", id); show_subdevice(rec, grp_dev, sub_dev); free(grp_dev); + free(sub_dev); } else { /* group device */ - sprintf(card, "card%02x", id); + util_asprintf(&card, "card%02x", id); show_device(rec, card); + free(card); } return; } diff --git a/zconf/zcrypt/zcryptstats.8 b/zconf/zcrypt/zcryptstats.8 index 8c31c69..7d000d2 100644 --- a/zconf/zcrypt/zcryptstats.8 +++ b/zconf/zcrypt/zcryptstats.8 @@ -72,6 +72,11 @@ the parallel execution of cryptographic operations. Cryptographic performance measurement data might not be available when Linux is running as guest under z/VM or under KVM. \fBzcryptstats\fP then displays an error message and exits. +.PP +.B Note: +\fBzcryptstats\fP utilizes the device node \fB/dev/chsc\fP. When this device +node is not available, you might have to load kernel module \fBchsc_sch\fP using +\fBmodprobe chsc_sch\fP to make it available. . . . diff --git a/zconf/zcrypt/zcryptstats.c b/zconf/zcrypt/zcryptstats.c index 136d5ba..3bb2078 100644 --- a/zconf/zcrypt/zcryptstats.c +++ b/zconf/zcrypt/zcryptstats.c @@ -147,8 +147,9 @@ struct chsc_cmb_area { #define CRYPTO_TYPE_CEX4S 10 #define CRYPTO_TYPE_CEX5S 11 #define CRYPTO_TYPE_CEX6S 12 +#define CRYPTO_TYPE_CEX7S 13 -#define CRYPTO_TYPE_TOLERATION CRYPTO_TYPE_CEX6S +#define CRYPTO_TYPE_TOLERATION CRYPTO_TYPE_CEX7S struct crypto_counter { const char *name; @@ -235,8 +236,8 @@ const struct crypto_mode mode_pcica[1] = { .counters = counter_pcica }, }; -#define NUM_CEX456_MODES 11 -const struct crypto_mode mode_cex456[NUM_CEX456_MODES] = { +#define NUM_CEX4567_MODES 11 +const struct crypto_mode mode_cex4567[NUM_CEX4567_MODES] = { { 0 }, { 0 }, { 0 }, @@ -256,7 +257,7 @@ const struct crypto_mode mode_cex456[NUM_CEX456_MODES] = { .counters = counter_ep11 }, }; -#define NUM_CRYPTO_TYPES 13 +#define NUM_CRYPTO_TYPES 14 const struct crypto_type crypto_types[NUM_CRYPTO_TYPES] = { { 0 }, { 0 }, @@ -275,12 +276,14 @@ const struct crypto_type crypto_types[NUM_CRYPTO_TYPES] = { .modes = mode_accel }, { .name = "CEX3C", .num_modes = NUM_COPROC_MODES, .modes = mode_coproc }, - { .name = "CEX4", .num_modes = NUM_CEX456_MODES, - .modes = mode_cex456 }, - { .name = "CEX5", .num_modes = NUM_CEX456_MODES, - .modes = mode_cex456 }, - { .name = "CEX6", .num_modes = NUM_CEX456_MODES, - .modes = mode_cex456 }, + { .name = "CEX4", .num_modes = NUM_CEX4567_MODES, + .modes = mode_cex4567 }, + { .name = "CEX5", .num_modes = NUM_CEX4567_MODES, + .modes = mode_cex4567 }, + { .name = "CEX6", .num_modes = NUM_CEX4567_MODES, + .modes = mode_cex4567 }, + { .name = "CEX7", .num_modes = NUM_CEX4567_MODES, + .modes = mode_cex4567 }, }; @@ -2393,7 +2396,11 @@ int main(int argc, char *argv[]) g.chsc_fd = open(CHSC_DEVICE, O_RDWR); if (g.chsc_fd < 0) { + rc = errno; warnx("File '%s:' %s", CHSC_DEVICE, strerror(errno)); + if (rc == ENOENT) + warnx("You might have to load kernel module 'chsc_sch' " + "using 'modprobe chsc_sch'"); return EXIT_FAILURE; } pr_verbose("Device '%s' has been opened successfully", CHSC_DEVICE); -- 2.21.3 From afcf268f8af2a6e925c44c057cb04c2dc38c9b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 7 Nov 2019 13:48:23 +0100 Subject: [PATCH 37/56] lstape, lsluns: handle non-zfcp; lin_tape multiple paths (#1766569) Symptom: lstape shows unexpected additional Device suffix numbers in excess output columns for each additional path of the same tape/changer driven by the IBM lin_tape device driver (independent of actual path failover enablement in lin_tape). It also shows a wrong number of found devices in the header (without --scsi-only). lstape prints error about "Unexpected extra argument:" and the usage for "sg_inq" along with wrong tabular output. lsluns prints ENOENT error text for "cat" on SCSI device sysfs attributes hba_id, wwpn, and fcp_lun. lstape with --verbose option prints ENOENT error text for "cat" on SCSI device sysfs attributes hba_id and wwpn. lstape man page: Description of --type and filter for channel tapes is incomplete. SCSI output description is incomplete. lstape shows "N/A" instead of the HBA device bus-ID with virtio-scsi-ccw. Problem: s390-tools-1.8.0 before the first upstream commit b627b8d8e1ab ("Initial s390-tools-2.0.0 import") introduced SCSI tape/changer output for lstape. It used the SCSI device serial number as lookup key to find a match in IBM lin_tape device driver proc-fs output for a given SCSI device name. Multiple paths to the same tape/changer have the same serial number. Multiple matches cause excess arguments to printf. Explaining the resulting output, the bash man page says: "The format is reused as necessary to consume all of the arguments." This also causes a wrong number of found devices. The default bash settings have nullglob disabled so if $SCSI_DEV/scsi_generic* aka /sys/bus/scsi/devices/*:*:*:*/scsi_generic* does not match anything, it leaves the glob pattern unmodified and SG_DEV=$(basename $SG_DEV/*) results in the literal "*". If $SG_INQ exists, it invoked sg_inq with more than the one allowed positional argument for a SCSI generic device node "sg_inq /dev/*". Causing error messages and the usage of sg_inq to land in $TAPE_SERIAL. lsluns iterates SCSI generic devices and unconditionally reads zfcp-specific SCSI device sysfs attributes hba_id, wwpn, and fcp_lun. lstape --verbose unconditionally reads zfcp-specific SCSI device sysfs attributes hba_id and wwpn. filter missing from synopsis. example at wrong place with filter. filter option description is a duplicate of filter option description. SCSI output description misses fields. Lstape only used the zfcp-specific sysfs attribute hba_id. Solution: Prefer sysfs to find lin_tape device name for SCSI device. Fallback: The lin_tape proc-fs output format has changed over the years. The HBA device driver string can contain whitespace (e.g. "Virtio SCSI HBA") and breaks the field numbers with tokenized parsing. Grep for the SCSI device name as word (to skip names with same substring, such as 0:0:1:1 also matching 0:0:1:10) and cut the first field 'Number' (lin_tape device name suffix). If there is no SCSI column at all [lin_tape before v2.2.0] (and no SCSI LLDD or other column with a name accidentally matching an existing SCSI device name), we get no match and better bail out with the initialized "N/A" for the lstape column "Device". To not have to rely on the nullglob setting, explicitly check for the existence of $SCSI_DEV/scsi_generic before evaluating SG_DEV=$(basename $SG_DEV/*). Also handle availability of sg_inq but absence of scsi_generic individually to provide the user with a hint if only sg is missing. Simply skip non-zfcp SCSI devices, such as iSCSI or virtio-scsi-ccw, to not erroneously access absent attributes. Assume "N/A" for HBA and WWPN of non-zfcp SCSI devices, such as iSCSI or virtio-scsi-ccw, to not erroneously access absent zfcp-specific sysfs attributes. Add filter to synopsis. Move example to option. Replace filter option description. Move existing SCSI output description to a new subsection and add description of missing fields. Also search sysfs for an ancestor with subsystem ccw. Reproduction: Attach more than one path to the same SCSI tape or changer and load the IBM lin_tape device driver. Unload sg kernel module. Attach non-zfcp SCSI devices such as iSCSI or virtio-scsi-ccw. Attach non-zfcp SCSI devices such as iSCSI or virtio-scsi-ccw. man lstape Attach SCSI tape or changer with virtio-scsi-ccw to a KVM guest and run "lstape --verbose". Upstream-ID: ef4dc7a45b1b80c58076a0a317245569d84b2754 Upstream-ID: cdc787db1ba17dd2c0ed841fd7443bc32be63b65 Upstream-ID: 80e0c41b896e1eeffc594416b4c2787ed29363b6 Upstream-ID: eba744a25edcc7202068c1d46fd99d4c98c1acf2 Upstream-ID: 4cf8f5f46c0f80b49a2a70854372289c3fda932b Upstream-ID: b9b3d2d23069e021693a251a3aadd4eefc30e6ea Upstream-ID: 34260f17360f1034c562c941b0f879c2204e40b7 Upstream-ID: d5291eed1c46e1c97831e771b2975575ba268992 --- zconf/lsluns | 14 ++++++--- zconf/lsluns.8 | 5 ++- zconf/lstape | 69 ++++++++++++++++++++++++++++++++++------- zconf/lstape.8 | 84 +++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 135 insertions(+), 37 deletions(-) diff --git a/zconf/lsluns b/zconf/lsluns index f88aa0f..bfe48f1 100755 --- a/zconf/lsluns +++ b/zconf/lsluns @@ -2,7 +2,7 @@ # # lsluns - list LUNs discovered in the FC SAN, or show encryption state of attached LUNs # -# Copyright IBM Corp. 2008, 2017 +# Copyright IBM Corp. 2008, 2018 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. @@ -152,6 +152,11 @@ sub get_lun_hash my %lun_hash; foreach my $device () { + # skip non-zfcp SCSI devices and avoid file access error messages + next unless -r "$device/device/fcp_lun"; + next unless -r "$device/device/wwpn"; + next unless -r "$device/device/hba_id"; + my $l = `cat $device/device/fcp_lun`; my $p = `cat $device/device/wwpn`; my $a = `cat $device/device/hba_id`; @@ -170,9 +175,8 @@ sub get_lun_hash sub lsluns_usage { print <] ... [-p ] ... [-h] [-v] @@ -220,7 +224,7 @@ EOD sub lsluns_version { print "$PROGRAM_NAME: version %S390_TOOLS_VERSION%\n"; - print "Copyright IBM Corp. 2008, 2017\n"; + print "Copyright IBM Corp. 2008, 2018\n"; } sub lsluns_invalid_usage { diff --git a/zconf/lsluns.8 b/zconf/lsluns.8 index cca649a..a4a1849 100644 --- a/zconf/lsluns.8 +++ b/zconf/lsluns.8 @@ -28,9 +28,8 @@ zfcp-attached LUNs .SH DESCRIPTION .PP -This tool is designed for environments where all SCSI devices are attached -through the zfcp device driver. Expect error messages in mixed environments -such as with iSCSI. +This tool is designed for environments with SCSI devices attached +through the zfcp device driver. .B lsluns lists all logical unit numbers (LUNs) discovered in the diff --git a/zconf/lstape b/zconf/lstape index 3090451..e2410e1 100755 --- a/zconf/lstape +++ b/zconf/lstape @@ -2,7 +2,7 @@ # # lstape - Tool to show information about tape devices # -# Copyright IBM Corp. 2003, 2017 +# Copyright IBM Corp. 2003, 2018 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. @@ -48,6 +48,9 @@ function PrintUsage() { : -v|--version : Display the version of the tools package and : the lstape command. + : + :$(basename $0) without the --ccw-only option causes extra SAN traffic + :for each SCSI tape or changer device by invoking the sg_inq command. EOD } @@ -55,7 +58,7 @@ function PrintVersion() { cat <<-EOD $CMD: version %S390_TOOLS_VERSION% - Copyright IBM Corp. 2003, 2017 + Copyright IBM Corp. 2003, 2018 EOD } @@ -220,6 +223,24 @@ function SysfsCreateListCCW() { ' | sort } +# handle SCSI device not necessarily zfcp-attached, e.g. virtio-scsi-ccw +function SCSISearchCCWBusid() +{ + local SCSI_DEV=$1 + local SDEVCAN=$(readlink -e $SCSI_DEV) + while [ -n "$SDEVCAN" ]; do + # ascend to parent: strip last path part + SDEVCAN=${SDEVCAN%/*} + [ -h $SDEVCAN/subsystem ] || continue + local SUBSYSTEM=$(readlink -e $SDEVCAN/subsystem) + if [ "${SUBSYSTEM##*/}" = "ccw" ]; then + echo ${SDEVCAN##*/} + return + fi + done + echo "N/A" +} + function SysfsCreateListSCSI() { for SCSI_DEV in $1/bus/scsi/devices/*:*:*:*; do @@ -249,11 +270,16 @@ function SysfsCreateListSCSI() if [ -h $SG_DEV ]; then # deprecated sysfs layout SG_DEV=$(echo $SG_DEV | awk -F: '{print $NF}') - else + elif [ -d $SCSI_DEV/scsi_generic ]; then SG_DEV=$(basename $SG_DEV/*) + else + SG_DEV="" fi - if [ "$SG_INQ" != "" ]; then + if [ -z "$SG_DEV" ]; then + SG_DEV="N/A" + TAPE_SERIAL="NO/SG" + elif [ "$SG_INQ" != "" ]; then TAPE_SERIAL=$( sg_inq /dev/$SG_DEV | awk '/serial/{print $NF}' @@ -291,17 +317,31 @@ function SysfsCreateListSCSI() if [ "$CHG_IDX" != "" ]; then TAPE_DEV=$CHG_IDX fi + elif [ "$(echo "$SCSI_LIST"|grep lin_tape)" != "" ]; then + # bash glob sorts so IBMtape0 comes before IBMtape0n + local IBM_PATH=$( + ls -1d $SCSI_DEV/lin_tape/$DEV_NAME[0-9]* | + head -n 1) + if [ -d "$IBM_PATH" ]; then + IBM_IDX=${IBM_PATH##*/} + else + # deprecated sysfs layout + IBM_IDX=$( + echo "$SCSI_LIST" | + awk -F: '/lin_tape\:'"$DEV_NAME"'[0-9]+$/{print $NF}' + ) + fi + if [ "$IBM_IDX" != "" ]; then + TAPE_DEV=$IBM_IDX + fi elif [ -r /proc/scsi/$DEV_NAME ]; then - if [ "$TAPE_SERIAL" != "NO/INQ" ]; then IBM_IDX=$( - awk '$3 == "'$TAPE_SERIAL'"{ - print $1 - }' /proc/scsi/$DEV_NAME + grep -wF "$SCSI_ID" /proc/scsi/$DEV_NAME | + cut -d ' ' -f 1 ) if [ "$IBM_IDX" != "" ]; then TAPE_DEV=$DEV_NAME$IBM_IDX fi - fi fi printf "$SCSIFORMAT" \ @@ -313,9 +353,16 @@ function SysfsCreateListSCSI() $STATE if $VERBOSE; then + if [ -r $SCSI_DEV/hba_id ]; then + HBA_ID=$(cat $SCSI_DEV/hba_id) + else + HBA_ID=$(SCSISearchCCWBusid $SCSI_DEV) + fi + WWPN="N/A" + [ -r $SCSI_DEV/wwpn ] && WWPN=$(cat $SCSI_DEV/wwpn) printf "$SCSIVFORMAT" \ - $(cat $SCSI_DEV/hba_id) \ - $(cat $SCSI_DEV/wwpn) \ + "$HBA_ID" \ + "$WWPN" \ $TAPE_SERIAL fi done diff --git a/zconf/lstape.8 b/zconf/lstape.8 index 544cb08..019b6e5 100644 --- a/zconf/lstape.8 +++ b/zconf/lstape.8 @@ -1,8 +1,8 @@ -.\" Copyright 2017 IBM Corp. +.\" Copyright 2017, 2018 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 LSTAPE 8 "Jul 2007" "s390-tools" +.TH LSTAPE 8 "Jun 2018" "s390-tools" .SH NAME lstape \- list tape devices. @@ -20,6 +20,8 @@ lstape \- list tape devices. .br .RB [ -t .IR [, ] "" ...] +.br +.RI [ ...] .SH DESCRIPTION The lstape command lists all available tape devices on the current host. For @@ -27,19 +29,8 @@ channel attached tape devices this output is the same as the contents of /proc/tapedevices (which is obsolete) but also includes offline devices. By default all tape devices are displayed. -Since SCSI tape devices are accessed differently to channel attached tape -devices they are only visible if they are known to the SCSI layer. There -are at least two possible drivers that can claim a SCSI tape device and the -lstape command tries to find out which one this is. For the generic tape -and changer driver the device names start with "st" or "sch", while for the -IBM tape driver this would be "IBMtape" or "IBMchanger". If "N/A" is shown, -the correct driver could not be obtained. -This happens for example if there is no sg_inq command installed which is -required to read the drive's serial number which in turn is used to find out -the device number of the IBM tape driver. - -The serial number of a SCSI tape can be displayed with the --verbose option. If -there is no sg_inq command available "NO/INQ" is shown as the tape's serial. +The lstape command without the --ccw-only option causes extra SAN traffic +for each SCSI tape or changer device by invoking the sg_inq command. .SH OPTIONS .TP 8 @@ -75,12 +66,64 @@ on the output of SCSI devices. .TP .BR -t | --type " \fI\fR" -Limit output to given device types (currently only applies to channel attached +Limit output to given device types, for example 3490 +(currently only applies to channel-attached tape devices). .TP -\fB\fR = -Device type of devices that should be displayed (e.g. 3490). +.I +Limits the output to information about the specified tape device or +devices only. For CCW-attached devices only. + +.SH OUTPUT FIELDS FOR SCSI TAPE/CHANGER DEVICES +.TP +.B Generic +SCSI generic device file for the tape drive, for example /dev/sg0. +"N/A" if the SCSI generic (sg) kernel functionality is not available. +.TP +.B Device +Main character device node file for accessing the tape drive or medium changer. +SCSI tape devices are only visible if they are known to the SCSI layer. There +are at least two possible drivers that can claim a SCSI tape device. The +lstape command tries to determine the device driver. For the generic tape +and changer driver the device names start with "st" or "sch", while for the +IBM tape driver this would be "IBMtape" or "IBMchanger". If "N/A" is shown, +the device driver could not be determined. +.TP +.B Target +Linux SCSI device name in H:C:T:L format. +.TP +.B Vendor +The vendor field from the SCSI device. +.TP +.B Model +The model field from the SCSI device. +.TP +.B Type +"tapedrv" for a tape drive or "changer" for a medium changer. +.TP +.B State +The state of the SCSI device object in the kernel. +Any state other than "running" can indicate problems. + +.PP + +For SCSI devices, the --verbose option additionally displays: +.TP +.B HBA +The device bus-ID of the FCP device +or of the virtio-scsi-ccw virtual HBA +through which the tape drive is attached. +"N/A" if the device does not have a sysfs ancestor with subsystem ccw. +.TP +.B WWPN +The WWPN (worldwide port name) of the tape drive in the SAN. +"N/A" if device is not attached through zfcp. +.TP +.B Serial +The serial number. +"NO/INQ" if there is no sg_inq command available. +"NO/SG" if no SCSI generic (sg) kernel support is available. .SH EXAMPLES \fBlstape\fR @@ -92,3 +135,8 @@ List all tape devices that are available .RS Show all 3490 CCW devices that are online. .RE + +\fBlstape --scsi-only --verbose\fR +.RS +Show all SCSI tape or changer devices with maximum information. +.RE -- 2.21.3 From 164d283c73cc17b8a5927a449ed59671545f7066 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 1 Oct 2019 15:33:59 +0200 Subject: [PATCH 38/56] zipl: fix the scanned tokens array size calculation (#1751587) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The zipl config file (zipl.conf) and the BootLoaderSpec (BLS) fragments in /boot/loader/entries define a set of tokens that are parsed by zipl. These are stored in an array of tokens whose size is calculated to make sure that there is enough memory allocated for all the scanned tokens. But the size calculation logic was wrong, since it was checking if the current size was enough to store a single token per BLS fragment, while up to 4 tokens can be defined in a BLS file: a section heading and the image, ramdisk and parameter keywords. This led to zipl being killed by a SIGABRT signal when trying to parse more tokens than the ones that could fit in the scanned tokens array: Using config file '/etc/zipl.conf' Using BLS config file '/boot/loader/entries/vmlinuz-9.conf' Using BLS config file '/boot/loader/entries/vmlinuz-8.conf' Using BLS config file '/boot/loader/entries/vmlinuz-7.conf' Using BLS config file '/boot/loader/entries/vmlinuz-6.conf' Using BLS config file '/boot/loader/entries/vmlinuz-5.conf' Using BLS config file '/boot/loader/entries/vmlinuz-4.conf' Using BLS config file '/boot/loader/entries/vmlinuz-3.conf' Using BLS config file '/boot/loader/entries/vmlinuz-2.conf' Using BLS config file '/boot/loader/entries/vmlinuz-1.conf' Using BLS config file '/boot/loader/entries/vmlinuz-0.conf' double free or corruption (out) Aborted (core dumped) Fixes: https://github.com/ibm-s390-tools/s390-tools/issues/68 Closes: https://github.com/ibm-s390-tools/s390-tools/pull/73 Signed-off-by: Javier Martinez Canillas Signed-off-by: Stefan Haberland Signed-off-by: Jan Höppner (cherry picked from commit 8bfe18e674e17a20682dbd1fa0e4813c789e22e5) --- zipl/src/scan.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/zipl/src/scan.c b/zipl/src/scan.c index e57361e..ee04251 100644 --- a/zipl/src/scan.c +++ b/zipl/src/scan.c @@ -751,8 +751,14 @@ scan_bls(const char* blsdir, struct scan_token** token, int scan_size) remaining = scan_size - count; - if (remaining < n) { - size = scan_size - remaining + n; + /* The array of scanned tokens is allocated when the zipl config file is + * parsed. Its size is a multiple of INITIAL_ARRAY_LENGTH so it may have + * enough space to scan all the tokens that are defined in the BLS files. + * Calculate if is enough assuming that a BLS fragment can contain up to + * 4 tokens: a section heading and 3 keywords (image, ramdisk, parameter). + */ + if (remaining < n * 4) { + size = scan_size - remaining + (n * 4); buffer = (struct scan_token *)misc_malloc(size * sizeof(struct scan_token)); if (!buffer) goto err; -- 2.21.3 From ba8021cb17f74ea5130d7c5acdc267b6fb0a433c Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Wed, 12 Sep 2018 09:40:22 -0500 Subject: [PATCH 39/56] zipl: use FIEMAP mapping ioctl if it exists (moved from Patch101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zipl currently uses the FIBMAP ioctl to map blocks for the bootloader; on XFS, if FIBMAP is requested on a reflinked file, it will fail - and FIBMAP returns 0 in this case, which is indistinguishable from a hole. This causes boot to fail because the file is not mapped. We can use the FIEMAP ioctl instead, which is able to map reflinked files. While FIEMAP is able to map entire extents at once, here we simply use it to obtain the mapping block-by-block so that it fits in with the current FIBMAP calls. Fixes: https://github.com/ibm-s390-tools/s390-tools/issues/34 Closes: https://github.com/ibm-s390-tools/s390-tools/pull/36 Signed-off-by: Eric Sandeen Reviewed-by: Peter Oberparleiter Reviewed-by: Stefan Haberland Tested-by: Stefan Haberland Signed-off-by: Jan Höppner (cherry picked from commit 07e30951f8f31cdb5ea3cbd020254239be522e6a) --- zipl/src/disk.c | 57 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/zipl/src/disk.c b/zipl/src/disk.c index 0d8e779..43092bf 100644 --- a/zipl/src/disk.c +++ b/zipl/src/disk.c @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include "lib/util_proc.h" @@ -550,8 +552,12 @@ disk_get_blocknum(int fd, int fd_is_basedisk, blocknum_t logical, { struct statfs buf; blocknum_t phy_per_fs; - int mapped; + blocknum_t mapped; + int block; int subblock; + int fiemap_size; + int map_offset; + struct fiemap *fiemap; /* No file system: partition or raw disk */ if (info->fs_block_size == -1) { @@ -576,12 +582,55 @@ disk_get_blocknum(int fd, int fd_is_basedisk, blocknum_t logical, } /* Get mapping in file system blocks */ phy_per_fs = info->fs_block_size / info->phy_block_size; - mapped = logical / phy_per_fs; subblock = logical % phy_per_fs; - if (ioctl(fd, FIBMAP, &mapped)) { - error_reason("Could not get file mapping"); + + /* First try FIEMAP, more complicated to set up */ + fiemap_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent); + + fiemap = misc_malloc(fiemap_size); + if (!fiemap) return -1; + memset(fiemap, 0, fiemap_size); + + fiemap->fm_extent_count = 1; + fiemap->fm_flags = FIEMAP_FLAG_SYNC; + /* fm_start, fm_length in bytes; logical is in physical block units */ + fiemap->fm_start = logical * info->phy_block_size; + fiemap->fm_length = info->phy_block_size; + + if (ioctl(fd, FS_IOC_FIEMAP, (unsigned long)fiemap)) { + /* FIEMAP failed, fall back to FIBMAP */ + block = logical / phy_per_fs; + if (ioctl(fd, FIBMAP, &block)) { + error_reason("Could not get file mapping"); + free(fiemap); + return -1; + } + mapped = block; + } else { + if (fiemap->fm_mapped_extents) { + if (fiemap->fm_extents[0].fe_flags & + FIEMAP_EXTENT_ENCODED) { + error_reason("File mapping is encoded"); + free(fiemap); + return -1; + } + /* + * returned extent may start prior to our request + */ + map_offset = fiemap->fm_start - + fiemap->fm_extents[0].fe_logical; + mapped = fiemap->fm_extents[0].fe_physical + + map_offset; + /* set mapped to fs block units */ + mapped = mapped / info->fs_block_size; + } else { + mapped = 0; + } } + + free(fiemap); + if (mapped == 0) { /* This is a hole in the file */ *physical = 0; -- 2.21.3 From 658eef5bc3ea1ec9b917c35a36dcbe1219bd0b08 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 6 Nov 2018 13:29:32 +0100 Subject: [PATCH 40/56] zipl: use the BLS "title" field as the IPL section name (moved from Patch103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most bootloaders use the BootLoaderSpec "title" field to name the entries in their boot menu. The zipl bootloader used the "version" field instead, since it was wrongly assumed that the zipl boot menu didn't support names that contained spaces, which are usually present in a BLS "title" field. But this is not the case, names with space characters are supported by the IPL and is just a constraint of the section heading in the zipl.conf file. So to be consistent with all the other bootloaders, use the "title" field also on zipl when populating the boot menu entries from BLS files. Closes: https://github.com/ibm-s390-tools/s390-tools/pull/47 Signed-off-by: Javier Martinez Canillas Reviewed-by: Peter Oberparleiter Reviewed-by: Stefan Haberland sth@linux.ibm.com Signed-off-by: Jan Höppner (cherry picked from commit 49510442133e63bfd650a7b7e27e388d38baba81) --- scripts/zipl-switch-to-blscfg | 1 + zipl/src/scan.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/zipl-switch-to-blscfg b/scripts/zipl-switch-to-blscfg index 5272033..5141b1c 100755 --- a/scripts/zipl-switch-to-blscfg +++ b/scripts/zipl-switch-to-blscfg @@ -150,6 +150,7 @@ while IFS='= ' read key val; do if [ -f "${OUTPUT}" ]; then print_error "BLS file ${OUTPUT} already exists" fi + echo "title $section" >> ${OUTPUT} fi elif [[ $val ]]; then val="$(echo $val | sed -e 's/^[ \t"]*//;s/[ \t"]*$//')" diff --git a/zipl/src/scan.c b/zipl/src/scan.c index ee04251..b8fea13 100644 --- a/zipl/src/scan.c +++ b/zipl/src/scan.c @@ -702,7 +702,7 @@ scan_bls_field(struct misc_file_buffer *file, struct scan_token* scan, file->buffer[key_end] = '\0'; file->buffer[val_end] = '\0'; - if (strncmp("version", &file->buffer[key_start], key_end - key_start) == 0) { + if (strncmp("title", &file->buffer[key_start], key_end - key_start) == 0) { scan_append_section_heading(scan, index, &file->buffer[val_start]); } -- 2.21.3 From 2515287113812346ef8a3bb00a80632b698ab542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 7 Nov 2019 14:37:36 +0100 Subject: [PATCH 41/56] zipl: config file handling improvements for CoreOS (#1764706) Use TOOLS_SYSCONFDIR for the default zipl.conf path - c01bcfa73a6d795f33df639ba36304a704d13561 zipl: ship a minimal zipl.conf - 19259aeb656df5cbc7bda89599442dd51d80a7a8 zipl: allow check for other locations of zipl.conf - 275105fe3d64d214f982a5c0eb113d806853919b zipl: add value of target= as search path for BLS case - d71628326d80e623fc9f008fe4ea93edb5592b2e --- zipl/Makefile | 1 + zipl/doc/zipl.conf.minimal | 10 ++++ zipl/include/scan.h | 1 + zipl/include/zipl.h | 4 +- zipl/man/Makefile | 10 +++- zipl/man/{zipl.8 => zipl.8.in} | 2 +- zipl/man/{zipl.conf.5 => zipl.conf.5.in} | 17 +++++-- zipl/src/job.c | 23 +++++++++- zipl/src/scan.c | 58 ++++++++++++++++++++++++ 9 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 zipl/doc/zipl.conf.minimal rename zipl/man/{zipl.8 => zipl.8.in} (99%) rename zipl/man/{zipl.conf.5 => zipl.conf.5.in} (96%) diff --git a/zipl/Makefile b/zipl/Makefile index a02e997..931b113 100644 --- a/zipl/Makefile +++ b/zipl/Makefile @@ -8,6 +8,7 @@ all: install: all $(MAKE) -C src install $(MAKE) -C man install + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 doc/zipl.conf.minimal $(DESTDIR)$(TOOLS_LIBDIR)/zipl.conf clean: $(MAKE) -C src clean diff --git a/zipl/doc/zipl.conf.minimal b/zipl/doc/zipl.conf.minimal new file mode 100644 index 0000000..9d6fa5e --- /dev/null +++ b/zipl/doc/zipl.conf.minimal @@ -0,0 +1,10 @@ +# This is an example of a minimal zipl.conf file that can be used when the +# sections are defined in BootLoaderSpec fragments files. +# +# See the zipl and zipl.conf man page for more details. +[defaultboot] +defaultauto +prompt=1 +timeout=5 +secure=auto +target=/boot diff --git a/zipl/include/scan.h b/zipl/include/scan.h index 4dcee0b..f39e96e 100644 --- a/zipl/include/scan.h +++ b/zipl/include/scan.h @@ -126,6 +126,7 @@ char* scan_keyword_name(enum scan_keyword_id id); int scan_check_defaultboot(struct scan_token* scan); struct scan_token* scan_build_automenu(struct scan_token* scan); int scan_check(struct scan_token* scan); +int scan_check_bls(struct scan_token *scan); int scan_find_section(struct scan_token* scan, char* name, enum scan_id type, int offset); int scan_check_section_data(char* keyword[], int* line, char* name, diff --git a/zipl/include/zipl.h b/zipl/include/zipl.h index 6f2d115..0ef06e4 100644 --- a/zipl/include/zipl.h +++ b/zipl/include/zipl.h @@ -47,7 +47,9 @@ #define DEFAULTBOOT_SECTION "defaultboot" #define ZIPL_CONF_VAR "ZIPLCONF" -#define ZIPL_DEFAULT_CONF "/etc/zipl.conf" +#define ZIPL_RUNTIME_CONF "/run/zipl/zipl.conf" +#define ZIPL_DEFAULT_CONF TOOLS_SYSCONFDIR "/zipl.conf" +#define ZIPL_MINIMAL_CONF TOOLS_LIBDIR "/zipl.conf" #define ZIPL_DEFAULT_BLSDIR "/boot/loader/entries" #define ZIPL_STAGE3_PATH TOOLS_LIBDIR "/stage3.bin" #define ZIPL_SIPL_PATH "/sys/firmware/ipl/has_secure" diff --git a/zipl/man/Makefile b/zipl/man/Makefile index 6439205..1d8dbe3 100644 --- a/zipl/man/Makefile +++ b/zipl/man/Makefile @@ -6,7 +6,13 @@ all: install: $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man5 $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8 - $(INSTALL) -m 644 -c zipl.8 $(DESTDIR)$(MANDIR)/man8 - $(INSTALL) -m 644 -c zipl.conf.5 $(DESTDIR)$(MANDIR)/man5 + sed -e 's@%SYSCONFDIR%@$(SYSCONFDIR)@' \ + -e 's@%TOOLS_LIBDIR%@$(TOOLS_LIBDIR)@' zipl.8.in \ + > $(DESTDIR)$(MANDIR)/man8/zipl.8 + sed -e 's@%SYSCONFDIR%@$(SYSCONFDIR)@' \ + -e 's@%TOOLS_LIBDIR%@$(TOOLS_LIBDIR)@' zipl.conf.5.in \ + > $(DESTDIR)$(MANDIR)/man5/zipl.conf.5 + chmod 644 $(DESTDIR)$(MANDIR)/man8/zipl.8 \ + $(DESTDIR)$(MANDIR)/man5/zipl.conf.5 clean: diff --git a/zipl/man/zipl.8 b/zipl/man/zipl.8.in similarity index 99% rename from zipl/man/zipl.8 rename to zipl/man/zipl.8.in index 2f873f3..f12e27b 100644 --- a/zipl/man/zipl.8 +++ b/zipl/man/zipl.8.in @@ -120,7 +120,7 @@ Print version information, then exit. .TP .BR "\-c " " or " "\-\-config=" Use the specified . If none is supplied, the environment -variable ZIPLCONF is evaluated if set, otherwise /etc/zipl.conf is used. +variable ZIPLCONF is evaluated if set, otherwise %SYSCONFDIR%/zipl.conf is used. .TP .BR "\-b " " or " "\-\-blsdir=" diff --git a/zipl/man/zipl.conf.5 b/zipl/man/zipl.conf.5.in similarity index 96% rename from zipl/man/zipl.conf.5 rename to zipl/man/zipl.conf.5.in index 71d1252..ef40287 100644 --- a/zipl/man/zipl.conf.5 +++ b/zipl/man/zipl.conf.5.in @@ -17,8 +17,13 @@ boot loader tool .BR zipl (8)). .br -By default this configuration file is located at /etc/zipl.conf. A different -location may be specified either using the '\-\-config' option of +By default +.B zipl +checks for +.I zipl.conf +at /run/zipl/zipl.conf, %SYSCONFDIR%/zipl.conf, %TOOLS_LIBDIR%/zipl.conf in that +order - whichever is found first will be used. Users can specifically choose a +location using the '\-\-config' option of .B zipl or by setting the ZIPLCONF shell environment variable. .br @@ -138,7 +143,13 @@ initrd /initramfs-4.15.9 options root=/dev/dasda1 console=ttyS0 .PP -The location of the linux and initrd has to be specified relative to the boot partition. The BLS config files are only used to specify the IPL sections, a zipl.conf configuration files is still needed for global parameters. +The location of the linux and initrd has to be specified relative to the boot +partition. The BLS config files are only used to specify the IPL sections, a +zipl.conf configuration file is still needed for global parameters. For this +purpose, a minimal zipl.conf configuration file is shipped at +%TOOLS_LIBDIR%/zipl.conf which would help when used with BLS config files, by +not requiring users to create the traditional configuration file at +%SYSCONFDIR%/zipl.conf. .B Boot menu diff --git a/zipl/src/job.c b/zipl/src/job.c index 1178a7d..7d61c84 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -59,6 +59,14 @@ static struct option options[] = { /* Command line option abbreviations */ static const char option_string[] = "-c:b:t:i:r:p:P:d:D:M:s:S:m:hHnVvaT:fk:"; +/* Locations of zipl.conf configuration file */ +static const char *zipl_conf[] = { + ZIPL_RUNTIME_CONF, + ZIPL_DEFAULT_CONF, + ZIPL_MINIMAL_CONF, + NULL +}; + struct command_line { char* data[SCAN_KEYWORD_NUM]; char* config; @@ -1783,7 +1791,7 @@ get_job_from_config_file(struct command_line* cmdline, struct job_data* job) char* filename; char *blsdir; char* source; - int rc, scan_size; + int i, rc, scan_size; /* Read configuration file */ if (cmdline->config != NULL) { @@ -1797,7 +1805,12 @@ get_job_from_config_file(struct command_line* cmdline, struct job_data* job) ZIPL_CONF_VAR ")"; } else { /* Use default config file */ - filename = ZIPL_DEFAULT_CONF; + for (i = 0; zipl_conf[i]; i++) { + if (misc_check_readable_file(zipl_conf[i]) == 0) { + filename = zipl_conf[i]; + break; + } + } source = ""; } printf("Using config file '%s'%s\n", filename, source); @@ -1840,6 +1853,12 @@ get_job_from_config_file(struct command_line* cmdline, struct job_data* job) scan_free(scan); return rc; } + rc = scan_check_bls(scan); + if (rc) { + error_text("BLS parsing '%s'", blsdir); + scan_free(scan); + return rc; + } /* Get job from config file data */ if (cmdline->menu != NULL) rc = get_menu_job(scan, cmdline->menu, job); diff --git a/zipl/src/scan.c b/zipl/src/scan.c index b8fea13..f4228d3 100644 --- a/zipl/src/scan.c +++ b/zipl/src/scan.c @@ -1559,6 +1559,64 @@ scan_check(struct scan_token* scan) return 0; } +/* + * Check if kernel and initrd image paths provided by BLS files are readable. + * If not, add value of 'scan_keyword_target' into search path and silently + * update scan list. + */ +int +scan_check_bls(struct scan_token *scan) +{ + int i, rc; + char *target_value = NULL; + char *img_value = NULL; + char *buffer = NULL; + /* + * In the BLS case, each BLS section heading inherits a keyword + * assignment target= from zipl.conf, and they are all the same. + * + */ + for (i = 0 ; scan[i].id != scan_id_empty; i++) { + if (scan[i].id == scan_id_keyword_assignment && + scan[i].content.keyword.keyword == scan_keyword_target) { + target_value = scan[i].content.keyword.value; + break; + } + } + if (!target_value) + return -1; + for (i = 0 ; scan[i].id != scan_id_empty; i++) { + if (scan[i].id != scan_id_keyword_assignment) + continue; + if (scan[i].content.keyword.keyword == scan_keyword_image || + scan[i].content.keyword.keyword == scan_keyword_ramdisk) { + + rc = misc_check_readable_file( + scan[i].content.keyword.value); + if (rc) { + misc_asprintf(&img_value, "%s%s", + target_value, + scan[i].content.keyword.value); + rc = misc_check_readable_file(img_value); + if (rc) { + error_text( + "Image file '%s' is not accessible", + scan[i].content.keyword.value); + return rc; + } + buffer = (char *) + misc_malloc(strlen(img_value) + 1); + if (buffer == NULL) + return -1; + memcpy(buffer, img_value, strlen(img_value)); + buffer[strlen(img_value)] = 0; + free(scan[i].content.keyword.value); + scan[i].content.keyword.value = buffer; + } + } + } + return 0; +} static int scan_get_defaultboot_type(char* keyword[], int line[], int section_line, -- 2.21.3 From 1ca71226d392c59855432ee992e263be680644d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 27 Jan 2020 11:22:42 +0100 Subject: [PATCH 42/56] zkey: Fix display of clear key size for XTS keys (#1794375) Description: zkey: Fix display of clear key size for XTS keys Symptom: The 'zkey list' command shows bogus values for the keys 'Clear key size' for XTS keys of type CCA-AESDATA or CCA-AESCIPHER. Problem: XTS keys consist of 2 keys concatenated to each other. To calculate the clear key size, the clear key size of both keys must be added. The code does not address the second key correctly, and thus reads the clear key size of the second key from an invalid memory location. This results in bogus values reported as clear key size. This bug has been introduced with feature SEC1717 "Cipher key support" with commit 298fab68fee8 "zkey: Preparations for introducing a new key type". Solution: Correct the addressing of the second key. Reproduction: Generate an XTS key of type CCA-AESDATA or CCA-AESCIPHER and then run 'zkey list'. Upstream-ID: e7f446432b92b293e758099842843cfb1f18fa97 --- zkey/pkey.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zkey/pkey.c b/zkey/pkey.c index 462f9fe..640ff86 100644 --- a/zkey/pkey.c +++ b/zkey/pkey.c @@ -1591,8 +1591,8 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) if (is_cca_aes_data_key(key, key_size)) { *bitsize = datakey->bitsize; if (key_size == 2 * AESDATA_KEY_SIZE) { - datakey = (struct aesdatakeytoken *)key + - AESDATA_KEY_SIZE; + datakey = (struct aesdatakeytoken *)(key + + AESDATA_KEY_SIZE); *bitsize += datakey->bitsize; } } else if (is_cca_aes_cipher_key(key, key_size)) { @@ -1601,8 +1601,8 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) else *bitsize = 0; /* Unknown */ if (key_size > cipherkey->length) { - cipherkey = (struct aescipherkeytoken *)key + - cipherkey->length; + cipherkey = (struct aescipherkeytoken *)(key + + cipherkey->length); if (cipherkey->pfv == 0x00) /* V0 payload */ *bitsize += cipherkey->pl - 384; } -- 2.21.3 From 2beeedb4f5eca1cf2faf34d1a0db176ea887854f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 27 Jan 2020 11:39:24 +0100 Subject: [PATCH 43/56] zkey: Fix listing of keys on file systems reporting DT_UNKNOWN (#1792957) Description: zkey: Fix listing of keys on file systems reporting DT_UNKNOWN. Symptom: When the zkey key repository is located in a file system that does not have full support for report the file type, such as XFS, the 'zkey list' command does not show any keys, although keys exist in the repository. Problem: The zkey list function uses scandir() to look for files in the zkey key repository directory. It checks the dirent.d_type field to consider only regular files, but skips all others. File systems that do not have full support for returning the file type in d_type will return DT_UNKNOWN instead. zkey skips those directory entries and thus does not show any keys. Solution: Also consider directory entries with d_type = DT_UNKNOWN. Reproduction: Use zkey with a zkey repository directory located in a file system that does not have full support for returning the file type, such as XFS. Generate a key in the repository and then list the key s with 'zkey list'. Note: Newly created XFS file systems usually support returning the file type, but existing XFS file systems might not. To create an XFS file system that does not support returning the file type, use 'mkfs.xfs -f -m crc=0 -n ftype=0' to create the file system. Upstream-ID: 0de533aef9def920fed751c6025e4f19c4cba763 --- zkey/keystore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zkey/keystore.c b/zkey/keystore.c index af67721..e6be3a4 100644 --- a/zkey/keystore.c +++ b/zkey/keystore.c @@ -906,7 +906,7 @@ static int _keystore_info_file_filter(const struct dirent *dirent) { size_t len; - if (dirent->d_type != DT_REG) + if (dirent->d_type != DT_REG && dirent->d_type != DT_UNKNOWN) return 0; len = strlen(dirent->d_name); -- 2.21.3 From 931db1dba8822a14ad15c52ae95e0d8a219dd94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Wed, 27 May 2020 10:29:10 +0200 Subject: [PATCH 44/56] zipl/libc: Fix potential buffer overflow in printf (#1807973) Description: zipl/libc: Fix potential buffer overflow in printf Symptom: Crash of the zipl boot loader during boot. Problem: The zipl boot loaders have their own minimalistic libc implementation. In it printf and sprintf use vsprintf for string formatting. Per definition vsprintf assumes that the buffer it writes to is large enough to contain the formatted string and performs no size checks. This is problematic for the boot loaders because the buffer they use are often allocated on the stack. Thus even small changes to the string format can potentially cause buffer overflows on the stack. Solution: Implement vsnprintf and make use of it. Reproduction: Use printf to print a string with >81 characters (exact number depends on the stack layout/compiler used). Upstream-ID: 6fe9e6c55c69c14971dca55551009f5060418aae Upstream-ID: 8874b908254c47c8a6fd7a1aca2c7371c11035c4 Upstream-ID: f7430027b41d5ad6220e962a179c2a5213330a44 Upstream-ID: 36fed0e6c6590631c4ce1707c8fe3c3397bcce4d --- zipl/boot/libc.c | 360 ++++++++++++++++++++++++++++-------------- zipl/boot/libc.h | 4 +- zipl/boot/menu.c | 2 +- zipl/boot/menu.h | 1 - zipl/boot/tape2dump.c | 2 +- 5 files changed, 248 insertions(+), 121 deletions(-) diff --git a/zipl/boot/libc.c b/zipl/boot/libc.c index 5944c4e..bd88d15 100644 --- a/zipl/boot/libc.c +++ b/zipl/boot/libc.c @@ -125,81 +125,6 @@ int strncmp(const char *s1, const char *s2, unsigned long count) return 0; } -/* - * Convert number to string - * - * Parameters: - * - * - buf: Output buffer - * - base: Base used for formatting (e.g. 10 or 16) - * - val: Number to format - * - zero: If > 0, fill with leading zeros, otherwise use blanks - * - count: Minimum number of characters used for output string - */ -static int num_to_str(char *buf, int base, unsigned long val, int zero, - unsigned long count) -{ - static const char conv_vec[] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - unsigned long num = 0, val_work = val, in_number = 1; - int i; - - /* Count number of characters needed for number */ - do { - num++; - val_work /= base; - } while (val_work); - /* Real character number overwrites count */ - if (count < num) - count = num; - /* Format number */ - for (i = count - 1; i >= 0; i--) { - if (in_number) { - buf[i] = conv_vec[val % base]; - val /= base; - in_number = val ? 1 : 0; - } else { - buf[i] = zero ? '0' : ' '; - } - } - buf[count] = 0; - return count; -} - -/* - * Convert string to string with indentation - */ -static int str_to_str(char *buf, const char *str, unsigned long count) -{ - unsigned long size; - - size = strlen(str); - if (count < size) - count = size; - else - memset(buf, ' ', count - size); - strcpy(buf + (count - size), str); - return count; -} - -/* - * Convert string to number with given base - */ -unsigned long strtoul(const char *nptr, char **endptr, int base) -{ - unsigned long val = 0; - - while (isdigit(*nptr)) { - if (val != 0) - val *= base; - val += *nptr - '0'; - nptr++; - } - if (endptr) - *endptr = (char *) nptr; - return val; -} - /* * Convert ebcdic string to number with given base */ @@ -218,79 +143,282 @@ unsigned long ebcstrtoul(char *nptr, char **endptr, int base) return val; } -/* - * Convert string to number with given base - */ -static int sprintf_fmt(char type, char *buf, unsigned long val, int zero, - int count) +static int skip_atoi(const char **c) { - switch (type) { + int i = 0; + + do { + i = i*10 + *((*c)++) - '0'; + } while (isdigit(**c)); + + return i; +} + +enum format_type { + FORMAT_TYPE_NONE, + FORMAT_TYPE_STR, + FORMAT_TYPE_ULONG, +}; + +struct printf_spec { + unsigned int type:8; /* format_type enum */ + signed int field_width:24; /* width of output field */ + unsigned int zeropad:1; /* pad numbers with zero */ + unsigned int base:8; /* number base, 8, 10 or 16 only */ + signed int precision:16; /* # of digits/chars */ +}; + +#define FIELD_WIDTH_MAX ((1 << 23) - 1) + +static int format_decode(const char *fmt, struct printf_spec *spec) +{ + const char *start = fmt; + + spec->type = FORMAT_TYPE_NONE; + while (*fmt) { + if (*fmt == '%') + break; + fmt++; + } + + /* return current non-format string */ + if (fmt != start || !*fmt) + return fmt - start; + + /* first char is '%', skip it */ + fmt++; + if (*fmt == '0') { + spec->zeropad = 1; + fmt++; + } + + spec->field_width = -1; + if (isdigit(*fmt)) + spec->field_width = skip_atoi(&fmt); + + spec->precision = -1; + if (*fmt == '.') { + fmt++; + if (isdigit(*fmt)) + spec->precision = skip_atoi(&fmt); + } + + /* always use long form, i.e. ignore long qualifier */ + if (*fmt == 'l') + fmt++; + + switch (*fmt) { case 's': - return str_to_str(buf, (const char *) val, count); - case 'x': - return num_to_str(buf, 16, val, zero, count); + spec->type = FORMAT_TYPE_STR; + break; + + case 'o': + spec->base = 8; + spec->type = FORMAT_TYPE_ULONG; + break; + case 'u': - return num_to_str(buf, 10, val, zero, count); + spec->base = 10; + spec->type = FORMAT_TYPE_ULONG; + break; + + case 'x': + spec->base = 16; + spec->type = FORMAT_TYPE_ULONG; + break; + default: libc_stop(EINTERNAL); } - return 0; + + return ++fmt - start; +} + +static char *string(char *buf, char *end, const char *s, + struct printf_spec *spec) +{ + int limit = spec->precision; + int len = 0; + int spaces; + + /* Copy string to buffer */ + while (limit--) { + char c = *s++; + if (!c) + break; + if (buf < end) + *buf = c; + buf++; + len++; + } + + /* right align if necessary */ + if (len < spec->field_width && buf < end) { + spaces = spec->field_width - len; + if (spaces >= end - buf) + spaces = end - buf; + memmove(buf + spaces, buf, len); + memset(buf, ' ', spaces); + buf += spaces; + } + + return buf; +} + +static char *number(char *buf, char *end, unsigned long val, + struct printf_spec *spec) +{ + /* temporary buffer to prepare the string. + * Worst case: base = 8 -> 3 bits per char -> 2.67 chars per byte */ + char tmp[3 * sizeof(val)]; + static const char vec[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + int field_width = spec->field_width; + int precision = spec->precision; + int len; + + /* prepare string in reverse order */ + len = 0; + while (val) { + tmp[len++] = vec[val % spec->base]; + val /= spec->base; + } + + if (len > precision) + precision = len; + + field_width -= precision; + while (field_width-- > 0) { + char c = spec->zeropad ? '0' : ' '; + if (buf < end) + *buf = c; + buf++; + } + + /* needed if no field width but a precision is given */ + while (len < precision--) { + if (buf < end) + *buf = '0'; + buf++; + } + + while (len-- > 0) { + if (buf < end) + *buf = tmp[len]; + buf++; + } + + return buf; } /* - * Print formated string (va version) + * vsnprintf - Format string and place in a buffer + * + * This funcion only supports a subset of format options defined in the + * C standard, i.e. + * specifiers: + * * %s (strings) + * * %o (unsigned int octal) + * * %u (unsigned int decimal) + * * %x (unsigned int hexadecimal) + * + * length modifier: + * * 'l' (ignored, see below) + * + * flag: + * * '0' (zero padding for integers) + * + * precision and field width as integers, i.e. _not_ by asterix '*'. + * + * The integer specifiers (o, u and, x) always use the long form, i.e. + * assume the argument to be of type 'unsigned long int'. + * + * Returns the number of characters the function would have generated for + * the given input (excluding the trailing '\0'. If the return value is + * greater than or equal @size the resulting string is trunctuated. */ -static void vsprintf(char *str, const char *fmt, va_list va) +static int vsnprintf(char *buf, unsigned long size, const char *fmt, + va_list args) { - unsigned long val, zero, count; - char *fmt_next; + struct printf_spec spec = {0}; + char *str, *end; - do { - if (*fmt == '%') { - fmt++; - if (*fmt == '0') { - zero = 1; - fmt++; - } else { - zero = 0; + str = buf; + end = buf + size; + + /* use negative (large positive) buffer sizes as indication for + * unknown/unlimited buffer sizes. */ + if (end < buf) { + end = ((void *)-1); + size = end - buf; + } + + while (*fmt) { + const char *old_fmt = fmt; + int read = format_decode(fmt, &spec); + int copy; + + fmt += read; + + switch (spec.type) { + case FORMAT_TYPE_NONE: + copy = read; + if (str < end) { + if (copy > end - str) + copy = end - str; + memcpy(str, old_fmt, copy); } - /* No number found by strtoul: count=0 fmt_next=fmt */ - count = strtoul(fmt, &fmt_next, 10); - fmt = fmt_next; - if (*fmt == 'l') - fmt++; - val = va_arg(va, unsigned long); - str += sprintf_fmt(*fmt, str, val, zero, count); - fmt++; - } else { - *str++ = *fmt++; + str += read; + break; + + case FORMAT_TYPE_STR: + str = string(str, end, va_arg(args, char *), &spec); + break; + + case FORMAT_TYPE_ULONG: + str = number(str, end, va_arg(args, unsigned long), + &spec); + break; } - } while (*fmt); - *str = 0; + } + + if (size) { + if (str < end) + *str = '\0'; + else + end[-1] = '\0'; + } + return str - buf; } /* - * Write formated string to string + * Write formatted string to buffer */ -void sprintf(char *str, const char *fmt, ...) +void snprintf(char *buf, unsigned long size, const char *fmt, ...) { va_list va; va_start(va, fmt); - vsprintf(str, fmt, va); + vsnprintf(buf, size, fmt, va); va_end(va); } /* - * Print formated string + * Print formatted string to console */ void printf(const char *fmt, ...) { - char buf[81]; + char buf[LINE_LENGTH + 1]; + int len; va_list va; va_start(va, fmt); - vsprintf(buf, fmt, va); + len = vsnprintf(buf, sizeof(buf), fmt, va); + if (len > LINE_LENGTH) { + buf[LINE_LENGTH - 1] = '.'; + buf[LINE_LENGTH - 2] = '.'; + buf[LINE_LENGTH - 3] = '.'; + } sclp_print(buf); va_end(va); } diff --git a/zipl/boot/libc.h b/zipl/boot/libc.h index 44097fa..68ecd00 100644 --- a/zipl/boot/libc.h +++ b/zipl/boot/libc.h @@ -40,6 +40,7 @@ #define ENOTTY 25 /* Not a typewriter */ #define MIB (1024ULL * 1024) +#define LINE_LENGTH 80 /* max line length printed by printf */ typedef unsigned long long uint64_t; typedef unsigned int uint32_t; @@ -47,13 +48,12 @@ typedef unsigned short uint16_t; typedef unsigned char uint8_t; void printf(const char *, ...); -void sprintf(char *, const char *, ...); +void snprintf(char *buf, unsigned long size, const char *fmt, ...); void *memcpy(void *, const void *, unsigned long); void *memmove(void *, const void *, unsigned long); void *memset(void *, int c, unsigned long); char *strcat(char *, const char *); int strncmp(const char *, const char *, unsigned long); -unsigned long strtoul(const char *, char **, int); unsigned long ebcstrtoul(char *, char **, int); int strlen(const char *); char *strcpy(char *, const char *); diff --git a/zipl/boot/menu.c b/zipl/boot/menu.c index 3f68620..35ac4de 100644 --- a/zipl/boot/menu.c +++ b/zipl/boot/menu.c @@ -186,7 +186,7 @@ boot: (void *)&__stage2_params + TEXT_OFFSET)); /* append 'BOOT_IMAGE=' to parmline */ - sprintf(endstring, " BOOT_IMAGE=%u", value); + snprintf(endstring, sizeof(endstring), " BOOT_IMAGE=%u", value); if ((strlen(cmd_line_extra) + strlen(endstring)) < COMMAND_LINE_SIZE) strcat(cmd_line_extra, endstring); diff --git a/zipl/boot/menu.h b/zipl/boot/menu.h index 1b103a8..ee0f2c5 100644 --- a/zipl/boot/menu.h +++ b/zipl/boot/menu.h @@ -20,7 +20,6 @@ /* max command line length */ #define COMMAND_LINE_SIZE 896 #define BOOT_MENU_ENTRIES 63 -#define LINE_LENGTH 80 #define PARAM_SIZE 8 #define TEXT_OFFSET 4 diff --git a/zipl/boot/tape2dump.c b/zipl/boot/tape2dump.c index 1da3ce1..800b943 100644 --- a/zipl/boot/tape2dump.c +++ b/zipl/boot/tape2dump.c @@ -186,7 +186,7 @@ static void progress_print_disp(unsigned long addr) if (addr % (1024 * 1024 * 16) != 0) return; - sprintf(msg, "%08u", addr >> 20); + snprintf(msg, sizeof(msg), "%08u", addr >> 20); ccw_load_display(msg); } -- 2.21.3 From c3fe7d1b44512bb0e645b54476cafc4a27141022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B6ppner?= Date: Wed, 20 Nov 2019 22:03:54 +0100 Subject: [PATCH 45/56] dasdview: Fix exit status in error cases (#1783288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In error cases, dasdview returns -1 which results in the return value 255. This is due to the fact that only the low-order 8 bits are used for the status value. See 2.13 Status Information [1] in the POSIX standard and the exit() POSIX man page [2] for more details. Instead of returning -1, use the EXIT_FAILURE constant to indicate unsuccessful termination properly. This change also makes the exit status consistent for all error cases in dasdview, as some exit() calls already use EXIT_FAILURE. [1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html [2]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/exit.html Reviewed-by: Stefan Haberland Signed-off-by: Jan Höppner (cherry picked from commit bfbf456b839a3684fbb1d4649b12d2ea57704750) --- dasdview/dasdview.c | 76 ++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/dasdview/dasdview.c b/dasdview/dasdview.c index 3dc5890..1f837e9 100644 --- a/dasdview/dasdview.c +++ b/dasdview/dasdview.c @@ -160,19 +160,19 @@ dasdview_get_info(dasdview_info_t *info) if (err != EBADF) zt_error_print("dasdview: " "Could not retrieve geo information!\n"); - exit(-1); + exit(EXIT_FAILURE); } if (dasd_get_blocksize(info->device, &info->blksize) != 0) { zt_error_print("dasdview: " "Could not retrieve blocksize information!\n"); - exit(-1); + exit(EXIT_FAILURE); } if (dasd_get_info(info->device, &info->dasd_info) != 0) { zt_error_print("dasdview: " "Could not retrieve disk information!\n"); - exit(-1); + exit(EXIT_FAILURE); } characteristics = (struct dasd_eckd_characteristics *) @@ -261,7 +261,7 @@ dasdview_parse_input(unsigned long long *p, dasdview_info_t *info, char *s) error: zt_error_print("dasdview: usage error\n" "%s is not a valid begin/size value!", s); - exit(-1); + exit(EXIT_FAILURE); } /* @@ -454,7 +454,7 @@ dasdview_print_vlabel(dasdview_info_t *info) if (rc) { zt_error_print("error when reading label from device:" " rc=%d\n", rc); - exit(-1); + exit(EXIT_FAILURE); } lzds_dasd_get_vlabel(info->dasd, &tmpvlabel); memcpy(&vlabel, tmpvlabel, sizeof(vlabel)); @@ -600,7 +600,7 @@ dasdview_print_volser(dasdview_info_t *info) if (rc) { zt_error_print("error when reading label from device:" " rc=%d\n", rc); - exit(-1); + exit(EXIT_FAILURE); } lzds_dasd_get_vlabel(info->dasd, &tmpvlabel); memcpy(&vlabel, tmpvlabel, sizeof(vlabel)); @@ -644,7 +644,7 @@ dasdview_read_vtoc(dasdview_info_t *info) zt_error_print("dasdview: disk layout error\n" "%s is not formatted with the z/OS " "compatible disk layout!\n", info->device); - exit(-1); + exit(EXIT_FAILURE); } vtocblk = (u_int64_t)vtoc_get_cyl_from_cchhb(&vlabel.vtoc) * @@ -664,7 +664,7 @@ dasdview_read_vtoc(dasdview_info_t *info) if ((vtocblk <= 0) || (vtocblk > maxblk)) { zt_error_print("dasdview: VTOC error\n" "Volume label VTOC pointer is not valid!\n"); - exit(-1); + exit(EXIT_FAILURE); } vtoc_read_label(info->device, (vtocblk - 1) * info->blksize, @@ -676,7 +676,7 @@ dasdview_read_vtoc(dasdview_info_t *info) /* format4 DSCB is invalid */ zt_error_print("dasdview: VTOC error\n" "Format 4 DSCB is invalid!\n"); - exit(-1); + exit(EXIT_FAILURE); } info->f4c++; @@ -724,19 +724,19 @@ dasdview_read_vtoc(dasdview_info_t *info) if (info->f4c > 1) { zt_error_print("dasdview: VTOC error\n" "More than one FMT4 DSCB!\n"); - exit(-1); + exit(EXIT_FAILURE); } if (info->f5c > 1) { zt_error_print("dasdview: VTOC error\n" "More than one FMT5 DSCB!\n"); - exit(-1); + exit(EXIT_FAILURE); } if (info->f7c > 1) { zt_error_print("dasdview: VTOC error\n" "More than one FMT7 DSCB!\n"); - exit(-1); + exit(EXIT_FAILURE); } } @@ -975,7 +975,7 @@ static void dasdview_print_format1_8_short_info_raw(format1_label_t *f1, } if (rc) { zt_error_print("dasdview: Broken format 3 DSCB chain \n"); - exit(-1); + exit(EXIT_FAILURE); } f3 = (dscb && dscb->fmtid == 0xf3) ? (format3_label_t *)dscb : NULL; @@ -990,7 +990,7 @@ static void dasdview_print_format1_8_short_info_raw(format1_label_t *f1, if (f3->DS3FMTID != 0xf3) { zt_error_print("dasdview: Broken format 3 DSCB" " chain \n"); - exit(-1); + exit(EXIT_FAILURE); } for (j = 0; j < 4; ++j) dasdview_print_short_info_extent_raw(&f3->DS3EXTNT[j]); @@ -1002,7 +1002,7 @@ static void dasdview_print_format1_8_short_info_raw(format1_label_t *f1, if (rc) { zt_error_print("dasdview: Broken format 3 DSCB" " chain \n"); - exit(-1); + exit(EXIT_FAILURE); } } printf("\n"); @@ -1025,7 +1025,7 @@ static void dasdview_print_vtoc_info_raw(dasdview_info_t *info) rc = lzds_raw_vtoc_alloc_dscbiterator(info->rawvtoc, &it); if (rc) { zt_error_print("dasdview: could not allocate DSCB iterator \n"); - exit(-1); + exit(EXIT_FAILURE); } while (!lzds_dscbiterator_get_next_dscb(it, &dscb)) { if (dscb->fmtid == 0xf1) @@ -1058,7 +1058,7 @@ static void dasdview_print_vtoc_info_raw(dasdview_info_t *info) rc = lzds_raw_vtoc_alloc_dscbiterator(info->rawvtoc, &it); if (rc) { zt_error_print("dasdview: could not allocate DSCB iterator \n"); - exit(-1); + exit(EXIT_FAILURE); } while (!lzds_dscbiterator_get_next_dscb(it, &dscb)) { if (dscb->fmtid == 0xf1 || dscb->fmtid == 0xf8) @@ -1739,23 +1739,23 @@ static void dasdview_print_vtoc_raw(dasdview_info_t *info) if (rc) { zt_error_print("error when reading label from device:" " rc=%d\n", rc); - exit(-1); + exit(EXIT_FAILURE); } rc = lzds_dasd_alloc_rawvtoc(info->dasd); if (rc == EINVAL) { zt_error_print("dasdview: Cannot read VTOC because disk does" " not contain valid VOL1 label.\n", info->device); - exit(-1); + exit(EXIT_FAILURE); } else if (rc) { zt_error_print("error when reading vtoc from device:" " rc=%d\n", rc); - exit(-1); + exit(EXIT_FAILURE); } rc = lzds_dasd_get_rawvtoc(info->dasd, &info->rawvtoc); if (rc || !info->rawvtoc) { zt_error_print("dasdview: libvtoc could not read vtoc\n"); - exit(-1); + exit(EXIT_FAILURE); } if (info->vtoc_info || info->vtoc_all) @@ -1764,7 +1764,7 @@ static void dasdview_print_vtoc_raw(dasdview_info_t *info) rc = lzds_raw_vtoc_alloc_dscbiterator(info->rawvtoc, &it); if (rc) { zt_error_print("dasdview: could not allocate DSCB iterator\n"); - exit(-1); + exit(EXIT_FAILURE); } while (!lzds_dscbiterator_get_next_dscb(it, &record)) dasdview_print_vtoc_dscb(info, record); @@ -1858,7 +1858,7 @@ static void dasdview_view_standard(dasdview_info_t *info) zt_error_print("dasdview: open error\n" "Unable to open device %s in read-only mode!\n", info->device); - exit(-1); + exit(EXIT_FAILURE); } j = (info->begin / SEEK_STEP); @@ -1877,7 +1877,7 @@ static void dasdview_view_standard(dasdview_info_t *info) zt_error_print("dasdview: seek error\n" "Unable to seek in device %s!\n", info->device); - exit(-1); + exit(EXIT_FAILURE); } b++; a += SEEK_STEP; @@ -1890,7 +1890,7 @@ static void dasdview_view_standard(dasdview_info_t *info) zt_error_print("dasdview: seek error\n" "Unable to seek in device %s!\n", info->device); - exit(-1); + exit(EXIT_FAILURE); } } @@ -1926,7 +1926,7 @@ static void dasdview_view_standard(dasdview_info_t *info) zt_error_print("dasdview: read error\n" "Unable to read from device %s!\n", info->device); - exit(-1); + exit(EXIT_FAILURE); } if (info->format1) @@ -1945,7 +1945,7 @@ static void dasdview_view_standard(dasdview_info_t *info) zt_error_print("dasdview: read error\n" "Unable to read from device %s!\n", info->device); - exit(-1); + exit(EXIT_FAILURE); } if (info->format1) @@ -2112,18 +2112,18 @@ static void dasdview_view_raw(dasdview_info_t *info) trackdata = memalign(4096, trckbuffsize * RAWTRACKSIZE); if (!trackdata) { zt_error_print("failed to allocate memory\n"); - exit(-1); + exit(EXIT_FAILURE); } rc = lzds_dasd_alloc_dasdhandle(info->dasd, &dasdh); if (rc) { zt_error_print("failed to allocate memory\n"); - exit(-1); + exit(EXIT_FAILURE); } rc = lzds_dasdhandle_open(dasdh); if (rc) { lzds_dasdhandle_free(dasdh); zt_error_print("failed to open device\n"); - exit(-1); + exit(EXIT_FAILURE); } /* residual is the number of tracks we still have to read */ residual = tracks_to_read; @@ -2135,7 +2135,7 @@ static void dasdview_view_raw(dasdview_info_t *info) trckend, trackdata); if (rc) { perror("Error on read"); - exit(-1); + exit(EXIT_FAILURE); } data = trackdata; for (i = 0; i < trckcount; ++i) { @@ -2153,7 +2153,7 @@ static void dasdview_view_raw(dasdview_info_t *info) lzds_dasdhandle_free(dasdh); if (rc < 0) { perror("Error on closing file"); - exit(-1); + exit(EXIT_FAILURE); } } @@ -2253,7 +2253,7 @@ int main(int argc, char *argv[]) zt_error_print("dasdview: usage error\n" "%s is no valid argument for" " option -t/--vtoc\n", optarg); - exit(-1); + exit(EXIT_FAILURE); } info.vtoc = 1; info.action_specified = 1; @@ -2301,13 +2301,13 @@ int main(int argc, char *argv[]) rc = lzds_zdsroot_alloc(&info.zdsroot); if (rc) { zt_error_print("Could not allocate index\n"); - exit(-1); + exit(EXIT_FAILURE); } rc = lzds_zdsroot_add_device(info.zdsroot, info.device, &info.dasd); if (rc) { zt_error_print("Could not add device to index\n"); - exit(-1); + exit(EXIT_FAILURE); } } @@ -2328,7 +2328,7 @@ int main(int argc, char *argv[]) if (info.begin > max) { zt_error_print("dasdview: usage error\n" "'begin' value is not within disk range!"); - exit(-1); + exit(EXIT_FAILURE); } if (info.size_specified) @@ -2343,7 +2343,7 @@ int main(int argc, char *argv[]) zt_error_print("dasdview: usage error\n" "'begin' + 'size' is not within " "disk range!"); - exit(-1); + exit(EXIT_FAILURE); } if ((info.begin_specified || info.size_specified) && @@ -2355,7 +2355,7 @@ int main(int argc, char *argv[]) zt_error_print("dasdview: usage error\n" "Options -1 or -2 make only sense with " "options -b or -s!"); - exit(-1); + exit(EXIT_FAILURE); } /* do the output */ -- 2.21.3 From 54b3e8b2d925fcd9083fc020b75c98b83c299f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Wed, 27 May 2020 10:37:10 +0200 Subject: [PATCH 46/56] zkey: Fix display of XTS attribute for validate command (#1808494) Description: zkey: Fix display of XTS attribute for validate command Symptom: The 'zkey validate' command shows an invalid value for the XTS attribute. Problem: Due to a use after free of the secure key, the XTS attribute is not determined correctly, and is displayed incorrectly. Function is_xts_key() is called with a secure key that has already been freed and thus most likely returns false. This bug has been introduced with feature SEC1717 "Cipher key support" with commit 298fab68fee8 "zkey: Preparations for introducing a new key type" Solution: Free the secure key only after the last use. Reproduction: Generate an XTS key of type CCA-AESDATA or CCA-AESCIPHER and then run 'zkey validate'. Upstream-ID: f75f4aff8f6e4ae148bde858ee1cb7f1066f5f23 --- zkey/keystore.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zkey/keystore.c b/zkey/keystore.c index e6be3a4..4f90bdd 100644 --- a/zkey/keystore.c +++ b/zkey/keystore.c @@ -2516,7 +2516,7 @@ static int _keystore_process_validate(struct keystore *keystore, size_t clear_key_bitsize; size_t secure_key_size; char *apqns = NULL; - u8 *secure_key; + u8 *secure_key = NULL; int is_old_mk; int rc, valid; u64 mkvp; @@ -2550,8 +2550,7 @@ static int _keystore_process_validate(struct keystore *keystore, rc = get_master_key_verification_pattern(secure_key, secure_key_size, &mkvp, keystore->verbose); - free(secure_key); - if (rc) + if (rc != 0) goto out; _keystore_print_record(info->rec, name, properties, 1, @@ -2577,6 +2576,8 @@ static int _keystore_process_validate(struct keystore *keystore, info->num_warnings++; out: + if (secure_key != NULL) + free(secure_key); if (apqns != NULL) free(apqns); if (apqn_list != NULL) -- 2.21.3 From c1d79840cd820a58ad629bb18dd9b1643c1ca5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Wed, 27 May 2020 10:38:25 +0200 Subject: [PATCH 47/56] zkey: Fix display of clear key size for CCA-AESCIPHER keys (#1808492) Description: zkey: Fix display of clear key size for CCA-AESCIPHER keys Symptom: The 'zkey list' command shows bogus values for the keys 'Clear key size' for keys of type CCA-AESCIPHER. Problem: Secure keys of type CCA-AESCIPHER are variable length, dependent on the effective key size (e.g. 128, 192, or 256 bits). However, the key blob stored is padded to a fixed length, so that all key blobs of type CCA-AESCIPHER are the same size, regardless of the effective key bit size. To code to display the clear key bitsize does not correctly handle the padding and may treat a non-XTS key like an XTS key and thus reads past the end of the key blob. This results in bogus values reported as clear key size. This bug has been introduced with feature SEC1717 "Cipher key support" with commit ddde3f354f35 ("zkey: Introduce th CCA-AESCIPHER key type"). Solution: Correct the handling of key of type CCA-AESCIPHER. Reproduction: Generate a key of type CCA-AESCIPHER and then run 'zkey list'. Upstream-ID: 49cbaba302f002aa7f148631a76fc21a3069bc25 --- zkey/pkey.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zkey/pkey.c b/zkey/pkey.c index 640ff86..793ed9e 100644 --- a/zkey/pkey.c +++ b/zkey/pkey.c @@ -1600,9 +1600,9 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) *bitsize = cipherkey->pl - 384; else *bitsize = 0; /* Unknown */ - if (key_size > cipherkey->length) { + if (key_size == 2 * AESCIPHER_KEY_SIZE) { cipherkey = (struct aescipherkeytoken *)(key + - cipherkey->length); + AESCIPHER_KEY_SIZE); if (cipherkey->pfv == 0x00) /* V0 payload */ *bitsize += cipherkey->pl - 384; } -- 2.21.3 From 8575d2798cfa9087fcdabe944e38c0ad2ac69e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Wed, 27 May 2020 10:40:41 +0200 Subject: [PATCH 48/56] zipl: fix secure boot config handling (#1814322) Description: zipl: fix secure boot config handling Symptom: The config file parsing for secure boot worked not as it was expected to be. For example a config section setting was not evaluated properly. It is not possible to specify command line option -S without other options. Additionally the man page showed an invalid example. Problem: The config file parsing was not implemented properly. Solution: The hierarchy of the secure boot settings in the config file is: defaultboot > menu > section Allow that --secure or -S is specified on command line without the need to allow all options on the command line. Also ensure that the command line option overrules the config option and correctly ensure that secure boot is only set for SCSI devices. Fix man page example. Reproduction: Run zipl with a secure= setting in a configuration section or specify -S on command line. Upstream-ID: dcce14923c3e9615df53773d1d8a3a22cbb23b96 Upstream-ID: 27f6c0a167da8d08f7f3343360528528f85d661f Upstream-ID: 6f9337d1016e00f360cf4a81d39a42df5184b3a2 Upstream-ID: 299fd2b7729f35c6fe3be18964f7e5e6a365f94d --- zipl/include/job.h | 1 + zipl/include/zipl.h | 1 + zipl/man/zipl.conf.5.in | 6 ++-- zipl/src/bootmap.c | 14 +++++++- zipl/src/job.c | 75 ++++++++++++++++++++++++++--------------- zipl/src/zipl.c | 9 ++++- 6 files changed, 74 insertions(+), 32 deletions(-) diff --git a/zipl/include/job.h b/zipl/include/job.h index fce811c..e04a7c1 100644 --- a/zipl/include/job.h +++ b/zipl/include/job.h @@ -94,6 +94,7 @@ struct job_menu_entry { char* name; enum job_id id; union job_menu_entry_data data; + int is_secure; }; struct job_menu_data { diff --git a/zipl/include/zipl.h b/zipl/include/zipl.h index 0ef06e4..e5842ed 100644 --- a/zipl/include/zipl.h +++ b/zipl/include/zipl.h @@ -59,6 +59,7 @@ #define MAX_DUMP_VOLUMES 32 +#define SECURE_BOOT_UNDEFINED -1 #define SECURE_BOOT_DISABLED 0 #define SECURE_BOOT_ENABLED 1 #define SECURE_BOOT_AUTO 2 diff --git a/zipl/man/zipl.conf.5.in b/zipl/man/zipl.conf.5.in index ef40287..20731b0 100644 --- a/zipl/man/zipl.conf.5.in +++ b/zipl/man/zipl.conf.5.in @@ -87,8 +87,6 @@ below). .br defaultmenu = menu1 .br -secure = auto -.br [linux] .br @@ -122,6 +120,8 @@ prompt = 1 .br timeout = 0 .br +secure = auto +.br .PP .B BootLoaderSpec configuration files @@ -533,7 +533,7 @@ non-default memory location. .B secure = .IR auto / 1 / 0 -(configuration only) +(configuration and menu) .IP .B Configuration section: .br diff --git a/zipl/src/bootmap.c b/zipl/src/bootmap.c index 456c2ef..5926622 100644 --- a/zipl/src/bootmap.c +++ b/zipl/src/bootmap.c @@ -942,6 +942,7 @@ build_program_table(int fd, struct job_data* job, disk_blockptr_t* pointer, { disk_blockptr_t* table; int entries, component_header; + int is_secure; int i; int rc; @@ -1013,13 +1014,18 @@ build_program_table(int fd, struct job_data* job, disk_blockptr_t* pointer, component_header_ipl; printf("\n"); } + if (job->is_secure != SECURE_BOOT_UNDEFINED) + is_secure = job->is_secure; + else + is_secure = + job->data.menu.entry[i].is_secure; rc = add_ipl_program(fd, &job->data.menu.entry[i].data.ipl, &table[job->data.menu.entry[i].pos], verbose || job->command_line, job->add_files, component_header, info, &job->target, - job->is_secure); + is_secure); break; case job_print_usage: case job_print_version: @@ -1130,6 +1136,12 @@ bootmap_create(struct job_data *job, disk_blockptr_t *program_table, disk_get_type_name(info->type)); goto out_disk_free_info; } + /* Check if secure boot was enabled only for SCSI */ + if (job->is_secure == SECURE_BOOT_ENABLED && + info->type != disk_type_scsi) { + error_reason("Secure boot forced for non-SCSI disk type"); + goto out_disk_free_info; + } if (verbose) { printf("Target device information\n"); disk_print_info(info); diff --git a/zipl/src/job.c b/zipl/src/job.c index 7d61c84..80ddb34 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -80,6 +80,7 @@ struct command_line { int add_files; int dry_run; int force; + int is_secure; enum scan_section_type type; }; @@ -97,6 +98,22 @@ store_option(struct command_line* cmdline, enum scan_keyword_id keyword, return 0; } +static int +set_secure_ipl(char *keyword, int *is_secure) +{ + if (strcmp(keyword, "auto") == 0) { + *is_secure = SECURE_BOOT_AUTO; + } else if (strcmp(keyword, "0") == 0) { + *is_secure = SECURE_BOOT_DISABLED; + } else if (strcmp(keyword, "1") == 0) { + *is_secure = SECURE_BOOT_ENABLED; + } else { + error_reason("Invalid secure boot setting '%s'", + keyword); + return -1; + } + return 0; +} static int get_command_line(int argc, char* argv[], struct command_line* line) @@ -110,6 +127,7 @@ get_command_line(int argc, char* argv[], struct command_line* line) memset((void *) &cmdline, 0, sizeof(struct command_line)); cmdline.type = section_invalid; is_keyword = 0; + cmdline.is_secure = SECURE_BOOT_UNDEFINED; /* Process options */ do { opt = getopt_long(argc, argv, option_string, options, NULL); @@ -225,9 +243,7 @@ get_command_line(int argc, char* argv[], struct command_line* line) cmdline.menu = optarg; break; case 'S': - is_keyword = 1; - rc = store_option(&cmdline, scan_keyword_secure, - optarg); + rc = set_secure_ipl(optarg, &cmdline.is_secure); break; case 'h': cmdline.help = 1; @@ -1048,6 +1064,21 @@ check_job_mvdump_data(struct job_mvdump_data* dump, char* name) return 0; } +static int +check_secure_boot(struct job_data *job) +{ + switch (job->is_secure) { + case SECURE_BOOT_UNDEFINED: + case SECURE_BOOT_DISABLED: + case SECURE_BOOT_ENABLED: + case SECURE_BOOT_AUTO: + return 0; + default: + error_reason("Invalid secure boot setting '%d'", + job->is_secure); + return -1; + } +} static int check_job_data(struct job_data* job) @@ -1092,6 +1123,8 @@ check_job_data(struct job_data* job) case job_mvdump: rc = check_job_mvdump_data(&job->data.mvdump, job->name); } + if (!rc) + rc = check_secure_boot(job); return rc; } @@ -1277,27 +1310,6 @@ type_from_target(char *target, disk_type_t *type) } } -static int -set_secure_ipl(char *keyword, struct job_data *job) -{ - if (strcmp(keyword, "auto") == 0) { - job->is_secure = SECURE_BOOT_AUTO; - } else if (strcmp(keyword, "0") == 0) { - job->is_secure = SECURE_BOOT_DISABLED; - } else if (strcmp(keyword, "1") == 0) { - if (job->target.targettype != disk_type_scsi) { - error_reason("Secure boot forced for non-SCSI disk type"); - return -1; - } - job->is_secure = SECURE_BOOT_ENABLED; - } else { - error_reason("Invalid secure boot setting '%s'", - keyword); - return -1; - } - return 0; -} - static int get_job_from_section_data(char* data[], struct job_data* job, char* section) { @@ -1382,7 +1394,7 @@ get_job_from_section_data(char* data[], struct job_data* job, char* section) /* Fill in secure boot */ if (data[(int) scan_keyword_secure] != NULL) { rc = set_secure_ipl(data[(int) scan_keyword_secure], - job); + &job->is_secure); if (rc) return rc; } @@ -1546,7 +1558,7 @@ get_menu_job(struct scan_token* scan, char* menu, struct job_data* job) case scan_keyword_secure: rc = set_secure_ipl( scan[i].content.keyword.value, - job); + &job->is_secure); if (rc) return rc; break; @@ -1608,6 +1620,7 @@ get_menu_job(struct scan_token* scan, char* menu, struct job_data* job) sizeof(struct job_menu_entry) * job->data.menu.num); /* Fill in data */ current = 0; + job->data.menu.entry->is_secure = SECURE_BOOT_UNDEFINED; for (i=index+1; (scan[i].id != scan_id_empty) && (scan[i].id != scan_id_section_heading) && (scan[i].id != scan_id_menu_heading); i++) { @@ -1639,6 +1652,7 @@ get_menu_job(struct scan_token* scan, char* menu, struct job_data* job) if (temp_job == NULL) return -1; memset((void *) temp_job, 0, sizeof(struct job_data)); + temp_job->is_secure = SECURE_BOOT_UNDEFINED; rc = get_job_from_section_data(data, temp_job, job->data.menu.entry[current].name); if (rc) { @@ -1651,6 +1665,8 @@ get_menu_job(struct scan_token* scan, char* menu, struct job_data* job) job->data.menu.entry[current].id = job_ipl; job->data.menu.entry[current].data.ipl = temp_job->data.ipl; + job->data.menu.entry[current].is_secure = + temp_job->is_secure; memset((void *) &temp_job->data.ipl, 0, sizeof(struct job_ipl_data)); break; @@ -1899,7 +1915,7 @@ job_get(int argc, char* argv[], struct job_data** data) job->add_files = cmdline.add_files; job->data.mvdump.force = cmdline.force; job->dry_run = cmdline.dry_run; - job->is_secure = SECURE_BOOT_AUTO; + job->is_secure = SECURE_BOOT_UNDEFINED; /* Get job data from user input */ if (cmdline.help) { job->command_line = 1; @@ -1918,6 +1934,11 @@ job_get(int argc, char* argv[], struct job_data** data) job_free(job); return rc; } + if (cmdline.is_secure != SECURE_BOOT_UNDEFINED) + job->is_secure = cmdline.is_secure; + else if (job->id != job_menu && job->is_secure == SECURE_BOOT_UNDEFINED) + job->is_secure = SECURE_BOOT_AUTO; + /* Check job data for validity */ rc = check_job_data(job); if (rc) { diff --git a/zipl/src/zipl.c b/zipl/src/zipl.c index 65eefdb..2c53489 100644 --- a/zipl/src/zipl.c +++ b/zipl/src/zipl.c @@ -68,6 +68,8 @@ static const char* usage_text[] = { "-P, --parameters PARMLINE Use specified kernel PARMLINE", "-T, --tape TAPEDEV Install bootloader on tape device TAPEDEV", "-s, --segment SEGMENT,ADDR Install a segment from file SEGMENT", +"-k, --kdump=auto Install a kdump kernel that can be used as a", +" stand-alone dump tool", "-d, --dumpto DUMPDEV[,SIZE] Install a system dump record on tape device", " or disk partition DUMPDEV", "-M, --mvdump DEVLIST[,SIZE] Install a multi-volume dump record on each", @@ -78,7 +80,12 @@ static const char* usage_text[] = { "-n, --noninteractive Answer all confirmation questions with 'yes'", "-V, --verbose Provide more verbose output", "-a, --add-files Add all referenced files to bootmap file", -" --dry-run Simulate run but don't modify IPL records" +" --dry-run Simulate run but don't modify IPL records", +"-S, --secure SWITCH Control the zIPL secure boot support.", +" auto (default):", +" Write signatures if available and supported", +" 1: Write signatures regardless of support", +" 0: Do not write signatures" }; -- 2.21.3 From 9c07b5194f80b57c6d3e74d868b52a96f831653a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Wed, 27 May 2020 10:42:25 +0200 Subject: [PATCH 49/56] zpcictl: Initiate recover after reset (#1814303) Description: zpcictl: Initiate recover after reset Symptom: If a PCI function is reset using zpcictl --reset, the function is in an error state. Problem: zpcictl --reset only issues a SCLP reset and leaves the PCI function in an error state. Solution: Initiate an OS level recovery by calling /sys/bus/devices//recover after the SCLP reset. Reproduction: Call zpcictl --reset Under z/VM check the state of the function with 'vmcp q pcif' Upstream-ID: 73bab8e1a1dd93a90ffb5a39e4dc91f194a2f063 Upstream-ID: bc0d40c5803d4c5426b17b6d59aa0f1e46a2aacc Upstream-ID: d77234ddb68719819c7e8380c71dbebc555539ab Upstream-ID: 304c3d8086bc2a9230c5404f9c9fec72de08d229 --- zpcictl/zpcictl.c | 63 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/zpcictl/zpcictl.c b/zpcictl/zpcictl.c index f2a55ad..7cbfad9 100644 --- a/zpcictl/zpcictl.c +++ b/zpcictl/zpcictl.c @@ -96,6 +96,35 @@ static void fopen_err(char *path) exit(EXIT_FAILURE); } +static void fclose_err(char *path) +{ + if (errno == EIO || errno == EOPNOTSUPP) + warnx("Unsupported operation: %s: %s", path, strerror(errno)); + else + warnx("Could not close file: %s: %s", path, strerror(errno)); + free(path); + exit(EXIT_FAILURE); + +} + +static void fread_err(FILE *fp, char *path) +{ + warnx("Could not read file: %s: %s", path, strerror(errno)); + if (fclose(fp)) + fclose_err(path); + free(path); + exit(EXIT_FAILURE); +} + +static void fwrite_err(FILE *fp, char *path) +{ + warnx("Could not write to file: %s: %s", path, strerror(errno)); + if (fclose(fp)) + fclose_err(path); + free(path); + exit(EXIT_FAILURE); +} + #define READ_CHUNK_SIZE 512 static char *collect_smart_data(struct zpci_device *pdev) @@ -150,14 +179,35 @@ static unsigned int sysfs_read_value(struct zpci_device *pdev, const char *attr) fp = fopen(path, "r"); if (!fp) fopen_err(path); - fscanf(fp, "%x", &val); - fclose(fp); + if (fscanf(fp, "%x", &val) != 1) { + fread_err(fp, path); + } + if (fclose(fp)) + fclose_err(path); free(path); return val; } -static void sysfs_write_data(struct zpci_report_error *report, char *slot) +static void sysfs_write_value(struct zpci_device *pdev, const char *attr, + unsigned int val) +{ + char *path; + FILE *fp; + + path = util_path_sysfs("bus/pci/devices/%s/%s", pdev->slot, attr); + fp = fopen(path, "w"); + if (!fp) + fopen_err(path); + if (fprintf(fp, "%x", val) < 0) { + fwrite_err(fp, path); + } + if (fclose(fp)) + fclose_err(path); + free(path); +} + +static void sysfs_report_error(struct zpci_report_error *report, char *slot) { size_t r_size; char *path; @@ -170,9 +220,9 @@ static void sysfs_write_data(struct zpci_report_error *report, char *slot) if (!fp) fopen_err(path); if (fwrite(report, 1, r_size, fp) != r_size) - warnx("Could not write to file: %s: %s", path, strerror(errno)); + fwrite_err(fp, path); if (fclose(fp)) - warnx("Could not close file: %s: %s", path, strerror(errno)); + fclose_err(path); free(path); } @@ -316,7 +366,7 @@ static void sclp_issue_action(struct zpci_device *pdev, int action) strncpy(report.data.log_data, sdata, sizeof(report.data.log_data)); free(sdata); } - sysfs_write_data(&report, pdev->slot); + sysfs_report_error(&report, pdev->slot); } /* @@ -325,6 +375,7 @@ static void sclp_issue_action(struct zpci_device *pdev, int action) static void sclp_reset_device(struct zpci_device *pdev) { sclp_issue_action(pdev, SCLP_ERRNOTIFY_AQ_RESET); + sysfs_write_value(pdev, "recover", 1); } /* -- 2.21.3 From d9db8c5d6adfc0ee8665a6c51bce8b347fbfc212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Wed, 27 May 2020 10:47:35 +0200 Subject: [PATCH 50/56] dbginfo.sh: Extend data collection (#1814323) Description: dbginfo.sh: Extend data collection Symptom: This update covers various symptoms on dbginfo.sh data collection: - There is no data collection for ethtool, tc and bridge output for debugging network issues - There is no data collection for hyptop output - There is no data collection for nvme devices - There is no data collection for smc devices - Sometimes the lsof output is too long which makes the runtime.out hard to be opened - The journalctl was limited to 50000 lines Problem: Following problems exist: - Missing data collection for ethtool, tc and bridge - Missing data collection for hyptop output - Missing data collection for nvme devices - Missing data collection for smc devices - Sometimes the lsof output is too long which makes the runtime.out hard to be opened - The journalctl was limited to 50000 lines. A very long journalctl will overwrite the beginning part Solution: - Extend the data collection to collect output for ethtool, tc, bridge and hyptop output - Extend the data collection to collect nvme and smc device information - Write the output of lsof to a separate file - Extend journalctl to 100000 lines Reproduction: Run this script and verify the output Upstream-ID: bbd88f26c98c9be7e82c7a7e745d8d07b0be0e5b Upstream-ID: 60bda7ed0d29cfa840e7262663b097a6ef167c80 Upstream-ID: 36292cb1663f703e9247e6b9e84f36748aaf44d0 Upstream-ID: e6a3b1810487f08eb0633172ee81b9fdcbcd1a6d Upstream-ID: 7a2a96e8a38023145bc700838ccc8fa30a6e4228 Upstream-ID: 852fb9dfb8f0bf1aa742a89895132e2c78d2f8dd Upstream-ID: 125df5288779d17be3cc357c1eaa36c138e802ce Upstream-ID: c079e48d7d398f7fdcd58dfeb734c924c780f789 Upstream-ID: 235e997a7718298ed9d8208efc60069406b5bbdd Upstream-ID: 0652c687e3df6d570b0123d31c137faec9a8ffb3 --- scripts/dbginfo.sh | 132 ++++++++++++++++++++++++++++++++++++++----- scripts/dbginfo.sh.1 | 38 ++++++++----- 2 files changed, 143 insertions(+), 27 deletions(-) diff --git a/scripts/dbginfo.sh b/scripts/dbginfo.sh index f3d7f43..574bd03 100755 --- a/scripts/dbginfo.sh +++ b/scripts/dbginfo.sh @@ -167,9 +167,21 @@ readonly OUTPUT_FILE_VMCMD="${WORKPATH}zvm_runtime.out" # File that includes content of files from sysfs readonly OUTPUT_FILE_SYSFS="${WORKPATH}sysfsfiles.out" +# File that includes the output of lsof +readonly OUTPUT_FILE_LSOF="${WORKPATH}open_files.out" + # File that includes content of OSA OAT readonly OUTPUT_FILE_OSAOAT="${WORKPATH}osa_oat" +# File that includes content of Ethtool commands +readonly OUTPUT_FILE_ETHTOOL="${WORKPATH}ethtool.out" + +# File that includes content of tc commands +readonly OUTPUT_FILE_TC="${WORKPATH}tc.out" + +# File that includes content of bridge commands +readonly OUTPUT_FILE_BRIDGE="${WORKPATH}bridge.out" + # File that includes the output of journalctl readonly OUTPUT_FILE_JOURNALCTL="${WORKPATH}journalctl.out" @@ -189,7 +201,7 @@ readonly OUTPUT_FILE_NVME="${WORKPATH}nvme.out" readonly MOUNT_POINT_DEBUGFS="/sys/kernel/debug" # The amount of steps running the whole collections -readonly COLLECTION_COUNT=12 +readonly COLLECTION_COUNT=15 # The kernel version (e.g. '2' from 2.6.32 or '3' from 3.2.1) readonly KERNEL_VERSION=$(uname -r 2>/dev/null | cut -d'.' -f1) @@ -367,7 +379,7 @@ CONFIGFILES="\ $(find /lib/modules -name modules.dep 2>/dev/null)\ /etc/docker\ /lib/systemd/system/docker.service\ - /usr/lib/systemd/system/docker.service\ + /usr/lib/systemd/system\ /etc/apparmor.d\ " @@ -394,6 +406,7 @@ CMDS="uname -a\ :ip link show\ :ip ntable\ :ip a sh\ + :ip -s -s link\ :firewall-cmd --list-all\ :ipcs -a\ :netstat -pantu\ @@ -441,7 +454,8 @@ CMDS="uname -a\ :SPident\ :rpm -qa | sort\ :sysctl -a\ - :lsof\ + :lsof \ + > '${OUTPUT_FILE_LSOF}'\ :mount\ :df -h\ :df -i\ @@ -451,7 +465,7 @@ CMDS="uname -a\ :java -version\ :cat /root/.bash_history\ :env\ - :journalctl --all --no-pager --since=$(date -d '5 day ago' +%Y-%m-%d) --until=now --lines=50000 \ + :journalctl --all --no-pager --lines=100000 --output=short-precise\ > '${OUTPUT_FILE_JOURNALCTL}'\ :openssl engine\ :systemd-delta\ @@ -465,6 +479,10 @@ CMDS="uname -a\ :docker version\ :docker stats --no-stream\ :systemctl status docker.service\ + :blockdev --report\ + :lvdisplay\ + :lspci -vv\ + :smc_dbg\ " ######################################## @@ -549,6 +567,11 @@ collect_cmdsout() { done IFS="${ifs_orig}" + if echo "${RUNTIME_ENVIRONMENT}" | grep -qi "z/VM" >/dev/null 2>&1; then + call_run_command "hyptop -b -d 1 -n 5 -f \#,c,m,C:s,M:s,o -S c" "${OUTPUT_FILE_CMD}" + else call_run_command "hyptop -b -d 1 -n 5 -f \#,T,c,e,m,C:s,E:s,M:s,o -S c" "${OUTPUT_FILE_CMD}" + fi + pr_log_stdout " " } @@ -743,6 +766,83 @@ collect_osaoat() { pr_log_stdout " " } +######################################## +collect_ethtool() { + local network_devices + local network_device + + network_devices=$(ls /sys/class/net 2>/dev/null) + if which ethtool >/dev/null 2>&1; then + if test -n "${network_devices}"; then + pr_syslog_stdout "8 of ${COLLECTION_COUNT}: Collecting ethtool output" + for network_device in ${network_devices}; do + call_run_command "ethtool ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -k ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -a ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -c ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -g ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -i ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -l ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -P ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -S ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -T ${network_device}" "${OUTPUT_FILE_ETHTOOL}" + done + else + pr_syslog_stdout "8 of ${COLLECTION_COUNT}: Collecting ethtool output skipped - no devices" + fi + else + pr_syslog_stdout "8 of ${COLLECTION_COUNT}: Collecting ethtool output skipped - not available" + fi + + pr_log_stdout " " +} + +######################################## +collect_tc() { + local network_devices + local network_device + + network_devices=$(ls /sys/class/net 2>/dev/null) + if which tc >/dev/null 2>&1; then + if test -n "${network_devices}"; then + pr_syslog_stdout "9 of ${COLLECTION_COUNT}: Collecting tc output" + for network_device in ${network_devices}; do + call_run_command "tc -s qdisc show dev ${network_device}" "${OUTPUT_FILE_TC}" + done + else + pr_syslog_stdout "9 of ${COLLECTION_COUNT}: Collecting tc output skipped - no devices" + fi + else + pr_syslog_stdout "9 of ${COLLECTION_COUNT}: Collecting tc output skipped - not available" + fi + + pr_log_stdout " " +} + +######################################## +collect_bridge() { + local network_devices + local network_device + + network_devices=$(ls /sys/class/net 2>/dev/null) + if which bridge >/dev/null 2>&1; then + if test -n "${network_devices}"; then + pr_syslog_stdout "10 of ${COLLECTION_COUNT}: Collecting bridge output" + for network_device in ${network_devices}; do + call_run_command "bridge -d link show dev ${network_device}" "${OUTPUT_FILE_BRIDGE}" + call_run_command "bridge -s fdb show dev ${network_device}" "${OUTPUT_FILE_BRIDGE}" + call_run_command "bridge -d mdb show dev ${network_device}" "${OUTPUT_FILE_BRIDGE}" + done + else + pr_syslog_stdout "10 of ${COLLECTION_COUNT}: Collecting bridge output skipped - no devices" + fi + else + pr_syslog_stdout "10 of ${COLLECTION_COUNT}: Collecting bridge output skipped - not available" + fi + + pr_log_stdout " " +} + ######################################## # OpenVSwitch collect_ovs() { @@ -759,7 +859,7 @@ collect_ovs() { :ovsdb-client dump\ " if test -n "${br_list}"; then - pr_syslog_stdout "8 of ${COLLECTION_COUNT}: Collecting OpenVSwitch output" + pr_syslog_stdout "11 of ${COLLECTION_COUNT}: Collecting OpenVSwitch output" IFS=: for ovscmd in ${ovscmds}; do IFS=${ifs_orig} call_run_command "${ovscmd}" "${OUTPUT_FILE_OVS}.out" @@ -778,7 +878,7 @@ collect_ovs() { IFS="${ifs_orig}" done else - pr_syslog_stdout "8 of ${COLLECTION_COUNT}: Collecting OpenVSwitch output skipped" + pr_syslog_stdout "11 of ${COLLECTION_COUNT}: Collecting OpenVSwitch output skipped" fi pr_log_stdout " " @@ -791,12 +891,12 @@ collect_domain_xml() { domain_list=$(virsh list --all --name) if test -n "${domain_list}"; then - pr_syslog_stdout "9 of ${COLLECTION_COUNT}: Collecting domain xml files" + pr_syslog_stdout "12 of ${COLLECTION_COUNT}: Collecting domain xml files" for domain in ${domain_list}; do call_run_command "virsh dumpxml ${domain}" "${OUTPUT_FILE_XML}_${domain}.xml" done else - pr_syslog_stdout "9 of ${COLLECTION_COUNT}: Collecting domain xml files skipped" + pr_syslog_stdout "12 of ${COLLECTION_COUNT}: Collecting domain xml files skipped" fi pr_log_stdout " " @@ -810,23 +910,23 @@ collect_docker() { # call docker inspect for all containers item_list=$(docker ps -qa) if test -n "${item_list}"; then - pr_syslog_stdout "10a of ${COLLECTION_COUNT}: Collecting docker container output" + pr_syslog_stdout "13a of ${COLLECTION_COUNT}: Collecting docker container output" for item in ${item_list}; do call_run_command "docker inspect ${item}" "${OUTPUT_FILE_DOCKER}" done else - pr_syslog_stdout "10a of ${COLLECTION_COUNT}: Collecting docker container output skipped" + pr_syslog_stdout "13a of ${COLLECTION_COUNT}: Collecting docker container output skipped" fi # call docker inspect for all networks item_list=$(docker network ls -q) if test -n "${item_list}"; then - pr_syslog_stdout "10b of ${COLLECTION_COUNT}: Collecting docker network output" + pr_syslog_stdout "13b of ${COLLECTION_COUNT}: Collecting docker network output" for item in ${item_list}; do call_run_command "docker network inspect ${item}" "${OUTPUT_FILE_DOCKER}" done else - pr_syslog_stdout "10b of ${COLLECTION_COUNT}: Collecting docker network output skipped" + pr_syslog_stdout "13b of ${COLLECTION_COUNT}: Collecting docker network output skipped" fi pr_log_stdout " " @@ -836,7 +936,7 @@ collect_docker() { collect_nvme() { local NVME - pr_syslog_stdout "11 of ${COLLECTION_COUNT}: Collecting nvme output" + pr_syslog_stdout "14 of ${COLLECTION_COUNT}: Collecting nvme output" call_run_command "nvme list" "${OUTPUT_FILE_NVME}" for NVME in /dev/nvme[0-9]*; do @@ -1143,6 +1243,12 @@ collect_configfiles collect_osaoat +collect_ethtool + +collect_tc + +collect_bridge + collect_ovs collect_domain_xml diff --git a/scripts/dbginfo.sh.1 b/scripts/dbginfo.sh.1 index 4771192..ef9fe89 100644 --- a/scripts/dbginfo.sh.1 +++ b/scripts/dbginfo.sh.1 @@ -52,7 +52,7 @@ Sample invocation: .br dbginfo.sh: Debug information script version %S390_TOOLS_VERSION% .br -Copyright IBM Corp. 2002, 2017 +Copyright IBM Corp. 2002, 2019 .PP Hardware platform = s390x .br @@ -60,34 +60,44 @@ Kernel version = .br Runtime environment = z/VM .PP -1 of 11: Collecting command output +1 of 15: Collecting command output .PP -2 of 11: Collecting z/VM command output +2 of 15: Collecting z/VM command output .PP -3 of 11: Collecting procfs +3 of 15: Collecting procfs .PP -4 of 11: Collecting sysfs +4 of 15: Collecting sysfs .PP -5 of 11: Collecting log files +5 of 15: Collecting log files .PP -6 of 11: Collecting config files +6 of 15: Collecting config files .PP -7 of 11: Collecting osa oat output skipped \- not available +7 of 15: Collecting osa oat output skipped \- not available .PP -8 of 11: Collecting OpenVSwitch output +8 of 15: Collecting ethtool output .PP -9 of 11: Collecting domain xml files +9 of 15: Collecting tc output +.pp +10 of 15: Collecting bridge output +.pp +11 of 15: Collecting OpenVSwitch output .PP -10a of 11: Collecting docker container output -10b of 11: Collecting docker network output +12 of 15: Collecting domain xml files .PP -11 of 11: Postprocessing +13a of 15: Collecting docker container output +13b of 15: Collecting docker network output +.PP +14 of 15: Collecting nvme output +.PP +15 of 15: Postprocessing .PP Finalizing: Creating archive with collected data .PP Collected data was saved to: .br - >> /data\-collection/DBGINFO\-2015\-02\-26\-21\-39\-16\-host\-012345.tgz << + >> /data\-collection/DBGINFO\-2019\-08\-19\-21\-39\-16\-host\-012345.tgz << +.br +Review the collected data before sending to your service organization. .SH HINTS Run the script with root authority. .br -- 2.21.3 From 40d2a0d3d48b798ff325b70c02f799ac3829160b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Wed, 27 May 2020 10:50:10 +0200 Subject: [PATCH 51/56] lscpumf: New z15 CPU-MF counters not available (#1821591) Description: lscpumf: New z15 CPU-MF counters not available Symptom: Command lscpumf -c does not show the new deflate counters on IBM z15. Problem: The new counter names have not been published in document SA23-2261-06 by the time te code was release. Solution: Add the definition for the new deflate counters. Reproduction: Run command lscpumf -c and check for counters rf7, rfc, r107 and r108. Upstream-ID: 5d2871d626de6c2b3ab6b12783b87a8b3564cb56 --- cpumf/Makefile | 3 +- cpumf/data/cpum-cf-extended-z15.ctr | 376 ++++++++++++++++++++++++++++ cpumf/data/cpum-cf-hw-counter.map | 5 +- 3 files changed, 380 insertions(+), 4 deletions(-) create mode 100644 cpumf/data/cpum-cf-extended-z15.ctr diff --git a/cpumf/Makefile b/cpumf/Makefile index 2e11810..e97f20d 100644 --- a/cpumf/Makefile +++ b/cpumf/Makefile @@ -9,7 +9,8 @@ DATA_FILES = cpum-cf-hw-counter.map \ cpum-cf-csvn-12345.ctr cpum-cf-csvn-6.ctr \ cpum-cf-extended-z10.ctr cpum-cf-extended-z196.ctr \ cpum-cf-extended-zEC12.ctr cpum-sf-modes.ctr \ - cpum-cf-extended-z13.ctr cpum-cf-extended-z14.ctr + cpum-cf-extended-z13.ctr cpum-cf-extended-z14.ctr \ + cpum-cf-extended-z15.ctr LIB_FILES = bin/cpumf_helper USRBIN_SCRIPTS = bin/lscpumf USRSBIN_SCRIPTS = bin/chcpumf diff --git a/cpumf/data/cpum-cf-extended-z15.ctr b/cpumf/data/cpum-cf-extended-z15.ctr new file mode 100644 index 0000000..0fc935c --- /dev/null +++ b/cpumf/data/cpum-cf-extended-z15.ctr @@ -0,0 +1,376 @@ +# Counter decriptions for the +# IBM z14 extended counter and MT-diagnostic counter set +# +# Notes for transactional-execution mode symbolic names: +# TX .. transactional-execution mode +# NC .. nonconstrained +# C .. constrained +# +# Undefined counters in the extended counter set: +# 142 +# 158-161 +# 176-223 +# 227-231 +# 233-242 +# 246-255 +# Undefined counters in the MT-diagnostic counter set: +# 450-495 +# +# +# Extended Counter Set +# --------------------------------------------------------------------- +Counter:128 Name:L1D_RO_EXCL_WRITES +Short-Description:L1D Read-only Exclusive Writes +Description: +A directory write to the Level-1 Data cache where the line was +originally in a Read-Only state in the cache but has been updated +to be in the Exclusive state that allows stores to the cache line +. +Counter:129 Name:DTLB2_WRITES +Short-Description:DTLB2 Writes +Description: +A translation has been written into The Translation Lookaside +Buffer 2 (TLB2) and the request was made by the data cache +. +Counter:130 Name:DTLB2_MISSES +Short-Description:DTLB2 Misses +Description: +A TLB2 miss is in progress for a request made by the data cache. +Incremented by one for every TLB2 miss in progress for the Level-1 +Data cache on this cycle +. +Counter:131 Name:DTLB2_HPAGE_WRITES +Short-Description:DTLB2 One-Megabyte Page Writes +Description: +A translation entry was written into the Combined Region and Segment +Table Entry array in the Level-2 TLB for a one-megabyte page +. +Counter:132 Name:DTLB2_GPAGE_WRITES +Short-Description:DTLB2 Two-Gigabyte Page Writes +Description: +A translation entry for a two-gigabyte page was written into the +Level-2 TLB +. +Counter:133 Name:L1D_L2D_SOURCED_WRITES +Short-Description:L1D L2D Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the +returned cache line was sourced from the Level-2 Data cache +. +Counter:134 Name:ITLB2_WRITES +Short-Description:ITLB2 Writes +Description: +A translation entry has been written into the Translation Lookaside +Buffer 2 (TLB2) and the request was made by the instruction cache +. +Counter:135 Name:ITLB2_MISSES +Short-Description:ITLB2 Misses +Description: +A TLB2 miss is in progress for a request made by the instruction cache. +Incremented by one for every TLB2 miss in progress for the Level-1 +Instruction cache in a cycle +. +Counter:136 Name:L1I_L2I_SOURCED_WRITES +Short-Description:L1I L2I Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from the Level-2 Instruction cache +. +Counter:137 Name:TLB2_PTE_WRITES +Short-Description:TLB2 PTE Writes +Description: +A translation entry was written into the Page Table Entry array in the +Level-2 TLB +. +Counter:138 Name:TLB2_CRSTE_WRITES +Short-Description:TLB2 CRSTE Writes +Description: +Translation entries were written into the Combined Region and Segment +Table Entry array and the Page Table Entry array in the Level-2 TLB +. +Counter:139 Name:TLB2_ENGINES_BUSY +Short-Description:TLB2 Engines Busy +Description: +The number of Level-2 TLB translation engines busy in a cycle +. +Counter:140 Name:TX_C_TEND +Short-Description:Completed TEND instructions in constrained TX mode +Description: +A TEND instruction has completed in a constrained transactional-execution +mode +. +Counter:141 Name:TX_NC_TEND +Short-Description:Completed TEND instructions in non-constrained TX mode +Description: +A TEND instruction has completed in a non-constrained +transactional-execution mode +. +Counter:143 Name:L1C_TLB2_MISSES +Short-Description:L1C TLB2 Misses +Description: +Increments by one for any cycle where a level-1 cache or level-2 TLB miss +is in progress +. +Counter:144 Name:L1D_ONCHIP_L3_SOURCED_WRITES +Short-Description:L1D On-Chip L3 Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from an On-Chip Level-3 cache without intervention +. +Counter:145 Name:L1D_ONCHIP_MEMORY_SOURCED_WRITES +Short-Description:L1D On-Chip Memory Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from On-Chip memory +. +Counter:146 Name:L1D_ONCHIP_L3_SOURCED_WRITES_IV +Short-Description:L1D On-Chip L3 Sourced Writes with Intervention +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from an On-Chip Level-3 cache with intervention +. +Counter:147 Name:L1D_ONCLUSTER_L3_SOURCED_WRITES +Short-Description:L1D On-Cluster L3 Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from On-Cluster Level-3 cache withountervention +. +Counter:148 Name:L1D_ONCLUSTER_MEMORY_SOURCED_WRITES +Short-Description:L1D On-Cluster Memory Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from an On-Cluster memory +. +Counter:149 Name:L1D_ONCLUSTER_L3_SOURCED_WRITES_IV +Short-Description:L1D On-Cluster L3 Sourced Writes with Intervention +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from an On-Cluster Level-3 cache with intervention +. +Counter:150 Name:L1D_OFFCLUSTER_L3_SOURCED_WRITES +Short-Description:L1D Off-Cluster L3 Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from an Off-Cluster Level-3 cache without +intervention +. +Counter:151 Name:L1D_OFFCLUSTER_MEMORY_SOURCED_WRITES +Short-Description:L1D Off-Cluster Memory Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from Off-Cluster memory +. +Counter:152 Name:L1D_OFFCLUSTER_L3_SOURCED_WRITES_IV +Short-Description:L1D Off-Cluster L3 Sourced Writes with Intervention +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from an Off-Cluster Level-3 cache with intervention +. +Counter:153 Name:L1D_OFFDRAWER_L3_SOURCED_WRITES +Short-Description:L1D Off-Drawer L3 Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from an Off-Drawer Level-3 cache without +intervention +. +Counter:154 Name:L1D_OFFDRAWER_MEMORY_SOURCED_WRITES +Short-Description:L1D Off-Drawer Memory Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from Off-Drawer memory +. +Counter:155 Name:L1D_OFFDRAWER_L3_SOURCED_WRITES_IV +Short-Description:L1D Off-Drawer L3 Sourced Writes with Intervention +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from an Off-Drawer Level-3 cache with intervention +. +Counter:156 Name:L1D_ONDRAWER_L4_SOURCED_WRITES +Short-Description:L1D On-Drawer L4 Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from On-Drawer Level-4 cache +. +Counter:157 Name:L1D_OFFDRAWER_L4_SOURCED_WRITES +Short-Description:L1D Off-Drawer L4 Sourced Writes +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from Off-Drawer Level-4 cache +. +Counter:158 Name:L1D_ONCHIP_L3_SOURCED_WRITES_RO +Short-Description:L1D On-Chip L3 Sourced Writes read-only +Description: +A directory write to the Level-1 Data cache directory where the returned +cache line was sourced from On-Chip L3 but a read-only invalidate was +done to remove other copies of the cache line +. +Counter:162 Name:L1I_ONCHIP_L3_SOURCED_WRITES +Short-Description:L1I On-Chip L3 Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache ine was sourced from an On-Chip Level-3 cache without +intervention +. +Counter:163 Name:L1I_ONCHIP_MEMORY_SOURCED_WRITES +Short-Description:L1I On-Chip Memory Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache ine was sourced from On-Chip memory +. +Counter:164 Name:L1I_ONCHIP_L3_SOURCED_WRITES_IV +Short-Description:L1I On-Chip L3 Sourced Writes with Intervention +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache ine was sourced from an On-Chip Level-3 cache with +intervention +. +Counter:165 Name:L1I_ONCLUSTER_L3_SOURCED_WRITES +Short-Description:L1I On-Cluster L3 Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from an On-Cluster Level-3 cache without +intervention +. +Counter:166 Name:L1I_ONCLUSTER_MEMORY_SOURCED_WRITES +Short-Description:L1I On-Cluster Memory Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from an On-Cluster memory +. +Counter:167 Name:L1I_ONCLUSTER_L3_SOURCED_WRITES_IV +Short-Description:L1I On-Cluster L3 Sourced Writes with Intervention +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from On-Cluster Level-3 cache with +intervention +. +Counter:168 Name:L1I_OFFCLUSTER_L3_SOURCED_WRITES +Short-Description:L1I Off-Cluster L3 Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from an Off-Cluster Level-3 cache without +intervention +. +Counter:169 Name:L1I_OFFCLUSTER_MEMORY_SOURCED_WRITES +Short-Description:L1I Off-Cluster Memory Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from Off-Cluster memory +. +Counter:170 Name:L1I_OFFCLUSTER_L3_SOURCED_WRITES_IV +Short-Description:L1I Off-Cluster L3 Sourced Writes with Intervention +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from an Off-Cluster Level-3 cache with +intervention +. +Counter:171 Name:L1I_OFFDRAWER_L3_SOURCED_WRITES +Short-Description:L1I Off-Drawer L3 Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from an Off-Drawer Level-3 cache without +intervention +. +Counter:172 Name:L1I_OFFDRAWER_MEMORY_SOURCED_WRITES +Short-Description:L1I Off-Drawer Memory Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from Off-Drawer memory +. +Counter:173 Name:L1I_OFFDRAWER_L3_SOURCED_WRITES_IV +Short-Description:L1I Off-Drawer L3 Sourced Writes with Intervention +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from an Off-Drawer Level-3 cache with +intervention +. +Counter:174 Name:L1I_ONDRAWER_L4_SOURCED_WRITES +Short-Description:L1I On-Drawer L4 Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from On-Drawer Level-4 cache +. +Counter:175 Name:L1I_OFFDRAWER_L4_SOURCED_WRITES +Short-Description:L1I Off-Drawer L4 Sourced Writes +Description: +A directory write to the Level-1 Instruction cache directory where the +returned cache line was sourced from Off-Drawer Level-4 cache +. +Counter:224 Name:BCD_DFP_EXECUTION_SLOTS +Short-Description:BCD DFP Execution Slots +Description: +Count of floating point execution slots used for finished Binary Coded +Decimal to Decimal Floating Point conversions. Instructions: CDZT, +CXZT, CZDT, CZXT +. +Counter:225 Name:VX_BCD_EXECUTION_SLOTS +Short-Description:VX BCD Execution Slots +Description: +Count of floating point execution slots used for finished vector arithmetic +Binary Coded Decimal instructions. Instructions: VAP, VSP, VMPVMSP, VDP, +VSDP, VRP, VLIP, VSRP, VPSOPVCP, VTP, VPKZ, VUPKZ, VCVB, VCVBG, VCVDVCVDG +. +Counter:226 Name:DECIMAL_INSTRUCTIONS +Short-Description:Decimal Instructions +Description: +Decimal instructions dispatched. Instructions: CVB, CVD, AP, CP, DP, ED, +EDMK, MP, SRP, SP, ZAP +. +Counter:232 Name:LAST_HOST_TRANSLATIONS +Short-Description:Last host translation done +Description: +Last Host Translation done +. +Counter:243 Name:TX_NC_TABORT +Short-Description:Aborted transactions in non-constrained TX mode +Description: +A transaction abort has occurred in a non-constrained +transactional-execution mode +. +Counter:244 Name:TX_C_TABORT_NO_SPECIAL +Short-Description:Aborted transactions in constrained TX mode not using special completion logic +Description: +A transaction abort has occurred in a constrained transactional-execution +mode and the CPU is not using any special logic to allow the transaction +to complete +. +Counter:245 Name:TX_C_TABORT_SPECIAL +Short-Description:Aborted transactions in constrained TX mode using special completion logic +Description: +A transaction abort has occurred in a constrained transactional-execution +mode and the CPU is using special logic to allow the transaction to +complete +. +Counter:247 Name:DFLT_ACCESS +Short-Description:Cycles CPU spent obtaining access to Deflate unit +Description: +Cycles CPU spent obtaining access to Deflate unit +. +Counter:252 Name:DFLT_CYCLES +Short-Description:Cycles CPU is using Deflate unit +Description: +Cycles CPU is using Deflate unit +. +Counter:264 Name:DFLT_CC +Short-Description:Increments by one for every DEFLATE CONVERSION CALL instruction executed +Description: +Increments by one for every DEFLATE CONVERSION CALL instruction executed +. +Counter:265 Name:DFLT_CCERROR +Short-Description:Increments by one for every DEFLATE CONVERSION CALL instruction executed that ended in Condition Codes 0, 1 or 2 +Description: +Increments by one for every DEFLATE CONVERSION CALL instruction executed that ended in Condition Codes 0, 1 or 2 +. +# +# MT-diagnostic counter set +# --------------------------------------------------------------------- +Counter:448 Name:MT_DIAG_CYCLES_ONE_THR_ACTIVE +Short-Description:Cycle count with one thread active +Description: +Cycle count with one thread active +. +Counter:449 Name:MT_DIAG_CYCLES_TWO_THR_ACTIVE +Short-Description:Cycle count with two threads active +Description: +Cycle count with two threads active +. diff --git a/cpumf/data/cpum-cf-hw-counter.map b/cpumf/data/cpum-cf-hw-counter.map index 5729b95..fec8048 100644 --- a/cpumf/data/cpum-cf-hw-counter.map +++ b/cpumf/data/cpum-cf-hw-counter.map @@ -28,7 +28,6 @@ 2965 => 'cpum-cf-extended-z13.ctr', 3906 => 'cpum-cf-extended-z14.ctr', 3907 => 'cpum-cf-extended-z14.ctr', - # Identical with z14 - 8561 => 'cpum-cf-extended-z14.ctr', - 8562 => 'cpum-cf-extended-z14.ctr', + 8561 => 'cpum-cf-extended-z15.ctr', + 8562 => 'cpum-cf-extended-z15.ctr', }; -- 2.21.3 From 4dae25adb621f717938de97aefeb5cae61aec90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Wed, 27 May 2020 11:24:08 +0200 Subject: [PATCH 52/56] ipl_tools: support clear attribute for FCP and CCW re-IPL (#1812983) This patch adds support for the "clear" sysfs attribute for re-IPL, if available. This attribute allows to control whether the memory should be cleared on re-IPL. Upstream ID: 4753340e79a1fb1f45e86c39e391fc8eaa03408f --- ipl_tools/cmd_chreipl.c | 32 +++++++++++++++++++++++++++++++- ipl_tools/cmd_lsreipl.c | 6 ++++++ ipl_tools/man/chreipl.8 | 15 +++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/ipl_tools/cmd_chreipl.c b/ipl_tools/cmd_chreipl.c index 5ba0b22..640aa0c 100644 --- a/ipl_tools/cmd_chreipl.c +++ b/ipl_tools/cmd_chreipl.c @@ -59,6 +59,7 @@ static const char *const usage_chreipl = "Options for ccw target:\n" " -d, --device Device number of the CCW IPL device\n" " -L, --loadparm Loadparm specification\n" +" -c, --clear 0|1 Control if memory is cleared on re-IPL\n" "\n" "Options for fcp target:\n" " -d, --device Device number of the adapter of the FCP IPL device\n" @@ -66,6 +67,7 @@ static const char *const usage_chreipl = " -w --wwpn World Wide Port Name of the FCP IPL device\n" " -b, --bootprog Bootprog specification\n" " -L, --loadparm Loadparm specification\n" +" -c, --clear 0|1 Control if memory is cleared on re-IPL\n" "\n" "Options for nss target:\n" " -n, --name Identifier of the NSS\n" @@ -95,6 +97,7 @@ static struct locals { int target_type_set; int target_type_auto_mode; enum reipl_type reipl_type; /* CCW, FCP, NSS */ + int reipl_clear; } l; static void __noreturn print_usage_chreipl_exit(void) @@ -457,6 +460,16 @@ static void set_target_type_auto(const char *arg) } } +static void set_reipl_clear(const char *arg) +{ + if (arg[0] == '1') + l.reipl_clear = 1; + else if (arg[0] == '0') + l.reipl_clear = 0; + else + ERR_EXIT("re-IPL clear argument must be either 1 or 0"); +} + static void parse_chreipl_options(int argc, char *argv[]) { int opt, idx; @@ -471,9 +484,10 @@ static void parse_chreipl_options(int argc, char *argv[]) { "bootparms", required_argument, NULL, 'p' }, { "force", no_argument, NULL, 'f' }, { "version", no_argument, NULL, 'v' }, + { "clear", required_argument, NULL, 'c' }, { NULL, 0, NULL, 0 } }; - static const char optstr[] = "hcd:vw:l:fL:b:n:p:"; + static const char optstr[] = "hd:vw:l:fL:b:n:p:c:"; /* dont run without any argument */ if (argc == 1) @@ -490,6 +504,8 @@ static void parse_chreipl_options(int argc, char *argv[]) else set_target_type_auto(argv[1]); + l.reipl_clear = -1; + while ((opt = getopt_long(argc, argv, optstr, long_opts, &idx)) != -1) { switch (opt) { case 'h': @@ -518,6 +534,9 @@ static void parse_chreipl_options(int argc, char *argv[]) case 'f': l.force_set = 1; break; + case 'c': + set_reipl_clear(optarg); + break; case 'v': print_version_exit(); default: @@ -597,6 +616,11 @@ static void chreipl_ccw(void) strlen(l.bootparms), BOOTPARMS_CCW_MAX); } + if (l.reipl_clear >= 0) { + check_exists("reipl/ccw/clear", "CCW re-IPL clear attribute"); + write_str(l.reipl_clear ? "1" : "0", "reipl/ccw/clear"); + } + /* * On old systems that use CCW reipl loadparm cannot be set */ @@ -624,6 +648,12 @@ static void chreipl_fcp(void) ERR_EXIT("Maximum boot parameter length exceeded (%zu/%u)", strlen(l.bootparms), BOOTPARMS_FCP_MAX); } + + if (l.reipl_clear >= 0) { + check_exists("reipl/fcp/clear", "FCP re-IPL clear attribute"); + write_str(l.reipl_clear ? "1" : "0", "reipl/fcp/clear"); + } + /* * On old systems the FCP reipl loadparm cannot be set */ diff --git a/ipl_tools/cmd_lsreipl.c b/ipl_tools/cmd_lsreipl.c index c906f83..a829b9d 100644 --- a/ipl_tools/cmd_lsreipl.c +++ b/ipl_tools/cmd_lsreipl.c @@ -59,6 +59,7 @@ void print_fcp(int show_ipl, int dump) char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" : "/sys/firmware/reipl/fcp/loadparm"; char loadparm[9], loadparm_path[PATH_MAX]; + char *path_reipl_clear = "/sys/firmware/reipl/fcp/clear"; if (dump) printf("%-12s fcp_dump\n", get_ipl_banner(show_ipl)); @@ -79,6 +80,8 @@ void print_fcp(int show_ipl, int dump) } if (access(path_bootparms, R_OK) == 0) print_fw_str("Bootparms: \"%s\"\n", dir, "scp_data"); + if (!show_ipl && access(path_reipl_clear, R_OK) == 0) + print_fw_str("clear: %s\n", dir, "clear"); } void print_ccw(int show_ipl) @@ -89,6 +92,7 @@ void print_ccw(int show_ipl) "/sys/firmware/reipl/ccw/loadparm"; char *path_bootparms = show_ipl ? "/sys/firmware/ipl/parm" : "/sys/firmware/reipl/ccw/parm"; + char *path_reipl_clear = "/sys/firmware/reipl/ccw/clear"; printf("%-12s ccw\n", get_ipl_banner(show_ipl)); print_fw_str("Device: %s\n", dir, "device"); @@ -101,6 +105,8 @@ void print_ccw(int show_ipl) } if (access(path_bootparms, R_OK) == 0) print_fw_str("Bootparms: \"%s\"\n", dir, "parm"); + if (!show_ipl && access(path_reipl_clear, R_OK) == 0) + print_fw_str("clear: %s\n", dir, "clear"); } static void parse_lsreipl_options(int argc, char *argv[]) diff --git a/ipl_tools/man/chreipl.8 b/ipl_tools/man/chreipl.8 index 9adb93a..91ddfad 100644 --- a/ipl_tools/man/chreipl.8 +++ b/ipl_tools/man/chreipl.8 @@ -132,6 +132,14 @@ the same as 0.0.5000). Specifies an entry in the .BR zipl (8) boot menu. If this option is omitted, the default menu entry is used. + +.TP +.BR "\-c" " or " "\-\-clear" +Specify whether memory should be cleared on re-IPL. Possible values are 0 to +disable and 1 to enable memory clearing on re-IPL. +Memory clearing is supported if the "clear" attribute is present in +/sys/firmware/reipl/ccw/. + .PP \fBExamples:\fP .br @@ -176,6 +184,13 @@ configuration that is defined by the boot menu. Instead it can be used to control higher level boot loaders like GRUB. For more details refer to distribution specific documentation. +.TP +.BR "\-c" " or " "\-\-clear" +Specify whether memory should be cleared on re-IPL. Possible values are 0 to +disable and 1 to enable memory clearing on re-IPL. +Memory clearing is supported if the "clear" attribute is present in +/sys/firmware/reipl/fcp/. + .PP \fBExamples:\fP .br -- 2.21.3 From cf5bfa1758a83d0bc29763214d1f194a259bc12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 28 May 2020 09:51:52 +0200 Subject: [PATCH 53/56] zipl: Rebase to 2.13.0 (#1821250) Summary: zipl: Rebase to 2.13.0 Description: In order to prepare for the inclusion of the new genprotimg tool rebase zipl to 2.13.0 level. --- Makefile | 4 +- README.md | 14 +- cmsfs-fuse/cmsfs-fuse.1 | 2 +- cmsfs-fuse/dasd.c | 1 + cmsfs-fuse/helper.h | 2 - dasdinfo/dasdinfo.8 | 4 +- genprotimg/.gitignore | 5 + genprotimg/Makefile | 27 + genprotimg/README.md | 85 +++ genprotimg/boot/.gitignore | 4 + genprotimg/boot/Makefile | 97 +++ genprotimg/boot/common_memory_layout.h | 25 + genprotimg/boot/head.S | 29 + genprotimg/boot/stage3a.c | 62 ++ genprotimg/boot/stage3a.h | 34 + genprotimg/boot/stage3a.lds.S | 103 ++++ genprotimg/boot/stage3a_init.S | 26 + genprotimg/boot/stage3b.c | 77 +++ genprotimg/boot/stage3b.h | 42 ++ genprotimg/boot/stage3b.lds.S | 87 +++ genprotimg/boot/stage3b_reloc.S | 53 ++ genprotimg/man/Makefile | 12 + genprotimg/man/genprotimg.8 | 97 +++ genprotimg/samples/check_hostkeydoc | 276 +++++++++ genprotimg/src/Makefile | 101 +++ genprotimg/src/common.h | 35 ++ genprotimg/src/genprotimg.c | 181 ++++++ genprotimg/src/include/pv_crypto_def.h | 25 + genprotimg/src/include/pv_hdr_def.h | 84 +++ genprotimg/src/pv/pv_args.c | 405 ++++++++++++ genprotimg/src/pv/pv_args.h | 53 ++ genprotimg/src/pv/pv_comp.c | 446 ++++++++++++++ genprotimg/src/pv/pv_comp.h | 78 +++ genprotimg/src/pv/pv_comps.c | 252 ++++++++ genprotimg/src/pv/pv_comps.h | 42 ++ genprotimg/src/pv/pv_error.c | 37 ++ genprotimg/src/pv/pv_error.h | 62 ++ genprotimg/src/pv/pv_hdr.c | 293 +++++++++ genprotimg/src/pv/pv_hdr.h | 36 ++ genprotimg/src/pv/pv_image.c | 821 +++++++++++++++++++++++++ genprotimg/src/pv/pv_image.h | 68 ++ genprotimg/src/pv/pv_ipib.c | 128 ++++ genprotimg/src/pv/pv_ipib.h | 27 + genprotimg/src/pv/pv_opt_item.c | 26 + genprotimg/src/pv/pv_opt_item.h | 20 + genprotimg/src/pv/pv_stage3.c | 164 +++++ genprotimg/src/pv/pv_stage3.h | 30 + genprotimg/src/utils/align.h | 24 + genprotimg/src/utils/buffer.c | 69 +++ genprotimg/src/utils/buffer.h | 31 + genprotimg/src/utils/crypto.c | 798 ++++++++++++++++++++++++ genprotimg/src/utils/crypto.h | 104 ++++ genprotimg/src/utils/file_utils.c | 234 +++++++ genprotimg/src/utils/file_utils.h | 34 + include/boot/ipl.h | 191 ++++++ include/boot/linux_layout.h | 34 + include/boot/loaders_layout.h | 43 ++ include/boot/s390.h | 467 ++++++++++++++ include/boot/sigp.h | 56 ++ include/lib/util_base.h | 17 +- include/lib/zt_common.h | 58 ++ libutil/util_panic_example.c | 2 +- netboot/Makefile.pxelinux.0 | 4 +- qethconf/qethconf.8 | 2 +- vmur/vmur.8 | 2 +- zconf/qeth/misc.h | 2 - zdev/include/misc.h | 2 +- zdump/opts.c | 4 +- zdump/zg.c | 2 +- zdump/zg.h | 3 - zipl/boot/.gitignore | 2 + zipl/boot/Makefile | 47 +- zipl/boot/cio.c | 2 +- zipl/boot/cio.h | 2 +- zipl/boot/ebcdic.c | 30 + zipl/boot/ebcdic.h | 45 ++ zipl/boot/ebcdic_conv.c | 167 +++++ zipl/boot/ebcdic_conv.h | 21 + zipl/boot/eckd0_cdl.S | 6 +- zipl/boot/eckd0_ldl.S | 6 +- zipl/boot/eckd2.c | 2 +- zipl/boot/eckd2dump.c | 2 +- zipl/boot/eckd2dump_mv.c | 8 +- zipl/boot/eckd2dump_sv.c | 5 +- zipl/boot/error.h | 6 + zipl/boot/fba0.S | 6 +- zipl/boot/fba2.c | 2 +- zipl/boot/fba2dump.c | 6 +- zipl/boot/head.S | 6 +- zipl/boot/kdump.c | 2 +- zipl/boot/kdump.h | 2 +- zipl/boot/kdump3.c | 4 +- zipl/boot/libc.c | 38 +- zipl/boot/libc.h | 43 +- zipl/boot/menu.c | 26 +- zipl/boot/menu.h | 5 - zipl/boot/s390.h | 99 +-- zipl/boot/sclp.c | 69 ++- zipl/boot/sclp.h | 39 +- zipl/boot/sclp_stage3.c | 2 +- zipl/boot/sclp_stage3.h | 8 +- zipl/boot/stage2.c | 7 +- zipl/boot/stage2.h | 18 +- zipl/boot/{stage2.lds => stage2.lds.S} | 44 +- zipl/boot/stage2dump.c | 6 +- zipl/boot/stage2dump.h | 5 +- zipl/boot/stage3.c | 281 +++------ zipl/boot/stage3.h | 179 +----- zipl/boot/stage3.lds | 18 +- zipl/boot/stage3.lds.S | 62 ++ zipl/boot/tape0.S | 25 +- zipl/boot/tape2dump.c | 10 +- zipl/include/boot.h | 71 +-- zipl/include/bootmap.h | 4 +- zipl/include/misc.h | 1 + zipl/include/zipl.h | 29 +- zipl/man/zipl.8.in | 10 +- zipl/man/zipl.conf.5.in | 8 +- zipl/src/Makefile | 8 +- zipl/src/boot.c | 32 +- zipl/src/bootmap.c | 34 +- zipl/src/disk.c | 32 +- zipl/src/install.c | 3 +- zipl/src/job.c | 23 +- zipl/src/misc.c | 16 +- zipl/src/scan.c | 5 + 126 files changed, 7617 insertions(+), 787 deletions(-) create mode 100644 genprotimg/.gitignore create mode 100644 genprotimg/Makefile create mode 100644 genprotimg/README.md create mode 100644 genprotimg/boot/.gitignore create mode 100644 genprotimg/boot/Makefile create mode 100644 genprotimg/boot/common_memory_layout.h create mode 100644 genprotimg/boot/head.S create mode 100644 genprotimg/boot/stage3a.c create mode 100644 genprotimg/boot/stage3a.h create mode 100644 genprotimg/boot/stage3a.lds.S create mode 100644 genprotimg/boot/stage3a_init.S create mode 100644 genprotimg/boot/stage3b.c create mode 100644 genprotimg/boot/stage3b.h create mode 100644 genprotimg/boot/stage3b.lds.S create mode 100644 genprotimg/boot/stage3b_reloc.S create mode 100644 genprotimg/man/Makefile create mode 100644 genprotimg/man/genprotimg.8 create mode 100644 genprotimg/samples/check_hostkeydoc create mode 100644 genprotimg/src/Makefile create mode 100644 genprotimg/src/common.h create mode 100644 genprotimg/src/genprotimg.c create mode 100644 genprotimg/src/include/pv_crypto_def.h create mode 100644 genprotimg/src/include/pv_hdr_def.h create mode 100644 genprotimg/src/pv/pv_args.c create mode 100644 genprotimg/src/pv/pv_args.h create mode 100644 genprotimg/src/pv/pv_comp.c create mode 100644 genprotimg/src/pv/pv_comp.h create mode 100644 genprotimg/src/pv/pv_comps.c create mode 100644 genprotimg/src/pv/pv_comps.h create mode 100644 genprotimg/src/pv/pv_error.c create mode 100644 genprotimg/src/pv/pv_error.h create mode 100644 genprotimg/src/pv/pv_hdr.c create mode 100644 genprotimg/src/pv/pv_hdr.h create mode 100644 genprotimg/src/pv/pv_image.c create mode 100644 genprotimg/src/pv/pv_image.h create mode 100644 genprotimg/src/pv/pv_ipib.c create mode 100644 genprotimg/src/pv/pv_ipib.h create mode 100644 genprotimg/src/pv/pv_opt_item.c create mode 100644 genprotimg/src/pv/pv_opt_item.h create mode 100644 genprotimg/src/pv/pv_stage3.c create mode 100644 genprotimg/src/pv/pv_stage3.h create mode 100644 genprotimg/src/utils/align.h create mode 100644 genprotimg/src/utils/buffer.c create mode 100644 genprotimg/src/utils/buffer.h create mode 100644 genprotimg/src/utils/crypto.c create mode 100644 genprotimg/src/utils/crypto.h create mode 100644 genprotimg/src/utils/file_utils.c create mode 100644 genprotimg/src/utils/file_utils.h create mode 100644 include/boot/ipl.h create mode 100644 include/boot/linux_layout.h create mode 100644 include/boot/loaders_layout.h create mode 100644 include/boot/s390.h create mode 100644 include/boot/sigp.h create mode 100644 zipl/boot/.gitignore create mode 100644 zipl/boot/ebcdic.c create mode 100644 zipl/boot/ebcdic.h create mode 100644 zipl/boot/ebcdic_conv.c create mode 100644 zipl/boot/ebcdic_conv.h rename zipl/boot/{stage2.lds => stage2.lds.S} (57%) create mode 100644 zipl/boot/stage3.lds.S diff --git a/Makefile b/Makefile index 8a07a4d..f2a4680 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,9 @@ 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 + systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \ + genprotimg + SUB_DIRS = $(LIB_DIRS) $(TOOL_DIRS) all: $(TOOL_DIRS) diff --git a/README.md b/README.md index af1024e..af91537 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ Package contents * dasdinfo: Display unique DASD ID, either UID or volser. + * genprotimg: + Create a protected virtualization image. + * udev rules: - 59-dasd.rules: rules for unique DASD device nodes created in /dev/disk/. - 57-osasnmpd.rules: udev rules for osasnmpd. @@ -265,9 +268,10 @@ build options: | pfm | `HAVE_PFM` | cpacfstats | | net-snmp | `HAVE_SNMP` | osasnmpd | | glibc-static | `HAVE_LIBC_STATIC` | zfcpdump | -| openssl | `HAVE_OPENSSL` | zkey | +| openssl | `HAVE_OPENSSL` | genprotimg,zkey | | cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup | | json-c | `HAVE_JSONC` | zkey-cryptsetup | +| glib2 | `HAVE_GLIB2` | genprotimg | This table lists additional build or install options: @@ -285,6 +289,14 @@ Build and runtime requirements for specific tools In the following more details on the build an runtime requirements of the different tools are provided: +* genprotimg: + For building genprotimg you need OpenSSL version 1.1.0 or newer + installed (openssl-devel.rpm). Also required is glib2 + (glib2-devel.rpm). Tip: you may skip the genprotimg build by adding + `HAVE_OPENSSL=0` or `HAVE_GLIB2=0`. + + The runtime requirements are: openssl-libs (>= 1.1.0) and glib2. + * osasnmpd: You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm) installed, before building the osasnmpd subagent. diff --git a/cmsfs-fuse/cmsfs-fuse.1 b/cmsfs-fuse/cmsfs-fuse.1 index f5fb385..c3082a5 100644 --- a/cmsfs-fuse/cmsfs-fuse.1 +++ b/cmsfs-fuse/cmsfs-fuse.1 @@ -165,7 +165,7 @@ cmsfs-fuse uses a configuration file for automatic translation based on the file Upon startup, cmsfs-fuse evaluates the file .cmsfs-fuse/filetypes.conf in the user's home directory. If the file does not exist cmsfs-fuse evaluates the file /etc/cmsfs-fuse/filetypes.conf. -The filetypes.conf file contains the CMS file types that are automaticaly translated to ASCII if cmsfs-fuse is started +The filetypes.conf file contains the CMS file types that are automatically translated to ASCII if cmsfs-fuse is started with the -t option. The syntax of the configuration file is one file type per line. Lines that start with a # followed by a space are treated as comments and are ignored. The file type is 8 characters long and must consist of valid CMS file name characters only. diff --git a/cmsfs-fuse/dasd.c b/cmsfs-fuse/dasd.c index 33bbfc7..16c2265 100644 --- a/cmsfs-fuse/dasd.c +++ b/cmsfs-fuse/dasd.c @@ -19,6 +19,7 @@ #include #include +#include "lib/zt_common.h" #include "cmsfs-fuse.h" #include "edf.h" #include "helper.h" diff --git a/cmsfs-fuse/helper.h b/cmsfs-fuse/helper.h index e44f76e..0787f59 100644 --- a/cmsfs-fuse/helper.h +++ b/cmsfs-fuse/helper.h @@ -49,6 +49,4 @@ extern FILE *logfile; fprintf(stderr, COMP "Warning, " __VA_ARGS__); \ } while (0) -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) - #endif diff --git a/dasdinfo/dasdinfo.8 b/dasdinfo/dasdinfo.8 index 4be39cd..2ff28f0 100644 --- a/dasdinfo/dasdinfo.8 +++ b/dasdinfo/dasdinfo.8 @@ -34,7 +34,7 @@ Print DASD uid This option prints the full uid of the DASD. When z/VM provides two virtual devices that are actually located on the same real device, the first four tokens of the uid will be identical for both devices. z/VM -may provide an additional token that allows to distinguish between +may provide an additional token that can be used to distinguish between different minidisks. You need both support in the Linux kernel and z/VM to receive such an additional token. @@ -45,7 +45,7 @@ available by applying the PTFs for VM APAR VM64273 on z/VM 5.2.0 and higher. .BI "-u|--uid" Print DASD uid without z/VM minidisk token -z/VM may provide an additional token that allows to distinguish +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 levels, the -u option will print the uid excluding any z/VM-provided diff --git a/genprotimg/.gitignore b/genprotimg/.gitignore new file mode 100644 index 0000000..08318df --- /dev/null +++ b/genprotimg/.gitignore @@ -0,0 +1,5 @@ +tags +compile_commands.json +src/.check-dep-genprotimg +src/.detect-openssl.dep.c +src/genprotimg diff --git a/genprotimg/Makefile b/genprotimg/Makefile new file mode 100644 index 0000000..127bde2 --- /dev/null +++ b/genprotimg/Makefile @@ -0,0 +1,27 @@ +# Common definitions +include ../common.mak + +.DEFAULT_GOAL := all + +PKGDATADIR := "$(DESTDIR)$(TOOLS_DATADIR)/genprotimg" +TESTS := +SUBDIRS := boot src man +RECURSIVE_TARGETS := all-recursive install-recursive clean-recursive + +all: all-recursive + +install: all install-recursive + $(INSTALL) -d -m 755 "$(PKGDATADIR)" + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 boot/stage3a.bin "$(PKGDATADIR)" + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 boot/stage3b_reloc.bin "$(PKGDATADIR)" + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 samples/check_hostkeydoc "$(PKGDATADIR)" + +clean: clean-recursive + +$(RECURSIVE_TARGETS): + @target=`echo $@ |sed s/-recursive//`; \ + for d in $(SUBDIRS); do \ + $(MAKE) -C $$d $$target; \ + done + +.PHONY: all install clean $(RECURSIVE_TARGETS) diff --git a/genprotimg/README.md b/genprotimg/README.md new file mode 100644 index 0000000..e37ac33 --- /dev/null +++ b/genprotimg/README.md @@ -0,0 +1,85 @@ +# genprotimg + +`genprotimg` takes a kernel, key files, optionally an initrd image, +optionally a file containing the kernel command line parameters, and +generates a single, bootable image file. The generated image file +consists of a concatenation of a plain text boot loader, the encrypted +components for kernel, initrd, kernel command line, and the +integrity-protected PV header, containing the metadata necessary for +running the guest in protected mode. See [Memory Layout](#memory-layout) +for details about the internal structure of the created image. + +It is possible to use the generated image as a kernel for zipl or for +a direct kernel boot using QEMU. + +## Getting started + +If all dependencies are met a simple `make` call in the source tree +should be enough for building `genprotimg`. + +## Details + +The main idea of `genprotimg` is: + +1. read in all keys, IVs, and other information needed for the + encryption of the components and the generation of the PV header +2. add stub stage3a (so we can calculate the memory addresses) +3. add components: prepare the components (alignment and encryption) + and add them to the memory layout +4. build and add stage3b: generate the stage3b and add it to the memory layout +5. generate the PV header: generate the hashes (pld, ald, and tld) of + the components and create the PV header and IPIB +6. parameterize the stub stage3a: uses the IPIB and PV header +7. write the final image to the specified output path + +### Boot Loader + +The boot loader consists of two parts: + +1. stage3a boot loader (cleartext), this loader is responsible for the + transition into the protected mode by doing diag308 subcode 8 and + 10 calls. +2. stage3b boot loader (encrypted), this loader is very similar to the + normal zipl stage3 boot loader. It will be loaded by the Ultravisor + after the successful transition into protected mode. Like the zipl + stage3 boot loader it moves the kernel and patches in the values + for initrd and parmline. + +The loaders have the following constraints: + +1. It must be possible to place stage3a and stage3b at a location + greater than 0x10000 because the zipl stage3 loader zeroes out + everything at addresses lower than 0x10000 of the image. +2. As the stage3 loader of zipl assumes that the passed kernel image + looks like a normal kernel image, the zipl stage3 loader modifies the + content at the memory area 0x10400 - 0x10800, therefore we leave this + area unused in our stage3a loader. +3. The default entry address used by the zipl stage3 loader is 0x10000 + so we add a simple branch to 0x11000 at 0x10000 so the zipl stage3 + loader can modify the area 0x10400 - 0x10800 without affecting the + stage3a loader. + +#### Detail about stage3b + +The stage3b.bin is linked at address 0x9000, therefore it will not +work at another address. The relocation support for the stage3b +loader, so that it can be placed at addresses != 0x9000, is added in +the loader with the name stage3b_reloc.bin. By default, if we're +talking about stage3b we refer to stage3b_reloc.bin. + +### Memory Layout + +The memory layout of the bootable file looks like: + +| Start | End | Use | +|------------------------|------------|-----------------------------------------------------------------------| +| 0 | 0x7 | Short PSW, starting instruction at 0x11000 | +| 0x10000 | 0x10012 | Branch to 0x11000 | +| 0x10013 | 0x10fff | Left intentionally unused | +| 0x11000 | 0x12fff | Stage3a | +| 0x13000 | 0x13fff | IPIB used as argument for the diag308 call | +| 0x14000 | 0x1[45]fff | UV header used for the diag308 call (size can be either 1 or 2 pages) | +| NEXT_PAGE_ALIGNED_ADDR | | Encrypted kernel | +| NEXT_PAGE_ALIGNED_ADDR | | Encrypted kernel parameters | +| NEXT_PAGE_ALIGNED_ADDR | | Encrypted initrd | +| NEXT_PAGE_ALIGNED_ADDR | | Encrypted stage3b_reloc | diff --git a/genprotimg/boot/.gitignore b/genprotimg/boot/.gitignore new file mode 100644 index 0000000..837684e --- /dev/null +++ b/genprotimg/boot/.gitignore @@ -0,0 +1,4 @@ +*.elf +*.lds +*.bin +*.d diff --git a/genprotimg/boot/Makefile b/genprotimg/boot/Makefile new file mode 100644 index 0000000..d39bbd6 --- /dev/null +++ b/genprotimg/boot/Makefile @@ -0,0 +1,97 @@ +# Common definitions +include ../../common.mak + +ZIPL_DIR := $(rootdir)/zipl +ZIPL_BOOT_DIR := $(ZIPL_DIR)/boot + +INCLUDE_PATHS := $(ZIPL_BOOT_DIR) $(ZIPL_DIR)/include $(rootdir)/include +INCLUDE_PARMS := $(addprefix -I,$(INCLUDE_PATHS)) + +ALL_CFLAGS := $(NO_PIE_CFLAGS) -Os -g \ + $(INCLUDE_PARMS) \ + -DENABLE_SCLP_ASCII=1 \ + -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \ + -fno-builtin -ffreestanding -fno-asynchronous-unwind-tables \ + -fno-delete-null-pointer-checks \ + -fexec-charset=IBM1047 -m64 -mpacked-stack \ + -mstack-size=4096 -mstack-guard=128 -msoft-float \ + -Wall -Wformat-security -Wextra -Werror + +FILES := stage3a.bin stage3b.bin stage3b_reloc.bin + +ZIPL_SRCS_C := libc.c ebcdic.c ebcdic_conv.c sclp.c +ZIPL_SRCS_ASM := entry.S + +ZIPL_OBJS_C := $(ZIPL_SRCS_C:%.c=%.o) +ZIPL_OBJS_ASM := $(ZIPL_SRCS_ASM:%.S=%.o) +ZIPL_OBJS := $(ZIPL_OBJS_C) $(ZIPL_OBJS_ASM) + + +all: $(FILES) + +# Prevent make from using some default rules... +%: %.S + +%.o: %.S Makefile + $(CC) $(ALL_CFLAGS) -c -o $@ $< + +%.o: %.c Makefile + $(CC) $(ALL_CFLAGS) -c -o $@ $< + + +# Dependencies for the .lds generation +sources_lds_S = $(wildcard *.lds.S) +dependencies_lds_S = $(sources_lds_S:%.lds.S=.%.lds.d) +# Include all ".lds.d" dependency files for all make targets except for "clean" +ifneq ($(MAKECMDGOALS),clean) +-include $(dependencies_lds_S) +endif + +%.lds: %.lds.S Makefile + $(CPP) -Wp,-MD,.$@.d,-MT,$@ $(INCLUDE_PARMS) -P -C -o $@ $< + +# Special rules for zipl object files +$(ZIPL_OBJS_C): %.o : $(ZIPL_BOOT_DIR)/%.c + $(CC) $(ALL_CFLAGS) -c -o $@ $< + +$(ZIPL_OBJS_ASM): %.o : $(ZIPL_BOOT_DIR)/%.S + $(CC) $(ALL_CFLAGS) -c -o $@ $< + +dependencies_zipl_c := $(ZIPL_SRCS_C:%.c=.%.o.d) + +$(dependencies_zipl_c): .%.o.d : $(ZIPL_BOOT_DIR)/%.c + $(CC_SILENT) -MM $(ALL_CPPFLAGS) $(ALL_CFLAGS) $< > $@ + +ifneq ($(MAKECMDGOALS),clean) +-include $(dependencies_zipl_c) +endif + +stage3b_reloc.o: stage3b.bin + +stage3a.elf: head.o stage3a_init.o stage3a.o stage3a.lds $(ZIPL_OBJS) +stage3b.elf: head.o stage3b.o stage3b.lds $(ZIPL_OBJS) +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";; \ + esac; \ + $(LINK) $$SFLAGS -m64 $(filter %.o, $^) -o $@ + @chmod a-x $@ + +%.bin: %.elf + $(OBJCOPY) -O binary \ + --only-section=.text* \ + --only-section=.ex_table* \ + --only-section=.fixup* \ + --only-section=.data* \ + --only-section=.rodata* \ + $< $@ + @chmod a-x $@ + +clean: + rm -f *.o *.elf *.bin *.map .*.d *.lds + +.PHONY: all clean diff --git a/genprotimg/boot/common_memory_layout.h b/genprotimg/boot/common_memory_layout.h new file mode 100644 index 0000000..4750191 --- /dev/null +++ b/genprotimg/boot/common_memory_layout.h @@ -0,0 +1,25 @@ +/* + * Common memory layout for stage3a and stage3b bootloader. + * + * 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 COMMON_MEMORY_LAYOUT_H +#define COMMON_MEMORY_LAYOUT_H + +#include "boot/loaders_layout.h" + +#define STACK_ADDRESS STAGE3_STACK_ADDRESS +#define STACK_SIZE STAGE3_STACK_SIZE + +#define HEAP_ADDRESS STAGE3_HEAP_ADDRESS +#define HEAP_SIZE STAGE3_HEAP_SIZE + + +#ifndef __ASSEMBLER__ + +#endif /* __ASSEMBLER__ */ +#endif /* COMMON_MEMORY_LAYOUT_H */ diff --git a/genprotimg/boot/head.S b/genprotimg/boot/head.S new file mode 100644 index 0000000..8fae460 --- /dev/null +++ b/genprotimg/boot/head.S @@ -0,0 +1,29 @@ +/* + * Entry code for stage 3a boot loader + * + * 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. + */ + + +#include "common_memory_layout.h" + +#include "boot/s390.h" +#include "boot/sigp.h" + +.section .text.start +.globl _start +_start: + /* Might be called after a diag308 so better set + * architecture and addressing mode + */ + lhi %r1, 1 + sigp %r1, %r0, SIGP_SET_ARCHITECTURE + sam64 + + /* Initialize stack */ + lgfi %r15, STACK_ADDRESS + STACK_SIZE - STACK_FRAME_OVERHEAD + brasl %r14, initialize +.previous diff --git a/genprotimg/boot/stage3a.c b/genprotimg/boot/stage3a.c new file mode 100644 index 0000000..ae944d2 --- /dev/null +++ b/genprotimg/boot/stage3a.c @@ -0,0 +1,62 @@ +/* + * Main program for stage3a bootloader + * + * 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. + */ + +#include "libc.h" +#include "stage3a.h" + +#include "lib/zt_common.h" +#include "boot/s390.h" +#include "boot/ipl.h" +#include "sclp.h" +#include "error.h" + + +static volatile struct stage3a_args __section(".loader_parms") loader_parms; + +void __noreturn start(void) +{ + int rc; + volatile struct stage3a_args *args = &loader_parms; + /* calculate the IPIB memory address */ + struct ipl_parameter_block *ipib = (void *)((uint64_t)args + args->ipib_offs); + + /* Calculate the PV header memory address and set it and its + * size in the IPIB. This allows the PV header to be position + * independent. + */ + ipib->pv.pv_hdr_addr = (uint64_t)args + args->hdr_offs; + ipib->pv.pv_hdr_size = args->hdr_size; + + /* set up ASCII and line-mode */ + sclp_setup(SCLP_LINE_ASCII_INIT); + + /* test if Secure Execution Unpack facility is available */ + stfle(S390_lowcore.stfle_fac_list, + ARRAY_SIZE(S390_lowcore.stfle_fac_list)); + rc = test_facility(UNPACK_FACILITY); + if (rc == 0) + panic(ENOPV, "Secure unpack facility is not available\n"); + + rc = diag308(DIAG308_SET_PV, ipib); + if (rc != DIAG308_RC_OK) + panic(EPV, "Protected boot setup has failed: 0x%x\n", rc); + + rc = diag308(DIAG308_UNPACK_PV, 0x0); + if (rc != DIAG308_RC_OK) { + sclp_setup(SCLP_LINE_ASCII_INIT); + panic(EPV, "Protected boot has failed: 0x%x\n", rc); + } + + while (1) + ; +} + +void panic_notify(unsigned long UNUSED(rc)) +{ +} diff --git a/genprotimg/boot/stage3a.h b/genprotimg/boot/stage3a.h new file mode 100644 index 0000000..f362e86 --- /dev/null +++ b/genprotimg/boot/stage3a.h @@ -0,0 +1,34 @@ +/* + * Main program for stage3a bootloader. + * + * 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 STAGE3A_H +#define STAGE3A_H + +#include "lib/zt_common.h" +#include "boot/loaders_layout.h" + +#define STAGE3A_INIT_ENTRY IMAGE_ENTRY +#define STAGE3A_ENTRY (STAGE3A_INIT_ENTRY + _AC(0x1000, UL)) +#define STAGE3A_LOAD_ADDRESS IMAGE_LOAD_ADDRESS + + +#ifndef __ASSEMBLER__ + +#include + +/* Must not have any padding */ +struct stage3a_args { + uint64_t hdr_offs; + uint64_t hdr_size; + uint64_t ipib_offs; +}; +STATIC_ASSERT(sizeof(struct stage3a_args) == 3 * 8) + +#endif /* __ASSEMBLER__ */ +#endif /* STAGE3A_H */ diff --git a/genprotimg/boot/stage3a.lds.S b/genprotimg/boot/stage3a.lds.S new file mode 100644 index 0000000..eefb52c --- /dev/null +++ b/genprotimg/boot/stage3a.lds.S @@ -0,0 +1,103 @@ +/* + * Memory layout for stage 3a + * ========================== + * + * General memory layout + * --------------------- + * + * 0x00000 - 0x01fff Lowcore + * 0x02000 - 0x05fff Memory allocation (heap) + * 0x0f000 - 0x0ffff Stack + * 0x10000 - 0x10012 Jump to the "actual" stage3a code + * 0x11000 - 0x12fff Stage3a code + arguments (offsets and lengths to the + * actual data: IPIB and UV header) + */ + +#include "stage3a.h" +#include "common_memory_layout.h" + +OUTPUT_FORMAT("elf64-s390", "elf64-s390", "elf64-s390") +OUTPUT_ARCH(s390:64-bit) + +ENTRY(_init) + +SECTIONS +{ + . = 0x0; + + . = HEAP_ADDRESS; + __heap_start = .; + .heap : { + . = . + HEAP_SIZE; + ASSERT(__heap_stop - __heap_start == HEAP_SIZE, + "Heap section doesn't conform to the described memory layout"); + } + __heap_stop = .; + + . = STACK_ADDRESS; + __stack_start = .; + .stack : { + . = . + STACK_SIZE; + ASSERT(__stack_end - __stack_start == STACK_SIZE, + "Stack section doesn't conform to the described memory layout"); + } + __stack_end = .; + + . = STAGE3A_INIT_ENTRY; + __text_init_start = .; + .text : { + stage3a_init.o(.text.init) + __text_init_stop = ABSOLUTE(.); + /* Text size of text_init must be smaller than 'PARMAREA - IMAGE_ENTRY', + * otherwise the text data could be overwritten by the original zipl stage3 + * boot loader */ + ASSERT(__text_init_stop - __text_init_start < PARMAREA - IMAGE_ENTRY, + "Text size must be smaller than 'PARMAREA - IMAGE_ENTRY'"); + . = 0x1000; + ASSERT(ABSOLUTE(.) == STAGE3A_ENTRY, + "Text section doesn't conform to the described memory layout"); + head.o(.text.start) + *(.text) + } + + .ex_table ALIGN(16) : { + __ex_table_start = .; + *(.ex_table) + __ex_table_stop = .; + } + + .bss ALIGN(16) : { + __bss_start = .; + *(.bss) + __bss_stop = .; + } + + .rodata ALIGN(16) : { + *(.rodata) + *(.rodata.*) + } + + .data ALIGN(16) : { + *(.data) + . = ALIGN(16); + /* The IPIB offset and the UV header offset and size will be + * saved in 'loader_parms' */ + __loader_parms_start = .; + KEEP(*(.loader_parms)); + __loader_parms_stop = .; + ASSERT(__loader_parms_stop - __loader_parms_start == 3 * 8, + "Data size must be equal to 'sizeof(struct stage3a_args)'"); + ASSERT(ABSOLUTE(.) < 0x13000, "Data section doesn't conform to the described memory layout"); + } + + /* List this explicitly as otherwise .note.gnu.build-id will be + * put at 0x0 */ + .notes : { + *(.note.*) + } + + /* Sections to be discarded */ + /DISCARD/ : { + *(.eh_frame) + } +} diff --git a/genprotimg/boot/stage3a_init.S b/genprotimg/boot/stage3a_init.S new file mode 100644 index 0000000..87c8d75 --- /dev/null +++ b/genprotimg/boot/stage3a_init.S @@ -0,0 +1,26 @@ +/* + * Entry code for stage 3a boot loader + * + * 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. + */ + +#include "stage3a.h" +#include "boot/sigp.h" + +.section .text.init +.globl _init +_init: + /* set architecture and switch to 64bit */ + lhi %r1, 1 + sigp %r1, %r0, SIGP_SET_ARCHITECTURE + sam64 + /* The original stage3 boot loader will try to store the + * kernel command line and the address and size of the + * ramdisk. Simply ignore this by starting at 0x11000. + */ + lgfi %r1, STAGE3A_ENTRY + br %r1 +.previous diff --git a/genprotimg/boot/stage3b.c b/genprotimg/boot/stage3b.c new file mode 100644 index 0000000..b1a7fbe --- /dev/null +++ b/genprotimg/boot/stage3b.c @@ -0,0 +1,77 @@ +/* + * Main program for stage3b bootloader + * + * 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. + */ + +#include "libc.h" +#include "stage3b.h" + +#include "lib/zt_common.h" +#include "boot/s390.h" +#include "boot/linux_layout.h" +#include "boot/loaders_layout.h" +#include "sclp.h" +#include "error.h" + + +static volatile struct stage3b_args __section(".loader_parms") loader_parms; + +static inline void __noreturn load_psw(struct psw_t psw) +{ + asm volatile("lpswe %0" : : "Q"(psw) : "cc"); + + while (1) + ; +} + +void __noreturn start(void) +{ + volatile struct stage3b_args *args = &loader_parms; + volatile struct memblob *kernel = &args->kernel; + volatile struct memblob *cmdline = &args->cmdline; + volatile struct memblob *initrd = &args->initrd; + volatile struct psw_t psw = args->psw; + + /* set up ASCII and line-mode */ + sclp_setup(SCLP_LINE_ASCII_INIT); + + if (kernel->size < IMAGE_LOAD_ADDRESS) + panic(EINTERNAL, "Invalid kernel\n"); + + if (cmdline->size > COMMAND_LINE_SIZE) + panic(EINTERNAL, "Command line is too large\n"); + + /* move the kernel and cut the kernel header */ + memmove((void *)IMAGE_LOAD_ADDRESS, + (void *)(kernel->src + IMAGE_LOAD_ADDRESS), + kernel->size - IMAGE_LOAD_ADDRESS); + + /* move the kernel cmdline */ + memmove((void *)COMMAND_LINE, + (void *)cmdline->src, + cmdline->size); + /* the initrd does not need to be moved */ + + if (initrd->size != 0) { + /* copy initrd start address and size into new kernel space */ + *(unsigned long long *)INITRD_START = initrd->src; + *(unsigned long long *)INITRD_SIZE = initrd->size; + } + + /* disable ASCII and line-mode */ + sclp_setup(SCLP_DISABLE); + + /* use lpswe instead of diag308 as a I/O subsystem reset is not + * needed as this was already done by the diag308 subcode 10 call + * in stage3a + */ + load_psw(psw); +} + +void panic_notify(unsigned long UNUSED(rc)) +{ +} diff --git a/genprotimg/boot/stage3b.h b/genprotimg/boot/stage3b.h new file mode 100644 index 0000000..421f0ea --- /dev/null +++ b/genprotimg/boot/stage3b.h @@ -0,0 +1,42 @@ +/* + * Main program for stage3b bootloader + * + * 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 STAGE3B_H +#define STAGE3B_H + +#include "lib/zt_common.h" +#include "boot/loaders_layout.h" + +#define STAGE3B_ENTRY STAGE3_ENTRY +#define STAGE3B_LOAD_ADDRESS STAGE3B_ENTRY + + +#ifndef __ASSEMBLER__ + +#include + +#include "boot/s390.h" + +/* Must not have any padding included */ +struct memblob { + uint64_t src; + uint64_t size; +}; +STATIC_ASSERT(sizeof(struct memblob) == 2 * 8) + +/* Must not have any padding included */ +struct stage3b_args { + struct memblob kernel; + struct memblob cmdline; + struct memblob initrd; + struct psw_t psw; +}; +STATIC_ASSERT(sizeof(struct stage3b_args) == 3 * sizeof(struct memblob) + 16) +#endif /* __ASSEMBLER__ */ +#endif /* STAGE3B_H */ diff --git a/genprotimg/boot/stage3b.lds.S b/genprotimg/boot/stage3b.lds.S new file mode 100644 index 0000000..2711d84 --- /dev/null +++ b/genprotimg/boot/stage3b.lds.S @@ -0,0 +1,87 @@ +/* + * Memory layout for stage 3b + * ========================== + * + * General memory layout + * --------------------- + * + * 0x00000 - 0x01fff Lowcore + * 0x02000 - 0x05fff Memory allocation (heap) + * 0x0a000 - 0x0efff Stage3b code + * 0x0f000 - 0x0ffff Stack + */ + +#include "stage3b.h" +#include "common_memory_layout.h" + +OUTPUT_FORMAT("elf64-s390", "elf64-s390", "elf64-s390") +OUTPUT_ARCH(s390:64-bit) + +ENTRY(_start) + +SECTIONS +{ + . = 0x0; + + . = HEAP_ADDRESS; + __heap_start = .; + .heap : { + . = . + HEAP_SIZE; + ASSERT(__heap_stop - __heap_start == HEAP_SIZE, + "Heap section doesn't conform to the described memory layout"); + } + __heap_stop = .; + + . = STAGE3B_ENTRY; + .text : { + head.o(.text.start) + *(.text) + } + + .ex_table ALIGN(16) : { + __ex_table_start = .; + *(.ex_table) + __ex_table_stop = .; + } + + .bss ALIGN(16) : { + __bss_start = .; + *(.bss) + __bss_stop = .; + } + + .rodata ALIGN(16) : { + *(.rodata) + *(.rodata.*) + } + + .data ALIGN(16) : { + *(.data) + . = ALIGN(16); + __loader_parms_start = .; + KEEP(*(.loader_parms)); + __loader_parms_end = .; + ASSERT(__loader_parms_end - __loader_parms_start == 3 * 16 + 16, + "Data size must be equal to 'sizeof(struct stage3b_args)'"); + } + + . = STACK_ADDRESS; + __stack_start = .; + .stack : { + . = . + STACK_SIZE; + ASSERT(__stack_end - __stack_start == STACK_SIZE, + "Stack section doesn't conform to the described memory layout"); + } + __stack_end = .; + + /* List this explicitly as otherwise .note.gnu.build-id will be + * put at 0x0 */ + .notes : { + *(.note.*) + } + + /* Sections to be discarded */ + /DISCARD/ : { + *(.eh_frame) + } +} diff --git a/genprotimg/boot/stage3b_reloc.S b/genprotimg/boot/stage3b_reloc.S new file mode 100644 index 0000000..7b2242d --- /dev/null +++ b/genprotimg/boot/stage3b_reloc.S @@ -0,0 +1,53 @@ +/* + * Relocator code for stage 3b boot loader + * + * 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. + */ + +#include "stage3b.h" +#include "boot/sigp.h" + +.macro MEMCPY dst,src,len + lgr %r0, \dst + lgr %r1, \len + lgr %r2, \src + lgr %r3, \len + +20: mvcle %r0, %r2, 0 + jo 20b +.endm + +.org 0x0 +.section .text.start +.globl stage3b_reloc_start +stage3b_reloc_start: + /* Might be called after a diag308 so better set + * architecture and addressing mode + */ + lhi %r1, 1 + sigp %r1, %r0, SIGP_SET_ARCHITECTURE + sam64 + +.copy_stage3b: + /* Location of stage3b in memory */ + larl %r8, stage3b_start + + /* Destination for stage3b */ + lgfi %r9, STAGE3B_LOAD_ADDRESS + + /* Size of stage3b */ + lghi %r11, stage3b_end - stage3b_start + + /* Copy the stage3b loader to address STAGE3B_LOAD_ADDRESS */ + MEMCPY %r9, %r8, %r11 + + /* Branch to STAGE3B_ENTRY */ + lgfi %r9, STAGE3B_ENTRY + br %r9 +stage3b_start: + .incbin "stage3b.bin" +stage3b_end: +.previous diff --git a/genprotimg/man/Makefile b/genprotimg/man/Makefile new file mode 100644 index 0000000..e60bab4 --- /dev/null +++ b/genprotimg/man/Makefile @@ -0,0 +1,12 @@ +# Common definitions +include ../../common.mak + +all: + +install: + $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8 + $(INSTALL) -m 644 -c genprotimg.8 $(DESTDIR)$(MANDIR)/man8 + +clean: + +.PHONY: all install clean diff --git a/genprotimg/man/genprotimg.8 b/genprotimg/man/genprotimg.8 new file mode 100644 index 0000000..597106e --- /dev/null +++ b/genprotimg/man/genprotimg.8 @@ -0,0 +1,97 @@ +.\" Copyright 2020 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 GENPROTIMG 8 "March 2020" "s390-tools" +.SH NAME +genprotimg \- Create a protected virtualization image + +.SH SYNOPSIS +.SY +.B genprotimg +\fB\-k\fR \fIHOST_KEY_DOCUMENT\fR... +\fB\-i\fR \fIVMLINUZ\fR +[\fB\-r\fR \fIRAMDISK\fR] +[\fB\-p\fR \fIPARMFILE\fR] +\fB\-o\fR \fIOUTFILE\fR +[\fIOPTION\fR]... +.YS + +.SH DESCRIPTION +.PP +Use \fBgenprotimg\fR to generate a single bootable image file with +encrypted and integrity-protected parts. The command requires a kernel +image, a host-key document, and an output file name. Optionally, +specify an initial RAM filesystem, and a file containing the kernel +parameters. Should special circumstances require it, you can +optionally specify your own keys for the encryption by using the +experimental options. In the resulting image file, a plain text boot +loader, the encrypted components for kernel, initial RAM disk, kernel +parameters, and the encrypted and integrity-protected header are +concatenated. The header contains metadata necessary for running the +guest in protected mode. +.PP +Use this image file as a kernel image for zipl or for a direct kernel +boot using QEMU. + +.SH OPTIONS +.TP +\fB\-h\fR, \fB\-\-help\fR +Prints usage information, then exits. +.TP +\fB\-\-help-experimental\fR +Prints experimental usage information, then exits. +.TP +\fB\-\-help-all\fR +Prints all usage information, then exits. +.TP +\fB\-V\fR, \fB\-\-verbose\fR +Provides more detailed output. +.TP +\fB\-k\fR, \fB\-\-host-key-document\fR=\fI\,HOST_KEY_DOCUMENT\/\fR +Specifies a host-key document. At least one is required. Specify this +option multiple times to enable the image to run on more than one +host. +.TP +\fB\-o\fR, \fB\-\-output\fR=\fI\,OUTPUT_FILE\/\fR +Specifies the output file. Required. +.TP +\fB\-i\fR, \fB\-\-image\fR=\fI\,VMLINUZ\/\fR +Specifies the Linux kernel image file. Required. +.TP +\fB\-r\fR, \fB\-\-ramdisk\fR=\fI\,RAMDISK\/\fR +Specifies the RAM disk image. Optional. +.TP +\fB\-p\fR, \fB\-\-parmfile\fR=\fI\,PARMFILE\/\fR +Specifies the kernel command line stored in \fI\,PARMFILE\/\fR. 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\-v\fR, \fB\-\-version\fR +Prints version information, then exits. + +.SH EXAMPLE +.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, 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 \-o \fI\,/boot/vmlinuz.pv\/\fR +.EE +.Ve +.PP + +.SH NOTES +.IP "1." 4 +An ELF file cannot be used as a Linux kernel image. +.IP "2." 4 +Remember to re-run \fBzipl\fR after updating a protected +virtualization image. + +.SH SEE ALSO +\&\fBzipl\fR\|(5), \fBqemu\fR\|(1) diff --git a/genprotimg/samples/check_hostkeydoc b/genprotimg/samples/check_hostkeydoc new file mode 100644 index 0000000..2327bd3 --- /dev/null +++ b/genprotimg/samples/check_hostkeydoc @@ -0,0 +1,276 @@ +#!/bin/sh +# +# check_hostkeydoc - Verify an IBM Secure Execution host key document +# +# Sample script to verify that a host key document is genuine by +# verifying the issuer, the validity date and the signature. +# Optionally verify the full trust chain using a CA certficate. +# +# Revocation list checking not yet implemented. +# +# Sample invocation: +# +# ./check_hostkeydoc HKD1234.crt signing-key.crt +# +# 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. + + +# Allocate temporary files +ISSUER_PUBKEY_FILE=$(mktemp) +SIGNATURE_FILE=$(mktemp) +BODY_FILE=$(mktemp) +ISSUER_DN_FILE=$(mktemp) +SUBJECT_DN_FILE=$(mktemp) +DEF_ISSUER_DN_FILE=$(mktemp) +CRL_SERIAL_FILE=$(mktemp) + +# Cleanup on exit +cleanup() +{ + rm -f $ISSUER_PUBKEY_FILE $SIGNATURE_FILE $BODY_FILE \ + $ISSUER_DN_FILE $SUBJECT_DN_FILE $DEF_ISSUER_DN_FILE \ + $CRL_SERIAL_FILE +} +trap cleanup EXIT + +# Enhanced error checking for bash +if echo $SHELL | grep /bin/bash > /dev/null +then + set -o pipefail + set -o nounset +fi +set -e + +# Usage +usage() +{ +cat <<-EOF +Usage: `basename $1` host-key-doc signing-key-cert [-c CA-cert] [-r CRL] + +Verify an IBM Secure Execution host key document against +a signing key. + +Note that in order to have the full trust chain verified +it is necessary to provide the issueing CA's certificate. + +EOF +} + +check_verify_chain() +{ + # Verify certificate chain in case a CA certificate file/bundle + # was specified on the command line. + if [ $# = 1 ] + then + cat >&2 <<-EOF +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +No CA certificate specified! Skipping trust chain verification. +Make sure that '$1' is a valid certificate. +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +EOF + else + openssl verify -crl_download -crl_check $2 && + openssl verify -crl_download -crl_check -untrusted $2 $1 || + exit 1 + fi +} + +extract_pubkey() +{ + openssl x509 -in $1 -pubkey -noout > $2 +} + +extract_signature() +{ + # Assuming that the last field is the signature + SIGOFFSET=$(openssl asn1parse -in $1 | tail -1 | cut -d : -f 1) + + openssl asn1parse -in $1 -out $2 -strparse $SIGOFFSET -noout +} + +extract_body() +{ + # Assuming that the first field is the full cert body + SIGOFFSET=$(openssl asn1parse -in $1 | head -2 | tail -1 | cut -d : -f 1) + + openssl asn1parse -in $1 -out $2 -strparse $SIGOFFSET -noout +} + +verify_signature() +{ + # Assuming that the signature algorith is SHA512 with RSA + openssl sha512 -verify $1 -signature $2 $3 +} + +canonical_dn() +{ + OBJTYPE=$1 + OBJ=$2 + DNTYPE=$3 + OUTPUT=$4 + + openssl $OBJTYPE -in $OBJ -$DNTYPE -noout -nameopt multiline \ + | sort | grep -v $DNTYPE= > $OUTPUT +} + +default_issuer() +{ + cat <<-EOF + commonName = International Business Machines Corporation + countryName = US + localityName = Poughkeepsie + organizationalUnitName = IBM Z Host Key Signing Service + organizationName = International Business Machines Corporation + stateOrProvinceName = New York +EOF +} + +verify_issuer_files() +{ + default_issuer > $DEF_ISSUER_DN_FILE + + if ! diff $ISSUER_DN_FILE $DEF_ISSUER_DN_FILE + then + echo Incorrect default issuer >&2 && exit 1 + fi + + if diff $ISSUER_DN_FILE $SUBJECT_DN_FILE + then + echo Issuer verification OK + else + echo Issuer verification failed >&2 && exit 1 + fi +} + +cert_time() +{ + DATE=$(openssl x509 -in $1 -$2 -noout | sed "s/^.*=//") + + date -d "$DATE" +%s +} + +crl_time() +{ + DATE=$(openssl crl -in $1 -$2 -noout | sed "s/^.*=//") + + date -d "$DATE" +%s +} + +verify_dates() +{ + START="$1" + END="$2" + MSG="${3:-Certificate}" + NOW=$(date +%s) + + if [ $START -le $NOW -a $NOW -le $END ] + then + echo "${MSG} dates are OK" + else + echo "${MSG} date verification failed" >&2 && exit 1 + fi +} + +crl_serials() +{ + openssl crl -in $1 -text -noout | \ + grep "Serial Number" > $CRL_SERIAL_FILE +} + +check_serial() +{ + CERT_SERIAL=$(openssl x509 -in $1 -noout -serial | cut -d = -f 2) + + grep -q $CERT_SERIAL $CRL_SERIAL_FILE +} + +check_file() +{ + [ $# = 0 ] || + [ -e "$1" ] || + (echo "File '$1' not found" >&2 && exit 1) +} + +# check args +CRL_FILE= +CA_FILE= + +args=$(getopt -qu "r:c:h" $*) +if [ $? = 0 ] +then + set -- $args + while [ $1 != "" ] + do + case $1 in + -r) CRL_FILE=$2; shift 2;; + -c) CA_FILE=$2; shift 2;; + -h) usage $0; exit 0;; + --) shift; break;; + esac + done +else + usage $0 >&2 + exit 1 +fi + +if [ $# -ne 2 ] +then + usage $0 >&2 + exit 1 +fi + +HKD_FILE=$1 +HKSK_FILE=$2 + +# Check whether all specified files exist +check_file $HKD_FILE +check_file $HKSK_FILE +check_file $CA_FILE +check_file $CRL_FILE + +# Check trust chain +check_verify_chain $HKSK_FILE $CA_FILE + +# Verify host key document signature +echo -n "Checking host key document signature: " +extract_pubkey $HKSK_FILE $ISSUER_PUBKEY_FILE && +extract_signature $HKD_FILE $SIGNATURE_FILE && +extract_body $HKD_FILE $BODY_FILE && +verify_signature $ISSUER_PUBKEY_FILE $SIGNATURE_FILE $BODY_FILE || +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 dates +verify_dates $(cert_time $HKD_FILE startdate) $(cert_time $HKD_FILE enddate) + +# Check CRL if specified +if [ -n "$CRL_FILE" ] +then + echo -n "Checking CRL signature: " + extract_signature $CRL_FILE $SIGNATURE_FILE && + extract_body $CRL_FILE $BODY_FILE && + verify_signature $ISSUER_PUBKEY_FILE $SIGNATURE_FILE $BODY_FILE || + exit 1 + + 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_dates $(crl_time $CRL_FILE lastupdate) $(crl_time $CRL_FILE nextupdate) 'CRL' + + crl_serials $CRL_FILE + check_serial $HKD_FILE && + echo "Certificate is revoked, do not use it anymore!" >&2 && + exit 1 +fi + +# We made it +echo All checks reqested for \'$HKD_FILE\' were successful diff --git a/genprotimg/src/Makefile b/genprotimg/src/Makefile new file mode 100644 index 0000000..1adeac3 --- /dev/null +++ b/genprotimg/src/Makefile @@ -0,0 +1,101 @@ +# Common definitions +include ../../common.mak + +bin_PROGRAM = genprotimg + +PKGDATADIR ?= "$(DESTDIR)$(TOOLS_DATADIR)/genprotimg" +SRC_DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) +TOP_SRCDIR := $(SRC_DIR)/../ +ROOT_DIR = $(TOP_SRC_DIR)/../../ +ZIPL_DIR = $(ROOT_DIR)/zipl +LOADER_DIR = $(TOP_SRCDIR)/boot + +INCLUDE_PATHS = "$(SRC_DIR)" "$(TOP_SRCDIR)" "$(ROOTDIR)/include" +INCLUDE_PARMS = $(addprefix -I,$(INCLUDE_PATHS)) + +WARNINGS := -Wall -Wextra -Wshadow \ + -Wcast-align -Wwrite-strings -Wmissing-prototypes \ + -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline \ + -Wno-long-long -Wuninitialized -Wconversion -Wstrict-prototypes \ + -Wpointer-arith -Werror \ + $(NULL) + +$(bin_PROGRAM)_SRCS := $(bin_PROGRAM).c pv/pv_stage3.c pv/pv_image.c \ + pv/pv_comp.c pv/pv_hdr.c pv/pv_ipib.c utils/crypto.c utils/file_utils.c \ + pv/pv_args.c utils/buffer.c pv/pv_comps.c pv/pv_error.c \ + pv/pv_opt_item.c \ + $(NULL) +$(bin_PROGRAM)_OBJS := $($(bin_PROGRAM)_SRCS:.c=.o) + +ALL_CFLAGS += -std=gnu11 -DPKGDATADIR=$(PKGDATADIR) \ + $(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) \ + $(WARNINGS) \ + $(NULL) +ALL_CPPFLAGS += $(INCLUDE_PARMS) +LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) + + +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) +LIBCRYPTO_LIBS := $(shell pkg-config --silence-errors --libs libcrypto) +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 +endif + +BUILD_TARGETS := skip-$(bin_PROGRAM) +INSTALL_TARGETS := skip-$(bin_PROGRAM) +ifneq (${HAVE_OPENSSL},0) +ifneq (${HAVE_GLIB2},0) +BUILD_TARGETS := $(bin_PROGRAM) +INSTALL_TARGETS := install-$(bin_PROGRAM) +endif +endif + +all: $(BUILD_TARGETS) + +install: $(INSTALL_TARGETS) + +$(bin_PROGRAM): $($(bin_PROGRAM)_OBJS) + +skip-$(bin_PROGRAM): + echo " SKIP $(bin_PROGRAM) due to unresolved dependencies" + +install-$(bin_PROGRAM): $(bin_PROGRAM) + $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) + $(INSTALL) -c $^ $(DESTDIR)$(USRBINDIR) + +clean: + $(RM) -f $($(bin_PROGRAM)_OBJS) $(bin_PROGRAM) .check-dep-$(bin_PROGRAM) .detect-openssl.dep.c + +.PHONY: all install clean skip-$(bin_PROGRAM) install-$(bin_PROGRAM) + +$($(bin_PROGRAM)_OBJS): .check-dep-$(bin_PROGRAM) + +.detect-openssl.dep.c: + echo "#include " > $@ + echo "#if OPENSSL_VERSION_NUMBER < 0x10100000L" >> $@ + 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-devel / libssl-dev version >= 1.1.0", \ + "HAVE_OPENSSL=0", \ + "-I.") + touch $@ diff --git a/genprotimg/src/common.h b/genprotimg/src/common.h new file mode 100644 index 0000000..401d690 --- /dev/null +++ b/genprotimg/src/common.h @@ -0,0 +1,35 @@ +#ifndef COMMON_H +#define COMMON_H + +#define GETTEXT_PACKAGE "genprotimg" +#include +#include + +#include "boot/linux_layout.h" +#include "boot/s390.h" +#include "lib/zt_common.h" + +static const gchar tool_name[] = "genprotimg"; +static const gchar copyright_notice[] = "Copyright IBM Corp. 2020"; + +/* default values */ +#define GENPROTIMG_STAGE3A_PATH (STRINGIFY(PKGDATADIR) "/stage3a.bin") +#define GENPROTIMG_STAGE3B_PATH (STRINGIFY(PKGDATADIR) "/stage3b_reloc.bin") + +#define DEFAULT_INITIAL_PSW_ADDR IMAGE_ENTRY +#define DEFAULT_INITIAL_PSW_MASK (PSW_MASK_EA | PSW_MASK_BA) + +#define DO_PRAGMA(x) _Pragma(#x) + +# 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 + +#endif diff --git a/genprotimg/src/genprotimg.c b/genprotimg/src/genprotimg.c new file mode 100644 index 0000000..0d82550 --- /dev/null +++ b/genprotimg/src/genprotimg.c @@ -0,0 +1,181 @@ +/* + * genprotimg - build relocatable secure images + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "pv/pv_args.h" +#include "pv/pv_image.h" + +enum { + LOG_LEVEL_CRITICAL = 0, + LOG_LEVEL_INFO = 1, + LOG_LEVEL_DEBUG = 2, +}; + +static gint log_level = LOG_LEVEL_CRITICAL; +static gchar *tmp_dir; + +static void rmdir_recursive(gchar *dir_path, GError **err) +{ + const gchar *file = NULL; + g_autoptr(GDir) d = NULL; + + if (!dir_path) + return; + + d = g_dir_open(dir_path, 0, err); + if (!d) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to open directory '%s': %s"), dir_path, + g_strerror(errno)); + return; + } + + while ((file = g_dir_read_name(d)) != NULL) { + g_autofree gchar *file_path = + g_build_filename(dir_path, file, NULL); + /* ignore error */ + (void)g_unlink(file_path); + } + + if (g_rmdir(dir_path) != 0) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to remove directory '%s': %s"), dir_path, + g_strerror(errno)); + return; + } +} + +static void sig_term_handler(int signal G_GNUC_UNUSED) +{ + rmdir_recursive(tmp_dir, NULL); + exit(EXIT_FAILURE); +} + +static void log_handler_cb(const gchar *log_domain G_GNUC_UNUSED, + GLogLevelFlags level, const gchar *message, + gpointer user_data G_GNUC_UNUSED) +{ + const gchar *prefix = ""; + + /* filter out messages depending on debugging level */ + if ((level & G_LOG_LEVEL_DEBUG) && log_level < LOG_LEVEL_DEBUG) + return; + + if ((level & G_LOG_LEVEL_INFO) && log_level < LOG_LEVEL_INFO) + return; + + if (level & G_LOG_LEVEL_WARNING) + prefix = "WARNING: "; + + if (level & G_LOG_LEVEL_ERROR) + prefix = "ERROR: "; + + if (level & (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR)) + g_printerr("%s%s\n", prefix, message); + else + g_print("%s%s\n", prefix, message); +} + +static void setup_prgname(const gchar *name) +{ + g_set_prgname(name); + g_set_application_name(_(name)); +} + +static void setup_handler(const gint *signals, const gsize signals_n) +{ + /* set up logging handler */ + g_log_set_handler(NULL, + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | + G_LOG_FLAG_RECURSION, + log_handler_cb, NULL); + + /* set signal handler */ + for (gsize i = 0; i < signals_n; i++) + signal(signals[i], sig_term_handler); +} + +static void remove_signal_handler(const gint *signals, const gsize signals_n) +{ + for (gsize i = 0; i < signals_n; i++) + signal(signals[i], SIG_DFL); +} + +gint main(gint argc, gchar *argv[]) +{ + g_autoptr(PvArgs) args = pv_args_new(); + gint signals[] = { SIGINT, SIGTERM }; + g_autoptr(PvImage) img = NULL; + gint ret = EXIT_FAILURE; + GError *err = NULL; + + setlocale(LC_CTYPE, ""); + setup_prgname(tool_name); + setup_handler(signals, G_N_ELEMENTS(signals)); + + if (pv_args_parse_options(args, &argc, &argv, &err) < 0) + goto error; + + /* set new log level */ + log_level = args->log_level; + + /* if the user has not specified a temporary directory let's + * create one + */ + if (!args->tmp_dir) { + tmp_dir = g_dir_make_tmp("genprotimg-XXXXXX", &err); + if (!tmp_dir) + goto error; + args->tmp_dir = g_strdup(tmp_dir); + } + + /* allocate and initialize ``pv_img`` data structure */ + img = pv_img_new(args, GENPROTIMG_STAGE3A_PATH, &err); + if (!img) + goto error; + + /* add user components: `args->comps` must be sorted by the + * component type => by memory address + */ + for (GSList *iterator = args->comps; iterator; iterator = iterator->next) { + const PvArg *arg = iterator->data; + + if (pv_img_add_component(img, arg, &err) < 0) + goto error; + } + + if (pv_img_finalize(img, GENPROTIMG_STAGE3B_PATH, &err) < 0) + goto error; + + if (pv_img_write(img, args->output_path, &err) < 0) + goto error; + + ret = EXIT_SUCCESS; + +error: + if (err) { + fputs(err->message, stderr); + fputc('\n', stderr); + g_clear_error(&err); + } + rmdir_recursive(tmp_dir, NULL); + remove_signal_handler(signals, G_N_ELEMENTS(signals)); + g_free(tmp_dir); + exit(ret); +} diff --git a/genprotimg/src/include/pv_crypto_def.h b/genprotimg/src/include/pv_crypto_def.h new file mode 100644 index 0000000..ddb8652 --- /dev/null +++ b/genprotimg/src/include/pv_crypto_def.h @@ -0,0 +1,25 @@ +/* + * PV cryptography related 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 PV_CRYPTO_DEF_H +#define PV_CRYPTO_DEF_H + +#include + +#include "lib/zt_common.h" + +union ecdh_pub_key { + struct { + uint8_t x[80]; + uint8_t y[80]; + }; + uint8_t data[160]; +} __packed; + +#endif diff --git a/genprotimg/src/include/pv_hdr_def.h b/genprotimg/src/include/pv_hdr_def.h new file mode 100644 index 0000000..850ed26 --- /dev/null +++ b/genprotimg/src/include/pv_hdr_def.h @@ -0,0 +1,84 @@ +/* + * PV 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 PV_HDR_DEF_H +#define PV_HDR_DEF_H + +#include + +#include "boot/s390.h" +#include "lib/zt_common.h" +#include "utils/crypto.h" + +#include "pv_crypto_def.h" + +/* Magic number which is used to identify the file containing the PV + * header + */ +#define PV_MAGIC_NUMBER 0x49424d5365634578ULL +#define PV_VERSION_1 0x00000100U + +/* prevent Ultravisor decryption during unpack operation */ +#define PV_CFLAG_NO_DECRYPTION 0x10000000ULL + +/* maxima for the PV version 1 */ +#define PV_V1_IPIB_MAX_SIZE PAGE_SIZE +#define PV_V1_PV_HDR_MAX_SIZE (2 * PAGE_SIZE) + +typedef struct pv_hdr_key_slot { + uint8_t digest_key[SHA256_DIGEST_LENGTH]; + uint8_t wrapped_key[32]; + uint8_t tag[AES_256_GCM_TAG_SIZE]; +} __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[AES_256_GCM_IV_SIZE]; + uint32_t res1; + uint64_t nks; + uint64_t sea; + uint64_t nep; + uint64_t pcf; + union ecdh_pub_key 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[AES_256_XTS_KEY_SIZE / 2]; + uint8_t img_enc_key_2[AES_256_XTS_KEY_SIZE / 2]; + struct psw_t psw; + uint64_t scf; + uint32_t noi; + uint32_t res2; +}; +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[AES_256_GCM_TAG_SIZE]; +} PvHdr; + +#endif diff --git a/genprotimg/src/pv/pv_args.c b/genprotimg/src/pv/pv_args.c new file mode 100644 index 0000000..9fb7298 --- /dev/null +++ b/genprotimg/src/pv/pv_args.c @@ -0,0 +1,405 @@ +/* + * PV arguments related definitions and functions + * + * 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. + */ + +#include +#include + +#include "common.h" + +#include "pv_comp.h" +#include "pv_error.h" +#include "pv_args.h" + +static gchar summary[] = + "Use genprotimg to create a protected virtualization kernel image file,\n" + "which can be loaded using zipl or QEMU."; + +static gint pv_arg_compare(gconstpointer arg_1, gconstpointer arg_2) +{ + g_assert(arg_1); + g_assert(arg_2); + + PvComponentType a = ((PvArg *)arg_1)->type; + PvComponentType b = ((PvArg *)arg_2)->type; + + if (a < b) + return -1; + if (a == b) + return 0; + return 1; +} + +static gint pv_arg_has_type(gconstpointer arg, gconstpointer type) +{ + const PvArg *c = arg; + const PvComponentType *t = type; + + g_assert(arg); + + if (c->type == *t) + return 0; + if (c->type < *t) + return -1; + return 1; +} + +static gint pv_args_set_defaults(PvArgs *args, GError **err G_GNUC_UNUSED) +{ + if (!args->psw_addr) + args->psw_addr = + g_strdup_printf("0x%lx", DEFAULT_INITIAL_PSW_ADDR); + + return 0; +} + +static gint pv_args_validate_options(PvArgs *args, GError **err) +{ + PvComponentType KERNEL = PV_COMP_TYPE_KERNEL; + + if (args->unused_values->len > 0) { + g_autofree gchar *unused = NULL; + + for (gsize i = args->unused_values->len; i > 0; i--) { + g_autofree gchar *tmp = unused; + + unused = g_strjoin(" ", g_ptr_array_index(args->unused_values, i - 1), + tmp, + NULL); + } + + g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_INVALID_ARGUMENT, + _("Unrecognized arguments: '%s'.\nUse 'genprotimg --help' for more information"), + unused); + 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")); + return -1; + } + + if (!g_slist_find_custom(args->comps, &KERNEL, pv_arg_has_type)) { + g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, + _("Option '--image' is required.\nUse 'genprotimg --help' for more information")); + return -1; + } + + if (!args->host_keys || g_strv_length(args->host_keys) == 0) { + g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, + _("Option '--host-key-document' is required.\nUse 'genprotimg --help' for more information")); + return -1; + } + + if (!args->no_verify) { + g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, + _("Use the option '--no-verify' as the verification support is not available yet.")); + return -1; + } + + return 0; +} + +static gboolean cb_add_component(const gchar *option, const gchar *value, + PvArgs *args, GError **err) +{ + PvArg *comp = NULL; + gint type = -1; + + if (g_str_equal(option, "-i") || g_str_equal(option, "--image")) + type = PV_COMP_TYPE_KERNEL; + if (g_str_equal(option, "-r") || g_str_equal(option, "--ramdisk")) + type = PV_COMP_TYPE_INITRD; + if (g_str_equal(option, "-p") || g_str_equal(option, "--parmfile")) + type = PV_COMP_TYPE_CMDLINE; + + if (type < 0) { + g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX, + _("Invalid option '%s': "), option); + return FALSE; + } + + if (g_slist_find_custom(args->comps, &type, pv_arg_has_type)) { + g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX, + _("Multiple values for option '%s'"), option); + return FALSE; + } + + comp = pv_arg_new((PvComponentType)type, value); + args->comps = g_slist_insert_sorted(args->comps, comp, pv_arg_compare); + return TRUE; +} + +static gboolean cb_set_string_option(const gchar *option, const gchar *value, + PvArgs *args, GError **err) +{ + gchar **args_option = NULL; + + 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")) + args_option = &args->pcf; + if (g_str_equal(option, "--x-psw")) + args_option = &args->psw_addr; + if (g_str_equal(option, "--x-scf")) + args_option = &args->scf; + + if (!args_option) { + g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX, + _("Invalid option '%s': "), option); + return FALSE; + } + + if (*args_option) { + g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX, + _("Multiple values for option '%s'"), option); + return FALSE; + } + + *args_option = g_strdup(value); + 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) +{ + args->log_level++; + return TRUE; +} + +static gboolean cb_remaining_values(const gchar *option G_GNUC_UNUSED, + const gchar *value, PvArgs *args, + GError **err G_GNUC_UNUSED) +{ + g_ptr_array_add(args->unused_values, g_strdup(value)); + return TRUE; +} + +#define INDENT " " + +gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], + GError **err) +{ + g_autoptr(GOptionContext) context = NULL; + gboolean print_version = FALSE; + GOptionGroup *group, *x_group; + + g_autofree gchar *psw_desc = g_strdup_printf( + _("Load from the specified hexadecimal ADDRESS.\n" INDENT + "Optional; default: '0x%lx'."), + DEFAULT_INITIAL_PSW_ADDR); + GOptionEntry entries[] = { + { .long_name = "host-key-document", + .short_name = 'k', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_FILENAME_ARRAY, + .arg_data = &args->host_keys, + .description = + _("FILE specifies a host-key document. At least\n" INDENT + "one is required."), + .arg_description = _("FILE") }, + { .long_name = "output", + .short_name = 'o', + .flags = G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _("Set FILE as the output file."), + .arg_description = _("FILE") }, + { .long_name = "image", + .short_name = 'i', + .flags = G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_add_component, + .description = _("Use IMAGE as the Linux kernel image."), + .arg_description = _("IMAGE") }, + { .long_name = "ramdisk", + .short_name = 'r', + .flags = G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_add_component, + .description = _("Use RAMDISK as the initial RAM disk\n" INDENT + "(optional)."), + .arg_description = _("RAMDISK") }, + { .long_name = "parmfile", + .short_name = 'p', + .flags = G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_add_component, + .description = _("Use the kernel parameters stored in PARMFILE\n" INDENT + "(optional)."), + .arg_description = _("PARMFILE") }, + { .long_name = "no-verify", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &args->no_verify, + .description = _("Disable the host-key document verification\n" INDENT + "(optional)."), + .arg_description = NULL }, + { .long_name = "verbose", + .short_name = 'V', + .flags = G_OPTION_FLAG_NO_ARG, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_log_level, + .description = _("Provide more detailed output (optional)."), + .arg_description = NULL }, + { .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."), + .arg_description = NULL }, + { .long_name = G_OPTION_REMAINING, + .short_name = 0, + .flags = 0, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_remaining_values, + .description = NULL, + .arg_description = NULL }, + { 0 }, + }; + + 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, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _( + "Use FILE as the AES 256-bit XTS key\n" INDENT + "that is used for the component encryption.\n" INDENT + "Optional; default: auto-generated."), + .arg_description = _("FILE") }, + { .long_name = "x-header-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 AES 256-bit GCM header key\n" INDENT + "that protects the PV header.\n" INDENT + "Optional; default: auto-generated."), + .arg_description = _("FILE") }, + { .long_name = "x-pcf", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = + _("Specify the plaintext control flags\n" INDENT + "as a hexadecimal value.\n" INDENT + "Optional; default: '0x0'."), + .arg_description = _("VALUE") }, + { .long_name = "x-psw", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = psw_desc, + .arg_description = _("ADDRESS") }, + { .long_name = "x-scf", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _("Specify the secret control flags\n" INDENT + "as a hexadecimal value.\n" INDENT + "Optional; default: '0x0'."), + .arg_description = _("VALUE") }, + { 0 }, + }; + + context = g_option_context_new( + _("- Create a protected virtualization image")); + g_option_context_set_summary(context, _(summary)); + group = g_option_group_new(GETTEXT_PACKAGE, _("Application Options:"), + _("Show help options"), args, NULL); + g_option_group_add_entries(group, entries); + g_option_context_set_main_group(context, group); + + x_group = g_option_group_new("experimental", _("Experimental Options:"), + _("Show experimental options"), args, NULL); + g_option_group_add_entries(x_group, x_entries); + g_option_context_add_group(context, x_group); + if (!g_option_context_parse(context, argc, argv, err)) + return -1; + + if (print_version) { + g_printf(_("%s version %s\n"), tool_name, RELEASE_STRING); + g_printf("%s\n", copyright_notice); + exit(EXIT_SUCCESS); + } + + if (pv_args_set_defaults(args, err) < 0) + return -1; + + return pv_args_validate_options(args, err); +} + +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); + return g_steal_pointer(&args); +} + +void pv_args_free(PvArgs *args) +{ + if (!args) + return; + + g_free(args->pcf); + g_free(args->scf); + g_free(args->psw_addr); + g_free(args->cust_root_key_path); + g_free(args->cust_comm_key_path); + g_free(args->gcm_iv_path); + g_strfreev(args->host_keys); + g_free(args->xts_key_path); + g_slist_free_full(args->comps, (GDestroyNotify)pv_arg_free); + g_ptr_array_free(args->unused_values, TRUE); + g_free(args->output_path); + g_free(args->tmp_dir); + g_free(args); +} + +void pv_arg_free(PvArg *arg) +{ + if (!arg) + return; + + g_free(arg->path); + g_free(arg); +} +PvArg *pv_arg_new(PvComponentType type, const gchar *path) +{ + g_autoptr(PvArg) ret = g_new0(struct pv_arg, 1); + + ret->type = type; + ret->path = g_strdup(path); + return g_steal_pointer(&ret); +} diff --git a/genprotimg/src/pv/pv_args.h b/genprotimg/src/pv/pv_args.h new file mode 100644 index 0000000..f17e7b8 --- /dev/null +++ b/genprotimg/src/pv/pv_args.h @@ -0,0 +1,53 @@ +/* + * PV arguments related definitions and functions + * + * 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 PV_ARGS_H +#define PV_ARGS_H + +#include + +#include "pv_comp.h" + +typedef struct pv_arg { + PvComponentType type; + gchar *path; +} PvArg; + +PvArg *pv_arg_new(PvComponentType type, const gchar *path); +void pv_arg_free(PvArg *arg); + +typedef struct { + gint log_level; + gint no_verify; + gchar *pcf; + gchar *scf; + gchar *psw_addr; /* PSW address which will be used for the start of + * the actual component (e.g. Linux kernel) + */ + gchar *cust_root_key_path; + gchar *cust_comm_key_path; + gchar *gcm_iv_path; + gchar **host_keys; + gchar *xts_key_path; + GSList *comps; + gchar *output_path; + gchar *tmp_dir; + GPtrArray *unused_values; +} PvArgs; + +PvArgs *pv_args_new(void); +void pv_args_free(PvArgs *args); + +gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], + GError **err); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvArg, pv_arg_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvArgs, pv_args_free) + +#endif diff --git a/genprotimg/src/pv/pv_comp.c b/genprotimg/src/pv/pv_comp.c new file mode 100644 index 0000000..1f64eea --- /dev/null +++ b/genprotimg/src/pv/pv_comp.c @@ -0,0 +1,446 @@ +/* + * PV component related definitions and functions + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "boot/s390.h" +#include "common.h" +#include "utils/align.h" +#include "utils/buffer.h" +#include "utils/crypto.h" +#include "utils/file_utils.h" + +#include "pv_comp.h" +#include "pv_error.h" + +static void comp_file_free(CompFile *comp) +{ + if (!comp) + return; + + g_free(comp->path); + g_free(comp); +} + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(CompFile, comp_file_free) + +static PvComponent *pv_component_new(PvComponentType type, gsize size, + PvComponentDataType d_type, void **data, + GError **err) +{ + g_autoptr(PvComponent) ret = g_new0(PvComponent, 1); + + g_assert(type >= 0 && type <= UINT16_MAX); + + ret->type = (int)type; + ret->d_type = (int)d_type; + ret->data = g_steal_pointer(data); + ret->orig_size = size; + + if (generate_tweak(&ret->tweak, (uint16_t)type, err) < 0) + return NULL; + + return g_steal_pointer(&ret); +} + +PvComponent *pv_component_new_file(PvComponentType type, const gchar *path, + GError **err) +{ + g_autoptr(CompFile) file = g_new0(CompFile, 1); + gsize size; + gint rc; + + g_assert(path != NULL); + + rc = file_size(path, &size, err); + if (rc < 0) + return NULL; + + file->path = g_strdup(path); + file->size = size; + return pv_component_new(type, size, DATA_FILE, (void **)&file, err); +} + +PvComponent *pv_component_new_buf(PvComponentType type, const Buffer *buf, + GError **err) +{ + g_assert(buf); + + g_autoptr(Buffer) dup_buf = buffer_dup(buf, FALSE); + return pv_component_new(type, buf->size, DATA_BUFFER, (void **)&dup_buf, + err); +} + +void pv_component_free(PvComponent *component) +{ + if (!component) + return; + + switch ((PvComponentDataType)component->d_type) { + case DATA_BUFFER: + buffer_clear(&component->buf); + break; + case DATA_FILE: + comp_file_free(component->file); + break; + } + + g_free(component); +} + +gint pv_component_type(const PvComponent *component) +{ + return component->type; +} + +const gchar *pv_component_name(const PvComponent *component) +{ + gint type = pv_component_type(component); + + switch ((PvComponentType)type) { + case PV_COMP_TYPE_KERNEL: + return "kernel"; + case PV_COMP_TYPE_INITRD: + return "ramdisk"; + case PV_COMP_TYPE_CMDLINE: + return "parmline"; + case PV_COMP_TYPE_STAGE3B: + return "stage3b"; + } + + g_assert_not_reached(); +} + +uint64_t pv_component_size(const PvComponent *component) +{ + switch ((PvComponentDataType)component->d_type) { + case DATA_BUFFER: + return component->buf->size; + case DATA_FILE: + return component->file->size; + } + + g_assert_not_reached(); +} + +uint64_t pv_component_get_src_addr(const PvComponent *component) +{ + return component->src_addr; +} + +uint64_t pv_component_get_orig_size(const PvComponent *component) +{ + return component->orig_size; +} + +uint64_t pv_component_get_tweak_prefix(const PvComponent *component) +{ + return GUINT64_FROM_BE(component->tweak.cmp_idx.data); +} + +gboolean pv_component_is_stage3b(const PvComponent *component) +{ + return pv_component_type(component) == PV_COMP_TYPE_STAGE3B; +} + +gint pv_component_align_and_encrypt(PvComponent *component, const gchar *tmp_path, + void *opaque, GError **err) +{ + struct cipher_parms *parms = opaque; + + switch ((PvComponentDataType)component->d_type) { + case DATA_BUFFER: { + g_autoptr(Buffer) enc_buf = NULL; + + if (!(IS_PAGE_ALIGNED(pv_component_size(component)))) { + g_autoptr(Buffer) new = NULL; + + /* create a page aligned copy */ + new = buffer_dup(component->buf, TRUE); + buffer_clear(&component->buf); + component->buf = g_steal_pointer(&new); + } + enc_buf = encrypt_buf(parms, component->buf, err); + if (!enc_buf) + return -1; + + buffer_clear(&component->buf); + component->buf = g_steal_pointer(&enc_buf); + return 0; + } + case DATA_FILE: { + const gchar *comp_name = pv_component_name(component); + gchar *path_in = component->file->path; + g_autofree gchar *path_out = NULL; + gsize orig_size; + gsize prep_size; + + g_assert(path_in); + + path_out = g_build_filename(tmp_path, comp_name, NULL); + if (encrypt_file(parms, path_in, path_out, &orig_size, + &prep_size, err) < 0) + return -1; + + if (component->orig_size != orig_size) { + g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL, + _("File has changed during the preparation '%s'"), + path_out); + return -1; + } + + g_free(component->file->path); + component->file->size = prep_size; + component->file->path = g_steal_pointer(&path_out); + return 0; + } + } + + g_assert_not_reached(); +} + +/* Page align the size of the component */ +gint pv_component_align(PvComponent *component, const gchar *tmp_path, + void *opaque G_GNUC_UNUSED, GError **err) +{ + if (IS_PAGE_ALIGNED(pv_component_size(component))) + return 0; + + switch (component->d_type) { + case DATA_BUFFER: { + g_autoptr(Buffer) buf = NULL; + + buf = buffer_dup(component->buf, TRUE); + buffer_clear(&component->buf); + component->buf = g_steal_pointer(&buf); + return 0; + } break; + case DATA_FILE: { + const gchar *comp_name = pv_component_name(component); + g_autofree gchar *path_out = + g_build_filename(tmp_path, comp_name, NULL); + gchar *path_in = component->file->path; + gsize size_out; + + if (pad_file_right(path_out, path_in, &size_out, PAGE_SIZE, + err) < 0) + return -1; + + g_free(component->file->path); + component->file->path = g_steal_pointer(&path_out); + component->file->size = size_out; + return 0; + } break; + } + + g_assert_not_reached(); +} + +/* Convert uint64_t address to byte array */ +static void uint64_to_uint8_buf(uint8_t dst[8], uint64_t addr) +{ + uint8_t *p = (uint8_t *)&addr; + + g_assert(dst); + + for (gint i = 0; i < 8; i++) { + /* cppcheck-suppress objectIndex */ + dst[i] = p[i]; + } +} + +int64_t pv_component_update_ald(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err) +{ + uint64_t addr = pv_component_get_src_addr(comp); + uint64_t size = pv_component_size(comp); + uint64_t cur = addr; + int64_t nep = 0; + + g_assert(IS_PAGE_ALIGNED(size) && size != 0); + + do { + uint64_t cur_be = GUINT64_TO_BE(cur); + uint8_t addr_buf[8]; + + uint64_to_uint8_buf(addr_buf, cur_be); + + if (EVP_DigestUpdate(ctx, addr_buf, sizeof(addr_buf)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + return -1; + } + + cur += PAGE_SIZE; + nep++; + } while (cur < addr + size); + + return nep; +} + +int64_t pv_component_update_pld(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err) +{ + uint64_t size = pv_component_size(comp); + int64_t nep = 0; + + g_assert(IS_PAGE_ALIGNED(size) && size != 0); + + switch (comp->d_type) { + case DATA_BUFFER: { + const Buffer *buf = comp->buf; + + g_assert(buf->size <= INT64_MAX); + g_assert(buf->size == size); + + if (EVP_DigestUpdate(ctx, buf->data, buf->size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + return -1; + } + + nep = (int64_t)(buf->size / PAGE_SIZE); + break; + } + case DATA_FILE: { + const gchar *in_path = comp->file->path; + guchar in_buf[PAGE_SIZE]; + gsize num_bytes_read_total = 0; + gsize num_bytes_read = 0; + FILE *f_in; + + f_in = file_open(in_path, "rb", err); + if (!f_in) + return -1; + + do { + /* Read data in blocks. Update the digest + * context each read. + */ + if (file_read(f_in, in_buf, sizeof(*in_buf), + sizeof(in_buf), &num_bytes_read, + err) < 0) { + fclose(f_in); + return -1; + } + num_bytes_read_total += num_bytes_read; + + if (EVP_DigestUpdate(ctx, in_buf, sizeof(in_buf)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + fclose(f_in); + return -1; + } + + nep++; + } while (num_bytes_read_total < pv_component_size(comp) && + num_bytes_read != 0); + + if (num_bytes_read_total != pv_component_size(comp)) { + g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL, + _("'%s' has changed during the preparation"), + in_path); + fclose(f_in); + return -1; + } + fclose(f_in); + break; + } + default: + g_assert_not_reached(); + } + + return nep; +} + +int64_t pv_component_update_tld(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err) +{ + uint64_t size = pv_component_size(comp); + const union tweak *tweak = &comp->tweak; + g_autoptr(BIGNUM) tweak_num = NULL; + int64_t nep = 0; + + g_assert(IS_PAGE_ALIGNED(size) && size != 0); + + tweak_num = BN_bin2bn(tweak->data, sizeof(tweak->data), NULL); + if (!tweak_num) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_bin2bn failed")); + } + + for (uint64_t cur = 0; cur < size; cur += PAGE_SIZE) { + guchar tmp[sizeof(tweak->data)] = { 0 }; + + g_assert(BN_num_bytes(tweak_num) >= 0); + g_assert(sizeof(tmp) - (guint)BN_num_bytes(tweak_num) > 0); + + if (BN_bn2binpad(tweak_num, tmp, sizeof(tmp)) < 0) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_bn2binpad failed")); + } + + if (EVP_DigestUpdate(ctx, tmp, sizeof(tmp)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + return -1; + } + + /* calculate new tweak value */ + if (BN_add_word(tweak_num, PAGE_SIZE) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_add_word failed")); + } + + nep++; + } + + return nep; +} + +gint pv_component_write(const PvComponent *component, FILE *f, GError **err) +{ + uint64_t offset = pv_component_get_src_addr(component); + + g_assert(f); + + switch (component->d_type) { + case DATA_BUFFER: { + const Buffer *buf = component->buf; + + if (seek_and_write_buffer(f, buf, offset, err) < 0) + return -1; + + return 0; + } + case DATA_FILE: { + const CompFile *file = component->file; + + if (seek_and_write_file(f, file, offset, err) < 0) + return -1; + + return 0; + } + } + + g_assert_not_reached(); +} diff --git a/genprotimg/src/pv/pv_comp.h b/genprotimg/src/pv/pv_comp.h new file mode 100644 index 0000000..aa1b5ae --- /dev/null +++ b/genprotimg/src/pv/pv_comp.h @@ -0,0 +1,78 @@ +/* + * PV component related definitions and functions + * + * 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 PV_COMP_H +#define PV_COMP_H + +#include +#include +#include + +#include "utils/crypto.h" + +/* The order of this enum also implicitly defines the order of the + * components within the PV image! + */ +typedef enum { + PV_COMP_TYPE_KERNEL = 0, + PV_COMP_TYPE_CMDLINE = 1, + PV_COMP_TYPE_INITRD = 2, + PV_COMP_TYPE_STAGE3B = 3, +} PvComponentType; + +typedef enum { + DATA_FILE = 0, + DATA_BUFFER, +} PvComponentDataType; + +typedef struct comp_file { + gchar *path; + gsize size; +} CompFile; + +typedef struct { + gint type; /* PvComponentType */ + gint d_type; /* PvComponentDataType */ + union { + struct comp_file *file; + Buffer *buf; + void *data; + }; + uint64_t src_addr; + uint64_t orig_size; + union tweak tweak; /* used for the AES XTS encryption */ +} PvComponent; + +PvComponent *pv_component_new_file(PvComponentType type, const gchar *path, + GError **err); +PvComponent *pv_component_new_buf(PvComponentType type, const Buffer *buf, + GError **err); +void pv_component_free(PvComponent *component); +gint pv_component_type(const PvComponent *component); +const gchar *pv_component_name(const PvComponent *component); +uint64_t pv_component_size(const PvComponent *component); +uint64_t pv_component_get_src_addr(const PvComponent *component); +uint64_t pv_component_get_orig_size(const PvComponent *component); +uint64_t pv_component_get_tweak_prefix(const PvComponent *component); +gboolean pv_component_is_stage3b(const PvComponent *component); +gint pv_component_align_and_encrypt(PvComponent *component, const gchar *tmp_path, + void *opaque, GError **err); +gint pv_component_align(PvComponent *component, const gchar *tmp_path, + void *opaque G_GNUC_UNUSED, GError **err); +int64_t pv_component_update_pld(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err); +int64_t pv_component_update_ald(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err); +int64_t pv_component_update_tld(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err); +gint pv_component_write(const PvComponent *component, FILE *f, GError **err); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvComponent, pv_component_free) + +#endif diff --git a/genprotimg/src/pv/pv_comps.c b/genprotimg/src/pv/pv_comps.c new file mode 100644 index 0000000..15d32f0 --- /dev/null +++ b/genprotimg/src/pv/pv_comps.c @@ -0,0 +1,252 @@ +/* + * PV components related definitions and functions + * + * 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. + */ + +#include +#include +#include +#include + +#include "boot/s390.h" +#include "boot/stage3b.h" +#include "common.h" +#include "utils/align.h" +#include "utils/crypto.h" + +#include "pv_comp.h" +#include "pv_comps.h" +#include "pv_error.h" +#include "pv_stage3.h" + +struct _pv_img_comps { + gboolean finalized; + uint64_t next_src; + uint64_t nep; + EVP_MD_CTX *ald; /* context used for the hash of the addresses */ + EVP_MD_CTX *pld; /* context used for the hash of the pages content */ + EVP_MD_CTX *tld; /* context used for the hash of the tweaks */ + GSList *comps; /* elements sorted by component type */ +}; + +void pv_img_comps_free(PvImgComps *comps) +{ + if (!comps) + return; + + EVP_MD_CTX_free(comps->ald); + EVP_MD_CTX_free(comps->pld); + EVP_MD_CTX_free(comps->tld); + g_slist_free_full(comps->comps, (GDestroyNotify)pv_component_free); + g_free(comps); +} + +PvImgComps *pv_img_comps_new(const EVP_MD *ald_md, const EVP_MD *pld_md, + const EVP_MD *tld_md, GError **err) +{ + g_autoptr(PvImgComps) ret = g_new0(PvImgComps, 1); + + ret->ald = digest_ctx_new(ald_md, err); + if (!ret->ald) + return NULL; + + ret->pld = digest_ctx_new(pld_md, err); + if (!ret->pld) + return NULL; + + ret->tld = digest_ctx_new(tld_md, err); + if (!ret->tld) + return NULL; + + return g_steal_pointer(&ret); +} + +guint pv_img_comps_length(const PvImgComps *comps) +{ + return g_slist_length(comps->comps); +} + +/* Update hashes and nep */ +/* Returns 0 in case of success and -1 in case of a failure */ +static gint pv_img_comps_hash_comp(PvImgComps *comps, const PvComponent *comp, + GError **err) +{ + int64_t nep_1 = 0; + int64_t nep_2 = 0; + int64_t nep_3 = 0; + + /* update pld */ + nep_1 = pv_component_update_pld(comp, comps->pld, err); + if (nep_1 < 0) + return -1; + + /* update ald */ + nep_2 = pv_component_update_ald(comp, comps->ald, err); + if (nep_2 < 0) + return -1; + + /* update tld */ + nep_3 = pv_component_update_tld(comp, comps->tld, err); + if (nep_3 < 0) + return -1; + + g_assert(nep_1 == nep_2); + g_assert(nep_2 == nep_3); + + /* update comps->nep */ + g_assert_true(g_uint64_checked_add(&comps->nep, comps->nep, + (uint64_t)nep_1)); + return 0; +} + +gint pv_img_comps_add_component(PvImgComps *comps, PvComponent **comp, + GError **err) +{ + g_assert(comp); + g_assert(*comp); + g_assert(comps); + g_assert(IS_PAGE_ALIGNED(comps->next_src)); + + uint64_t src_addr = comps->next_src; + uint64_t src_size = pv_component_size(*comp) + ? PAGE_ALIGN(pv_component_size(*comp)) + : PAGE_SIZE; + + if (comps->finalized) { + g_set_error(err, PV_COMPONENT_ERROR, PV_COMPONENT_ERROR_FINALIZED, + _("Failed to add component, image is already finalized")); + return -1; + } + + /* set the address of the component in the memory layout */ + (*comp)->src_addr = src_addr; + + g_info("%12s:\t0x%012lx (%12ld / %12ld Bytes)", + pv_component_name(*comp), pv_component_get_src_addr(*comp), + pv_component_size(*comp), pv_component_get_orig_size(*comp)); + + /* append the component and pass the responsibility of @comp + * to @comps + */ + comps->comps = g_slist_append(comps->comps, g_steal_pointer(comp)); + comps->next_src += src_size; + + g_assert(IS_PAGE_ALIGNED(comps->next_src)); + g_assert(!*comp); + return 0; +} + +struct stage3b_args *pv_img_comps_get_stage3b_args(const PvImgComps *comps, + struct psw_t *psw) +{ + g_autofree struct stage3b_args *ret = g_new0(struct stage3b_args, 1); + + for (GSList *iterator = comps->comps; iterator; iterator = iterator->next) { + const PvComponent *img_comp = iterator->data; + uint64_t src_addr, dst_size; + + g_assert(img_comp); + + src_addr = pv_component_get_src_addr(img_comp); + dst_size = pv_component_get_orig_size(img_comp); + + g_assert(dst_size <= pv_component_size(img_comp)); + + switch ((PvComponentType)pv_component_type(img_comp)) { + case PV_COMP_TYPE_KERNEL: + memblob_init(&ret->kernel, src_addr, dst_size); + break; + case PV_COMP_TYPE_CMDLINE: + memblob_init(&ret->cmdline, src_addr, dst_size); + break; + case PV_COMP_TYPE_INITRD: + memblob_init(&ret->initrd, src_addr, dst_size); + break; + case PV_COMP_TYPE_STAGE3B: + /* nothing needs to be done since it is the + * stage3b itself + */ + break; + default: + g_assert_not_reached(); + break; + } + } + + /* for `stage3b_args` big-endian format must be used */ + ret->psw.mask = GUINT64_TO_BE(psw->mask); + ret->psw.addr = GUINT64_TO_BE(psw->addr); + return g_steal_pointer(&ret); +} + +gint pv_img_comps_set_offset(PvImgComps *comps, gsize offset, GError **err) +{ + g_assert(IS_PAGE_ALIGNED(comps->next_src)); + + if (!IS_PAGE_ALIGNED(offset)) { + g_set_error(err, PV_IMAGE_ERROR, PV_IMAGE_ERROR_OFFSET, + _("Offset must be page aligned")); + return -1; + } + + if (pv_img_comps_length(comps) > 0) { + g_set_error(err, PV_IMAGE_ERROR, PV_IMAGE_ERROR_OFFSET, + _("Offset cannot be changed after a component was added")); + return -1; + } + + comps->next_src += offset; + + g_assert(IS_PAGE_ALIGNED(comps->next_src)); + return 0; +} + +GSList *pv_img_comps_get_comps(const PvImgComps *comps) +{ + return comps->comps; +} + +gint pv_img_comps_finalize(PvImgComps *comps, Buffer **pld_digest, + Buffer **ald_digest, Buffer **tld_digest, + uint64_t *nep, GError **err) +{ + g_autoptr(Buffer) tmp_pld_digest = NULL; + g_autoptr(Buffer) tmp_ald_digest = NULL; + g_autoptr(Buffer) tmp_tld_digest = NULL; + + comps->finalized = TRUE; + for (GSList *iterator = comps->comps; iterator; iterator = iterator->next) { + const PvComponent *comp = iterator->data; + + /* update hashes and nep */ + if (pv_img_comps_hash_comp(comps, comp, err) < 0) + return -1; + } + + tmp_pld_digest = digest_ctx_finalize(comps->pld, err); + if (!tmp_pld_digest) + return -1; + + tmp_ald_digest = digest_ctx_finalize(comps->ald, err); + if (!tmp_ald_digest) + return -1; + + tmp_tld_digest = digest_ctx_finalize(comps->tld, err); + if (!tmp_tld_digest) + return -1; + + *pld_digest = g_steal_pointer(&tmp_pld_digest); + *ald_digest = g_steal_pointer(&tmp_ald_digest); + *tld_digest = g_steal_pointer(&tmp_tld_digest); + *nep = comps->nep; + return 0; +} + +PvComponent *pv_img_comps_get_nth_comp(PvImgComps *comps, guint n) +{ + return g_slist_nth_data(comps->comps, n); +} diff --git a/genprotimg/src/pv/pv_comps.h b/genprotimg/src/pv/pv_comps.h new file mode 100644 index 0000000..d555e36 --- /dev/null +++ b/genprotimg/src/pv/pv_comps.h @@ -0,0 +1,42 @@ +/* + * PV components related definitions and functions + * + * 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 PV_COMPS_H +#define PV_COMPS_H + +#include +#include +#include + +#include "boot/s390.h" +#include "boot/stage3b.h" +#include "utils/buffer.h" + +#include "pv_comp.h" + +typedef struct _pv_img_comps PvImgComps; + +PvImgComps *pv_img_comps_new(const EVP_MD *ald_md, const EVP_MD *pld_md, + const EVP_MD *tld_md, GError **err); +guint pv_img_comps_length(const PvImgComps *comps); +GSList *pv_img_comps_get_comps(const PvImgComps *comps); +struct stage3b_args *pv_img_comps_get_stage3b_args(const PvImgComps *comps, + struct psw_t *psw); +gint pv_img_comps_add_component(PvImgComps *comps, PvComponent **comp, + GError **err); +PvComponent *pv_img_comps_get_nth_comp(PvImgComps *comps, guint n); +gint pv_img_comps_set_offset(PvImgComps *comps, gsize offset, GError **err); +gint pv_img_comps_finalize(PvImgComps *comps, Buffer **pld_digest, + Buffer **ald_digest, Buffer **tld_digest, + uint64_t *nep, GError **err); +void pv_img_comps_free(PvImgComps *comps); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvImgComps, pv_img_comps_free) + +#endif diff --git a/genprotimg/src/pv/pv_error.c b/genprotimg/src/pv/pv_error.c new file mode 100644 index 0000000..9d6f264 --- /dev/null +++ b/genprotimg/src/pv/pv_error.c @@ -0,0 +1,37 @@ +/* + * PV error related functions + * + * 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. + */ + +#include + +#include "pv_error.h" + +GQuark pv_error_quark(void) +{ + return g_quark_from_static_string("pv-error-quark"); +} + +GQuark pv_crypto_error_quark(void) +{ + return g_quark_from_static_string("pv-crypto-error-quark"); +} + +GQuark pv_component_error_quark(void) +{ + return g_quark_from_static_string("pv-component-error-quark"); +} + +GQuark pv_image_error_quark(void) +{ + return g_quark_from_static_string("pv-image-error-quark"); +} + +GQuark pv_parse_error_quark(void) +{ + return g_quark_from_static_string("pv-parse-error-quark"); +} diff --git a/genprotimg/src/pv/pv_error.h b/genprotimg/src/pv/pv_error.h new file mode 100644 index 0000000..1dd24fc --- /dev/null +++ b/genprotimg/src/pv/pv_error.h @@ -0,0 +1,62 @@ +/* + * PV error related definitions and functions + * + * 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 PV_ERROR_H +#define PV_ERROR_H + +#include + +GQuark pv_error_quark(void); +GQuark pv_parse_error_quark(void); +GQuark pv_component_error_quark(void); +GQuark pv_crypto_error_quark(void); +GQuark pv_image_error_quark(void); + +#define PV_ERROR pv_error_quark() +#define PV_PARSE_ERROR pv_parse_error_quark() +#define PV_CRYPTO_ERROR pv_crypto_error_quark() +#define PV_COMPONENT_ERROR pv_component_error_quark() +#define PV_IMAGE_ERROR pv_image_error_quark() + +typedef enum { + PV_ERROR_IPIB_SIZE, + PV_ERROR_PV_HDR_SIZE, + PV_ERROR_INTERNAL, +} PvErrors; + +typedef enum { + PV_PARSE_ERROR_OK = 0, + PV_PARSE_ERROR_SYNTAX, + PR_PARSE_ERROR_INVALID_ARGUMENT, + PR_PARSE_ERROR_MISSING_ARGUMENT, +} PvParseErrors; + +typedef enum { + PV_COMPONENT_ERROR_UNALIGNED, + PV_COMPONENT_ERROR_FINALIZED, +} PvComponentErrors; + +typedef enum { + PV_IMAGE_ERROR_OFFSET, + PV_IMAGE_ERROR_FINALIZED, +} PvImageErrors; + +typedef enum { + PV_CRYPTO_ERROR_VERIFICATION, + PV_CRYPTO_ERROR_INIT, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + PV_CRYPTO_ERROR_INTERNAL, + PV_CRYPTO_ERROR_DERIVE, + PV_CRYPTO_ERROR_KEYGENERATION, + PV_CRYPTO_ERROR_RANDOMIZATION, + PV_CRYPTO_ERROR_INVALID_PARM, + PV_CRYPTO_ERROR_INVALID_KEY_SIZE, +} PvCryptoErrors; + +#endif diff --git a/genprotimg/src/pv/pv_hdr.c b/genprotimg/src/pv/pv_hdr.c new file mode 100644 index 0000000..45e721d --- /dev/null +++ b/genprotimg/src/pv/pv_hdr.c @@ -0,0 +1,293 @@ +/* + * PV header related functions + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "boot/s390.h" +#include "include/pv_crypto_def.h" +#include "utils/buffer.h" +#include "utils/crypto.h" + +#include "pv_comp.h" +#include "pv_hdr.h" +#include "pv_image.h" + +void pv_hdr_free(PvHdr *hdr) +{ + if (!hdr) + return; + + g_free(hdr->optional_items); + g_free(hdr->encrypted); + g_free(hdr->slots); + g_free(hdr); +} + +uint32_t pv_hdr_size(const PvHdr *hdr) +{ + return GUINT32_FROM_BE(hdr->head.phs); +} + +gboolean pv_hdr_uses_encryption(const PvHdr *hdr) +{ + return !(GUINT64_FROM_BE(hdr->head.pcf) & PV_CFLAG_NO_DECRYPTION); +} + +uint64_t pv_hdr_enc_size(const PvHdr *hdr) +{ + return GUINT64_FROM_BE(hdr->head.sea); +} + +uint32_t pv_hdr_enc_size_casted(const PvHdr *hdr) +{ + uint64_t size = pv_hdr_enc_size(hdr); + + if (size > UINT32_MAX) + g_abort(); + + return (uint32_t)size; +} + +static guint pv_hdr_tag_size(const PvHdr *hdr) +{ + return sizeof(hdr->tag); +} + +uint32_t pv_hdr_aad_size(const PvHdr *hdr) +{ + return pv_hdr_size(hdr) - pv_hdr_enc_size_casted(hdr) - + pv_hdr_tag_size(hdr); +} + +uint64_t pv_hdr_get_nks(const PvHdr *hdr) +{ + return GUINT64_FROM_BE(hdr->head.nks); +} + +/* In-place modification of ``buf`` */ +static gint pv_hdr_encrypt(const PvHdr *hdr, const PvImage *img, Buffer *buf, + GError **err) +{ + uint32_t hdr_len = pv_hdr_size(hdr); + uint32_t aad_len = pv_hdr_aad_size(hdr); + guint tag_len = pv_hdr_tag_size(hdr); + uint32_t enc_len = pv_hdr_enc_size_casted(hdr); + const Buffer aad_part = { .data = buf->data, .size = aad_len }; + Buffer enc_part = { .data = (uint8_t *)buf->data + aad_len, + .size = enc_len }; + Buffer tag_part = { .data = (uint8_t *)buf->data + hdr_len - tag_len, + .size = tag_len }; + struct cipher_parms parms; + int64_t c_len; + + g_assert(aad_part.size + enc_part.size + tag_part.size == buf->size); + g_assert(img->cust_root_key->size <= INT_MAX); + g_assert(img->gcm_iv->size <= INT_MAX); + g_assert(EVP_CIPHER_key_length(img->gcm_cipher) == + (int)img->cust_root_key->size); + g_assert(EVP_CIPHER_iv_length(img->gcm_cipher) == (int)img->gcm_iv->size); + + parms.key = img->cust_root_key; + parms.iv_or_tweak = img->gcm_iv; + parms.cipher = img->gcm_cipher; + + /* in-place encryption */ + c_len = gcm_encrypt(&enc_part, &aad_part, &parms, &enc_part, &tag_part, err); + if (c_len < 0) + return -1; + + g_assert(c_len == enc_len); + return 0; +} + +/* Initializes the unencrypted, but integrity protected part of the PV + * header + */ +static gint pv_hdr_aad_init(PvHdr *hdr, const PvImage *img, GError **err) +{ + g_autofree union ecdh_pub_key *cust_pub_key = NULL; + struct pv_hdr_key_slot *hdr_slot = hdr->slots; + struct pv_hdr_head *head = &hdr->head; + g_autoptr(Buffer) pld = NULL; + g_autoptr(Buffer) ald = NULL; + g_autoptr(Buffer) tld = NULL; + uint64_t nep = 0; + + g_assert(sizeof(head->iv) == img->gcm_iv->size); + g_assert(sizeof(head->cust_pub_key) == sizeof(*cust_pub_key)); + + cust_pub_key = evp_pkey_to_ecdh_pub_key(img->cust_pub_priv_key, err); + if (!cust_pub_key) + return -1; + + head->magic = GUINT64_TO_BE(PV_MAGIC_NUMBER); + head->version = GUINT32_TO_BE(PV_VERSION_1); + /* ``phs`` is already set so we can skip it here */ + memcpy(head->iv, img->gcm_iv->data, sizeof(head->iv)); + /* ``nks`` is already set so we can skip it here */ + /* ``sea`` is already set so we can skip it here */ + head->pcf = GUINT64_TO_BE(img->pcf); + memcpy(head->cust_pub_key.data, cust_pub_key, + sizeof(head->cust_pub_key)); + + if (pv_img_calc_pld_ald_tld_nep(img, &pld, &ald, &tld, &nep, err) < 0) + return -1; + + g_assert(sizeof(head->pld) == pld->size); + g_assert(sizeof(head->ald) == ald->size); + g_assert(sizeof(head->tld) == tld->size); + + head->nep = GUINT64_TO_BE(nep); + memcpy(head->pld, pld->data, sizeof(head->pld)); + memcpy(head->ald, ald->data, sizeof(head->ald)); + memcpy(head->tld, tld->data, sizeof(head->tld)); + + /* set the key slots */ + for (GSList *iterator = img->key_slots; iterator; iterator = iterator->next) { + const PvHdrKeySlot *slot = iterator->data; + + g_assert(slot); + + /* the memory for the slots is pre-allocated so we + * have not to allocate and since PvHdrKeySlot is + * stored in the big-edian format we can simply use + * memcpy. + */ + memcpy(hdr_slot++, slot, sizeof(*slot)); + } + + return 0; +} + +/* Initializes the encrypted and also integrity protected part of the + * PV header + */ +static gint pv_hdr_enc_init(PvHdr *hdr, const PvImage *img, GError **err) +{ + struct pv_hdr_encrypted *enc = hdr->encrypted; + const PvComponent *stage3b; + struct psw_t psw; + + g_assert(sizeof(enc->img_enc_key_1) + sizeof(enc->img_enc_key_2) == + EVP_CIPHER_key_length(img->xts_cipher)); + g_assert(sizeof(enc->cust_comm_key) == img->cust_comm_key->size); + g_assert(img->xts_key->size == + (guint)EVP_CIPHER_key_length(img->xts_cipher)); + + stage3b = pv_img_get_stage3b_comp(img, err); + if (!stage3b) + return -1; + + memcpy(enc->cust_comm_key, img->cust_comm_key->data, + sizeof(enc->cust_comm_key)); + memcpy(enc->img_enc_key_1, img->xts_key->data, + sizeof(enc->img_enc_key_1)); + memcpy(enc->img_enc_key_2, + (uint8_t *)img->xts_key->data + sizeof(enc->img_enc_key_1), + sizeof(enc->img_enc_key_2)); + + /* Setup program check handler */ + psw.mask = GUINT64_TO_BE(DEFAULT_INITIAL_PSW_MASK); + psw.addr = GUINT64_TO_BE(pv_component_get_src_addr(stage3b)); + enc->psw = psw; + enc->scf = GUINT64_TO_BE(img->scf); + enc->noi = GUINT32_TO_BE(g_slist_length(img->optional_items)); + + /* set the optional items */ + for (GSList *iterator = img->optional_items; iterator; + iterator = iterator->next) { + const struct pv_hdr_opt_item *item = iterator->data; + + g_assert(item); + + /* not supported in the first version */ + g_assert_not_reached(); + } + + return 0; +} + +PvHdr *pv_hdr_new(const PvImage *img, GError **err) +{ + uint32_t noi = g_slist_length(img->optional_items); + uint32_t hdr_size = pv_img_get_pv_hdr_size(img); + gsize nks = g_slist_length(img->key_slots); + uint32_t sea = pv_img_get_enc_size(img); + g_autoptr(PvHdr) ret = NULL; + + g_assert(nks > 0); + /* must be a multiple of AES block size */ + g_assert(sea % AES_BLOCK_SIZE == 0); + g_assert(sea >= sizeof(struct pv_hdr_encrypted)); + + ret = g_new0(PvHdr, 1); + ret->slots = g_new0(struct pv_hdr_key_slot, nks); + ret->head.phs = GUINT32_TO_BE(hdr_size); + ret->head.nks = GUINT64_TO_BE(nks); + ret->head.sea = GUINT64_TO_BE(sea); + + ret->encrypted = g_new0(struct pv_hdr_encrypted, 1); + ret->optional_items = g_malloc0(sea - sizeof(struct pv_hdr_encrypted)); + ret->encrypted->noi = GUINT32_TO_BE(noi); + + if (pv_hdr_aad_init(ret, img, err) < 0) + return NULL; + + if (pv_hdr_enc_init(ret, img, err) < 0) + return NULL; + + return g_steal_pointer(&ret); +} + +static void pv_hdr_memcpy(const PvHdr *hdr, const Buffer *dst) +{ + uint64_t nks = pv_hdr_get_nks(hdr); + uint8_t *data; + + g_assert(dst->size == pv_hdr_size(hdr)); + g_assert(pv_hdr_enc_size_casted(hdr) >= sizeof(*hdr->encrypted)); + + data = memcpy(dst->data, &hdr->head, sizeof(hdr->head)); + data = memcpy(data + sizeof(hdr->head), hdr->slots, + sizeof(struct pv_hdr_key_slot) * nks); + data = memcpy(data + sizeof(struct pv_hdr_key_slot) * nks, + hdr->encrypted, sizeof(*hdr->encrypted)); + if (pv_hdr_enc_size_casted(hdr) - sizeof(*hdr->encrypted) > 0) { + (void)memcpy(data + sizeof(*hdr->encrypted), + hdr->optional_items, + pv_hdr_enc_size_casted(hdr) - sizeof(*hdr->encrypted)); + } +} + +Buffer *pv_hdr_serialize(const PvHdr *hdr, const PvImage *img, + enum PvCryptoMode mode, GError **err) +{ + uint32_t hdr_size = pv_hdr_size(hdr); + g_autoptr(Buffer) ret = NULL; + + ret = buffer_alloc(hdr_size); + pv_hdr_memcpy(hdr, ret); + + if (mode == PV_ENCRYPT) { + /* The buffer @ret is modified in-place */ + if (pv_hdr_encrypt(hdr, img, ret, err) < 0) + return NULL; + } else { + /* Simply copy the tag */ + memcpy((uint8_t *)ret->data + hdr_size - pv_hdr_tag_size(hdr), + hdr->tag, pv_hdr_tag_size(hdr)); + } + + return g_steal_pointer(&ret); +} diff --git a/genprotimg/src/pv/pv_hdr.h b/genprotimg/src/pv/pv_hdr.h new file mode 100644 index 0000000..8df7a6f --- /dev/null +++ b/genprotimg/src/pv/pv_hdr.h @@ -0,0 +1,36 @@ +/* + * PV header related functions + * + * 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 PV_HDR_H +#define PV_HDR_H + +#include +#include + +#include "boot/s390.h" +#include "include/pv_hdr_def.h" +#include "utils/crypto.h" +#include "utils/buffer.h" + +#include "pv_image.h" + +PvHdr *pv_hdr_new(const PvImage *img, GError **err); +void pv_hdr_free(PvHdr *hdr); +G_GNUC_UNUSED gboolean pv_hdr_uses_encryption(const PvHdr *hdr); +Buffer *pv_hdr_serialize(const PvHdr *hdr, const PvImage *img, + enum PvCryptoMode mode, GError **err); +uint32_t pv_hdr_size(const PvHdr *hdr); +uint32_t pv_hdr_aad_size(const PvHdr *hdr); +uint64_t pv_hdr_enc_size(const PvHdr *hdr); +uint32_t pv_hdr_enc_size_casted(const PvHdr *hdr); +uint64_t pv_hdr_get_nks(const PvHdr *hdr); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvHdr, pv_hdr_free) + +#endif diff --git a/genprotimg/src/pv/pv_image.c b/genprotimg/src/pv/pv_image.c new file mode 100644 index 0000000..7ec5fe9 --- /dev/null +++ b/genprotimg/src/pv/pv_image.c @@ -0,0 +1,821 @@ +/* + * PV image related definitions and functions + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "boot/s390.h" +#include "boot/stage3a.h" +#include "common.h" +#include "include/pv_crypto_def.h" +#include "include/pv_hdr_def.h" +#include "utils/align.h" +#include "utils/crypto.h" +#include "utils/file_utils.h" + +#include "pv_args.h" +#include "pv_comps.h" +#include "pv_error.h" +#include "pv_hdr.h" +#include "pv_image.h" +#include "pv_ipib.h" +#include "pv_opt_item.h" +#include "pv_stage3.h" + +const PvComponent *pv_img_get_stage3b_comp(const PvImage *img, GError **err) +{ + const PvComponent *comp; + + g_return_val_if_fail(pv_img_comps_length(img->comps) >= 1, NULL); + + comp = pv_img_comps_get_nth_comp(img->comps, + pv_img_comps_length(img->comps) - 1); + if (!pv_component_is_stage3b(comp)) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Failed to get 'stage3b' component")); + return NULL; + } + return comp; +} + +typedef gint (*prepare_func)(PvComponent *obj, const gchar *tmp_path, + void *opaque, GError **err); + +static gint pv_img_prepare_component(const PvImage *img, PvComponent *comp, + GError **err) +{ + struct cipher_parms parms = { 0 }; + g_autoptr(Buffer) tweak = NULL; + prepare_func func = NULL; + void *opaque = NULL; + gint rc; + + if (img->pcf & PV_CFLAG_NO_DECRYPTION) { + /* we only need to align the components */ + func = pv_component_align; + opaque = NULL; + } else { + const EVP_CIPHER *cipher = img->xts_cipher; + + g_assert_cmpint((int)img->xts_key->size, ==, + EVP_CIPHER_key_length(cipher)); + g_assert_cmpint((int)PAGE_SIZE % EVP_CIPHER_block_size(cipher), + ==, 0); + g_assert_cmpint(sizeof(comp->tweak), ==, + EVP_CIPHER_iv_length(cipher)); + g_assert(img->xts_key->size <= UINT_MAX); + + tweak = buffer_alloc(sizeof(comp->tweak.data)); + memcpy(tweak->data, comp->tweak.data, tweak->size); + func = pv_component_align_and_encrypt; + parms.cipher = cipher; + parms.key = img->xts_key; + parms.iv_or_tweak = tweak; + + opaque = &parms; + } + + rc = (*func)(comp, img->tmp_dir, opaque, err); + if (rc < 0) + return -1; + + return 0; +} + +static Buffer *pv_img_read_key(const gchar *path, guint key_size, + GError **err) +{ + g_autoptr(Buffer) tmp_ret = NULL; + Buffer *ret = NULL; + gsize bytes_read; + FILE *f = NULL; + gsize size; + + if (file_size(path, &size, err) != 0) + return NULL; + + if (size - key_size != 0) { + g_set_error(err, PV_ERROR, PV_CRYPTO_ERROR_INVALID_KEY_SIZE, + _("Wrong file size '%s': read %zd, expected %u"), path, size, + key_size); + return NULL; + } + + f = file_open(path, "rb", err); + if (!f) + return NULL; + + tmp_ret = buffer_alloc(size); + if (file_read(f, tmp_ret->data, 1, tmp_ret->size, &bytes_read, err) < 0) + goto err; + + if (bytes_read - key_size != 0) { + g_set_error(err, PV_ERROR, PV_CRYPTO_ERROR_INVALID_KEY_SIZE, + _("Wrong file size '%s': read %zd, expected %u"), + path, bytes_read, key_size); + goto err; + } + + ret = g_steal_pointer(&tmp_ret); +err: + if (f) + fclose(f); + return ret; +} + +static EVP_PKEY *pv_img_get_cust_pub_priv_key(gint nid, GError **err) +{ + return generate_ec_key(nid, err); +} + +static HostKeyList *pv_img_get_host_keys(gchar **host_cert_paths, + X509_STORE *store, gint nid, + GError **err) +{ + g_autoslist(EVP_PKEY) ret = NULL; + + g_assert(host_cert_paths); + + for (gchar **iterator = host_cert_paths; iterator != NULL && *iterator != NULL; + iterator++) { + g_autoptr(EVP_PKEY) host_key = NULL; + const gchar *path = *iterator; + + g_assert(path); + + host_key = read_ec_pubkey_cert(store, nid, path, err); + if (!host_key) + return NULL; + + ret = g_slist_append(ret, g_steal_pointer(&host_key)); + } + + return g_steal_pointer(&ret); +} + +static Buffer *pv_img_get_key(const EVP_CIPHER *cipher, const gchar *path, + GError **err) +{ + gint key_len = EVP_CIPHER_key_length(cipher); + + g_assert(key_len > 0); + + if (path) + return pv_img_read_key(path, (guint)key_len, err); + + return generate_aes_key((guint)key_len, err); +} + +static Buffer *pv_img_get_iv(const EVP_CIPHER *cipher, const gchar *path, + GError **err) +{ + gint iv_len = EVP_CIPHER_iv_length(cipher); + + g_assert(iv_len > 0); + + if (path) + return pv_img_read_key(path, (guint)iv_len, err); + + return generate_aes_iv((guint)iv_len, err); +} + +static int hex_str_toull(const gchar *nptr, uint64_t *dst, + GError **err) +{ + uint64_t value; + gchar *end; + + g_assert(dst); + + if (!g_str_is_ascii(nptr)) { + g_set_error(err, PV_ERROR, EINVAL, + _("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"), + nptr); + return -1; + } + + value = g_ascii_strtoull(nptr, &end, 16); + if ((value == G_MAXUINT64 && errno == ERANGE) || + (end && *end != '\0')) { + g_set_error(err, PV_ERROR, EINVAL, + _("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"), + nptr); + return -1; + } + *dst = value; + return 0; +} + +static gint pv_img_set_psw_addr(PvImage *img, const gchar *psw_addr_s, + GError **err) +{ + if (psw_addr_s) { + uint64_t psw_addr; + + if (hex_str_toull(psw_addr_s, &psw_addr, err) < 0) + return -1; + + img->initial_psw.addr = psw_addr; + } + + return 0; +} + +static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s, + const gchar *scf_s, GError **err) +{ + uint64_t flags; + + if (pcf_s) { + if (hex_str_toull(pcf_s, &flags, err) < 0) + return -1; + + img->pcf = flags; + } + + if (scf_s) { + if (hex_str_toull(scf_s, &flags, err) < 0) + return -1; + + img->scf = flags; + } + + return 0; +} + +/* read in the keys or auto-generate them */ +static gint pv_img_set_keys(PvImage *img, const PvArgs *args, GError **err) +{ + g_autoptr(X509_STORE) store = NULL; + + g_assert(img->xts_cipher); + g_assert(img->cust_comm_cipher); + g_assert(img->gcm_cipher); + g_assert(img->nid); + + img->xts_key = pv_img_get_key(img->xts_cipher, args->xts_key_path, err); + if (!img->xts_key) + return -1; + + img->cust_comm_key = pv_img_get_key(img->cust_comm_cipher, + args->cust_comm_key_path, err); + if (!img->cust_comm_key) + return -1; + + img->cust_root_key = + pv_img_get_key(img->gcm_cipher, args->cust_root_key_path, err); + if (!img->cust_root_key) + return -1; + + img->gcm_iv = pv_img_get_iv(img->gcm_cipher, args->gcm_iv_path, err); + if (!img->gcm_iv) + return -1; + + img->cust_pub_priv_key = pv_img_get_cust_pub_priv_key(img->nid, err); + if (!img->cust_pub_priv_key) + return -1; + + img->host_pub_keys = + pv_img_get_host_keys(args->host_keys, store, img->nid, err); + if (!img->host_pub_keys) + return -1; + + return 0; +} + +static void pv_img_add_host_slot(PvImage *img, PvHdrKeySlot *slot) +{ + img->key_slots = g_slist_append(img->key_slots, slot); +} + +static void pv_hdr_key_slot_free(PvHdrKeySlot *slot) +{ + if (!slot) + return; + + g_free(slot); +} + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvHdrKeySlot, pv_hdr_key_slot_free) + +static PvHdrKeySlot *pv_hdr_key_slot_new(const EVP_CIPHER *gcm_cipher, + const Buffer *cust_root_key, + EVP_PKEY *cust_key, EVP_PKEY *host_key, + GError **err) +{ + g_autoptr(PvHdrKeySlot) ret = g_new0(PvHdrKeySlot, 1); + g_autofree union ecdh_pub_key *pub = NULL; + g_autoptr(Buffer) exchange_key = NULL; + g_autoptr(Buffer) digest_key = NULL; + g_autoptr(Buffer) iv = NULL; + Buffer pub_buf; + /* No AAD data is used */ + Buffer aad = { .data = NULL, .size = 0 }; + /* Set the output buffers for the encrypted data and the + * generated GCM tag + */ + Buffer enc = { .data = ret->wrapped_key, .size = sizeof(ret->wrapped_key) }; + Buffer tag = { .data = ret->tag, .size = sizeof(ret->tag) }; + struct cipher_parms parms; + int64_t c_len = 0; + + g_assert(EVP_CIPHER_iv_length(gcm_cipher) >= 0); + + pub = evp_pkey_to_ecdh_pub_key(host_key, err); + if (!pub) + return NULL; + + pub_buf.data = pub->data; + pub_buf.size = sizeof(*pub); + digest_key = sha256_buffer(&pub_buf, err); + if (!digest_key) + return NULL; + + g_assert(digest_key->size == sizeof(ret->digest_key)); + /* set `digest_key` field */ + memcpy(ret->digest_key, digest_key->data, sizeof(ret->digest_key)); + + exchange_key = compute_exchange_key(cust_key, host_key, err); + if (!exchange_key) + return NULL; + + /* initialize cipher parameters */ + g_assert(exchange_key->size <= INT_MAX); + g_assert(exchange_key->size == (guint)EVP_CIPHER_key_length(gcm_cipher)); + + /* create zero IV */ + iv = buffer_alloc((guint)EVP_CIPHER_iv_length(gcm_cipher)); + parms.iv_or_tweak = iv; + parms.key = exchange_key; + parms.cipher = gcm_cipher; + + /* Encrypt the customer root key that is used for the encryption + * of the PV header + */ + c_len = gcm_encrypt(cust_root_key, &aad, &parms, &enc, &tag, err); + if (c_len < 0) + return NULL; + + g_assert(c_len == (int64_t)cust_root_key->size); + return g_steal_pointer(&ret); +} + +static gint pv_img_set_host_slots(PvImage *img, GError **err) +{ + for (GSList *iterator = img->host_pub_keys; iterator; iterator = iterator->next) { + EVP_PKEY *host_key = iterator->data; + + g_assert(host_key); + + PvHdrKeySlot *slot = pv_hdr_key_slot_new(img->gcm_cipher, + img->cust_root_key, + img->cust_pub_priv_key, + host_key, err); + if (!slot) + return -1; + + pv_img_add_host_slot(img, slot); + } + + return 0; +} + +static gint pv_img_set_comps_offset(PvImage *img, uint64_t offset, GError **err) +{ + return pv_img_comps_set_offset(img->comps, offset, err); +} + +PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err) +{ + g_autoptr(PvImage) ret = g_new0(PvImage, 1); + uint64_t offset; + + g_assert(args->tmp_dir); + g_assert(stage3a_path); + + if (args->no_verify) + g_warning(_("host-key document verification is disabled. Your workload is not secured.")); + + ret->comps = pv_img_comps_new(EVP_sha512(), EVP_sha512(), EVP_sha512(), err); + if (!ret->comps) + return NULL; + + ret->cust_comm_cipher = EVP_aes_256_gcm(); + ret->gcm_cipher = EVP_aes_256_gcm(); + ret->initial_psw.addr = DEFAULT_INITIAL_PSW_ADDR; + ret->initial_psw.mask = DEFAULT_INITIAL_PSW_MASK; + ret->nid = NID_secp521r1; + ret->tmp_dir = g_strdup(args->tmp_dir); + ret->xts_cipher = EVP_aes_256_xts(); + + /* set initial PSW that will be loaded by the stage3b */ + if (pv_img_set_psw_addr(ret, args->psw_addr, err) < 0) + return NULL; + + /* set the control flags: PCF and SCF */ + if (pv_img_set_control_flags(ret, args->pcf, args->scf, err) < 0) + return NULL; + + /* read in the keys */ + if (pv_img_set_keys(ret, args, err) < 0) + return NULL; + + if (pv_img_set_host_slots(ret, err) < 0) + return NULL; + + /* allocate enough memory for the stage3a args and load the + * stage3a template into memory and set the loader_psw + */ + if (pv_img_load_and_set_stage3a(ret, stage3a_path, err) < 0) + return NULL; + + offset = PAGE_ALIGN(STAGE3A_LOAD_ADDRESS + ret->stage3a->size); + + /* shift right all components by the size of stage3a loader */ + if (pv_img_set_comps_offset(ret, offset, err) < 0) + return NULL; + + return g_steal_pointer(&ret); +} + +void pv_img_free(PvImage *img) +{ + if (!img) + return; + + g_slist_free_full(img->optional_items, + (GDestroyNotify)pv_opt_item_free); + g_slist_free_full(img->key_slots, (GDestroyNotify)pv_hdr_key_slot_free); + g_slist_free_full(img->host_pub_keys, (GDestroyNotify)EVP_PKEY_free); + EVP_PKEY_free(img->cust_pub_priv_key); + buffer_clear(&img->stage3a); + pv_img_comps_free(img->comps); + g_free(img->tmp_dir); + buffer_free(img->xts_key); + buffer_free(img->cust_root_key); + buffer_free(img->gcm_iv); + buffer_free(img->cust_comm_key); + g_free(img); +} + +static gint pv_img_prepare_and_add_component(PvImage *img, PvComponent **comp, + GError **err) +{ + g_assert(comp); + g_assert(*comp); + + /* prepares the component: does the alignment and encryption + * if required + */ + if (pv_img_prepare_component(img, *comp, err) < 0) + return -1; + + /* calculates the memory layout and adds the component to its + * internal list + */ + if (pv_img_comps_add_component(img->comps, comp, err) < 0) + return -1; + + g_assert(!*comp); + return 0; +} + +gint pv_img_add_component(PvImage *img, const PvArg *arg, GError **err) +{ + g_autoptr(PvComponent) comp = NULL; + + comp = pv_component_new_file(arg->type, arg->path, err); + if (!comp) + return -1; + + if (pv_img_prepare_and_add_component(img, &comp, err) < 0) + return -1; + + g_assert(!comp); + return 0; +} + +gint pv_img_calc_pld_ald_tld_nep(const PvImage *img, Buffer **pld, Buffer **ald, + Buffer **tld, uint64_t *nep, GError **err) +{ + return pv_img_comps_finalize(img->comps, pld, ald, tld, nep, err); +} + +static gint pv_img_build_stage3b(PvImage *img, Buffer *stage3b, GError **err) +{ + g_autofree struct stage3b_args *args = NULL; + + args = pv_img_comps_get_stage3b_args(img->comps, &img->initial_psw); + if (!args) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Cannot generate stage3b arguments")); + return -1; + } + + build_stage3b(stage3b, args); + return 0; +} + +gint pv_img_add_stage3b_comp(PvImage *img, const gchar *path, GError **err) +{ + g_autoptr(PvComponent) comp = NULL; + g_autoptr(Buffer) stage3b = NULL; + + stage3b = stage3b_getblob(path, err); + if (!stage3b) + return -1; + + /* set the stage3b data */ + if (pv_img_build_stage3b(img, stage3b, err) < 0) + return -1; + + comp = pv_component_new_buf(PV_COMP_TYPE_STAGE3B, stage3b, err); + if (!comp) + return -1; + + if (pv_img_prepare_and_add_component(img, &comp, err) < 0) + return -1; + + g_assert(!comp); + return 0; +} + +static uint32_t pv_img_get_aad_size(const PvImage *img) +{ + uint32_t key_size, size = 0; + + g_assert(sizeof(struct pv_hdr_head) <= UINT32_MAX); + g_assert(sizeof(struct pv_hdr_key_slot) <= UINT32_MAX); + + g_assert_true(g_uint_checked_add(&size, size, + (uint32_t)sizeof(struct pv_hdr_head))); + g_assert_true(g_uint_checked_mul(&key_size, + (uint32_t)sizeof(struct pv_hdr_key_slot), + g_slist_length(img->key_slots))); + g_assert_true(g_uint_checked_add(&size, size, key_size)); + return size; +} + +static uint32_t pv_img_get_opt_items_size(const PvImage *img) +{ + uint32_t ret = 0; + + g_assert(img); + + for (GSList *iterator = img->optional_items; iterator; + iterator = iterator->next) { + const struct pv_hdr_opt_item *item = iterator->data; + + g_assert(item); + g_assert_true(g_uint_checked_add(&ret, ret, pv_opt_item_size(item))); + } + return ret; +} + +uint32_t pv_img_get_enc_size(const PvImage *img) +{ + uint32_t ret = 0; + + g_assert(sizeof(struct pv_hdr_encrypted) <= UINT32_MAX); + + g_assert_true(g_uint_checked_add( + &ret, ret, (uint32_t)sizeof(struct pv_hdr_encrypted))); + g_assert_true( + g_uint_checked_add(&ret, ret, pv_img_get_opt_items_size(img))); + return ret; +} + +static uint32_t pv_img_get_tag_size(const PvImage *img G_GNUC_UNUSED) +{ + g_assert(sizeof(((struct pv_hdr *)0)->tag) <= UINT32_MAX); + + return (uint32_t)sizeof(((struct pv_hdr *)0)->tag); +} + +uint32_t pv_img_get_pv_hdr_size(const PvImage *img) +{ + uint32_t size = 0; + + g_assert_true( + g_uint_checked_add(&size, size, pv_img_get_aad_size(img))); + g_assert_true( + g_uint_checked_add(&size, size, pv_img_get_enc_size(img))); + g_assert_true( + g_uint_checked_add(&size, size, pv_img_get_tag_size(img))); + return size; +} + +static gint get_stage3a_data_size(const PvImage *img, gsize *data_size, + GError **err) +{ + gsize ipib_size, hdr_size; + + g_assert(data_size); + g_assert(*data_size == 0); + + ipib_size = pv_ipib_get_size(pv_img_comps_length(img->comps)); + if (ipib_size > PV_V1_IPIB_MAX_SIZE) { + g_set_error(err, PV_ERROR, PV_ERROR_IPIB_SIZE, + _("IPIB size is too large: '%zu' > '%zu'"), + ipib_size, PV_V1_IPIB_MAX_SIZE); + return -1; + } + + hdr_size = pv_img_get_pv_hdr_size(img); + if (hdr_size > PV_V1_PV_HDR_MAX_SIZE) { + g_set_error(err, PV_ERROR, PV_ERROR_PV_HDR_SIZE, + _("PV header size is too large: '%zu' > '%zu'"), + hdr_size, PV_V1_PV_HDR_MAX_SIZE); + return -1; + } + + *data_size += PAGE_ALIGN(ipib_size); + *data_size += PAGE_ALIGN(hdr_size); + return 0; +} + +gint pv_img_load_and_set_stage3a(PvImage *img, const gchar *path, GError **err) +{ + g_autoptr(Buffer) stage3a = NULL; + gsize bin_size, data_size = 0; + + if (get_stage3a_data_size(img, &data_size, err) < 0) + return -1; + + stage3a = stage3a_getblob(path, &bin_size, data_size, err); + if (!stage3a) + return -1; + + img->stage3a_psw.addr = STAGE3A_ENTRY; + img->stage3a_psw.mask = DEFAULT_INITIAL_PSW_MASK; + + /* set addresses and size */ + img->stage3a = g_steal_pointer(&stage3a); + img->stage3a_bin_size = bin_size; + return 0; +} + +/* Creates the PV IPIB and sets the stage3a arguments */ +static gint pv_img_build_stage3a(Buffer *stage3a, gsize stage3a_bin_size, + GSList *comps, const Buffer *hdr, GError **err) +{ + g_autofree struct ipl_parameter_block *ipib = NULL; + + g_assert(stage3a); + g_assert(hdr); + + ipib = pv_ipib_new(comps, hdr, err); + if (!ipib) + return -1; + + if (build_stage3a(stage3a, stage3a_bin_size, hdr, ipib, err) < 0) + return -1; + + g_info("%12s:\t0x%012lx (%12ld / %12ld Bytes)", "stage3a", + STAGE3A_LOAD_ADDRESS, stage3a->size, stage3a->size); + return 0; +} + +/* Creates the actual PV header (serialized and AES-GCM encrypted) */ +static Buffer *pv_img_create_pv_hdr(PvImage *img, GError **err) +{ + g_autoptr(Buffer) hdr_buf = NULL; + g_autoptr(PvHdr) hdr = NULL; + + hdr = pv_hdr_new(img, err); + if (!hdr) + return NULL; + + hdr_buf = pv_hdr_serialize(hdr, img, PV_ENCRYPT, err); + if (!hdr_buf) + return NULL; + + return g_steal_pointer(&hdr_buf); +} + +/* No changes to the components are allowed after calling this + * function + */ +gint pv_img_finalize(PvImage *pv, const gchar *stage3b_path, GError **err) +{ + g_autoptr(Buffer) hdr = NULL; + + /* load stage3b template into memory and add it to the list of + * components. This must be done before calling + * `pv_img_load_and_set_stage3a`. + */ + if (pv_img_add_stage3b_comp(pv, stage3b_path, err) < 0) + return -1; + + /* create the PV header */ + hdr = pv_img_create_pv_hdr(pv, err); + if (!hdr) + return -1; + + /* generate stage3a. At this point in time the PV header and + * the stage3b must be generated and encrypted + */ + if (pv_img_build_stage3a(pv->stage3a, pv->stage3a_bin_size, + pv_img_comps_get_comps(pv->comps), hdr, err) < 0) + return -1; + + return 0; +} + +static gint convert_psw_to_short_psw(const struct psw_t *psw, uint64_t *dst, + GError **err) +{ + g_assert(psw); + g_assert(dst); + + uint64_t psw_addr = psw->addr; + uint64_t psw_mask = psw->mask; + + /* test if PSW mask can be converted */ + if (psw_mask & PSW32_ADDR_MASK) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Failed to convert PSW to short PSW")); + return -1; + } + + /* test for bit 12 */ + if (psw_mask & PSW_MASK_BIT_12) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Failed to convert PSW to short PSW")); + return -1; + } + + /* test if PSW addr can be converted */ + if (psw_addr & ~PSW32_ADDR_MASK) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Failed to convert PSW to short PSW")); + return -1; + } + + *dst = psw_mask; + /* set bit 12 to 1 */ + *dst |= PSW_MASK_BIT_12; + *dst |= psw_addr; + return 0; +} + +static gint write_short_psw(FILE *f, struct psw_t *psw, GError **err) +{ + uint64_t short_psw, short_psw_be; + + if (convert_psw_to_short_psw(psw, &short_psw, err) < 0) + return -1; + + short_psw_be = GUINT64_TO_BE(short_psw); + return file_write(f, &short_psw_be, 1, sizeof(short_psw_be), NULL, err); +} + +gint pv_img_write(PvImage *img, const gchar *path, GError **err) +{ + gint ret = -1; + FILE *f = file_open(path, "wb", err); + + if (!f) + return -1; + + if (write_short_psw(f, &img->stage3a_psw, err) < 0) { + g_prefix_error(err, _("Failed to write image '%s': "), path); + goto err; + } + + if (seek_and_write_buffer(f, img->stage3a, STAGE3A_LOAD_ADDRESS, err) < + 0) { + g_prefix_error(err, _("Failed to write image '%s': "), path); + goto err; + } + + /* list is sorted by component type => by address */ + for (GSList *iterator = pv_img_comps_get_comps(img->comps); iterator; + iterator = iterator->next) { + gint rc; + const PvComponent *comp = iterator->data; + + rc = pv_component_write(comp, f, err); + if (rc < 0) { + g_prefix_error(err, _("Failed to write image '%s': "), + path); + goto err; + } + } + + ret = 0; +err: + if (f) + fclose(f); + return ret; +} diff --git a/genprotimg/src/pv/pv_image.h b/genprotimg/src/pv/pv_image.h new file mode 100644 index 0000000..7c624e2 --- /dev/null +++ b/genprotimg/src/pv/pv_image.h @@ -0,0 +1,68 @@ +/* + * PV image related definitions and functions + * + * 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 PV_IMAGE_H +#define PV_IMAGE_H + +#include +#include +#include +#include + +#include "boot/s390.h" +#include "utils/buffer.h" + +#include "pv_args.h" +#include "pv_comp.h" +#include "pv_comps.h" +#include "pv_stage3.h" + +typedef struct { + gchar *tmp_dir; /* directory used for temporary files */ + Buffer *stage3a; /* stage3a containing IPIB and PV header */ + gsize stage3a_bin_size; /* size of stage3a.bin */ + struct psw_t stage3a_psw; /* (short) PSW that is written to + * location 0 of the created image + */ + struct psw_t initial_psw; /* PSW loaded by stage3b */ + EVP_PKEY *cust_pub_priv_key; /* customer private/public key */ + GSList *host_pub_keys; /* public host keys */ + gint nid; /* Elliptic Curve used for the key derivation */ + /* keys and cipher used for the AES-GCM encryption */ + Buffer *cust_root_key; + Buffer *gcm_iv; + const EVP_CIPHER *gcm_cipher; + /* Information for the IPIB and PV header */ + uint64_t pcf; + uint64_t scf; + Buffer *cust_comm_key; + const EVP_CIPHER *cust_comm_cipher; + Buffer *xts_key; + const EVP_CIPHER *xts_cipher; + GSList *key_slots; + GSList *optional_items; + PvImgComps *comps; +} PvImage; + +PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err); +void pv_img_free(PvImage *img); +gint pv_img_add_component(PvImage *img, const PvArg *arg, GError **err); +gint pv_img_finalize(PvImage *img, const gchar *stage3b_path, GError **err); +gint pv_img_calc_pld_ald_tld_nep(const PvImage *img, Buffer **pld, Buffer **ald, + Buffer **tld, uint64_t *nep, GError **err); +gint pv_img_load_and_set_stage3a(PvImage *img, const gchar *path, GError **err); +const PvComponent *pv_img_get_stage3b_comp(const PvImage *img, GError **err); +gint pv_img_add_stage3b_comp(PvImage *img, const gchar *path, GError **err); +uint32_t pv_img_get_enc_size(const PvImage *img); +uint32_t pv_img_get_pv_hdr_size(const PvImage *img); +gint pv_img_write(PvImage *img, const gchar *path, GError **err); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvImage, pv_img_free) + +#endif diff --git a/genprotimg/src/pv/pv_ipib.c b/genprotimg/src/pv/pv_ipib.c new file mode 100644 index 0000000..2517e54 --- /dev/null +++ b/genprotimg/src/pv/pv_ipib.c @@ -0,0 +1,128 @@ +/* + * PV IPIB related definitions and functions + * + * 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. + */ + +#include +#include +#include +#include + +#include "boot/ipl.h" +#include "boot/s390.h" +#include "common.h" +#include "include/pv_hdr_def.h" +#include "lib/zt_common.h" +#include "utils/align.h" +#include "utils/buffer.h" + +#include "pv_comp.h" +#include "pv_error.h" +#include "pv_ipib.h" + +uint64_t pv_ipib_get_size(uint32_t num_comp) +{ + gsize ipib_size = sizeof(struct ipl_pl_hdr) + + sizeof(struct ipl_pb0_pv) + + num_comp * sizeof(struct ipl_pb0_pv_comp); + + /* the minimal size is one page */ + return MAX(ipib_size, PAGE_SIZE); +} + +static gint pv_ipib_init(IplParameterBlock *ipib, GSList *comps, + const Buffer *hdr) +{ + g_assert(sizeof(struct ipl_pl_hdr) <= UINT32_MAX); + g_assert(sizeof(struct ipl_pb0_pv_comp) <= UINT32_MAX); + g_assert(sizeof(struct ipl_pb0_pv) <= UINT32_MAX); + g_assert(ipib); + + guint comps_length = g_slist_length(comps); + uint32_t ipl_pl_hdr_size = (uint32_t)sizeof(struct ipl_pl_hdr); + struct ipl_pb0_pv *pv = &ipib->pv; + uint32_t ipib_comps_size; + uint32_t blk0_len; + uint32_t ipib_size; + gsize i; + + g_assert_true( + g_uint_checked_mul(&ipib_comps_size, comps_length, + (uint32_t)sizeof(struct ipl_pb0_pv_comp))); + g_assert_true(g_uint_checked_add(&blk0_len, (uint32_t)sizeof(*pv), + ipib_comps_size)); + g_assert(ipl_pl_hdr_size + blk0_len <= PAGE_SIZE); + + ipib_size = MAX(ipl_pl_hdr_size + blk0_len, (uint32_t)PAGE_SIZE); + g_assert(pv_ipib_get_size(comps_length) == ipib_size); + + pv->pbt = IPL_TYPE_PV; + pv->len = GUINT32_TO_BE(blk0_len); + pv->num_comp = GUINT32_TO_BE(comps_length); + /* both values will be overwritten during the IPL process by + * the stage3a loader + */ + pv->pv_hdr_addr = GUINT64_TO_BE(0x0); + pv->pv_hdr_size = GUINT64_TO_BE(hdr->size); + + ipib->hdr.len = GUINT32_TO_BE(ipib_size); + ipib->hdr.version = IPL_PARM_BLOCK_VERSION; + + i = 0; + for (GSList *iterator = comps; iterator; iterator = iterator->next, i++) { + const PvComponent *comp = iterator->data; + uint64_t comp_addr, comp_size; + + g_assert(comp); + + comp_addr = pv_component_get_src_addr(comp); + comp_size = pv_component_size(comp); + + g_assert(IS_PAGE_ALIGNED(comp_size)); + + pv->components[i].addr = GUINT64_TO_BE(comp_addr); + pv->components[i].len = GUINT64_TO_BE(comp_size); + pv->components[i].tweak_pref = + GUINT64_TO_BE(pv_component_get_tweak_prefix(comp)); + if (i > 0) { + /* tweak prefixes of the components must grow + * strictly monotonous + */ + g_assert(GUINT64_FROM_BE(pv->components[i].tweak_pref) > + GUINT64_FROM_BE(pv->components[i - 1].tweak_pref)); + } + } + + return 0; +} + +IplParameterBlock *pv_ipib_new(GSList *comps, const Buffer *hdr, GError **err) +{ + uint64_t ipib_size = pv_ipib_get_size(g_slist_length(comps)); + g_autoptr(IplParameterBlock) ret = NULL; + + if (ipib_size > PV_V1_IPIB_MAX_SIZE) { + g_set_error(err, PV_ERROR, PV_ERROR_IPIB_SIZE, + _("IPIB size is too large: %lu < %lu"), ipib_size, + PAGE_SIZE); + return NULL; + } + + ret = g_malloc0(ipib_size); + if (pv_ipib_init(ret, comps, hdr) < 0) + return NULL; + + return g_steal_pointer(&ret); +} + +void pv_ipib_free(IplParameterBlock *ipib) +{ + if (!ipib) + return; + + g_free(ipib); +} diff --git a/genprotimg/src/pv/pv_ipib.h b/genprotimg/src/pv/pv_ipib.h new file mode 100644 index 0000000..9331790 --- /dev/null +++ b/genprotimg/src/pv/pv_ipib.h @@ -0,0 +1,27 @@ +/* + * PV IPIB related definitions and functions + * + * 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 PV_IPIB_H +#define PV_IPIB_H + +#include +#include + +#include "boot/ipl.h" +#include "utils/buffer.h" + +typedef struct ipl_parameter_block IplParameterBlock; + +uint64_t pv_ipib_get_size(uint32_t num_comp); +IplParameterBlock *pv_ipib_new(GSList *comps, const Buffer *hdr, GError **err); +void pv_ipib_free(IplParameterBlock *ipib); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(IplParameterBlock, pv_ipib_free) + +#endif diff --git a/genprotimg/src/pv/pv_opt_item.c b/genprotimg/src/pv/pv_opt_item.c new file mode 100644 index 0000000..39b9a8d --- /dev/null +++ b/genprotimg/src/pv/pv_opt_item.c @@ -0,0 +1,26 @@ +/* + * PV optional item related definitions and functions + * + * 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. + */ + +#include + +#include "pv_opt_item.h" + +uint32_t pv_opt_item_size(const struct pv_hdr_opt_item *item G_GNUC_UNUSED) +{ + /* not implemented yet */ + g_assert_not_reached(); +} + +void pv_opt_item_free(struct pv_hdr_opt_item *item) +{ + if (!item) + return; + + g_free(item); +} diff --git a/genprotimg/src/pv/pv_opt_item.h b/genprotimg/src/pv/pv_opt_item.h new file mode 100644 index 0000000..34bb0a9 --- /dev/null +++ b/genprotimg/src/pv/pv_opt_item.h @@ -0,0 +1,20 @@ +/* + * PV optional item related definitions and functions + * + * 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 PV_OPT_ITEM_H +#define PV_OPT_ITEM_H + +#include + +#include "include/pv_hdr_def.h" + +uint32_t pv_opt_item_size(const struct pv_hdr_opt_item *item); +void pv_opt_item_free(struct pv_hdr_opt_item *item); + +#endif diff --git a/genprotimg/src/pv/pv_stage3.c b/genprotimg/src/pv/pv_stage3.c new file mode 100644 index 0000000..a1e5b16 --- /dev/null +++ b/genprotimg/src/pv/pv_stage3.c @@ -0,0 +1,164 @@ +/* + * PV stage3 loader related definitions and functions + * + * 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. + */ + +#include +#include +#include + +#include "boot/ipl.h" +#include "boot/stage3a.h" +#include "boot/stage3b.h" +#include "common.h" +#include "utils/align.h" + +#include "pv_error.h" +#include "pv_stage3.h" + +#define STAGE3A_ARGS(data_ptr, loader_size) \ + ((struct stage3a_args *)((uint64_t)data_ptr + loader_size - \ + sizeof(struct stage3a_args))) + +static Buffer *loader_getblob(const gchar *filename, gsize *loader_size, + gsize args_size, gsize data_size, + gboolean data_aligned, GError **err) +{ + g_autoptr(GMappedFile) mapped_file = NULL; + g_autoptr(Buffer) ret = NULL; + gsize size, tmp_loader_size; + gchar *loader_data; + + g_assert(loader_size); + + mapped_file = g_mapped_file_new(filename, FALSE, err); + if (!mapped_file) + return NULL; + + loader_data = g_mapped_file_get_contents(mapped_file); + if (!loader_data) { + g_set_error(err, G_FILE_ERROR, G_FILE_ERROR_BADF, + _("File '%s' is empty"), filename); + return NULL; + } + tmp_loader_size = g_mapped_file_get_length(mapped_file); + + if (tmp_loader_size < args_size) { + g_set_error(err, G_FILE_ERROR, G_FILE_ERROR_BADF, + _("File size less than expected: %lu < %ln"), + tmp_loader_size, loader_size); + return NULL; + } + + /* For example, the PV header and IPIB data must be page + * aligned. + */ + size = (data_aligned ? PAGE_ALIGN(tmp_loader_size) : tmp_loader_size) + + data_size; + + ret = buffer_alloc(size); + + /* copy the loader "template" */ + memcpy(ret->data, loader_data, tmp_loader_size); + /* reset our dummy data (offsets and length) to zeros */ + memset((uint8_t *)ret->data + tmp_loader_size - args_size, 0, + args_size); + *loader_size = tmp_loader_size; + return g_steal_pointer(&ret); +} + +Buffer *stage3a_getblob(const gchar *filename, gsize *loader_size, + gsize data_size, GError **err) +{ + return loader_getblob(filename, loader_size, + sizeof(struct stage3a_args), data_size, TRUE, + err); +} + +/* For the memory layout see stage3a.lds */ +/* Set the right offsets and sizes in the stage3a template + add + * the IPIB block with the PV header + */ +static gint stage3a_set_data(Buffer *loader, gsize loader_size, + const Buffer *hdr, struct ipl_parameter_block *ipib, + GError **err) +{ + uint32_t ipib_size = GUINT32_FROM_BE(ipib->hdr.len); + gsize args_size = sizeof(struct stage3a_args); + uint32_t hdr_size = (uint32_t)hdr->size; + uint64_t args_addr, next_data_addr; + + if (hdr->size > UINT32_MAX) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Invalid header size: %zu"), hdr->size); + return -1; + } + + /* we assume here that the loader ``stage3a`` is loaded page + * aligned in the guest + */ + args_addr = (uint64_t)loader->data + loader_size - args_size; + + /* therefore `next_data_addr` is also page aligned */ + next_data_addr = (uint64_t)loader->data + PAGE_ALIGN(loader_size); + + /* copy IPIB data */ + memcpy((void *)next_data_addr, ipib, ipib_size); + + /* set IPIB offset in relation to the stage3a arguments */ + STAGE3A_ARGS(loader->data, loader_size)->ipib_offs = + GUINT64_TO_BE(next_data_addr - args_addr); + + next_data_addr = next_data_addr + PAGE_ALIGN(ipib_size); + /* copy PV header */ + memcpy((void *)next_data_addr, hdr->data, hdr_size); + /* set PV header size and offset in relation to the stage3a + * arguments + */ + STAGE3A_ARGS(loader->data, loader_size)->hdr_offs = + GUINT64_TO_BE(next_data_addr - args_addr); + STAGE3A_ARGS(loader->data, loader_size)->hdr_size = GUINT64_TO_BE(hdr_size); + + return 0; +} + +gint build_stage3a(Buffer *loader, gsize loader_size, const Buffer *hdr, + struct ipl_parameter_block *ipib, GError **err) +{ + return stage3a_set_data(loader, loader_size, hdr, ipib, err); +} + +Buffer *stage3b_getblob(const gchar *filename, GError **err) +{ + g_autoptr(Buffer) ret = NULL; + gsize rb_size; + + ret = loader_getblob(filename, &rb_size, sizeof(struct stage3b_args), 0, + FALSE, err); + if (!ret) + return NULL; + + g_assert(ret->size == rb_size); + return g_steal_pointer(&ret); +} + +void build_stage3b(Buffer *stage3b, const struct stage3b_args *args) +{ + g_assert(stage3b->size > sizeof(*args)); + + /* at the end of the stage3b there are the stage3b args + * positioned + */ + memcpy((uint8_t *)stage3b->data + stage3b->size - sizeof(*args), args, + sizeof(*args)); +} + +void memblob_init(struct memblob *arg, uint64_t src, uint64_t size) +{ + arg->src = GUINT64_TO_BE(src); + arg->size = GUINT64_TO_BE(size); +} diff --git a/genprotimg/src/pv/pv_stage3.h b/genprotimg/src/pv/pv_stage3.h new file mode 100644 index 0000000..baaf921 --- /dev/null +++ b/genprotimg/src/pv/pv_stage3.h @@ -0,0 +1,30 @@ +/* + * PV stage3 loader related definitions and functions + * + * 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 PV_STAGE3_H +#define PV_STAGE3_H + +#include +#include +#include + +#include "boot/ipl.h" +#include "boot/s390.h" +#include "boot/stage3b.h" +#include "utils/buffer.h" + +Buffer *stage3a_getblob(const gchar *filename, gsize *loader_size, + gsize data_size, GError **err); +gint build_stage3a(Buffer *dc, gsize dc_size, const Buffer *hdr, + struct ipl_parameter_block *ipib, GError **err); +Buffer *stage3b_getblob(const gchar *filename, GError **err); +void build_stage3b(Buffer *stage3b, const struct stage3b_args *args); +void memblob_init(struct memblob *arg, uint64_t src, uint64_t size); + +#endif diff --git a/genprotimg/src/utils/align.h b/genprotimg/src/utils/align.h new file mode 100644 index 0000000..fa19e58 --- /dev/null +++ b/genprotimg/src/utils/align.h @@ -0,0 +1,24 @@ +/* + * Alignment 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. + */ + +#ifndef PV_UTILS_ALIGN_H +#define PV_UTILS_ALIGN_H + +#include "boot/s390.h" +#include "lib/zt_common.h" + +#define IS_ALIGNED(addr, size) (!(addr & (size - 1))) + +/* align addr to the next page boundary */ +#define PAGE_ALIGN(addr) ALIGN((unsigned long)addr, PAGE_SIZE) + +/* test whether an address is aligned to PAGE_SIZE or not */ +#define IS_PAGE_ALIGNED(addr) IS_ALIGNED((unsigned long)(addr), PAGE_SIZE) + +#endif diff --git a/genprotimg/src/utils/buffer.c b/genprotimg/src/utils/buffer.c new file mode 100644 index 0000000..35aed74 --- /dev/null +++ b/genprotimg/src/utils/buffer.c @@ -0,0 +1,69 @@ +/* + * Buffer functions + * + * 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. + */ + +#include +#include +#include +#include + +#include "align.h" +#include "buffer.h" +#include "common.h" +#include "file_utils.h" + +Buffer *buffer_alloc(gsize size) +{ + Buffer *ret = g_new0(Buffer, 1); + + ret->data = g_malloc0(size); + ret->size = size; + return ret; +} + +Buffer *buffer_dup(const Buffer *buf, gboolean page_aligned) +{ + Buffer *ret; + gsize size; + + if (!buf) + return NULL; + + size = buf->size; + if (page_aligned) + size = PAGE_ALIGN(size); + + ret = buffer_alloc(size); + + /* content will be 0-right-padded */ + memcpy(ret->data, buf->data, buf->size); + return ret; +} + +gint buffer_write(const Buffer *buf, FILE *file, GError **err) +{ + return file_write(file, buf->data, buf->size, 1, NULL, err); +} + +void buffer_free(Buffer *buf) +{ + if (!buf) + return; + + g_free(buf->data); + g_free(buf); +} + +void buffer_clear(Buffer **buf) +{ + if (!buf || !*buf) + return; + + buffer_free(*buf); + *buf = NULL; +} diff --git a/genprotimg/src/utils/buffer.h b/genprotimg/src/utils/buffer.h new file mode 100644 index 0000000..7239d5c --- /dev/null +++ b/genprotimg/src/utils/buffer.h @@ -0,0 +1,31 @@ +/* + * Buffer definition and functions + * + * 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 PV_UTILS_BUFFER_H +#define PV_UTILS_BUFFER_H + +#include +#include + +#include "common.h" + +typedef struct Buffer { + void *data; + gsize size; /* in bytes */ +} Buffer; + +Buffer *buffer_alloc(gsize size); +void buffer_free(Buffer *buf); +void buffer_clear(Buffer **buf); +gint buffer_write(const Buffer *buf, FILE *file, GError **err); +Buffer *buffer_dup(const Buffer *buf, gboolean page_aligned); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(Buffer, buffer_free) + +#endif diff --git a/genprotimg/src/utils/crypto.c b/genprotimg/src/utils/crypto.c new file mode 100644 index 0000000..b0c4aa9 --- /dev/null +++ b/genprotimg/src/utils/crypto.c @@ -0,0 +1,798 @@ +/* + * General cryptography helper functions + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "boot/s390.h" +#include "common.h" +#include "include/pv_crypto_def.h" +#include "pv/pv_error.h" + +#include "buffer.h" +#include "crypto.h" + +EVP_MD_CTX *digest_ctx_new(const EVP_MD *md, GError **err) +{ + g_autoptr(EVP_MD_CTX) ctx = EVP_MD_CTX_new(); + + if (!ctx) + g_abort(); + + if (EVP_DigestInit_ex(ctx, md, NULL) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestInit_ex failed")); + return NULL; + } + + return g_steal_pointer(&ctx); +} + +Buffer *digest_ctx_finalize(EVP_MD_CTX *ctx, GError **err) +{ + gint md_size = EVP_MD_size(EVP_MD_CTX_md(ctx)); + g_autoptr(Buffer) ret = NULL; + guint digest_size; + + g_assert(md_size > 0); + + ret = buffer_alloc((guint)md_size); + if (EVP_DigestFinal_ex(ctx, ret->data, &digest_size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestFinal_ex failed")); + return NULL; + } + + g_assert(digest_size == (guint)md_size); + g_assert(digest_size == ret->size); + return g_steal_pointer(&ret); +} + +/* Returns the digest of @buf using the hash algorithm @md */ +static Buffer *digest_buffer(const EVP_MD *md, const Buffer *buf, GError **err) +{ + g_autoptr(EVP_MD_CTX) md_ctx = NULL; + g_autoptr(Buffer) ret = NULL; + g_assert(buf); + + md_ctx = digest_ctx_new(md, err); + if (!md_ctx) + return NULL; + + if (EVP_DigestUpdate(md_ctx, buf->data, buf->size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + return NULL; + } + + ret = digest_ctx_finalize(md_ctx, err); + if (!ret) + return NULL; + + return g_steal_pointer(&ret); +} + +/* Returns the SHA256 digest of @buf */ +Buffer *sha256_buffer(const Buffer *buf, GError **err) +{ + g_autoptr(Buffer) ret = NULL; + + ret = digest_buffer(EVP_sha256(), buf, err); + if (!ret) + return NULL; + + g_assert(ret->size == SHA256_DIGEST_LENGTH); + return g_steal_pointer(&ret); +} + +/* Convert a EVP_PKEY to the key format used in the PV header */ +union ecdh_pub_key *evp_pkey_to_ecdh_pub_key(EVP_PKEY *key, GError **err) +{ + g_autofree union ecdh_pub_key *ret = g_new0(union ecdh_pub_key, 1); + g_autoptr(BIGNUM) pub_x_big = NULL; + g_autoptr(BIGNUM) pub_y_big = NULL; + g_autoptr(EC_KEY) ec_key = NULL; + const EC_POINT *pub_key; + const EC_GROUP *grp; + + ec_key = EVP_PKEY_get1_EC_KEY(key); + if (!ec_key) { + g_set_error(err, 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(err, 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(err, 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(err, 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(err, 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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Cannot convert key to internal format")); + return NULL; + } + + return g_steal_pointer(&ret); +} + +static Buffer *derive_key(EVP_PKEY *cust, EVP_PKEY *host, GError **err) +{ + g_autoptr(EVP_PKEY_CTX) ctx = NULL; + g_autoptr(Buffer) ret = NULL; + gsize key_size; + + ctx = EVP_PKEY_CTX_new(cust, NULL); + if (!ctx) + g_abort(); + + if (EVP_PKEY_derive_init(ctx) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Key derivation failed")); + return NULL; + } + + if (EVP_PKEY_derive_set_peer(ctx, host) != 1) { + g_set_error(err, 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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_DERIVE, + _("Key derivation failed")); + return NULL; + } + + ret = buffer_alloc(key_size); + if (EVP_PKEY_derive(ctx, ret->data, &key_size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_DERIVE, + _("Key derivation failed")); + return NULL; + } + + g_assert(ret->size == key_size); + return g_steal_pointer(&ret); +} + +Buffer *compute_exchange_key(EVP_PKEY *cust, EVP_PKEY *host, GError **err) +{ + g_autoptr(Buffer) raw = buffer_alloc(70); + g_autoptr(Buffer) ret = NULL; + g_autoptr(Buffer) key = NULL; + guchar *data; + + key = derive_key(cust, host, err); + if (!key) + return NULL; + + g_assert(key->size == 66); + g_assert(key->size < raw->size); + + /* ANSI X.9.63-2011: 66 bytes x with leading 7 bits and + * concatenate 32 bit int '1' + */ + memcpy(raw->data, key->data, key->size); + data = raw->data; + data[66] = 0x00; + data[67] = 0x00; + data[68] = 0x00; + data[69] = 0x01; + + ret = sha256_buffer(raw, err); + if (!ret) + return NULL; + + return g_steal_pointer(&ret); +} + +gint generate_tweak(union tweak *tweak, uint16_t i, GError **err) +{ + tweak->cmp_idx.idx = GUINT16_TO_BE(i); + if (RAND_bytes(tweak->cmp_idx.rand, sizeof(tweak->cmp_idx.rand)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_RANDOMIZATION, + _("Generating a tweak failed because the required amount of random data is not available")); + return -1; + } + + return 0; +} + +static Buffer *generate_rand_data(guint size, const gchar *err_msg, + GError **err) +{ + g_autoptr(Buffer) buf = buffer_alloc(size); + + g_assert(size <= INT_MAX); + + if (RAND_bytes(buf->data, (int)size) != 1) { + g_set_error_literal(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_RANDOMIZATION, + err_msg); + return NULL; + } + + return g_steal_pointer(&buf); +} + +Buffer *generate_aes_iv(guint size, GError **err) +{ + return generate_rand_data(size, + _("Generating a IV failed because the required amount of random data is not available"), + err); +} + +Buffer *generate_aes_key(guint size, GError **err) +{ + return generate_rand_data(size, + _("Generating a key failed because the required amount of random data is not available"), + err); +} + +EVP_PKEY *generate_ec_key(gint nid, GError **err) +{ + g_autoptr(EVP_PKEY_CTX) ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + g_autoptr(EVP_PKEY) ret = NULL; + + if (!ctx) + g_abort(); + + if (EVP_PKEY_keygen_init(ctx) != 1) { + g_set_error(err, 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(err, 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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION, + _("EC key could not be auto-generated")); + return NULL; + } + + return g_steal_pointer(&ret); +} + +static gboolean certificate_uses_correct_curve(EVP_PKEY *key, gint nid, + GError **err) +{ + g_autoptr(EC_KEY) ec = NULL; + gint rc; + + g_assert(key); + + if (EVP_PKEY_id(key) != EVP_PKEY_EC) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INVALID_PARM, + _("No EC key found")); + return FALSE; + } + + ec = EVP_PKEY_get1_EC_KEY(key); + if (!ec) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INVALID_PARM, + _("No EC key found")); + return FALSE; + } + + if (EC_KEY_check_key(ec) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INVALID_PARM, + _("Invalid EC key")); + return FALSE; + } + + rc = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); + if (rc != nid) { + /* maybe the NID is unset */ + if (rc == 0) { + g_autoptr(EC_GROUP) grp = EC_GROUP_new_by_curve_name(nid); + const EC_POINT *pub = EC_KEY_get0_public_key(ec); + g_autoptr(BN_CTX) ctx = BN_CTX_new(); + + if (EC_POINT_is_on_curve(grp, pub, ctx) != 1) { + g_set_error_literal(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INVALID_PARM, + _("Invalid EC curve")); + return FALSE; + } + } else { + /* NID was set but doesn't match with the expected NID + */ + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INVALID_PARM, + _("Wrong NID used: '%d'"), + EC_GROUP_get_curve_name(EC_KEY_get0_group(ec))); + return FALSE; + } + } + + return TRUE; +} + +static gboolean verify_certificate(X509_STORE *store, X509 *cert, GError **err) +{ + g_autoptr(X509_STORE_CTX) csc = X509_STORE_CTX_new(); + if (!csc) + g_abort(); + + if (X509_STORE_CTX_init(csc, store, cert, NULL) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INIT, + _("Failed to initialize X.509 store")); + return FALSE; + } + + if (X509_verify_cert(csc) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_VERIFICATION, + _("Failed to verify host-key document")); + return FALSE; + } + + return TRUE; +} + +static X509 *load_certificate(const gchar *path, GError **err) +{ + g_autoptr(X509) ret = NULL; + g_autoptr(BIO) bio = BIO_new_file(path, "rb"); + + if (!bio) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("Failed to read host-key document: '%s'"), path); + return NULL; + } + + ret = PEM_read_bio_X509(bio, NULL, 0, NULL); + if (!ret) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("Failed to load host-key document: '%s'"), path); + return NULL; + } + + return g_steal_pointer(&ret); +} + +EVP_PKEY *read_ec_pubkey_cert(X509_STORE *store, gint nid, const gchar *path, + GError **err) +{ + g_autoptr(EVP_PKEY) ret = NULL; + g_autoptr(X509) cert = NULL; + + cert = load_certificate(path, err); + if (!cert) + return NULL; + + if (store && !verify_certificate(store, cert, err)) { + g_prefix_error(err, + _("Failed to load host-key document: '%s': "), + path); + return NULL; + } + + ret = X509_get_pubkey(cert); + if (!ret) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INVALID_PARM, + _("Failed to get public key from host-key document: '%s'"), + path); + return NULL; + } + + if (!certificate_uses_correct_curve(ret, nid, err)) { + g_prefix_error(err, + _("Failed to load host-key document: '%s': "), + path); + return NULL; + } + + return g_steal_pointer(&ret); +} + +static gint __encrypt_decrypt_bio(const struct cipher_parms *parms, BIO *b_in, + BIO *b_out, gsize *size_in, gsize *size_out, + gboolean encrypt, GError **err) +{ + gint num_bytes_read, num_bytes_written; + g_autoptr(EVP_CIPHER_CTX) ctx = NULL; + g_autoptr(BIGNUM) tweak_num = NULL; + const EVP_CIPHER *cipher = parms->cipher; + gint cipher_block_size = EVP_CIPHER_block_size(cipher); + guchar in_buf[PAGE_SIZE], + out_buf[PAGE_SIZE + (guint)cipher_block_size]; + const Buffer *key = parms->key; + const Buffer *tweak = parms->iv_or_tweak; + g_autofree guchar *tmp_tweak = NULL; + gint out_len, tweak_size; + gsize tmp_size_in = 0, tmp_size_out = 0; + + g_assert(cipher_block_size > 0); + g_assert(key); + g_assert(tweak); + g_assert(tweak->size <= INT_MAX); + + /* copy the value for leaving the original value untouched */ + tmp_tweak = g_malloc0(tweak->size); + memcpy(tmp_tweak, tweak->data, tweak->size); + tweak_size = (int)tweak->size; + tweak_num = BN_bin2bn(tmp_tweak, tweak_size, NULL); + if (!tweak_num) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("BN_bin2bn failed")); + return -1; + } + + ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + g_abort(); + + /* don't set the key or tweak right away as we want to check + * lengths before + */ + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherInit_ex failed")); + return -1; + } + + /* Now we can set the key and tweak */ + if (EVP_CipherInit_ex(ctx, NULL, NULL, key->data, tmp_tweak, encrypt) != + 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherInit_ex failed")); + return -1; + } + + do { + memset(in_buf, 0, sizeof(in_buf)); + /* Read in data in 4096 bytes blocks. Update the ciphering + * with each read. + */ + num_bytes_read = BIO_read(b_in, in_buf, (int)PAGE_SIZE); + if (num_bytes_read < 0) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("Failed to read")); + return -1; + } + tmp_size_in += (guint)num_bytes_read; + + /* in case we reached the end and it's not the special + * case of an empty component we can break here + */ + if (num_bytes_read == 0 && tmp_size_in != 0) + break; + + if (EVP_CipherUpdate(ctx, out_buf, &out_len, in_buf, + sizeof(in_buf)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherUpdate failed")); + return -1; + } + g_assert(out_len >= 0); + + num_bytes_written = BIO_write(b_out, out_buf, out_len); + if (num_bytes_written < 0) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("Failed to write")); + return -1; + } + g_assert(num_bytes_written == out_len); + + tmp_size_out += (guint)num_bytes_written; + + /* Set new tweak value. Please keep in mind that the + * tweaks are stored in big-endian form. Therefore we + * must use the correct OpenSSL functions + */ + if (BN_add_word(tweak_num, PAGE_SIZE) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_add_word failed")); + } + g_assert(BN_num_bytes(tweak_num) > 0); + g_assert(BN_num_bytes(tweak_num) <= tweak_size); + + if (BN_bn2binpad(tweak_num, tmp_tweak, tweak_size) < 0) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_bn2binpad failed")); + }; + + /* set new tweak */ + if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, tmp_tweak, + encrypt) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherInit_ex failed")); + return -1; + } + } while (num_bytes_read == PAGE_SIZE); + + /* Now cipher the final block and write it out to file */ + if (EVP_CipherFinal_ex(ctx, out_buf, &out_len) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherFinal_ex failed")); + return -1; + } + g_assert(out_len >= 0); + + num_bytes_written = BIO_write(b_out, out_buf, out_len); + if (num_bytes_written < 0) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Failed to write")); + return -1; + } + g_assert(out_len == num_bytes_written); + tmp_size_out += (guint)out_len; + + if (BIO_flush(b_out) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Failed to flush")); + return -1; + } + + *size_in = tmp_size_in; + *size_out = tmp_size_out; + return 0; +} + +static Buffer *__encrypt_decrypt_buffer(const struct cipher_parms *parms, + const Buffer *in, gboolean encrypt, + GError **err) +{ + g_autoptr(Buffer) ret = NULL; + g_autoptr(BIO) b_out = NULL; + g_autoptr(BIO) b_in = NULL; + gsize in_size, out_size; + gchar *data = NULL; + long data_size; + + g_assert(in->size <= INT_MAX); + + b_in = BIO_new_mem_buf(in->data, (int)in->size); + if (!b_in) + g_abort(); + + b_out = BIO_new(BIO_s_mem()); + if (!b_out) + g_abort(); + + if (__encrypt_decrypt_bio(parms, b_in, b_out, &in_size, &out_size, + encrypt, err) < 0) + return NULL; + + data_size = BIO_get_mem_data(b_out, &data); + if (data_size < 0) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Could not read buffer")); + return NULL; + } + + ret = buffer_alloc((unsigned long)data_size); + memcpy(ret->data, data, ret->size); + return g_steal_pointer(&ret); +} + +Buffer *encrypt_buf(const struct cipher_parms *parms, const Buffer *in, + GError **err) +{ + return __encrypt_decrypt_buffer(parms, in, TRUE, err); +} + +Buffer *decrypt_buf(const struct cipher_parms *parms, const Buffer *in, + GError **err) +{ + return __encrypt_decrypt_buffer(parms, in, FALSE, err); +} + +static gint __encrypt_decrypt_file(const struct cipher_parms *parms, + const gchar *path_in, const gchar *path_out, + gsize *size_in, gsize *size_out, gboolean encrypt, + GError **err) +{ + g_autoptr(BIO) b_out = NULL; + g_autoptr(BIO) b_in = NULL; + + b_in = BIO_new_file(path_in, "rb"); + if (!b_in) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("Failed to read file '%s'"), path_in); + return -1; + } + + b_out = BIO_new_file(path_out, "wb"); + if (!b_out) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("Failed to write file '%s'"), path_out); + return -1; + } + + if (__encrypt_decrypt_bio(parms, b_in, b_out, size_in, size_out, + encrypt, err) < 0) + return -1; + + return 0; +} + +gint encrypt_file(const struct cipher_parms *parms, const gchar *path_in, + const gchar *path_out, gsize *in_size, gsize *out_size, + GError **err) +{ + return __encrypt_decrypt_file(parms, path_in, path_out, in_size, + out_size, TRUE, err); +} + +G_GNUC_UNUSED static gint decrypt_file(const struct cipher_parms *parms, + const gchar *path_in, const gchar *path_out, + gsize *in_size, gsize *out_size, + GError **err) +{ + return __encrypt_decrypt_file(parms, path_in, path_out, in_size, + out_size, FALSE, err); +} + +/* GCM mode uses (zero-)padding */ +static int64_t gcm_encrypt_decrypt(const Buffer *in, const Buffer *aad, + const struct cipher_parms *parms, + Buffer *out, Buffer *tag, + enum PvCryptoMode mode, GError **err) +{ + g_autoptr(EVP_CIPHER_CTX) ctx = NULL; + const EVP_CIPHER *cipher = parms->cipher; + const Buffer *iv = parms->iv_or_tweak; + gboolean encrypt = mode == PV_ENCRYPT; + const Buffer *key = parms->key; + int64_t ret = -1; + gint len = -1; + + g_assert(cipher); + g_assert(key); + g_assert(iv); + /* Checks for later casts */ + g_assert(aad->size <= INT_MAX); + g_assert(in->size <= INT_MAX); + g_assert(tag->size <= INT_MAX); + g_assert(iv->size <= INT_MAX); + g_assert(out->size == in->size); + + ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + g_abort(); + + /* 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(err, 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(err, 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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherInit_ex failed")); + return -1; + } + + if (aad->size > 0) { + /* Provide any AAD data */ + if (EVP_CipherUpdate(ctx, NULL, &len, aad->data, + (int)aad->size) != 1) { + g_set_error(err, 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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherUpdate failed")); + return -1; + } + ret = len; + + if (!encrypt) { + /* Set expected tag value */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, + (int)tag->size, tag->data) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("Setting the GCM tag failed")); + return -1; + } + } + + /* Finalize the en/decryption */ + if (EVP_CipherFinal_ex(ctx, (guchar *)out->data + len, &len) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherFinal_ex 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(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("Getting the GCM tag failed")); + return -1; + } + } + + g_assert(ret == (int)in->size); + return ret; +} + +int64_t gcm_encrypt(const Buffer *in, const Buffer *aad, + const struct cipher_parms *parms, Buffer *out, Buffer *tag, + GError **err) +{ + return gcm_encrypt_decrypt(in, aad, parms, out, tag, PV_ENCRYPT, err); +} diff --git a/genprotimg/src/utils/crypto.h b/genprotimg/src/utils/crypto.h new file mode 100644 index 0000000..34418ed --- /dev/null +++ b/genprotimg/src/utils/crypto.h @@ -0,0 +1,104 @@ +/* + * General cryptography helper functions and 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 PV_UTILS_CRYPTO_H +#define PV_UTILS_CRYPTO_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "include/pv_crypto_def.h" +#include "lib/zt_common.h" + +#include "buffer.h" + +#define AES_256_GCM_IV_SIZE 12 +#define AES_256_GCM_TAG_SIZE 16 + +#define AES_256_XTS_TWEAK_SIZE 16 +#define AES_256_XTS_KEY_SIZE 64 + +enum PvCryptoMode { + PV_ENCRYPT, + PV_DECRYPT, +}; + +typedef GSList HostKeyList; + +/* Register auto cleanup functions */ +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BIGNUM, BN_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BIO, BIO_free_all) +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_MD_CTX, EVP_MD_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) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509, X509_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_LOOKUP, X509_LOOKUP_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) + +union cmp_index { + struct { + uint16_t idx; + guchar rand[6]; + } __packed; + uint64_t data; +}; + +/* The tweak is always stored in big endian format */ +union tweak { + struct { + union cmp_index cmp_idx; + uint64_t page_idx; /* page index */ + } __packed; + uint8_t data[AES_256_XTS_TWEAK_SIZE]; +}; + +struct cipher_parms { + const EVP_CIPHER *cipher; + const Buffer *key; + const Buffer *iv_or_tweak; +}; + +EVP_PKEY *read_ec_pubkey_cert(X509_STORE *store, gint nid, const gchar *path, + GError **err); +Buffer *compute_exchange_key(EVP_PKEY *cust, EVP_PKEY *host, GError **err); +Buffer *generate_aes_key(guint size, GError **err); +Buffer *generate_aes_iv(guint size, GError **err); +EVP_PKEY *generate_ec_key(gint nid, GError **err); +gint generate_tweak(union tweak *tweak, uint16_t i, GError **err); +union ecdh_pub_key *evp_pkey_to_ecdh_pub_key(EVP_PKEY *key, GError **err); +EVP_MD_CTX *digest_ctx_new(const EVP_MD *md, GError **err); +Buffer *digest_ctx_finalize(EVP_MD_CTX *ctx, GError **err); +Buffer *sha256_buffer(const Buffer *buf, GError **err); +int64_t gcm_encrypt(const Buffer *in, const Buffer *aad, + const struct cipher_parms *parms, Buffer *out, + Buffer *tag, GError **err); +gint encrypt_file(const struct cipher_parms *parms, const gchar *in_path, + const gchar *path_out, gsize *in_size, gsize *out_size, + GError **err); +Buffer *encrypt_buf(const struct cipher_parms *parms, const Buffer *in, + GError **err); +G_GNUC_UNUSED Buffer *decrypt_buf(const struct cipher_parms *parms, + const Buffer *in, GError **err); + +#endif diff --git a/genprotimg/src/utils/file_utils.c b/genprotimg/src/utils/file_utils.c new file mode 100644 index 0000000..1d6fc37 --- /dev/null +++ b/genprotimg/src/utils/file_utils.c @@ -0,0 +1,234 @@ +/* + * General file 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pv/pv_error.h" + +#include "align.h" +#include "buffer.h" +#include "common.h" +#include "file_utils.h" + +FILE *file_open(const gchar *filename, const gchar *mode, GError **err) +{ + FILE *f = fopen(filename, mode); + + if (!f) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to open file '%s': %s"), filename, + g_strerror(errno)); + return NULL; + } + + return f; +} + +gint file_size(const gchar *filename, gsize *size, GError **err) +{ + GStatBuf st_buf; + + g_assert(size); + + if (g_stat(filename, &st_buf) != 0) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to get file status '%s': %s"), filename, + g_strerror(errno)); + return -1; + } + + if (!S_ISREG(st_buf.st_mode)) { + g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL, + _("File '%s' is not a regular file"), filename); + return -1; + } + + if (st_buf.st_size < 0) { + g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL, + _("Invalid file size for '%s': %zu"), filename, + st_buf.st_size); + return -1; + } + + *size = (gsize)st_buf.st_size; + return 0; +} + +/* Returns 0 on success, otherwise -1. Stores the total number of + * elements successfully read in @count_read + */ +gint file_read(FILE *in, void *ptr, gsize size, gsize count, + gsize *count_read, GError **err) +{ + gsize tmp_count_read; + + tmp_count_read = fread(ptr, size, count, in); + if (count_read) + *count_read = tmp_count_read; + + if (ferror(in)) { + g_set_error(err, G_FILE_ERROR, 0, _("Failed to read file")); + return -1; + } + + return 0; +} + +gint file_write(FILE *out, const void *ptr, gsize size, gsize count, + gsize *count_written, GError **err) +{ + gsize tmp_count_written; + + tmp_count_written = fwrite(ptr, size, count, out); + if (count_written) + *count_written = tmp_count_written; + + if (tmp_count_written != count || ferror(out)) { + g_set_error(err, G_FILE_ERROR, 0, _("Failed to write file")); + return -1; + } + + return 0; +} + +static gint file_seek(FILE *f, uint64_t offset, GError **err) +{ + gint rc; + + if (offset > LONG_MAX) { + g_set_error(err, PV_ERROR, 0, _("Offset is too large")); + return -1; + } + + rc = fseek(f, (long)offset, SEEK_SET); + if (rc != 0) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to seek: '%s'"), g_strerror(errno)); + return -1; + } + + return 0; +} + +gint seek_and_write_file(FILE *o, const CompFile *ifile, uint64_t offset, + GError **err) +{ + gsize bytes_read, bytes_written; + gsize total_bytes_read = 0; + FILE *i = NULL; + gchar buf[4096]; + gint ret = -1; + + if (file_seek(o, offset, err) < 0) + return -1; + + i = file_open(ifile->path, "rb", err); + if (!i) + return -1; + + do { + if (file_read(i, buf, 1, sizeof(buf), &bytes_read, err) < 0) { + g_prefix_error(err, _("Failed to read file '%s': "), + ifile->path); + goto err; + } + + if (bytes_read == 0) + break; + + total_bytes_read += bytes_read; + + if (file_write(o, buf, bytes_read, 1, &bytes_written, err) < 0) + goto err; + } while (bytes_written != 0); + + if (ifile->size != total_bytes_read) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("'%s' has changed during the preparation"), + ifile->path); + goto err; + } + + ret = 0; +err: + fclose(i); + return ret; +} + +gint seek_and_write_buffer(FILE *o, const Buffer *buf, uint64_t offset, + GError **err) +{ + if (file_seek(o, offset, err) < 0) + return -1; + + if (buffer_write(buf, o, err) < 0) + return -1; + + return 0; +} + +gint pad_file_right(const gchar *path_out, const gchar *path_in, gsize *size_out, + guint padding, GError **err) +{ + FILE *f_in, *f_out = NULL; + guchar buf[padding]; + gsize num_bytes_written; + gsize num_bytes_read; + uint64_t size_in = 0; + gint ret = -1; + + *size_out = 0; + f_in = file_open(path_in, "rb", err); + if (!f_in) + goto err; + + f_out = file_open(path_out, "wb", err); + if (!f_out) + goto err; + + do { + memset(buf, 0, sizeof(buf)); + + if (file_read(f_in, buf, 1, sizeof(buf), &num_bytes_read, err) < 0) { + g_prefix_error(err, _("Failed to read file '%s': "), + path_in); + goto err; + } + + size_in += num_bytes_read; + + if (file_write(f_out, buf, 1, sizeof(buf), &num_bytes_written, err)) { + g_prefix_error(err, _("Failed to write file '%s': "), + path_out); + goto err; + } + + *size_out += num_bytes_written; + } while (num_bytes_read == padding); + + g_assert(num_bytes_written == ALIGN(num_bytes_read, padding)); + + ret = 0; +err: + if (f_out) + fclose(f_out); + if (f_in) + fclose(f_in); + return ret; +} diff --git a/genprotimg/src/utils/file_utils.h b/genprotimg/src/utils/file_utils.h new file mode 100644 index 0000000..47df114 --- /dev/null +++ b/genprotimg/src/utils/file_utils.h @@ -0,0 +1,34 @@ +/* + * General file 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. + */ + +#ifndef PV_FILE_UTILS_H +#define PV_FILE_UTILS_H + +#include +#include +#include + +#include "pv/pv_comp.h" + +#include "buffer.h" + +FILE *file_open(const gchar *filename, const gchar *mode, GError **err); +gint file_size(const gchar *filename, gsize *size, GError **err); +gint file_read(FILE *in, void *ptr, gsize size, gsize count, + gsize *count_read, GError **err); +gint file_write(FILE *out, const void *ptr, gsize size, gsize count, + gsize *count_written, GError **err); +gint pad_file_right(const gchar *path_out, const gchar *path_in, + gsize *size_out, guint padding, GError **err); +gint seek_and_write_buffer(FILE *out, const Buffer *buf, uint64_t offset, + GError **err); +gint seek_and_write_file(FILE *o, const CompFile *ifile, uint64_t offset, + GError **err); + +#endif diff --git a/include/boot/ipl.h b/include/boot/ipl.h new file mode 100644 index 0000000..bc9b7fe --- /dev/null +++ b/include/boot/ipl.h @@ -0,0 +1,191 @@ +/* + * IPL related 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 IPL_H +#define IPL_H + +#include "lib/zt_common.h" +#include "s390.h" + +#define IPL_FLAG_SECURE 0x40 + +#define IPL_RB_COMPONENT_FLAG_SIGNED 0x80 +#define IPL_RB_COMPONENT_FLAG_VERIFIED 0x40 + +#define IPL_MAX_SUPPORTED_VERSION 0 +#define IPL_PARM_BLOCK_VERSION 0x1 + +/* IPL Types */ +#define IPL_TYPE_PV 0x5 + + +#ifndef __ASSEMBLER__ + +#include + +/* IPL Parameter List header */ +struct ipl_pl_hdr { + uint32_t len; + uint8_t flags; + uint8_t reserved1[2]; + uint8_t version; +} __packed; + +/* IPL Parameter Block header */ +struct ipl_pb_hdr { + uint32_t len; + uint8_t pbt; +} __packed; + +/* IPL Parameter Block 0 with common fields */ +struct ipl_pb0_common { + uint32_t len; + uint8_t pbt; + uint8_t flags; + uint8_t reserved1[2]; + uint8_t loadparm[8]; + uint8_t reserved2[84]; +} __packed; + +/* IPL Parameter Block 0 for FCP */ +struct ipl_pb0_fcp { + uint32_t len; + uint8_t pbt; + uint8_t reserved1[3]; + uint8_t loadparm[8]; + uint8_t reserved2[304]; + uint8_t opt; + uint8_t reserved3[3]; + uint8_t cssid; + uint8_t reserved4[1]; + uint8_t devno; + uint8_t reserved5[4]; + uint64_t wwpn; + uint64_t lun; + uint32_t bootprog; + uint8_t reserved6[12]; + uint64_t br_lba; + uint32_t scp_data_len; + uint8_t reserved7[260]; + uint8_t scp_data[]; +} __packed; + +/* IPL Parameter Block 0 for CCW */ +struct ipl_pb0_ccw { + uint32_t len; + uint8_t pbt; + uint8_t flags; + uint8_t reserved1[2]; + uint8_t loadparm[8]; + uint8_t reserved2[84]; + uint16_t reserved3 : 13; + uint8_t ssid : 3; + uint16_t devno; + uint8_t vm_flags; + uint8_t reserved4[3]; + uint32_t vm_parm_len; + uint8_t nss_name[8]; + uint8_t vm_parm[64]; + uint8_t reserved5[8]; +} __packed; + +/* Structure must not have any padding */ +struct ipl_pb0_pv_comp { + uint64_t tweak_pref; + uint64_t addr; + uint64_t len; +}; +STATIC_ASSERT(sizeof(struct ipl_pb0_pv_comp) == 3 * 8) + +/* IPL Parameter Block 0 for PV */ +struct ipl_pb0_pv { + uint32_t len; + uint8_t pbt; + uint8_t reserved1[3]; + uint8_t loadparm[8]; + uint8_t reserved2[84]; + uint8_t reserved3[3]; + uint8_t version; + uint8_t reserved4[4]; + uint32_t num_comp; + uint64_t pv_hdr_addr; + uint64_t pv_hdr_size; + struct ipl_pb0_pv_comp components[]; +} __packed; + +struct ipl_parameter_block { + struct ipl_pl_hdr hdr; + union { + struct ipl_pb_hdr pb0_hdr; + struct ipl_pb0_common common; + struct ipl_pb0_fcp fcp; + struct ipl_pb0_ccw ccw; + struct ipl_pb0_pv pv; + char raw[PAGE_SIZE - sizeof(struct ipl_pl_hdr)]; + }; +} __packed; + +/* IPL Report List header */ +struct ipl_rl_hdr { + uint32_t len; + uint8_t flags; + uint8_t reserved1[2]; + uint8_t version; + uint8_t reserved2[8]; +} __packed; + +/* IPL Report Block header */ +/* Structure must not have any padding */ +struct ipl_rb_hdr { + uint32_t len; + uint8_t rbt; + uint8_t reserved1[11]; +}; +STATIC_ASSERT(sizeof(struct ipl_rb_hdr) == 4 + 1 + 11) + +/* IPL Report Block types */ +enum ipl_rbt { + IPL_RBT_CERTIFICATES = 1, + IPL_RBT_COMPONENTS = 2, +}; + +/* IPL Report Block for the certificate list */ +struct ipl_rb_certificate_entry { + uint64_t addr; + uint64_t len; +} __packed; + +struct ipl_rb_certificates { + uint32_t len; + uint8_t rbt; + uint8_t reserved1[11]; + struct ipl_rb_certificate_entry entries[]; +} __packed; + +/* IPL Report Block for the component list */ +struct ipl_rb_component_entry { + uint64_t addr; + uint64_t len; + uint8_t flags; + uint8_t reserved1[5]; + uint16_t certificate_index; + uint8_t reserved2[8]; +}; + +/* Structure must not have any padding */ +struct ipl_rb_components { + uint32_t len; + uint8_t rbt; + uint8_t reserved1[11]; + struct ipl_rb_component_entry entries[]; +}; +STATIC_ASSERT(sizeof(struct ipl_rb_components) == 4 + 1 + 11) + +#endif /* __ASSEMBLER__ */ +#endif /* IPL_H */ diff --git a/include/boot/linux_layout.h b/include/boot/linux_layout.h new file mode 100644 index 0000000..83f91a2 --- /dev/null +++ b/include/boot/linux_layout.h @@ -0,0 +1,34 @@ +/* + * s390 Linux layout 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 LINUX_LAYOUT_H +#define LINUX_LAYOUT_H + +#include "lib/zt_common.h" + +/* Entry address offsets */ +#define IMAGE_ENTRY _AC(0x10000, UL) +#define IMAGE_ENTRY_KDUMP _AC(0x10010, UL) + +/* Parameter address offsets */ +#define PARMAREA _AC(0x10400, UL) +#define IPL_DEVICE _AC(0x10400, UL) +#define INITRD_START _AC(0x10408, UL) +#define INITRD_SIZE _AC(0x10410, UL) +#define OLDMEM_BASE _AC(0x10418, UL) +#define OLDMEM_SIZE _AC(0x10420, UL) +#define COMMAND_LINE _AC(0x10480, UL) + +/* Parameter sizes */ +#define COMMAND_LINE_SIZE 896 + + +#ifndef __ASSEMBLER__ +#endif /* __ASSEMBLER__ */ +#endif /* LINUX_LAYOUT_H */ diff --git a/include/boot/loaders_layout.h b/include/boot/loaders_layout.h new file mode 100644 index 0000000..5fc6d25 --- /dev/null +++ b/include/boot/loaders_layout.h @@ -0,0 +1,43 @@ +/* + * zipl stage2/stage3 layout 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 LOADERS_LAYOUT_H +#define LOADERS_LAYOUT_H + +#include "lib/zt_common.h" +#include "linux_layout.h" + +#define STAGE2_DESC _AC(0x78, UL) +#define STAGE2_ENTRY _AC(0x2018, UL) +#define STAGE2_HEAP_ADDRESS _AC(0x6000, UL) +#define STAGE2_HEAP_SIZE _AC(0x3000, UL) +#define STAGE2_STACK_ADDRESS _AC(0xe400, UL) +#define STAGE2_STACK_SIZE _AC(0x1c00, UL) + +#define STAGE3_ENTRY _AC(0xa000, UL) + +#define STAGE2_LOAD_ADDRESS _AC(0x2000, UL) +#define STAGE3_LOAD_ADDRESS STAGE3_ENTRY +#define IMAGE_LOAD_ADDRESS IMAGE_ENTRY + +#define STAGE3_MAXIMUM_SIZE _AC(0x3000, UL) +#define STAGE3_HEAP_SIZE _AC(0x4000, UL) +#define STAGE3_HEAP_ADDRESS _AC(0x2000, UL) +#define STAGE3_STACK_SIZE _AC(0x1000, UL) +#define STAGE3_STACK_ADDRESS _AC(0xF000, UL) +#define STAGE3_PARAMS_ADDRESS _AC(0x9000, UL) +#define STAGE3_PARAMS_MAXIMUM_SIZE _AC(0x1000, UL) + +#define COMMAND_LINE_EXTRA _AC(0xE000, UL) +#define COMMAND_LINE_EXTRA_SIZE _AC(0x0400, UL) + +#ifndef __ASSEMBLER__ +#endif /* __ASSEMBLER__ */ +#endif /* LOADERS_LAYOUT_H */ diff --git a/include/boot/s390.h b/include/boot/s390.h new file mode 100644 index 0000000..8262f5a --- /dev/null +++ b/include/boot/s390.h @@ -0,0 +1,467 @@ +/* + * s390 related definitions and functions. + * + * Copyright IBM Corp. 2013, 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 S390_H +#define S390_H + +#include "lib/zt_common.h" +#include "boot/sigp.h" + +#define __LC_IPLDEV 0x0c6c +#define __LC_OS_INFO 0x0e18 + +#define PAGE_SIZE _AC(4096, UL) + +/* Minimum size of a stack frame in bytes */ +#define STACK_FRAME_OVERHEAD _AC(160, U) + +/* Facilities */ +#define UNPACK_FACILITY _AC(161, U) + +#define PSW32_ADDR_MASK _AC(0x000000007fffffff, UL) +#define PSW_MASK_BA _AC(0x0000000080000000, UL) +#define PSW_MASK_EA _AC(0x0000000100000000, UL) +#define PSW_MASK_BIT_12 _AC(0x0008000000000000, UL) +#define PSW_LOAD _AC(0x0008000080000000, UL) +#define PSW_DISABLED_WAIT _AC(0x000a000000000000, UL) + + +#ifndef __ASSEMBLER__ + +/* + * Helper macro for exception table entries + */ +#define EX_TABLE(_fault, _target) \ + ".section .ex_table,\"a\"\n" \ + ".align 4\n" \ + ".long (" #_fault ")\n" \ + ".long (" #_target ")\n" \ + ".previous\n" + +struct psw_t { + uint64_t mask; + uint64_t addr; +} __aligned(8); + +struct psw32_t { + uint32_t mask; + uint32_t addr; +} __aligned(8); + +void load_wait_psw(uint64_t, struct psw_t *); + +struct _lowcore { + uint8_t pad_0x0000[0x0014-0x0000]; /* 0x0000 */ + uint32_t ipl_parmblock_ptr; /* 0x0014 */ + uint8_t pad_0x0018[0x0080-0x0018]; /* 0x0018 */ + uint32_t ext_params; /* 0x0080 */ + uint16_t ext_cpu_addr; /* 0x0084 */ + uint16_t ext_int_code; /* 0x0086 */ + uint16_t svc_ilc; /* 0x0088 */ + uint16_t svc_code; /* 0x008a */ + uint16_t pgm_ilc; /* 0x008c */ + uint16_t pgm_code; /* 0x008e */ + uint32_t data_exc_code; /* 0x0090 */ + uint16_t mon_class_num; /* 0x0094 */ + uint16_t per_perc_atmid; /* 0x0096 */ + uint64_t per_address; /* 0x0098 */ + uint8_t exc_access_id; /* 0x00a0 */ + uint8_t per_access_id; /* 0x00a1 */ + uint8_t op_access_id; /* 0x00a2 */ + uint8_t ar_access_id; /* 0x00a3 */ + 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 */ + uint8_t pad_0x00c4[0x00c8-0x00c4]; /* 0x00c4 */ + uint32_t stfl_fac_list; /* 0x00c8 */ + uint8_t pad_0x00cc[0x00e8-0x00cc]; /* 0x00cc */ + uint32_t mcck_interruption_code[2]; /* 0x00e8 */ + uint8_t pad_0x00f0[0x00f4-0x00f0]; /* 0x00f0 */ + uint32_t external_damage_code; /* 0x00f4 */ + uint64_t failing_storage_address; /* 0x00f8 */ + uint8_t pad_0x0100[0x0110-0x0100]; /* 0x0100 */ + uint64_t breaking_event_addr; /* 0x0110 */ + uint8_t pad_0x0118[0x0120-0x0118]; /* 0x0118 */ + struct psw_t restart_old_psw; /* 0x0120 */ + struct psw_t external_old_psw; /* 0x0130 */ + struct psw_t svc_old_psw; /* 0x0140 */ + struct psw_t program_old_psw; /* 0x0150 */ + struct psw_t mcck_old_psw; /* 0x0160 */ + struct psw_t io_old_psw; /* 0x0170 */ + uint8_t pad_0x0180[0x01a0-0x0180]; /* 0x0180 */ + struct psw_t restart_psw; /* 0x01a0 */ + struct psw_t external_new_psw; /* 0x01b0 */ + struct psw_t svc_new_psw; /* 0x01c0 */ + struct psw_t program_new_psw; /* 0x01d0 */ + struct psw_t mcck_new_psw; /* 0x01e0 */ + struct psw_t io_new_psw; /* 0x01f0 */ + + /* Save areas. */ + uint64_t save_area_sync[8]; /* 0x0200 */ + uint64_t save_area_async[8]; /* 0x0240 */ + uint64_t save_area_restart[1]; /* 0x0280 */ + uint8_t pad_0x0288[0x0290-0x0288]; /* 0x0288 */ + + /* Return psws. */ + struct psw_t return_psw; /* 0x0290 */ + struct psw_t return_mcck_psw; /* 0x02a0 */ + + /* CPU accounting and timing values. */ + uint64_t sync_enter_timer; /* 0x02b0 */ + uint64_t async_enter_timer; /* 0x02b8 */ + uint64_t mcck_enter_timer; /* 0x02c0 */ + uint64_t exit_timer; /* 0x02c8 */ + uint64_t user_timer; /* 0x02d0 */ + uint64_t system_timer; /* 0x02d8 */ + uint64_t steal_timer; /* 0x02e0 */ + uint64_t last_update_timer; /* 0x02e8 */ + uint64_t last_update_clock; /* 0x02f0 */ + uint64_t int_clock; /* 0x02f8 */ + uint64_t mcck_clock; /* 0x0300 */ + uint64_t clock_comparator; /* 0x0308 */ + + /* Current process. */ + uint64_t current_task; /* 0x0310 */ + uint64_t thread_info; /* 0x0318 */ + uint64_t kernel_stack; /* 0x0320 */ + + /* Interrupt, panic and restart stack. */ + uint64_t async_stack; /* 0x0328 */ + uint64_t panic_stack; /* 0x0330 */ + uint64_t restart_stack; /* 0x0338 */ + + /* Restart function and parameter. */ + uint64_t restart_fn; /* 0x0340 */ + uint64_t restart_data; /* 0x0348 */ + uint64_t restart_source; /* 0x0350 */ + + /* Address space pointer. */ + uint64_t kernel_asce; /* 0x0358 */ + uint64_t user_asce; /* 0x0360 */ + uint64_t current_pid; /* 0x0368 */ + + /* SMP info area */ + uint32_t cpu_nr; /* 0x0370 */ + uint32_t softirq_pending; /* 0x0374 */ + uint64_t percpu_offset; /* 0x0378 */ + uint64_t vdso_per_cpu_data; /* 0x0380 */ + uint64_t machine_flags; /* 0x0388 */ + uint64_t ftrace_func; /* 0x0390 */ + uint64_t gmap; /* 0x0398 */ + uint8_t pad_0x03a0[0x0400-0x03a0]; /* 0x03a0 */ + + /* Interrupt response block. */ + uint8_t irb[64]; /* 0x0400 */ + + /* Per cpu primary space access list */ + uint32_t paste[16]; /* 0x0440 */ + + uint8_t pad_0x0480[0x0e00-0x0480]; /* 0x0480 */ + + /* + * 0xe00 contains the address of the IPL Parameter Information + * block. Dump tools need IPIB for IPL after dump. + * Note: do not change the position of any fields in 0x0e00-0x0f00 + */ + uint64_t ipib; /* 0x0e00 */ + uint32_t ipib_checksum; /* 0x0e08 */ + uint64_t vmcore_info; /* 0x0e0c */ + uint8_t pad_0x0e14[0x0e18-0x0e14]; /* 0x0e14 */ + uint64_t os_info; /* 0x0e18 */ + uint8_t pad_0x0e20[0x0f00-0x0e20]; /* 0x0e20 */ + + /* Extended facility list */ + uint64_t stfle_fac_list[32]; /* 0x0f00 */ + uint8_t pad_0x1000[0x11b0-0x1000]; /* 0x1000 */ + uint64_t vector_save_area_addr; /* 0x11b0 */ + /* 64 bit extparam used for pfault/diag 250: defined by architecture */ + uint64_t ext_params2; /* 0x11B8 */ + uint8_t pad_0x11c0[0x1200-0x11C0]; /* 0x11C0 */ + + /* CPU register save area: defined by architecture */ + uint64_t floating_pt_save_area[16]; /* 0x1200 */ + uint64_t gpregs_save_area[16]; /* 0x1280 */ + struct psw_t psw_save_area; /* 0x1300 */ + uint8_t pad_0x1310[0x1318-0x1310]; /* 0x1310 */ + uint32_t prefixreg_save_area; /* 0x1318 */ + uint32_t fpt_creg_save_area; /* 0x131c */ + uint8_t pad_0x1320[0x1324-0x1320]; /* 0x1320 */ + uint32_t tod_progreg_save_area; /* 0x1324 */ + uint32_t cpu_timer_save_area[2]; /* 0x1328 */ + uint32_t clock_comp_save_area[2]; /* 0x1330 */ + uint8_t pad_0x1338[0x1340-0x1338]; /* 0x1338 */ + uint32_t access_regs_save_area[16]; /* 0x1340 */ + uint64_t cregs_save_area[16]; /* 0x1380 */ + uint8_t pad_0x1400[0x1800-0x1400]; /* 0x1400 */ + + /* Transaction abort diagnostic block */ + uint8_t pgm_tdb[256]; /* 0x1800 */ + + /* align to the top of the prefix area */ + uint8_t pad_0x1900[0x2000-0x1900]; /* 0x1900 */ +} __packed __aligned(8192); + +#define S390_lowcore (*((struct _lowcore *) 0)) + + +static __always_inline int page_is_valid(unsigned long addr) +{ + unsigned long tmp; + int rc; + + asm volatile( + "0: ic %1,%2\n" + "1: lhi %0,1\n" + "2:\n" + ".pushsection .fixup, \"ax\"\n" + "3: xr %0,%0\n" + " jg 2b\n" + ".popsection\n" + EX_TABLE(0b, 3b) EX_TABLE(1b, 3b) + : "=d" (rc), "=d" (tmp) + : "Q" (*(unsigned long *) addr) + : "cc"); + return rc; +} + +static __always_inline uint32_t csum_partial(const void *buf, int len, uint32_t sum) +{ + register unsigned long reg2 asm("2") = (unsigned long) buf; + register unsigned long reg3 asm("3") = (unsigned long) len; + + asm volatile( + "0: cksm %0,%1\n" /* do checksum on longs */ + " jo 0b\n" + : "+d" (sum), "+d" (reg2), "+d" (reg3) : : "cc", "memory"); + return sum; +} + +#define __ctl_store(array, low, high) ({ \ + typedef struct { char _[sizeof(array)]; } __may_alias addrtype;\ + asm volatile( \ + " stctg %1,%2,%0\n" \ + : "=Q" (*(addrtype *)(&array)) \ + : "i" (low), "i" (high)); \ +}) + +#define __ctl_load(array, low, high) ({ \ + typedef struct { char _[sizeof(array)]; } __may_alias addrtype; \ + asm volatile( \ + " lctlg %1,%2,%0\n" \ + : : "Q" (*(addrtype *)(&array)), \ + "i" (low), "i" (high)); \ +}) + +static __always_inline void __ctl_set_bit(unsigned int cr, unsigned int bit) +{ + unsigned long reg; + + __ctl_store(reg, cr, cr); + reg |= 1UL << bit; + __ctl_load(reg, cr, cr); +} + +/* + * DIAG 308 support + */ +enum diag308_subcode { + DIAG308_REL_HSA = 2, + DIAG308_IPL = 3, + DIAG308_DUMP = 4, + DIAG308_SET = 5, + DIAG308_STORE = 6, + DIAG308_SET_PV = 8, + DIAG308_UNPACK_PV = 10, +}; + +enum diag308_rc { + DIAG308_RC_OK = 0x0001, +}; + +static __always_inline unsigned long diag308(unsigned long subcode, void *addr) +{ + register unsigned long _addr asm("0") = (unsigned long) addr; + register unsigned long _rc asm("1") = 0; + + asm volatile( + " diag %0,%2,0x308\n" + "0:\n" + : "+d" (_addr), "+d" (_rc) + : "d" (subcode) : "cc", "memory"); + return _rc; +} + +/* + * Store CPU address + */ +static __always_inline unsigned short stap(void) +{ + unsigned short cpu_address; + + asm volatile("stap %0" : "=m" (cpu_address)); + return cpu_address; +} + +/* + * Program the clock comparator + */ +static __always_inline void set_clock_comparator(uint64_t time) +{ + asm volatile("sckc %0" : : "Q" (time)); +} + +/* + * Program the CPU timer + */ +static __always_inline void set_cpu_timer(uint64_t timer) +{ + asm volatile("spt %0" : : "Q" (timer)); +} + +/* + * Get current time (store clock) + */ +static __always_inline unsigned long long get_tod_clock(void) +{ + unsigned long long clk; + + asm volatile("stck %0" : "=Q" (clk) : : "cc"); + return clk; +} + +/* + * Get ID of current CPU + */ +struct cpuid { + unsigned int version:8; + unsigned int ident:24; + unsigned int machine:16; + unsigned int unused:16; +} __packed __aligned(8); + +static __always_inline void get_cpu_id(struct cpuid *ptr) +{ + asm volatile("stidp %0" : "=Q" (*ptr)); +} + +/* + * Check if we run under z/VM + */ +static __always_inline int is_zvm(void) +{ + struct cpuid cpuid; + + get_cpu_id(&cpuid); + return cpuid.version == 0xff; +} + +/* To avoid conflicts add a macro guard since __vector128 is also + * defined in 'linux/asm/types.h'. + */ +#ifndef _S390_TYPES_H +/* + * Vector register definition + */ +typedef struct { + uint32_t u[4]; +} __vector128; +#endif + +/* + * Save vector registers + */ +static __always_inline void save_vx_regs(__vector128 *vxrs) +{ + typedef struct { __vector128 _[32]; } addrtype; + + asm volatile( + " la 1,%0\n" + " .word 0xe70f,0x1000,0x003e\n" /* vstm 0,15,0(1) */ + " .word 0xe70f,0x1100,0x0c3e\n" /* vstm 16,31,256(1) */ + : "=Q" (*(addrtype *) vxrs) : : "1"); +} + +/* + * Save vector registers safe + */ +static __always_inline void save_vx_regs_safe(__vector128 *vxrs) +{ + unsigned long cr0; + + __ctl_store(cr0, 0, 0); + __ctl_set_bit(0, 17); + __ctl_set_bit(0, 18); /* AFP-register-control */ + save_vx_regs(vxrs); + __ctl_load(cr0, 0, 0); +} + +#define MAX_FACILITY_BIT (256*8) /* stfle_fac_list has 256 bytes */ + +static __always_inline int __test_facility(unsigned long nr, void *facilities) +{ + unsigned char *ptr; + + if (nr >= MAX_FACILITY_BIT) + return 0; + ptr = (unsigned char *) facilities + (nr >> 3); + return (*ptr & (0x80 >> (nr & 7))) != 0; +} + +/* + * The test_facility function uses the bit odering where the MSB is bit 0. + * That makes it easier to query facility bits with the bit number as + * documented in the Principles of Operation. + */ +static __always_inline int test_facility(unsigned long nr) +{ + return __test_facility(nr, &S390_lowcore.stfle_fac_list); +} + +static __always_inline unsigned long __stfle_asm(uint64_t *stfle_fac_list, unsigned int size) +{ + register unsigned long reg0 asm("0") = size - 1; + + asm volatile( + ".insn s,0xb2b00000,0(%1)" /* stfle */ + : "+d" (reg0) + : "a" (stfle_fac_list) + : "memory", "cc"); + return reg0; +} + +/** + * stfle - Store facility list extended + * @stfle_fac_list: array where facility list can be stored + * @size: size of passed in array in double words + */ +static __always_inline void stfle(uint64_t *stfle_fac_list, unsigned int size) +{ + unsigned long nr; + + asm volatile( + " .insn s,0xb2b10000,0(0)\n" /* stfl */ + "0:\n" + EX_TABLE(0b, 0b) + : "+m" (S390_lowcore.stfl_fac_list)); + nr = 4; /* bytes stored by stfl */ + memcpy(stfle_fac_list, &S390_lowcore.stfl_fac_list, 4); + if (S390_lowcore.stfl_fac_list & 0x01000000) { + /* More facility bits available with stfle */ + nr = __stfle_asm(stfle_fac_list, size); + nr = MIN((nr + 1) * 8, size * 8UL); + } + memset((char *) stfle_fac_list + nr, 0, size * 8 - nr); +} + +#endif /* __ASSEMBLER__ */ +#endif /* S390_H */ diff --git a/include/boot/sigp.h b/include/boot/sigp.h new file mode 100644 index 0000000..1606580 --- /dev/null +++ b/include/boot/sigp.h @@ -0,0 +1,56 @@ +/* + * SIGP related definitions and functions. + * + * 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 S390_SIGP_H +#define S390_SIGP_H + +/* Signal Processor Order Codes */ +#define SIGP_STOP_AND_STORE_STATUS 9 +#define SIGP_SET_ARCHITECTURE 18 +#define SIGP_SET_MULTI_THREADING 22 +#define SIGP_STORE_ASTATUS_AT_ADDRESS 23 + +/* Signal Processor Condition Codes */ +#define SIGP_CC_ORDER_CODE_ACCEPTED 0 +#define SIGP_CC_BUSY 2 + + +#ifndef __ASSEMBLER__ + +#include + +static inline int sigp(uint16_t addr, uint8_t order, uint32_t parm, + uint32_t *status) +{ + register unsigned int reg1 asm ("1") = parm; + int cc; + + asm volatile( + " sigp %1,%2,0(%3)\n" + " ipm %0\n" + " srl %0,28\n" + : "=d" (cc), "+d" (reg1) : "d" (addr), "a" (order) : "cc"); + if (status && cc == 1) + *status = reg1; + return cc; +} + +static inline int sigp_busy(uint16_t addr, uint8_t order, uint32_t parm, + uint32_t *status) +{ + int cc; + + do { + cc = sigp(addr, order, parm, status); + } while (cc == SIGP_CC_BUSY); + return cc; +} + +#endif /* __ASSEMBLER__ */ +#endif /* S390_SIGP_H */ diff --git a/include/lib/util_base.h b/include/lib/util_base.h index c4b52a1..74f67f9 100644 --- a/include/lib/util_base.h +++ b/include/lib/util_base.h @@ -14,6 +14,7 @@ #include #include +#include "zt_common.h" void util_hexdump(FILE *fh, const char *tag, const void *data, int cnt); void util_hexdump_grp(FILE *fh, const char *tag, const void *data, int group, @@ -22,22 +23,6 @@ void util_print_indented(const char *str, int indent); #define UTIL_ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) -#define MIN(x, y) \ -({ \ - __typeof__(x) _x = (x); \ - __typeof__(y) _y = (y); \ - \ - _x < _y ? _x : _y; \ -}) - -#define MAX(x, y) \ -({ \ - __typeof__(x) _x = (x); \ - __typeof__(y) _y = (y); \ - \ - _x > _y ? _x : _y; \ -}) - static inline void util_ptr_vec_free(void **ptr_vec, int count) { int i; diff --git a/include/lib/zt_common.h b/include/lib/zt_common.h index c486428..8f4f164 100644 --- a/include/lib/zt_common.h +++ b/include/lib/zt_common.h @@ -15,6 +15,21 @@ #define STRINGIFY_1(x) #x #define STRINGIFY(x) STRINGIFY_1(x) +/* Use this macro to make constant macros usable in both assembler and + * C code. + * + * Usage example: + * #define IMAGE_ENTRY _AC(0x10000, UL) + */ +#ifdef __ASSEMBLER__ +#define _AC(X, TYPE) X +#else +#define _AC(X, TYPE) X##TYPE +#endif + + +#ifndef __ASSEMBLER__ + #ifdef UNUSED #elif defined(__GNUC__) # define UNUSED(x) UNUSED_ ## x __attribute__((unused)) @@ -22,6 +37,17 @@ # define UNUSED(x) x #endif +#ifdef STATIC_ASSERT +#elif defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ >= 5) +# define STATIC_ASSERT(test) _Static_assert((test), "(" #test ") failed"); +#else +# define STATIC_ASSERT(test) +#endif + +#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a) - 1) +#define __ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask)) +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + #define RELEASE_STRING STRINGIFY (S390_TOOLS_RELEASE) #define TOOLS_LIBDIR STRINGIFY (S390_TOOLS_LIBDIR) #define TOOLS_SYSCONFDIR STRINGIFY (S390_TOOLS_SYSCONFDIR) @@ -31,6 +57,37 @@ #define __packed __attribute__((packed)) #define __aligned(x) __attribute__((aligned(x))) #define __may_alias __attribute__((may_alias)) +#define __section(x) __attribute__((__section__(#x))) +#define __noinline __attribute__((__noinline__)) +/* The Linux kernel (in stddef.h) and glibc (sys/cdefs.h) define + * __always_inline. Therefore undefine it first to allow the headers + * to be included first. + */ +#undef __always_inline +#define __always_inline inline __attribute__((always_inline)) + +#define __pa32(x) ((uint32_t)(unsigned long)(x)) +#define __pa(x) ((unsigned long)(x)) + +#define barrier() __asm__ __volatile__("": : :"memory") + +#undef MIN +#define MIN(x, y) \ + ({ \ + __typeof__(x) _x = (x); \ + __typeof__(y) _y = (y); \ + \ + _x < _y ? _x : _y; \ + }) + +#undef MAX +#define MAX(x, y) \ + ({ \ + __typeof__(x) _x = (x); \ + __typeof__(y) _y = (y); \ + \ + _x > _y ? _x : _y; \ + }) typedef unsigned long long u64; typedef signed long long s64; @@ -41,4 +98,5 @@ typedef signed short int s16; typedef unsigned char u8; typedef signed char s8; +#endif /* __ASSEMBLER__ */ #endif /* LIB_ZT_COMMON_H */ diff --git a/libutil/util_panic_example.c b/libutil/util_panic_example.c index a676bce..aa9c159 100644 --- a/libutil/util_panic_example.c +++ b/libutil/util_panic_example.c @@ -15,10 +15,10 @@ #include #include +#include "lib/zt_common.h" #include "lib/util_panic.h" /* Make functions noinline to have a nice backtrace */ -#define __noinline __attribute__((noinline)) /* * Test util_panic() diff --git a/netboot/Makefile.pxelinux.0 b/netboot/Makefile.pxelinux.0 index 093e0cf..41296cb 100644 --- a/netboot/Makefile.pxelinux.0 +++ b/netboot/Makefile.pxelinux.0 @@ -33,8 +33,8 @@ pxelinux.initramfs: $(BBINSTALL) $(BUSYBOX)/_install: wget https://busybox.net/downloads/$(BUSYBOX).tar.bz2 tar xjf $(BUSYBOX).tar.bz2 - make -C $(BUSYBOX) defconfig - make -C $(BUSYBOX) install + $(MAKE) -C $(BUSYBOX) defconfig + $(MAKE) -C $(BUSYBOX) install install: diff --git a/qethconf/qethconf.8 b/qethconf/qethconf.8 index deb58a6..0669384 100644 --- a/qethconf/qethconf.8 +++ b/qethconf/qethconf.8 @@ -58,7 +58,7 @@ e.g. \-xc0a80a26 .TP \fIMASKBITS\fR Number of bits set in the network mask. -This allows to specify an address range, e.g. 192.168.10.0/24. +Network masks can be used to specify address ranges, e.g. 192.168.10.0/24. This subparameter is only valid for function IPA. .TP \fIINTERFACE\fR diff --git a/vmur/vmur.8 b/vmur/vmur.8 index 6fd3a21..f0a03de 100644 --- a/vmur/vmur.8 +++ b/vmur/vmur.8 @@ -21,7 +21,7 @@ vmur \- Work with z/VM spool file queues . . .SH DESCRIPTION -The \*v program allows to read, create, list, purge, or order files +With the \*v program you can read, create, list, purge, or order files on the z/VM spool files queues (RDR, PUN, and PRT). \*v supports the following commands: diff --git a/zconf/qeth/misc.h b/zconf/qeth/misc.h index d9df17a..3f43327 100644 --- a/zconf/qeth/misc.h +++ b/zconf/qeth/misc.h @@ -12,8 +12,6 @@ #include -#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) - char *misc_link_target(const char *fmt, ...); bool misc_str_in_list(const char *str, const char *strings[], int array_size); int misc_argz_add_from_file(char **argz, size_t *argz_len, diff --git a/zdev/include/misc.h b/zdev/include/misc.h index 8103433..6f2106d 100644 --- a/zdev/include/misc.h +++ b/zdev/include/misc.h @@ -15,10 +15,10 @@ #include #include +#include "lib/zt_common.h" #include "lib/util_list.h" #include "exit_code.h" -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define SCOPE_ACTIVE(x) ((x) & config_active ? 1 : 0) #define SCOPE_PERSISTENT(x) ((x) & config_persistent ? 1 : 0) #define SCOPE_AUTOCONF(x) ((x) & config_autoconf ? 1 : 0) diff --git a/zdump/opts.c b/zdump/opts.c index 387c102..81ff7aa 100644 --- a/zdump/opts.c +++ b/zdump/opts.c @@ -167,7 +167,7 @@ static void argv_fuse_set(char **argv, int argc) static void action_set(enum zg_action action) { if (g.opts.action_specified) - ERR_EXIT("Please specifiy only one of the \"-i\", \"-d\", " + ERR_EXIT("Please specify only one of the \"-i\", \"-d\", " "\"-m\" or \"-u\" option"); g.opts.action = action; g.opts.action_specified = 1; @@ -183,7 +183,7 @@ static void verify_opts(void) g.opts.action != ZG_ACTION_STDOUT && g.opts.action != ZG_ACTION_DUMP_INFO) ERR_EXIT("The \"--select\" option can only be " - "specifed for info, mount, or copy"); + "specified for info, mount, or copy"); } if (!g.opts.fmt_specified) return; diff --git a/zdump/zg.c b/zdump/zg.c index 869c361..378cbf9 100644 --- a/zdump/zg.c +++ b/zdump/zg.c @@ -432,7 +432,7 @@ char *zg_devnode_create(dev_t dev) char *file_path; unsigned int i; - for (i = 0; i < ARRAY_ELEMENT_CNT(dir_vec); i++) { + for (i = 0; i < ARRAY_SIZE(dir_vec); i++) { if (dir_vec[i] == NULL) continue; file_path = devnode_create_dir(dir_vec[i], dev); diff --git a/zdump/zg.h b/zdump/zg.h index b97a3d3..efde05b 100644 --- a/zdump/zg.h +++ b/zdump/zg.h @@ -122,10 +122,7 @@ do { \ * Misc */ #define PAGE_SIZE 4096UL -#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1) -#define __ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask)) #define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE) -#define ARRAY_ELEMENT_CNT(x) (sizeof(x) / sizeof(x[0])) #define ROUNDUP(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) static inline u32 zg_csum_partial(const void *buf, int len, u32 sum) diff --git a/zipl/boot/.gitignore b/zipl/boot/.gitignore new file mode 100644 index 0000000..352157d --- /dev/null +++ b/zipl/boot/.gitignore @@ -0,0 +1,2 @@ +*.lds +*.lds.d diff --git a/zipl/boot/Makefile b/zipl/boot/Makefile index 0faf3ec..359189e 100644 --- a/zipl/boot/Makefile +++ b/zipl/boot/Makefile @@ -1,7 +1,10 @@ # Common definitions include ../../common.mak -CFLAGS_BOOT = $(NO_PIE_CFLAGS) -Os -g -I../include -D__ASSEMBLY__ \ +INCLUDE_PATHS := $(rootdir)/zipl/include $(rootdir)/include +INCLUDE_PARMS := $(addprefix -I,$(INCLUDE_PATHS)) + +ALL_CFLAGS = $(NO_PIE_CFLAGS) -Os -g $(INCLUDE_PARMS) \ -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \ -fno-builtin -ffreestanding -fno-asynchronous-unwind-tables \ -fno-delete-null-pointer-checks \ @@ -21,32 +24,43 @@ all: data.o data.h tape0.bin stage3.bin %: %.S %.o: %.S - $(CC) $(CFLAGS_BOOT) -c -o $@ $< + $(CC) $(ALL_CFLAGS) -c -o $@ $< %.o: %.c - $(CC) $(CFLAGS_BOOT) -c -o $@ $< + $(CC) $(ALL_CFLAGS) -c -o $@ $< + +# Dependencies for the .lds generation +sources_lds_S = $(wildcard *.lds.S) +dependencies_lds_S = $(sources_lds_S:%.lds.S=.%.lds.d) +# Include all ".lds.d" dependency files for all make targets except for "clean" +ifneq ($(MAKECMDGOALS),clean) +-include $(dependencies_lds_S) +endif + +%.lds: %.lds.S + $(CPP) -Wp,-MD,.$@.d,-MT,$@ $(INCLUDE_PARMS) -P -C -o $@ $< eckd2dump_sv.exec: \ head.o stage2dump.o cio.o eckd2dump.o eckd2dump_sv.o \ - libc.o sclp.o entry.o + libc.o ebcdic.o sclp.o entry.o stage2.lds eckd2dump_mv.exec: \ head.o stage2dump.o cio.o eckd2dump.o eckd2dump_mv.o \ - libc.o sclp.o entry.o + libc.o ebcdic.o sclp.o entry.o stage2.lds fba2dump.exec: \ head.o stage2dump.o cio.o fba2dump.o \ - libc.o sclp.o entry.o + libc.o ebcdic.o sclp.o entry.o stage2.lds tape2dump.exec: \ head.o stage2dump.o cio.o tape2dump.o \ - libc.o sclp.o entry.o -eckd2.exec: head.o stage2.o cio.o eckd2.o libc.o menu.o sclp.o \ - kdump2.o kdump.o entry.o -fba2.exec: head.o stage2.o cio.o fba2.o libc.o menu.o sclp.o \ - kdump2.o kdump.o entry.o -stage3.exec: head.o stage3.o kdump3.o libc.o sclp.o sclp_stage3.o \ - kdump.o entry.o stage3.lds + libc.o ebcdic.o sclp.o entry.o stage2.lds +eckd2.exec: head.o stage2.o cio.o eckd2.o libc.o ebcdic.o menu.o sclp.o \ + kdump2.o kdump.o entry.o stage2.lds +fba2.exec: head.o stage2.o cio.o fba2.o libc.o ebcdic.o menu.o sclp.o \ + kdump2.o kdump.o entry.o stage2.lds +stage3.exec: head.o stage3.o kdump3.o libc.o ebcdic.o ebcdic_conv.o sclp.o \ + sclp_stage3.o kdump.o entry.o stage3.lds %.exec: %.o - @STAGE=$$( \ + STAGE=$$( \ echo $@ | awk ' \ match($$0,/[0-9]+b*/){ \ print substr($$0,RSTART,RLENGTH) \ @@ -59,7 +73,7 @@ stage3.exec: head.o stage3.o kdump3.o libc.o sclp.o sclp_stage3.o \ 2) SFLAGS="$(NO_PIE_LINKFLAGS) -nostdlib -Wl,-T,stage2.lds";; \ 3) SFLAGS="$(NO_PIE_LINKFLAGS) -nostdlib -Wl,-T,stage3.lds";; \ esac; \ - $(LINK) $$SFLAGS -m64 $^ -o $@ + $(LINK) $$SFLAGS -m64 $(filter %.o, $^) -o $@ %.bin: %.exec $(OBJCOPY) -O binary \ @@ -70,6 +84,7 @@ stage3.exec: head.o stage3.o kdump3.o libc.o sclp.o sclp_stage3.o \ --only-section=.ex_table \ --only-section=.data \ --only-section=.rodata.str1.2 \ + --only-section=.rodata.cst8 \ --only-section=.rodata \ --only-section=.stage2dump.tail \ --only-section=.eckd2dump_mv.tail \ @@ -103,6 +118,6 @@ data.h: data.o clean: rm -f *.o *.exec *.bin $(FILES) data.o data.h tape0.bin *.xxx *.yyy \ - stage3.bin + stage3.bin *.lds *.lds.d .PHONY: all clean diff --git a/zipl/boot/cio.c b/zipl/boot/cio.c index 453feaf..8a0d140 100644 --- a/zipl/boot/cio.c +++ b/zipl/boot/cio.c @@ -12,7 +12,7 @@ #include "cio.h" #include "error.h" #include "libc.h" -#include "s390.h" +#include "boot/s390.h" static unsigned long initial_lpm = 0x00; static const char *msg_essch = "Start subchannel failed"; diff --git a/zipl/boot/cio.h b/zipl/boot/cio.h index 0f49e08..fc39608 100644 --- a/zipl/boot/cio.h +++ b/zipl/boot/cio.h @@ -12,7 +12,7 @@ #define CIO_H #include "libc.h" -#include "s390.h" +#include "boot/s390.h" /* Condition codes */ #define CC_INITIATED 0 diff --git a/zipl/boot/ebcdic.c b/zipl/boot/ebcdic.c new file mode 100644 index 0000000..be3441b --- /dev/null +++ b/zipl/boot/ebcdic.c @@ -0,0 +1,30 @@ +/* + * EBCDIC specific functions + * + * Copyright IBM Corp. 2013, 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. + * + */ + +#include "ebcdic.h" + + +/* + * Convert ebcdic string to number with given base + */ +unsigned long ebcdic_strtoul(char *nptr, char **endptr, int base) +{ + unsigned long val = 0; + + while (ebcdic_isdigit(*nptr)) { + if (val != 0) + val *= base; + val += *nptr - 0xf0; + nptr++; + } + if (endptr) + *endptr = (char *)nptr; + return val; +} diff --git a/zipl/boot/ebcdic.h b/zipl/boot/ebcdic.h new file mode 100644 index 0000000..e5392dd --- /dev/null +++ b/zipl/boot/ebcdic.h @@ -0,0 +1,45 @@ +/* + * EBCDIC specific functions + * + * Copyright IBM Corp. 2013, 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 EBCDIC_H +#define EBCDIC_H + +#include "lib/zt_common.h" + + +#ifndef __ASSEMBLER__ + +unsigned long ebcdic_strtoul(char *, char **, int); + +static __always_inline int ecbdic_isspace(char c) +{ + return (c == 0x40) || (c == 0x05) || (c == 0x15) || (c == 0x25) || + (c == 0x0b) || (c == 0x0c) || (c == 0x0d); +} + +static __always_inline int ebcdic_isdigit(char c) +{ + return (c >= 0xf0) && (c <= 0xf9); +} + +static __always_inline int ebcdic_isupper(char c) +{ + return (c >= 0xC1 && c <= 0xC9) || (c >= 0xD1 && c <= 0xD9) || + (c >= 0xE2 && c <= 0xE9); +} + +static __always_inline char ebcdic_tolower(char c) +{ + if (ebcdic_isupper(c)) + c -= 0x40; + return c; +} +#endif /* __ASSEMBLER__ */ +#endif /* EBCDIC_H */ diff --git a/zipl/boot/ebcdic_conv.c b/zipl/boot/ebcdic_conv.c new file mode 100644 index 0000000..7fc6086 --- /dev/null +++ b/zipl/boot/ebcdic_conv.c @@ -0,0 +1,167 @@ +/* + * EBCDIC conversion functions + * + * Copyright IBM Corp. 2013, 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. + * + */ + +#include "ebcdic_conv.h" +#include "libc.h" +#include "boot/s390.h" + + +static unsigned char ebcdic_037[256] = { +/* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */ + 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, +/* 0x08 -GE -SPS -RPT VT FF CR SO SI */ + 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, +/* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC + -ENP ->LF */ + 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, +/* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB + -IUS */ + 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, +/* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC + -INP */ + 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, +/* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL + -SW */ + 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, +/* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */ + 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, +/* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */ + 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, +/* 0x40 SP RSP ä ---- */ + 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, +/* 0x48 . < ( + | */ + 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, +/* 0x50 & ---- */ + 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, +/* 0x58 ß ! $ * ) ; */ + 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA, +/* 0x60 - / ---- Ä ---- ---- ---- */ + 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, +/* 0x68 ---- , % _ > ? */ + 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, +/* 0x70 --- ---- ---- ---- ---- ---- ---- */ + 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, +/* 0x78 * ` : # @ ' = " */ + 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, +/* 0x80 * a b c d e f g */ + 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, +/* 0x88 h i ---- ---- ---- */ + 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, +/* 0x90 ° j k l m n o p */ + 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, +/* 0x98 q r ---- ---- */ + 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, +/* 0xA0 ~ s t u v w x */ + 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, +/* 0xA8 y z ---- ---- ---- ---- */ + 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07, +/* 0xB0 ^ ---- § ---- */ + 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, +/* 0xB8 ---- [ ] ---- ---- ---- ---- */ + 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07, +/* 0xC0 { A B C D E F G */ + 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, +/* 0xC8 H I ---- ö ---- */ + 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, +/* 0xD0 } J K L M N O P */ + 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, +/* 0xD8 Q R ---- ü */ + 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, +/* 0xE0 \ S T U V W X */ + 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, +/* 0xE8 Y Z ---- Ö ---- ---- ---- */ + 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, +/* 0xF0 0 1 2 3 4 5 6 7 */ + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, +/* 0xF8 8 9 ---- ---- Ü ---- ---- ---- */ + 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 +}; + +static unsigned char ebcdic_500[256] = { +/* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */ + 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, +/* 0x08 -GE -SPS -RPT VT FF CR SO SI */ + 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, +/* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC + -ENP ->LF */ + 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, +/* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB + -IUS */ + 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, +/* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC + -INP */ + 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, +/* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL + -SW */ + 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, +/* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */ + 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, +/* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */ + 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, +/* 0x40 SP RSP ä ---- */ + 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, +/* 0x48 . < ( + | */ + 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, +/* 0x50 & ---- */ + 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, +/* 0x58 ß ! $ * ) ; */ + 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA, +/* 0x60 - / ---- Ä ---- ---- ---- */ + 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, +/* 0x68 ---- , % _ > ? */ + 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, +/* 0x70 --- ---- ---- ---- ---- ---- ---- */ + 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, +/* 0x78 * ` : # @ ' = " */ + 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, +/* 0x80 * a b c d e f g */ + 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, +/* 0x88 h i ---- ---- ---- */ + 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, +/* 0x90 ° j k l m n o p */ + 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, +/* 0x98 q r ---- ---- */ + 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, +/* 0xA0 ~ s t u v w x */ + 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, +/* 0xA8 y z ---- ---- ---- ---- */ + 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07, +/* 0xB0 ^ ---- § ---- */ + 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, +/* 0xB8 ---- [ ] ---- ---- ---- ---- */ + 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07, +/* 0xC0 { A B C D E F G */ + 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, +/* 0xC8 H I ---- ö ---- */ + 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, +/* 0xD0 } J K L M N O P */ + 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, +/* 0xD8 Q R ---- ü */ + 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, +/* 0xE0 \ S T U V W X */ + 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, +/* 0xE8 Y Z ---- Ö ---- ---- ---- */ + 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, +/* 0xF0 0 1 2 3 4 5 6 7 */ + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, +/* 0xF8 8 9 ---- ---- Ü ---- ---- ---- */ + 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 +}; + +void ebcdic_to_ascii(unsigned char *target, const unsigned char *source, + unsigned int l) +{ + unsigned char *ebc; + unsigned int i; + + ebc = is_zvm() ? ebcdic_037 : ebcdic_500; + for (i = 0; i < l; i++) + target[i] = ebc[source[i]]; +} diff --git a/zipl/boot/ebcdic_conv.h b/zipl/boot/ebcdic_conv.h new file mode 100644 index 0000000..af7e726 --- /dev/null +++ b/zipl/boot/ebcdic_conv.h @@ -0,0 +1,21 @@ +/* + * EBCDIC conversion functions + * + * Copyright IBM Corp. 2013, 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 EBCDIC_CONV_H +#define EBCDIC_CONV_H + + +#ifndef __ASSEMBLER__ + +void ebcdic_to_ascii(unsigned char *target, const unsigned char *source, + unsigned int l); + +#endif /* __ASSEMBLER__ */ +#endif /* EBCDIC_CONV_H */ diff --git a/zipl/boot/eckd0_cdl.S b/zipl/boot/eckd0_cdl.S index c3f6f48..8a537aa 100644 --- a/zipl/boot/eckd0_cdl.S +++ b/zipl/boot/eckd0_cdl.S @@ -13,9 +13,13 @@ # The 1st CCW reads stage 1 IPL record. # The 2nd CCW tics to the loaded stage 1 IPL record. # + +#include "boot/loaders_layout.h" +#include "boot/s390.h" + .org 0x0 .globl _start _start: - .long 0x00080000,0x80002018 # PSW :the first 24 byte are loaded by IPL + .quad PSW_LOAD+STAGE2_ENTRY # PSW :the first 24 byte are loaded by IPL .long 0x06000018,0x60000068 # read IPL record 2 .long 0x08000018,0x00000000 # TIC to stage 1 diff --git a/zipl/boot/eckd0_ldl.S b/zipl/boot/eckd0_ldl.S index 31d3510..78bf7eb 100644 --- a/zipl/boot/eckd0_ldl.S +++ b/zipl/boot/eckd0_ldl.S @@ -14,9 +14,13 @@ # The 2nd CCW reads stage 1 IPL record (re-read record 1) and continues with # command chaining at address 0x18 # + +#include "boot/loaders_layout.h" +#include "boot/s390.h" + .org 0x0 .globl _start _start: - .long 0x00080000,0x80002018 # PSW :the first 24 byte are loaded by IPL + .quad PSW_LOAD+STAGE2_ENTRY # PSW :the first 24 byte are loaded by IPL .long 0x16002000,0x60000016 # Read record zero .long 0x06000000,0x60000080 # Read IPL record 1 again diff --git a/zipl/boot/eckd2.c b/zipl/boot/eckd2.c index 3943d45..7ac6da2 100644 --- a/zipl/boot/eckd2.c +++ b/zipl/boot/eckd2.c @@ -10,7 +10,7 @@ */ #include "eckd.h" -#include "s390.h" +#include "boot/s390.h" #include "stage2.h" int extract_length(void *data) diff --git a/zipl/boot/eckd2dump.c b/zipl/boot/eckd2dump.c index 3cfa158..28598ad 100644 --- a/zipl/boot/eckd2dump.c +++ b/zipl/boot/eckd2dump.c @@ -12,7 +12,7 @@ #include "cio.h" #include "eckd2dump.h" #include "error.h" -#include "s390.h" +#include "boot/s390.h" #include "stage2dump.h" #define ECKD_CCW_LOCATE_RECORD 0x47 diff --git a/zipl/boot/eckd2dump_mv.c b/zipl/boot/eckd2dump_mv.c index 9fb25fd..964db10 100644 --- a/zipl/boot/eckd2dump_mv.c +++ b/zipl/boot/eckd2dump_mv.c @@ -9,6 +9,8 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include "lib/zt_common.h" + #include "eckd2dump.h" #include "error.h" #include "stage2dump.h" @@ -19,8 +21,7 @@ /* * Magic number at start of dump record */ -uint64_t magic __attribute__((section(".stage2.head"))) - = 0x584d554c54363401ULL; /* XMULT64, version 1 */ +uint64_t __section(.stage2.head) magic = 0x584d554c54363401ULL; /* XMULT64, version 1 */ /* * Parameter format for ECKD MV dumper (13 bytes): @@ -59,8 +60,7 @@ struct mvdump_parm_table { (MAX_DUMP_VOLUMES * (sizeof(struct mvdump_param) + 1))]; } __packed; -static struct mvdump_parm_table mvdump_table - __attribute__((section(".eckd2dump_mv.tail"))); +static struct mvdump_parm_table __section(.eckd2dump_mv.tail) mvdump_table; static int volnr_current; diff --git a/zipl/boot/eckd2dump_sv.c b/zipl/boot/eckd2dump_sv.c index 047cc22..84e783b 100644 --- a/zipl/boot/eckd2dump_sv.c +++ b/zipl/boot/eckd2dump_sv.c @@ -9,6 +9,8 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include "lib/zt_common.h" + #include "eckd2dump.h" #include "error.h" #include "stage2dump.h" @@ -16,8 +18,7 @@ /* * Magic number at start of dump record */ -uint64_t magic __attribute__((section(".stage2.head"))) = - 0x5845434b44363401ULL; /* "XECKD64", version 1 */ +uint64_t __section(.stage2.head) magic = 0x5845434b44363401ULL; /* "XECKD64", version 1 */ /* * ECKD parameter block passed by zipl diff --git a/zipl/boot/error.h b/zipl/boot/error.h index 715973e..63eae4b 100644 --- a/zipl/boot/error.h +++ b/zipl/boot/error.h @@ -71,4 +71,10 @@ #define ENOTIME 0x00004605 /* The zipl time stamps do not match */ #define ENOMSS 0x00004606 /* Could not enable MSS */ +/* + * PV error codes + */ +#define ENOPV 0x00004607 /* No support for PV */ +#define EPV 0x00004608 /* PV error */ + #endif /* ERROR_H */ diff --git a/zipl/boot/fba0.S b/zipl/boot/fba0.S index d8f4769..1119f3d 100644 --- a/zipl/boot/fba0.S +++ b/zipl/boot/fba0.S @@ -15,10 +15,14 @@ # The 1st CCW reads block 0 again with READ IPL # The 2nd CCW tics to the re-loaded stage 1 IPL record. # + +#include "boot/loaders_layout.h" +#include "boot/s390.h" + .org 0x0 .globl _start _start: - .long 0x00080000,0x80002018 # The first 24 byte are loaded + .quad PSW_LOAD+STAGE2_ENTRY # The first 24 byte are loaded .long 0x02000000,0x60000000+.Lend # by ipl to addresses 0-23. .long 0x08000000+.Ltic,0x40000000 # Tic to continuation # diff --git a/zipl/boot/fba2.c b/zipl/boot/fba2.c index 7d3b5cc..1a570c2 100644 --- a/zipl/boot/fba2.c +++ b/zipl/boot/fba2.c @@ -11,7 +11,7 @@ #include "fba.h" #include "libc.h" -#include "s390.h" +#include "boot/s390.h" int extract_length(void *data) { struct linear_blockptr *blockptr = (struct linear_blockptr *)data; diff --git a/zipl/boot/fba2dump.c b/zipl/boot/fba2dump.c index 9a8f5fe..100556b 100644 --- a/zipl/boot/fba2dump.c +++ b/zipl/boot/fba2dump.c @@ -9,19 +9,19 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include "lib/zt_common.h" #include "error.h" #include "fba.h" #include "stage2dump.h" -#define BLK_PWRT 64 /* Blocks per write */ +#define BLK_PWRT 64U /* Blocks per write */ #define BLK_SIZE 0x200 /* FBA block size */ #define BLK_PER_PAGE (PAGE_SIZE / BLK_SIZE) /* FBA blocks per page */ /* * Magic number at start of dump record */ -uint64_t magic __attribute__((section(".stage2.head"))) - = 0x5844464241363401ULL; /* XDFBA64, version 1 */ +uint64_t __section(.stage2.head) magic = 0x5844464241363401ULL; /* XDFBA64, version 1 */ /* * FBA dump device partition specification diff --git a/zipl/boot/head.S b/zipl/boot/head.S index cc68b8a..f2395f6 100644 --- a/zipl/boot/head.S +++ b/zipl/boot/head.S @@ -9,16 +9,18 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include "boot/sigp.h" + .section .text.start .globl _start _start: basr %r13,0 0: la %r7,2 /* First try code 2: */ la %r6,0 /* 64 bit psws are restored */ - sigp %r7,%r6,0x12 /* Switch to 64 bit */ + sigp %r7,%r6,SIGP_SET_ARCHITECTURE /* Switch to 64 bit */ bc 8,.Lswitched_64-0b(%r13) /* Accepted ? */ la %r7,1 /* Failed - try code 1 */ - sigp %r7,%r6,0x12 /* Switch to 64 bit */ + sigp %r7,%r6,SIGP_SET_ARCHITECTURE /* Switch to 64 bit */ .Lswitched_64: sam64 /* Switch to 64 bit addr mode */ basr %r13,0 diff --git a/zipl/boot/kdump.c b/zipl/boot/kdump.c index fc0281e..eb3ae07 100644 --- a/zipl/boot/kdump.c +++ b/zipl/boot/kdump.c @@ -13,7 +13,7 @@ #include "kdump.h" #include "libc.h" #include "menu.h" -#include "s390.h" +#include "boot/s390.h" void kdump_failed(unsigned long reason) { diff --git a/zipl/boot/kdump.h b/zipl/boot/kdump.h index f6fae1c..f81d20c 100644 --- a/zipl/boot/kdump.h +++ b/zipl/boot/kdump.h @@ -14,7 +14,7 @@ #define KDUMP_H #include "libc.h" -#include "s390.h" +#include "boot/s390.h" #define OS_INFO_VERSION_MAJOR_SUPPORTED 1 #define OS_INFO_MAGIC 0x4f53494e464f535aULL /* OSINFOSZ */ diff --git a/zipl/boot/kdump3.c b/zipl/boot/kdump3.c index 3fff01d..5d118dd 100644 --- a/zipl/boot/kdump3.c +++ b/zipl/boot/kdump3.c @@ -100,9 +100,9 @@ void kdump_stage3(void) { unsigned long crash_base, crash_size; - if (!(stage3_flags & STAGE3_FLAG_KDUMP)) + if (!(_stage3_parms.flags & STAGE3_FLAG_KDUMP)) return; - if (!(stage3_flags & STAGE3_FLAG_SCSI)) + if (!(_stage3_parms.flags & STAGE3_FLAG_SCSI)) kdump_stage3_dasd(&crash_base, &crash_size); else kdump_stage3_scsi(&crash_base, &crash_size); diff --git a/zipl/boot/libc.c b/zipl/boot/libc.c index bd88d15..3442a86 100644 --- a/zipl/boot/libc.c +++ b/zipl/boot/libc.c @@ -9,11 +9,16 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include "libc.h" + #include +#include "lib/zt_common.h" +#include "boot/s390.h" + #include "error.h" -#include "libc.h" #include "sclp.h" +#include "ebcdic.h" extern char __heap_start[]; extern char __heap_stop[]; @@ -125,24 +130,6 @@ int strncmp(const char *s1, const char *s2, unsigned long count) return 0; } -/* - * Convert ebcdic string to number with given base - */ -unsigned long ebcstrtoul(char *nptr, char **endptr, int base) -{ - unsigned long val = 0; - - while (ebc_isdigit(*nptr)) { - if (val != 0) - val *= base; - val += *nptr - 0xf0; - nptr++; - } - if (endptr) - *endptr = (char *) nptr; - return val; -} - static int skip_atoi(const char **c) { int i = 0; @@ -279,10 +266,10 @@ static char *number(char *buf, char *end, unsigned long val, /* prepare string in reverse order */ len = 0; - while (val) { + do { tmp[len++] = vec[val % spec->base]; val /= spec->base; - } + } while (val); if (len > precision) precision = len; @@ -419,8 +406,11 @@ void printf(const char *fmt, ...) buf[LINE_LENGTH - 2] = '.'; buf[LINE_LENGTH - 3] = '.'; } - sclp_print(buf); va_end(va); + sclp_print(buf); +#ifdef ENABLE_SCLP_ASCII + sclp_print_ascii(buf); +#endif /* ENABLE_SCLP_ASCII */ } /* @@ -471,7 +461,7 @@ void pgm_check_handler_fn(void) libc_stop(psw_old->addr); } -__attribute__ ((noinline)) void load_wait_psw(uint64_t psw_mask, struct psw_t *psw) +void __noinline load_wait_psw(uint64_t psw_mask, struct psw_t *psw) { struct psw_t wait_psw = { .mask = psw_mask, .addr = 0 }; struct psw_t old_psw, *wait_psw_ptr = &wait_psw; @@ -511,7 +501,7 @@ void initialize(void) /* * Load disabled wait PSW with reason code in address field */ -void libc_stop(unsigned long reason) +void __noreturn libc_stop(unsigned long reason) { struct psw_t psw; diff --git a/zipl/boot/libc.h b/zipl/boot/libc.h index 68ecd00..7c4df72 100644 --- a/zipl/boot/libc.h +++ b/zipl/boot/libc.h @@ -11,7 +11,8 @@ #ifndef LIBC_H #define LIBC_H -#define NULL ((void *) 0) +#include +#include #define EPERM 1 /* Operation not permitted */ #define ENOENT 2 /* No such file or directory */ @@ -42,11 +43,6 @@ #define MIB (1024ULL * 1024) #define LINE_LENGTH 80 /* max line length printed by printf */ -typedef unsigned long long uint64_t; -typedef unsigned int uint32_t; -typedef unsigned short uint16_t; -typedef unsigned char uint8_t; - void printf(const char *, ...); void snprintf(char *buf, unsigned long size, const char *fmt, ...); void *memcpy(void *, const void *, unsigned long); @@ -54,16 +50,23 @@ void *memmove(void *, const void *, unsigned long); void *memset(void *, int c, unsigned long); char *strcat(char *, const char *); int strncmp(const char *, const char *, unsigned long); -unsigned long ebcstrtoul(char *, char **, int); int strlen(const char *); char *strcpy(char *, const char *); unsigned long get_zeroed_page(void); void free_page(unsigned long); void initialize(void); -void libc_stop(unsigned long) __attribute__((noreturn)); +void libc_stop(unsigned long); void start(void); void pgm_check_handler(void); void pgm_check_handler_fn(void); +void panic_notify(unsigned long reason); + +#define panic(reason, ...) \ + do { \ + printf(__VA_ARGS__); \ + panic_notify(reason); \ + libc_stop(reason); \ + } while (0) static inline int isdigit(int c) { @@ -75,28 +78,4 @@ static inline int isspace(char c) return (c == 32) || (c >= 9 && c <= 13); } -static inline int ebc_isspace(char c) -{ - return (c == 0x40) || (c == 0x05) || (c == 0x15) || (c == 0x25) || - (c == 0x0b) || (c == 0x0c) || (c == 0x0d); -} - -static inline int ebc_isdigit(char c) -{ - return (c >= 0xf0) && (c <= 0xf9); -} - -static inline int ebc_isupper(char c) -{ - return (c >= 0xC1 && c <= 0xC9) || (c >= 0xD1 && c <= 0xD9) || - (c >= 0xE2 && c <= 0xE9); -} - -static inline char ebc_tolower(char c) -{ - if (ebc_isupper(c)) - c -= 0x40; - return c; -} - #endif /* LIBC_H */ diff --git a/zipl/boot/menu.c b/zipl/boot/menu.c index 35ac4de..1e40c08 100644 --- a/zipl/boot/menu.c +++ b/zipl/boot/menu.c @@ -3,7 +3,7 @@ * * Bootmenu Subroutines * - * Copyright IBM Corp. 2013, 2017 + * Copyright IBM Corp. 2013, 2018 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. @@ -12,6 +12,9 @@ #include "libc.h" #include "menu.h" #include "sclp.h" +#include "ebcdic.h" +#include "boot/linux_layout.h" +#include "boot/loaders_layout.h" static const char *msg_econfig = "Error: undefined configuration\n"; @@ -27,7 +30,6 @@ static void menu_prompt(int timeout) static int menu_read(void) { char *temp_area = (char *)get_zeroed_page(); - uint16_t *configs = __stage2_params.config; int timeout, rc, i, count = 0; char *endptr; int value; @@ -56,11 +58,11 @@ static int menu_read(void) /* input under zVM needs to be converted to lower case */ if (is_zvm()) for (i = 0; i < count; i++) - temp_area[i] = ebc_tolower(temp_area[i]); - value = ebcstrtoul((char *)temp_area, &endptr, 10); + temp_area[i] = ebcdic_tolower(temp_area[i]); + value = ebcdic_strtoul((char *)temp_area, &endptr, 10); if ((endptr != temp_area) && (value < BOOT_MENU_ENTRIES - 1) && - (configs[value] != 0)) { + (__stage2_params.config[value] != 0)) { /* valid config found - finish */ break; } else { @@ -80,14 +82,13 @@ out_free_page: static int menu_list(void) { - uint16_t *configs = __stage2_params.config; char *name; int i; for (i = 0; i < BOOT_MENU_ENTRIES; i++) { - if (configs[i] == 0) + if (__stage2_params.config[i] == 0) continue; - name = configs[i] + ((void *)&__stage2_params); + name = __stage2_params.config[i] + ((void *)&__stage2_params); printf("%s\n", name); if (i == 0) printf("\n"); @@ -114,7 +115,7 @@ static int menu_param(unsigned long *value) int i; if (!sclp_param(loadparm)) - *value = ebcstrtoul(loadparm, &endptr, 10); + *value = ebcdic_strtoul(loadparm, &endptr, 10); /* got number, done */ if (endptr != loadparm) @@ -123,7 +124,7 @@ static int menu_param(unsigned long *value) /* no number, check for keyword */ i = 0; /* skip leading whitespaces */ - while (ebc_isspace(loadparm[i]) && (i < PARAM_SIZE)) + while ((i < PARAM_SIZE) && ecbdic_isspace(loadparm[i])) i++; if (!strncmp(&loadparm[i], "PROMPT", 6)) { @@ -136,7 +137,6 @@ static int menu_param(unsigned long *value) int menu(void) { - uint16_t *configs = __stage2_params.config; unsigned long value = 0; char *cmd_line_extra; char endstring[15]; @@ -178,11 +178,11 @@ int menu(void) boot: /* sanity - config entry not valid */ - if (configs[value] == 0) + if (__stage2_params.config[value] == 0) panic(EINTERNAL, "%s", msg_econfig); printf("Booting %s\n", - (char *)(configs[value] + + (char *)(__stage2_params.config[value] + (void *)&__stage2_params + TEXT_OFFSET)); /* append 'BOOT_IMAGE=' to parmline */ diff --git a/zipl/boot/menu.h b/zipl/boot/menu.h index ee0f2c5..c6d688c 100644 --- a/zipl/boot/menu.h +++ b/zipl/boot/menu.h @@ -14,11 +14,6 @@ #include "stage2.h" -/* address of extra command line */ -#define COMMAND_LINE_EXTRA 0xE000 - -/* max command line length */ -#define COMMAND_LINE_SIZE 896 #define BOOT_MENU_ENTRIES 63 #define PARAM_SIZE 8 #define TEXT_OFFSET 4 diff --git a/zipl/boot/s390.h b/zipl/boot/s390.h index 6e5a7ae..886233b 100644 --- a/zipl/boot/s390.h +++ b/zipl/boot/s390.h @@ -11,14 +11,9 @@ #ifndef S390_H #define S390_H -#include "../../include/lib/zt_common.h" -#include "libc.h" +#include "lib/zt_common.h" +#include "boot/sigp.h" -#define __pa32(x) ((uint32_t)(unsigned long)(x)) -#define __pa(x) ((unsigned long)(x)) -#define MIN(x, y) ((x) < (y) ? (x) : (y)) -#define barrier() __asm__ __volatile__("": : :"memory") -#define inline inline __attribute__((always_inline)) /* * Helper macro for exception table entries @@ -195,7 +190,7 @@ struct _lowcore { /* align to the top of the prefix area */ uint8_t pad_0x1900[0x2000-0x1900]; /* 0x1900 */ -} __packed; +} __packed __aligned(8192); #define S390_lowcore (*((struct _lowcore *) 0)) @@ -213,7 +208,7 @@ do { \ libc_stop(reason); \ } while (0) -static inline int page_is_valid(unsigned long addr) +static __always_inline int page_is_valid(unsigned long addr) { unsigned long tmp; int rc; @@ -233,7 +228,7 @@ static inline int page_is_valid(unsigned long addr) return rc; } -static inline uint32_t csum_partial(const void *buf, int len, uint32_t sum) +static __always_inline uint32_t csum_partial(const void *buf, int len, uint32_t sum) { register unsigned long reg2 asm("2") = (unsigned long) buf; register unsigned long reg3 asm("3") = (unsigned long) len; @@ -261,7 +256,7 @@ static inline uint32_t csum_partial(const void *buf, int len, uint32_t sum) "i" (low), "i" (high)); \ }) -static inline void __ctl_set_bit(unsigned int cr, unsigned int bit) +static __always_inline void __ctl_set_bit(unsigned int cr, unsigned int bit) { unsigned long reg; @@ -281,7 +276,7 @@ enum diag308_subcode { DIAG308_STORE = 6, }; -static inline int diag308(unsigned long subcode, void *addr) +static __always_inline int diag308(unsigned long subcode, void *addr) { register unsigned long _addr asm("0") = (unsigned long) addr; register unsigned long _rc asm("1") = 0; @@ -294,47 +289,10 @@ static inline int diag308(unsigned long subcode, void *addr) return _rc; } -/* - * Signal Processor - */ -#define SIGP_STOP_AND_STORE_STATUS 9 -#define SIGP_SET_MULTI_THREADING 22 -#define SIGP_STORE_ASTATUS_AT_ADDRESS 23 - -#define SIGP_CC_ORDER_CODE_ACCEPTED 0 -#define SIGP_CC_BUSY 2 - -static inline int sigp(uint16_t addr, uint8_t order, uint32_t parm, - uint32_t *status) -{ - register unsigned int reg1 asm ("1") = parm; - int cc; - - asm volatile( - " sigp %1,%2,0(%3)\n" - " ipm %0\n" - " srl %0,28\n" - : "=d" (cc), "+d" (reg1) : "d" (addr), "a" (order) : "cc"); - if (status && cc == 1) - *status = reg1; - return cc; -} - -static inline int sigp_busy(uint16_t addr, uint8_t order, uint32_t parm, - uint32_t *status) -{ - int cc; - - do { - cc = sigp(addr, order, parm, status); - } while (cc == SIGP_CC_BUSY); - return cc; -} - /* * Store CPU address */ -static inline unsigned short stap(void) +static __always_inline unsigned short stap(void) { unsigned short cpu_address; @@ -345,7 +303,7 @@ static inline unsigned short stap(void) /* * Program the clock comparator */ -static inline void set_clock_comparator(uint64_t time) +static __always_inline void set_clock_comparator(uint64_t time) { asm volatile("sckc %0" : : "Q" (time)); } @@ -353,7 +311,7 @@ static inline void set_clock_comparator(uint64_t time) /* * Program the CPU timer */ -static inline void set_cpu_timer(uint64_t timer) +static __always_inline void set_cpu_timer(uint64_t timer) { asm volatile("spt %0" : : "Q" (timer)); } @@ -361,7 +319,7 @@ static inline void set_cpu_timer(uint64_t timer) /* * Get current time (store clock) */ -static inline unsigned long long get_tod_clock(void) +static __always_inline unsigned long long get_tod_clock(void) { unsigned long long clk; @@ -379,7 +337,7 @@ struct cpuid { unsigned int unused:16; } __packed __aligned(8); -static inline void get_cpu_id(struct cpuid *ptr) +static __always_inline void get_cpu_id(struct cpuid *ptr) { asm volatile("stidp %0" : "=Q" (*ptr)); } @@ -387,7 +345,7 @@ static inline void get_cpu_id(struct cpuid *ptr) /* * Check if we run under z/VM */ -static inline int is_zvm(void) +static __always_inline int is_zvm(void) { struct cpuid cpuid; @@ -405,7 +363,7 @@ typedef struct { /* * Save vector registers */ -static inline void save_vx_regs(__vector128 *vxrs) +static __always_inline void save_vx_regs(__vector128 *vxrs) { typedef struct { __vector128 _[32]; } addrtype; @@ -419,7 +377,7 @@ static inline void save_vx_regs(__vector128 *vxrs) /* * Save vector registers safe */ -static inline void save_vx_regs_safe(__vector128 *vxrs) +static __always_inline void save_vx_regs_safe(__vector128 *vxrs) { unsigned long cr0; @@ -432,7 +390,7 @@ static inline void save_vx_regs_safe(__vector128 *vxrs) #define MAX_FACILITY_BIT (256*8) /* stfle_fac_list has 256 bytes */ -static inline int __test_facility(unsigned long nr, void *facilities) +static __always_inline int __test_facility(unsigned long nr, void *facilities) { unsigned char *ptr; @@ -447,17 +405,29 @@ static inline int __test_facility(unsigned long nr, void *facilities) * That makes it easier to query facility bits with the bit number as * documented in the Principles of Operation. */ -static inline int test_facility(unsigned long nr) +static __always_inline int test_facility(unsigned long nr) { return __test_facility(nr, &S390_lowcore.stfle_fac_list); } +static __always_inline unsigned long __stfle_asm(uint64_t *stfle_fac_list, int size) +{ + register unsigned long reg0 asm("0") = size - 1; + + asm volatile( + ".insn s,0xb2b00000,0(%1)" /* stfle */ + : "+d" (reg0) + : "a" (stfle_fac_list) + : "memory", "cc"); + return reg0; +} + /** * stfle - Store facility list extended * @stfle_fac_list: array where facility list can be stored * @size: size of passed in array in double words */ -static inline void stfle(u64 *stfle_fac_list, int size) +static __always_inline void stfle(uint64_t *stfle_fac_list, int size) { unsigned long nr; @@ -470,13 +440,8 @@ static inline void stfle(u64 *stfle_fac_list, int size) memcpy(stfle_fac_list, &S390_lowcore.stfl_fac_list, 4); if (S390_lowcore.stfl_fac_list & 0x01000000) { /* More facility bits available with stfle */ - register unsigned long reg0 asm("0") = size - 1; - - asm volatile(".insn s,0xb2b00000,0(%1)" /* stfle */ - : "+d" (reg0) - : "a" (stfle_fac_list) - : "memory", "cc"); - nr = (reg0 + 1) * 8; /* # bytes stored by stfle */ + nr = __stfle_asm(stfle_fac_list, size); + nr = MIN((nr + 1) * 8, size * 8UL); } memset((char *) stfle_fac_list + nr, 0, size * 8 - nr); } diff --git a/zipl/boot/sclp.c b/zipl/boot/sclp.c index 04006b2..95229ac 100644 --- a/zipl/boot/sclp.c +++ b/zipl/boot/sclp.c @@ -9,9 +9,14 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include "libc.h" #include "error.h" -#include "s390.h" +#include "boot/s390.h" #include "sclp.h" +#include "ebcdic.h" +#ifdef ENABLE_SCLP_ASCII +# include "ebcdic_conv.h" +#endif /* ENABLE_SCLP_ASCII */ /* Perform service call. Return 0 on success, non-zero otherwise. */ static int sclp_service_call(unsigned int command, void *sccb) @@ -125,20 +130,24 @@ int sclp_setup(int initialise) switch (initialise) { case SCLP_INIT: - sccb->receive_mask = 0x80000000; - sccb->send_mask = 0x40000000; + sccb->receive_mask = SCLP_EVENT_MASK_OPCMD; + sccb->send_mask = SCLP_EVENT_MASK_MSG; break; case SCLP_DISABLE: - sccb->receive_mask = 0x0; - sccb->send_mask = 0x0; + sccb->receive_mask = SCLP_EVENT_MASK_DISABLE; + sccb->send_mask = SCLP_EVENT_MASK_DISABLE; + break; + case SCLP_LINE_ASCII_INIT: + sccb->receive_mask = SCLP_EVENT_MASK_DISABLE; + sccb->send_mask = SCLP_EVENT_MASK_MSG | SCLP_EVENT_MASK_ASCII; break; case SCLP_HSA_INIT: - sccb->receive_mask = 0x0; - sccb->send_mask = 0x40000010; + sccb->receive_mask = SCLP_EVENT_MASK_DISABLE; + sccb->send_mask = SCLP_EVENT_MASK_MSG | SCLP_EVENT_MASK_SDIAS; break; case SCLP_HSA_INIT_ASYNC: - sccb->receive_mask = 0x00000010; - sccb->send_mask = 0x40000010; + sccb->receive_mask = SCLP_EVENT_MASK_SDIAS; + sccb->send_mask = SCLP_EVENT_MASK_MSG | SCLP_EVENT_MASK_SDIAS; break; } @@ -160,6 +169,48 @@ out_free_page: return rc; } +#ifdef ENABLE_SCLP_ASCII +/* Content of @buffer must be EBCDIC encoded. The function used for + * the conversion `ebcdic_to_ascii` differentiates whether the code + * runs on z/VM or not and then selects the appropriate EBCDIC + * coding. + */ +int sclp_print_ascii(const char *buffer) +{ + struct write_sccb *sccb = NULL; + int rc, str_len = strlen(buffer); + unsigned long data_len = str_len + 1; + + /* don't overflow the sccb buffer */ + if (data_len > SCCB_MAX_DATA_LEN) + data_len = SCCB_MAX_DATA_LEN; + + sccb = (void *)get_zeroed_page(); + sccb->header.length = sizeof(struct write_sccb) - sizeof(struct mdb) + + data_len; + sccb->header.function_code = SCLP_FC_NORMAL_WRITE; + sccb->msg_buf.header.length = sizeof(struct msg_buf) - sizeof(struct mdb) + + data_len; + sccb->msg_buf.header.type = SCLP_EVENT_DATA_ASCII; + sccb->msg_buf.header.flags = 0; + ebcdic_to_ascii(sccb->msg_buf.data, + (const unsigned char *)buffer, + data_len - 1); + sccb->msg_buf.data[data_len - 1] = '\0'; + + /* SCLP command for write data */ + rc = start_sclp(SCLP_CMD_WRITE_DATA, sccb); + if (rc || sccb->header.response_code != 0x20) { + rc = 1; + goto out_free_page; + } + rc = 0; +out_free_page: + free_page((unsigned long) sccb); + return rc; +} +#endif /* ENABLE_SCLP_ASCII */ + int sclp_print(char *buffer) { struct write_sccb *sccb; diff --git a/zipl/boot/sclp.h b/zipl/boot/sclp.h index ffee587..a215dd1 100644 --- a/zipl/boot/sclp.h +++ b/zipl/boot/sclp.h @@ -13,7 +13,7 @@ #define SCLP_H #include "libc.h" -#include "s390.h" +#include "boot/s390.h" /* vector keys and ids */ #define GDS_ID_MDSMU 0x1310 @@ -28,10 +28,18 @@ #define SCLP_CMD_READ_INFO2 0x00020001 #define SCLP_CMD_READ_DATA 0x00770005 -#define PSW_EXT_MASK 0x00080000ULL -#define PSW_EXT_ADDR 0x80000000ULL -#define PSW_WAIT_MASK 0x010a0000ULL -#define PSW_WAIT_ADDR 0x00000000ULL +/* SCLP function codes */ +#define SCLP_FC_NORMAL_WRITE 0 + +/* SCLP event data types */ +#define SCLP_EVENT_DATA_ASCII 0x1a + +/* SCLP event masks */ +#define SCLP_EVENT_MASK_DISABLE 0x00000000 +#define SCLP_EVENT_MASK_SDIAS 0x00000010 +#define SCLP_EVENT_MASK_ASCII 0x00000040 +#define SCLP_EVENT_MASK_MSG 0x40000000 +#define SCLP_EVENT_MASK_OPCMD 0x80000000 #define CTL_SERVICE_SIGNAL 0x0200 #define CTL_CLOCK_COMPARATOR 0x0800 @@ -40,6 +48,11 @@ #define SCLP_DISABLE 0x1 #define SCLP_HSA_INIT 0x2 #define SCLP_HSA_INIT_ASYNC 0x3 +#define SCLP_LINE_ASCII_INIT 0x4 + +#define SCCB_SIZE PAGE_SIZE +#define SCCB_MAX_DATA_LEN (SCCB_SIZE - sizeof(struct sccb_header) \ + - sizeof(struct evbuf_header)) typedef uint32_t sccb_mask_t; @@ -53,19 +66,23 @@ struct gds_subvector { uint8_t key; } __packed; +/* Structure must not have any padding */ struct sccb_header { uint16_t length; uint8_t function_code; uint8_t control_mask[3]; uint16_t response_code; -} __packed; +}; +STATIC_ASSERT(sizeof(struct sccb_header) == 2 + 1 + 3 + 2) +/* Structure must not have any padding */ struct evbuf_header { uint16_t length; uint8_t type; uint8_t flags; uint16_t _reserved; -} __packed; +}; +STATIC_ASSERT(sizeof(struct evbuf_header) == 2 + 1 + 1 + 2) struct mto { uint16_t length; @@ -104,7 +121,10 @@ struct mdb { struct msg_buf { struct evbuf_header header; - struct mdb mdb; + union { + struct mdb mdb; + uint8_t data[0]; + }; } __packed; struct write_sccb { @@ -154,6 +174,9 @@ struct read_sccb { int start_sclp(unsigned int, void *); int sclp_setup(int); int sclp_print(char *); +# ifdef ENABLE_SCLP_ASCII +int sclp_print_ascii(const char *); +# endif /* ENABLE_SCLP_ASCII */ int sclp_param(char *); int sclp_read(unsigned long, void *, int *); int sclp_read_info(struct read_info_sccb *sccb); diff --git a/zipl/boot/sclp_stage3.c b/zipl/boot/sclp_stage3.c index 7deab4f..9acb670 100644 --- a/zipl/boot/sclp_stage3.c +++ b/zipl/boot/sclp_stage3.c @@ -9,6 +9,7 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include "libc.h" #include "sclp.h" #include "sclp_stage3.h" @@ -104,7 +105,6 @@ int sclp_hsa_get_size(unsigned long *hsa_size) sccb = (void *)get_zeroed_page(); sccb->header.length = sizeof(*sccb); - sccb->header.length = sizeof(*sccb); sccb->evbuf.header.length = sizeof(struct sdias_evbuf); sccb->evbuf.header.type = EVTYP_SDIAS; sccb->evbuf.event_qual = EQ_SIZE; diff --git a/zipl/boot/sclp_stage3.h b/zipl/boot/sclp_stage3.h index ae0c05c..d115e0d 100644 --- a/zipl/boot/sclp_stage3.h +++ b/zipl/boot/sclp_stage3.h @@ -17,9 +17,6 @@ #define SDIAS_EVSTATE_ALL_STORED 0x00 #define SDIAS_EVSTATE_PART_STORED 0x10 -#define SDIAS_EVSTATE_ALL_STORED 0x00 -#define SDIAS_EVSTATE_PART_STORED 0x10 - struct sdias_evbuf { struct evbuf_header header; uint8_t event_qual; @@ -40,10 +37,13 @@ struct sdias_evbuf { uint16_t dbs; } __packed; +/* Structure must not have any padding */ struct sdias_sccb { struct sccb_header header; struct sdias_evbuf evbuf; -} __packed; +}; +STATIC_ASSERT(sizeof(struct sdias_sccb) == + sizeof(struct sccb_header) + sizeof(struct sdias_evbuf)) int sclp_hsa_copy(void *, unsigned long, unsigned long); diff --git a/zipl/boot/stage2.c b/zipl/boot/stage2.c index 99e1bd1..78591c1 100644 --- a/zipl/boot/stage2.c +++ b/zipl/boot/stage2.c @@ -9,10 +9,13 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include "lib/zt_common.h" + #include "error.h" #include "libc.h" #include "menu.h" -#include "s390.h" +#include "boot/s390.h" +#include "boot/loaders_layout.h" #include "stage2.h" static int is_null_descriptor(disk_blockptr_t *address) @@ -141,4 +144,4 @@ void panic_notify(unsigned long UNUSED(reason)) { } -uint64_t stage2_head __attribute__((section(".stage2.head"))); +uint64_t __section(.stage2.head) stage2_head; diff --git a/zipl/boot/stage2.h b/zipl/boot/stage2.h index 8fd5bd3..b29eea1 100644 --- a/zipl/boot/stage2.h +++ b/zipl/boot/stage2.h @@ -12,15 +12,17 @@ #ifndef COMMON_H #define COMMON_H -#include "cio.h" -#include "error.h" -#include "libc.h" -#include "s390.h" +#include "lib/zt_common.h" -#define DESCR_PER_BLOCK 16 +#define DESCR_PER_BLOCK _AC(16, U) -/* ADRESS */ -#define STAGE2_DESC 0x78 + +#ifndef __ASSEMBLER__ + +#include "libc.h" +#include "boot/s390.h" +#include "cio.h" +#include "error.h" /* Layout of ECKD disk block pointer */ struct eckd_blockptr { @@ -74,6 +76,6 @@ int extract_length(void *); int is_zero_block(void *); void kdump_stage2(unsigned long); +#endif /* __ASSEMBLER__ */ #endif /* COMMON_H */ - diff --git a/zipl/boot/stage2.lds b/zipl/boot/stage2.lds.S similarity index 57% rename from zipl/boot/stage2.lds rename to zipl/boot/stage2.lds.S index 34ed014..c4c9456 100644 --- a/zipl/boot/stage2.lds +++ b/zipl/boot/stage2.lds.S @@ -25,15 +25,19 @@ * 0x5000-0x51ff eckd2dump_mv parameter block (426 bytes) */ +#include "boot/loaders_layout.h" + SECTIONS { - . = 0x78; + . = STAGE2_DESC; __stage2_desc = .; - . = 0x2000; + . = STAGE2_LOAD_ADDRESS; .stage2.head : { *(.stage2.head) } - . = 0x2018; - .text.start : { *(.text.start) } + . = STAGE2_ENTRY; + .text.start : { + *(.text.start) + } .text : { *(.text) } __ex_table_start = .; .ex_table : { *(.ex_table) } @@ -52,12 +56,38 @@ SECTIONS .bss : { *(.bss) } __bss_stop = .; - . = 0x6000; + . = STAGE2_HEAP_ADDRESS; __heap_start = .; - . = 0x9000; + .heap : { + . += STAGE2_HEAP_SIZE; + ASSERT(__heap_stop - __heap_start == STAGE2_HEAP_SIZE, + "Heap section doesn't conform to the described memory layout"); + } __heap_stop = .; - . = 0xf000; + /* Memory reserved for stage3. Use a dummy section to check if changes + * in stage3 memory layout work with stage2 and vice versa. + */ + . = STAGE3_PARAMS_ADDRESS; + .stage3 : { + . += STAGE3_PARAMS_MAXIMUM_SIZE; + + . = STAGE3_LOAD_ADDRESS - STAGE3_PARAMS_ADDRESS; + . += STAGE3_MAXIMUM_SIZE; + + . = COMMAND_LINE_EXTRA - STAGE3_PARAMS_ADDRESS; + . += COMMAND_LINE_EXTRA_SIZE; + } + + . = STAGE2_STACK_ADDRESS; + __stack_start = .; + .stack : { + . += STAGE2_STACK_SIZE; + ASSERT(__stack_end - __stack_start == STAGE2_STACK_SIZE, + "Stack section doesn't conform to the described memory layout"); + } + __stack_end = .; + .eh_frame : { *(.eh_frame) } .note.gnu.build-id : { *(.note.gnu.build-id) } } diff --git a/zipl/boot/stage2dump.c b/zipl/boot/stage2dump.c index 4fa3d15..9ba5d48 100644 --- a/zipl/boot/stage2dump.c +++ b/zipl/boot/stage2dump.c @@ -11,6 +11,9 @@ #include +#include "lib/zt_common.h" + +#include "libc.h" #include "error.h" #include "sclp.h" #include "stage2dump.h" @@ -34,8 +37,7 @@ struct ipib_info { /* * Tail parameters */ -struct stage2dump_parm_tail parm_tail - __attribute__ ((section(".stage2dump.tail"))) = { +struct stage2dump_parm_tail parm_tail = { .mem_upper_limit = 0xffffffffffffffffULL, }; diff --git a/zipl/boot/stage2dump.h b/zipl/boot/stage2dump.h index 2a7e7fa..784b299 100644 --- a/zipl/boot/stage2dump.h +++ b/zipl/boot/stage2dump.h @@ -13,7 +13,7 @@ #define STAGE2DUMP_H #include "libc.h" -#include "s390.h" +#include "boot/s390.h" #define IPL_SC *((struct subchannel_id *) &S390_lowcore.subchannel_id) #define ROUND_DOWN(x, a) ((x) & ~((typeof(x))(a) - 1)) @@ -28,8 +28,7 @@ struct stage2dump_parm_tail { uint64_t mem_upper_limit; } __packed; -extern struct stage2dump_parm_tail parm_tail - __attribute__ ((section(".stage2dump.tail"))); +extern struct stage2dump_parm_tail __section(.stage2dump.tail) parm_tail; /* * S390 dump format defines diff --git a/zipl/boot/stage3.c b/zipl/boot/stage3.c index f7a9597..bb490dc 100644 --- a/zipl/boot/stage3.c +++ b/zipl/boot/stage3.c @@ -3,16 +3,23 @@ * * Main program for stage3 bootloader * - * Copyright IBM Corp. 2013, 2017 + * Copyright IBM Corp. 2013, 2018 * * 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 "libc.h" -#include "s390.h" +#include "boot/sigp.h" +#include "boot/s390.h" +#include "boot/sigp.h" +#include "boot/linux_layout.h" +#include "boot/loaders_layout.h" + #include "stage3.h" #include "error.h" +#include "ebcdic.h" +#include "ebcdic_conv.h" #define for_each_rb_entry(entry, rb) \ for (entry = rb->entries; \ @@ -21,162 +28,9 @@ static const char *msg_sipl_inval = "Secure boot failure: invalid load address"; static const char *msg_sipl_unverified = "Secure boot failure: unverified load address"; +static const char *msg_sipl_noparm = "Secure boot failure: unable to load ipl parameter"; -static unsigned char ebc_037[256] = { -/* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */ - 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, -/* 0x08 -GE -SPS -RPT VT FF CR SO SI */ - 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, -/* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC - -ENP ->LF */ - 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, -/* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB - -IUS */ - 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, -/* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC - -INP */ - 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, -/* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL - -SW */ - 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, -/* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */ - 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, -/* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */ - 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, -/* 0x40 SP RSP ä ---- */ - 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, -/* 0x48 . < ( + | */ - 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, -/* 0x50 & ---- */ - 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, -/* 0x58 ß ! $ * ) ; */ - 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA, -/* 0x60 - / ---- Ä ---- ---- ---- */ - 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, -/* 0x68 ---- , % _ > ? */ - 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, -/* 0x70 --- ---- ---- ---- ---- ---- ---- */ - 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, -/* 0x78 * ` : # @ ' = " */ - 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, -/* 0x80 * a b c d e f g */ - 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, -/* 0x88 h i ---- ---- ---- */ - 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, -/* 0x90 ° j k l m n o p */ - 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, -/* 0x98 q r ---- ---- */ - 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, -/* 0xA0 ~ s t u v w x */ - 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, -/* 0xA8 y z ---- ---- ---- ---- */ - 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07, -/* 0xB0 ^ ---- § ---- */ - 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, -/* 0xB8 ---- [ ] ---- ---- ---- ---- */ - 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07, -/* 0xC0 { A B C D E F G */ - 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, -/* 0xC8 H I ---- ö ---- */ - 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, -/* 0xD0 } J K L M N O P */ - 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, -/* 0xD8 Q R ---- ü */ - 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, -/* 0xE0 \ S T U V W X */ - 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, -/* 0xE8 Y Z ---- Ö ---- ---- ---- */ - 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, -/* 0xF0 0 1 2 3 4 5 6 7 */ - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, -/* 0xF8 8 9 ---- ---- Ü ---- ---- ---- */ - 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 -}; - -static unsigned char ebc_500[256] = { -/* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */ - 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, -/* 0x08 -GE -SPS -RPT VT FF CR SO SI */ - 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, -/* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC - -ENP ->LF */ - 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, -/* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB - -IUS */ - 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, -/* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC - -INP */ - 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, -/* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL - -SW */ - 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, -/* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */ - 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, -/* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */ - 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, -/* 0x40 SP RSP ä ---- */ - 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, -/* 0x48 . < ( + | */ - 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, -/* 0x50 & ---- */ - 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, -/* 0x58 ß ! $ * ) ; */ - 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA, -/* 0x60 - / ---- Ä ---- ---- ---- */ - 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, -/* 0x68 ---- , % _ > ? */ - 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, -/* 0x70 --- ---- ---- ---- ---- ---- ---- */ - 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, -/* 0x78 * ` : # @ ' = " */ - 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, -/* 0x80 * a b c d e f g */ - 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, -/* 0x88 h i ---- ---- ---- */ - 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, -/* 0x90 ° j k l m n o p */ - 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, -/* 0x98 q r ---- ---- */ - 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, -/* 0xA0 ~ s t u v w x */ - 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, -/* 0xA8 y z ---- ---- ---- ---- */ - 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07, -/* 0xB0 ^ ---- § ---- */ - 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, -/* 0xB8 ---- [ ] ---- ---- ---- ---- */ - 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07, -/* 0xC0 { A B C D E F G */ - 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, -/* 0xC8 H I ---- ö ---- */ - 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, -/* 0xD0 } J K L M N O P */ - 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, -/* 0xD8 Q R ---- ü */ - 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, -/* 0xE0 \ S T U V W X */ - 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, -/* 0xE8 Y Z ---- Ö ---- ---- ---- */ - 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, -/* 0xF0 0 1 2 3 4 5 6 7 */ - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, -/* 0xF8 8 9 ---- ---- Ü ---- ---- ---- */ - 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 -}; - -static void ebcdic_to_ascii(unsigned char *target, unsigned char *source, - unsigned int l) -{ - unsigned char *ebc; - unsigned int i; - - ebc = is_zvm() ? ebc_037 : ebc_500; - for (i = 0; i < l; i++) - target[i] = ebc[source[i]]; -} - -static void -start_kernel(void) +static inline void __noreturn start_kernel(void) { struct psw_t *psw = &S390_lowcore.program_new_psw; unsigned long addr, code; @@ -194,11 +48,25 @@ start_kernel(void) " sam31\n" " sr %r1,%r1\n" " sr %r2,%r2\n" - " sigp %r1,%r2,0x12\n" + " sigp %r1,%r2,%[order]\n" " lpsw 0\n" : [addr] "=&d" (addr), [code] "+&d" (code) - : [psw] "a" (psw) ); + : [psw] "a" (psw), + [order] "L" (SIGP_SET_ARCHITECTURE)); + while (1); +} + +unsigned int store_ipl_parmblock(struct ipl_pl_hdr *pl_hdr) +{ + int rc; + + rc = diag308(DIAG308_STORE, pl_hdr); + if (rc == DIAG308_RC_OK && + pl_hdr->version <= IPL_MAX_SUPPORTED_VERSION) + return 0; + + return 1; } unsigned int @@ -238,6 +106,10 @@ is_verified_address(unsigned long image_addr) rb_hdr = (void *) rb_hdr + rb_hdr->len; } + + if (!comps) + return 0; + for_each_rb_entry(comp, comps) { if (image_addr == comp->addr && comp->flags & IPL_RB_COMPONENT_FLAG_SIGNED && @@ -251,20 +123,23 @@ unsigned int secure_boot_enabled() { struct ipl_pl_hdr *pl_hdr; - unsigned long tmp; + unsigned int rc; - tmp = (unsigned long) S390_lowcore.ipl_parmblock_ptr; - pl_hdr = (struct ipl_pl_hdr *) tmp; + pl_hdr = (void *)get_zeroed_page(); + if (!pl_hdr || store_ipl_parmblock(pl_hdr)) + panic(ESECUREBOOT, "%s", msg_sipl_noparm); + rc = !!(pl_hdr->flags & IPL_FLAG_SECURE); + free_page((unsigned long) pl_hdr); - return pl_hdr->flags & IPL_FLAG_SECURE; + return rc; } void start(void) { unsigned int subchannel_id; unsigned char *cextra = (unsigned char *)COMMAND_LINE_EXTRA; - unsigned char *command_line = (unsigned char *)COMMAND_LINE; - unsigned int begin = 0, end = 0, length = 0; + unsigned char *cmdline = (unsigned char *)COMMAND_LINE; + unsigned int cmdline_len = 0, cextra_len = 0; /* * IPL process is secure we have to use default IPL values and @@ -272,80 +147,84 @@ void start(void) * verified component. If it is not IPL is aborted. */ if (secure_boot_enabled()) { - if (_image_addr != DEFAULT_IMAGE_ADDR || - _load_psw != DEFAULT_PSW_LOAD) + if (_stage3_parms.image_addr != IMAGE_LOAD_ADDRESS || + _stage3_parms.load_psw != DEFAULT_PSW_LOAD) panic(ESECUREBOOT, "%s", msg_sipl_inval); - if (!is_verified_address(_load_psw & PSW_ADDR_MASK)) + if (!is_verified_address(_stage3_parms.load_psw & PSW32_ADDR_MASK)) panic(ESECUREBOOT, "%s", msg_sipl_unverified); } /* * cut the kernel header */ - memmove((void *)_image_addr, - (void *)_image_addr + KERNEL_HEADER_SIZE, - _image_len - KERNEL_HEADER_SIZE); + memmove((void *)_stage3_parms.image_addr, + (void *)_stage3_parms.image_addr + IMAGE_LOAD_ADDRESS, + _stage3_parms.image_len - IMAGE_LOAD_ADDRESS); /* store subchannel ID into low core and into new kernel space */ subchannel_id = S390_lowcore.subchannel_id; *(unsigned int *)__LC_IPLDEV = subchannel_id; - *(unsigned int *)IPL_DEVICE = subchannel_id; + *(unsigned long long *)IPL_DEVICE = subchannel_id; /* if valid command line is given, copy it into new kernel space */ - if (_parm_addr != UNSPECIFIED_ADDRESS) { - memcpy((void *)COMMAND_LINE, - (void *)(unsigned long *)_parm_addr, COMMAND_LINE_SIZE); + if (_stage3_parms.parm_addr != UNSPECIFIED_ADDRESS) { + memcpy(cmdline, + (void *)(unsigned long *)_stage3_parms.parm_addr, + COMMAND_LINE_SIZE); /* terminate \0 */ - *(char *)(COMMAND_LINE + COMMAND_LINE_SIZE - 1) = 0; + cmdline[COMMAND_LINE_SIZE - 1] = 0; } /* convert extra parameter to ascii */ - if (!_extra_parm || !*cextra) + if (!_stage3_parms.extra_parm || !*cextra) goto noextra; /* Handle extra kernel parameters specified in DASD boot menu. */ ebcdic_to_ascii(cextra, cextra, COMMAND_LINE_SIZE); - /* remove leading whitespace */ - while (begin <= COMMAND_LINE_SIZE && cextra[begin] == 0x20) - begin++; - /* determine length of extra parameter */ - while (length <= COMMAND_LINE_SIZE && cextra[length] != 0) - length++; + cextra_len = MIN(strlen((const char *)cextra), COMMAND_LINE_SIZE - 1); + + /* remove leading whitespace of extra parameter */ + while (cextra_len > 0 && *cextra == 0x20) { + cextra++; + cextra_len--; + } - /* find end of original parm line */ - while (command_line[end] != 0 && command_line[end] != 0) - end++; + /* determine length of original parm line */ + cmdline_len = MIN(strlen((const char *)cmdline), + COMMAND_LINE_SIZE - 1); /* * if extra parm string starts with '=' replace original string, * else append */ - if (cextra[begin] == 0x3d) { - memcpy((void *)COMMAND_LINE, (void *)(cextra + begin), - length); - } else { - /* check if length is within max value */ - length = (end + 1 + length <= COMMAND_LINE_SIZE) ? length : - (COMMAND_LINE_SIZE - end - 1); + if (*cextra == 0x3d && cextra_len >= 1) { + /* skip '=' */ + cextra++; + cextra_len--; + memcpy(cmdline, cextra, cextra_len); + cmdline[cextra_len] = 0; + } else if (cmdline_len + 1 <= COMMAND_LINE_SIZE - 1) { /* add blank */ - command_line[end] = 0x20; - end++; + cmdline[cmdline_len] = 0x20; + cmdline_len++; + /* check if length is within max value */ + cextra_len = (cmdline_len + cextra_len <= COMMAND_LINE_SIZE - 1) ? + cextra_len : (COMMAND_LINE_SIZE - 1 - cmdline_len); /* append string */ - memcpy((void *)(command_line + end), - (void *)(cextra + begin), length); + memcpy(cmdline + cmdline_len, cextra, cextra_len); /* terminate 0 */ - command_line[end + length] = 0; + cmdline[cmdline_len + cextra_len] = 0; } noextra: /* copy initrd start address and size intop new kernle space */ - *(unsigned long long *)INITRD_START = _initrd_addr; - *(unsigned long long *)INITRD_SIZE = _initrd_len; + *(unsigned long long *)INITRD_START = _stage3_parms.initrd_addr; + *(unsigned long long *)INITRD_SIZE = _stage3_parms.initrd_len; /* store address of new kernel to 0 to be able to start it */ - *(unsigned long long *)0 = _load_psw; + *(unsigned long long *)0 = _stage3_parms.load_psw; kdump_stage3(); diff --git a/zipl/boot/stage3.h b/zipl/boot/stage3.h index 3a02001..a11257e 100644 --- a/zipl/boot/stage3.h +++ b/zipl/boot/stage3.h @@ -12,171 +12,34 @@ #ifndef STAGE3_H #define STAGE3_H -#include "libc.h" -#include "s390.h" +#include "lib/zt_common.h" +#include "boot/s390.h" +#include "boot/ipl.h" +#include "boot/linux_layout.h" -#define IPL_DEVICE 0x10404 -#define INITRD_START 0x10408 -#define INITRD_SIZE 0x10410 -#define OLDMEM_BASE 0x10418 -#define OLDMEM_SIZE 0x10420 -#define COMMAND_LINE 0x10480 -#define COMMAND_LINE_SIZE 896 -#define COMMAND_LINE_EXTRA 0xE000 #define STAGE3_FLAG_SCSI 0x0001000000000000ULL #define STAGE3_FLAG_KDUMP 0x0002000000000000ULL -#define IPL_FLAG_SECURE 0x40 - -#define DEFAULT_IMAGE_ADDR 0x10000 -#define DEFAULT_PSW_LOAD 0x0008000080010000L -#define PSW_ADDR_MASK 0x000000007FFFFFFFL -#define KERNEL_HEADER_SIZE 65536 - -#define UNSPECIFIED_ADDRESS -1ULL - - -/* IPL Parameter List header */ -struct ipl_pl_hdr { - uint32_t len; - uint8_t flags; - uint8_t reserved1[2]; - uint8_t version; -} __packed; - -/* IPL Parameter Block header */ -struct ipl_pb_hdr { - uint32_t len; - uint8_t pbt; -} __packed; - -/* IPL Parameter Block 0 with common fields */ -struct ipl_pb0_common { - uint32_t len; - uint8_t pbt; - uint8_t flags; - uint8_t reserved1[2]; - uint8_t loadparm[8]; - uint8_t reserved2[84]; -} __packed; - -/* IPL Parameter Block 0 for FCP */ -struct ipl_pb0_fcp { - uint32_t len; - uint8_t pbt; - uint8_t reserved1[3]; - uint8_t loadparm[8]; - uint8_t reserved2[304]; - uint8_t opt; - uint8_t reserved3[3]; - uint8_t cssid; - uint8_t reserved4[1]; - uint8_t devno; - uint8_t reserved5[4]; - uint64_t wwpn; - uint64_t lun; - uint32_t bootprog; - uint8_t reserved6[12]; - uint64_t br_lba; - uint32_t scp_data_len; - uint8_t reserved7[260]; - uint8_t scp_data[]; -} __packed; - -/* IPL Parameter Block 0 for CCW */ -struct ipl_pb0_ccw { - uint32_t len; - uint8_t pbt; - uint8_t flags; - uint8_t reserved1[2]; - uint8_t loadparm[8]; - uint8_t reserved2[84]; - uint16_t reserved3 : 13; - uint8_t ssid : 3; - uint16_t devno; - uint8_t vm_flags; - uint8_t reserved4[3]; - uint32_t vm_parm_len; - uint8_t nss_name[8]; - uint8_t vm_parm[64]; - uint8_t reserved5[8]; -} __packed; - -struct ipl_parameter_block { - struct ipl_pl_hdr hdr; - union { - struct ipl_pb_hdr pb0_hdr; - struct ipl_pb0_common common; - struct ipl_pb0_fcp fcp; - struct ipl_pb0_ccw ccw; - char raw[PAGE_SIZE - sizeof(struct ipl_pl_hdr)]; - }; -} __packed __aligned(PAGE_SIZE); - -/* IPL Report List header */ -struct ipl_rl_hdr { - uint32_t len; - uint8_t flags; - uint8_t reserved1[2]; - uint8_t version; - uint8_t reserved2[8]; -} __packed; - -/* IPL Report Block header */ -struct ipl_rb_hdr { - uint32_t len; - uint8_t rbt; - uint8_t reserved1[11]; -} __packed; - -/* IPL Report Block types */ -enum ipl_rbt { - IPL_RBT_CERTIFICATES = 1, - IPL_RBT_COMPONENTS = 2, +#define DEFAULT_PSW_LOAD 0x0008000080010000UL + +#define UNSPECIFIED_ADDRESS -1UL + +/* Stage 3 bootloader parameter structure */ +/* Structure must not have any padding */ +struct stage3_parms { + unsigned long long parm_addr; /* address of parmline */ + unsigned long long initrd_addr; /* address of initrd */ + unsigned long long initrd_len; /* length of initrd */ + unsigned long long load_psw; /* load psw of kernel */ + unsigned long long extra_parm; /* use extra parm line mechanism? */ + unsigned long long flags; /* flags (e.g. STAGE3_FLAG_KDUMP) */ + unsigned long long image_len; /* length of kernel */ + unsigned long long image_addr; /* target address of kernel */ }; +STATIC_ASSERT(sizeof(struct stage3_parms) == 8 * 8) -/* IPL Report Block for the certificate list */ -struct ipl_rb_certificate_entry { - uint64_t addr; - uint64_t len; -} __packed; - -struct ipl_rb_certificates { - uint32_t len; - uint8_t rbt; - uint8_t reserved1[11]; - struct ipl_rb_certificate_entry entries[]; -} __packed; - -/* IPL Report Block for the component list */ -struct ipl_rb_component_entry { - uint64_t addr; - uint64_t len; - uint8_t flags; - uint8_t reserved1[5]; - uint16_t certificate_index; - uint8_t reserved2[8]; -}; - -#define IPL_RB_COMPONENT_FLAG_SIGNED 0x80 -#define IPL_RB_COMPONENT_FLAG_VERIFIED 0x40 - -struct ipl_rb_components { - uint32_t len; - uint8_t rbt; - uint8_t reserved1[11]; - struct ipl_rb_component_entry entries[]; -} __packed; - -extern unsigned long long _parm_addr; /* address of parmline */ -extern unsigned long long _initrd_addr; /* address of initrd */ -extern unsigned long long _initrd_len; /* length of initrd */ -extern unsigned long long _load_psw; /* load psw of kernel */ -extern unsigned long long _extra_parm; /* use extra parm line mechanism? */ -extern unsigned long long stage3_flags; /* flags (e.g. STAGE3_FLAG_KDUMP) */ -extern unsigned long long _image_len; /* length of kernel */ -extern unsigned long long _image_addr; /* target address of kernel */ +extern struct stage3_parms _stage3_parms; extern void kdump_stage3(); #endif /* STAGE3_H */ diff --git a/zipl/boot/stage3.lds b/zipl/boot/stage3.lds index c67bc32..a9f17fb 100644 --- a/zipl/boot/stage3.lds +++ b/zipl/boot/stage3.lds @@ -10,7 +10,7 @@ * 0x6000-0x8fff free * 0x9000-0x9fff Stage3 parameter * 0xa000-0xdfff Stage3 code + data - * 0xe000-0xffff Stack + * 0xf000-0xffff Stack */ SECTIONS @@ -25,21 +25,7 @@ SECTIONS /* stage 3 parameter */ . = 0x9000; - _parm_addr = .; - . = 0x9008; - _initrd_addr = .; - . = 0x9010; - _initrd_len = .; - . = 0x9018; - _load_psw = .; - . = 0x9020; - _extra_parm = .; - . = 0x9028; - stage3_flags =.; - . = 0x9030; - _image_len = .; - . = 0x9038; - _image_addr = .; + _stage3_parms = .; . = 0xa000; .text.start : { *(.text.start) } diff --git a/zipl/boot/stage3.lds.S b/zipl/boot/stage3.lds.S new file mode 100644 index 0000000..2362d4f --- /dev/null +++ b/zipl/boot/stage3.lds.S @@ -0,0 +1,62 @@ +/* + * Memory layout for stage 3 + * ========================= + * + * General memory layout + * --------------------- + * + * 0x0000-0x1fff Lowcore + * 0x2000-0x5fff Memory allocation (heap) + * 0x6000-0x8fff free + * 0x9000-0x9fff Stage3 parameter + * 0xa000-0xdfff Stage3 code + data + * 0xf000-0xffff Stack + */ + +#include "boot/loaders_layout.h" + +SECTIONS +{ + . = 0x0; + + . = STAGE3_HEAP_ADDRESS; + __heap_start = .; + .heap : { + . += STAGE3_HEAP_SIZE; + ASSERT(__heap_stop - __heap_start == STAGE3_HEAP_SIZE, + "Heap section doesn't conform to the described memory layout"); + } + __heap_stop = .; + + /* stage 3 parameter */ + . = STAGE3_PARAMS_ADDRESS; + _stage3_parms = .; + + . = STAGE3_ENTRY; + .text.start : { *(.text.start) } + .text : { *(.text) } + __ex_table_start = .; + .ex_table : { *(.ex_table) } + __ex_table_stop = .; + .eh_frame : { *(.eh_frame) } + + __bss_start = .; + .bss : { *(.bss) } + __bss_stop = .; + .rodata : {*(.rodata) } + .data : { *(.data) } + + . = COMMAND_LINE_EXTRA; + .cmdline_extra : { + . += COMMAND_LINE_EXTRA_SIZE; + } + + . = STAGE3_STACK_ADDRESS; + __stack_start = .; + .stack : { + . += STAGE3_STACK_SIZE; + ASSERT(__stack_end - __stack_start == STAGE3_STACK_SIZE, + "Stack section doesn't conform to the described memory layout"); + } + __stack_end = .; +} diff --git a/zipl/boot/tape0.S b/zipl/boot/tape0.S index 2b1e3f4..e7c605d 100644 --- a/zipl/boot/tape0.S +++ b/zipl/boot/tape0.S @@ -7,28 +7,23 @@ # it under the terms of the MIT license. See LICENSE for details. # +#include "boot/sigp.h" +#include "boot/linux_layout.h" +#include "boot/s390.h" + IPL_BS = 1024 # block size for tape access IPL_OFF = 0x4000 # temporary kernel load addr -COMMAND_LINE_SIZE = 896 # max command line length -KERNEL_OFF = 0x10000 # kernel start code offset +KERNEL_OFF = IMAGE_ENTRY # kernel start code offset # relative to image start __LC_IO_NEW_PSW = 0x1f0 # IO New PSW addr -# Parameter address offsets - -PARMAREA = 0x10400 # Parameter area offset -IPL_DEVICE = 0x10400 # IPL device offset -INITRD_START = 0x10408 # ramdisk addr offset -INITRD_SIZE = 0x10410 # ramdisk size offset -COMMAND_LINE = 0x10480 # command line offset - # Default IPL parameter - will be overwritten by zIPL RAMDISK_ORIGIN = 0x800000 # default ramdisk load addr RAMDISK_SIZE = 0x800000 # default ramdisk size PARMFILE_ADDR = 0x1000 # default parmfile load addr -KERNEL_ADDR = 0x10000 # default kernel load addr +KERNEL_ADDR = IMAGE_ENTRY # default kernel load addr .org 0x0 @@ -37,7 +32,7 @@ _start: # Stage 0 loader - loads first block of this loader - .long 0x00080000,0x80000000+iplstart # PSW to start execution + .quad PSW_LOAD+iplstart # PSW to start execution .long 0x27000000,0x60000001 # backward Space Block .long 0x02000000,0x20000000+IPL_BS # read IPL_BS bytes to addr 0 @@ -63,7 +58,7 @@ _parm_addr: _initrd_addr: .quad RAMDISK_ORIGIN # ramdisk load address _load_psw: - .quad 0x0008000080000000+KERNEL_ADDR # kernel start PSW + .quad PSW_LOAD+KERNEL_ADDR # kernel start PSW # # Subroutine to load from tape until tape mark @@ -184,10 +179,10 @@ iplstart: 0: la %r7,2 #/* First try code 2: */ la %r6,0 #/* 64 bit psws are restored */ - sigp %r7,%r6,0x12 #/* Switch to 64 bit */ + sigp %r7,%r6,SIGP_SET_ARCHITECTURE #/* Switch to 64 bit */ bc 8,.Lswitched_64-0b(%r13) #/* Accepted ? */ la %r7,1 #/* Failed - try code 1 */ - sigp %r7,%r6,0x12 #/* Switch to 64 bit */ + sigp %r7,%r6,SIGP_SET_ARCHITECTURE #/* Switch to 64 bit */ .Lswitched_64: sam64 #/* Switch to 64 bit addr mode */ basr %r13,0 diff --git a/zipl/boot/tape2dump.c b/zipl/boot/tape2dump.c index 800b943..638c524 100644 --- a/zipl/boot/tape2dump.c +++ b/zipl/boot/tape2dump.c @@ -9,9 +9,13 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include "libc.h" +#include "lib/zt_common.h" +#include "boot/loaders_layout.h" +#include "boot/s390.h" + #include "cio.h" #include "error.h" -#include "libc.h" #include "stage2dump.h" #define BLK_SIZE 0x8000 /* We write 32 KB at a time */ @@ -33,8 +37,8 @@ struct tape_head { uint64_t ccw2; } __packed; -struct tape_head tape_head __attribute__((section(".stage2.head"))) = { - .psw = 0x0008000080002018ULL, /* Start code at 0x2018 */ +struct tape_head __section(.stage2.head) tape_head = { + .psw = PSW_LOAD | STAGE2_ENTRY, /* Start code at 0x2018 */ .ccw1 = 0x0700000060000001ULL, /* Rewind ccw */ .ccw2 = 0x0200200020003000ULL, /* CCW to load dump tool to 0x2000 */ }; diff --git a/zipl/include/boot.h b/zipl/include/boot.h index 6bd2a9b..35cb981 100644 --- a/zipl/include/boot.h +++ b/zipl/include/boot.h @@ -14,6 +14,8 @@ #include +#include "lib/zt_common.h" + #include "disk.h" #include "job.h" #include "zipl.h" @@ -51,7 +53,7 @@ struct scsi_dump_sb { uint64_t csum_offset; uint64_t csum_size; uint64_t csum; -} __attribute((packed)); +} __packed; #define SCSI_DUMP_SB_MAGIC 0x5a46435044554d50ULL; /* ZFCPDUMP */ /* To avoid a csum entry of 0 a seed is used */ @@ -63,7 +65,7 @@ struct scsi_dump_sb { struct scsi_dump_param { uint64_t block; uint64_t reserved; -} __attribute((packed)); +} __packed; /* ECKD dump parameter */ struct eckd_dump_param { @@ -73,14 +75,14 @@ struct eckd_dump_param { uint8_t num_heads; uint8_t bpt; char reserved[4]; -} __attribute((packed, may_alias)); +} __packed __may_alias; /* FBA dump parameter */ struct fba_dump_param { uint64_t start_blk; uint64_t blockct; -} __attribute((packed)); +} __packed; struct boot_info_bp_dump { union { @@ -89,7 +91,7 @@ struct boot_info_bp_dump { struct scsi_dump_param scsi; } param; uint8_t unused[16]; -} __attribute__ ((packed)); +} __packed; /* * Layout of block pointer for linear devices @@ -101,7 +103,7 @@ struct linear_blockptr { uint16_t size; uint16_t blockct; uint8_t reserved[4]; -} __attribute((packed)); +} __packed; /* * Layout of block pointer for cylinder/head/sector devices @@ -115,7 +117,7 @@ struct eckd_blockptr { uint16_t size; uint8_t blockct; uint8_t reserved[8]; -} __attribute((packed)); +} __packed; struct boot_info_bp_ipl { union { @@ -123,7 +125,7 @@ struct boot_info_bp_ipl { struct linear_blockptr lin; } bm_ptr; uint8_t unused[16]; -} __attribute__ ((packed)); +} __packed; struct boot_info { char magic[4]; @@ -135,7 +137,7 @@ struct boot_info { struct boot_info_bp_dump dump; struct boot_info_bp_ipl ipl; } bp; -} __attribute__ ((packed)); +} __packed; struct boot_ccw0 { uint8_t cmd; @@ -144,21 +146,21 @@ struct boot_ccw0 { uint8_t flags; uint8_t pad; uint16_t count; -} __attribute__ ((packed)); +} __packed; /* Boot data structures for FBA disks */ struct boot_fba_locread { struct boot_ccw0 locate; struct boot_ccw0 read; -} __attribute__ ((packed)); +} __packed; struct boot_fba_locdata { uint8_t command; uint8_t dummy; uint16_t blockct; uint32_t blocknr; -} __attribute__ ((packed)); +} __packed; struct boot_fba_stage0 { uint64_t psw; @@ -169,13 +171,13 @@ struct boot_fba_stage0 { struct boot_fba_locdata locdata[2]; uint64_t reserved[4]; struct boot_info boot_info; -} __attribute__ ((packed)); +} __packed; struct boot_fba_stage1b { struct boot_fba_locread locread[STAGE2_BLK_CNT_MAX]; struct boot_fba_locdata locdata[STAGE2_BLK_CNT_MAX]; uint8_t unused[448]; -} __attribute__ ((packed)); +} __packed; /* Boot data structures for ECKD disks */ @@ -184,14 +186,14 @@ struct boot_eckd_ccw1 { uint8_t flags; uint16_t count; uint32_t address; -} __attribute__ ((packed)); +} __packed; struct boot_eckd_ssrt { struct boot_ccw0 seek; struct boot_ccw0 search; struct boot_ccw0 tic; struct boot_ccw0 read; -} __attribute__ ((packed)); +} __packed; struct boot_eckd_seekarg { uint16_t pad; @@ -199,32 +201,32 @@ struct boot_eckd_seekarg { uint16_t head; uint8_t sec; uint8_t pad2; -} __attribute__ ((packed)); +} __packed; struct boot_eckd_cdl_stage0 { uint64_t psw; struct boot_ccw0 read; struct boot_ccw0 tic; -} __attribute__ ((packed)); +} __packed; struct boot_eckd_ldl_stage0 { uint64_t psw; struct boot_ccw0 read_r0; struct boot_ccw0 read_r1; -} __attribute__ ((packed)); +} __packed; struct boot_eckd_stage1 { struct boot_eckd_ssrt ssrt[2]; struct boot_ccw0 tic1b; struct boot_eckd_seekarg seek[2]; struct boot_info boot_info; -} __attribute__ ((packed)); +} __packed; struct boot_eckd_stage1b { struct boot_eckd_ssrt ssrt[STAGE2_BLK_CNT_MAX]; struct boot_eckd_seekarg seek[STAGE2_BLK_CNT_MAX]; uint8_t unused[64]; -} __attribute__ ((packed)); +} __packed; /* Stage 2 boot menu parameter structure */ @@ -236,25 +238,8 @@ struct boot_stage2_params { uint16_t banner; uint16_t config[BOOT_MENU_ENTRIES + 1]; uint64_t config_kdump; -} __attribute__ ((packed)); - - -/* Stage 3 bootloader parameter structure */ - -struct boot_stage3_params { - uint64_t parm_addr; - uint64_t initrd_addr; - uint64_t initrd_len; - uint64_t load_psw; - uint64_t extra_parm; - uint16_t flags; - uint16_t reserved[3]; - uint64_t image_len; - uint64_t image_addr; -} __attribute__ ((packed)); +} __packed; -#define STAGE3_FLAG_SCSI 0x0001 -#define STAGE3_FLAG_KDUMP 0x0002 /* Tape IPL bootloader parameter structure */ @@ -275,7 +260,7 @@ struct mvdump_param { uint8_t blocksize; uint8_t bpt; uint8_t num_heads; -} __attribute__ ((packed)); +} __packed; struct mvdump_parm_table { uint64_t timestamp; @@ -284,7 +269,7 @@ struct mvdump_parm_table { uint8_t ssid[MAX_DUMP_VOLUMES]; unsigned char reserved[512 - sizeof(uint64_t) - sizeof(uint16_t) - (MAX_DUMP_VOLUMES * (sizeof(struct mvdump_param) + 1))]; -} __attribute__ ((packed)); +} __packed; void boot_get_dump_info(struct boot_info *boot_info, uint8_t dev_type, void *param); @@ -310,8 +295,8 @@ int boot_init_fba_stage1b(struct boot_fba_stage1b *stage1b, int boot_get_eckd_stage2(void** data, size_t* size, struct job_data* job); int boot_get_stage3_parms(void **buffer, size_t *bytecount, address_t parm_addr, address_t initrd_addr, size_t initrd_len, - address_t load_addr, int extra_parm, uint16_t flags, - size_t image_len); + address_t entry, int extra_parm, uint64_t flags, + address_t image_addr, size_t image_len); int boot_get_tape_ipl(void** data, size_t* size, address_t parm_addr, address_t initrd_addr, address_t image_addr); int boot_get_tape_dump(void** data, size_t* size, uint64_t mem); diff --git a/zipl/include/bootmap.h b/zipl/include/bootmap.h index 4a0bd04..f49e394 100644 --- a/zipl/include/bootmap.h +++ b/zipl/include/bootmap.h @@ -12,6 +12,8 @@ #ifndef BOOTMAP_H #define BOOTMAP_H +#include "lib/zt_common.h" + #include "disk.h" #include "job.h" #include "zipl.h" @@ -23,7 +25,7 @@ struct signature_header { uint8_t format; uint8_t reserved[3]; uint32_t length; -} __attribute((packed)); +} __packed; typedef union { uint64_t load_address; diff --git a/zipl/include/misc.h b/zipl/include/misc.h index 5a349a7..f222d01 100644 --- a/zipl/include/misc.h +++ b/zipl/include/misc.h @@ -46,6 +46,7 @@ char* misc_make_path(char* dirname, char* filename); int misc_temp_dev(dev_t dev, int blockdev, char** devno); int misc_temp_dev_from_file(char* file, char** devno); void misc_free_temp_dev(char* device); +void misc_free_temp_file(char *filename); int misc_check_writable_directory(const char* directory); int misc_check_readable_file(const char* filename); int misc_check_writable_device(const char* devno, int blockdev, int chardev); diff --git a/zipl/include/zipl.h b/zipl/include/zipl.h index e5842ed..8ed839e 100644 --- a/zipl/include/zipl.h +++ b/zipl/include/zipl.h @@ -14,32 +14,17 @@ #include #include "lib/zt_common.h" +#include "boot/loaders_layout.h" #define ZIPL_MAGIC "zIPL" #define ZIPL_MAGIC_SIZE 4 #define DISK_LAYOUT_ID 0x00000001 -#define ZIPL_STAGE2_LOAD_ADDRESS 0x2000 -#define ZIPL_STAGE3_ENTRY_ADDRESS 0xa000LL -#define DEFAULT_IMAGE_ADDRESS 0x10000LL -#define KDUMP_IMAGE_ADDRESS 0x10010LL -#define DEFAULT_STAGE3_ADDRESS 0xa000LL -#define DEFAULT_STAGE3_PARAMS_ADDRESS 0x9000LL -#define MINIMUM_ADDRESS 0x10000LL -#define ADDRESS_LIMIT 0x80000000LL +#define ADDRESS_LIMIT 0x80000000UL #define ADDRESS_LIMIT_KDUMP 0x2000000UL /* HSA size: 32 MiB */ -#define UNSPECIFIED_ADDRESS -1ULL -#define MAXIMUM_PARMLINE_SIZE 0x380 -#define MAXIMUM_PHYSICAL_BLOCKSIZE 0x1000 - -#define STAGE3_HEAP_SIZE 0x4000 -#define STAGE3_HEAP_ADDRESS 0x2000 -#define STAGE3_STACK_SIZE 0x1000 -#define STAGE3_STACK_ADDRESS 0xF000 - -#define PSW_ADDRESS_MASK 0x000000007fffffffLL -#define PSW_LOAD 0x0008000080000000LL -#define PSW_DISABLED_WAIT 0x000a000000000000LL +#define UNSPECIFIED_ADDRESS -1UL +#define MAXIMUM_PARMLINE_SIZE 0x380UL +#define MAXIMUM_PHYSICAL_BLOCKSIZE 0x1000UL #define BOOTMAP_FILENAME "bootmap" #define BOOTMAP_TEMPLATE_FILENAME "bootmap_temp.XXXXXX" @@ -75,10 +60,6 @@ typedef uint64_t address_t; * resulting return code or 0. */ #define DRY_RUN_FUNC(x) (dry_run ? 0 : (x)) -#define ALIGN(x,a) __ALIGN_MASK(x,(typeof(x))(a)-1) -#define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask)) -#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) - extern int verbose; extern int interactive; extern int dry_run; diff --git a/zipl/man/zipl.8.in b/zipl/man/zipl.8.in index f12e27b..70e6134 100644 --- a/zipl/man/zipl.8.in +++ b/zipl/man/zipl.8.in @@ -59,9 +59,9 @@ tool implements a boot menu which includes the following features: .IP " -" display a list of available configurations .IP " -" -allow to choose a configuration +choose a configuration .IP " -" -allow to specify additional kernel command line parameters +specify additional kernel command line parameters .PP See the @@ -360,11 +360,11 @@ Control the zIPL secure boot support. auto (default) Write signatures if available and supported by the system. 1 - Signatures are written independent of support indicated by the local - system. Also missing signatures for stage 3 and kernel IPL files + Write signatures regardless of support indicated by the local + system. Missing signatures for stage 3 and kernel IPL files will result in an error. 0 - No signatures will be written. + Do not write signatures. .SH EXAMPLE diff --git a/zipl/man/zipl.conf.5.in b/zipl/man/zipl.conf.5.in index 20731b0..41bf976 100644 --- a/zipl/man/zipl.conf.5.in +++ b/zipl/man/zipl.conf.5.in @@ -538,17 +538,17 @@ non-default memory location. .B Configuration section: .br Control the zIPL secure boot support. -Set this option to one of the following: +Set this option to one of the following values: .IP " - " 12 .BR auto: Write signatures if available and supported by the system. .IP " - " 12 .BR 1: -Signatures are written independent of support indicated by the local system. -Also missing signatures for stage 3 and kernel IPL files will result in an error. +Write signatures regardless of support indicated by the local system. +Missing signatures for stage 3 and kernel IPL files will result in an error. .IP " - " 12 .BR 0: -No signatures will be written. +Do not write signatures. The default value for .B 'secure' diff --git a/zipl/src/Makefile b/zipl/src/Makefile index fd77670..61d0e67 100644 --- a/zipl/src/Makefile +++ b/zipl/src/Makefile @@ -42,11 +42,13 @@ clean: # Additional manual dependencies +.boot.o.d boot.o: ../boot/data.h + ../boot/data.h: - make -C ../boot data.h + $(MAKE) -C ../boot data.h ../boot/data.o: - make -C ../boot data.o + $(MAKE) -C ../boot data.o ../boot/stage3.bin: - make -C ../boot stage3.bin + $(MAKE) -C ../boot stage3.bin diff --git a/zipl/src/boot.c b/zipl/src/boot.c index b13bcb7..281b5b2 100644 --- a/zipl/src/boot.c +++ b/zipl/src/boot.c @@ -17,6 +17,8 @@ #include #include +#include "stage3.h" + #include "../boot/data.h" #include "boot.h" #include "bootmap.h" @@ -79,14 +81,14 @@ boot_check_data(void) int boot_get_stage3_parms(void **buffer, size_t *bytecount, address_t parm_addr, address_t initrd_addr, size_t initrd_len, - address_t image_addr, int extra_parm, uint16_t flags, - size_t image_len) + address_t entry, int extra_parm, uint64_t flags, + address_t image_addr, size_t image_len) { - struct boot_stage3_params params; + struct stage3_parms params; void* data; - if (image_addr != (image_addr & PSW_ADDRESS_MASK)) { - error_reason("Kernel image load address to high (31 bit " + if (entry != (entry & PSW32_ADDR_MASK)) { + error_reason("Kernel image entry point to high (31 bit " "addressing mode)"); return -1; } @@ -99,7 +101,7 @@ boot_get_stage3_parms(void **buffer, size_t *bytecount, address_t parm_addr, params.parm_addr = (uint64_t) parm_addr; params.initrd_addr = (uint64_t) initrd_addr; params.initrd_len = (uint64_t) initrd_len; - params.load_psw = (uint64_t)(image_addr | PSW_LOAD); + params.load_psw = (uint64_t)(entry | PSW_LOAD); params.extra_parm = (uint64_t) extra_parm; params.flags = flags; params.image_len = (uint64_t) image_len; @@ -118,7 +120,7 @@ boot_init_fba_stage0(struct boot_fba_stage0 *stage0, blocknum_t i; /* Initialize stage 0 data */ - memcpy(stage0, DATA_ADDR(fba0), sizeof(*stage0)); + memcpy(stage0, DATA_ADDR(fba0), DATA_SIZE(fba0)); /* Fill in blocklist for stage 2 loader */ if (stage1b_count > STAGE1B_BLK_CNT_MAX) { error_reason("Not enough room for FBA stage 1b loader"); @@ -138,7 +140,7 @@ boot_init_fba_stage0(struct boot_fba_stage0 *stage0, void boot_init_eckd_ldl_stage0(struct boot_eckd_ldl_stage0 *stage0) { - memcpy(stage0, DATA_ADDR(eckd0_ldl), sizeof(*stage0)); + memcpy(stage0, DATA_ADDR(eckd0_ldl), DATA_SIZE(eckd0_ldl)); /* Fill in size of stage 1 plus stage 0 loader */ stage0->read_r1.count = sizeof(struct boot_eckd_stage1) + sizeof(struct boot_eckd_ldl_stage0); @@ -147,7 +149,7 @@ boot_init_eckd_ldl_stage0(struct boot_eckd_ldl_stage0 *stage0) void boot_init_eckd_cdl_stage0(struct boot_eckd_cdl_stage0 *stage0) { - memcpy(stage0, DATA_ADDR(eckd0_cdl), sizeof(*stage0)); + memcpy(stage0, DATA_ADDR(eckd0_cdl), DATA_SIZE(eckd0_cdl)); /* Fill in size of stage 1 loader */ stage0->read.count = sizeof(struct boot_eckd_stage1); } @@ -158,7 +160,7 @@ boot_init_eckd_stage1(struct boot_eckd_stage1 *stage1, { blocknum_t i; - memcpy(stage1, DATA_ADDR(eckd1), sizeof(*stage1)); + memcpy(stage1, DATA_ADDR(eckd1), DATA_SIZE(eckd1)); /* Fill in blocklist for stage 1b loader */ if (stage1b_count > STAGE1B_BLK_CNT_MAX) { error_reason("Not enough room for ECKD stage 1b loader " @@ -186,7 +188,7 @@ boot_init_fba_stage1b(struct boot_fba_stage1b *stage1b, { blocknum_t i; - memcpy(stage1b, DATA_ADDR(fba1b), sizeof(*stage1b)); + memcpy(stage1b, DATA_ADDR(fba1b), DATA_SIZE(fba1b)); if (stage2_count > STAGE2_BLK_CNT_MAX) { error_reason("Not enough room for FBA stage 2 loader"); return -1; @@ -195,7 +197,7 @@ boot_init_fba_stage1b(struct boot_fba_stage1b *stage1b, stage1b->locdata[i].blocknr = (uint32_t) stage2_list[i].linear.block; stage1b->locread[i].read.address_lo = - ZIPL_STAGE2_LOAD_ADDRESS + i * FBA_BLK_SIZE; + STAGE2_LOAD_ADDRESS + i * FBA_BLK_SIZE; } /* Terminate CCW chain */ stage1b->locread[i - 1].read.flags &= ~CCW_FLAG_CC; @@ -208,7 +210,7 @@ boot_init_eckd_stage1b(struct boot_eckd_stage1b *stage1b, { blocknum_t i; - memcpy(stage1b, DATA_ADDR(eckd1b), sizeof(*stage1b)); + memcpy(stage1b, DATA_ADDR(eckd1b), DATA_SIZE(eckd1b)); if (stage2_count > STAGE2_BLK_CNT_MAX) { error_reason("Not enough room for ECKD stage 2 loader " "(try larger block size)"); @@ -220,7 +222,7 @@ boot_init_eckd_stage1b(struct boot_eckd_stage1b *stage1b, stage1b->seek[i].head = stage2_list[i].chs.head | ((stage2_list[i].chs.cyl >> 12) & 0xfff0); stage1b->seek[i].sec = stage2_list[i].chs.sec; - stage1b->ssrt[i].read.address_lo = ZIPL_STAGE2_LOAD_ADDRESS + + stage1b->ssrt[i].read.address_lo = STAGE2_LOAD_ADDRESS + i * stage2_list[i].chs.size; stage1b->ssrt[i].read.flags = CCW_FLAG_CC | CCW_FLAG_SLI; } @@ -236,7 +238,7 @@ boot_get_tape_ipl(void** data, size_t* size, address_t parm_addr, struct boot_tape_ipl_params params; void* buffer; - if (image_addr != (image_addr & PSW_ADDRESS_MASK)) { + if (image_addr != (image_addr & PSW32_ADDR_MASK)) { error_reason("Kernel image load address to high (31 bit " "addressing mode)"); return -1; diff --git a/zipl/src/bootmap.c b/zipl/src/bootmap.c index 5926622..30128c9 100644 --- a/zipl/src/bootmap.c +++ b/zipl/src/bootmap.c @@ -18,8 +18,11 @@ #include #include +#include "lib/zt_common.h" #include "lib/util_part.h" #include "lib/util_path.h" +#include "boot/s390.h" +#include "stage3.h" #include "boot.h" #include "bootmap.h" @@ -128,7 +131,10 @@ check_secure_boot_support(void) if (!fp) return false; - fscanf(fp, "%d", &val); + if (fscanf(fp, "%d", &val) != 1) { + fclose(fp); + return false; + } fclose(fp); return val ? true : false; @@ -220,7 +226,7 @@ struct component_entry { uint8_t data[23]; uint8_t type; component_data compdat; -} __attribute((packed)); +} __packed; typedef enum { component_execute = 0x01, @@ -260,7 +266,7 @@ struct component_header { uint8_t magic[4]; uint8_t type; uint8_t reserved[27]; -} __attribute((packed)); +} __packed; typedef enum { component_header_ipl = 0x00, @@ -623,7 +629,7 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, } /* Add stage 3 loader to bootmap */ - rc = add_component_file(fd, ZIPL_STAGE3_PATH, DEFAULT_STAGE3_ADDRESS, + rc = add_component_file(fd, ZIPL_STAGE3_PATH, STAGE3_LOAD_ADDRESS, signature_size, VOID_ADD(table, offset), 1, info, target, &comp_loc[comp_nr]); if (rc) { @@ -640,17 +646,17 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, rc = boot_get_stage3_parms(&stage3_params, &stage3_params_size, ipl->parm_addr, ipl->ramdisk_addr, ramdisk_size, - ipl->is_kdump ? ipl->image_addr + 0x10 : - ipl->image_addr, + ipl->is_kdump ? IMAGE_ENTRY_KDUMP : + IMAGE_ENTRY, (info->type == disk_type_scsi) ? 0 : 1, - flags, image_size); + flags, ipl->image_addr, image_size); if (rc) { free(table); return rc; } rc = add_component_buffer(fd, stage3_params, stage3_params_size, (component_data) (uint64_t) - DEFAULT_STAGE3_PARAMS_ADDRESS, + STAGE3_PARAMS_ADDRESS, VOID_ADD(table, offset), info, &comp_loc[comp_nr], component_load); free(stage3_params); @@ -788,7 +794,7 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, create_component_entry(VOID_ADD(table, offset), NULL, component_execute, (component_data) (uint64_t) - (ZIPL_STAGE3_ENTRY_ADDRESS | PSW_LOAD), + (STAGE3_ENTRY | PSW_LOAD), info); /* Write component table */ rc = disk_write_block_aligned(fd, table, info->phy_block_size, @@ -1183,7 +1189,7 @@ bootmap_create(struct job_data *job, disk_blockptr_t *program_table, ulong unused_size; /* Use approximated stage 3 size as starting point */ - size = MINIMUM_ADDRESS; + size = IMAGE_LOAD_ADDRESS; /* Ramdisk */ if (job->data.dump.ramdisk != NULL) { @@ -1195,7 +1201,7 @@ bootmap_create(struct job_data *job, disk_blockptr_t *program_table, /* Kernel */ if (stat(job->data.dump.image, &st)) goto out_misc_free_temp_dev; - size += DIV_ROUND_UP(st.st_size - 0x10000, + size += DIV_ROUND_UP(st.st_size - IMAGE_LOAD_ADDRESS, info->phy_block_size); /* Parmfile */ size += DIV_ROUND_UP(DUMP_PARAM_MAX_LEN, info->phy_block_size); @@ -1282,9 +1288,7 @@ bootmap_create(struct job_data *job, disk_blockptr_t *program_table, break; } if (dry_run) { - if (remove(filename) == -1) - fprintf(stderr, "Warning: could not remove temporary " - "file %s!\n", filename); + misc_free_temp_file(filename); } else if (job->id != job_dump_partition) { /* Rename to final bootmap name */ mapname = misc_make_path(job->target.bootmap_dir, @@ -1311,6 +1315,8 @@ out_disk_free_info: disk_free_info(info); out_close_fd: close(fd); + if (job->id != job_dump_partition) + misc_free_temp_file(filename); out_free_filename: free(filename); return -1; diff --git a/zipl/src/disk.c b/zipl/src/disk.c index 43092bf..8abf668 100644 --- a/zipl/src/disk.c +++ b/zipl/src/disk.c @@ -198,8 +198,8 @@ disk_get_info(const char* device, struct job_target_data* target, long devsize; FILE *fh; char *script_pre = TOOLS_LIBDIR "/zipl_helper."; - char script_file[80]; - char ppn_cmd[80]; + char *script_file = NULL; + char *ppn_cmd = NULL; char buffer[80]; char value[40]; int majnum, minnum; @@ -235,23 +235,22 @@ disk_get_info(const char* device, struct job_target_data* target, } data->source = source_user; /* Check if targetbase script is available */ - strcpy(script_file, script_pre); - if (data->drv_name) { - strcat(script_file, data->drv_name); - } + if (data->drv_name) + misc_asprintf(&script_file, "%s%s", script_pre, data->drv_name); + else + misc_asprintf(&script_file, "%s", script_pre); if ((target->targetbase == NULL) && (!stat(script_file, &script_stats))) { data->source = source_script; /* Run targetbase script */ - strcpy(ppn_cmd, script_file); if (target->bootmap_dir == NULL) { /* happens in case of partition dump */ - snprintf(ppn_cmd, sizeof(ppn_cmd), "%s %d:%d", - script_file, major(stats.st_rdev), - minor(stats.st_rdev)); + misc_asprintf(&ppn_cmd, "%s %d:%d", + script_file, major(stats.st_rdev), + minor(stats.st_rdev)); } else { - strcat(ppn_cmd, " "); - strcat(ppn_cmd, target->bootmap_dir); + misc_asprintf(&ppn_cmd, "%s %s", + script_file, target->bootmap_dir); } printf("Run %s\n", ppn_cmd); fh = popen(ppn_cmd, "r"); @@ -406,6 +405,12 @@ disk_get_info(const char* device, struct job_target_data* target, "determined."); goto out_close; } + /* NVMe path, driver name is 'blkext' */ + } else if (strcmp(data->drv_name, "blkext") == 0) { + data->devno = -1; + data->type = disk_type_scsi; + data->partnum = stats.st_rdev & SCSI_PARTN_MASK; + data->device = stats.st_rdev & ~SCSI_PARTN_MASK; } else { /* Driver name is unknown */ error_reason("Unsupported device driver '%s'", data->drv_name); @@ -444,9 +449,12 @@ type_determined: data->fs_block_size = -1; close(fd); *info = data; + free(script_file); return 0; out_close: close(fd); + free(ppn_cmd); + free(script_file); free(data); return -1; diff --git a/zipl/src/install.c b/zipl/src/install.c index 20b53f5..32f9dfa 100644 --- a/zipl/src/install.c +++ b/zipl/src/install.c @@ -23,6 +23,7 @@ #include #include +#include "lib/zt_common.h" #include "lib/util_sys.h" #include "boot.h" @@ -89,7 +90,7 @@ update_scsi_mbr(void* bootblock, disk_blockptr_t* table, uint8_t program_table_pointer[16]; uint8_t reserved2[0x50]; struct boot_info boot_info; - } __attribute__ ((packed))* mbr; + } __packed* mbr; struct scsi_dump_param param; void* buffer; diff --git a/zipl/src/job.c b/zipl/src/job.c index 80ddb34..2c9cef8 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -23,6 +23,7 @@ #include "job.h" #include "misc.h" #include "scan.h" +#include "zipl.h" /* Command line options */ static struct option options[] = { @@ -671,12 +672,12 @@ check_component_address_data(struct component_loc *cl, int num, char *name, address_limit); return -1; } - if (*cl[i].addrp < MINIMUM_ADDRESS) { + if (*cl[i].addrp < IMAGE_LOAD_ADDRESS) { if (name != NULL) error_text("Section '%s'", name); error_reason("Component '%s' falls below available " "address space (limit is 0x%08x)", - cl[i].name, MINIMUM_ADDRESS); + cl[i].name, IMAGE_LOAD_ADDRESS); return -1; } } @@ -714,12 +715,12 @@ finalize_component_address_data(struct component_loc *cl, int num, for (j = -1; j < i; j++) { if (j < 0) { /* Try address before first component */ - addr = MINIMUM_ADDRESS; + addr = IMAGE_LOAD_ADDRESS; } else { /* Try address after component j */ addr = *cl[j].addrp + cl[j].size; - if (addr < MINIMUM_ADDRESS) - addr = MINIMUM_ADDRESS; + if (addr < IMAGE_LOAD_ADDRESS) + addr = IMAGE_LOAD_ADDRESS; } addr = ALIGN(addr, cl[i].align); if (addr + cl[i].size > address_limit) { @@ -913,7 +914,7 @@ check_job_dump_images(struct job_dump_data* dump, char* name) dump->image = misc_strdup(ZFCPDUMP_IMAGE); if (dump->image == NULL) return -1; - dump->image_addr = DEFAULT_IMAGE_ADDRESS; + dump->image_addr = IMAGE_LOAD_ADDRESS; /* Ramdisk is no longer required with new initramfs dump system */ if (misc_check_readable_file(ZFCPDUMP_INITRD)) @@ -1359,7 +1360,7 @@ get_job_from_section_data(char* data[], struct job_data* job, char* section) return -1; if (extract_address(job->data.ipl.image, &job->data.ipl.image_addr)) { - job->data.ipl.image_addr = DEFAULT_IMAGE_ADDRESS; + job->data.ipl.image_addr = IMAGE_LOAD_ADDRESS; } /* Fill in parmline */ rc = get_parmline(data[(int) scan_keyword_parmfile], @@ -1414,7 +1415,7 @@ get_job_from_section_data(char* data[], struct job_data* job, char* section) return -1; if (extract_address(job->data.ipl_tape.image, &job->data.ipl_tape.image_addr)) { - job->data.ipl_tape.image_addr = DEFAULT_IMAGE_ADDRESS; + job->data.ipl_tape.image_addr = IMAGE_LOAD_ADDRESS; } /* Fill in parmline */ rc = get_parmline(data[(int) scan_keyword_parmfile], @@ -1804,7 +1805,7 @@ get_job_from_config_file(struct command_line* cmdline, struct job_data* job) { struct scan_token* scan; struct scan_token* new_scan; - char* filename; + const char *filename = NULL; char *blsdir; char* source; int i, rc, scan_size; @@ -1827,6 +1828,10 @@ get_job_from_config_file(struct command_line* cmdline, struct job_data* job) break; } } + if (filename == NULL) { + error_text("No zipl configuration was readable"); + return -1; + } source = ""; } printf("Using config file '%s'%s\n", filename, source); diff --git a/zipl/src/misc.c b/zipl/src/misc.c index 057c9a0..8d1a2ee 100644 --- a/zipl/src/misc.c +++ b/zipl/src/misc.c @@ -9,6 +9,7 @@ * it under the terms of the MIT license. See LICENSE for details. */ +#include #include #include #include @@ -296,7 +297,7 @@ misc_temp_dev(dev_t dev, int blockdev, char** devno) char filename[] = "zipl0000"; mode_t mode; unsigned int path; - int retry; + unsigned int retry; int rc; int fd; @@ -310,7 +311,8 @@ misc_temp_dev(dev_t dev, int blockdev, char** devno) if (pathname[path] == NULL) continue; for (retry=0; retry < TEMP_DEV_MAX_RETRIES; retry++) { - sprintf(filename, "zipl%04d", retry); + assert(retry < 10000); + sprintf(filename, "zipl%04u", retry); result = misc_make_path(pathname[path], filename); if (result == NULL) return -1; @@ -366,6 +368,16 @@ misc_free_temp_dev(char* device) free(device); } +/* Delete temporary bootmap file */ +void +misc_free_temp_file(char *filename) +{ + if (remove(filename)) { + fprintf(stderr, + "Warning: Could not remove temporary file %s: %s", + filename, strerror(errno)); + } +} /* Write COUNT bytes from memory at location DATA to the file identified by * file descriptor FD. Return 0 when all bytes were successfully written, diff --git a/zipl/src/scan.c b/zipl/src/scan.c index f4228d3..38fa545 100644 --- a/zipl/src/scan.c +++ b/zipl/src/scan.c @@ -699,6 +699,10 @@ scan_bls_field(struct misc_file_buffer *file, struct scan_token* scan, } val_end = file->pos; + + while (val_end > val_start && isblank(file->buffer[val_end - 1])) + val_end--; + file->buffer[key_end] = '\0'; file->buffer[val_end] = '\0'; @@ -786,6 +790,7 @@ scan_bls(const char* blsdir, struct scan_token** token, int scan_size) case EOF: break; case '\t': + case '\n': case '\0': case ' ': file.pos++; -- 2.21.3 From efd2e0495726f8890fbff31053081472f948aeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 28 May 2020 09:54:34 +0200 Subject: [PATCH 54/56] ipl-tools: Add nvme device support to zipl, lsreipl/chreipl (#1525178) Summary: ipl-tools: Add nvme device support to zipl, lsreipl/chreipl Description: Code is also added to lsreipl and chreipl to list and change sysfs entries associated with nvme reipl kernel parameters. Code is also added to zipl to allow bootload installation to nvme devices. Upstream-ID: 71b36d17f019c9e2cf218351520d5b55a6c2d479 Upstream-ID: 0472b5ea5c97f2c59a938deebe53b7f27e8a9a32 --- include/lib/util_libc.h | 1 + ipl_tools/Makefile | 6 +- ipl_tools/cmd_chreipl.c | 153 ++++++++++++++++++++++++++++++++++-- ipl_tools/cmd_lsreipl.c | 33 ++++++++ ipl_tools/ipl_tools.h | 11 +++ ipl_tools/man/chreipl.8 | 53 ++++++++++++- ipl_tools/nvme.c | 169 ++++++++++++++++++++++++++++++++++++++++ libutil/util_libc.c | 27 +++++++ zipl/src/disk.c | 95 +++++++++++++++++++++- 9 files changed, 538 insertions(+), 10 deletions(-) create mode 100644 ipl_tools/nvme.c diff --git a/include/lib/util_libc.h b/include/lib/util_libc.h index dc6b3d0..394aca1 100644 --- a/include/lib/util_libc.h +++ b/include/lib/util_libc.h @@ -130,6 +130,7 @@ char *util_strcat_realloc(char *str1, const char *str2); void util_str_toupper(char *str); char *util_strstrip(char *s); +size_t util_strlcpy(char *dest, const char *src, size_t size); #ifdef __cplusplus } diff --git a/ipl_tools/Makefile b/ipl_tools/Makefile index 506d5cd..e5ad00e 100644 --- a/ipl_tools/Makefile +++ b/ipl_tools/Makefile @@ -1,11 +1,13 @@ include ../common.mak +libs = $(rootdir)/libutil/libutil.a + all: chreipl lsreipl chshut lsshut -objects = main.o ccw.o fcp.o system.o shutdown.o \ +objects = main.o ccw.o fcp.o nvme.o system.o shutdown.o \ cmd_lsshut.o cmd_chshut.o cmd_lsreipl.o cmd_chreipl.o proc.o -chreipl: $(objects) +chreipl: $(objects) $(libs) $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ lsreipl: diff --git a/ipl_tools/cmd_chreipl.c b/ipl_tools/cmd_chreipl.c index 640aa0c..9638e49 100644 --- a/ipl_tools/cmd_chreipl.c +++ b/ipl_tools/cmd_chreipl.c @@ -27,12 +27,14 @@ enum target_type { TT_FCP, TT_NSS, TT_NODE, + TT_NVME, }; enum reipl_type { REIPL_FCP, REIPL_CCW, - REIPL_NSS + REIPL_NSS, + REIPL_NVME, }; static const char *const usage_chreipl = @@ -40,6 +42,7 @@ static const char *const usage_chreipl = "\n" " chreipl [ccw] [-d] [OPTIONS]\n" " chreipl [fcp] [-d] [-w] [-l] [OPTIONS]\n" +" chreipl nvme [-i] [-s] [OPTIONS]\n" " chreipl [node] [OPTIONS]\n" " chreipl nss [-n] [OPTIONS]\n" " chreipl [-h] [-v]\n" @@ -47,6 +50,7 @@ static const char *const usage_chreipl = "The following re-IPL targets are supported:\n" " ccw IPL from CCW device\n" " fcp IPL from FCP device\n" +" nvme IPL from NVME device\n" " nss IPL from NSS\n" " node IPL from device specified by device node or directory\n" "\n" @@ -69,6 +73,12 @@ static const char *const usage_chreipl = " -L, --loadparm Loadparm specification\n" " -c, --clear 0|1 Control if memory is cleared on re-IPL\n" "\n" +"Options for nvme target:\n" +" -i, --fid PCI Function ID of NVME IPL device (hex)\n" +" -s --nsid Namespace ID of NVME IPL device (decimal, default 1)\n" +" -b, --bootprog Bootprog specification\n" +" -L, --loadparm Loadparm specification\n" +"\n" "Options for nss target:\n" " -n, --name Identifier of the NSS\n" "\n" @@ -85,6 +95,10 @@ static struct locals { char lun[20]; /* 18 character +0x" */ int lun_set; char busid[10]; /* Bus ID e.g. 0.0.4711 */ + int fid_set; + char fid[FID_MAX_LEN]; + int nsid_set; + char nsid[11]; /* 10 decimal chars + null */ int busid_set; char dev[15]; /* Device (e.g. dasda) */ int dev_set; @@ -93,10 +107,10 @@ static struct locals { char bootparms[4096]; int bootparms_set; int force_set; - enum target_type target_type; /* CCW, FCP, NSS or NODE */ + enum target_type target_type; /* CCW,FCP,NVME,NSS or NODE */ int target_type_set; int target_type_auto_mode; - enum reipl_type reipl_type; /* CCW, FCP, NSS */ + enum reipl_type reipl_type; /* CCW, FCP, NVME, NSS */ int reipl_clear; } l; @@ -228,6 +242,34 @@ static void set_wwpn(const char *wwpn) l.wwpn_set = 1; } +static void set_nvme_nsid(const char *nsid) +{ + unsigned long long nsid_tmp; + char *endptr; + + nsid_tmp = strtoull(nsid, &endptr, 10); + if (*endptr) + ERR_EXIT("NSID \"%s\" is not a decimal number", nsid); + snprintf(l.nsid, sizeof(l.nsid), "%08llu", nsid_tmp); + l.nsid_set = 1; +} + +static void set_nvme_fid(const char *fid) +{ + unsigned long long fid_tmp; + char *endptr; + + fid_tmp = strtoull(fid, &endptr, 16); + if (*endptr) + ERR_EXIT("FID \"%s\" is not a hexadecimal number", fid); + snprintf(l.fid, sizeof(l.fid), "0x%08llx", fid_tmp); + l.fid_set = 1; + + /* nsid defaults to 1, if not already set */ + if (!l.nsid_set) + set_nvme_nsid("1"); +} + static void parse_fcp_args(char *nargv[], int nargc) { /* @@ -247,6 +289,28 @@ static void parse_fcp_args(char *nargv[], int nargc) set_lun(nargv[2]); } +static void parse_nvme_args(char *nargv[], int nargc) +{ + /* + * we might be called like this: + * chreipl nvme 0x13 1 + */ + if (l.busid_set || l.fid_set || l.nsid_set || l.dev_set) + ERR_EXIT("Use either options or positional parameters"); + if (nargc > 2) + ERR_EXIT("Too many arguments specified for \"nvme\" re-IPL " + "type"); + else if (nargc < 1) + ERR_EXIT("The \"nvme\" re-IPL type requires function id, and " + "optional namespace id"); + set_nvme_fid(nargv[0]); + + if (nargc == 2) + set_nvme_nsid(nargv[1]); + else + set_nvme_nsid("1"); +} + static void parse_ccw_args(char *nargv[], int nargc) { /* @@ -287,6 +351,13 @@ static void dev_from_part(char *dev_name) dev_name[i] = 0; } +static void dev_from_part_nvme(char *dev_name) +{ + char *delim = strrchr(dev_name, 'p'); + if (delim) + *delim = 0; +} + static int set_reipl_type(const char *dev_name) { if (strncmp(dev_name, "dasd", strlen("dasd")) == 0 || @@ -294,11 +365,18 @@ static int set_reipl_type(const char *dev_name) l.reipl_type = REIPL_CCW; else if (strncmp(dev_name, "sd", strlen("sd")) == 0) l.reipl_type = REIPL_FCP; + else if (strncmp(dev_name, "nvme", strlen("nvme")) == 0) + l.reipl_type = REIPL_NVME; else return -1; strncpy(l.dev, dev_name, sizeof(l.dev)); - dev_from_part(l.dev); + + if (l.reipl_type == REIPL_NVME) + dev_from_part_nvme(l.dev); + else + dev_from_part(l.dev); + l.dev_set = 1; return 0; } @@ -399,6 +477,9 @@ static void parse_pos_args(char *nargv[], int nargc) case TT_FCP: parse_fcp_args(nargv, nargc); break; + case TT_NVME: + parse_nvme_args(nargv, nargc); + break; case TT_CCW: parse_ccw_args(nargv, nargc); break; @@ -420,6 +501,14 @@ static void check_fcp_opts(void) "and LUN"); } +static void check_nvme_opts(void) +{ + if (l.nss_name_set || l.wwpn_set || l.lun_set || l.busid_set) + ERR_EXIT("Invalid option for \"nvme\" target specified"); + if (!(l.fid_set && l.nsid_set)) + ERR_EXIT("The \"nvme\" target requires FID, and optional NSID"); +} + static void check_ccw_opts(void) { if (l.bootprog_set || l.lun_set || l.wwpn_set || l.nss_name_set) @@ -479,6 +568,8 @@ static void parse_chreipl_options(int argc, char *argv[]) { "device", required_argument, NULL, 'd' }, { "lun", required_argument, NULL, 'l' }, { "wwpn", required_argument, NULL, 'w' }, + { "fid", required_argument, NULL, 'i' }, + { "nsid", required_argument, NULL, 's' }, { "loadparm", required_argument, NULL, 'L' }, { "name", required_argument, NULL, 'n' }, { "bootparms", required_argument, NULL, 'p' }, @@ -487,7 +578,7 @@ static void parse_chreipl_options(int argc, char *argv[]) { "clear", required_argument, NULL, 'c' }, { NULL, 0, NULL, 0 } }; - static const char optstr[] = "hd:vw:l:fL:b:n:p:c:"; + static const char optstr[] = "hd:vw:l:fL:b:n:p:c:i:s:"; /* dont run without any argument */ if (argc == 1) @@ -499,6 +590,8 @@ static void parse_chreipl_options(int argc, char *argv[]) set_target_type(TT_CCW, 0); else if (strcmp(argv[1], "nss") == 0) set_target_type(TT_NSS, 0); + else if (strcmp(argv[1], "nvme") == 0) + set_target_type(TT_NVME, 0); else if (strcmp(argv[1], "node") == 0) set_target_type(TT_NODE, 0); else @@ -513,9 +606,15 @@ static void parse_chreipl_options(int argc, char *argv[]) case 'd': set_device(optarg); break; + case 'i': + set_nvme_fid(optarg); + break; case 'l': set_lun(optarg); break; + case 's': + set_nvme_nsid(optarg); + break; case 'w': set_wwpn(optarg); break; @@ -677,6 +776,40 @@ static void chreipl_fcp(void) print_fcp(0, 0); } +static void chreipl_nvme(void) +{ + check_nvme_opts(); + + if (!nvme_is_device(l.fid, l.nsid) && !l.force_set) { + ERR_EXIT("Could not find NVME device with fid %s and nsid %s", + l.fid, l.nsid); + } + check_exists("reipl/nvme/fid", "\"nvme\" re-IPL target"); + + if (l.bootparms_set && strlen(l.bootparms) > BOOTPARMS_FCP_MAX) { + ERR_EXIT("Maximum boot parameter length exceeded (%zu/%u)", + strlen(l.bootparms), BOOTPARMS_FCP_MAX); + } + + write_str_optional(l.loadparm, "reipl/nvme/loadparm", l.loadparm_set, + "loadparm"); + write_str_optional(l.bootparms, "reipl/nvme/scp_data", l.bootparms_set, + "boot parameters"); + write_str(l.fid, "reipl/nvme/fid"); + write_str(l.nsid, "reipl/nvme/nsid"); + /* + * set the boot record logical block address. Master boot + * record. It is always 0 for Linux + */ + write_str("0", "reipl/nvme/br_lba"); + if (!l.bootprog_set) + sprintf(l.bootprog, "0"); + write_str(l.bootprog, "reipl/nvme/bootprog"); + write_str("nvme", "reipl/reipl_type"); + + print_nvme(0, 0); +} + static void chreipl_nss(void) { check_nss_opts(); @@ -717,6 +850,13 @@ static void chreipl_node(void) l.busid_set = 1; chreipl_fcp(); break; + case REIPL_NVME: + nvme_fid_get(l.dev, l.fid); + l.fid_set = 1; + nvme_nsid_get(l.dev, l.nsid); + l.nsid_set = 1; + chreipl_nvme(); + break; default: ERR_EXIT("Internal error: chreipl_node"); } @@ -732,6 +872,9 @@ void cmd_chreipl(int argc, char *argv[]) case TT_FCP: chreipl_fcp(); break; + case TT_NVME: + chreipl_nvme(); + break; case TT_NSS: chreipl_nss(); break; diff --git a/ipl_tools/cmd_lsreipl.c b/ipl_tools/cmd_lsreipl.c index a829b9d..af9b9bc 100644 --- a/ipl_tools/cmd_lsreipl.c +++ b/ipl_tools/cmd_lsreipl.c @@ -84,6 +84,35 @@ void print_fcp(int show_ipl, int dump) print_fw_str("clear: %s\n", dir, "clear"); } +void print_nvme(int show_ipl, int dump) +{ + char *dir = show_ipl ? "ipl" : "reipl/nvme"; + char *path_bootparms = show_ipl ? "/sys/firmware/ipl/scp_data" : + "/sys/firmware/reipl/nvme/scp_data"; + char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" : + "/sys/firmware/reipl/nvme/loadparm"; + char loadparm[9], loadparm_path[PATH_MAX]; + + if (dump) + printf("%-12s nvme_dump\n", get_ipl_banner(show_ipl)); + else + printf("%-12s nvme\n", get_ipl_banner(show_ipl)); + + print_fw_str("FID: %s\n", dir, "fid"); + print_fw_str("NSID: %s\n", dir, "nsid"); + print_fw_str("bootprog: %s\n", dir, "bootprog"); + print_fw_str("br_lba: %s\n", dir, "br_lba"); + if (access(path_loadparm, R_OK) == 0) { + sprintf(loadparm_path, "%s/%s", dir, "loadparm"); + read_fw_str(loadparm, loadparm_path, sizeof(loadparm)); + if (strcmp(loadparm, " ") == 0) + loadparm[0] = 0; + printf("Loadparm: \"%s\"\n", loadparm); + } + if (access(path_bootparms, R_OK) == 0) + print_fw_str("Bootparms: \"%s\"\n", dir, "scp_data"); +} + void print_ccw(int show_ipl) { char loadparm[9], loadparm_path[PATH_MAX]; @@ -155,6 +184,10 @@ void cmd_lsreipl(int argc, char *argv[]) print_fcp(l.ipl_set, 0); else if (strcmp(reipl_type_str, "fcp_dump") == 0) print_fcp(l.ipl_set, 1); + else if (strcmp(reipl_type_str, "nvme") == 0) + print_nvme(l.ipl_set, 0); + else if (strcmp(reipl_type_str, "nvme_dump") == 0) + print_nvme(l.ipl_set, 1); else if (strcmp(reipl_type_str, "ccw") == 0) print_ccw(l.ipl_set); else if (strcmp(reipl_type_str, "nss") == 0) diff --git a/ipl_tools/ipl_tools.h b/ipl_tools/ipl_tools.h index 50d0b74..576d33a 100644 --- a/ipl_tools/ipl_tools.h +++ b/ipl_tools/ipl_tools.h @@ -43,6 +43,7 @@ extern void cmd_chreipl(int argc, char *argv[]); extern void print_ccw(int show_ipl); extern void print_fcp(int show_ipl, int dump); +extern void print_nvme(int show_ipl, int dump); extern void print_nss(int show_ipl); /* @@ -70,6 +71,16 @@ extern void fcp_lun_get(const char *device, char *lun); extern void fcp_wwpn_get(const char *device, char *wwpn); extern void fcp_busid_get(const char *device, char *devno); +/* + * NVME + */ +#define FID_MAX_LEN 11 /* 8 characters + 0x + null */ +#define NVME_PATH_MAX (PATH_MAX + NAME_MAX + 1) + +extern void nvme_fid_get(const char *device, char *fid); +extern void nvme_nsid_get(const char *device, char *nsid); +int nvme_is_device(char *fid_str, char *nsid_str); + /* * CCW */ diff --git a/ipl_tools/man/chreipl.8 b/ipl_tools/man/chreipl.8 index 91ddfad..e6ffc16 100644 --- a/ipl_tools/man/chreipl.8 +++ b/ipl_tools/man/chreipl.8 @@ -39,6 +39,9 @@ Specify a DASD CCW device for reboot .RB "- " fcp : Specify a FCP device for reboot .TP +.RB "- " nvme : +Specify an NVMe device for reboot +.TP .RB "- " nss : Specify a named saved system (NSS) for reboot .TP @@ -118,6 +121,11 @@ WWPN 0x500507630300c562, and LUN 0x401040b300000000. In addition to that append kernel parameter "mem=" to restrict memory to 512 MB: \fB# chreipl 0.0.1700 0x500507630300c562 0x401040b300000000 -p "mem=512M"\fP + +4. Next time reboot from the NVMe device with function id 0x13, namespace 1: + +\fB# chreipl nvme 0x13 1 + .SH ccw Use the ccw re-IPL target for DASD devices that are accessed by the hardware using channel command word (CCW) channels. @@ -207,6 +215,44 @@ use options instead of positional parameters: .br \fB# chreipl fcp -d 0.0.1700 -w 0x5005076... -l 0x401040b3... -b 2\fP +.SH nvme +Use the nvme re-IPL target for specifying an NVMe disk for reboot. +.TP +.BR "\-i" " or " "\-\-fid" +PCI Function ID of NVME IPL device (hex). + +.TP +.BR "\-s" " or " "\-\-nsid" +Namespace ID of the NVME IPL device (decimal, default 1). + +.TP +.BR "\-b" " or " "\-\-bootprog" +Specifies an entry in the boot configuration by defining the IPL boot +program selector. If omitted, '0' will be used. + +.TP +.BR "\-L" " or " "\-\-loadparm" +The loadparm for the nvme re-IPL target is not used to control the boot +configuration that is defined by the +.BR zipl (8) +boot menu. Instead it can be used to control higher level boot loaders +like GRUB. For more details refer to distribution specific documentation. + +.PP +\fBExamples:\fP +.br + +1. Next time reboot from the NVMe disk with function-id 0x13 and namespace 1: +.br + +\fB# chreipl nvme 0x13 1\fP +.br + +2. Use same configuration as (1) but choose boot program selector 2 and +use options instead of positional parameters: +.br + +\fB# chreipl nvme -i 0x13 -s 1 -b 2\fP .SH nss Use the nss re-IPL target to specify z/VM named saved systems (NSS) for reboot. @@ -221,7 +267,7 @@ Use the NSS named LINUX1 for the next reboot: \fB# chreipl nss LINUX1\fP .SH node -You can identify DASD or SCSI re-IPL devices indirectly through a device +You can identify DASD, SCSI, or NVMe re-IPL devices indirectly through a device node or directory. The chreipl tool then determines the information that you would otherwise have to specify with the ccw or fcp target. .PP @@ -244,6 +290,11 @@ that you would otherwise have to specify with the ccw or fcp target. \fB# chreipl node /mnt/boot\fP +4. Next time reboot from the NVMe device represented by /dev/nvme0n1 +.br + +\fB# chreipl node /dev/nvme0n1\fP + .SH SEE ALSO .BR lsreipl (8), .BR zipl (8), diff --git a/ipl_tools/nvme.c b/ipl_tools/nvme.c new file mode 100644 index 0000000..2a95972 --- /dev/null +++ b/ipl_tools/nvme.c @@ -0,0 +1,169 @@ +/* + * ipl_tools - Linux for System z reipl and shutdown tools + * + * NVMe device functions + * + * 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. + */ +#include +#include +#include +#include +#include + +#include "lib/util_libc.h" +#include "lib/util_file.h" +#include "ipl_tools.h" + +/* + * Return the fid of a device + */ +void nvme_fid_get(const char *device, char *fid) +{ + char path[PATH_MAX], buf[FID_MAX_LEN]; + + snprintf(path, PATH_MAX, "/sys/block/%s/device/device/function_id", + device); + if (util_file_read_line(buf, FID_MAX_LEN, path)) + ERR_EXIT_ERRNO("Could not read from \"%s\"", path); + + util_strlcpy(fid, buf, FID_MAX_LEN); +} +/* + * Return the nsid of a device + */ +void nvme_nsid_get(const char *device, char *nsid) +{ + char path[PATH_MAX], buf[FID_MAX_LEN]; + + snprintf(path, PATH_MAX, "/sys/block/%s/nsid", device); + if (util_file_read_line(buf, FID_MAX_LEN, path)) + ERR_EXIT_ERRNO("Could not read from \"%s\"", path); + + util_strlcpy(nsid, buf, FID_MAX_LEN); +} + +static int next_entry(DIR *dir, char *in_path, char *out_path, + unsigned char entry_type) +{ + struct dirent *dirent; + char temp_path[NVME_PATH_MAX]; + + while ((dirent = readdir(dir)) != NULL) { + if (strcmp(dirent->d_name, ".") == 0 || + strcmp(dirent->d_name, "..") == 0 || + dirent->d_type != entry_type) + continue; + + /* Resolve the symlink, if needed */ + if (dirent->d_type == DT_LNK) { + snprintf(temp_path, sizeof(temp_path), "%s/%s", in_path, + dirent->d_name); + if (!realpath(temp_path, out_path)) + ERR_EXIT_ERRNO("Could not resolve link %s", + temp_path); + return 1; + } + + snprintf(out_path, NVME_PATH_MAX, "%s/%s", in_path, + dirent->d_name); + return 1; + } + return 0; +} + +static int nvme_getdev_by_fid(char *fidstr, char *devpath) +{ + char temp_path[PATH_MAX+19], real_path[PATH_MAX]; + char *sys_path = "/sys/class/nvme"; + u_int64_t target_fid, curfid; + DIR *dir; + char *end; + int rc = -1; + + target_fid = strtoul(fidstr, &end, 16); + if (*end) + ERR_EXIT("Invalid function_id given %s", fidstr); + + dir = opendir(sys_path); + if (!dir) + ERR_EXIT("Could not open %s", sys_path); + + errno = 0; + while (next_entry(dir, sys_path, real_path, DT_LNK)) { + snprintf(temp_path, sizeof(temp_path), "%s/%s", real_path, + "device/function_id"); + if (access(temp_path, F_OK)) + continue; + + if (util_file_read_ul(&curfid, 16, temp_path)) + ERR_EXIT("Invalid function_id found in %s", temp_path); + + if (curfid == target_fid) { + strncpy(devpath, real_path, PATH_MAX); + rc = 0; + break; + } + } + + closedir(dir); + return rc; +} + +static int nvme_getdev_by_nsid(char *nsid_str, char *path, char *dev_path) +{ + char full_path[NVME_PATH_MAX+1], nsid_path[sizeof(full_path)+5]; + char *end; + u_int64_t nsid, curnsid; + DIR *dir; + + nsid = strtoul(nsid_str, &end, 10); + if (*end) + ERR_EXIT_ERRNO("Invalid namespace id given %s", nsid_str); + + dir = opendir(path); + if (!dir) + ERR_EXIT_ERRNO("Could not open %s", path); + + errno = 0; + while (next_entry(dir, path, full_path, DT_DIR)) { + snprintf(nsid_path, sizeof(nsid_path), "%s/%s", full_path, + "nsid"); + if (access(nsid_path, F_OK)) + continue; + + if (util_file_read_ul(&curnsid, 10, nsid_path)) + ERR_EXIT("Invalid namespace id found in %s", nsid_path); + + if (curnsid == nsid) { + strncpy(dev_path, full_path, NVME_PATH_MAX+1); + closedir(dir); + return 0; + } + } + closedir(dir); + return -1; +} + +static int nvme_getdev(char *fid_str, char *nsid_str, char *dev_path) +{ + char path_tmp[NVME_PATH_MAX]; + + if (nvme_getdev_by_fid(fid_str, path_tmp)) + return -1; + + return nvme_getdev_by_nsid(nsid_str, path_tmp, dev_path); +} + +/* + * Check if the specified fid and nsid leads to a valid nvme device + */ +int nvme_is_device(char *fid_str, char *nsid_str) +{ + char path_tmp[NVME_PATH_MAX+1]; + + return !(nvme_getdev(fid_str, nsid_str, path_tmp)); +} diff --git a/libutil/util_libc.c b/libutil/util_libc.c index 127b921..d3e7676 100644 --- a/libutil/util_libc.c +++ b/libutil/util_libc.c @@ -228,3 +228,30 @@ char *util_strstrip(char *s) return s; } + +/** + * Copy \a src to buffer \a dest of size \a size. At most size - 1 + * chars will be copied. \a dest will always be NUL terminated. + * + * Note: If the return value is greater than or equal to size truncation + * occurred. + * + * @param[in] dest Destination buffer + * @param[in] src Source string + * @param[in] size Size of destination buffer + * + * @returns strlen Length of \a src string + */ +size_t util_strlcpy(char *dest, const char *src, size_t size) +{ + size_t str_len = strlen(src); + size_t len; + + if (size) { + len = MIN(size - 1, str_len); + memcpy(dest, src, len); + dest[len] = '\0'; + } + + return str_len; +} diff --git a/zipl/src/disk.c b/zipl/src/disk.c index 8abf668..5baeaeb 100644 --- a/zipl/src/disk.c +++ b/zipl/src/disk.c @@ -89,6 +89,88 @@ disk_determine_dasd_type(struct disk_info *data, return 0; } +static int blkext_get_partnum(dev_t dev) +{ + char path[PATH_MAX], *buf; + int dev_major, dev_minor, partnum = -1; + + dev_major = major(dev); + dev_minor = minor(dev); + snprintf(path, PATH_MAX, "/sys/dev/block/%d:%d/partition", + dev_major, dev_minor); + + if (misc_read_special_file(path, &buf, NULL, 1)) { + error_text("Could not read from path '%s'", path); + return -1; + } + + partnum = atoi(buf); + free(buf); + if (partnum < 0) { + error_text("Bad partition number in '%s'", path); + return -1; + } + + return partnum; +} + +static int blkext_is_base_device(dev_t dev) +{ + int dev_major, dev_minor; + char path[PATH_MAX]; + struct stat stats; + + dev_major = major(dev); + dev_minor = minor(dev); + + snprintf(path, PATH_MAX, "/sys/dev/block/%d:%d/partition", + dev_major, dev_minor); + return (stat(path, &stats)); +} + +static int blkext_get_base_dev(dev_t dev, dev_t *base_dev) +{ + int base_major, base_minor; + char dev_path[PATH_MAX], base_path[PATH_MAX]; + char *temp_path, *buf; + + misc_asprintf(&temp_path, "/sys/dev/block/%d:%d", major(dev), minor(dev)); + if (!realpath(temp_path, dev_path)) { + error_reason(strerror(errno)); + error_text("Could not resolve link %s", temp_path); + free(temp_path); + return -1; + } + free(temp_path); + + misc_asprintf(&temp_path, "%s/..", dev_path); + if (!realpath(temp_path, base_path)) { + error_reason(strerror(errno)); + error_text("Could not resolve path %s", temp_path); + free(temp_path); + return -1; + } + free(temp_path); + + misc_asprintf(&temp_path, "%s/dev", base_path); + if (misc_read_special_file(temp_path, &buf, NULL, 1)) { + error_text("Could not read from path '%s'", temp_path); + free(temp_path); + return -1; + } + free(temp_path); + + if (sscanf(buf, "%i:%i", &base_major, &base_minor) != 2) { + error_text("Could not parse major:minor from string '%s'", buf); + free(buf); + return -1; + } + + free(buf); + *base_dev = makedev(base_major, base_minor); + return 0; +} + /* Return non-zero for ECKD type. */ int disk_is_eckd(disk_type_t type) @@ -409,8 +491,17 @@ disk_get_info(const char* device, struct job_target_data* target, } else if (strcmp(data->drv_name, "blkext") == 0) { data->devno = -1; data->type = disk_type_scsi; - data->partnum = stats.st_rdev & SCSI_PARTN_MASK; - data->device = stats.st_rdev & ~SCSI_PARTN_MASK; + + if (blkext_is_base_device(stats.st_rdev)) { + data->device = stats.st_rdev; + data->partnum = 0; + } else { + if (blkext_get_base_dev(stats.st_rdev, &data->device)) + goto out_close; + data->partnum = blkext_get_partnum(stats.st_rdev); + if (data->partnum == -1) + goto out_close; + } } else { /* Driver name is unknown */ error_reason("Unsupported device driver '%s'", data->drv_name); -- 2.21.3 From de78dc56b9fa81f78151f419396026f7c7214ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 28 May 2020 10:00:16 +0200 Subject: [PATCH 55/56] zkey: Add support for EP11 secure keys (#1723845) Summary: zkey: Add support for EP11 secure keys Description: With z15 and the CEX7 there is new crypto infrastructure available to support derivation of EP11 secure keys to protected keys. The zkey as well as the zkey-cryptsetup tools are enhanced to support EP11 secure keys. That is, zkey can manage CCA AES DATA keys, CCA AES CIPHER keys, and now also EP11 Secure keys. The key type must be specified at key generation time, the default remains to generate CCA AES DATA keys. Upstream-ID: d4cee7e65a4a3abf68380041a0bcd38075906f15 Upstream-ID: 52192908ebc5b802de877e10d48cdc4ce972c355 Upstream-ID: 1fd78d9f5ef795f466490a9aa5d72d039d6d27c4 Upstream-ID: 1a87d3a3b45fdc005116680f1ed5eaf41fdc38d3 Upstream-ID: 82c86fa8bd1108dcca77e6610d9e42a29c7584b1 Upstream-ID: 2ca442feed2b7a963105301590e0f192f227c7d7 Upstream-ID: ce4704f36537828d9378ea7640777f5b1275dfe8 Upstream-ID: d91e728e3c3b72e56ee48c60ebe99464bf8d8df6 Upstream-ID: b48aa5f4355c4547012bb60620ed4fa52e241e9d Upstream-ID: a7e47685e0d05b0af79402cd69b2aa984a9b5ce6 Upstream-ID: 88e6a18f960c8cfe0abd67ae7bc454fc37f222ef Upstream-ID: 0be7efc956340a20ecf6a5d2e71278dc66f03f05 Upstream-ID: f52aeabca4615e8330670f793d27d38e2bd81930 Upstream-ID: 729a98fcb30330273e1d05d0f170e4567b8fb67c Upstream-ID: 33031241c320f0479b39f55a0f86c5786f4c1822 --- zkey/Makefile | 20 +- zkey/cca.c | 20 +- zkey/cca.h | 2 +- zkey/ep11.c | 510 ++++++++++++++++++++++++++++++++++++ zkey/ep11.h | 178 +++++++++++++ zkey/keystore.c | 223 ++++++++-------- zkey/keystore.h | 7 +- zkey/pkey.c | 290 +++++++++++++++++++-- zkey/pkey.h | 81 +++++- zkey/utils.c | 573 +++++++++++++++++++++++++++++++++-------- zkey/utils.h | 44 +++- zkey/zkey-cryptsetup.1 | 53 ++-- zkey/zkey-cryptsetup.c | 216 +++++++--------- zkey/zkey.1 | 98 ++++--- zkey/zkey.c | 180 ++++++------- 15 files changed, 1947 insertions(+), 548 deletions(-) create mode 100644 zkey/ep11.c create mode 100644 zkey/ep11.h diff --git a/zkey/Makefile b/zkey/Makefile index f92de4f..100e408 100644 --- a/zkey/Makefile +++ b/zkey/Makefile @@ -64,20 +64,22 @@ zkey-cryptsetup-skip-jsonc: all: $(BUILD_TARGETS) -zkey.o: zkey.c pkey.h cca.h misc.h -pkey.o: pkey.c pkey.h -cca.o: cca.c cca.h pkey.h utils.h -utils.o: utils.h +zkey.o: zkey.c pkey.h cca.h ep11.h misc.h +pkey.o: pkey.c pkey.h cca.h ep11.h utils.h +cca.o: cca.c cca.h pkey.h ep11.h utils.h +ep11.o: ep11.c ep11.h pkey.h cca.h utils.h +utils.o: utils.h pkey.h cca.h ep11.h properties.o: check-dep-zkey properties.c properties.h -keystore.o: keystore.c keystore.h properties.h pkey.h cca.h utils.h -zkey-cryptsetup.o: check-dep-zkey-cryptsetup zkey-cryptsetup.c pkey.h cca.h misc.h +keystore.o: keystore.c keystore.h properties.h pkey.h cca.h ep11.h utils.h +zkey-cryptsetup.o: check-dep-zkey-cryptsetup zkey-cryptsetup.c pkey.h cca.h \ + ep11.h misc.h utils.h zkey: LDLIBS = -ldl -lcrypto -zkey: zkey.o pkey.o cca.o properties.o keystore.o utils.o $(libs) +zkey: zkey.o pkey.o cca.o ep11.o properties.o keystore.o utils.o $(libs) $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ -zkey-cryptsetup: LDLIBS = -ldl -lcryptsetup -ljson-c -zkey-cryptsetup: zkey-cryptsetup.o pkey.o cca.o utils.o $(libs) +zkey-cryptsetup: LDLIBS = -ldl -lcryptsetup -ljson-c -lcrypto +zkey-cryptsetup: zkey-cryptsetup.o pkey.o cca.o ep11.o utils.o $(libs) $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ install-common: diff --git a/zkey/cca.c b/zkey/cca.c index 01f7bfd..411a276 100644 --- a/zkey/cca.c +++ b/zkey/cca.c @@ -558,7 +558,7 @@ int select_cca_adapter(struct cca_lib *cca, int card, int domain, bool verbose) { unsigned int adapters, adapter; char adapter_serialnr[9]; - char apqn_serialnr[9]; + char apqn_serialnr[SERIALNR_LENGTH]; char temp[10]; int rc, found = 0; @@ -630,7 +630,7 @@ int select_cca_adapter(struct cca_lib *cca, int card, int domain, bool verbose) } struct find_mkvp_info { - u64 mkvp; + u8 mkvp[MKVP_LENGTH]; unsigned int flags; bool found; int card; @@ -653,12 +653,12 @@ static int find_mkvp(int card, int domain, void *handler_data) if (info->flags & FLAG_SEL_CCA_MATCH_CUR_MKVP) if (mk_info.cur_mk.mk_state == MK_STATE_VALID && - mk_info.cur_mk.mkvp == info->mkvp) + MKVP_EQ(mk_info.cur_mk.mkvp, info->mkvp)) found = true; if (info->flags & FLAG_SEL_CCA_MATCH_OLD_MKVP) if (mk_info.old_mk.mk_state == MK_STATE_VALID && - mk_info.old_mk.mkvp == info->mkvp) + MKVP_EQ(mk_info.old_mk.mkvp, info->mkvp)) found = true; if (info->flags & FLAG_SEL_CCA_NEW_MUST_BE_SET) @@ -700,25 +700,27 @@ static int find_mkvp(int card, int domain, void *handler_data) * because the zcrypt kernel module is on an older level. -ENODEV is * returned if no APQN is available with the desired mkvp. */ -int select_cca_adapter_by_mkvp(struct cca_lib *cca, u64 mkvp, const char *apqns, +int select_cca_adapter_by_mkvp(struct cca_lib *cca, u8 *mkvp, const char *apqns, unsigned int flags, bool verbose) { struct find_mkvp_info info; int rc; util_assert(cca != NULL, "Internal error: cca is NULL"); + util_assert(mkvp != NULL, "Internal error: mkvp is NULL"); - pr_verbose(verbose, "Select mkvp %016llx in APQNs %s for the CCA host " - "library", mkvp, apqns == 0 ? "ANY" : apqns); + pr_verbose(verbose, "Select mkvp %s in APQNs %s for the CCA host " + "library", printable_mkvp(CARD_TYPE_CCA, mkvp), + apqns == 0 ? "ANY" : apqns); - info.mkvp = mkvp; + memcpy(info.mkvp, mkvp, sizeof(info.mkvp)); info.flags = flags; info.found = false; info.card = 0; info.domain = 0; info.verbose = verbose; - rc = handle_apqns(apqns, find_mkvp, &info, verbose); + rc = handle_apqns(apqns, CARD_TYPE_CCA, find_mkvp, &info, verbose); if (rc < 0) return rc; diff --git a/zkey/cca.h b/zkey/cca.h index 2b248ec..c4761d5 100644 --- a/zkey/cca.h +++ b/zkey/cca.h @@ -129,7 +129,7 @@ int select_cca_adapter(struct cca_lib *cca, int card, int domain, bool verbose); #define FLAG_SEL_CCA_MATCH_OLD_MKVP 0x02 #define FLAG_SEL_CCA_NEW_MUST_BE_SET 0x80 -int select_cca_adapter_by_mkvp(struct cca_lib *cca, u64 mkvp, const char *apqns, +int select_cca_adapter_by_mkvp(struct cca_lib *cca, u8 *mkvp, const char *apqns, unsigned int flags, bool verbose); void print_msg_for_cca_envvars(const char *key_name); diff --git a/zkey/ep11.c b/zkey/ep11.c new file mode 100644 index 0000000..22f81f7 --- /dev/null +++ b/zkey/ep11.c @@ -0,0 +1,510 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * Copyright IBM Corp. 2019 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_base.h" +#include "lib/util_libc.h" +#include "lib/util_panic.h" + +#include "ep11.h" +#include "pkey.h" +#include "utils.h" + +#define pr_verbose(verbose, fmt...) do { \ + if (verbose) \ + warnx(fmt); \ + } while (0) + +/* + * Definitions for the EP11 library + */ +#define EP11_LIBRARY_NAME "libep11.so" +#define EP11_LIBRARY_VERSION 3 +#define EP11_WEB_PAGE "http://www.ibm.com/security/cryptocards" + +/** + * Returns the major and minor version of the of the used EP11 host library. + * + * @param[in] ep11 the EP11 library structure + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error + */ +static int get_ep11_version(struct ep11_lib *ep11, bool verbose) +{ + unsigned int host_version; + CK_ULONG version_len = sizeof(host_version); + CK_RV rc; + + rc = ep11->dll_m_get_xcp_info(&host_version, &version_len, + CK_IBM_XCPHQ_VERSION, 0, 0); + if (rc != CKR_OK) { + pr_verbose(verbose, "Failed to obtain the EP11 host library " + "version: m_get_xcp_info: 0x%lx", rc); + return -EIO; + } + + pr_verbose(verbose, "host_version: 0x%08x", host_version); + + ep11->version.major = (host_version & 0x00FF0000) >> 16; + ep11->version.minor = host_version & 0x000000FF; + /* + * EP11 host library < v2.0 returns an invalid version (i.e. 0x100). + * This can safely be treated as version 1.0 + */ + if (ep11->version.major == 0) { + ep11->version.major = 1; + ep11->version.minor = 0; + } + + pr_verbose(verbose, "EP11 library version: %u.%u", + ep11->version.major, ep11->version.minor); + + return 0; +} + +/** + * Loads the EP11 library and provides the entry points of several functions. + * + * @param[out] ep11 on return this contains the address of the EP11 + * library and certain EP11 symbols. dlclose() should + * be used to free the library when no longer needed. + * @param verbose if true, verbose messages are printed + * + * @returns 0 on success, -ELIBACC in case of library load errors + */ +int load_ep11_library(struct ep11_lib *ep11, bool verbose) +{ + char lib_name[256]; + int libver; + int rc; + + util_assert(ep11 != NULL, "Internal error: ep11 is NULL"); + + /* Load the EP11 library with highest available version'd SO name */ + for (libver = EP11_LIBRARY_VERSION; libver >= 0; libver--) { + if (libver > 0) + sprintf(lib_name, "%s.%d", EP11_LIBRARY_NAME, libver); + else + sprintf(lib_name, "%s", EP11_LIBRARY_NAME); + + ep11->lib_ep11 = dlopen(lib_name, RTLD_GLOBAL | RTLD_NOW); + if (ep11->lib_ep11 != NULL) + break; + } + if (ep11->lib_ep11 == NULL) { + pr_verbose(verbose, "%s", dlerror()); + warnx("The command requires the IBM Z Enterprise PKCS #11 " + "(EP11) Support Program (EP11 host library).\n" + "For the supported environments and downloads, see:\n%s", + EP11_WEB_PAGE); + return -ELIBACC; + } + + /* Get several EP11 host library functions */ + ep11->dll_m_init = (m_init_t)dlsym(ep11->lib_ep11, "m_init"); + ep11->dll_m_add_module = (m_add_module_t)dlsym(ep11->lib_ep11, + "m_add_module"); + ep11->dll_m_rm_module = (m_rm_module_t)dlsym(ep11->lib_ep11, + "m_rm_module"); + ep11->dll_m_get_xcp_info = (m_get_xcp_info_t)dlsym(ep11->lib_ep11, + "m_get_xcp_info"); + + ep11->dll_m_admin = (m_admin_t)dlsym(ep11->lib_ep11, "m_admin"); + ep11->dll_xcpa_cmdblock = (xcpa_cmdblock_t)dlsym(ep11->lib_ep11, + "xcpa_cmdblock"); + if (ep11->dll_xcpa_cmdblock == NULL) + ep11->dll_xcpa_cmdblock = (xcpa_cmdblock_t)dlsym(ep11->lib_ep11, + "ep11a_cmdblock"); + ep11->dll_xcpa_internal_rv = (xcpa_internal_rv_t)dlsym(ep11->lib_ep11, + "xcpa_internal_rv"); + if (ep11->dll_xcpa_internal_rv == NULL) + ep11->dll_xcpa_internal_rv = + (xcpa_internal_rv_t)dlsym(ep11->lib_ep11, + "ep11a_internal_rv"); + + /* dll_m_add_module and dll_m_rm_module may be NULL for V1 EP11 lib */ + if (ep11->dll_m_init == NULL || + ep11->dll_m_get_xcp_info == NULL || + ep11->dll_m_admin == NULL || + ep11->dll_xcpa_cmdblock == NULL || + ep11->dll_xcpa_internal_rv == NULL) { + pr_verbose(verbose, "%s", dlerror()); + warnx("The command requires the IBM Z Enterprise PKCS #11 " + "(EP11) Support Program (EP11 host library).\n" + "For the supported environments and downloads, see:\n%s", + EP11_WEB_PAGE); + dlclose(ep11->lib_ep11); + ep11->lib_ep11 = NULL; + return -ELIBACC; + } + + /* Initialize the EP11 library */ + rc = ep11->dll_m_init(); + if (rc != 0) { + pr_verbose(verbose, "Failed to initialize the EP11 host " + "library: m_init: 0x%x", rc); + dlclose(ep11->lib_ep11); + ep11->lib_ep11 = NULL; + return -ELIBACC; + } + + pr_verbose(verbose, "EP11 library '%s' has been loaded successfully", + lib_name); + + return get_ep11_version(ep11, verbose); +} + +/** + * Get an EP11 target handle for a specific APQN (card and domain) + * + * @param[in] ep11 the EP11 library structure + * @param[in] card the card number + * @param[in] domain the domain number + * @param[out] target on return: the target handle for the APQN + * @param verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of errors + */ +int get_ep11_target_for_apqn(struct ep11_lib *ep11, int card, int domain, + target_t *target, bool verbose) +{ + ep11_target_t *target_list; + struct XCP_Module module; + CK_RV rc; + + util_assert(ep11 != NULL, "Internal error: ep11 is NULL"); + util_assert(target != NULL, "Internal error: target is NULL"); + + *target = XCP_TGT_INIT; + + if (ep11->dll_m_add_module != NULL) { + memset(&module, 0, sizeof(module)); + module.version = ep11->version.major >= 3 ? XCP_MOD_VERSION_2 + : XCP_MOD_VERSION_1; + module.flags = XCP_MFL_MODULE; + module.module_nr = card; + XCPTGTMASK_SET_DOM(module.domainmask, domain); + rc = ep11->dll_m_add_module(&module, target); + if (rc != 0) { + pr_verbose(verbose, "Failed to add APQN %02x.%04x: " + "m_add_module rc=0x%lx", card, domain, rc); + return -EIO; + } + } else { + /* Fall back to old target handling */ + target_list = (ep11_target_t *)calloc(1, sizeof(ep11_target_t)); + if (target_list == NULL) + return -ENOMEM; + target_list->length = 1; + target_list->apqns[0] = card; + target_list->apqns[1] = domain; + *target = (target_t)target_list; + } + + return 0; +} + +/** + * Free an EP11 target handle + * + * @param[in] ep11 the EP11 library structure + * @param[in] target the target handle to free + * + * @returns 0 on success, a negative errno in case of errors + */ +void free_ep11_target_for_apqn(struct ep11_lib *ep11, target_t target) +{ + util_assert(ep11 != NULL, "Internal error: ep11 is NULL"); + + if (ep11->dll_m_rm_module != NULL) { + ep11->dll_m_rm_module(NULL, target); + } else { + /* + * With the old target handling, target is a pointer to + * ep11_target_t + */ + free((ep11_target_t *)target); + } +} + +struct find_mkvp_info { + u8 mkvp[MKVP_LENGTH]; + unsigned int flags; + bool found; + int card; + int domain; + bool verbose; +}; + +static int find_mkvp(int card, int domain, void *handler_data) +{ + struct find_mkvp_info *info = (struct find_mkvp_info *)handler_data; + struct mk_info mk_info; + bool found = false; + int rc; + + rc = sysfs_get_mkvps(card, domain, &mk_info, info->verbose); + if (rc == -ENODEV) + return 0; + if (rc != 0) + return rc; + + if (info->flags & FLAG_SEL_EP11_MATCH_CUR_MKVP) + if (mk_info.cur_mk.mk_state == MK_STATE_VALID && + MKVP_EQ(mk_info.cur_mk.mkvp, info->mkvp)) + found = true; + + if (info->flags & FLAG_SEL_EP11_NEW_MUST_BE_SET) + if (mk_info.new_mk.mk_state != MK_STATE_COMMITTED) + found = false; + + if (found) { + info->card = card; + info->domain = domain; + info->found = true; + + pr_verbose(info->verbose, "%02x.%04x has the desired mkvp%s", + card, domain, + info->flags & FLAG_SEL_EP11_NEW_MUST_BE_SET ? + " and NEW MK set" : ""); + + return 1; + } + + return 0; +} + +/** + * Selects an APQN to be used for the Ep11 host library that has the specified + * master key verification pattern + * + * @param[in] ep11 the EP11 library structure + * @param[in] mkvp the master key verification pattern to search for + * @param[in] apqns a comma separated list of APQNs. If NULL is specified, + * or an empty string, then all online EP11 APQNs are + * checked. + * @param[in] flags Flags that control the MKVM matching and NEW register + * checking. Multiple flags can be combined. + * @param[out] target on return: the target handle for the APQN. If this is + * NULL, then no target is built. + * @param[out] card on return: the card that was selected (can be NULL) + * @param[out] domain on return: the domain that was selected (can be NULL) + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of errors + */ +int select_ep11_apqn_by_mkvp(struct ep11_lib *ep11, u8 *mkvp, + const char *apqns, unsigned int flags, + target_t *target, int *card, int *domain, + bool verbose) +{ + struct find_mkvp_info info; + int rc; + + util_assert(ep11 != NULL, "Internal error: ep11 is NULL"); + util_assert(mkvp != NULL, "Internal error: mkvp is NULL"); + + pr_verbose(verbose, "Select mkvp %s in APQNs %s for the EP11 host " + "library", printable_mkvp(CARD_TYPE_EP11, mkvp), + apqns == 0 ? "ANY" : apqns); + + memcpy(info.mkvp, mkvp, sizeof(info.mkvp)); + info.flags = flags; + info.found = false; + info.card = 0; + info.domain = 0; + info.verbose = verbose; + + rc = handle_apqns(apqns, CARD_TYPE_EP11, find_mkvp, &info, verbose); + if (rc < 0) + return rc; + + if (!info.found) + return -ENODEV; + + if (target != NULL) { + rc = get_ep11_target_for_apqn(ep11, info.card, info.domain, + target, verbose); + if (rc != 0) + return rc; + } + + if (card != NULL) + *card = info.card; + if (domain != NULL) + *domain = info.domain; + + return 0; +} + +/** + * Performs an EP11 administrative request to Re-encrypt a single EP11 secure + * key with a new EP11 master key (wrapping key). + * + * @param[in] ep11 the EP11 library structure + * @param[in] target the target handle to use for the re-encipher operation + * @param[in] card the card that corresponds to the target handle + * @param[in] domain the domain that corresponds to the target handle + * @param[in/out] ep11key the EP11 key token to reencipher. The re-enciphered + * secure key will be returned in this buffer. + * @param[in] ep11key_size the size of the secure key + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of errors + */ +static int ep11_adm_reencrypt(struct ep11_lib *ep11, target_t target, int card, + int domain, struct ep11keytoken *ep11key, + unsigned int ep11key_size, bool verbose) +{ + CK_BYTE resp[MAX_BLOBSIZE]; + CK_BYTE req[MAX_BLOBSIZE]; + char ep11_token_header[sizeof(ep11key->head)]; + struct XCPadmresp lrb; + struct XCPadmresp rb; + size_t resp_len; + size_t blob_len; + long req_len; + CK_RV rv; + int rc; + + blob_len = ep11key->head.length; + if (blob_len > ep11key_size) { + pr_verbose(verbose, "Blob length larger than secure key size"); + return -EINVAL; + } + + rb.domain = domain; + lrb.domain = domain; + + /* The token header is an overlay over the (all zero) session field */ + memcpy(ep11_token_header, ep11key, sizeof(ep11_token_header)); + memset(ep11key->session, 0, sizeof(ep11key->session)); + + resp_len = sizeof(resp); + req_len = ep11->dll_xcpa_cmdblock(req, sizeof(req), XCP_ADM_REENCRYPT, + &rb, NULL, (unsigned char *)ep11key, + blob_len); + if (req_len < 0) { + pr_verbose(verbose, "Failed to build XCP command block"); + return -EIO; + } + + rv = ep11->dll_m_admin(resp, &resp_len, NULL, 0, req, req_len, NULL, 0, + target); + if (rv != CKR_OK || resp_len == 0) { + pr_verbose(verbose, "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) { + pr_verbose(verbose, "Failed to parse response. rc = %d", rc); + return -EIO; + } + + if (rv != CKR_OK) { + pr_verbose(verbose, "Failed to re-encrypt the EP11 secure key. " + "rc = 0x%lx", rv); + switch (rv) { + case CKR_IBM_WKID_MISMATCH: + warnx("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 (blob_len != lrb.pllen) { + pr_verbose(verbose, "Re-encrypted EP11 secure key size has " + "changed: org-len: %lu, new-len: %lu", blob_len, + lrb.pllen); + return -EIO; + } + + memcpy(ep11key, lrb.payload, blob_len); + memcpy(ep11key, ep11_token_header, sizeof(ep11_token_header)); + + return 0; +} + +/** + * Re-encipher an EP11 secure key with a new EP11 master key (wrapping key). + * + * @param[in] ep11 the EP11 library structure + * @param[in] target the target handle to use for the re-encipher operation + * @param[in] card the card that corresponds to the target handle + * @param[in] domain the domain that corresponds to the target handle + * @param[in/out] secure_key the EP11 key token to reencipher. The re-enciphered + * secure key will be returned in this buffer. + * @param[in] secure_key_size the size of the secure key + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of errors + */ +int reencipher_ep11_key(struct ep11_lib *ep11, target_t target, int card, + int domain, u8 *secure_key, + unsigned int secure_key_size, bool verbose) +{ + struct ep11keytoken *ep11key = (struct ep11keytoken *)secure_key; + CK_IBM_DOMAIN_INFO dinf; + CK_ULONG dinf_len = sizeof(dinf); + CK_RV rv; + int rc; + + util_assert(ep11 != NULL, "Internal error: ep11 is NULL"); + util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + + rv = ep11->dll_m_get_xcp_info(&dinf, &dinf_len, CK_IBM_XCPQ_DOMAIN, 0, + target); + if (rv != CKR_OK) { + pr_verbose(verbose, "Failed to query domain information for " + "%02X.%04X: m_get_xcp_info rc: 0x%lx", card, domain, + rv); + return -EIO; + } + + if ((dinf.flags & CK_IBM_DOM_COMMITTED_NWK) == 0) { + warnx("The NEW master key register of APQN %02X.%04X is not " + "in COMMITTED state", card, domain); + return -ENODEV; + } + + rc = ep11_adm_reencrypt(ep11, target, card, domain, ep11key, + secure_key_size, verbose); + if (rc != 0) + return rc; + + if (is_xts_key(secure_key, secure_key_size)) { + secure_key += EP11_KEY_SIZE; + secure_key_size -= EP11_KEY_SIZE; + ep11key = (struct ep11keytoken *)secure_key; + + rc = ep11_adm_reencrypt(ep11, target, card, domain, ep11key, + secure_key_size, verbose); + if (rc != 0) + return rc; + } + + return 0; +} + diff --git a/zkey/ep11.h b/zkey/ep11.h new file mode 100644 index 0000000..3a0c2b5 --- /dev/null +++ b/zkey/ep11.h @@ -0,0 +1,178 @@ +/* + * zkey - Generate, re-encipher, and validate secure keys + * + * This header file defines the interface to the EP11 host library. + * + * Copyright IBM Corp. 2019 + * + * 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 EP11_H +#define EP11_H + +#include + +#include "lib/zt_common.h" + +/* EP11 definitions */ + +typedef uint64_t target_t; +typedef unsigned long int CK_ULONG; +typedef CK_ULONG CK_RV; +typedef unsigned char CK_BYTE; +typedef CK_BYTE CK_CHAR; +typedef CK_ULONG *CK_ULONG_PTR; +typedef void *CK_VOID_PTR; + +typedef struct XCP_ModuleSocket { + char host[256 + 1]; + uint32_t port; +} *XCP_ModuleSocket_t; + +typedef struct XCP_DomainPerf { + unsigned int lastperf[256]; +} *XCP_DomainPerf_t; + +typedef struct XCP_Module { + uint32_t version; + uint64_t flags; + uint32_t domains; + unsigned char domainmask[256 / 8]; + struct XCP_ModuleSocket socket; + uint32_t module_nr; + void *mhandle; + struct XCP_DomainPerf perf; + /* ----- end of v1 fields ----- */ + uint32_t api; + /* ----- end of v2 fields ----- */ +} *XCP_Module_t; + +typedef enum { + XCP_MFL_SOCKET = 1, + XCP_MFL_MODULE = 2, + XCP_MFL_MHANDLE = 4, + XCP_MFL_PERF = 8, + XCP_MFL_VIRTUAL = 0x10, + XCP_MFL_STRICT = 0x20, + XCP_MFL_PROBE = 0x40, + XCP_MFL_ALW_TGT_ADD = 0x80, + XCP_MFL_MAX = 0xff +} XCP_Module_Flags; + +#define XCP_MOD_VERSION_1 1 +#define XCP_MOD_VERSION_2 2 +#define XCP_TGT_INIT ~0UL + +#define XCPTGTMASK_SET_DOM(mask, domain) \ + mask[((domain)/8)] |= (1 << (7-(domain)%8)) + +#define XCP_SERIALNR_CHARS 8 +#define XCP_ADMCTR_BYTES ((size_t) (128/8)) +#define XCP_KEYCSUM_BYTES (256/8) + +#define XCP_ADM_REENCRYPT 25 /* transform blobs to next WK */ + +#define MAX_BLOBSIZE 8192 + +#define CKR_VENDOR_DEFINED 0x80000000 +#define CKR_IBM_WKID_MISMATCH CKR_VENDOR_DEFINED + 0x10001 + +typedef struct XCPadmresp { + uint32_t fn; + uint32_t domain; + uint32_t domainInst; + + /* module ID || module instance */ + unsigned char module[XCP_SERIALNR_CHARS + XCP_SERIALNR_CHARS]; + unsigned char modNr[XCP_SERIALNR_CHARS]; + unsigned char modInst[XCP_SERIALNR_CHARS]; + + unsigned char tctr[XCP_ADMCTR_BYTES]; /* transaction counter */ + + CK_RV rv; + uint32_t reason; + + const unsigned char *payload; + size_t pllen; +} *XCPadmresp_t; + +typedef struct CK_IBM_DOMAIN_INFO { + CK_ULONG domain; + CK_BYTE wk[XCP_KEYCSUM_BYTES]; + CK_BYTE nextwk[XCP_KEYCSUM_BYTES]; + CK_ULONG flags; + CK_BYTE mode[8]; +} CK_IBM_DOMAIN_INFO; + +#define CK_IBM_DOM_COMMITTED_NWK 8 + +#define CK_IBM_XCPHQ_VERSION 0xff000001 +#define CK_IBM_XCPQ_DOMAIN 3 + +#define MAX_APQN 256 + +typedef struct { + short format; + short length; + short apqns[2 * MAX_APQN]; +} __packed ep11_target_t; + +#define CKR_OK 0x00000000 + +typedef int (*m_init_t) (void); +typedef int (*m_add_module_t) (XCP_Module_t module, target_t *target); +typedef int (*m_rm_module_t) (XCP_Module_t module, target_t target); +typedef CK_RV (*m_get_xcp_info_t)(CK_VOID_PTR pinfo, CK_ULONG_PTR infbytes, + unsigned int query, unsigned int subquery, + target_t target); +typedef unsigned long int (*m_admin_t)(unsigned char *resp1, size_t *r1len, + unsigned char *resp2, size_t *r2len, + const unsigned char *cmd, size_t clen, + const unsigned char *sigs, size_t slen, + target_t target); +typedef long (*xcpa_cmdblock_t)(unsigned char *blk, size_t blen, + unsigned int fn, const struct XCPadmresp *minf, + const unsigned char *tctr, + const unsigned char *payload, size_t plen); +typedef long (*xcpa_internal_rv_t)(const unsigned char *rsp, size_t rlen, + struct XCPadmresp *rspblk, CK_RV *rv); + +struct ep11_version { + unsigned int minor; + unsigned int major; +}; + +struct ep11_lib { + void *lib_ep11; + m_init_t dll_m_init; + m_add_module_t dll_m_add_module; + m_rm_module_t dll_m_rm_module; + m_get_xcp_info_t dll_m_get_xcp_info; + m_admin_t dll_m_admin; + xcpa_cmdblock_t dll_xcpa_cmdblock; + xcpa_internal_rv_t dll_xcpa_internal_rv; + struct ep11_version version; +}; + +int load_ep11_library(struct ep11_lib *ep11, bool verbose); + +int get_ep11_target_for_apqn(struct ep11_lib *ep11, int card, int domain, + target_t *target, bool verbose); + +void free_ep11_target_for_apqn(struct ep11_lib *ep11, target_t target); + +#define FLAG_SEL_EP11_MATCH_CUR_MKVP 0x01 +#define FLAG_SEL_EP11_NEW_MUST_BE_SET 0x80 + +int select_ep11_apqn_by_mkvp(struct ep11_lib *ep11, u8 *mkvp, + const char *apqns, unsigned int flags, + target_t *target, int *card, int *domain, + bool verbose); + +int reencipher_ep11_key(struct ep11_lib *ep11, target_t target, int card, + int domain, u8 *secure_key, + unsigned int secure_key_size, bool verbose); + +#endif diff --git a/zkey/keystore.c b/zkey/keystore.c index 4f90bdd..2cf37bc 100644 --- a/zkey/keystore.c +++ b/zkey/keystore.c @@ -343,6 +343,8 @@ static int _keystore_valid_key_type(const char *key_type) return 1; if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) return 1; + if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) + return 1; return 0; } @@ -1085,6 +1087,7 @@ free: struct apqn_check { bool noonlinecheck; bool nomsg; + enum card_type cardtype; }; /** @@ -1106,6 +1109,7 @@ static int _keystore_apqn_check(const char *apqn, bool remove, bool UNUSED(set), int rc, card, domain; regmatch_t pmatch[1]; regex_t reg_buf; + unsigned int num; *normalized = NULL; @@ -1120,7 +1124,9 @@ static int _keystore_apqn_check(const char *apqn, bool remove, bool UNUSED(set), goto out; } - if (sscanf(apqn, "%x.%x", &card, &domain) != 2) { + if (sscanf(apqn, "%x.%x%n", &card, &domain, &num) != 2 || + num != strlen(apqn) || card < 0 || card > 0xff || + domain < 0 || domain > 0xFFFF) { warnx("the APQN '%s' is not valid", apqn); rc = -EINVAL; goto out; @@ -1133,11 +1139,11 @@ static int _keystore_apqn_check(const char *apqn, bool remove, bool UNUSED(set), goto out; } - rc = sysfs_is_apqn_online(card, domain); + rc = sysfs_is_apqn_online(card, domain, info->cardtype); if (rc != 1) { if (info->nomsg == 0) warnx("The APQN %02x.%04x is %s", card, domain, - rc == -1 ? "not a CCA card" : "not online"); + rc == -1 ? "not the correct type" : "not online"); rc = -EIO; goto out; } else { @@ -1574,7 +1580,9 @@ static int _keystore_create_info_file(struct keystore *keystore, struct volume_check vol_check = { .keystore = keystore, .name = name, .set = 0 }; struct apqn_check apqn_check = { .noonlinecheck = noapqncheck, - .nomsg = 0 }; + .nomsg = 0, + .cardtype = get_card_type_for_keytype( + key_type), }; struct properties *key_props; char temp[10]; int rc; @@ -1722,9 +1730,11 @@ int keystore_generate_key(struct keystore *keystore, const char *name, if (rc != 0) goto out_free_key_filenames; - rc = cross_check_apqns(apqns, 0, - get_min_card_level_for_keytype(key_type), true, - keystore->verbose); + rc = cross_check_apqns(apqns, NULL, + get_min_card_level_for_keytype(key_type), + get_min_fw_version_for_keytype(key_type), + get_card_type_for_keytype(key_type), + true, keystore->verbose); if (rc == -EINVAL) goto out_free_key_filenames; if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { @@ -1801,7 +1811,7 @@ out_free_key_filenames: * default is used. * @param[in] import_file The name of a secure key containing the key to import * @param[in] volume_type the type of volume - * @param[in] cca the CCA library struct + * @param[in] lib the external library struct * * @returns 0 for success or a negative errno in case of an error */ @@ -1809,15 +1819,15 @@ int keystore_import_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, const char *apqns, bool noapqncheck, size_t sector_size, const char *import_file, const char *volume_type, - struct cca_lib *cca) + struct ext_lib *lib) { struct key_filenames file_names = { NULL, NULL, NULL }; struct properties *key_props = NULL; size_t secure_key_size; const char *key_type; + u8 mkvp[MKVP_LENGTH]; int selected = 1; u8 *secure_key; - u64 mkvp; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); @@ -1848,7 +1858,7 @@ int keystore_import_key(struct keystore *keystore, const char *name, } rc = get_master_key_verification_pattern(secure_key, secure_key_size, - &mkvp, keystore->verbose); + mkvp, keystore->verbose); if (rc != 0) { warnx("Failed to get the master key verification pattern: %s", strerror(-rc)); @@ -1856,8 +1866,10 @@ int keystore_import_key(struct keystore *keystore, const char *name, } rc = cross_check_apqns(apqns, mkvp, - get_min_card_level_for_keytype(key_type), true, - keystore->verbose); + get_min_card_level_for_keytype(key_type), + get_min_fw_version_for_keytype(key_type), + get_card_type_for_keytype(key_type), + true, keystore->verbose); if (rc == -EINVAL) goto out_free_key; if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { @@ -1866,13 +1878,13 @@ int keystore_import_key(struct keystore *keystore, const char *name, } if (is_cca_aes_cipher_key(secure_key, secure_key_size)) { - if (cca->lib_csulcca == NULL) { - rc = load_cca_library(cca, keystore->verbose); + if (lib->cca->lib_csulcca == NULL) { + rc = load_cca_library(lib->cca, keystore->verbose); if (rc != 0) goto out_free_key; } - rc = select_cca_adapter_by_mkvp(cca, mkvp, apqns, + rc = select_cca_adapter_by_mkvp(lib->cca, mkvp, apqns, FLAG_SEL_CCA_MATCH_CUR_MKVP | FLAG_SEL_CCA_MATCH_OLD_MKVP, keystore->verbose); @@ -1887,7 +1899,7 @@ int keystore_import_key(struct keystore *keystore, const char *name, goto out_free_key; } - rc = restrict_key_export(cca, secure_key, secure_key_size, + rc = restrict_key_export(lib->cca, secure_key, secure_key_size, keystore->verbose); if (rc != 0) { warnx("Failed to export-restrict the imported secure " @@ -1991,9 +2003,9 @@ int keystore_change_key(struct keystore *keystore, const char *name, struct properties *key_props = NULL; char *apqns_prop, *key_type; size_t secure_key_size; + u8 mkvp[MKVP_LENGTH]; u8 *secure_key; char temp[30]; - u64 mkvp; int rc; util_assert(keystore != NULL, "Internal error: keystore is NULL"); @@ -2050,7 +2062,7 @@ int keystore_change_key(struct keystore *keystore, const char *name, rc = get_master_key_verification_pattern(secure_key, secure_key_size, - &mkvp, + mkvp, keystore->verbose); free(secure_key); if (rc) @@ -2060,6 +2072,8 @@ int keystore_change_key(struct keystore *keystore, const char *name, key_type = properties_get(key_props, PROP_NAME_KEY_TYPE); rc = cross_check_apqns(apqns_prop, mkvp, get_min_card_level_for_keytype(key_type), + get_min_fw_version_for_keytype(key_type), + get_card_type_for_keytype(key_type), true, keystore->verbose); free(apqns_prop); free(key_type); @@ -2274,7 +2288,7 @@ static void _keystore_print_record(struct util_rec *rec, bool validation, const char *skey_filename, size_t secure_key_size, bool is_xts, size_t clear_key_bitsize, bool valid, - bool is_old_mk, bool reenc_pending, u64 mkvp) + bool is_old_mk, bool reenc_pending, u8 *mkvp) { char temp_vp[VERIFICATION_PATTERN_LEN + 2]; char *volumes_argz = NULL; @@ -2338,11 +2352,17 @@ static void _keystore_print_record(struct util_rec *rec, if (validation) { if (valid) util_rec_set(rec, REC_MASTERKEY, - "%s CCA master key (MKVP: %016llx)", - is_old_mk ? "OLD" : "CURRENT", mkvp); + "%s master key (MKVP: %s)", + is_old_mk ? "OLD" : "CURRENT", + printable_mkvp( + get_card_type_for_keytype(key_type), + mkvp)); else util_rec_set(rec, REC_MASTERKEY, - "(unknown, MKVP: %016llx)", mkvp); + "(unknown, MKVP: %s)", + printable_mkvp( + get_card_type_for_keytype(key_type), + mkvp)); } if (volumes_argz != NULL) util_rec_set_argz(rec, REC_VOLUMES, volumes_argz, @@ -2424,7 +2444,7 @@ struct validate_info { */ static int _keystore_display_apqn_status(struct keystore *keystore, struct properties *properties, - u64 mkvp) + u8 *mkvp) { int rc, warning = 0; char *apqns; @@ -2437,7 +2457,9 @@ static int _keystore_display_apqn_status(struct keystore *keystore, apqns = properties_get(properties, PROP_NAME_APQNS); key_type = properties_get(properties, PROP_NAME_KEY_TYPE); rc = cross_check_apqns(apqns, mkvp, - get_min_card_level_for_keytype(key_type), true, + get_min_card_level_for_keytype(key_type), + get_min_fw_version_for_keytype(key_type), + get_card_type_for_keytype(key_type), true, keystore->verbose); if (rc != 0 && rc != -ENOTSUP) warning = 1; @@ -2515,11 +2537,11 @@ static int _keystore_process_validate(struct keystore *keystore, char **apqn_list = NULL; size_t clear_key_bitsize; size_t secure_key_size; + u8 mkvp[MKVP_LENGTH]; char *apqns = NULL; u8 *secure_key = NULL; int is_old_mk; int rc, valid; - u64 mkvp; rc = _keystore_ensure_keyfiles_exist(file_names, name); if (rc != 0) @@ -2549,7 +2571,7 @@ static int _keystore_process_validate(struct keystore *keystore, } rc = get_master_key_verification_pattern(secure_key, secure_key_size, - &mkvp, keystore->verbose); + mkvp, keystore->verbose); if (rc != 0) goto out; @@ -2562,9 +2584,9 @@ static int _keystore_process_validate(struct keystore *keystore, if (valid && is_old_mk) { util_print_indented("WARNING: The secure key is currently " - "enciphered with the OLD CCA master key. " + "enciphered with the OLD master key. " "To mitigate the danger of data loss " - "re-encipher it with the CURRENT CCA " + "re-encipher it with the CURRENT " "master key\n", 0); info->num_warnings++; } @@ -2647,7 +2669,7 @@ struct reencipher_params { struct reencipher_info { struct reencipher_params params; int pkey_fd; - struct cca_lib *cca; + struct ext_lib *lib; unsigned long num_reenciphered; unsigned long num_failed; unsigned long num_skipped; @@ -2658,7 +2680,7 @@ struct reencipher_info { * * @param[in] keystore the keystore * @param[in] name the name of the key - * @param[in] cca the CCA library struct + * @param[in] lib the external library struct * @param[in] params reenciphering parameters * @param[in] secure_key a buffer containing the secure key * @param[in] secure_key_size the size of the secure key @@ -2670,37 +2692,29 @@ struct reencipher_info { */ static int _keystore_perform_reencipher(struct keystore *keystore, const char *name, - struct cca_lib *cca, + struct ext_lib *lib, struct reencipher_params *params, u8 *secure_key, size_t secure_key_size, bool is_old_mk, const char *apqns) { - int rc, selected = 1; - u64 mkvp; - - rc = get_master_key_verification_pattern(secure_key, secure_key_size, - &mkvp, keystore->verbose); - if (rc != 0) { - warnx("Failed to get the master key verification pattern: %s", - strerror(-rc)); - return rc; - } + bool selected; + int rc; if (!params->from_old && !params->to_new) { /* Autodetect reencipher mode */ if (is_old_mk) { params->from_old = 1; util_print_indented("The secure key is currently " - "enciphered with the OLD CCA " + "enciphered with the OLD " "master key and is being " "re-enciphered with the CURRENT " - "CCA master key\n", 0); + "master key\n", 0); } else { params->to_new = 1; util_print_indented("The secure key is currently " - "enciphered with the CURRENT CCA " + "enciphered with the CURRENT " "master key and is being " - "re-enciphered with the NEW CCA " + "re-enciphered with the NEW " "master key\n", 0); } } @@ -2711,64 +2725,52 @@ static int _keystore_perform_reencipher(struct keystore *keystore, pr_verbose(keystore, "Secure key '%s' will be re-enciphered from OLD " - "to the CURRENT CCA master key", name); - - rc = select_cca_adapter_by_mkvp(cca, mkvp, apqns, - FLAG_SEL_CCA_MATCH_OLD_MKVP, - keystore->verbose); - if (rc == -ENOTSUP) { - rc = 0; - selected = 0; - } - if (rc != 0) { - warnx("No APQN found that is suitable for " - "re-enciphering this secure AES key"); - return rc; - } + "to the CURRENT master key", name); - rc = key_token_change(cca, secure_key, secure_key_size, - METHOD_OLD_TO_CURRENT, - keystore->verbose); + rc = reencipher_secure_key(lib, secure_key, secure_key_size, + apqns, REENCIPHER_OLD_TO_CURRENT, + &selected, keystore->verbose); if (rc != 0) { - warnx("Failed to re-encipher '%s' from OLD to " - "CURRENT CCA master key", name); - if (!selected) - print_msg_for_cca_envvars("secure AES key"); + if (rc == -ENODEV) { + warnx("No APQN found that is suitable for " + "re-enciphering this secure AES key"); + } else { + warnx("Failed to re-encipher '%s' from OLD to " + "CURRENT master key", name); + if (!selected && + !is_ep11_aes_key(secure_key, + secure_key_size)) + print_msg_for_cca_envvars( + "secure AES key"); + } return rc; } } if (params->to_new) { pr_verbose(keystore, "Secure key '%s' will be re-enciphered from " - "CURRENT to the NEW CCA master key", name); + "CURRENT to the NEW master key", name); if (params->inplace == -1) params->inplace = 0; - rc = select_cca_adapter_by_mkvp(cca, mkvp, apqns, - FLAG_SEL_CCA_MATCH_CUR_MKVP | - FLAG_SEL_CCA_NEW_MUST_BE_SET, - keystore->verbose); - if (rc == -ENOTSUP) { - rc = 0; - selected = 0; - } - if (rc != 0) { - util_print_indented("No APQN found that is suitable " - "for re-enciphering this secure " - "AES key and has the NEW master " - "key loaded", 0); - return rc; - } - - rc = key_token_change(cca, secure_key, secure_key_size, - METHOD_CURRENT_TO_NEW, - keystore->verbose); + rc = reencipher_secure_key(lib, secure_key, secure_key_size, + apqns, REENCIPHER_CURRENT_TO_NEW, + &selected, keystore->verbose); if (rc != 0) { - warnx("Failed to re-encipher '%s' from CURRENT to " - "NEW CCA master key", name); - if (!selected) - print_msg_for_cca_envvars("secure AES key"); + if (rc == -ENODEV) { + warnx("No APQN found that is suitable for " + "re-enciphering this secure AES key and " + "has the NEW master key loaded"); + } else { + warnx("Failed to re-encipher '%s' from CURRENT " + "to NEW master key", name); + if (!selected && + !is_ep11_aes_key(secure_key, + secure_key_size)) + print_msg_for_cca_envvars( + "secure AES key"); + } return rc; } } @@ -2848,7 +2850,7 @@ static int _keystore_process_reencipher(struct keystore *keystore, if (params.complete) { warnx("Key '%s' is not valid, re-enciphering is not " "completed", name); - warnx("The new CCA master key might yet have to be set " + warnx("The new master key might yet have to be set " "as the CURRENT master key."); } else { warnx("Key '%s' is not valid, it is not re-enciphered", @@ -2862,7 +2864,7 @@ static int _keystore_process_reencipher(struct keystore *keystore, if (!params.complete) { printf("Re-enciphering key '%s'\n", name); - rc = _keystore_perform_reencipher(keystore, name, info->cca, + rc = _keystore_perform_reencipher(keystore, name, info->lib, ¶ms, secure_key, secure_key_size, is_old_mk, properties_get(properties, @@ -2931,7 +2933,7 @@ static int _keystore_process_reencipher(struct keystore *keystore, if (params.inplace != 1) { util_asprintf(&temp, "Staged re-enciphering is initiated for " - "key '%s'. After the NEW CCA master key has been " + "key '%s'. After the NEW master key has been " "set to become the CURRENT master key run " "'zkey reencipher' with option '--complete' to " "complete the re-enciphering process", name); @@ -2967,17 +2969,17 @@ out: * @param[in] name_filter the name filter to select the key (can be NULL) * @param[in] apqn_filter the APQN filter to seletc the key (can be NULL) * @param[in] from_old If true the key is reenciphered from the OLD to the - * CURRENT CCA master key. + * CURRENT master key. * @param[in] to_new If true the key is reenciphered from the CURRENT to - * the OLD CCA master key. + * the OLD master key. * @param[in] inplace if true, the key will be re-enciphere in-place * @param[in] staged if true, the key will be re-enciphere not in-place * @param[in] complete if true, a pending re-encipherment is completed * @param[in] pkey_fd the file descriptor of /dev/pkey - * @param[in] cca the CCA library struct - * Note: if both from Old and toNew are FALSE, then the reencipherement mode is + * @param[in] lib the external library struct + * Note: if both fromOld and toNew are FALSE, then the reencipherement mode is * detected automatically. If both are TRUE then the key is reenciphered - * from the OLD to the NEW CCA master key. + * from the OLD to the NEW master key. * Note: if both inplace and staged are FLASE, then the key is re-enciphered * inplace when for OLD-to-CURRENT, and is reenciphered staged for * CURRENT-to-NEW. @@ -2987,7 +2989,7 @@ int keystore_reencipher_key(struct keystore *keystore, const char *name_filter, const char *apqn_filter, bool from_old, bool to_new, bool inplace, bool staged, bool complete, int pkey_fd, - struct cca_lib *cca) + struct ext_lib *lib) { struct reencipher_info info; int rc; @@ -3003,7 +3005,7 @@ int keystore_reencipher_key(struct keystore *keystore, const char *name_filter, info.params.inplace = 0; info.params.complete = complete; info.pkey_fd = pkey_fd; - info.cca = cca; + info.lib = lib; info.num_failed = 0; info.num_reenciphered = 0; info.num_skipped = 0; @@ -3956,13 +3958,13 @@ int keystore_crypttab(struct keystore *keystore, const char *volume_filter, * @param[in] noapqncheck if true, the specified APQN(s) are not checked for * existence and type. * @param[in] pkey_fd the file descriptor of /dev/pkey - * @param[in] cca the CCA library struct + * @param[in] lib the external library struct * * @returns 0 for success or a negative errno in case of an error */ int keystore_convert_key(struct keystore *keystore, const char *name, const char *key_type, bool noapqncheck, bool quiet, - int pkey_fd, struct cca_lib *cca) + int pkey_fd, struct ext_lib *lib) { struct key_filenames file_names = { NULL, NULL, NULL }; u8 output_key[2 * MAX_SECURE_KEY_SIZE]; @@ -3973,9 +3975,9 @@ int keystore_convert_key(struct keystore *keystore, const char *name, char **apqn_list = NULL; size_t secure_key_size; u8 *secure_key = NULL; + u8 mkvp[MKVP_LENGTH]; char *apqns = NULL; char *temp; - u64 mkvp; util_assert(keystore != NULL, "Internal error: keystore is NULL"); util_assert(name != NULL, "Internal error: name is NULL"); @@ -4028,7 +4030,10 @@ int keystore_convert_key(struct keystore *keystore, const char *name, if (apqns != NULL) apqn_list = str_list_split(apqns); - rc = cross_check_apqns(apqns, 0, min_level, true, keystore->verbose); + rc = cross_check_apqns(apqns, NULL, min_level, + get_min_fw_version_for_keytype(key_type), + get_card_type_for_keytype(key_type), + true, keystore->verbose); if (rc == -EINVAL) goto out; if (rc != 0 && rc != -ENOTSUP && !noapqncheck) { @@ -4044,11 +4049,11 @@ int keystore_convert_key(struct keystore *keystore, const char *name, goto out; rc = get_master_key_verification_pattern(secure_key, secure_key_size, - &mkvp, keystore->verbose); + mkvp, keystore->verbose); if (rc) goto out; - rc = select_cca_adapter_by_mkvp(cca, mkvp, NULL, + rc = select_cca_adapter_by_mkvp(lib->cca, mkvp, apqns, FLAG_SEL_CCA_MATCH_CUR_MKVP, keystore->verbose); if (rc == -ENOTSUP) { @@ -4078,7 +4083,7 @@ int keystore_convert_key(struct keystore *keystore, const char *name, memset(output_key, 0, sizeof(output_key)); output_key_size = sizeof(output_key); - rc = convert_aes_data_to_cipher_key(cca, secure_key, + rc = convert_aes_data_to_cipher_key(lib->cca, secure_key, secure_key_size, output_key, &output_key_size, keystore->verbose); @@ -4090,7 +4095,7 @@ int keystore_convert_key(struct keystore *keystore, const char *name, goto out; } - rc = restrict_key_export(cca, output_key, output_key_size, + rc = restrict_key_export(lib->cca, output_key, output_key_size, keystore->verbose); if (rc != 0) { warnx("Export restricting the converted secure key '%s' has " diff --git a/zkey/keystore.h b/zkey/keystore.h index b17a575..d952814 100644 --- a/zkey/keystore.h +++ b/zkey/keystore.h @@ -14,7 +14,6 @@ #include -#include "cca.h" #include "pkey.h" struct keystore { @@ -38,7 +37,7 @@ int keystore_import_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, const char *apqns, bool noapqncheck, size_t sector_size, const char *import_file, const char *volume_type, - struct cca_lib *cca); + struct ext_lib *lib); int keystore_change_key(struct keystore *keystore, const char *name, const char *description, const char *volumes, @@ -56,7 +55,7 @@ int keystore_reencipher_key(struct keystore *keystore, const char *name_filter, const char *apqn_filter, bool from_old, bool to_new, bool inplace, bool staged, bool complete, int pkey_fd, - struct cca_lib *cca); + struct ext_lib *lib); int keystore_copy_key(struct keystore *keystore, const char *name, const char *newname, const char *volumes); @@ -83,7 +82,7 @@ int keystore_crypttab(struct keystore *keystore, const char *volume_filter, int keystore_convert_key(struct keystore *keystore, const char *name, const char *key_type, bool noapqncheck, bool quiet, - int pkey_fd, struct cca_lib *cca); + int pkey_fd, struct ext_lib *lib); void keystore_free(struct keystore *keystore); diff --git a/zkey/pkey.c b/zkey/pkey.c index 793ed9e..83e3b3f 100644 --- a/zkey/pkey.c +++ b/zkey/pkey.c @@ -26,6 +26,7 @@ #include "lib/util_panic.h" #include "pkey.h" +#include "utils.h" #ifndef AF_ALG #define AF_ALG 38 @@ -691,6 +692,10 @@ static int build_apqn_list_for_key_type(int pkey_fd, enum pkey_key_type type, apqn_entries, verbose); return rc; + case -EINVAL: + /* This is usually due to an unsupported key type */ + rc = -ENOTSUP; + goto out; default: goto out; } @@ -753,7 +758,6 @@ static int build_apqn_list_for_key(int pkey_fd, u8 *key, u32 keylen, u32 flags, u32 *apqn_entries, bool verbose) { struct pkey_apqns4key apqns4key; - u64 mkvp; int rc; util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); @@ -796,16 +800,14 @@ static int build_apqn_list_for_key(int pkey_fd, u8 *key, u32 keylen, u32 flags, if (!is_cca_aes_data_key(key, keylen)) return -ENOTSUP; - rc = get_master_key_verification_pattern(key, keylen, - &mkvp, - verbose); - if (rc != 0) - return rc; - rc = build_apqn_list_for_aes_data(apqn_list, apqns, apqn_entries, verbose); return rc; + case -EINVAL: + /* This is usually due to an unsupported key type */ + rc = -ENOTSUP; + goto out; default: goto out; } @@ -857,6 +859,8 @@ static enum pkey_key_type key_type_to_pkey_type(const char *key_type) return PKEY_TYPE_CCA_DATA; if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) return PKEY_TYPE_CCA_CIPHER; + if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) + return PKEY_TYPE_EP11; return 0; } @@ -875,6 +879,8 @@ static size_t key_size_for_type(enum pkey_key_type type) return AESDATA_KEY_SIZE; case PKEY_TYPE_CCA_CIPHER: return AESCIPHER_KEY_SIZE; + case PKEY_TYPE_EP11: + return EP11_KEY_SIZE; default: return 0; } @@ -1218,7 +1224,7 @@ static int validate_secure_xts_key(int pkey_fd, struct pkey_apqn *apqn, * @param[out] clear_key_bitsize on return , the cryptographic size of the * clear key * @param[out] is_old_mk in return set to 1 to indicate if the secure key - * is currently enciphered by the OLD CCA master key + * is currently enciphered by the OLD master key * @param[in] apqns a zero terminated array of pointers to APQN-strings, * or NULL for AUTOSELECT * @param[in] verbose if true, verbose messages are printed @@ -1234,6 +1240,7 @@ int validate_secure_key(int pkey_fd, struct pkey_apqn *list = NULL; u32 i, list_entries = 0; bool xts, valid; + u32 flags; int rc; util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); @@ -1241,11 +1248,15 @@ int validate_secure_key(int pkey_fd, xts = is_xts_key(secure_key, secure_key_size); + flags = PKEY_FLAGS_MATCH_CUR_MKVP; + if (is_cca_aes_data_key(secure_key, secure_key_size) || + is_cca_aes_cipher_key(secure_key, secure_key_size)) + flags |= PKEY_FLAGS_MATCH_ALT_MKVP; + rc = build_apqn_list_for_key(pkey_fd, secure_key, HALF_KEYSIZE_FOR_XTS(secure_key_size, xts), - PKEY_FLAGS_MATCH_CUR_MKVP | - PKEY_FLAGS_MATCH_ALT_MKVP, - apqns, &list, &list_entries, verbose); + flags, apqns, &list, &list_entries, + verbose); if (rc != 0) { pr_verbose(verbose, "Failed to build a list of APQNs that can " "validate this secure key: %s", strerror(-rc)); @@ -1454,18 +1465,22 @@ out: } int get_master_key_verification_pattern(const u8 *key, size_t key_size, - u64 *mkvp, bool UNUSED(verbose)) + u8 *mkvp, bool UNUSED(verbose)) { struct aesdatakeytoken *datakey = (struct aesdatakeytoken *)key; struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; + struct ep11keytoken *ep11key = (struct ep11keytoken *)key; util_assert(key != NULL, "Internal error: secure_key is NULL"); util_assert(mkvp != NULL, "Internal error: mkvp is NULL"); + memset(mkvp, 0, MKVP_LENGTH); if (is_cca_aes_data_key(key, key_size)) - *mkvp = datakey->mkvp; + memcpy(mkvp, &datakey->mkvp, sizeof(datakey->mkvp)); else if (is_cca_aes_cipher_key(key, key_size)) - memcpy(mkvp, cipherkey->kvp, sizeof(*mkvp)); + memcpy(mkvp, &cipherkey->kvp, sizeof(cipherkey->kvp)); + else if (is_ep11_aes_key(key, key_size)) + memcpy(mkvp, &ep11key->wkvp, sizeof(ep11key->wkvp)); else return -EINVAL; @@ -1545,6 +1560,34 @@ bool is_cca_aes_cipher_key(const u8 *key, size_t key_size) return true; } +/** + * Check if the specified key is a EP11 AES key token. + * + * @param[in] key the secure key token + * @param[in] key_size the size of the secure key + * + * @returns true if the key is an EP11 AES token type + */ +bool is_ep11_aes_key(const u8 *key, size_t key_size) +{ + struct ep11keytoken *ep11key = (struct ep11keytoken *)key; + + if (key == NULL || key_size < EP11_KEY_SIZE) + return false; + + if (ep11key->head.type != TOKEN_TYPE_NON_CCA) + return false; + if (ep11key->head.version != TOKEN_VERSION_EP11_AES) + return false; + if (ep11key->head.length > key_size) + return false; + + if (ep11key->version != 0x1234) + return false; + + return true; +} + /** * Check if the specified key is an XTS type key * @@ -1565,6 +1608,11 @@ bool is_xts_key(const u8 *key, size_t key_size) is_cca_aes_cipher_key(key + AESCIPHER_KEY_SIZE, key_size - AESCIPHER_KEY_SIZE)) return true; + } else if (is_ep11_aes_key(key, key_size)) { + if (key_size == 2 * EP11_KEY_SIZE && + is_ep11_aes_key(key + EP11_KEY_SIZE, + key_size - EP11_KEY_SIZE)) + return true; } return false; @@ -1585,6 +1633,7 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) { struct aesdatakeytoken *datakey = (struct aesdatakeytoken *)key; struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; + struct ep11keytoken *ep11key = (struct ep11keytoken *)key; util_assert(bitsize != NULL, "Internal error: bitsize is NULL"); @@ -1606,6 +1655,12 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) if (cipherkey->pfv == 0x00) /* V0 payload */ *bitsize += cipherkey->pl - 384; } + } else if (is_ep11_aes_key(key, key_size)) { + *bitsize = ep11key->head.keybitlen; + if (key_size == 2 * EP11_KEY_SIZE) { + ep11key = (struct ep11keytoken *)(key + EP11_KEY_SIZE); + *bitsize += ep11key->head.keybitlen; + } } else { return -EINVAL; } @@ -1627,7 +1682,8 @@ const char *get_key_type(const u8 *key, size_t key_size) return KEY_TYPE_CCA_AESDATA; if (is_cca_aes_cipher_key(key, key_size)) return KEY_TYPE_CCA_AESCIPHER; - + if (is_ep11_aes_key(key, key_size)) + return KEY_TYPE_EP11_AES; return NULL; } @@ -1647,10 +1703,48 @@ int get_min_card_level_for_keytype(const char *key_type) return 3; if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) return 6; + if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) + return 7; return -1; } +const struct fw_version *get_min_fw_version_for_keytype(const char *key_type) +{ + static const struct fw_version ep11_fw_version = { + .major = 0, .minor = 0, .api_ordinal = 4, }; + + if (key_type == NULL) + return NULL; + + if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) + return &ep11_fw_version; + + return NULL; +} + +/** + * Returns the card type required for a specific key type + * + * @param[in] key_type the type of the key + * + * @returns the card type, or CARD_TYPE_ANY for unknown key types + */ +enum card_type get_card_type_for_keytype(const char *key_type) +{ + if (key_type == NULL) + return CARD_TYPE_ANY; + + if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0) + return CARD_TYPE_CCA; + if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) + return CARD_TYPE_CCA; + if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) + return CARD_TYPE_EP11; + + return CARD_TYPE_ANY; +} + /** * Performs extended checks on an AES CIPHER key. It checks the key usage * fields (KUFs) and key management fields (KMFs) of the key. The function @@ -1787,3 +1881,169 @@ int check_aes_cipher_key(const u8 *key, size_t key_size) return mismatch ? -EINVAL : 0; } + +static int reencipher_cca_secure_key(struct cca_lib *cca, u8 *secure_key, + size_t secure_key_size, const char *apqns, + u8 *mkvp, enum reencipher_method method, + bool *apqn_selected, bool verbose) +{ + unsigned int flags; + int rc; + + if (method == REENCIPHER_OLD_TO_CURRENT) + flags = FLAG_SEL_CCA_MATCH_OLD_MKVP; + else + flags = FLAG_SEL_CCA_MATCH_CUR_MKVP | + FLAG_SEL_CCA_NEW_MUST_BE_SET; + + *apqn_selected = true; + + rc = select_cca_adapter_by_mkvp(cca, mkvp, apqns, flags, + verbose); + if (rc == -ENOTSUP) { + rc = 0; + *apqn_selected = false; + } + if (rc != 0) { + pr_verbose(verbose, "No APQN found that is suitable " + "for re-enciphering this secure key"); + return rc; + } + + rc = key_token_change(cca, secure_key, secure_key_size, + method == REENCIPHER_OLD_TO_CURRENT ? + METHOD_OLD_TO_CURRENT : + METHOD_CURRENT_TO_NEW, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to re-encipher secure key: " + "%s", strerror(-rc)); + return rc; + } + + return 0; +} + +static int reencipher_ep11_secure_key(struct ep11_lib *ep11, u8 *secure_key, + size_t secure_key_size, const char *apqns, + u8 *mkvp, bool *apqn_selected, + bool verbose) +{ + unsigned int flags; + int card, domain; + target_t target; + int rc; + + flags = FLAG_SEL_EP11_MATCH_CUR_MKVP | + FLAG_SEL_EP11_NEW_MUST_BE_SET; + + *apqn_selected = true; + + rc = select_ep11_apqn_by_mkvp(ep11, mkvp, apqns, flags, + &target, &card, &domain, verbose); + if (rc == -ENOTSUP) { + rc = 0; + *apqn_selected = false; + } + if (rc != 0) { + pr_verbose(verbose, "No APQN found that is suitable " + "for re-enciphering this secure key"); + return rc; + } + + rc = reencipher_ep11_key(ep11, target, card, domain, + secure_key, secure_key_size, verbose); + free_ep11_target_for_apqn(ep11, target); + if (rc != 0) { + pr_verbose(verbose, "Failed to re-encipher secure key: " + "%s", strerror(-rc)); + return rc; + } + + return 0; +} + + +/** + * Re-enciphers a secure key + * + * @param[in] lib the external library struct + * @param[in] secure_key a buffer containing the secure key + * @param[in] secure_key_size the secure key size + * @param[in] apqns a comma separated list of APQNs. If NULL is + * specified, or an empty string, then all online + * APQNs of the matching type are subject to be used. + * @param[in] method the re-encipher method + * @param[out] apqn_selected On return: true if a specific APQN was selected. + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error. + * -ENODEV is returned if no APQN could be found with a matching master key. + * -EIO is returned if the re-enciphering has failed. + */ +int reencipher_secure_key(struct ext_lib *lib, u8 *secure_key, + size_t secure_key_size, const char *apqns, + enum reencipher_method method, bool *apqn_selected, + bool verbose) +{ + u8 mkvp[MKVP_LENGTH]; + int rc; + + util_assert(lib != NULL, "Internal error: lib is NULL"); + util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + util_assert(apqn_selected != NULL, + "Internal error: apqn_selected is NULL"); + + *apqn_selected = true; + + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + mkvp, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to get the master key verification " + "pattern: %s", strerror(-rc)); + return rc; + } + + if (is_ep11_aes_key(secure_key, secure_key_size)) { + /* EP11 secure key: need the EP11 host library */ + if (lib->ep11->lib_ep11 == NULL) { + rc = load_ep11_library(lib->ep11, verbose); + if (rc != 0) + return rc; + } + + if (method == REENCIPHER_OLD_TO_CURRENT) { + util_print_indented("ERROR: An APQN of a IBM " + "cryptographic adapter in EP11 " + "coprocessor mode does not have an " + "OLD master key register. Thus, " + "you can not re-encipher a secure " + "key of type 'EP11-AES' from the " + "OLD to the CURRENT master key " + "register.\n", 0); + return -EINVAL; + } + + rc = reencipher_ep11_secure_key(lib->ep11, secure_key, + secure_key_size, apqns, mkvp, + apqn_selected, verbose); + } else if (is_cca_aes_data_key(secure_key, secure_key_size) || + is_cca_aes_cipher_key(secure_key, secure_key_size)) { + /* CCA secure key: need the CCA host library */ + if (lib->cca->lib_csulcca == NULL) { + rc = load_cca_library(lib->cca, verbose); + if (rc != 0) + return rc; + } + + rc = reencipher_cca_secure_key(lib->cca, secure_key, + secure_key_size, apqns, mkvp, + method, apqn_selected, verbose); + } else { + pr_verbose(verbose, "Invalid key type"); + rc = -EINVAL; + } + + return rc; +} + diff --git a/zkey/pkey.h b/zkey/pkey.h index 3dfd588..93c48af 100644 --- a/zkey/pkey.h +++ b/zkey/pkey.h @@ -15,6 +15,9 @@ #include "lib/zt_common.h" +#include "cca.h" +#include "ep11.h" + /* * Definitions for the /dev/pkey kernel module interface */ @@ -25,11 +28,17 @@ struct tokenheader { u8 res1[3]; } __packed; -#define TOKEN_TYPE_NON_CCA 0x00 -#define TOKEN_TYPE_CCA_INTERNAL 0x01 +#define TOKEN_TYPE_NON_CCA 0x00 +#define TOKEN_TYPE_CCA_INTERNAL 0x01 + +/* CCA-Internal token versions */ +#define TOKEN_VERSION_AESDATA 0x04 +#define TOKEN_VERSION_AESCIPHER 0x05 -#define TOKEN_VERSION_AESDATA 0x04 -#define TOKEN_VERSION_AESCIPHER 0x05 +/* Non-CCA token versions */ +#define TOKEN_VERSION_PROTECTED_KEY 0x01 +#define TOKEN_VERSION_CLEAR_KEY 0x02 +#define TOKEN_VERSION_EP11_AES 0x03 struct aesdatakeytoken { u8 type; /* TOKEN_TYPE_INTERNAL (0x01) for internal key token */ @@ -80,11 +89,36 @@ struct aescipherkeytoken { u8 varpart[80]; /* variable part */ } __packed; +struct ep11keytoken { + union { + u8 session[32]; + struct { + u8 type; /* TOKEN_TYPE_NON_CCA (0x00) */ + u8 res0; /* unused */ + u16 length; /* length of token */ + u8 version; /* TOKEN_VERSION_EP11_AES (0x03) */ + u8 res1; /* unused */ + u16 keybitlen; /* clear key bit len, 0 for unknown */ + } head; + }; + u8 wkvp[16]; /* wrapping key verification pattern */ + u64 attr; /* boolean key attributes */ + u64 mode; /* mode bits */ + u16 version; /* 0x1234, ep11 blob struct version */ + u8 iv[14]; + u8 encrypted_key_data[144]; + u8 mac[32]; + u8 padding[64]; +} __packed; + #define AESDATA_KEY_SIZE sizeof(struct aesdatakeytoken) #define AESCIPHER_KEY_SIZE sizeof(struct aescipherkeytoken) +#define EP11_KEY_SIZE sizeof(struct ep11keytoken) -#define MAX_SECURE_KEY_SIZE MAX(AESDATA_KEY_SIZE, AESCIPHER_KEY_SIZE) -#define MIN_SECURE_KEY_SIZE MIN(AESDATA_KEY_SIZE, AESCIPHER_KEY_SIZE) +#define MAX_SECURE_KEY_SIZE MAX(EP11_KEY_SIZE, \ + MAX(AESDATA_KEY_SIZE, AESCIPHER_KEY_SIZE)) +#define MIN_SECURE_KEY_SIZE MIN(EP11_KEY_SIZE, \ + MIN(AESDATA_KEY_SIZE, AESCIPHER_KEY_SIZE)) struct pkey_seckey { u8 seckey[AESDATA_KEY_SIZE]; /* the secure key blob */ @@ -136,6 +170,7 @@ struct pkey_verifykey { enum pkey_key_type { PKEY_TYPE_CCA_DATA = (u32) 1, PKEY_TYPE_CCA_CIPHER = (u32) 2, + PKEY_TYPE_EP11 = (u32) 3, }; enum pkey_key_size { @@ -226,11 +261,30 @@ struct pkey_apqns4keytype { #define KEY_TYPE_CCA_AESDATA "CCA-AESDATA" #define KEY_TYPE_CCA_AESCIPHER "CCA-AESCIPHER" +#define KEY_TYPE_EP11_AES "EP11-AES" #define PAES_BLOCK_SIZE 16 #define ENC_ZERO_LEN (2 * PAES_BLOCK_SIZE) #define VERIFICATION_PATTERN_LEN (2 * ENC_ZERO_LEN + 1) +#define MKVP_LENGTH 16 + +static const u8 zero_mkvp[MKVP_LENGTH] = { 0x00 }; + +#define MKVP_EQ(mkvp1, mkvp2) (memcmp(mkvp1, mkvp2, MKVP_LENGTH) == 0) +#define MKVP_ZERO(mkvp) (mkvp == NULL || MKVP_EQ(mkvp, zero_mkvp)) + +enum card_type { + CARD_TYPE_ANY = -1, + CARD_TYPE_CCA = 1, + CARD_TYPE_EP11 = 2, +}; + +struct ext_lib { + struct cca_lib *cca; + struct ep11_lib *ep11; +}; + int open_pkey_device(bool verbose); int generate_secure_key_random(int pkey_fd, const char *keyfile, @@ -257,14 +311,27 @@ int generate_key_verification_pattern(const u8 *key, size_t key_size, char *vp, size_t vp_len, bool verbose); int get_master_key_verification_pattern(const u8 *key, size_t key_size, - u64 *mkvp, bool verbose); + u8 *mkvp, bool verbose); bool is_cca_aes_data_key(const u8 *key, size_t key_size); bool is_cca_aes_cipher_key(const u8 *key, size_t key_size); +bool is_ep11_aes_key(const u8 *key, size_t key_size); bool is_xts_key(const u8 *key, size_t key_size); int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize); const char *get_key_type(const u8 *key, size_t key_size); int get_min_card_level_for_keytype(const char *key_type); +const struct fw_version *get_min_fw_version_for_keytype(const char *key_type); +enum card_type get_card_type_for_keytype(const char *key_type); int check_aes_cipher_key(const u8 *key, size_t key_size); +enum reencipher_method { + REENCIPHER_OLD_TO_CURRENT = 1, + REENCIPHER_CURRENT_TO_NEW = 2, +}; + +int reencipher_secure_key(struct ext_lib *lib, u8 *secure_key, + size_t secure_key_size, const char *apqns, + enum reencipher_method method, bool *apqn_selected, + bool verbose); + #endif diff --git a/zkey/utils.c b/zkey/utils.c index e70ebcd..a2d1c37 100644 --- a/zkey/utils.c +++ b/zkey/utils.c @@ -25,6 +25,8 @@ #include "lib/util_rec.h" #include "lib/util_base.h" + #include + #include "utils.h" #include "properties.h" @@ -34,14 +36,16 @@ } while (0) /** - * Checks if the specified card is of type CCA and is online + * Checks if the specified card is of the specified type and is online * * @param[in] card card number + * @param[in] cardtype card type (CCA, EP11 or ANY) * - * @returns 1 if its a CCA card and is online, 0 if offline and -1 if its - * not a CCA card. + * @returns 1 if its card of the specified type and is online, + * 0 if offline, + * -1 if its not the specified type. */ -int sysfs_is_card_online(int card) +int sysfs_is_card_online(int card, enum card_type cardtype) { long int online; char *dev_path; @@ -69,9 +73,21 @@ int sysfs_is_card_online(int card) rc = 0; goto out; } - if (type[4] != 'C') { - rc = -1; - goto out; + switch (cardtype) { + case CARD_TYPE_CCA: + if (type[4] != 'C') { + rc = -1; + goto out; + } + break; + case CARD_TYPE_EP11: + if (type[4] != 'P') { + rc = -1; + goto out; + } + break; + default: + break; } out: @@ -80,21 +96,23 @@ out: } /** - * Checks if the specified APQN is of type CCA and is online + * Checks if the specified APQN is of the specified type and is online * * @param[in] card card number * @param[in] domain the domain + * @param[in] cardtype card type (CCA, EP11 or ANY) * - * @returns 1 if its a CCA card and is online, 0 if offline and -1 if its - * not a CCA card. + * @returns 1 if its card of the specified type and is online, + * 0 if offline, + * -1 if its not the specified type. */ -int sysfs_is_apqn_online(int card, int domain) +int sysfs_is_apqn_online(int card, int domain, enum card_type cardtype) { long int online; char *dev_path; int rc = 1; - rc = sysfs_is_card_online(card); + rc = sysfs_is_card_online(card, cardtype); if (rc != 1) return rc; @@ -145,7 +163,7 @@ int sysfs_get_card_level(int card) rc = -1; goto out; } - if (type[4] != 'C') { + if (type[4] != 'C' && type[4] != 'P') { rc = -1; goto out; } @@ -162,18 +180,62 @@ out: } /** - * Gets the 8 character ASCII serial number string of an card from the sysfs. + * Returns the type of the card. For a CEXnC CARD_TYPE_CCA is returned, + * for a CEXnP CARD_TYPE_EP11. + * + * @param[in] card card number + * + * @returns The card type, or -1 of the type can not be determined. + */ +enum card_type sysfs_get_card_type(int card) +{ + char *dev_path; + char type[20]; + enum card_type cardtype; + + dev_path = util_path_sysfs("bus/ap/devices/card%02x", card); + if (!util_path_is_dir(dev_path)) { + cardtype = -1; + goto out; + } + if (util_file_read_line(type, sizeof(type), "%s/type", dev_path) != 0) { + cardtype = -1; + goto out; + } + if (strncmp(type, "CEX", 3) != 0 || strlen(type) < 5) { + cardtype = -1; + goto out; + } + switch (type[4]) { + case 'C': + cardtype = CARD_TYPE_CCA; + break; + case 'P': + cardtype = CARD_TYPE_EP11; + break; + default: + cardtype = -1; + break; + } + +out: + free(dev_path); + return cardtype; +} + +/** + * Gets the 8-16 character ASCII serial number string of an card from the sysfs. * * @param[in] card card number - * @param[out] serialnr Result buffer + * @param[out] serialnr Result buffer. Must be at least SERIALNR_LENGTH long. * @param[in] verbose if true, verbose messages are printed * * @returns 0 if the serial number was returned. -ENODEV if the APQN is not - * available, or is not a CCA card. -ENOTSUP if the serialnr sysfs - * attribute is not available, because the zcrypt kernel module is - * on an older level. + * available, or is not a CCA or EP11 card. + * -ENOTSUP if the serialnr sysfs attribute is not available, because + * the zcrypt kernel module is on an older level. */ -int sysfs_get_serialnr(int card, char serialnr[9], bool verbose) +int sysfs_get_serialnr(int card, char *serialnr, bool verbose) { char *dev_path; int rc = 0; @@ -181,7 +243,7 @@ int sysfs_get_serialnr(int card, char serialnr[9], bool verbose) if (serialnr == NULL) return -EINVAL; - if (sysfs_is_card_online(card) != 1) + if (sysfs_is_card_online(card, CARD_TYPE_ANY) != 1) return -ENODEV; dev_path = util_path_sysfs("bus/ap/devices/card%02x", card); @@ -189,7 +251,8 @@ int sysfs_get_serialnr(int card, char serialnr[9], bool verbose) rc = -ENODEV; goto out; } - if (util_file_read_line(serialnr, 9, "%s/serialnr", dev_path) != 0) { + if (util_file_read_line(serialnr, SERIALNR_LENGTH, "%s/serialnr", + dev_path) != 0) { rc = -ENOTSUP; goto out; } @@ -209,11 +272,79 @@ out: return rc; } -static int parse_mk_info(char *line, struct mk_info *mk_info) +/** + * Gets the firmware version of an card from the sysfs. + * Currently only EP11 cards provide this information. + * + * @param[in] card card number + * @param[out] fw_version On return: The firmware version numbers + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 if the firmware version was returned. -ENODEV if the APQN is not + * available, or is not a CCA or EP11 card. + * -ENOTSUP if the fw_version sysfs attribute is not available, because + * the zcrypt kernel module is on an older level, or because the card + * type does not provide this information. + */ +int sysfs_get_firmware_version(int card, struct fw_version *fw_version, + bool verbose) +{ + char *dev_path; + char buf[50]; + int rc = 0; + + if (fw_version == NULL) + return -EINVAL; + + if (sysfs_is_card_online(card, CARD_TYPE_ANY) != 1) + return -ENODEV; + + dev_path = util_path_sysfs("bus/ap/devices/card%02x", card); + if (!util_path_is_dir(dev_path)) { + rc = -ENODEV; + goto out; + } + if (util_file_read_line(buf, sizeof(buf), "%s/FW_version", + dev_path) != 0) { + rc = -ENOTSUP; + goto out; + } + + if (sscanf(buf, "%d.%d", &fw_version->major, &fw_version->minor) != 2) { + rc = -ENODEV; + goto out; + } + + if (util_file_read_line(buf, sizeof(buf), "%s/API_ordinalnr", + dev_path) != 0) { + rc = -ENOTSUP; + goto out; + } + + if (sscanf(buf, "%d", &fw_version->api_ordinal) != 1) { + rc = -ENODEV; + goto out; + } + + + pr_verbose(verbose, "Firmware version of %02x: %d.%d (API: %d)", card, + fw_version->major, fw_version->minor, + fw_version->api_ordinal); +out: + if (rc != 0) + pr_verbose(verbose, "Failed to get firmware version for " + "%02x: %s", card, strerror(-rc)); + + free(dev_path); + return rc; +} + +static int parse_cca_mk_info(char *line, struct mk_info *mk_info) { struct mk_info_reg *mk_reg; char *save; char *tok; + u64 mkvp; tok = strtok_r(line, " ", &save); if (tok == NULL) @@ -256,9 +387,79 @@ static int parse_mk_info(char *line, struct mk_info *mk_info) if (tok == NULL) return -EIO; - if (sscanf(tok, "%llx", &mk_reg->mkvp) != 1) + if (sscanf(tok, "%llx", &mkvp) != 1) return -EIO; + memcpy(mk_reg->mkvp, &mkvp, sizeof(mkvp)); + + return 0; +} + +static int parse_ep11_mk_info(char *line, struct mk_info *mk_info) +{ + struct mk_info_reg *mk_reg; + unsigned char *buf; + char *save; + char *tok; + long len; + + tok = strtok_r(line, " ", &save); + if (tok == NULL) + return -EIO; + + if (strcasecmp(tok, "WK") != 0) + return 0; + + tok = strtok_r(NULL, " ", &save); + if (tok == NULL) + return -EIO; + + if (strcasecmp(tok, "NEW:") == 0) + mk_reg = &mk_info->new_mk; + else if (strcasecmp(tok, "CUR:") == 0) + mk_reg = &mk_info->cur_mk; + else + return -EIO; + + tok = strtok_r(NULL, " ", &save); + if (tok == NULL) + return -EIO; + + if (strcasecmp(tok, "valid") == 0) + mk_reg->mk_state = MK_STATE_VALID; + else if (strcasecmp(tok, "invalid") == 0) + mk_reg->mk_state = MK_STATE_INVALID; + else if (strcasecmp(tok, "empty") == 0) + mk_reg->mk_state = MK_STATE_EMPTY; + else if (strcasecmp(tok, "uncommitted") == 0) + mk_reg->mk_state = MK_STATE_UNCOMMITTED; + else if (strcasecmp(tok, "committed") == 0) + mk_reg->mk_state = MK_STATE_COMMITTED; + else + mk_reg->mk_state = MK_STATE_UNKNOWN; + + tok = strtok_r(NULL, " ", &save); + if (tok == NULL) + return -EIO; + + /* + * EP11 uses a 32 byte master key verification pattern. + * Usually only the first 16 bytes are used, so we store only up to + * 16 bytes. + */ + if (strlen(tok) >= MKVP_LENGTH * 2) { + if (strncmp(tok, "0x", 2) == 0) + tok += 2; + + buf = OPENSSL_hexstr2buf(tok, &len); + if (buf == NULL) + return -EIO; + if (len > MKVP_LENGTH) + len = MKVP_LENGTH; + memcpy(mk_reg->mkvp, buf, len); + OPENSSL_free(buf); + } + return 0; } @@ -272,12 +473,13 @@ static int parse_mk_info(char *line, struct mk_info *mk_info) * @param[in] verbose if true, verbose messages are printed * * @returns 0 if the master key info was returned. -ENODEV if the APQN is not - * available, or is not a CCA card. -ENOTSUP if the mkvps sysfs - * attribute is not available, because the zcrypt kernel module is - * on an older level. + * available, or is not a CCA or EP11 card. + * -ENOTSUP if the mkvps sysfs attribute is not available, because the + * zcrypt kernel module is on an older level. */ int sysfs_get_mkvps(int card, int domain, struct mk_info *mk_info, bool verbose) { + enum card_type cardtype; char *dev_path; char *p, *end; char buf[100]; @@ -292,9 +494,11 @@ int sysfs_get_mkvps(int card, int domain, struct mk_info *mk_info, bool verbose) mk_info->cur_mk.mk_state = MK_STATE_UNKNOWN; mk_info->old_mk.mk_state = MK_STATE_UNKNOWN; - if (sysfs_is_apqn_online(card, domain) != 1) + if (sysfs_is_apqn_online(card, domain, CARD_TYPE_ANY) != 1) return -ENODEV; + cardtype = sysfs_get_card_type(card); + dev_path = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x/mkvps", card, card, domain); if (!util_path_is_reg_file(dev_path)) { @@ -310,14 +514,22 @@ int sysfs_get_mkvps(int card, int domain, struct mk_info *mk_info, bool verbose) /* * Expected contents: - * AES NEW: - * AES CUR: - * AES OLD: - * with - * : 'empty' or 'partial' or 'full' - * , : 'valid' or 'invalid' - * , , + * AES CUR: + * AES OLD: + * with + * : 'empty' or 'partial' or 'full' + * , : 'valid' or 'invalid' + * , , : + * 8 byte hex string with leading 0x + * For EP11 cards: + * WK NEW: + * WK CUR: + * with + * : 'invalid' or 'valid' + * : 'empty' or 'uncommitted' or 'committed' + * and : '-' or a 32 byte hash pattern */ while ((p = fgets(buf, sizeof(buf), fp)) != NULL) { end = memchr(buf, '\n', sizeof(buf)); @@ -329,7 +541,17 @@ int sysfs_get_mkvps(int card, int domain, struct mk_info *mk_info, bool verbose) pr_verbose(verbose, "mkvp for %02x.%04x: %s", card, domain, buf); - rc = parse_mk_info(buf, mk_info); + switch (cardtype) { + case CARD_TYPE_CCA: + rc = parse_cca_mk_info(buf, mk_info); + break; + case CARD_TYPE_EP11: + rc = parse_ep11_mk_info(buf, mk_info); + break; + default: + rc = -EINVAL; + break; + } if (rc != 0) break; } @@ -338,7 +560,8 @@ int sysfs_get_mkvps(int card, int domain, struct mk_info *mk_info, bool verbose) if (mk_info->new_mk.mk_state == MK_STATE_UNKNOWN && mk_info->cur_mk.mk_state == MK_STATE_UNKNOWN && - mk_info->old_mk.mk_state == MK_STATE_UNKNOWN) + (cardtype == CARD_TYPE_CCA && + mk_info->old_mk.mk_state == MK_STATE_UNKNOWN)) rc = -EIO; out: if (rc != 0) @@ -349,8 +572,9 @@ out: return rc; } -static int scan_for_domains(int card, apqn_handler_t handler, - void *handler_data, bool verbose) +static int scan_for_domains(int card, enum card_type cardtype, + apqn_handler_t handler, void *handler_data, + bool verbose) { struct dirent **namelist; char fname[290]; @@ -369,9 +593,9 @@ static int scan_for_domains(int card, apqn_handler_t handler, pr_verbose(verbose, "Found %02x.%04x", card, domain); - if (sysfs_is_apqn_online(card, domain) != 1) { + if (sysfs_is_apqn_online(card, domain, cardtype) != 1) { pr_verbose(verbose, "APQN %02x.%04x is offline or not " - "CCA", card, domain); + "the correct type", card, domain); continue; } @@ -385,8 +609,8 @@ static int scan_for_domains(int card, apqn_handler_t handler, } -static int scan_for_apqns(apqn_handler_t handler, void *handler_data, - bool verbose) +static int scan_for_apqns(enum card_type cardtype, apqn_handler_t handler, + void *handler_data, bool verbose) { struct dirent **namelist; int i, n, card, rc = 0; @@ -405,13 +629,14 @@ static int scan_for_apqns(apqn_handler_t handler, void *handler_data, pr_verbose(verbose, "Found card %02x", card); - if (sysfs_is_card_online(card) != 1) { - pr_verbose(verbose, "Card %02x is offline or not CCA", - card); + if (sysfs_is_card_online(card, cardtype) != 1) { + pr_verbose(verbose, "Card %02x is offline or not the " + "correct type", card); continue; } - rc = scan_for_domains(card, handler, handler_data, verbose); + rc = scan_for_domains(card, cardtype, handler, handler_data, + verbose); if (rc != 0) break; } @@ -421,21 +646,22 @@ static int scan_for_apqns(apqn_handler_t handler, void *handler_data, } /** - * Calls the handler for all APQNs specified in the apqns parameter, or of this - * is NULL, for all online CCA APQNs found in sysfs. In case sysfs is inspected, - * the cards and domains are processed in alphabetical order. + * Calls the handler for all APQNs specified in the apqns parameter, or if this + * is NULL, for all online CCA or EP11 APQNs found in sysfs. In case sysfs is + * inspected, the cards and domains are processed in alphabetical order. * * @param[in] apqns a comma separated list of APQNs. If NULL is specified, - * or an empty string, then all online CCA APQNs are - * handled. + * or an empty string, then all online CCA or EP11 APQNs + * are handled. + * @param[in] cardtype card type (CCA, EP11 or ANY) * @param[in] handler a handler function that is called for each APQN * @param[in] handler_data private data that is passed to the handler * @param[in] verbose if true, verbose messages are printed * * @returns 0 for success or a negative errno in case of an error */ -int handle_apqns(const char *apqns, apqn_handler_t handler, void *handler_data, - bool verbose) +int handle_apqns(const char *apqns, enum card_type cardtype, + apqn_handler_t handler, void *handler_data, bool verbose) { int card, domain; char *copy, *tok; @@ -443,7 +669,7 @@ int handle_apqns(const char *apqns, apqn_handler_t handler, void *handler_data, int rc = 0; if (apqns == NULL || (apqns != NULL && strlen(apqns) == 0)) { - rc = scan_for_apqns(handler, handler_data, verbose); + rc = scan_for_apqns(cardtype, handler, handler_data, verbose); } else { copy = util_strdup(apqns); tok = strtok_r(copy, ",", &save); @@ -472,6 +698,7 @@ int handle_apqns(const char *apqns, apqn_handler_t handler, void *handler_data, struct print_apqn_info { struct util_rec *rec; + enum card_type cardtype; bool verbose; }; @@ -480,33 +707,41 @@ static int print_apqn_mk_info(int card, int domain, void *handler_data) struct print_apqn_info *info = (struct print_apqn_info *)handler_data; struct mk_info mk_info; int rc, level; + enum card_type type; rc = sysfs_get_mkvps(card, domain, &mk_info, info->verbose); if (rc == -ENOTSUP) return rc; level = sysfs_get_card_level(card); + type = sysfs_get_card_type(card); util_rec_set(info->rec, "APQN", "%02x.%04x", card, domain); + if (info->cardtype != CARD_TYPE_ANY && type != info->cardtype) + rc = -EINVAL; + if (rc == 0) { - if (mk_info.new_mk.mk_state == MK_STATE_FULL) - util_rec_set(info->rec, "NEW", "%016llx", - mk_info.new_mk.mkvp); + if (mk_info.new_mk.mk_state == MK_STATE_FULL || + mk_info.new_mk.mk_state == MK_STATE_COMMITTED) + util_rec_set(info->rec, "NEW", "%s", + printable_mkvp(type, mk_info.new_mk.mkvp)); else if (mk_info.new_mk.mk_state == MK_STATE_PARTIAL) util_rec_set(info->rec, "NEW", "partially loaded"); + else if (mk_info.new_mk.mk_state == MK_STATE_UNCOMMITTED) + util_rec_set(info->rec, "NEW", "uncommitted"); else util_rec_set(info->rec, "NEW", "-"); if (mk_info.cur_mk.mk_state == MK_STATE_VALID) - util_rec_set(info->rec, "CUR", "%016llx", - mk_info.cur_mk.mkvp); + util_rec_set(info->rec, "CUR", "%s", + printable_mkvp(type, mk_info.cur_mk.mkvp)); else util_rec_set(info->rec, "CUR", "-"); if (mk_info.old_mk.mk_state == MK_STATE_VALID) - util_rec_set(info->rec, "OLD", "%016llx", - mk_info.old_mk.mkvp); + util_rec_set(info->rec, "OLD", "%s", + printable_mkvp(type, mk_info.old_mk.mkvp)); else util_rec_set(info->rec, "OLD", "-"); } else { @@ -515,8 +750,9 @@ static int print_apqn_mk_info(int card, int domain, void *handler_data) util_rec_set(info->rec, "OLD", "?"); } - if (level > 0) - util_rec_set(info->rec, "TYPE", "CEX%dC", level); + if (level > 0 && type != CARD_TYPE_ANY) + util_rec_set(info->rec, "TYPE", "CEX%d%c", level, + type == CARD_TYPE_CCA ? 'C' : 'P'); else util_rec_set(info->rec, "TYPE", "?"); @@ -529,40 +765,51 @@ static int print_apqn_mk_info(int card, int domain, void *handler_data) * Prints master key information for all specified APQNs * * @param[in] apqns a comma separated list of APQNs. If NULL is specified, - * or an empty string, then all online CCA APQNs are - * printed. + * or an empty string, then all online CCA or EP11 APQNs + * are printed. + * @param[in] cardtype card type (CCA, EP11 or ANY) * @param[in] verbose if true, verbose messages are printed * * @returns 0 for success or a negative errno in case of an error. -ENOTSUP is * returned when the mkvps sysfs attribute is not available, because * the zcrypt kernel module is on an older level. */ -int print_mk_info(const char *apqns, bool verbose) +int print_mk_info(const char *apqns, enum card_type cardtype, bool verbose) { struct print_apqn_info info; - int rc; + int rc, mklen; info.verbose = verbose; + info.cardtype = cardtype; info.rec = util_rec_new_wide("-"); + if (cardtype == CARD_TYPE_CCA) + mklen = 16; + else + mklen = 32; + util_rec_def(info.rec, "APQN", UTIL_REC_ALIGN_LEFT, 11, "CARD.DOMAIN"); - util_rec_def(info.rec, "NEW", UTIL_REC_ALIGN_LEFT, 16, "NEW MK"); - util_rec_def(info.rec, "CUR", UTIL_REC_ALIGN_LEFT, 16, "CURRENT MK"); - util_rec_def(info.rec, "OLD", UTIL_REC_ALIGN_LEFT, 16, "OLD MK"); + util_rec_def(info.rec, "NEW", UTIL_REC_ALIGN_LEFT, mklen, "NEW MK"); + util_rec_def(info.rec, "CUR", UTIL_REC_ALIGN_LEFT, mklen, "CURRENT MK"); + if (cardtype != CARD_TYPE_EP11) + util_rec_def(info.rec, "OLD", UTIL_REC_ALIGN_LEFT, mklen, + "OLD MK"); util_rec_def(info.rec, "TYPE", UTIL_REC_ALIGN_LEFT, 6, "TYPE"); util_rec_print_hdr(info.rec); - rc = handle_apqns(apqns, print_apqn_mk_info, &info, verbose); + rc = handle_apqns(apqns, cardtype, print_apqn_mk_info, &info, verbose); util_rec_free(info.rec); return rc; } struct cross_check_info { - u64 mkvp; - u64 new_mkvp; + u8 mkvp[MKVP_LENGTH]; + u8 new_mkvp[MKVP_LENGTH]; bool key_mkvp; + enum card_type cardtype; int min_level; + const struct fw_version *min_fw_version; u32 num_cur_match; u32 num_old_match; u32 num_new_match; @@ -575,15 +822,17 @@ struct cross_check_info { static int cross_check_mk_info(int card, int domain, void *handler_data) { struct cross_check_info *info = (struct cross_check_info *)handler_data; + struct fw_version fw_version; + enum card_type type; struct mk_info mk_info; char temp[200]; - int rc, level; + int rc, level = 0; rc = sysfs_get_mkvps(card, domain, &mk_info, info->verbose); if (rc == -ENODEV) { info->print_mks = 1; printf("WARNING: APQN %02x.%04x: Not available or not of " - "type CCA\n", card, domain); + "the correct type\n", card, domain); return 0; } if (rc != 0) @@ -591,6 +840,19 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) info->num_checked++; + if (info->cardtype != CARD_TYPE_ANY) { + type = sysfs_get_card_type(card); + if (type != info->cardtype) { + info->print_mks = 1; + info->mismatch = 1; + sprintf(temp, "WARNING: APQN %02x.%04x: The card type " + "is not CEXn%c.", card, domain, + info->cardtype == CARD_TYPE_CCA ? 'C' : 'P'); + util_print_indented(temp, 0); + return 0; + } + } + if (info->min_level >= 0) { level = sysfs_get_card_level(card); @@ -598,25 +860,63 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) info->print_mks = 1; info->mismatch = 1; sprintf(temp, "WARNING: APQN %02x.%04x: The card level " - "is less than CEX%dC.", card, domain, + "is less than CEX%dn.", card, domain, info->min_level); util_print_indented(temp, 0); } } + if (info->min_fw_version != NULL) { + rc = sysfs_get_firmware_version(card, &fw_version, + info->verbose); + if (rc == 0) { + if (fw_version.api_ordinal < + info->min_fw_version->api_ordinal) { + info->print_mks = 1; + info->mismatch = 1; + sprintf(temp, "WARNING: APQN %02x.%04x: The " + "firmware version is too less to " + "support secure keys of that type", + card, domain); + util_print_indented(temp, 0); + } + if (info->min_level > 0 && info->min_level == level && + (fw_version.major < info->min_fw_version->major || + (fw_version.major == info->min_fw_version->major && + fw_version.minor < info->min_fw_version->minor))) { + info->print_mks = 1; + info->mismatch = 1; + sprintf(temp, "WARNING: APQN %02x.%04x: The " + "firmware version is too less to " + "support secure keys of that type", + card, domain); + util_print_indented(temp, 0); + } + } + } + if (mk_info.new_mk.mk_state == MK_STATE_PARTIAL) { info->print_mks = 1; sprintf(temp, "INFO: APQN %02x.%04x: The NEW master key " "register is only partially loaded.", card, domain); util_print_indented(temp, 0); } + if (mk_info.new_mk.mk_state == MK_STATE_UNCOMMITTED) { + info->print_mks = 1; + sprintf(temp, "INFO: APQN %02x.%04x: The NEW master key " + "register is loaded but uncommitted.", card, domain); + util_print_indented(temp, 0); + } - if (info->new_mkvp == 0 && - mk_info.new_mk.mk_state == MK_STATE_FULL) - info->new_mkvp = mk_info.new_mk.mkvp; + if (MKVP_ZERO(info->new_mkvp) && + (mk_info.new_mk.mk_state == MK_STATE_FULL || + mk_info.new_mk.mk_state == MK_STATE_COMMITTED)) + memcpy(info->new_mkvp, mk_info.new_mk.mkvp, + sizeof(info->new_mkvp)); - if (mk_info.new_mk.mk_state == MK_STATE_FULL && - mk_info.new_mk.mkvp != info->new_mkvp) { + if ((mk_info.new_mk.mk_state == MK_STATE_FULL || + mk_info.new_mk.mk_state == MK_STATE_COMMITTED) && + !MKVP_EQ(mk_info.new_mk.mkvp, info->new_mkvp)) { info->print_mks = 1; sprintf(temp, "WARNING: APQN %02x.%04x: The NEW master key " "register contains a different master key than " @@ -634,15 +934,16 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) } if (mk_info.old_mk.mk_state == MK_STATE_VALID && - mk_info.old_mk.mkvp == mk_info.cur_mk.mkvp) { + MKVP_EQ(mk_info.old_mk.mkvp, mk_info.cur_mk.mkvp)) { info->print_mks = 1; sprintf(temp, "INFO: APQN %02x.%04x: The OLD master key " "register contains the same master key as the CURRENT " "master key register.", card, domain); util_print_indented(temp, 0); } - if (mk_info.new_mk.mk_state == MK_STATE_FULL && - mk_info.new_mk.mkvp == mk_info.cur_mk.mkvp) { + if ((mk_info.new_mk.mk_state == MK_STATE_FULL || + mk_info.new_mk.mk_state == MK_STATE_COMMITTED) && + MKVP_EQ(mk_info.new_mk.mkvp, mk_info.cur_mk.mkvp)) { info->print_mks = 1; sprintf(temp, "INFO: APQN %02x.%04x: The NEW master key " "register contains the same master key as the CURRENT " @@ -651,7 +952,7 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) } if (mk_info.new_mk.mk_state == MK_STATE_FULL && mk_info.old_mk.mk_state == MK_STATE_VALID && - mk_info.new_mk.mkvp == mk_info.old_mk.mkvp) { + MKVP_EQ(mk_info.new_mk.mkvp, mk_info.old_mk.mkvp)) { info->print_mks = 1; sprintf(temp, "INFO: APQN %02x.%04x: The NEW master key " "register contains the same master key as the OLD " @@ -659,28 +960,29 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) util_print_indented(temp, 0); } - if (info->mkvp == 0) - info->mkvp = mk_info.cur_mk.mkvp; + if (MKVP_ZERO(info->mkvp)) + memcpy(info->mkvp, mk_info.cur_mk.mkvp, sizeof(info->mkvp)); if (info->key_mkvp) { if (mk_info.cur_mk.mk_state == MK_STATE_VALID && - mk_info.cur_mk.mkvp == info->mkvp) + MKVP_EQ(mk_info.cur_mk.mkvp, info->mkvp)) info->num_cur_match++; if (mk_info.old_mk.mk_state == MK_STATE_VALID && - mk_info.old_mk.mkvp == info->mkvp) + MKVP_EQ(mk_info.old_mk.mkvp, info->mkvp)) info->num_old_match++; - if (mk_info.new_mk.mk_state == MK_STATE_FULL && - mk_info.new_mk.mkvp == info->mkvp) + if ((mk_info.new_mk.mk_state == MK_STATE_FULL || + mk_info.new_mk.mk_state == MK_STATE_COMMITTED) && + MKVP_EQ(mk_info.new_mk.mkvp, info->mkvp)) info->num_new_match++; } - if (mk_info.cur_mk.mkvp != info->mkvp) { + if (!MKVP_EQ(mk_info.cur_mk.mkvp, info->mkvp)) { if (info->key_mkvp) { if (mk_info.old_mk.mk_state == MK_STATE_VALID && - mk_info.old_mk.mkvp == info->mkvp) { + MKVP_EQ(mk_info.old_mk.mkvp, info->mkvp)) { info->print_mks = 1; sprintf(temp, "INFO: APQN %02x.%04x: The master" " key has been changed to a new " @@ -688,8 +990,10 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) "not yet been re-enciphered.", card, domain); util_print_indented(temp, 0); - } else if (mk_info.new_mk.mk_state == MK_STATE_FULL && - mk_info.new_mk.mkvp == info->mkvp) { + } else if ((mk_info.new_mk.mk_state == MK_STATE_FULL || + mk_info.new_mk.mk_state == + MK_STATE_COMMITTED) && + MKVP_EQ(mk_info.new_mk.mkvp, info->mkvp)) { info->print_mks = 1; sprintf(temp, "INFO: APQN %02x.%04x: The master" " key has been changed but is not " @@ -722,13 +1026,16 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) * out an information message about the APQNs that have a different master key. * * @param[in] apqns a comma separated list of APQNs. If NULL is specified, - * or an empty string, then all online CCA APQNs are + * or an empty string, then all online APQNs are * checked. * @param[in] mkvp The master key verification pattern of a secure key. - * If this is all zero, then the master keys are not - * matched against it. + * If this is all zero or NULL, then the master keys are + * not matched against it. * @param[in] min_level The minimum card level required. If min_level is -1 then * the card level is not checked. + * @param[in] min_fw_version The minimum firmware version required. If NULL tne + * the firmware version is not checked. + * @param[in] cardtype card type (CCA, EP11 or ANY) * @param[in] print_mks if true, then a the full master key info of all * specified APQns is printed, in case of a mismatch. * @param[in] verbose if true, verbose messages are printed @@ -738,40 +1045,49 @@ static int cross_check_mk_info(int card, int domain, void *handler_data) * -ENOTSUP is returned when the mkvps sysfs attribute is not * available, because the zcrypt kernel module is on an older level. */ -int cross_check_apqns(const char *apqns, u64 mkvp, int min_level, - bool print_mks, bool verbose) +int cross_check_apqns(const char *apqns, u8 *mkvp, int min_level, + const struct fw_version *min_fw_version, + enum card_type cardtype, bool print_mks, bool verbose) { struct cross_check_info info; char temp[200]; int rc; memset(&info, 0, sizeof(info)); - info.key_mkvp = mkvp != 0; - info.mkvp = mkvp; + info.key_mkvp = !MKVP_ZERO(mkvp); + if (mkvp != NULL) + memcpy(info.mkvp, mkvp, sizeof(info.mkvp)); + info.cardtype = cardtype; info.min_level = min_level; + info.min_fw_version = min_fw_version; info.verbose = verbose; - pr_verbose(verbose, "Cross checking APQNs with mkvp 0x%016llx and " - "min-level %d: %s", mkvp, min_level, + pr_verbose(verbose, "Cross checking APQNs with mkvp %s, " + "min-level %d, and min-fw-version %u.%u (api: %u): %s", + printable_mkvp(cardtype, info.mkvp), + min_level, + min_fw_version != NULL ? min_fw_version->major : 0, + min_fw_version != NULL ? min_fw_version->minor : 0, + min_fw_version != NULL ? min_fw_version->api_ordinal : 0, apqns != NULL ? apqns : "ANY"); - rc = handle_apqns(apqns, cross_check_mk_info, &info, verbose); + rc = handle_apqns(apqns, cardtype, cross_check_mk_info, &info, verbose); if (rc != 0) return rc; if (info.mismatch) { if (info.key_mkvp) printf("WARNING: Not all APQNs have the correct master " - "key (%016llx).\n", mkvp); + "key (%s) or fulfill the requirements.\n", + printable_mkvp(cardtype, info.mkvp)); else printf("WARNING: Not all APQNs have the same master " - "key.\n"); - + "key or fulfill the requirements.\n"); rc = -ENODEV; } if (info.num_checked == 0) { printf("WARNING: None of the APQNs is available or of " - "type CCA\n"); + "the correct type\n"); rc = -ENODEV; } if (info.num_old_match > 0 && info.num_new_match > 0) { @@ -787,7 +1103,7 @@ int cross_check_apqns(const char *apqns, u64 mkvp, int min_level, if (print_mks && info.print_mks) { printf("\n"); - print_mk_info(apqns, verbose); + print_mk_info(apqns, cardtype, verbose); printf("\n"); } @@ -817,3 +1133,38 @@ bool prompt_for_yes(bool verbose) return false; } + +/* + * Returns a printable version of the specified master key verification pattern + * (MKVP) for the specified card type. Different card types use different + * number of bytes for MKVP. + * + * @param[in] cardtype card type (CCA, EP11 or ANY) + * @param[in] mkvp the master key verification pattern to print + * + * @returns address of a static char array containing the printed MKVP, or NULL + * in case of an error. + */ +char *printable_mkvp(enum card_type cardtype, u8 *mkvp) +{ + static char mkvp_print_buf[MKVP_LENGTH * 2 + 1]; + + if (mkvp == NULL) + return NULL; + + switch (cardtype) { + case CARD_TYPE_CCA: + /* CCA uses an 8 byte MKVP */ + sprintf(mkvp_print_buf, "%016llx", *((u64 *)mkvp)); + break; + case CARD_TYPE_EP11: + /* EP11 uses an 32 byte MKVP, but truncated to 16 bytes*/ + sprintf(mkvp_print_buf, "%016llx%016llx", *((u64 *)&mkvp[0]), + *((u64 *)&mkvp[8])); + break; + default: + return NULL; + } + + return mkvp_print_buf; +} diff --git a/zkey/utils.h b/zkey/utils.h index 2a915eb..dd743f6 100644 --- a/zkey/utils.h +++ b/zkey/utils.h @@ -14,30 +14,47 @@ #include "lib/zt_common.h" -int sysfs_is_card_online(int card); +#include "pkey.h" -int sysfs_is_apqn_online(int card, int domain); +int sysfs_is_card_online(int card, enum card_type cardtype); + +int sysfs_is_apqn_online(int card, int domain, enum card_type cardtype); int sysfs_get_card_level(int card); -int sysfs_get_serialnr(int card, char serialnr[9], bool verbose); +enum card_type sysfs_get_card_type(int card); + +#define SERIALNR_LENGTH 17 + +int sysfs_get_serialnr(int card, char *serialnr, bool verbose); + +struct fw_version { + unsigned int major; + unsigned int minor; + unsigned int api_ordinal; +}; + +int sysfs_get_firmware_version(int card, struct fw_version *fw_version, + bool verbose); #define MK_STATE_EMPTY 0 -#define MK_STATE_PARTIAL 1 -#define MK_STATE_FULL 2 +#define MK_STATE_PARTIAL 1 /* For CCA only */ +#define MK_STATE_FULL 2 /* For CCA only */ #define MK_STATE_VALID 3 #define MK_STATE_INVALID 4 +#define MK_STATE_UNCOMMITTED 5 /* For EP11 only */ +#define MK_STATE_COMMITTED 6 /* For EP11 only */ #define MK_STATE_UNKNOWN -1 struct mk_info_reg { int mk_state; - u64 mkvp; + u8 mkvp[MKVP_LENGTH]; }; struct mk_info { struct mk_info_reg new_mk; struct mk_info_reg cur_mk; - struct mk_info_reg old_mk; + struct mk_info_reg old_mk; /* only available on CCA cards */ }; int sysfs_get_mkvps(int card, int domain, struct mk_info *mk_info, @@ -45,14 +62,17 @@ int sysfs_get_mkvps(int card, int domain, struct mk_info *mk_info, typedef int(*apqn_handler_t) (int card, int domain, void *handler_data); -int handle_apqns(const char *apqns, apqn_handler_t handler, void *handler_data, - bool verbose); +int handle_apqns(const char *apqns, enum card_type cardtype, + apqn_handler_t handler, void *handler_data, bool verbose); -int print_mk_info(const char *apqns, bool verbose); +int print_mk_info(const char *apqns, enum card_type cardtype, bool verbose); -int cross_check_apqns(const char *apqns, u64 mkvp, int min_level, - bool print_mks, bool verbose); +int cross_check_apqns(const char *apqns, u8 *mkvp, int min_level, + const struct fw_version *min_fw_version, + enum card_type cardtype, bool print_mks, bool verbose); bool prompt_for_yes(bool verbose); +char *printable_mkvp(enum card_type cardtype, u8 *mkvp); + #endif diff --git a/zkey/zkey-cryptsetup.1 b/zkey/zkey-cryptsetup.1 index 35e3591..69ec794 100644 --- a/zkey/zkey-cryptsetup.1 +++ b/zkey/zkey-cryptsetup.1 @@ -28,7 +28,7 @@ zkey\-cryptsetup \- Manage secure AES volume keys of volumes encrypted with Use \fBzkey\-cryptsetup\fP to validate and re-encipher secure AES volume keys of volumes encrypted with \fBLUKS2\fP and the \fBpaes\fP cipher. These secure AES volume keys are enciphered with a master key of an IBM -cryptographic adapter in CCA coprocessor mode. +cryptographic adapter in CCA or EP11 coprocessor mode. .PP To encrypt a volume using \fBLUKS2\fP and the \fBpaes\fP cipher, generate a secure AES key using \fBzkey\fP: \fB'zkey generate luks.key --xts'\fP. @@ -112,7 +112,7 @@ Use the .B reencipher command to re-encipher a secure AES volume key of a volume encrypted with \fBLUKS2\fP and the \fBpaes\fP cipher. A secure AES volume key must be -re-enciphered when the master key of the cryptographic adapter in CCA +re-enciphered when the master key of the cryptographic adapter in CCA or EP11 coprocessor mode changes. .PP The cryptographic adapter in CCA coprocessor mode has three different registers @@ -135,11 +135,15 @@ the current master key. You can pro-actively re-encipher a secure key with the option to do this. .RE .PP +\fBNote:\fP An EP11 cryptographic adapter has only two registers to store master +keys, \fBCURRENT\fP and \fBNEW\fP. +.PP Use the .B \-\-from\-old option to re-encipher a secure volume key that is currently enciphered with the master key in the \fBOLD\fP register with the master key in the -\fBCURRENT\fP register. +\fBCURRENT\fP register. This option is only available for secure keys of type +\fBCCA-AESDATA\fP or \fBCCA-AESCIPHER\fP. .PP .PP If both the @@ -171,14 +175,14 @@ Re-enciphering from \fBOLD\fP to \fBCURRENT\fP is performed in-place per default. You can use option \fB--in-place\fP to force an in-place re-enciphering for the \fBCURRENT\fP to \fBNEW\fP case. Be aware that an encrypted volume with a secure volume key that was re-enciphered in-place -from \fBCURRENT\fP to \fBNEW\fP is no longer usable, until the new CCA master -key has been made the current one. +from \fBCURRENT\fP to \fBNEW\fP is no longer usable, until the new CCA or EP11 +master key has been made the current one. .PP \fBStaged\fP mode means that the re-enciphered secure volume key is stored in a separate (unbound) key slot in the LUKS2 header of the encrypted volume. Thus all key slots containing the current secure volume key are still valid at this -point. Once the new CCA master key has been set (made active), you must rerun -the reencipher command with option \fB--complete\fP to complete the staged +point. Once the new CCA or EP11 master key has been set (made active), you must +rerun the reencipher command with option \fB--complete\fP to complete the staged re-enciphering. When completing the staged re-enciphering, the (unbound) key slot containing the re-enciphered secure volume key becomes the active key slot and, optionally, all key slots containing the old secure volume key @@ -217,9 +221,11 @@ function used to encrypt the volume key in the LUKS key slots is of less relevance. .PP .B Note: -The \fBreencipher\fP command requires the CCA host library (libcsulcca.so) -to be installed. For the supported environments and downloads, see: -\fIhttp://www.ibm.com/security/cryptocards\fP +The \fBreencipher\fP command requires the CCA host library (libcsulcca.so, for) +for secure volume keys of type CCA-AESDATA or CCA-AESCIPHER, or the IBM Z +Enterprise PKCS #11 (EP11) Support Program (EP11 host library) for secure volume +keys of type EP11-AES to be installed. For the supported environments and +downloads, see: \fIhttp://www.ibm.com/security/cryptocards\fP . . . @@ -293,12 +299,13 @@ command to set a new secure AES volume key for a volume encrypted with \fBLUKS2\fP and the \fBpaes\fP cipher. Use this command to recover from an invalid secure AES volume key contained in the LUKS2 header. A secure AES volume key contained in the LUKS2 header can become invalid when -the CCA master key is changed without re-enciphering the secure volume key. +the CCA or EP11 master key is changed without re-enciphering the secure volume +key. .PP You can recover the secure volume key only if you have a copy of the secure key -in a file, and this copy was re-enciphered when the CCA master key has been -changed. Thus, the copy of the secure key must be currently enciphered with the -CCA master key in the CURRENT or OLD master key register. +in a file, and this copy was re-enciphered when the CCA or EP11 master key has +been changed. Thus, the copy of the secure key must be currently enciphered with +the CCA or EP11 master key in the CURRENT or OLD master key register. Specify the secure key file with option .B \-\-master\-key\-file to set this secure key as the new volume key. @@ -369,17 +376,17 @@ Forces that the re-enciphering of a secure volume key in the LUKS2 header is performed in staged mode. Staged mode means that the re-enciphered secure volume key is stored in a separate (unbound) key slot in the LUKS2 header of the encrypted volume. Thus all key slots containing the current -secure volume key are still valid at this point. Once the new CCA master key -has been set (made active), you must rerun the reencipher command with option -\fB--complete\fP to complete the staged re-enciphering. Re-enciphering from -\fBCURRENT\fP to \fBNEW\fP is performed in staged mode per default. +secure volume key are still valid at this point. Once the new CCA or EP11 master +key has been set (made active), you must rerun the reencipher command with +option \fB--complete\fP to complete the staged re-enciphering. Re-enciphering +from \fBCURRENT\fP to \fBNEW\fP is performed in staged mode per default. .TP .BR \-p ", " \-\-complete -Completes a staged re-enciphering. Use this option after the new CCA master key -has been set (made active). When completing the staged re-enciphering, the -(unbound) key slot containing the re-enciphered secure volume key becomes -the active key slot and, optionally, all key slots containing the old secure -volume key are removed. +Completes a staged re-enciphering. Use this option after the new CCA or EP11 +master key has been set (made active). When completing the staged +re-enciphering, the (unbound) key slot containing the re-enciphered secure +volume key becomes the active key slot and, optionally, all key slots containing +the old secure volume key are removed. .TP .BR \-q ", " \-\-batch\-mode Suppresses all confirmation questions. Use with care! diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c index 7ba2842..24c0126 100644 --- a/zkey/zkey-cryptsetup.c +++ b/zkey/zkey-cryptsetup.c @@ -35,6 +35,8 @@ #include "misc.h" #include "pkey.h" #include "cca.h" +#include "ep11.h" +#include "utils.h" /* Detect if cryptsetup 2.1 or later is available */ #ifdef CRYPT_LOG_DEBUG_JSON @@ -104,12 +106,16 @@ static struct zkey_cryptsetup_globals { bool batch_mode; bool debug; bool verbose; + struct ext_lib lib; struct cca_lib cca; + struct ep11_lib ep11; int pkey_fd; struct crypt_device *cd; } g = { .tries = 3, .pkey_fd = -1, + .lib.cca = &g.cca, + .lib.ep11 = &g.ep11, }; /* @@ -195,7 +201,7 @@ static struct util_opt opt_vec[] = { { .option = {"complete", 0, NULL, 'c'}, .desc = "Completes a staged re-enciphering. Use this option " - "after the new CCA master key has been set (made " + "after the new master key has been set (made " "active)", .command = COMMAND_REENCIPHER, }, @@ -268,6 +274,7 @@ struct zkey_cryptsetup_command { unsigned int abbrev_len; int (*function)(void); int need_cca_library; + int need_ep11_library; int need_pkey_device; char *short_desc; char *long_desc; @@ -286,7 +293,7 @@ static struct zkey_cryptsetup_command zkey_cryptsetup_commands[] = { .command = COMMAND_REENCIPHER, .abbrev_len = 2, .function = command_reencipher, - .need_cca_library = 1, + /* Will load the CCA or EP11 library on demand */ .need_pkey_device = 1, .short_desc = "Re-encipher a secure volume key", .long_desc = "Re-encipher a secure volume key of a volume " @@ -624,7 +631,7 @@ out_err: if (rc != 0) secure_free(pass, MAX_PASSWORD_SIZE + 1); else - write(outfd, "\n", 1); + rc = write(outfd, "\n", 1) == 1 ? 0 : -EIO; if (infd != STDIN_FILENO) close(infd); @@ -1198,7 +1205,7 @@ out: /* * Prompts for yes or no. Returns true if 'y' or 'yes' was entered. */ -static bool prompt_for_yes(void) +static bool _prompt_for_yes(void) { char str[20]; @@ -1295,7 +1302,7 @@ static int activate_unbound_keyslot(int token, int keyslot, const char *key, "now in unbound state. Do you want to remove " "these key slots [y/N]?", 0); - if (!prompt_for_yes()) + if (!_prompt_for_yes()) return 0; for (i = 0, n = 0; ; i++) { @@ -1535,12 +1542,11 @@ static int reencipher_prepare(int token) char *password = NULL; size_t password_len; char *key = NULL; - int selected = 1; size_t keysize; int is_old_mk; + bool selected; char *prompt; char *msg; - u64 mkvp; int rc; if (token >= 0) { @@ -1551,7 +1557,7 @@ static int reencipher_prepare(int token) util_print_indented(msg, 0); free(msg); - if (!prompt_for_yes()) { + if (!_prompt_for_yes()) { warnx("Device '%s' is left unchanged", g.pos_arg); return -ECANCELED; } @@ -1598,88 +1604,66 @@ static int reencipher_prepare(int token) if (is_old_mk) { g.fromold = 1; util_asprintf(&msg, "The secure volume key of device " - "'%s' is enciphered with the OLD CCA " + "'%s' is enciphered with the OLD " "master key and is being re-enciphered " - "with the CURRENT CCA master key.", + "with the CURRENT master key.", g.pos_arg); util_print_indented(msg, 0); free(msg); } else { g.tonew = 1; util_asprintf(&msg, "The secure volume key of device " - "'%s' is enciphered with the CURRENT CCA " + "'%s' is enciphered with the CURRENT " "master key and is being re-enciphered " - "with the NEW CCA master key.", + "with the NEW master key.", g.pos_arg); util_print_indented(msg, 0); free(msg); } } - rc = get_master_key_verification_pattern((u8 *)key, keysize, &mkvp, - g.verbose); - if (rc != 0) { - warnx("Failed to get the master key verification pattern: %s", - strerror(-rc)); - goto out; - } - if (g.fromold) { - rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, - FLAG_SEL_CCA_MATCH_OLD_MKVP, - g.verbose); - if (rc == -ENOTSUP) { - rc = 0; - selected = 0; - } + rc = reencipher_secure_key(&g.lib, (u8 *)key, keysize, + NULL, REENCIPHER_OLD_TO_CURRENT, + &selected, g.verbose); if (rc != 0) { - util_print_indented("No APQN found that is suitable " - "for re-enciphering the secure AES " - "volume key from the OLD to the " - "CURRENT CCA master key.", 0); - goto out; - } - - rc = key_token_change(&g.cca, (u8 *)key, keysize, - METHOD_OLD_TO_CURRENT, g.verbose); - if (rc != 0) { - warnx("Failed to re-encipher the secure volume key of " - "device '%s'\n", g.pos_arg); - if (!selected) - print_msg_for_cca_envvars( + if (rc == -ENODEV) { + warnx("No APQN found that is suitable for " + "re-enciphering the secure AES volume " + "key from the OLD to the CURRENT master " + "key."); + } else { + warnx("Failed to re-encipher the secure volume " + "key for device '%s'\n", g.pos_arg); + if (!selected && + !is_ep11_aes_key((u8 *)key, keysize)) + print_msg_for_cca_envvars( "secure AES volume key"); - rc = -EINVAL; + rc = -EINVAL; + } goto out; } } if (g.tonew) { - rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, - FLAG_SEL_CCA_MATCH_CUR_MKVP | - FLAG_SEL_CCA_NEW_MUST_BE_SET, - g.verbose); - if (rc == -ENOTSUP) { - rc = 0; - selected = 0; - } + rc = reencipher_secure_key(&g.lib, (u8 *)key, keysize, + NULL, REENCIPHER_CURRENT_TO_NEW, + &selected, g.verbose); if (rc != 0) { - util_print_indented("No APQN found that is suitable " - "for re-enciphering the secure AES " - "volume key from the CURRENT to " - "the NEW CCA master key.", 0); - goto out; - } - - rc = key_token_change(&g.cca, (u8 *)key, keysize, - METHOD_CURRENT_TO_NEW, - g.verbose); - if (rc != 0) { - warnx("Failed to re-encipher the secure volume key of " - "device '%s'\n", g.pos_arg); - if (!selected) - print_msg_for_cca_envvars( + if (rc == -ENODEV) { + warnx("No APQN found that is suitable for " + "re-enciphering the secure AES volume " + "key from the CURRENT to the NEW master " + "key."); + } else { + warnx("Failed to re-encipher the secure volume " + "key for device '%s'\n", g.pos_arg); + if (!selected && + !is_ep11_aes_key((u8 *)key, keysize)) + print_msg_for_cca_envvars( "secure AES volume key"); - rc = -EINVAL; + rc = -EINVAL; + } goto out; } } @@ -1721,7 +1705,7 @@ static int reencipher_prepare(int token) rc = 0; util_asprintf(&msg, "Staged re-enciphering is initiated for " - "device '%s'. After the NEW CCA master key has been set " + "device '%s'. After the NEW master key has been set " "to become the CURRENT master key, run 'zkey-cryptsetup " "reencipher' with option '--complete' to complete the " "re-enciphering process.", g.pos_arg, @@ -1746,12 +1730,11 @@ static int reencipher_complete(int token) char *password = NULL; size_t password_len; char *key = NULL; - int selected = 1; size_t keysize; int is_old_mk; + bool selected; char *prompt; char *msg; - u64 mkvp; int rc; rc = get_reencipher_token(g.cd, token, &tok, true); @@ -1762,7 +1745,7 @@ static int reencipher_complete(int token) } util_asprintf(&msg, "The re-enciphered secure volume key for " - "device '%s' is not valid.\nThe new CCA master key might " + "device '%s' is not valid.\nThe new master key might " "yet have to be set as the CURRENT master key.", g.pos_arg); util_asprintf(&prompt, "Enter passphrase for key slot %d of '%s': ", @@ -1780,56 +1763,41 @@ static int reencipher_complete(int token) if (is_old_mk) { util_asprintf(&msg, "The re-enciphered secure volume key " - "of device '%s' is enciphered with the CCA " + "of device '%s' is enciphered with the " "master key from the OLD master key register. " - "The CCA master key might have changed again, " + "The master key might have changed again, " "before the previous volume key re-enciphering " "was completed.\n" "Do you want to re-encipher the secure key with " - "the CCA master key in the CURRENT master key " + "the master key in the CURRENT master key " "register [y/N]?", g.pos_arg); util_print_indented(msg, 0); free(msg); - if (!prompt_for_yes()) { + if (!_prompt_for_yes()) { warnx("Re-enciphering was aborted"); rc = -ECANCELED; goto out; } - rc = get_master_key_verification_pattern((u8 *)key, keysize, - &mkvp, g.verbose); - if (rc != 0) { - warnx("Failed to get the master key verification " - "pattern: %s", - strerror(-rc)); - goto out; - } - - rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, - FLAG_SEL_CCA_MATCH_OLD_MKVP, - g.verbose); - if (rc == -ENOTSUP) { - rc = 0; - selected = 0; - } + rc = reencipher_secure_key(&g.lib, (u8 *)key, keysize, + NULL, REENCIPHER_OLD_TO_CURRENT, + &selected, g.verbose); if (rc != 0) { - util_print_indented("No APQN found that is suitable " - "for re-enciphering the secure AES " - "volume key from the OLD to the " - "CURRENT CCA master key.", 0); - goto out; - } - - rc = key_token_change(&g.cca, (u8 *)key, keysize, - METHOD_OLD_TO_CURRENT, g.verbose); - if (rc != 0) { - warnx("Failed to re-encipher the secure volume key for " - "device '%s'\n", g.pos_arg); - if (!selected) - print_msg_for_cca_envvars( + if (rc == -ENODEV) { + warnx("No APQN found that is suitable for " + "re-enciphering the secure AES volume " + "key from the OLD to the CURRENT master " + "key."); + } else { + warnx("Failed to re-encipher the secure volume " + "key for device '%s'\n", g.pos_arg); + if (!selected && + !is_ep11_aes_key((u8 *)key, keysize)) + print_msg_for_cca_envvars( "secure AES volume key"); - rc = -EINVAL; + rc = -EINVAL; + } goto out; } @@ -1952,13 +1920,14 @@ static int command_validate(void) int reenc_pending = 0, vp_tok_avail = 0, is_valid = 0, is_old_mk = 0; struct reencipher_token reenc_tok; struct vp_token vp_tok; + const char *key_type; + u8 mkvp[MKVP_LENGTH]; size_t clear_keysize; size_t keysize = 0; char *key = NULL; char *prompt; char *msg; int token; - u64 mkvp; int rc; util_asprintf(&prompt, "Enter passphrase for '%s': ", g.pos_arg); @@ -1990,28 +1959,32 @@ static int command_validate(void) } rc = get_master_key_verification_pattern((u8 *)key, keysize, - &mkvp, g.verbose); + mkvp, g.verbose); if (rc != 0) { warnx("Failed to get the master key verification pattern: %s", strerror(-rc)); goto out; } + key_type = get_key_type((u8 *)key, keysize); + printf("Validation of secure volume key of device '%s':\n", g.pos_arg); printf(" Status: %s\n", is_valid ? "Valid" : "Invalid"); printf(" Secure key size: %lu bytes\n", keysize); printf(" XTS type key: %s\n", is_xts_key((u8 *)key, keysize) ? "Yes" : "No"); - printf(" Key type: %s\n", - get_key_type((u8 *)key, keysize)); + printf(" Key type: %s\n", key_type); if (is_valid) { printf(" Clear key size: %lu bits\n", clear_keysize); - printf(" Enciphered with: %s CCA master key (MKVP: " - "%016llx)\n", is_old_mk ? "OLD" : "CURRENT", mkvp); + printf(" Enciphered with: %s master key (MKVP: " + "%s)\n", is_old_mk ? "OLD" : "CURRENT", + printable_mkvp(get_card_type_for_keytype(key_type), + mkvp)); } else { printf(" Clear key size: (unknown)\n"); - printf(" Enciphered with: (unknown, MKVP: %016llx)\n", - mkvp); + printf(" Enciphered with: (unknown, MKVP: %s)\n", + printable_mkvp(get_card_type_for_keytype(key_type), + mkvp)); } if (vp_tok_avail) print_verification_pattern(vp_tok.verification_pattern); @@ -2029,10 +2002,10 @@ static int command_validate(void) if (is_old_mk) util_print_indented("\nWARNING: The secure volume key is " - "currently enciphered with the OLD CCA " + "currently enciphered with the OLD " "master key. To mitigate the danger of " "data loss re-encipher the volume key with " - "the CURRENT CCA master key.", 0); + "the CURRENT master key.", 0); if (is_valid && !vp_tok_avail) { util_asprintf(&msg, "\nWARNING: The volume key cannot be " @@ -2148,14 +2121,14 @@ static int command_setkey(void) if (is_old_mk) { util_asprintf(&msg, "The secure key in file '%s' is " - "enciphered with the CCA master key in the OLD " + "enciphered with the master key in the OLD " "master key register. Do you want to set this " "key as the new volume key anyway [y/N]?", g.master_key_file); util_print_indented(msg, 0); free(msg); - if (!prompt_for_yes()) { + if (!_prompt_for_yes()) { warnx("Device '%s' is left unchanged", g.pos_arg); rc = -EINVAL; goto out; @@ -2213,7 +2186,7 @@ static int command_setkey(void) util_print_indented(msg, 0); free(msg); - if (!prompt_for_yes()) { + if (!_prompt_for_yes()) { warnx("Device '%s' is left unchanged", g.pos_arg); rc = -EINVAL; goto out; @@ -2429,6 +2402,13 @@ int main(int argc, char *argv[]) goto out; } } + if (command->need_ep11_library) { + rc = load_ep11_library(&g.ep11, g.verbose); + if (rc != 0) { + rc = EXIT_FAILURE; + goto out; + } + } if (command->need_pkey_device) { g.pkey_fd = open_pkey_device(g.verbose); if (g.pkey_fd == -1) { diff --git a/zkey/zkey.1 b/zkey/zkey.1 index 31fff43..6326f19 100644 --- a/zkey/zkey.1 +++ b/zkey/zkey.1 @@ -24,9 +24,9 @@ zkey \- Manage secure AES keys . .SH DESCRIPTION Use the \fBzkey\fP tool to generate and manage secure AES keys that are -enciphered with a master key of an IBM cryptographic adapter in CCA coprocessor -mode. You can also use the \fBzkey\fP tool to validate and re-encipher secure -AES keys. +enciphered with a master key of an IBM cryptographic adapter in CCA or EP11 +coprocessor mode, dependent on the key type. You can also use the \fBzkey\fP +tool to validate and re-encipher secure AES keys. .PP The secure keys can either be stored in a file in the file system, or in the secure key repository. The default location of the secure key repository @@ -43,7 +43,7 @@ group \fBzkeyadm\fP. When storing the secure key in a key repository, additional information, such as a textual description of the key, can be associated with a secure key. You can associate a secure key with one or multiple cryptographic adapters -(APQNs) that are set up with the same CCA master key. +(APQNs) that are set up with the same CCA or EP11 master key. You can also associate a secure key with one or multiple volumes (block devices), which are encrypted using dm-crypt with the secure key. The volume association also contains the device-mapper name, separated by a colon, @@ -52,7 +52,7 @@ key. .PP The generated secure key is saved in a file with a size of 64 or 128 bytes. The file contains an AES key with a length of 128, 192, or 256 bits. The key is -enciphered with the master key of the CCA cryptographic adapter. +enciphered with the master key of the CCA or EP11 cryptographic adapter. Secure keys that are used for the XTS cipher mode can be 128 or 256 bits in size. . @@ -111,13 +111,13 @@ key repository. .PP Use the .B generate -command to generate a new secure AES key either randomly within the CCA +command to generate a new secure AES key either randomly within the CCA or EP11 cryptographic adapter, or from a clear AES key specified as input. When specifying a clear key as input, the clear key should be kept in a secure place, or be securely erased after creation of the secure key. The secure key itself does not need to be kept secure, because it can only be used together with a -CCA cryptographic adapter that contains the master key with which the secure -key was generated. +CCA or EP11 cryptographic adapter that contains the master key with which the +secure key was generated. .PP The generated secure key can either be stored in a file in the file system, or in the secure key repository. To store the generated secure key in a @@ -135,14 +135,17 @@ additional information can be associated with a secure key using the .B \-\-sector-size options. .PP -You can generate different types of secure keys: \fBCCA-AESDATA\fP keys, and -\fBCCA-AESCIPHER\fP keys. Specify the type of the secure key using the +You can generate different types of secure keys: \fBCCA-AESDATA\fP keys, +\fBCCA-AESCIPHER\fP, and \fBEP11-AES\fP keys. +Specify the type of the secure key using the .B \-\-key\-type option. The default key type is CCA-AESDATA. .PP .B Note: Secure keys of type \fBCCA-AESCIPHER\fP require an IBM cryptographic adapter in CCA coprocessor mode of version 6 or later, e.g. a CEX6C. +Secure keys of type \fBEP11-AES\fP require an IBM cryptographic +adapter in EP11 coprocessor mode of version 7 or later, e.g. a CEX7P. . .SS "Validating secure AES keys" . @@ -223,7 +226,7 @@ are validated. Use the .B reencipher command to re-encipher an existing secure key with a new master key. -A secure key must be re-enciphered when the master key of the CCA +A secure key must be re-enciphered when the master key of the CCA or EP11 cryptographic adapter changes. .PP The CCA cryptographic adapter has three different registers to store @@ -246,11 +249,15 @@ the current master key. You can pro-actively re-encipher a secure key with the option to do this. .RE .PP +\fBNote:\fP An EP11 cryptographic adapter has only two registers to store master +keys, \fBCURRENT\fP and \fBNEW\fP. +.PP Use the .B \-\-from\-old option to re-encipher a secure key that is currently enciphered with the master key in the \fBOLD\fP register with the master key in the -\fBCURRENT\fP register. +\fBCURRENT\fP register. This option is only available for secure keys of type +\fBCCA-AESDATA\fP or \fBCCA-AESCIPHER\fP. .PP .PP If both the @@ -301,19 +308,23 @@ the re-enciphered secure key. Re-enciphering from \fBOLD\fP to \fBCURRENT\fP is performed in-place per default. You can use option \fB\-\-in-place\fP to force an in-place re-enciphering for the \fBCURRENT\fP to \fBNEW\fP case. Be aware that a secure key that was re-enciphered in-place from \fBCURRENT\fP to \fBNEW\fP -is no longer valid, until the new CCA master key has been made the current one. +is no longer valid, until the new CCA or EP11 master key has been made the +current one. .PP \fBStaged\fP mode means that the re-enciphered secure key is stored in a separate file in the secure key repository. Thus the current secure key is still -valid at this point. Once the new CCA master key has been set (made active), you -must rerun the reencipher command with option \fB\-\-complete\fP to complete the -staged re-enciphering. Re-enciphering from \fBCURRENT\fP to \fBNEW\fP is -performed in staged mode per default. You can use option \fB\-\-staged\fP to force -a staged re-enciphering for the \fBOLD\fP to \fBCURRENT\fP case. +valid at this point. Once the new CCA or EP11 master key has been set (made +active), you must rerun the reencipher command with option \fB\-\-complete\fP +to complete the staged re-enciphering. Re-enciphering from \fBCURRENT\fP to +\fBNEW\fP is performed in staged mode per default. You can use option +\fB\-\-staged\fP to force a staged re-enciphering for the \fBOLD\fP to +\fBCURRENT\fP case. .PP .B Note: -The \fBreencipher\fP command requires the CCA host library (libcsulcca.so) -to be installed. For the supported environments and downloads, see: +The \fBreencipher\fP command requires the CCA host library (libcsulcca.so, for) +for secure keys of type CCA-AESDATA or CCA-AESCIPHER, or the IBM Z Enterprise +PKCS #11 (EP11) Support Program (EP11 host library) for secure keys of type +EP11-AES to be installed. For the supported environments and downloads, see: \fIhttp://www.ibm.com/security/cryptocards\fP . .SS "Import existing AES secure keys into the secure key repository" @@ -490,8 +501,8 @@ associations with one command. .B Note: The secure key itself cannot be changed, only information about the secure key is changed. To rename a secure key, use the \fBrename\fP command. -To re-encipher a secure key with a new CCA master key, use the \fBreencipher\fP -command. +To re-encipher a secure key with a new CCA or EP11 master key, use the +\fBreencipher\fP command. . .SS "Rename existing AES secure keys in the secure key repository" . @@ -788,7 +799,7 @@ A specific volume can only be associated with a single secure key. This option is only used for secure keys contained in the secure key repository. .TP .BR \-a ", " \-\-apqns\~\fIcard1.domain1[,card2.domain2[,...]]\fP -Specifies a comma-separated list of cryptographic adapters in CCA +Specifies a comma-separated list of cryptographic adapters in CCA or EP11 coprocessor mode (APQN) which are associated with the secure AES key in the repository. Each APQN association specifies a card and domain number separated by a period (like lszcrypt displays it). When at least one APQN is specified, @@ -818,11 +829,13 @@ the default volume type is \fBplain\fP. This option is only used for secure keys contained in the secure key repository. .TP .BR \-K ", " \-\-key-type\~\fItype\fP -Specifies the key type of the secure key. Possible values are \fBCCA-AESDATA\fP -and \fBCCA-AESCIPHER\fP. If this option is omitted, then a secure key of type -CCA-AESDATA is generated. Secure keys of type \fBCCA-AESCIPHER\fP require an -IBM cryptographic adapter in CCA coprocessor mode of version 6 or later, e.g. -a CEX6C. +Specifies the key type of the secure key. Possible values are \fBCCA-AESDATA\fP, +\fBCCA-AESCIPHER\fP, and \fBEP11-AES\fP. If this option is omitted, then a +secure key of type CCA-AESDATA is generated. +Secure keys of type \fBCCA-AESCIPHER\fP require an IBM cryptographic adapter +in CCA coprocessor mode of version 6 or later, e.g. a CEX6C. +Secure keys of type \fBEP11-AES\fP require an IBM cryptographic adapter +in EP11 coprocessor mode of version 7 or later, e.g. a CEX7P. . . . @@ -835,7 +848,7 @@ When wildcards are used you must quote the value. This option is only used for secure keys contained in the secure key repository. .TP .BR \-a ", " \-\-apqns\~\fIcard1.domain1[,card2.domain2[,...]]\fP -Specifies a comma-separated list of cryptographic adapters in CCA +Specifies a comma-separated list of cryptographic adapters in CCA or EP11 coprocessor mode (APQNs). You can use wildcards in the APQN specification. All secure keys contained in the secure key repository which are associated with the specified APQNs are validated. @@ -858,6 +871,8 @@ master key in the CURRENT register with the master key in the NEW register. .BR \-o ", " \-\-from\-old Re-enciphers a secure AES key that is currently enciphered with the master key in the OLD register with the master key in the CURRENT register. +This option is only available for secure keys of type CCA-AESDATA and +CCA-AESCIPHER. .TP .BR \-f ", " \-\-output\~\fIoutput\-file\fP Specifies the name of the output file to which the re-enciphered secure key @@ -873,7 +888,7 @@ When wildcards are used you must quote the value. This option is only used for secure keys contained in the secure key repository. .TP .BR \-a ", " \-\-apqns\~\fIcard1.domain1[,card2.domain2[,...]]\fP -Specifies a comma-separated list of cryptographic adapters in CCA +Specifies a comma-separated list of cryptographic adapters in CCA or EP11 coprocessor mode (APQNs). You can use wildcards in the APQN specification. All secure keys contained in the secure key repository which are associated with the specified APQNs are re-enciphered. @@ -892,16 +907,16 @@ This option is only used for secure keys contained in the secure key repository. Forces that the re-enciphering of a secure AES key contained in the secure key repository is performed in staged mode. Staged mode means that the re-enciphered secure key is stored in a separate file in the secure key repository. Thus the -current secure key is still valid at this point. Once the new CCA master key has -been set (made active), you must rerun the reencipher command with option -\fB\-\-complete\fP to complete the staged re-enciphering. +current secure key is still valid at this point. Once the new CCA or EP11 master +key has been set (made active), you must rerun the reencipher command with +option \fB\-\-complete\fP to complete the staged re-enciphering. Re-enciphering from CURRENT to NEW is performed in staged mode per default. This option is only used for secure keys contained in the secure key repository. .TP .BR \-p ", " \-\-complete -Completes a staged re-enciphering. Use this option after the new CCA master key -has been set (made active). This option replaces the secure key by its -re-enciphered version in the secure key repository. +Completes a staged re-enciphering. Use this option after the new CCA or EP11 +master key has been set (made active). This option replaces the secure key by +its re-enciphered version in the secure key repository. This option is only used for secure keys contained in the secure key repository. . . @@ -926,7 +941,7 @@ A specific volume can only be associated with a single secure key. This option is only used for secure keys contained in the secure key repository. .TP .BR \-a ", " \-\-apqns\~\fIcard1.domain1[,card2.domain2[,...]]\fP -Specifies a comma-separated list of cryptographic adapters in CCA +Specifies a comma-separated list of cryptographic adapters in CCA or EP11 coprocessor mode (APQN) which are associated with the secure AES key in the repository. Each APQN association specifies a card and domain number separated by a period (like lszcrypt displays it). All specified APQNs must be online, @@ -986,7 +1001,7 @@ When wildcards are used you must quote the value. This option is only used for secure keys contained in the secure key repository. .TP .BR \-a ", " \-\-apqns\~\fIcard1.domain1[,card2.domain2[,...]]\fP -Specifies a comma-separated list of cryptographic adapters in CCA +Specifies a comma-separated list of cryptographic adapters in CCA or EP11 coprocessor mode (APQN) which are associated with the secure AES key in the repository. Only those keys are listed, which are associated with the specified APQNs. Each APQN association specifies a card and domain number separated @@ -1004,8 +1019,9 @@ has been compiled with LUKS2 support enabled. This option is only used for secure keys contained in the secure key repository. .TP .BR \-K ", " \-\-key-type\~\fItype\fP -Specifies the key type of the secure key. Possible values are \fBCCA-AESDATA\fP -and \fBCCA-AESCIPHER\fP. Only keys with the specified key type are listed. +Specifies the key type of the secure key. Possible values are \fBCCA-AESDATA\fP, +\fBCCA-AESCIPHER\fP, and \fBEP11-AES\fP. Only keys with the specified key type +are listed. This option is only used for secure keys contained in the secure key repository. . . @@ -1050,7 +1066,7 @@ A specific volume can only be associated with a single secure key. This option is only used for secure keys contained in the secure key repository. .TP .BR \-a ", " \-\-apqns\~\fI[+|-]card1.domain1[,card2.domain2[,...]]\fP -Specifies a comma-separated list of cryptographic adapters in CCA +Specifies a comma-separated list of cryptographic adapters in CCA or EP11 coprocessor mode (APQN) which are associated with the secure AES key in the repository. Each APQN association specifies a card and domain number separated by a period (like lszcrypt displays it). diff --git a/zkey/zkey.c b/zkey/zkey.c index 3ae9a9b..e6356c0 100644 --- a/zkey/zkey.c +++ b/zkey/zkey.c @@ -28,6 +28,7 @@ #include "lib/zt_common.h" #include "cca.h" +#include "ep11.h" #include "keystore.h" #include "misc.h" #include "pkey.h" @@ -83,12 +84,16 @@ static struct zkey_globals { bool force; bool open; bool format; + struct ext_lib lib; struct cca_lib cca; + struct ep11_lib ep11; int pkey_fd; struct keystore *keystore; } g = { .pkey_fd = -1, .sector_size = -1, + .lib.cca = &g.cca, + .lib.ep11 = &g.ep11, }; /* @@ -164,7 +169,7 @@ static struct util_opt opt_vec[] = { .desc = "Name of the secure AES key in the repository. If " "option --name/-N is specified, then the generated " "secure AES key is stored in the repository. Parameter " - "SECURE-KEY-FILE is not used when option --name/-M is " + "SECURE-KEY-FILE is not used when option --name/-N is " "specified", .command = COMMAND_GENERATE, }, @@ -222,9 +227,9 @@ static struct util_opt opt_vec[] = { .option = { "key-type", required_argument, NULL, 'K'}, .argument = "type", .desc = "The type of the key. Possible values are '" - KEY_TYPE_CCA_AESDATA"' and '"KEY_TYPE_CCA_AESCIPHER"'. " - "When this option is omitted, the default is '" - KEY_TYPE_CCA_AESDATA"'", + KEY_TYPE_CCA_AESDATA"', '"KEY_TYPE_CCA_AESCIPHER"' " + "and '"KEY_TYPE_EP11_AES"'. When this option is " + "omitted, the default is '"KEY_TYPE_CCA_AESDATA"'", .command = COMMAND_GENERATE, }, /***********************************************************/ @@ -259,7 +264,7 @@ static struct util_opt opt_vec[] = { { .option = {"complete", 0, NULL, 'p'}, .desc = "Completes a staged re-enciphering. Use this option " - "after the new CCA master key has been set (made " + "after the new master key has been set (made " "active)", .command = COMMAND_REENCIPHER, }, @@ -447,9 +452,9 @@ static struct util_opt opt_vec[] = { .option = { "key-type", required_argument, NULL, 'K'}, .argument = "type", .desc = "The type of the key. Possible values are '" - KEY_TYPE_CCA_AESDATA"' and '"KEY_TYPE_CCA_AESCIPHER"'. " - "Use this option to list all keys with the specified " - "key type.", + KEY_TYPE_CCA_AESDATA"', '"KEY_TYPE_CCA_AESCIPHER"' " + "and '"KEY_TYPE_EP11_AES"'. Use this option to list " + "all keys with the specified key type.", .command = COMMAND_LIST, }, /***********************************************************/ @@ -822,6 +827,7 @@ struct zkey_command { unsigned int abbrev_len; int (*function)(void); int need_cca_library; + int need_ep11_library; int need_pkey_device; char *short_desc; char *long_desc; @@ -868,13 +874,13 @@ static struct zkey_command zkey_commands[] = { .command = COMMAND_REENCIPHER, .abbrev_len = 2, .function = command_reencipher, - .need_cca_library = 1, + /* Will load the CCA or EP11 library on demand */ .need_pkey_device = 1, .short_desc = "Re-encipher an existing secure AES key", .long_desc = "Re-encipher an existing secure AES " "key that is either contained in SECURE-KEY-FILE " "or is stored in the repository with another " - "CCA master key", + "master key", .has_options = 1, .pos_arg = "[SECURE-KEY-FILE]", .pos_arg_optional = 1, @@ -1171,8 +1177,10 @@ static int command_generate(void) return EXIT_FAILURE; } - rc = cross_check_apqns(NULL, 0, + rc = cross_check_apqns(NULL, NULL, get_min_card_level_for_keytype(g.key_type), + get_min_fw_version_for_keytype(g.key_type), + get_card_type_for_keytype(g.key_type), true, g.verbose); if (rc == -EINVAL) return EXIT_FAILURE; @@ -1198,9 +1206,8 @@ static int command_reencipher_file(void) { size_t secure_key_size; int rc, is_old_mk; - int selected = 1; u8 *secure_key; - u64 mkvp; + bool selected; if (g.name != NULL) { warnx("Option '--name|-N' is not valid for " @@ -1246,30 +1253,21 @@ static int command_reencipher_file(void) goto out; } - rc = get_master_key_verification_pattern(secure_key, secure_key_size, - &mkvp, g.verbose); - if (rc != 0) { - warnx("Failed to get the master key verification pattern: %s", - strerror(-rc)); - rc = EXIT_FAILURE; - goto out; - } - if (!g.fromold && !g.tonew) { /* Autodetect reencipher option */ if (is_old_mk) { g.fromold = 1; util_print_indented("The secure key is currently " - "enciphered with the OLD CCA " + "enciphered with the OLD " "master key and is being " "re-enciphered with the CURRENT " - "CCA master key\n", 0); + "master key\n", 0); } else { g.tonew = 1; util_print_indented("The secure key is currently " - "enciphered with the CURRENT CCA " + "enciphered with the CURRENT " "master key and is being " - "re-enciphered with the NEW CCA " + "re-enciphered with the NEW " "master key\n", 0); } } @@ -1278,68 +1276,56 @@ static int command_reencipher_file(void) if (g.fromold) { if (!is_old_mk) { warnx("The secure key is already enciphered " - "with the CURRENT CCA master key"); + "with the CURRENT master key"); rc = EXIT_FAILURE; goto out; } pr_verbose("Secure key will be re-enciphered from OLD to the " - "CURRENT CCA master key"); - - rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, - FLAG_SEL_CCA_MATCH_OLD_MKVP, - g.verbose); - if (rc == -ENOTSUP) { - rc = 0; - selected = 0; - } - if (rc != 0) { - warnx("No APQN found that is suitable for " - "re-enciphering the secure AES volume key"); - rc = EXIT_FAILURE; - goto out; - } + "CURRENT master key"); - rc = key_token_change(&g.cca, secure_key, secure_key_size, - METHOD_OLD_TO_CURRENT, - g.verbose); + rc = reencipher_secure_key(&g.lib, secure_key, secure_key_size, + NULL, REENCIPHER_OLD_TO_CURRENT, + &selected, g.verbose); if (rc != 0) { - warnx("Re-encipher from OLD to CURRENT CCA " - "master key has failed\n"); - if (!selected) - print_msg_for_cca_envvars("secure AES key"); + if (rc == -ENODEV) { + warnx("No APQN found that is suitable for " + "re-enciphering the secure AES volume " + "key"); + } else { + warnx("Re-encipher from OLD to CURRENT " + "master key has failed\n"); + if (!selected && + !is_ep11_aes_key(secure_key, + secure_key_size)) + print_msg_for_cca_envvars( + "secure AES key"); + } rc = EXIT_FAILURE; goto out; } } if (g.tonew) { pr_verbose("Secure key will be re-enciphered from CURRENT " - "to the NEW CCA master key"); - - rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL, - FLAG_SEL_CCA_MATCH_CUR_MKVP | - FLAG_SEL_CCA_NEW_MUST_BE_SET, - g.verbose); - if (rc == -ENOTSUP) { - rc = 0; - selected = 0; - } - if (rc != 0) { - util_print_indented("No APQN found that is suitable " - "for re-enciphering this secure " - "AES key and has the NEW master " - "key loaded", 0); - rc = EXIT_FAILURE; - goto out; - } + "to the NEW master key"); - rc = key_token_change(&g.cca, secure_key, secure_key_size, - METHOD_CURRENT_TO_NEW, g.verbose); + rc = reencipher_secure_key(&g.lib, secure_key, secure_key_size, + NULL, REENCIPHER_CURRENT_TO_NEW, + &selected, g.verbose); if (rc != 0) { - warnx("Re-encipher from CURRENT to NEW CCA " - "master key has failed\n"); - if (!selected) - print_msg_for_cca_envvars("secure AES key"); + if (rc == -ENODEV) { + warnx("No APQN found that is suitable for " + "re-enciphering the secure AES volume " + "key and has the NEW master key loaded"); + } else { + warnx("Re-encipher from CURRENT to NEW " + "master key has failed\n"); + if (!selected && + !is_ep11_aes_key(secure_key, + secure_key_size)) + print_msg_for_cca_envvars( + "secure AES key"); + } rc = EXIT_FAILURE; goto out; } @@ -1360,7 +1346,7 @@ out: /* * Command handler for 'reencipher in repository'. * - * Re-encipher the specified secure key with the NEW or CURRENT CCA master key. + * Re-encipher the specified secure key with the NEW or CURRENT master key. */ static int command_reencipher_repository(void) { @@ -1395,7 +1381,7 @@ static int command_reencipher_repository(void) rc = keystore_reencipher_key(g.keystore, g.name, g.apqns, g.fromold, g.tonew, g.inplace, g.staged, g.complete, - g.pkey_fd, &g.cca); + g.pkey_fd, &g.lib); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1403,7 +1389,7 @@ static int command_reencipher_repository(void) /* * Command handler for 'reencipher'. * - * Re-encipher the specified secure key with the NEW or CURRENT CCA master key. + * Re-encipher the specified secure key with the NEW or CURRENT master key. */ static int command_reencipher(void) { @@ -1425,9 +1411,10 @@ static int command_validate_file(void) char vp[VERIFICATION_PATTERN_LEN]; size_t secure_key_size; size_t clear_key_size; + const char *key_type; + u8 mkvp[MKVP_LENGTH]; u8 *secure_key; int is_old_mk; - u64 mkvp; int rc; if (g.name != NULL) { @@ -1475,7 +1462,7 @@ static int command_validate_file(void) } rc = get_master_key_verification_pattern(secure_key, secure_key_size, - &mkvp, g.verbose); + mkvp, g.verbose); if (rc != 0) { warnx("Failed to get the master key verification pattern: %s", strerror(-rc)); @@ -1483,24 +1470,27 @@ static int command_validate_file(void) goto out; } + key_type = get_key_type(secure_key, secure_key_size); + printf("Validation of secure key in file '%s':\n", g.pos_arg); printf(" Status: Valid\n"); printf(" Secure key size: %lu bytes\n", secure_key_size); - printf(" Key type: %s\n", - get_key_type(secure_key, secure_key_size)); + printf(" Key type: %s\n", key_type); printf(" Clear key size: %lu bits\n", clear_key_size); printf(" XTS type key: %s\n", is_xts_key(secure_key, secure_key_size) ? "Yes" : "No"); - printf(" Enciphered with: %s CCA master key (MKVP: %016llx)\n", - is_old_mk ? "OLD" : "CURRENT", mkvp); + printf(" Enciphered with: %s master key (MKVP: %s)\n", + is_old_mk ? "OLD" : "CURRENT", + printable_mkvp(get_card_type_for_keytype(key_type), mkvp)); printf(" Verification pattern: %.*s\n", VERIFICATION_PATTERN_LEN / 2, vp); printf(" %.*s\n", VERIFICATION_PATTERN_LEN / 2, &vp[VERIFICATION_PATTERN_LEN / 2]); rc = cross_check_apqns(NULL, mkvp, - get_min_card_level_for_keytype( - get_key_type(secure_key, secure_key_size)), + get_min_card_level_for_keytype(key_type), + get_min_fw_version_for_keytype(key_type), + get_card_type_for_keytype(key_type), true, g.verbose); if (rc == -EINVAL) return EXIT_FAILURE; @@ -1571,7 +1561,7 @@ static int command_import(void) rc = keystore_import_key(g.keystore, g.name, g.description, g.volumes, g.apqns, g.noapqncheck, g.sector_size, - g.pos_arg, g.volume_type, &g.cca); + g.pos_arg, g.volume_type, &g.lib); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -1750,11 +1740,11 @@ static int command_convert_file(void) u8 output_key[2 * MAX_SECURE_KEY_SIZE]; unsigned int output_key_size; size_t secure_key_size; + u8 mkvp[MKVP_LENGTH]; int rc, is_old_mk; int selected = 1; u8 *secure_key; int min_level; - u64 mkvp; if (g.name != NULL) { warnx("Option '--name|-N' is not valid for " @@ -1775,7 +1765,10 @@ static int command_convert_file(void) return EXIT_FAILURE; } - rc = cross_check_apqns(NULL, 0, min_level, true, g.verbose); + rc = cross_check_apqns(NULL, NULL, min_level, + get_min_fw_version_for_keytype(g.key_type), + get_card_type_for_keytype(g.key_type), + true, g.verbose); if (rc == -EINVAL) return EXIT_FAILURE; if (rc != 0 && rc != -ENOTSUP) { @@ -1797,7 +1790,7 @@ static int command_convert_file(void) } rc = get_master_key_verification_pattern(secure_key, secure_key_size, - &mkvp, g.verbose); + mkvp, g.verbose); if (rc != 0) { warnx("Failed to get the master key verification pattern: %s", strerror(-rc)); @@ -1910,7 +1903,7 @@ static int command_convert_repository(void) } rc = keystore_convert_key(g.keystore, g.name, g.key_type, g.noapqncheck, - g.force, g.pkey_fd, &g.cca); + g.force, g.pkey_fd, &g.lib); return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } @@ -2225,6 +2218,13 @@ int main(int argc, char *argv[]) goto out; } } + if (command->need_ep11_library) { + rc = load_ep11_library(&g.ep11, g.verbose); + if (rc != 0) { + rc = EXIT_FAILURE; + goto out; + } + } if (command->need_pkey_device) { g.pkey_fd = open_pkey_device(g.verbose); if (g.pkey_fd == -1) { @@ -2240,6 +2240,8 @@ int main(int argc, char *argv[]) out: if (g.cca.lib_csulcca) dlclose(g.cca.lib_csulcca); + if (g.ep11.lib_ep11) + dlclose(g.ep11.lib_ep11); if (g.pkey_fd >= 0) close(g.pkey_fd); if (g.keystore) -- 2.21.3 From 03becee6f57d9ec59a593efe388de81fb5b72643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Thu, 28 May 2020 12:13:27 +0200 Subject: [PATCH 56/56] zdev: report FC Endpoint Security of zfcp device (#1723843) Summary: zdev: report FC Endpoint Security of zfcp devices Description: Report FC Endpoint Security of zfcp devices, which includes: 1. Add support to report Fibre Channel (FC) Endpoint Security related information for zfcp-host and zfcp-lun devices. 2. Format and print HBA FC Endpoint Security trace records Upstream-ID: c063273b145c1f182968f318c04d3d8ea4dc0d9f Upstream-ID: 67496afe577d9bcf74d1100ac5216e05343930c8 Upstream-ID: 16b2799150981e9fc134573957d156d560f6d758 Upstream-ID: fbf8513d43656fddeecd844bbcdf468dd3cbd4cb --- scripts/zfcpdbf | 44 +++++++++++++++++++++++++++++++++++++++- zdev/include/attrib.h | 4 +++- zdev/src/device.c | 9 +++++++- zdev/src/export.c | 4 +++- zdev/src/table_attribs.c | 14 ++++++++----- zdev/src/zfcp_host.c | 22 ++++++++++++++++++++ zdev/src/zfcp_lun.c | 34 +++++++++++++++++++++++++++---- 7 files changed, 118 insertions(+), 13 deletions(-) diff --git a/scripts/zfcpdbf b/scripts/zfcpdbf index 1fb2fe3..7eca30d 100755 --- a/scripts/zfcpdbf +++ b/scripts/zfcpdbf @@ -2,7 +2,7 @@ # # zfcpdbf - Tool to interpret the information from logging/tracing sources # -# Copyright IBM Corp. 2010, 2017 +# Copyright IBM Corp. 2010, 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. @@ -640,6 +640,47 @@ sub _print_hba_id3 } } +sub _print_hba_id5 +{ + my $fsf_req_id = shift(); + my $rec = shift(); + my $payload_length = shift(); + my $rec_received = shift(); + my $rec_issued; + + $rec_issued = stck_to_timeval(substr($rec, 0, 16)); + + if (defined $timediff) { # do we want to see delayed responses ? + my @t_arr = split(/[-:]/, $rec_received); + + my $ts1 = mktime($t_arr[5], $t_arr[4], $t_arr[3], + $t_arr[2], $t_arr[1] - 1, $t_arr[0] - 1900); + + my ($ts2, $us2) = split(/:/, $rec_issued); + + my $ts_received = $ts1 . "." . $t_arr[6]; + my $ts_issued = $ts2 . "." . $us2; + + if (($ts_received - $ts_issued) >= $timediff) { + print "WARNING: delayed response above ", + "skip level of $timediff seconds.\n"; + } + } + + print "FSF issued : ",str_timestamp($rec_issued),"\n"; + print "FSF stat : 0x", substr($rec, 16, 8), "\n"; + print "FSF stat qual : n/a\n"; + print "Prot stat : n/a\n"; + print "Prot stat qual : n/a\n"; + + print "Port handle : 0x", substr($rec, 24, 8), "\n"; + print "LUN handle : n/a\n"; + + print "WWPN : 0x", substr($rec, 32, 16), "\n"; + print "FCES old : 0x", substr($rec, 48, 8), "\n"; + print "FCES new : 0x", substr($rec, 56, 8), "\n"; +} + sub print_deferr_common { my $rec = shift(); @@ -842,6 +883,7 @@ sub assign_callback_subs $print_hba_id[1] = \&_print_hba_id1; $print_hba_id[2] = \&_print_hba_id2; $print_hba_id[3] = \&_print_hba_id3; + $print_hba_id[5] = \&_print_hba_id5; $print_rec_id[1] = \&_print_rec_id1; $print_rec_id[2] = \&_print_rec_id2; diff --git a/zdev/include/attrib.h b/zdev/include/attrib.h index 052d4ce..24910ef 100644 --- a/zdev/include/attrib.h +++ b/zdev/include/attrib.h @@ -1,7 +1,7 @@ /* * zdev - Modify and display the persistent configuration of devices * - * Copyright IBM Corp. 2016, 2017 + * Copyright IBM Corp. 2016, 2019 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. @@ -181,6 +181,7 @@ struct value_map { * @activeonly: This attribute should only be changed in the active config * @unstable: The value read is not the last value written * @writeonly: This attribute cannot be read from + * @readonly: This attribute cannot be written to * @rewrite: Writing the same value multiple times has side-effects * @mandatory: This attribute cannot be removed from a configured device * @newline: There must be a newline when writing to this attribute @@ -211,6 +212,7 @@ struct attrib { unsigned int activeonly :1; unsigned int unstable :1; unsigned int writeonly :1; + unsigned int readonly :1; unsigned int rewrite :1; unsigned int mandatory :1; unsigned int newline :1; diff --git a/zdev/src/device.c b/zdev/src/device.c index ea15abc..1b1ff76 100644 --- a/zdev/src/device.c +++ b/zdev/src/device.c @@ -214,6 +214,9 @@ static exit_code_t apply_setting(struct device *dev, config_t config, /* Check for known attribute. */ a = subtype_find_dev_attrib(st, key); if (a) { + /* Check for read-only attribute. */ + if (a->readonly) + goto err_readonly; /* Check for acceptable value of known attribute. */ if (!force && !attrib_check_value(a, value)) goto err_invalid_forceable; @@ -306,6 +309,10 @@ err_int_noactive: delayed_err("Internal attribute '%s' cannot be set in the active config\n", key); return EXIT_INVALID_SETTING; + +err_readonly: + delayed_err("Cannot modify read-only attribute: %s\n", key); + return EXIT_INVALID_SETTING; } /* Apply device settings from strlist to device. */ @@ -520,7 +527,7 @@ void device_read_active_settings(struct device *dev, read_scope_t scope) a = attrib_find(st->dev_attribs, name); s = setting_list_apply_actual(dev->active.settings, a, name, value); - if (link || (scope == scope_all && + if ((a && a->readonly) || link || (scope == scope_all && !util_path_is_writable(path))) s->readonly = 1; if (link) diff --git a/zdev/src/export.c b/zdev/src/export.c index a66db0b..e538c93 100644 --- a/zdev/src/export.c +++ b/zdev/src/export.c @@ -1,7 +1,7 @@ /* * zdev - Modify and display the persistent configuration of devices * - * Copyright IBM Corp. 2016, 2017 + * Copyright IBM Corp. 2016, 2019 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. @@ -71,6 +71,8 @@ static bool is_exportable(struct setting *s, config_t config) if (!a) return true; + if (a->readonly) + return false; if (a->mandatory) return true; if (SCOPE_ACTIVE(config)) { diff --git a/zdev/src/table_attribs.c b/zdev/src/table_attribs.c index 50100c7..c6bb86a 100644 --- a/zdev/src/table_attribs.c +++ b/zdev/src/table_attribs.c @@ -1,7 +1,7 @@ /* * zdev - Modify and display the persistent configuration of devices * - * Copyright IBM Corp. 2016, 2017 + * Copyright IBM Corp. 2016, 2019 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. @@ -186,13 +186,15 @@ static void table_attribs_show_details_one(struct table_attrib *t, indent(j, "The default value is '%s'.\n", a->defval); } - printf("\n"); - indent(i, "ACCEPTED VALUES\n"); - attrib_print_acceptable(a, j); + if (!a->readonly) { + printf("\n"); + indent(i, "ACCEPTED VALUES\n"); + attrib_print_acceptable(a, j); + } if (!(a->multi || a->activeonly || a->unstable || a->writeonly || a->rewrite || a->mandatory || a->newline || a->activerem || - a->nounload || a->check)) + a->nounload || a->check || a->readonly)) return; printf("\n"); @@ -211,6 +213,8 @@ static void table_attribs_show_details_one(struct table_attrib *t, } if (a->writeonly) indent(j, "- You cannot read this attribute\n"); + if (a->readonly) + indent(j, "- You cannot write to this attribute\n"); if (a->rewrite) { indent(j, "- Setting the same value multiple times may have " "additional effects\n"); diff --git a/zdev/src/zfcp_host.c b/zdev/src/zfcp_host.c index 729bc42..ba91f35 100644 --- a/zdev/src/zfcp_host.c +++ b/zdev/src/zfcp_host.c @@ -70,6 +70,27 @@ static struct attrib zfcp_host_attr_port_rescan = { .accept = ACCEPT_ARRAY(ACCEPT_NUM(1)), }; +static struct attrib zfcp_host_attr_fc_security = { + .name = "fc_security", + .title = "Show FC Endpoint Security capability of FCP device", + .desc = + "This read-only attribute shows the Fibre Channel Endpoint Security\n" + "capabilities of the FCP device.\n" + "\n" + "Possible values are either one of the following:\n" + " unknown : The Fibre Channel Endpoint Security capabilities\n" + " of the FCP device are not known\n" + " unsupported : The FCP device does not support Fibre Channel\n" + " Endpoint Security\n" + " none : The FCP device does not report any Fibre Channel\n" + " Endpoint Security capabilities\n" + "\n" + "Or one or more comma-separated values:\n" + " Authentication: The FCP device supports authentication\n" + " Encryption : The FCP device supports encryption\n", + .readonly = 1, +}; + /* * zfcp host methods. */ @@ -248,6 +269,7 @@ struct subtype zfcp_host_subtype = { &zfcp_host_attr_failed, &zfcp_host_attr_port_remove, &zfcp_host_attr_port_rescan, + &zfcp_host_attr_fc_security, &internal_attr_early, ), .unknown_dev_attribs = 1, diff --git a/zdev/src/zfcp_lun.c b/zdev/src/zfcp_lun.c index f16cbfd..6fb5637 100644 --- a/zdev/src/zfcp_lun.c +++ b/zdev/src/zfcp_lun.c @@ -33,6 +33,7 @@ #include "zfcp_lun.h" #define DEVNAME "zFCP LUN" +#define FC_SECURITY_VATTR "fc_security" /* * zfcp lun namespace. @@ -413,6 +414,24 @@ static struct attrib zfcp_lun_attr_scsi_delete = { .accept = ACCEPT_ARRAY(ACCEPT_NUM(1)), }; +static struct attrib zfcp_lun_attr_fc_security = { + .name = FC_SECURITY_VATTR, + .title = "Show FC Endpoint Security state of connection", + .desc = + "This read-only attribute shows the current state of Fibre Channel\n" + "Endpoint Security of the connection between the FCP device and the\n" + "FC remote port used to access the LUN:\n" + " unknown : The Fibre Channel Endpoint Security state of the\n" + " connection is not known\n" + " unsupported : The FCP device does not support Fibre Channel\n" + " Endpoint Security\n" + " none : The connection has no Fibre Channel Endpoint\n" + " Security\n" + " Authentication: The connection has been authenticated\n" + " Encryption : The connection is encrypted\n", + .readonly = 1, +}; + /* * zfcp lun device methods. */ @@ -759,10 +778,8 @@ static char *zfcp_lun_st_get_active_attrib_path(struct subtype *st, char *hctl, *devpath, *path; size_t len = strlen(SCSI_ATTR_PREFIX); - if (!starts_with(name, SCSI_ATTR_PREFIX) || - !(name[len] == 0 || name[len] == '/' )) - devpath = path_get_zfcp_lun_dev(dev->devid); - else { + if (starts_with(name, SCSI_ATTR_PREFIX) && + (name[len] == 0 || name[len] == '/')) { hctl = scsi_hctl_from_zfcp_lun_devid(dev->devid); if (!hctl) return NULL; @@ -770,6 +787,14 @@ static char *zfcp_lun_st_get_active_attrib_path(struct subtype *st, free(hctl); name += strlen(SCSI_ATTR_PREFIX); + } else if (strcmp(name, FC_SECURITY_VATTR) == 0) { + devpath = path_get_zfcp_port_dev(dev->devid); + if (!util_path_exists(devpath)) { + free(devpath); + return NULL; + } + } else { + devpath = path_get_zfcp_lun_dev(dev->devid); } while (*name == '/') @@ -1098,6 +1123,7 @@ struct subtype zfcp_lun_subtype = { &zfcp_lun_attr_scsi_timeout, &zfcp_lun_attr_scsi_state, &zfcp_lun_attr_scsi_delete, + &zfcp_lun_attr_fc_security, &internal_attr_early, ), .prefixes = STRING_ARRAY(SCSI_ATTR_PREFIX), -- 2.21.3