diff mbox series

[RFC,v2,1/3] powerpc/pseries: define driver for Platform KeyStore

Message ID 20220622215648.96723-2-nayna@linux.ibm.com
State New
Headers show
Series powerpc/pseries: add support for local secure storage called Platform KeyStore(PKS) | expand

Commit Message

Nayna Jain June 22, 2022, 9:56 p.m. UTC
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             |  12 +-
 arch/powerpc/include/asm/plpks.h              |  92 ++++
 arch/powerpc/platforms/pseries/Kconfig        |  10 +
 arch/powerpc/platforms/pseries/Makefile       |   2 +
 arch/powerpc/platforms/pseries/plpks/Makefile |   7 +
 arch/powerpc/platforms/pseries/plpks/plpks.c  | 517 ++++++++++++++++++
 6 files changed, 639 insertions(+), 1 deletion(-)
 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.c
diff mbox series

Patch

diff --git a/arch/powerpc/include/asm/hvcall.h b/arch/powerpc/include/asm/hvcall.h
index d92a20a85395..1da429235632 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,10 +322,19 @@ 
 #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	0x450
-#define MAX_HCALL_OPCODE	H_GET_ENERGY_SCALE_INFO
+#define H_PKS_SIGNED_UPDATE	0x454
+#define MAX_HCALL_OPCODE	H_PKS_SIGNED_UPDATE
 
 /* Scope args for H_SCM_UNBIND_ALL */
 #define H_UNBIND_SCOPE_ALL (0x1)
diff --git a/arch/powerpc/include/asm/plpks.h b/arch/powerpc/include/asm/plpks.h
new file mode 100644
index 000000000000..c3b544bdbcb6
--- /dev/null
+++ b/arch/powerpc/include/asm/plpks.h
@@ -0,0 +1,92 @@ 
+/* 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
+
+struct plpks_var {
+	char *component;
+	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.
+ */
+extern struct plpks_config *plpks_get_config(void);
+
+/**
+ * Updates the authenticated variable. It expects NULL as the component.
+ */
+extern int plpks_signed_update_var(struct plpks_var var);
+
+/**
+ * Writes the specified var and its data to PKS.
+ * Any caller of PKS driver should present a valid component type for
+ * their variable.
+ */
+extern int plpks_write_var(struct plpks_var var);
+
+/**
+ * Removes the specified var and its data from PKS.
+ */
+extern int plpks_remove_var(char *component, struct plpks_var_name vname);
+
+/**
+ * Returns the data for the specified os variable.
+ */
+extern int plpks_read_os_var(struct plpks_var *var);
+
+/**
+ * Returns the data for the specified firmware variable.
+ */
+extern int plpks_read_fw_var(struct plpks_var *var);
+
+/**
+ * Returns the data for the specified bootloader variable.
+ */
+extern int plpks_read_bootloader_var(struct plpks_var *var);
+
+#endif
diff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig
index f7fd91d153a4..6c1ca487103f 100644
--- a/arch/powerpc/platforms/pseries/Kconfig
+++ b/arch/powerpc/platforms/pseries/Kconfig
@@ -142,6 +142,16 @@  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. Select this
+	  config to enable operating system interface to hypervisor to
+	  access this space.
+
 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.o
