Subject: [RFC PATCH 1/2] pseries: define driver for Platform Keystore

From: Nayna Jain <[email-masked]>
Date: Fri, 21 Jan 2022 19:56:36 -0500

PowerVM provides an isolated Platform Keystore(PKS) storage allocation
for each partition 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 PKS driver using H_CALL interface to access PKS storage.

Signed-off-by: Nayna Jain <[email-masked]>
---
 arch/powerpc/include/asm/hvcall.h       |  13 +-
 arch/powerpc/include/asm/pks.h          |  84 ++++
 arch/powerpc/platforms/pseries/Kconfig  |  10 +
 arch/powerpc/platforms/pseries/Makefile |   1 +
 arch/powerpc/platforms/pseries/pks.c    | 494 ++++++++++++++++++++++++
 5 files changed, 601 insertions(+), 1 deletion(-)
 create mode 100644 arch/powerpc/include/asm/pks.h
 create mode 100644 arch/powerpc/platforms/pseries/pks.c

diff --git a/arch/powerpc/include/asm/hvcall.h b/arch/powerpc/include/asm/hvcall.h
index 9bcf345cb208..08108dcf8677 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,9 +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_GET_OBJECT_LABELS 0x428
+#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 MAX_HCALL_OPCODE	H_SCM_FLUSH
+#define H_PKS_SB_SIGNED_UPDATE	0x454
+#define MAX_HCALL_OPCODE	H_PKS_SB_SIGNED_UPDATE
 
 /* Scope args for H_SCM_UNBIND_ALL */
 #define H_UNBIND_SCOPE_ALL (0x1)
diff --git a/arch/powerpc/include/asm/pks.h b/arch/powerpc/include/asm/pks.h
new file mode 100644
index 000000000000..ef6f541d75d3
--- /dev/null
+++ b/arch/powerpc/include/asm/pks.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 IBM Corporation
+ * Author: Nayna Jain
+ *
+ * Platform keystore for pseries.
+ */
+#ifndef _PSERIES_PKS_H
+#define _PSERIES_PKS_H
+
+
+#include <linux/types.h>
+#include <linux/list.h>
+
+struct pks_var {
+	char *prefix;
+	u8 *name;
+	u16 namelen;
+	u32 policy;
+	u16 datalen;
+	u8 *data;
+};
+
+struct pks_var_name {
+	u16 namelen;
+	u8  *name;
+};
+
+struct pks_var_name_list {
+	u32 varcount;
+	struct pks_var_name *varlist;
+};
+
+struct pks_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 pks_config *pks_get_config(void);
+
+/**
+ * Returns all the var names for this prefix.
+ * This only returns name list. If the caller needs data, it has to specifically
+ * call read for the required var name.
+ */
+int pks_get_var_ids_for_type(char *prefix, struct pks_var_name_list *list);
+
+/**
+ * Writes the specified var and its data to PKS.
+ * Any caller of PKS driver should present a valid prefix type for their
+ * variable. This is an exception only for signed variables exposed via
+ * sysfs which do not have any prefixes.
+ * The prefix should always start with '/'. For eg. '/sysfs'.
+ */
+extern int pks_write_var(struct pks_var var);
+
+/**
+ * Writes the specified signed var and its data to PKS.
+ */
+extern int pks_update_signed_var(struct pks_var var);
+
+/**
+ * Removes the specified var and its data from PKS.
+ */
+extern int pks_remove_var(char *prefix, struct pks_var_name vname);
+
+/**
+ * Returns the data for the specified variable.
+ */
+extern int pks_read_var(struct pks_var *var);
+
+#endif
diff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig
index 2e57391e0778..32d0df84e611 100644
--- a/arch/powerpc/platforms/pseries/Kconfig
+++ b/arch/powerpc/platforms/pseries/Kconfig
@@ -147,6 +147,16 @@ config IBMEBUS
 	help
 	  Bus device driver for GX bus based adapters.
 
