Merge tag 'kvm-s390-next-5.19-1' of git://git.kernel.org/pub/scm/linux/kernel/git...
authorPaolo Bonzini <pbonzini@redhat.com>
Wed, 25 May 2022 09:11:21 +0000 (05:11 -0400)
committerPaolo Bonzini <pbonzini@redhat.com>
Wed, 25 May 2022 09:11:21 +0000 (05:11 -0400)
KVM: s390: Fix and feature for 5.19

- ultravisor communication device driver
- fix TEID on terminating storage key ops

14 files changed:
Documentation/virt/kvm/api.rst
MAINTAINERS
arch/s390/include/asm/uv.h
arch/s390/include/uapi/asm/uvdevice.h [new file with mode: 0644]
arch/s390/kvm/gaccess.c
drivers/s390/char/Kconfig
drivers/s390/char/Makefile
drivers/s390/char/uvdevice.c [new file with mode: 0644]
tools/testing/selftests/Makefile
tools/testing/selftests/drivers/.gitignore
tools/testing/selftests/drivers/s390x/uvdevice/Makefile [new file with mode: 0644]
tools/testing/selftests/drivers/s390x/uvdevice/config [new file with mode: 0644]
tools/testing/selftests/drivers/s390x/uvdevice/test_uvdevice.c [new file with mode: 0644]
tools/testing/selftests/kvm/s390x/memop.c

index f9016fb..d10d408 100644 (file)
@@ -3814,12 +3814,18 @@ in case of KVM_S390_MEMOP_F_CHECK_ONLY), the ioctl returns a positive
 error number indicating the type of exception. This exception is also
 raised directly at the corresponding VCPU if the flag
 KVM_S390_MEMOP_F_INJECT_EXCEPTION is set.
+On protection exceptions, unless specified otherwise, the injected
+translation-exception identifier (TEID) indicates suppression.
 
 If the KVM_S390_MEMOP_F_SKEY_PROTECTION flag is set, storage key
 protection is also in effect and may cause exceptions if accesses are
 prohibited given the access key designated by "key"; the valid range is 0..15.
 KVM_S390_MEMOP_F_SKEY_PROTECTION is available if KVM_CAP_S390_MEM_OP_EXTENSION
 is > 0.
+Since the accessed memory may span multiple pages and those pages might have
+different storage keys, it is possible that a protection exception occurs
+after memory has been modified. In this case, if the exception is injected,
+the TEID does not indicate suppression.
 
 Absolute read/write:
 ^^^^^^^^^^^^^^^^^^^^
index e5b7a32..ba0f01e 100644 (file)
@@ -10785,9 +10785,12 @@ F:     Documentation/virt/kvm/s390*
 F:     arch/s390/include/asm/gmap.h
 F:     arch/s390/include/asm/kvm*
 F:     arch/s390/include/uapi/asm/kvm*
+F:     arch/s390/include/uapi/asm/uvdevice.h
 F:     arch/s390/kernel/uv.c
 F:     arch/s390/kvm/
 F:     arch/s390/mm/gmap.c
