Re: [PATCH 1/2] powerpc/pseries: define driver for Platform KeyStore
From: Eric Richter <hidden>
Date: 2022-07-19 19:10:13
On 7/12/22 7:59 PM, Nayna Jain wrote:
quoted hunk ↗ jump to hunk
PowerVM provides an isolated Platform Keystore(PKS) storage allocation for each LPAR with individually managed access controls to store sensitive information securely. It provides a new set of hypervisor calls for Linux kernel to access PKS storage. Define PLPKS driver using H_CALL interface to access PKS storage. Signed-off-by: Nayna Jain <nayna@linux.ibm.com> --- arch/powerpc/include/asm/hvcall.h | 9 + arch/powerpc/include/asm/plpks.h | 90 ++++ arch/powerpc/platforms/pseries/Kconfig | 13 + arch/powerpc/platforms/pseries/Makefile | 2 + arch/powerpc/platforms/pseries/plpks/Makefile | 7 + arch/powerpc/platforms/pseries/plpks/plpks.c | 509 ++++++++++++++++++ 6 files changed, 630 insertions(+) create mode 100644 arch/powerpc/include/asm/plpks.h create mode 100644 arch/powerpc/platforms/pseries/plpks/Makefile create mode 100644 arch/powerpc/platforms/pseries/plpks/plpks.cdiff --git a/arch/powerpc/include/asm/hvcall.h b/arch/powerpc/include/asm/hvcall.h index d92a20a85395..24b661b0717c 100644 --- a/arch/powerpc/include/asm/hvcall.h +++ b/arch/powerpc/include/asm/hvcall.h@@ -97,6 +97,7 @@ #define H_OP_MODE -73 #define H_COP_HW -74 #define H_STATE -75 +#define H_IN_USE -77 #define H_UNSUPPORTED_FLAG_START -256 #define H_UNSUPPORTED_FLAG_END -511 #define H_MULTI_THREADS_ACTIVE -9005@@ -321,6 +322,14 @@ #define H_SCM_UNBIND_ALL 0x3FC #define H_SCM_HEALTH 0x400 #define H_SCM_PERFORMANCE_STATS 0x418 +#define H_PKS_GET_CONFIG 0x41C +#define H_PKS_SET_PASSWORD 0x420 +#define H_PKS_GEN_PASSWORD 0x424 +#define H_PKS_WRITE_OBJECT 0x42C +#define H_PKS_GEN_KEY 0x430 +#define H_PKS_READ_OBJECT 0x434 +#define H_PKS_REMOVE_OBJECT 0x438 +#define H_PKS_CONFIRM_OBJECT_FLUSHED 0x43C #define H_RPT_INVALIDATE 0x448 #define H_SCM_FLUSH 0x44C #define H_GET_ENERGY_SCALE_INFO 0x450diff --git a/arch/powerpc/include/asm/plpks.h b/arch/powerpc/include/asm/plpks.h new file mode 100644 index 000000000000..cf60e53e1f15 --- /dev/null +++ b/arch/powerpc/include/asm/plpks.h@@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022 IBM Corporation + * Author: Nayna Jain <nayna@linux.ibm.com> + * + * Platform keystore for pseries LPAR(PLPKS). + */ + +#ifndef _PSERIES_PLPKS_H +#define _PSERIES_PLPKS_H + +#include <linux/types.h> +#include <linux/list.h> + +#define OSSECBOOTAUDIT 0x40000000 +#define OSSECBOOTENFORCE 0x20000000 +#define WORLDREADABLE 0x08000000 +#define SIGNEDUPDATE 0x01000000 + +#define PLPKS_VAR_LINUX 0x01 +#define PLPKS_VAR_COMMON 0x04 + +struct plpks_var { + char *component; + u8 os; + u8 *name; + u16 namelen; + u32 policy; + u16 datalen; + u8 *data; +}; + +struct plpks_var_name { + u16 namelen; + u8 *name; +}; + +struct plpks_var_name_list { + u32 varcount; + struct plpks_var_name varlist[]; +}; + +struct plpks_config { + u8 version; + u8 flags; + u32 rsvd0; + u16 maxpwsize; + u16 maxobjlabelsize; + u16 maxobjsize; + u32 totalsize; + u32 usedspace; + u32 supportedpolicies; + u64 rsvd1; +} __packed; + +/** + * Successful return from this API implies PKS is available. + * This is used to initialize kernel driver and user interfaces. + */ +struct plpks_config *plpks_get_config(void); + +/** + * Writes the specified var and its data to PKS. + * Any caller of PKS driver should present a valid component type for + * their variable. + */ +int plpks_write_var(struct plpks_var var); + +/** + * Removes the specified var and its data from PKS. + */ +int plpks_remove_var(char *component, u8 varos, + struct plpks_var_name vname); + +/** + * Returns the data for the specified os variable. + */ +int plpks_read_os_var(struct plpks_var *var); + +/** + * Returns the data for the specified firmware variable. + */ +int plpks_read_fw_var(struct plpks_var *var); + +/** + * Returns the data for the specified bootloader variable. + */ +int plpks_read_bootloader_var(struct plpks_var *var); + +#endifdiff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig index f7fd91d153a4..de6efe5d18c2 100644 --- a/arch/powerpc/platforms/pseries/Kconfig +++ b/arch/powerpc/platforms/pseries/Kconfig@@ -142,6 +142,19 @@ config IBMEBUS help Bus device driver for GX bus based adapters. +config PSERIES_PLPKS + depends on PPC_PSERIES + tristate "Support for the Platform Key Storage" + help + PowerVM provides an isolated Platform Keystore(PKS) storage + allocation for each LPAR with individually managed access + controls to store sensitive information securely. It can be + used to store asymmetric public keys or secrets as required + by different usecases. Select this config to enable + operating system interface to hypervisor to access this space. + + If unsure, select N. + config PAPR_SCM depends on PPC_PSERIES && MEMORY_HOTPLUG && LIBNVDIMM tristate "Support for the PAPR Storage Class Memory interface"diff --git a/arch/powerpc/platforms/pseries/Makefile b/arch/powerpc/platforms/pseries/Makefile index 7aaff5323544..d6a9209e08c0 100644 --- a/arch/powerpc/platforms/pseries/Makefile +++ b/arch/powerpc/platforms/pseries/Makefile@@ -37,3 +37,5 @@ obj-$(CONFIG_ARCH_HAS_CC_PLATFORM) += cc_platform.o # nothing that operates in real mode is safe for KASAN KASAN_SANITIZE_ras.o := n KASAN_SANITIZE_kexec.o := n + +obj-$(CONFIG_PSERIES_PLPKS) += plpks/diff --git a/arch/powerpc/platforms/pseries/plpks/Makefile b/arch/powerpc/platforms/pseries/plpks/Makefile new file mode 100644 index 000000000000..e651ace920db --- /dev/null +++ b/arch/powerpc/platforms/pseries/plpks/Makefile@@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2022 IBM Corporation +# Author: Nayna Jain <nayna@linux.ibm.com> +# + +obj-$(CONFIG_PSERIES_PLPKS) += plpks.odiff --git a/arch/powerpc/platforms/pseries/plpks/plpks.c b/arch/powerpc/platforms/pseries/plpks/plpks.c new file mode 100644 index 000000000000..463ce93f9066 --- /dev/null +++ b/arch/powerpc/platforms/pseries/plpks/plpks.c@@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * POWER LPAR Platform KeyStore (PLPKS) + * Copyright (C) 2022 IBM Corporation + * Author: Nayna Jain <nayna@linux.ibm.com> + * + * Provides access to variables stored in Power LPAR Platform KeyStore(PLPKS). + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <asm/hvcall.h> +#include <asm/plpks.h> +#include <asm/unaligned.h> +#include <asm/machdep.h> + +#define MODULE_VERS "1.0" +#define MODULE_NAME "pseries-plpks" + +#define PKS_FW_OWNER 0x1 +#define PKS_BOOTLOADER_OWNER 0x2 +#define PKS_OS_OWNER 0x3 + +#define MAX_LABEL_ATTR_SIZE 16 +#define MAX_NAME_SIZE 239 +#define MAX_DATA_SIZE 4000 + +#define PKS_FLUSH_MAX_TIMEOUT 5000 //msec +#define PKS_FLUSH_SLEEP 10 //msec +#define PKS_FLUSH_SLEEP_RANGE 400 + +static bool configset; +static struct plpks_config *config; +static u8 *ospassword; +static u16 ospasswordlength; + +struct plpks_auth { + u8 version; + u8 consumer; + __be64 rsvd0; + __be32 rsvd1; + __be16 passwordlength; + u8 password[]; +} __packed __aligned(16); + +struct label_attr { + u8 prefix[8]; + u8 version; + u8 os; + u8 length; + u8 reserved[5]; +}; + +struct label { + struct label_attr attr; + u8 name[MAX_NAME_SIZE]; +}; + +static int pseries_status_to_err(int rc) +{ + int err; + + switch (rc) { + case H_SUCCESS: + err = 0; + break; + case H_FUNCTION: + err = -ENXIO; + break; + case H_P2: + case H_P3: + case H_P4: + case H_P5: + case H_P6: + err = -EINVAL; + break; + case H_NOT_FOUND: + err = -ENOENT; + break; + case H_BUSY: + err = -EBUSY; + break; + case H_AUTHORITY: + err = -EPERM; + break; + case H_NO_MEM: + err = -ENOMEM; + break; + case H_RESOURCE: + err = -EEXIST; + break; + case H_TOO_BIG: + err = -EFBIG; + break; + default: + err = -EINVAL; + } + + return err; +} + +static int plpks_gen_password(void) +{ + unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0}; + u8 consumer = PKS_OS_OWNER; + int rc; + + ospassword = kzalloc(config->maxpwsize, GFP_KERNEL);
This unconditionally allocates over ospassword -- while this function shouldn't be called twice, it might be a good idea to place a check here anyway.
+ if (!ospassword) + return -ENOMEM; + + ospasswordlength = config->maxpwsize; + rc = plpar_hcall(H_PKS_GEN_PASSWORD, + retbuf, + consumer, + 0, + virt_to_phys(ospassword), + config->maxpwsize);
Since this hcall can return an error (as we discussed off-list, returns H_IN_USE if it is called again without a reboot), perhaps move the assignment to ospassword after this call succeeds.
+
+ return pseries_status_to_err(rc);
+}
+
+static int construct_auth(u8 consumer, struct plpks_auth **auth)
+{
+ pr_debug("max password size is %u\n", config->maxpwsize);
+
+ if (!auth || consumer > 3)
+ return -EINVAL;
+
+ *auth = kmalloc(struct_size(*auth, password, config->maxpwsize),
+ GFP_KERNEL);
+ if (!*auth)
+ return -ENOMEM;
+
+ (*auth)->version = 1;
+ (*auth)->consumer = consumer;
+ (*auth)->rsvd0 = 0;
+ (*auth)->rsvd1 = 0;
+ if (consumer == PKS_FW_OWNER || consumer == PKS_BOOTLOADER_OWNER) {
+ pr_debug("consumer is bootloader or firmware\n");
+ (*auth)->passwordlength = 0;
+ return 0;
+ }
+
+ (*auth)->passwordlength = (__force __be16)ospasswordlength;
+
+ memcpy((*auth)->password, ospassword,
+ flex_array_size(*auth, password,
+ (__force u16)((*auth)->passwordlength)));
+ (*auth)->passwordlength = cpu_to_be16((__force u16)((*auth)->passwordlength));
+
+ return 0;
+}
+
+/**
+ * Label is combination of label attributes + name.
+ * Label attributes are used internally by kernel and not exposed to the user.
+ */
+static int construct_label(char *component, u8 varos, u8 *name, u16 namelen, u8 **label)
+{
+ int varlen;
+ int len = 0;
+ int llen = 0;
+ int i;
+ int rc = 0;
+ u8 labellength = MAX_LABEL_ATTR_SIZE;
+
+ if (!label)
+ return -EINVAL;
+
+ varlen = namelen + sizeof(struct label_attr);
+ *label = kzalloc(varlen, GFP_KERNEL);
+
+ if (!*label)
+ return -ENOMEM;
+
+ if (component) {
+ len = strlen(component);
+ memcpy(*label, component, len);Is component a known size, or checked elsewhere to know this won't overrun?
+ }
+ llen = len;
+
+ if (component)
+ len = 8 - strlen(component);
+ else
+ len = 8;
+
+ memset(*label + llen, 0, len);
+ llen = llen + len;
+
+ ((*label)[llen]) = 0;
+ llen = llen + 1;
+
+ memcpy(*label + llen, &varos, 1);
+ llen = llen + 1;
+
+ memcpy(*label + llen, &labellength, 1);
+ llen = llen + 1;
+
+ memset(*label + llen, 0, 5);
+ llen = llen + 5;
+
+ memcpy(*label + llen, name, namelen);
+ llen = llen + namelen;
+
+ for (i = 0; i < llen; i++)
+ pr_debug("%c", (*label)[i]);
+
+ rc = llen;
+ return rc;
+}
+
+static int _plpks_get_config(void)
+{
+ unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+ int rc;
+ size_t size = sizeof(struct plpks_config);
+
+ config = kzalloc(size, GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+
+ rc = plpar_hcall(H_PKS_GET_CONFIG,
+ retbuf,
+ virt_to_phys(config),
+ size);
+
+ if (rc != H_SUCCESS)
+ return pseries_status_to_err(rc);
+
+ config->rsvd0 = be32_to_cpu((__force __be32)config->rsvd0);
+ config->maxpwsize = be16_to_cpu((__force __be16)config->maxpwsize);
+ config->maxobjlabelsize = be16_to_cpu((__force __be16)config->maxobjlabelsize);
+ config->maxobjsize = be16_to_cpu((__force __be16)config->maxobjsize);
+ config->totalsize = be32_to_cpu((__force __be32)config->totalsize);
+ config->usedspace = be32_to_cpu((__force __be32)config->usedspace);
+ config->supportedpolicies = be32_to_cpu((__force __be32)config->supportedpolicies);
+ config->rsvd1 = be64_to_cpu((__force __be64)config->rsvd1);
+
+ configset = true;
+
+ return 0;
+}
+
+static int plpks_confirm_object_flushed(u8 *label, u16 labellen)
+{
+ unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+ int rc;
+ u64 timeout = 0;
+ struct plpks_auth *auth;
+ u8 status;
+ int i;
+
+ rc = construct_auth(PKS_OS_OWNER, &auth);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < labellen; i++)
+ pr_debug("%02x ", label[i]);
+
+ do {
+ rc = plpar_hcall(H_PKS_CONFIRM_OBJECT_FLUSHED,
+ retbuf,
+ virt_to_phys(auth),
+ virt_to_phys(label),
+ labellen);
+
+ status = retbuf[0];
+ if (rc) {
+ pr_info("rc is %d, status is %d\n", rc, status);
+ if (rc == H_NOT_FOUND && status == 1)
+ rc = 0;
+ break;
+ }
+
+ pr_debug("rc is %d, status is %d\n", rc, status);
+
+ if (!rc && status == 1)
+ break;
+
+ usleep_range(PKS_FLUSH_SLEEP, PKS_FLUSH_SLEEP + PKS_FLUSH_SLEEP_RANGE);
+ timeout = timeout + PKS_FLUSH_SLEEP;
+ pr_debug("timeout is %llu\n", timeout);
+
+ } while (timeout < PKS_FLUSH_MAX_TIMEOUT);
+
+ rc = pseries_status_to_err(rc);
+
+ kfree(auth);
+
+ return rc;
+}
+
+int plpks_write_var(struct plpks_var var)
+{
+ unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+ int rc;
+ u8 *label;
+ u16 varlen;
+ u8 *data = var.data;
+ struct plpks_auth *auth;
+
+ if (!var.component || !data || var.datalen <= 0 ||
+ var.namelen > MAX_NAME_SIZE ||
+ var.datalen > MAX_DATA_SIZE)
+ return -EINVAL;
+
+ if (var.policy & SIGNEDUPDATE)
+ return -EINVAL;
+
+ rc = construct_auth(PKS_OS_OWNER, &auth);
+ if (rc)
+ return rc;
+
+ rc = construct_label(var.component, var.os, var.name, var.namelen,
+ &label);
+ if (rc <= 0)
+ goto out;
+
+ varlen = rc;
+ pr_debug("Name to be written is of label size %d\n", varlen);
+ rc = plpar_hcall(H_PKS_WRITE_OBJECT,
+ retbuf,
+ virt_to_phys(auth),
+ virt_to_phys(label),
+ varlen,
+ var.policy,
+ virt_to_phys(data),
+ var.datalen);
+
+ if (!rc)
+ rc = plpks_confirm_object_flushed(label, varlen);
+
+ rc = pseries_status_to_err(rc);
+ kfree(label);
+
+out:
+ kfree(auth);
+
+ return rc;
+}
+EXPORT_SYMBOL(plpks_write_var);
+
+int plpks_remove_var(char *component, u8 varos, struct plpks_var_name vname)
+{
+ unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+ int rc;
+ u8 *label;
+ u16 varlen;
+ struct plpks_auth *auth;
+
+ if (!component || vname.namelen > MAX_NAME_SIZE)
+ return -EINVAL;
+
+ rc = construct_auth(PKS_OS_OWNER, &auth);
+ if (rc)
+ return rc;
+
+ rc = construct_label(component, varos, vname.name, vname.namelen, &label);
+ if (rc <= 0)
+ goto out;
+
+ varlen = rc;
+ pr_debug("Name to be written is of label size %d\n", varlen);
+ rc = plpar_hcall(H_PKS_REMOVE_OBJECT,
+ retbuf,
+ virt_to_phys(auth),
+ virt_to_phys(label),
+ varlen);
+
+ if (!rc)
+ rc = plpks_confirm_object_flushed(label, varlen);
+
+ rc = pseries_status_to_err(rc);
+ kfree(label);
+
+out:
+ kfree(auth);
+
+ return rc;
+}
+EXPORT_SYMBOL(plpks_remove_var);
+
+static int plpks_read_var(u8 consumer, struct plpks_var *var)
+{
+ unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+ int rc;
+ u16 outlen = config->maxobjsize;
+ u8 *label;
+ u8 *out;Minor nit: there are three labels in this function with the name "out", might be clearer to rename this variable to something like "output" instead.
+ u16 varlen;
+ struct plpks_auth *auth;
+
+ if (var->namelen > MAX_NAME_SIZE)
+ return -EINVAL;
+
+ rc = construct_auth(PKS_OS_OWNER, &auth);
+ if (rc)
+ return rc;
+
+ rc = construct_label(var->component, var->os, var->name, var->namelen,
+ &label);
+ if (rc <= 0)
+ goto out;
+
+ varlen = rc;
+ pr_debug("Name to be written is of label size %d\n", varlen);
+ out = kzalloc(outlen, GFP_KERNEL);
+ if (!out)
+ goto out1;
+
+ rc = plpar_hcall(H_PKS_READ_OBJECT,
+ retbuf,
+ virt_to_phys(auth),
+ virt_to_phys(label),
+ varlen,
+ virt_to_phys(out),
+ outlen);
+
+ if (rc != H_SUCCESS) {
+ pr_err("Failed to read %d\n", rc);
+ rc = pseries_status_to_err(rc);
+ goto out2;
+ }
+
+ if (var->datalen == 0 || var->datalen > retbuf[0])
+ var->datalen = retbuf[0];
+
+ var->data = kzalloc(var->datalen, GFP_KERNEL);
+ if (!var->data) {
+ rc = -ENOMEM;
+ goto out2;
+ }
+ var->policy = retbuf[1];
+
+ memcpy(var->data, out, var->datalen);
+
+out2:
+ kfree(out);
+
+out1:
+ kfree(label);
+
+out:
+ kfree(auth);
+
+ return rc;
+}
+
+int plpks_read_os_var(struct plpks_var *var)
+{
+ return plpks_read_var(PKS_OS_OWNER, var);
+}
+EXPORT_SYMBOL(plpks_read_os_var);
+
+int plpks_read_fw_var(struct plpks_var *var)
+{
+ return plpks_read_var(PKS_FW_OWNER, var);
+}
+EXPORT_SYMBOL(plpks_read_fw_var);
+
+int plpks_read_bootloader_var(struct plpks_var *var)
+{
+ return plpks_read_var(PKS_BOOTLOADER_OWNER, var);
+}
+EXPORT_SYMBOL(plpks_read_bootloader_var);
+
+struct plpks_config *plpks_get_config(void)
+{
+ if (!configset) {
+ if (_plpks_get_config())
+ return NULL;
+ }
+
+ return config;
+}
+EXPORT_SYMBOL(plpks_get_config);
+
+static __init int pseries_plpks_init(void)
+{
+ int rc = 0;
+
+ rc = _plpks_get_config();
+
+ if (rc) {
+ pr_err("Error initializing plpks\n");
+ return rc;
+ }
+
+ rc = plpks_gen_password();
+ if (rc) {
+ if (rc == H_IN_USE) {
+ rc = 0;
+ } else {
+ pr_err("Failed setting password %d\n", rc);
+ rc = pseries_status_to_err(rc);
+ return rc;
+ }
+ }
+
+ pr_info("POWER LPAR Platform Keystore initialized successfully\n");
+
+ return rc;
+}
+arch_initcall(pseries_plpks_init);