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

From: Nayna Jain
Date: Fri Jan 21 2022 - 19:57:23 EST


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 <nayna@xxxxxxxxxxxxx>
---
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