+F:     drivers/s390/char/uvdevice.c
+F:     tools/testing/selftests/drivers/s390x/uvdevice/
 F:     tools/testing/selftests/kvm/*/s390x/
 F:     tools/testing/selftests/kvm/s390x/
 
index a2d376b..cfea7b7 100644 (file)
@@ -2,7 +2,7 @@
 /*
  * Ultravisor Interfaces
  *
- * Copyright IBM Corp. 2019
+ * Copyright IBM Corp. 2019, 2022
  *
  * Author(s):
  *     Vasily Gorbik <gor@linux.ibm.com>
@@ -52,6 +52,7 @@
 #define UVC_CMD_UNPIN_PAGE_SHARED      0x0342
 #define UVC_CMD_SET_SHARED_ACCESS      0x1000
 #define UVC_CMD_REMOVE_SHARED_ACCESS   0x1001
+#define UVC_CMD_RETR_ATTEST            0x1020
 
 /* Bits in installed uv calls */
 enum uv_cmds_inst {
@@ -76,6 +77,7 @@ enum uv_cmds_inst {
        BIT_UVC_CMD_UNSHARE_ALL = 20,
        BIT_UVC_CMD_PIN_PAGE_SHARED = 21,
        BIT_UVC_CMD_UNPIN_PAGE_SHARED = 22,
+       BIT_UVC_CMD_RETR_ATTEST = 28,
 };
 
 enum uv_feat_ind {
@@ -219,6 +221,25 @@ struct uv_cb_share {
        u64 reserved28;
 } __packed __aligned(8);
 
+/* Retrieve Attestation Measurement */
+struct uv_cb_attest {
+       struct uv_cb_header header;     /* 0x0000 */
+       u64 reserved08[2];              /* 0x0008 */
+       u64 arcb_addr;                  /* 0x0018 */
+       u64 cont_token;                 /* 0x0020 */
+       u8  reserved28[6];              /* 0x0028 */
+       u16 user_data_len;              /* 0x002e */
+       u8  user_data[256];             /* 0x0030 */
+       u32 reserved130[3];             /* 0x0130 */
+       u32 meas_len;                   /* 0x013c */
+       u64 meas_addr;                  /* 0x0140 */
+       u8  config_uid[16];             /* 0x0148 */
+       u32 reserved158;                /* 0x0158 */
+       u32 add_data_len;               /* 0x015c */
+       u64 add_data_addr;              /* 0x0160 */
+       u64 reserved168[4];             /* 0x0168 */
+} __packed __aligned(8);
+
 static inline int __uv_call(unsigned long r1, unsigned long r2)
 {
        int cc;
diff --git a/arch/s390/include/uapi/asm/uvdevice.h b/arch/s390/include/uapi/asm/uvdevice.h
new file mode 100644 (file)
index 0000000..10a5ac9
--- /dev/null
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ *  Copyright IBM Corp. 2022
+ *  Author(s): Steffen Eiden <seiden@linux.ibm.com>
+ */
+#ifndef __S390_ASM_UVDEVICE_H
+#define __S390_ASM_UVDEVICE_H
+
+#include <linux/types.h>
+
+struct uvio_ioctl_cb {
+       __u32 flags;
+       __u16 uv_rc;                    /* UV header rc value */
+       __u16 uv_rrc;                   /* UV header rrc value */
+       __u64 argument_addr;            /* Userspace address of uvio argument */
+       __u32 argument_len;
+       __u8  reserved14[0x40 - 0x14];  /* must be zero */
+};
+
+#define UVIO_ATT_USER_DATA_LEN         0x100
+#define UVIO_ATT_UID_LEN               0x10
+struct uvio_attest {
+       __u64 arcb_addr;                                /* 0x0000 */
+       __u64 meas_addr;                                /* 0x0008 */
+       __u64 add_data_addr;                            /* 0x0010 */
+       __u8  user_data[UVIO_ATT_USER_DATA_LEN];        /* 0x0018 */
+       __u8  config_uid[UVIO_ATT_UID_LEN];             /* 0x0118 */
+       __u32 arcb_len;                                 /* 0x0128 */
+       __u32 meas_len;                                 /* 0x012c */
+       __u32 add_data_len;                             /* 0x0130 */
+       __u16 user_data_len;                            /* 0x0134 */
+       __u16 reserved136;                              /* 0x0136 */
+};
+
+/*
+ * The following max values define an upper length for the IOCTL in/out buffers.
+ * However, they do not represent the maximum the Ultravisor allows which is
+ * often way smaller. By allowing larger buffer sizes we hopefully do not need
+ * to update the code with every machine update. It is therefore possible for
+ * userspace to request more memory than actually used by kernel/UV.
+ */
+#define UVIO_ATT_ARCB_MAX_LEN          0x100000
+#define UVIO_ATT_MEASUREMENT_MAX_LEN   0x8000
+#define UVIO_ATT_ADDITIONAL_MAX_LEN    0x8000
+
+#define UVIO_DEVICE_NAME "uv"
+#define UVIO_TYPE_UVC 'u'
+
+#define UVIO_IOCTL_ATT _IOWR(UVIO_TYPE_UVC, 0x01, struct uvio_ioctl_cb)
+
+#endif /* __S390_ASM_UVDEVICE_H */
index d53a183..227ed00 100644 (file)
@@ -491,8 +491,8 @@ enum prot_type {
        PROT_TYPE_IEP  = 4,
 };
 
-static int trans_exc(struct kvm_vcpu *vcpu, int code, unsigned long gva,
-                    u8 ar, enum gacc_mode mode, enum prot_type prot)
+static int trans_exc_ending(struct kvm_vcpu *vcpu, int code, unsigned long gva, u8 ar,
+                           enum gacc_mode mode, enum prot_type prot, bool terminate)
 {
        struct kvm_s390_pgm_info *pgm = &vcpu->arch.pgm;
        struct trans_exc_code_bits *tec;
@@ -520,6 +520,11 @@ static int trans_exc(struct kvm_vcpu *vcpu, int code, unsigned long gva,
                        tec->b61 = 1;
                        break;
                }
+               if (terminate) {
+                       tec->b56 = 0;
+                       tec->b60 = 0;
+                       tec->b61 = 0;
+               }
                fallthrough;
        case PGM_ASCE_TYPE:
        case PGM_PAGE_TRANSLATION:
@@ -552,6 +557,12 @@ static int trans_exc(struct kvm_vcpu *vcpu, int code, unsigned long gva,
        return code;
 }
 
+static int trans_exc(struct kvm_vcpu *vcpu, int code, unsigned long gva, u8 ar,
+                    enum gacc_mode mode, enum prot_type prot)
+{
+       return trans_exc_ending(vcpu, code, gva, ar, mode, prot, false);
+}
+
 static int get_vcpu_asce(struct kvm_vcpu *vcpu, union asce *asce,
                         unsigned long ga, u8 ar, enum gacc_mode mode)
 {
@@ -1109,8 +1120,11 @@ int access_guest_with_key(struct kvm_vcpu *vcpu, unsigned long ga, u8 ar,
                data += fragment_len;
                ga = kvm_s390_logical_to_effective(vcpu, ga + fragment_len);
        }
-       if (rc > 0)
-               rc = trans_exc(vcpu, rc, ga, ar, mode, prot);
+       if (rc > 0) {
+               bool terminate = (mode == GACC_STORE) && (idx > 0);
+
+               rc = trans_exc_ending(vcpu, rc, ga, ar, mode, prot, terminate);
+       }
 out_unlock:
        if (need_ipte_lock)
                ipte_unlock(vcpu);
index 6cc4b19..ef8f418 100644 (file)
@@ -100,6 +100,16 @@ config SCLP_OFB
          This option enables the Open-for-Business interface to the s390
          Service Element.
 
+config S390_UV_UAPI
+       def_tristate m
+       prompt "Ultravisor userspace API"
+       help
+         Selecting exposes parts of the UV interface to userspace
+         by providing a misc character device at /dev/uv.
+         Using IOCTLs one can interact with the UV.
+         The device is only available if the Ultravisor
+         Facility (158) is present.
+
 config S390_TAPE
        def_tristate m
        prompt "S/390 tape device support"
index c6fdb81..ce32270 100644 (file)
@@ -48,6 +48,7 @@ obj-$(CONFIG_MONREADER) += monreader.o
 obj-$(CONFIG_MONWRITER) += monwriter.o
 obj-$(CONFIG_S390_VMUR) += vmur.o
 obj-$(CONFIG_CRASH_DUMP) += sclp_sdias.o zcore.o
+obj-$(CONFIG_S390_UV_UAPI) += uvdevice.o
 
 hmcdrv-objs := hmcdrv_mod.o hmcdrv_dev.o hmcdrv_ftp.o hmcdrv_cache.o diag_ftp.o sclp_ftp.o
 obj-$(CONFIG_HMC_DRV) += hmcdrv.o
diff --git a/drivers/s390/char/uvdevice.c b/drivers/s390/char/uvdevice.c
new file mode 100644 (file)
index 0000000..66505d7
--- /dev/null
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  Copyright IBM Corp. 2022
+ *  Author(s): Steffen Eiden <seiden@linux.ibm.com>
+ *
+ *  This file provides a Linux misc device to give userspace access to some
+ *  Ultravisor (UV) functions. The device only accepts IOCTLs and will only
+ *  be present if the Ultravisor facility (158) is present.
+ *
+ *  When userspace sends a valid IOCTL uvdevice will copy the input data to
+ *  kernel space, do some basic validity checks to avoid kernel/system
+ *  corruption. Any other check that the Ultravisor does will not be done by
+ *  the uvdevice to keep changes minimal when adding new functionalities
+ *  to existing UV-calls.
+ *  After the checks uvdevice builds a corresponding
+ *  Ultravisor Call Control Block, and sends the request to the Ultravisor.
+ *  Then, it copies the response, including the return codes, back to userspace.
+ *  It is the responsibility of the userspace to check for any error issued
+ *  by UV and to interpret the UV response. The uvdevice acts as a communication
+ *  channel for userspace to the Ultravisor.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/types.h>
+#include <linux/stddef.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+
+#include <asm/uvdevice.h>
+#include <asm/uv.h>
+
+static int uvio_build_uvcb_attest(struct uv_cb_attest *uvcb_attest, u8 *arcb,
+                                 u8 *meas, u8 *add_data, struct uvio_attest *uvio_attest)
+{
+       void __user *user_buf_arcb = (void __user *)uvio_attest->arcb_addr;
+
+       if (copy_from_user(arcb, user_buf_arcb, uvio_attest->arcb_len))
+               return -EFAULT;
+
+       uvcb_attest->header.len = sizeof(*uvcb_attest);
+       uvcb_attest->header.cmd = UVC_CMD_RETR_ATTEST;
+       uvcb_attest->arcb_addr = (u64)arcb;
+       uvcb_attest->cont_token = 0;
+       uvcb_attest->user_data_len = uvio_attest->user_data_len;
+       memcpy(uvcb_attest->user_data, uvio_attest->user_data, sizeof(uvcb_attest->user_data));
+       uvcb_attest->meas_len = uvio_attest->meas_len;
+       uvcb_attest->meas_addr = (u64)meas;
+       uvcb_attest->add_data_len = uvio_attest->add_data_len;
+       uvcb_attest->add_data_addr = (u64)add_data;
+
+       return 0;
+}
+
+static int uvio_copy_attest_result_to_user(struct uv_cb_attest *uvcb_attest,
+                                          struct uvio_ioctl_cb *uv_ioctl,
+                                          u8 *measurement, u8 *add_data,
+                                          struct uvio_attest *uvio_attest)
+{
+       struct uvio_attest __user *user_uvio_attest = (void __user *)uv_ioctl->argument_addr;
+       void __user *user_buf_add = (void __user *)uvio_attest->add_data_addr;
+       void __user *user_buf_meas = (void __user *)uvio_attest->meas_addr;
+       void __user *user_buf_uid = &user_uvio_attest->config_uid;
+
+       if (copy_to_user(user_buf_meas, measurement, uvio_attest->meas_len))
+               return -EFAULT;
+       if (add_data && copy_to_user(user_buf_add, add_data, uvio_attest->add_data_len))
+               return -EFAULT;
+       if (copy_to_user(user_buf_uid, uvcb_attest->config_uid, sizeof(uvcb_attest->config_uid)))
+               return -EFAULT;
+       return 0;
+}
+
+static int get_uvio_attest(struct uvio_ioctl_cb *uv_ioctl, struct uvio_attest *uvio_attest)
+{
+       u8 __user *user_arg_buf = (u8 __user *)uv_ioctl->argument_addr;
+
+       if (copy_from_user(uvio_attest, user_arg_buf, sizeof(*uvio_attest)))
+               return -EFAULT;
+
+       if (uvio_attest->arcb_len > UVIO_ATT_ARCB_MAX_LEN)
+               return -EINVAL;
+       if (uvio_attest->arcb_len == 0)
+               return -EINVAL;
+       if (uvio_attest->meas_len > UVIO_ATT_MEASUREMENT_MAX_LEN)
+               return -EINVAL;
+       if (uvio_attest->meas_len == 0)
+               return -EINVAL;
+       if (uvio_attest->add_data_len > UVIO_ATT_ADDITIONAL_MAX_LEN)
+               return -EINVAL;
+       if (uvio_attest->reserved136)
+               return -EINVAL;
+       return 0;
+}
+
+/**
+ * uvio_attestation() - Perform a Retrieve Attestation Measurement UVC.
+ *
+ * @uv_ioctl: ioctl control block
+ *
+ * uvio_attestation() does a Retrieve Attestation Measurement Ultravisor Call.
+ * It verifies that the given userspace addresses are valid and request sizes
+ * are sane. Every other check is made by the Ultravisor (UV) and won't result
+ * in a negative return value. It copies the input to kernelspace, builds the
+ * request, sends the UV-call, and copies the result to userspace.
+ *
+ * The Attestation Request has two input and two outputs.
+ * ARCB and User Data are inputs for the UV generated by userspace.
+ * Measurement and Additional Data are outputs for userspace generated by UV.
+ *
+ * The Attestation Request Control Block (ARCB) is a cryptographically verified
+ * and secured request to UV and User Data is some plaintext data which is
+ * going to be included in the Attestation Measurement calculation.
+ *
+ * Measurement is a cryptographic measurement of the callers properties,
+ * optional data configured by the ARCB and the user data. If specified by the
+ * ARCB, UV will add some Additional Data to the measurement calculation.
+ * This Additional Data is then returned as well.
+ *
+ * If the Retrieve Attestation Measurement UV facility is not present,
+ * UV will return invalid command rc. This won't be fenced in the driver
+ * and does not result in a negative return value.
+ *
+ * Context: might sleep
+ *
+ * Return: 0 on success or a negative error code on error.
+ */
+static int uvio_attestation(struct uvio_ioctl_cb *uv_ioctl)
+{
+       struct uv_cb_attest *uvcb_attest = NULL;
+       struct uvio_attest *uvio_attest = NULL;
+       u8 *measurement = NULL;
+       u8 *add_data = NULL;
+       u8 *arcb = NULL;
+       int ret;
+
+       ret = -EINVAL;
+       if (uv_ioctl->argument_len != sizeof(*uvio_attest))
+               goto out;
+
+       ret = -ENOMEM;
+       uvio_attest = kzalloc(sizeof(*uvio_attest), GFP_KERNEL);
+       if (!uvio_attest)
+               goto out;
+
+       ret = get_uvio_attest(uv_ioctl, uvio_attest);
+       if (ret)
+               goto out;
+
+       ret = -ENOMEM;
+       arcb = kvzalloc(uvio_attest->arcb_len, GFP_KERNEL);
+       measurement = kvzalloc(uvio_attest->meas_len, GFP_KERNEL);
+       if (!arcb || !measurement)
+               goto out;
+
+       if (uvio_attest->add_data_len) {
+               add_data = kvzalloc(uvio_attest->add_data_len, GFP_KERNEL);
+               if (!add_data)
+                       goto out;
+       }
+
+       uvcb_attest = kzalloc(sizeof(*uvcb_attest), GFP_KERNEL);
+       if (!uvcb_attest)
+               goto out;
+
+       ret = uvio_build_uvcb_attest(uvcb_attest, arcb,  measurement, add_data, uvio_attest);
+       if (ret)
+               goto out;
+
+       uv_call_sched(0, (u64)uvcb_attest);
+
+       uv_ioctl->uv_rc = uvcb_attest->header.rc;
+       uv_ioctl->uv_rrc = uvcb_attest->header.rrc;
+
+       ret = uvio_copy_attest_result_to_user(uvcb_attest, uv_ioctl, measurement, add_data,
+                                             uvio_attest);
+out:
+       kvfree(arcb);
+       kvfree(measurement);
+       kvfree(add_data);
+       kfree(uvio_attest);
+       kfree(uvcb_attest);
+       return ret;
+}
+
+static int uvio_copy_and_check_ioctl(struct uvio_ioctl_cb *ioctl, void __user *argp)
+{
+       if (copy_from_user(ioctl, argp, sizeof(*ioctl)))
+               return -EFAULT;
+       if (ioctl->flags != 0)
+               return -EINVAL;
+       if (memchr_inv(ioctl->reserved14, 0, sizeof(ioctl->reserved14)))
+               return -EINVAL;
+
+       return 0;
+}
+
+/*
+ * IOCTL entry point for the Ultravisor device.
+ */
+static long uvio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       struct uvio_ioctl_cb uv_ioctl = { };
+       long ret;
+
+       switch (cmd) {
+       case UVIO_IOCTL_ATT:
+               ret = uvio_copy_and_check_ioctl(&uv_ioctl, argp);
+               if (ret)
+                       return ret;
+               ret = uvio_attestation(&uv_ioctl);
+               break;
+       default:
+               ret = -ENOIOCTLCMD;
+               break;
+       }
+       if (ret)
+               return ret;
+
+       if (copy_to_user(argp, &uv_ioctl, sizeof(uv_ioctl)))
+               ret = -EFAULT;
+
+       return ret;
+}
+
+static const struct file_operations uvio_dev_fops = {
+       .owner = THIS_MODULE,
+       .unlocked_ioctl = uvio_ioctl,
+       .llseek = no_llseek,
+};
+
+static struct miscdevice uvio_dev_miscdev = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = UVIO_DEVICE_NAME,
+       .fops = &uvio_dev_fops,
+};
+
+static void __exit uvio_dev_exit(void)
+{
+       misc_deregister(&uvio_dev_miscdev);
+}
+
+static int __init uvio_dev_init(void)
+{
+       if (!test_facility(158))
+               return -ENXIO;
+       return misc_register(&uvio_dev_miscdev);
+}
+
+module_init(uvio_dev_init);
+module_exit(uvio_dev_exit);
+
+MODULE_AUTHOR("IBM Corporation");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Ultravisor UAPI driver");
index 2319ec8..d6b3073 100644 (file)
@@ -10,6 +10,7 @@ TARGETS += core
 TARGETS += cpufreq
 TARGETS += cpu-hotplug
 TARGETS += drivers/dma-buf
+TARGETS += drivers/s390x/uvdevice
 TARGETS += efivarfs
 TARGETS += exec
 TARGETS += filesystems
index ca74f2e..09e23b5 100644 (file)
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0-only
 /dma-buf/udmabuf
+/s390x/uvdevice/test_uvdevice
diff --git a/tools/testing/selftests/drivers/s390x/uvdevice/Makefile b/tools/testing/selftests/drivers/s390x/uvdevice/Makefile
new file mode 100644 (file)
index 0000000..5e701d2
--- /dev/null
@@ -0,0 +1,22 @@
+include ../../../../../build/Build.include
+
+UNAME_M := $(shell uname -m)
+
+ifneq ($(UNAME_M),s390x)
+nothing:
+.PHONY: all clean run_tests install
+.SILENT:
+else
+
+TEST_GEN_PROGS := test_uvdevice
+
+top_srcdir ?= ../../../../../..
+KSFT_KHDR_INSTALL := 1
+khdr_dir = $(top_srcdir)/usr/include
+LINUX_TOOL_ARCH_INCLUDE = $(top_srcdir)/tools/arch/$(ARCH)/include
+
+CFLAGS += -Wall -Werror -static -I$(khdr_dir) -I$(LINUX_TOOL_ARCH_INCLUDE)
+
+include ../../../lib.mk
+
+endif
diff --git a/tools/testing/selftests/drivers/s390x/uvdevice/config b/tools/testing/selftests/drivers/s390x/uvdevice/config
new file mode 100644 (file)
index 0000000..f28a04b
--- /dev/null
@@ -0,0 +1 @@
+CONFIG_S390_UV_UAPI=y
diff --git a/tools/testing/selftests/drivers/s390x/uvdevice/test_uvdevice.c b/tools/testing/selftests/drivers/s390x/uvdevice/test_uvdevice.c
new file mode 100644 (file)
index 0000000..ea0cdc3
--- /dev/null
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  selftest for the Ultravisor UAPI device
+ *
+ *  Copyright IBM Corp. 2022
+ *  Author(s): Steffen Eiden <seiden@linux.ibm.com>
+ */
+
+#include <stdint.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <asm/uvdevice.h>
+
+#include "../../../kselftest_harness.h"
+
+#define UV_PATH  "/dev/uv"
+#define BUFFER_SIZE 0x200
+FIXTURE(uvio_fixture) {
+       int uv_fd;
+       struct uvio_ioctl_cb uvio_ioctl;
+       uint8_t buffer[BUFFER_SIZE];
+       __u64 fault_page;
+};
+
+FIXTURE_VARIANT(uvio_fixture) {
+       unsigned long ioctl_cmd;
+       uint32_t arg_size;
+};
+
+FIXTURE_VARIANT_ADD(uvio_fixture, att) {
+       .ioctl_cmd = UVIO_IOCTL_ATT,
+       .arg_size = sizeof(struct uvio_attest),
+};
+
+FIXTURE_SETUP(uvio_fixture)
+{
+       self->uv_fd = open(UV_PATH, O_ACCMODE);
+
+       self->uvio_ioctl.argument_addr = (__u64)self->buffer;
+       self->uvio_ioctl.argument_len = variant->arg_size;
+       self->fault_page =
+               (__u64)mmap(NULL, (size_t)getpagesize(), PROT_NONE, MAP_ANONYMOUS, -1, 0);
+}
+
+FIXTURE_TEARDOWN(uvio_fixture)
+{
+       if (self->uv_fd)
+               close(self->uv_fd);
+       munmap((void *)self->fault_page, (size_t)getpagesize());
+}
+
+TEST_F(uvio_fixture, fault_ioctl_arg)
+{
+       int rc, errno_cache;
+
+       rc = ioctl(self->uv_fd, variant->ioctl_cmd, NULL);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EFAULT);
+
+       rc = ioctl(self->uv_fd, variant->ioctl_cmd, self->fault_page);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EFAULT);
+}
+
+TEST_F(uvio_fixture, fault_uvio_arg)
+{
+       int rc, errno_cache;
+
+       self->uvio_ioctl.argument_addr = 0;
+       rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EFAULT);
+
+       self->uvio_ioctl.argument_addr = self->fault_page;
+       rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EFAULT);
+}
+
+/*
+ * Test to verify that IOCTLs with invalid values in the ioctl_control block
+ * are rejected.
+ */
+TEST_F(uvio_fixture, inval_ioctl_cb)
+{
+       int rc, errno_cache;
+
+       self->uvio_ioctl.argument_len = 0;
+       rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EINVAL);
+
+       self->uvio_ioctl.argument_len = (uint32_t)-1;
+       rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EINVAL);
+       self->uvio_ioctl.argument_len = variant->arg_size;
+
+       self->uvio_ioctl.flags = (uint32_t)-1;
+       rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EINVAL);
+       self->uvio_ioctl.flags = 0;
+
+       memset(self->uvio_ioctl.reserved14, 0xff, sizeof(self->uvio_ioctl.reserved14));
+       rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EINVAL);
+
+       memset(&self->uvio_ioctl, 0x11, sizeof(self->uvio_ioctl));
+       rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
+       ASSERT_EQ(rc, -1);
+}
+
+TEST_F(uvio_fixture, inval_ioctl_cmd)
+{
+       int rc, errno_cache;
+       uint8_t nr = _IOC_NR(variant->ioctl_cmd);
+       unsigned long cmds[] = {
+               _IOWR('a', nr, struct uvio_ioctl_cb),
+               _IOWR(UVIO_TYPE_UVC, nr, int),
+               _IO(UVIO_TYPE_UVC, nr),
+               _IOR(UVIO_TYPE_UVC, nr, struct uvio_ioctl_cb),
+               _IOW(UVIO_TYPE_UVC, nr, struct uvio_ioctl_cb),
+       };
+
+       for (size_t i = 0; i < ARRAY_SIZE(cmds); i++) {
+               rc = ioctl(self->uv_fd, cmds[i], &self->uvio_ioctl);
+               errno_cache = errno;
+               ASSERT_EQ(rc, -1);
+               ASSERT_EQ(errno_cache, ENOTTY);
+       }
+}
+
+struct test_attest_buffer {
+       uint8_t arcb[0x180];
+       uint8_t meas[64];
+       uint8_t add[32];
+};
+
+FIXTURE(attest_fixture) {
+       int uv_fd;
+       struct uvio_ioctl_cb uvio_ioctl;
+       struct uvio_attest uvio_attest;
+       struct test_attest_buffer attest_buffer;
+       __u64 fault_page;
+};
+
+FIXTURE_SETUP(attest_fixture)
+{
+       self->uv_fd = open(UV_PATH, O_ACCMODE);
+
+       self->uvio_ioctl.argument_addr = (__u64)&self->uvio_attest;
+       self->uvio_ioctl.argument_len = sizeof(self->uvio_attest);
+
+       self->uvio_attest.arcb_addr = (__u64)&self->attest_buffer.arcb;
+       self->uvio_attest.arcb_len = sizeof(self->attest_buffer.arcb);
+
+       self->uvio_attest.meas_addr = (__u64)&self->attest_buffer.meas;
+       self->uvio_attest.meas_len = sizeof(self->attest_buffer.meas);
+
+       self->uvio_attest.add_data_addr = (__u64)&self->attest_buffer.add;
+       self->uvio_attest.add_data_len = sizeof(self->attest_buffer.add);
+       self->fault_page =
+               (__u64)mmap(NULL, (size_t)getpagesize(), PROT_NONE, MAP_ANONYMOUS, -1, 0);
+}
+
+FIXTURE_TEARDOWN(attest_fixture)
+{
+       if (self->uv_fd)
+               close(self->uv_fd);
+       munmap((void *)self->fault_page, (size_t)getpagesize());
+}
+
+static void att_inval_sizes_test(uint32_t *size, uint32_t max_size, bool test_zero,
+                                struct __test_metadata *_metadata,
+                                FIXTURE_DATA(attest_fixture) *self)
+{
+       int rc, errno_cache;
+       uint32_t tmp = *size;
+
+       if (test_zero) {
+               *size = 0;
+               rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
+               errno_cache = errno;
+               ASSERT_EQ(rc, -1);
+               ASSERT_EQ(errno_cache, EINVAL);
+       }
+       *size = max_size + 1;
+       rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EINVAL);
+       *size = tmp;
+}
+
+/*
+ * Test to verify that attestation IOCTLs with invalid values in the UVIO
+ * attestation control block are rejected.
+ */
+TEST_F(attest_fixture, att_inval_request)
+{
+       int rc, errno_cache;
+
+       att_inval_sizes_test(&self->uvio_attest.add_data_len, UVIO_ATT_ADDITIONAL_MAX_LEN,
+                            false, _metadata, self);
+       att_inval_sizes_test(&self->uvio_attest.meas_len, UVIO_ATT_MEASUREMENT_MAX_LEN,
+                            true, _metadata, self);
+       att_inval_sizes_test(&self->uvio_attest.arcb_len, UVIO_ATT_ARCB_MAX_LEN,
+                            true, _metadata, self);
+
+       self->uvio_attest.reserved136 = (uint16_t)-1;
+       rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EINVAL);
+
+       memset(&self->uvio_attest, 0x11, sizeof(self->uvio_attest));
+       rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
+       ASSERT_EQ(rc, -1);
+}
+
+static void att_inval_addr_test(__u64 *addr, struct __test_metadata *_metadata,
+                               FIXTURE_DATA(attest_fixture) *self)
+{
+       int rc, errno_cache;
+       __u64 tmp = *addr;
+
+       *addr = 0;
+       rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EFAULT);
+       *addr = self->fault_page;
+       rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
+       errno_cache = errno;
+       ASSERT_EQ(rc, -1);
+       ASSERT_EQ(errno_cache, EFAULT);
+       *addr = tmp;
+}
+
+TEST_F(attest_fixture, att_inval_addr)
+{
+       att_inval_addr_test(&self->uvio_attest.arcb_addr, _metadata, self);
+       att_inval_addr_test(&self->uvio_attest.add_data_addr, _metadata, self);
+       att_inval_addr_test(&self->uvio_attest.meas_addr, _metadata, self);
+}
+
+static void __attribute__((constructor)) __constructor_order_last(void)
+{
+       if (!__constructor_order)
+               __constructor_order = _CONSTRUCTOR_ORDER_BACKWARD;
+}
+
+int main(int argc, char **argv)
+{
+       int fd = open(UV_PATH, O_ACCMODE);
+
+       if (fd < 0)
+               ksft_exit_skip("No uv-device or cannot access " UV_PATH  "\n"
+                              "Enable CONFIG_S390_UV_UAPI and check the access rights on "
+                              UV_PATH ".\n");
+       close(fd);
+       return test_harness_run(argc, argv);
+}
index b04c2c1..49f26f5 100644 (file)
@@ -10,6 +10,8 @@
 #include <string.h>
 #include <sys/ioctl.h>
 