+config PSERIES_PKS
+	depends on PPC_PSERIES
+	tristate "Support for the Platform Key Storage"
+	help
+	  PowerVM provides an isolated Platform Keystore(PKS) storage
+	  allocation for each partition 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 41d8aee98da4..83eb665a742f 100644
--- a/arch/powerpc/platforms/pseries/Makefile
+++ b/arch/powerpc/platforms/pseries/Makefile
@@ -33,3 +33,4 @@ obj-$(CONFIG_SUSPEND)		+= suspend.o
 obj-$(CONFIG_PPC_VAS)		+= vas.o
 
 obj-$(CONFIG_ARCH_HAS_CC_PLATFORM)	+= cc_platform.o
+obj-$(CONFIG_PSERIES_PKS)      += pks.o
diff --git a/arch/powerpc/platforms/pseries/pks.c b/arch/powerpc/platforms/pseries/pks.c
new file mode 100644
index 000000000000..df9334a4fb89
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/pks.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * POWER platform keystore
+ * Copyright (C) 2010 IBM Corporation
+ *
+ * This pseries platform device driver provides access to
+ * variables stored in platform keystore.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <asm/hvcall.h>
+#include <asm/firmware.h>
+#include <linux/slab.h>
+#include <asm/pks.h>
+#include <asm/unaligned.h>
+#include <asm/machdep.h>
+#include <linux/string.h>
+
+#define MODULE_VERS "1.0"
+#define MODULE_NAME "pseries-pks"
+
+static bool configset;
+static struct pks_config *config;
+
+struct pks_var_name_one {
+	struct pks_var_name var;
+	struct list_head link;
+};
+
+LIST_HEAD(pks_var_name_list);
+
+static u64 labelcount;
+
+struct pks_auth {
+	u8 version;
+	u8 consumer;
+	__be64 rsvd0;
+	__be32 rsvd1;
+	__be16 passwordlength;
+	u8 password[32];
+} __attribute__ ((packed, aligned(16)));
+
+static struct pks_auth auth;
+
+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 pks_gen_password(u8 *password[])
+{
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+	u8 consumer = 0x3;
+	int rc;
+
+	rc = plpar_hcall(H_PKS_GEN_PASSWORD,
+			retbuf,
+			consumer,
+			0,
+			virt_to_phys(*password),
+			config->maxpwsize);
+
+	return pseries_status_to_err(rc);
+}
+
+static int construct_auth(void)
+{
+	int rc = 0;
+	u8 *password;
+
+	auth.version = 1;
+	auth.consumer = 0x3;
+	auth.rsvd0 = 0;
+	auth.rsvd1 = 0;
+	auth.passwordlength = cpu_to_be16(config->maxpwsize);
+	password = kzalloc(config->maxpwsize, GFP_KERNEL);
+	if (!password)
+		return -ENOMEM;
+
+	rc = pks_gen_password(&password);
+	if (rc) {
+		if (rc == H_IN_USE) {
+			rc = 0;
+		} else {
+			pr_err("Failed setting password\n");
+			rc = pseries_status_to_err(rc);
+			goto err;
+		}
+	}
+	memcpy(auth.password, password, config->maxpwsize);
+
+err:
+	kfree(password);
+	return rc;
+}
+
+static bool validate_name(char *name)
+{
+	int i = 0;
+
+	for (i = 0; i < strlen(name); i++) {
+		if (!isalnum(name[i]) && (name[i] != '-')
+				      && (name[i] != '_')) {
+			pr_err("invalid name, should only contain alphanumeric,hyphen(-) or underscore(_)\n");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static int construct_label(char *prefix, u8 *name, u16 namelen, u8 **label)
+{
+	int varlen;
+
+	if (!label)
+		return -EINVAL;
+
+	if (!prefix) {
+		*label = kzalloc(namelen, GFP_KERNEL);
+		if (!*label)
+			return -ENOMEM;
+		memcpy(*label, name, namelen);
+	} else {
+		varlen = strlen(prefix) + namelen + 1;
+		*label = kzalloc(varlen, GFP_KERNEL);
+		if (!*label)
+			return -ENOMEM;
+
+		memcpy(*label, prefix, strlen(prefix));
+		(*label)[strlen(prefix)] = '/';
+		memcpy(*label + strlen(prefix) + 1, name, namelen);
+	}
+
+	return 0;
+}
+
+static int _pks_get_config(void)
+{
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+	int rc;
+	size_t size = sizeof(struct pks_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 rc;
+}
+
+static int get_objectlabels(void)
+{
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+	int rc = 0;
+	u16 bufsize = 1024;
+	u8 buf[1024];
+	int i;
+	int index;
+	u16 labelsize = 0;
+	u64 continuetoken = 0;
+	u64 count;
+	struct pks_var_name_one *vname = NULL;
+
+	do {
+		rc = plpar_hcall(H_PKS_GET_OBJECT_LABELS,
+				retbuf,
+				virt_to_phys(&auth),
+				continuetoken,
+				virt_to_phys(buf),
+				bufsize);
+
+		if (rc) {
+			rc = pseries_status_to_err(rc);
+			goto err;
+		}
+
+		count =  retbuf[0];
+		continuetoken = retbuf[1];
+		index = 0;
+		for (i = 0; i < count; i++) {
+			labelsize = be16_to_cpu(*(__be16 *)(&buf[index]));
+			vname = kzalloc(sizeof(struct pks_var_name_one),
+					GFP_KERNEL);
+			vname->var.namelen = labelsize;
+			vname->var.name = kzalloc(labelsize, GFP_KERNEL);
+			if (!vname->var.name) {
+				rc = -ENOMEM;
+				goto err;
+			}
+			index = index + 2;
+			memcpy(vname->var.name, buf + index, labelsize);
+			list_add(&vname->link, &pks_var_name_list);
+			index =  index + labelsize;
+		}
+		labelcount = labelcount + count;
+		pr_info("Total number of variables are %llu\n", labelcount);
+	} while (continuetoken != 0);
+err:
+	return rc;
+}
+
+int pks_get_var_ids_for_type(char *prefix, struct pks_var_name_list *list)
+{
+	int count = 0;
+	int idx = 0;
+	struct pks_var_name_one *vname = NULL;
+	u8 *name;
+	u16 namelen;
+
+	list_for_each_entry(vname, &pks_var_name_list, link) {
+		name = vname->var.name;
+		if (((!prefix) && (name[0] == '/'))
+		   || (prefix && (strncmp(name, prefix, strlen(prefix)))))
+			continue;
+		count++;
+	}
+
+	list->varcount = count;
+	list->varlist = kcalloc(count, sizeof(list->varlist), GFP_KERNEL);
+	if (!list->varlist)
+		return -ENOMEM;
+
+	list_for_each_entry(vname, &pks_var_name_list, link) {
+		name = (char *)vname->var.name;
+		if (((!prefix) && (name[0] == '/'))
+		   || (prefix && (strncmp(name, prefix, strlen(prefix)))))
+			continue;
+
+		if (!prefix)
+			namelen = vname->var.namelen;
+		else {
+			name = name + strlen(prefix) + 1;
+			namelen = strlen(name) + 1;
+		}
+		pr_debug("var is %s of size %d\n", name, namelen);
+
+		list->varlist[idx].namelen = namelen;
+		list->varlist[idx].name = kzalloc(namelen, GFP_KERNEL);
+		memcpy(list->varlist[idx].name, name, namelen);
+		idx++;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(pks_get_var_ids_for_type);
+
+int pks_update_signed_var(struct pks_var var)
+{
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+	int rc;
+	u8 *label;
+	u16 varlen;
+	u8 *data = var.data;
+
+	if (var.prefix)
+		return -EINVAL;
+
+	if (!validate_name(var.name))
+		return -EINVAL;
+
+	rc = construct_label(var.prefix, var.name, var.namelen, &label);
+	if (rc)
+		return rc;
+
+	pr_info("Label to be written is %s of size %d\n", label, varlen);
+	varlen = strlen(label) + 1;
+	rc = plpar_hcall(H_PKS_SB_SIGNED_UPDATE,
+			retbuf,
+			virt_to_phys(&auth),
+			virt_to_phys(label),
+			varlen,
+			var.policy,
+			virt_to_phys(data),
+			var.datalen);
+
+	kfree(label);
+
+	return pseries_status_to_err(rc);
+}
+EXPORT_SYMBOL(pks_update_signed_var);
+
+int pks_write_var(struct pks_var var)
+{
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+	int rc;
+	u8 *label;
+	u16 varlen;
+	u8 *data = var.data;
+
+	if ((!var.prefix) || (var.prefix[0] != '/'))
+		return -EINVAL;
+
+	if (!validate_name(var.name))
+		return -EINVAL;
+
+	rc = construct_label(var.prefix, var.name, var.namelen, &label);
+	if (rc)
+		return rc;
+
+	pr_info("Label to be written is %s of size %d\n", label, varlen);
+	varlen = strlen(label) + 1;
+	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);
+
+	kfree(label);
+
+	return pseries_status_to_err(rc);
+}
+EXPORT_SYMBOL(pks_write_var);
+
+int pks_remove_var(char *prefix, struct pks_var_name vname)
+{
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+	int rc;
+	u8 *label;
+	u16 varlen;
+
+	rc = construct_label(prefix, vname.name, vname.namelen, &label);
+	if (rc)
+		return rc;
+
+	varlen = strlen(label) + 1;
+	pr_info("Label to be removed is %s of size %d\n", label, varlen);
+	rc = plpar_hcall(H_PKS_REMOVE_OBJECT,
+			retbuf,
+			virt_to_phys(&auth),
+			virt_to_phys(label),
+			varlen);
+
+	kfree(label);
+
+	return pseries_status_to_err(rc);
+}
+EXPORT_SYMBOL(pks_remove_var);
+
+
+int pks_read_var(struct pks_var *var)
+{
+	unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = {0};
+	int rc;
+	u16 outlen = config->maxobjsize;
+	u8 *label;
+	u8 *out;
+	u16 varlen;
+
+	rc = construct_label(var->prefix, var->name, var->namelen, &label);
+	if (rc)
+		return rc;
+
+	varlen = strlen(label) + 1;
+	pr_info("Label to be read %s of size %d\n", label, varlen);
+	out = kzalloc(outlen, GFP_KERNEL);
+	if (!out)
+		return -ENOMEM;
+
+	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 err;
+	}
+
+	var->datalen = retbuf[0];
+	var->policy = retbuf[1];
+
+	var->data = kzalloc(var->datalen, GFP_KERNEL);
+	if (!var->data) {
+		rc = -ENOMEM;
+		goto err;
+	}
+
+	memcpy(var->data, out, var->datalen);
+err:
+	kfree(out);
+	kfree(label);
+
+	return rc;
+}
+EXPORT_SYMBOL(pks_read_var);
+
+struct pks_config *pks_get_config(void)
+{
+
+	if (!configset) {
+		if (_pks_get_config())
+			return NULL;
+	}
+
+	return config;
+}
+EXPORT_SYMBOL(pks_get_config);
+
+int __init pseries_pks_init(void)
+{
+	int rc = 0;
+	struct pks_var_name_one *vname = NULL;
+
+	rc = _pks_get_config();
+
+	if (rc) {
+		pr_err("Error initializing pks\n");
+		return rc;
+	}
+
+	rc = construct_auth();
+	if (rc)
+		return rc;
+
+	rc = get_objectlabels();
+	if (rc) {
+		pr_err("Getting object labels failed. Error initializing pks\n");
+		return rc;
+	}
+
+	list_for_each_entry(vname, &pks_var_name_list, link)
+		pr_info("name is %s\n", vname->var.name);
+
+	return rc;
+}
+arch_initcall(pseries_pks_init);
-- 
2.27.0