Re: [PATCH v2 2/3] drivers/s390/char: Add Ultravisor attestation to uvdevice

From: Janosch Frank
Date: Thu Mar 03 2022 - 09:45:10 EST


On 2/23/22 15:48, Steffen Eiden wrote:
This patch enables userspace to call the Retrieve Attestation Measurement
Ultravisor Call using IOCTLs on the uvdevice.

The uvdevice will do some sanity checks first.
Then, copy the request data to kernel space, build the uvcb,
perform the UV call, and copy the result back to userspace.

Userspace is now able to call the Retrieve Attestation Measurement
Ultravisor Call through the uvdevice.

Signed-off-by: Steffen Eiden <seiden@xxxxxxxxxxxxx>
---
arch/s390/include/asm/uv.h | 23 +++-
arch/s390/include/uapi/asm/uvdevice.h | 19 +++
drivers/s390/char/uvdevice.c | 163 ++++++++++++++++++++++++++
3 files changed, 204 insertions(+), 1 deletion(-)

diff --git a/arch/s390/include/asm/uv.h b/arch/s390/include/asm/uv.h
index 86218382d29c..760dda4024b9 100644
--- a/arch/s390/include/asm/uv.h
+++ b/arch/s390/include/asm/uv.h
@@ -2,7 +2,7 @@
/*
* Ultravisor Interfaces
*
- * Copyright IBM Corp. 2019
+ * Copyright IBM Corp. 2019, 2022
*
* Author(s):
* Vasily Gorbik <gor@xxxxxxxxxxxxx>
@@ -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 {
@@ -218,6 +220,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
index 60956f8d2dc0..2bab1063a82e 100644
--- a/arch/s390/include/uapi/asm/uvdevice.h
+++ b/arch/s390/include/uapi/asm/uvdevice.h
@@ -17,11 +17,30 @@ struct uvio_ioctl_cb {
__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 */
+};
+
#define UVIO_QUI_MAX_LEN 0x8000
+#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_QUI _IOWR(UVIO_TYPE_UVC, 0x01, struct uvio_ioctl_cb)
+#define UVIO_IOCTL_ATT _IOWR(UVIO_TYPE_UVC, 0x02, struct uvio_ioctl_cb)
#endif /* __S390X_ASM_UVDEVICE_H */
diff --git a/drivers/s390/char/uvdevice.c b/drivers/s390/char/uvdevice.c
index 1d90e129b570..5a9830d2d299 100644
--- a/drivers/s390/char/uvdevice.c
+++ b/drivers/s390/char/uvdevice.c
@@ -65,6 +65,163 @@ static int uvio_qui(struct uvio_ioctl_cb *uv_ioctl)
return ret;
}
+/* Create Attestation Measurement functions */
+
+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(struct uv_cb_attest);

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) {
+ ret = -ENOMEM;

The lines above didn't change ret

+ add_data = kvzalloc(uvio_attest->add_data_len, GFP_KERNEL);
+ if (!add_data)
+ goto out;
+ }
+
+ ret = -ENOMEM;

The lines above didn't change ret


With the nits fixed:
Reviewed-by: Janosch Frank <frankja@xxxxxxxxxxxxx>