+#include <linux/bits.h>
+
 #include "test_util.h"
 #include "kvm_util.h"
 
@@ -194,6 +196,7 @@ static int err_memop_ioctl(struct test_vcpu vcpu, struct kvm_s390_mem_op *ksmo)
 #define SIDA_OFFSET(o) ._sida_offset = 1, .sida_offset = (o)
 #define AR(a) ._ar = 1, .ar = (a)
 #define KEY(a) .f_key = 1, .key = (a)
+#define INJECT .f_inject = 1
 
 #define CHECK_N_DO(f, ...) ({ f(__VA_ARGS__, CHECK_ONLY); f(__VA_ARGS__); })
 
@@ -430,9 +433,18 @@ static void test_copy_key_fetch_prot(void)
        TEST_ASSERT(rv == 4, "Should result in protection exception");          \
 })
 
+static void guest_error_key(void)
+{
+       GUEST_SYNC(STAGE_INITED);
+       set_storage_key_range(mem1, PAGE_SIZE, 0x18);
+       set_storage_key_range(mem1 + PAGE_SIZE, sizeof(mem1) - PAGE_SIZE, 0x98);
+       GUEST_SYNC(STAGE_SKEYS_SET);
+       GUEST_SYNC(STAGE_IDLED);
+}
+
 static void test_errors_key(void)
 {
-       struct test_default t = test_default_init(guest_copy_key_fetch_prot);
+       struct test_default t = test_default_init(guest_error_key);
 
        HOST_SYNC(t.vcpu, STAGE_INITED);
        HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
@@ -446,6 +458,37 @@ static void test_errors_key(void)
        kvm_vm_free(t.kvm_vm);
 }
 