diff --git a/arch/powerpc/platforms/pseries/plpks/plpks.c b/arch/powerpc/platforms/pseries/plpks/plpks.c
new file mode 100644
index 000000000000..1edb5905bbef
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/plpks/plpks.c
@@ -0,0 +1,517 @@ 
+// 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 <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 PKS_VAR_LINUX	0x01
+#define PKS_VAR_COMMON	0x04
+
+#define MAX_LABEL_ATTR_SIZE 16
+#define MAX_NAME_SIZE 240
+#define MAX_DATA_SIZE 4000
+
+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[];
+} __attribute__ ((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);
+	if (!ospassword)
+		return -ENOMEM;
+
+	ospasswordlength = config->maxpwsize;
+	rc = plpar_hcall(H_PKS_GEN_PASSWORD,
+			retbuf,
+			consumer,
+			0,
+			virt_to_phys(ospassword),
+			config->maxpwsize);
+
+	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 = ospasswordlength;
+
+	memcpy((*auth)->password, ospassword, flex_array_size(*auth, password,
+					      (*auth)->passwordlength));
+	(*auth)->passwordlength = cpu_to_be16((*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 *name, u16 namelen, u8 **label)
+{
+	int varlen;
+	int len = 0;
+	int llen = 0;
+	u8 anyos;
+	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);
+	}
+	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;
+
+	if (component)
+		if (memcmp(component, "sed-opal", 8) == 0)
+			anyos = PKS_VAR_COMMON;
+		else
+			anyos = PKS_VAR_LINUX;
+	else
+		anyos = PKS_VAR_LINUX;
+	memcpy(*label + llen, &anyos, 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(config->rsvd0);
+	config->maxpwsize = be16_to_cpu(config->maxpwsize);
+	config->maxobjlabelsize = be16_to_cpu(config->maxobjlabelsize);
+	config->maxobjsize = be16_to_cpu(config->maxobjsize);
+	config->totalsize = be32_to_cpu(config->totalsize);
+	config->usedspace = be32_to_cpu(config->usedspace);
+	config->supportedpolicies = be32_to_cpu(config->supportedpolicies);
+	config->rsvd1 = be64_to_cpu(config->rsvd1);
+
+	configset = true;
+
+	return 0;
+}
+
+int plpks_signed_update_var(struct plpks_var var)
+{
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+	int rc;
+	u8 *label;
+	u16 varlen;
+	u8 *data;
+	struct plpks_auth *auth;
+	u64 flags;
+	u64 continuetoken;
+
+	if (!var.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.name, var.namelen, &label);
+	if (rc <= 0)
+		goto out;
+
+	varlen =  rc;
+	pr_debug("Name to be written is %s of label size %d\n", label, varlen);
+
+	memcpy(&flags, var.data, sizeof(u64));
+	data = kzalloc(var.datalen - sizeof(u64), GFP_KERNEL);
+	memcpy(data, var.data + sizeof(u64), var.datalen - sizeof(u64));
+
+	do {
+		rc = plpar_hcall(H_PKS_SIGNED_UPDATE,
+				retbuf,
+				virt_to_phys(auth),
+				virt_to_phys(label),
+				varlen,
+				var.policy,
+				flags,
+				virt_to_phys(data),
+				var.datalen);
+
+		continuetoken = retbuf[0];
+
+	} while (rc == H_BUSY);
+
+	rc = pseries_status_to_err(rc);
+
+	kfree(label);
+	kfree(data);
+
+out:
+	kfree(auth);
+
+	return rc;
+}
+EXPORT_SYMBOL(plpks_signed_update_var);
+
+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.name, var.namelen, &label);
+	if (rc <= 0)
+		goto out;
+
+	varlen =  rc;
+	pr_debug("Name to be written is %s of label size %d\n", label, 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);
+
+	rc = pseries_status_to_err(rc);
+	kfree(label);
+
+out:
+	kfree(auth);
+
+	return rc;
+}
+EXPORT_SYMBOL(plpks_write_var);
+
+int plpks_remove_var(char *component, 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, vname.name, vname.namelen, &label);
+	if (rc <= 0)
+		goto out;
+
+	varlen = rc;
+	pr_debug("Name to be written is %s of label size %d\n", label, varlen);
+	rc = plpar_hcall(H_PKS_REMOVE_OBJECT,
+			retbuf,
+			virt_to_phys(auth),
+			virt_to_phys(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;
+	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->name, var->namelen, &label);
+	if (rc <= 0)
+		goto out;
+
+	varlen = rc;
+	pr_debug("Name to be written is %s of label size %d\n", label, 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);
+
+int __init 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;
+		}
+	}
+
+	return rc;
+}
+arch_initcall(pseries_plpks_init);