+static void test_termination(void)
+{
+       struct test_default t = test_default_init(guest_error_key);
+       uint64_t prefix;
+       uint64_t teid;
+       uint64_t teid_mask = BIT(63 - 56) | BIT(63 - 60) | BIT(63 - 61);
+       uint64_t psw[2];
+
+       HOST_SYNC(t.vcpu, STAGE_INITED);
+       HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
+
+       /* vcpu, mismatching keys after first page */
+       ERR_PROT_MOP(t.vcpu, LOGICAL, WRITE, mem1, t.size, GADDR_V(mem1), KEY(1), INJECT);
+       /*
+        * The memop injected a program exception and the test needs to check the
+        * Translation-Exception Identification (TEID). It is necessary to run
+        * the guest in order to be able to read the TEID from guest memory.
+        * Set the guest program new PSW, so the guest state is not clobbered.
+        */
+       prefix = t.run->s.regs.prefix;
+       psw[0] = t.run->psw_mask;
+       psw[1] = t.run->psw_addr;
+       MOP(t.vm, ABSOLUTE, WRITE, psw, sizeof(psw), GADDR(prefix + 464));
+       HOST_SYNC(t.vcpu, STAGE_IDLED);
+       MOP(t.vm, ABSOLUTE, READ, &teid, sizeof(teid), GADDR(prefix + 168));
+       /* Bits 56, 60, 61 form a code, 0 being the only one allowing for termination */
+       ASSERT_EQ(teid & teid_mask, 0);
+
+       kvm_vm_free(t.kvm_vm);
+}
+
 static void test_errors_key_storage_prot_override(void)
 {
        struct test_default t = test_default_init(guest_copy_key_fetch_prot);
@@ -668,6 +711,7 @@ int main(int argc, char *argv[])
                test_copy_key_fetch_prot();
                test_copy_key_fetch_prot_override();
                test_errors_key();
+               test_termination();
                test_errors_key_storage_prot_override();
                test_errors_key_fetch_prot_override_not_enabled();
                test_errors_key_fetch_prot_override_enabled();