powerpc/pseries: define driver for Platform KeyStore
authorNayna Jain <nayna@linux.ibm.com>
Sat, 23 Jul 2022 11:30:46 +0000 (07:30 -0400)
committerMichael Ellerman <mpe@ellerman.id.au>
Thu, 28 Jul 2022 06:22:13 +0000 (16:22 +1000)
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 POWER LPAR Platform KeyStore(PLPKS) driver using H_CALL interface
to access PKS storage.

Signed-off-by: Nayna Jain <nayna@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20220723113048.521744-2-nayna@linux.ibm.com
arch/powerpc/include/asm/hvcall.h
arch/powerpc/platforms/pseries/Kconfig
arch/powerpc/platforms/pseries/Makefile
arch/powerpc/platforms/pseries/plpks.c [new file with mode: 0644]
arch/powerpc/platforms/pseries/plpks.h [new file with mode: 0644]

index 4457abe31e6e198d530bd019bff94878a0de7806..8abae463f6c12882fc85978d837011d96425d363 100644 (file)
@@ -79,6 +79,7 @@
 #define H_NOT_ENOUGH_RESOURCES -44
 #define H_R_STATE       -45
 #define H_RESCINDED     -46
+#define H_P1           -54
 #define H_P2           -55
 #define H_P3           -56
 #define H_P4           -57
@@ -98,6 +99,8 @@
 #define H_OP_MODE      -73
 #define H_COP_HW       -74
 #define H_STATE                -75
+#define H_IN_USE       -77
+#define H_ABORTED      -78
 #define H_UNSUPPORTED_FLAG_START       -256
 #define H_UNSUPPORTED_FLAG_END         -511
 #define H_MULTI_THREADS_ACTIVE -9005
 #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
index f7fd91d153a4524be93cfbd2865feec6317fbcca..c4a6d4083a7ae5a2e0a201549d8b03a50fe260cf 100644 (file)
@@ -142,6 +142,19 @@ config IBMEBUS
        help
          Bus device driver for GX bus based adapters.
 
+config PSERIES_PLPKS
+       depends on PPC_PSERIES
+       bool "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"
index 7aaff5323544261b231c4a3a26efa57747d2d4ae..14e143b946a36822c260674cc9d26ab9fcc92d61 100644 (file)
@@ -28,6 +28,7 @@ obj-$(CONFIG_PAPR_SCM)                += papr_scm.o
 obj-$(CONFIG_PPC_SPLPAR)       += vphn.o
 obj-$(CONFIG_PPC_SVM)          += svm.o
 obj-$(CONFIG_FA_DUMP)          += rtas-fadump.o
+obj-$(CONFIG_PSERIES_PLPKS) += plpks.o
 
 obj-$(CONFIG_SUSPEND)          += suspend.o
 obj-$(CONFIG_PPC_VAS)          += vas.o vas-sysfs.o
diff --git a/arch/powerpc/platforms/pseries/plpks.c b/arch/powerpc/platforms/pseries/plpks.c
new file mode 100644 (file)
index 0000000..52aaa28
--- /dev/null
@@ -0,0 +1,460 @@
+// 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).
+ */
+
+#define pr_fmt(fmt) "plpks: " fmt
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <asm/hvcall.h>
+
+#include "plpks.h"
+
+#define PKS_FW_OWNER        0x1
+#define PKS_BOOTLOADER_OWNER 0x2
+#define PKS_OS_OWNER        0x3
+
+#define LABEL_VERSION      0
+#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 u8 *ospassword;
+static u16 ospasswordlength;
+
+// Retrieved with H_PKS_GET_CONFIG
+static u16 maxpwsize;
+static u16 maxobjsize;
+
+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];
+       size_t 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_P1:
+       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;
+       case H_STATE:
+               err = -EIO;
+               break;
+       case H_R_STATE:
+               err = -EIO;
+               break;
+       case H_IN_USE:
+               err = -EEXIST;
+               break;
+       case H_ABORTED:
+               err = -EINTR;
+               break;
+       default:
+               err = -EINVAL;
+       }
+
+       return err;
+}
+
+static int plpks_gen_password(void)
+{
+       unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = { 0 };
+       u8 *password, consumer = PKS_OS_OWNER;
+       int rc;
+
+       password = kzalloc(maxpwsize, GFP_KERNEL);
+       if (!password)
+               return -ENOMEM;
+
+       rc = plpar_hcall(H_PKS_GEN_PASSWORD, retbuf, consumer, 0,
+                        virt_to_phys(password), maxpwsize);
+
+       if (!rc) {
+               ospasswordlength = maxpwsize;
+               ospassword = kzalloc(maxpwsize, GFP_KERNEL);
+               if (!ospassword) {
+                       kfree(password);
+                       return -ENOMEM;
+               }
+               memcpy(ospassword, password, ospasswordlength);
+       } else {
+               if (rc == H_IN_USE) {
+                       pr_warn("Password is already set for POWER LPAR Platform KeyStore\n");
+                       rc = 0;
+               } else {
+                       goto out;
+               }
+       }
+out:
+       kfree(password);
+
+       return pseries_status_to_err(rc);
+}
+
+static struct plpks_auth *construct_auth(u8 consumer)
+{
+       struct plpks_auth *auth;
+
+       if (consumer > PKS_OS_OWNER)
+               return ERR_PTR(-EINVAL);
+
+       auth = kmalloc(struct_size(auth, password, maxpwsize), GFP_KERNEL);
+       if (!auth)
+               return ERR_PTR(-ENOMEM);
+
+       auth->version = 1;
+       auth->consumer = consumer;
+       auth->rsvd0 = 0;
+       auth->rsvd1 = 0;
+
+       if (consumer == PKS_FW_OWNER || consumer == PKS_BOOTLOADER_OWNER) {
+               auth->passwordlength = 0;
+               return auth;
+       }
+
+       memcpy(auth->password, ospassword, ospasswordlength);
+
+       auth->passwordlength = cpu_to_be16(ospasswordlength);
+
+       return auth;
+}
+
+/**
+ * Label is combination of label attributes + name.
+ * Label attributes are used internally by kernel and not exposed to the user.
+ */
+static struct label *construct_label(char *component, u8 varos, u8 *name,
+                                    u16 namelen)
+{
+       struct label *label;
+       size_t slen;
+
+       if (!name || namelen > MAX_NAME_SIZE)
+               return ERR_PTR(-EINVAL);
+
+       slen = strlen(component);
+       if (component && slen > sizeof(label->attr.prefix))
+               return ERR_PTR(-EINVAL);
+
+       label = kzalloc(sizeof(*label), GFP_KERNEL);
+       if (!label)
+               return ERR_PTR(-ENOMEM);
+
+       if (component)
+               memcpy(&label->attr.prefix, component, slen);
+
+       label->attr.version = LABEL_VERSION;
+       label->attr.os = varos;
+       label->attr.length = MAX_LABEL_ATTR_SIZE;
+       memcpy(&label->name, name, namelen);
+
+       label->size = sizeof(struct label_attr) + namelen;
+
+       return label;
+}
+
+static int _plpks_get_config(void)
+{
+       unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = { 0 };
+       struct {
+               u8 version;
+               u8 flags;
+               __be32 rsvd0;
+               __be16 maxpwsize;
+               __be16 maxobjlabelsize;
+               __be16 maxobjsize;
+               __be32 totalsize;
+               __be32 usedspace;
+               __be32 supportedpolicies;
+               __be64 rsvd1;
+       } __packed config;
+       size_t size;
+       int rc;
+
+       size = sizeof(config);
+
+       rc = plpar_hcall(H_PKS_GET_CONFIG, retbuf, virt_to_phys(&config), size);
+
+       if (rc != H_SUCCESS)
+               return pseries_status_to_err(rc);
+
+       maxpwsize = be16_to_cpu(config.maxpwsize);
+       maxobjsize = be16_to_cpu(config.maxobjsize);
+
+       return 0;
+}
+
+static int plpks_confirm_object_flushed(struct label *label,
+                                       struct plpks_auth *auth)
+{
+       unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = { 0 };
+       u64 timeout = 0;
+       u8 status;
+       int rc;
+
+       do {
+               rc = plpar_hcall(H_PKS_CONFIRM_OBJECT_FLUSHED, retbuf,
+                                virt_to_phys(auth), virt_to_phys(label),
+                                label->size);
+
+               status = retbuf[0];
+               if (rc) {
+                       if (rc == H_NOT_FOUND && status == 1)
+                               rc = 0;
+                       break;
+               }
+
+               if (!rc && status == 1)
+                       break;
+
+               usleep_range(PKS_FLUSH_SLEEP,
+                            PKS_FLUSH_SLEEP + PKS_FLUSH_SLEEP_RANGE);
+               timeout = timeout + PKS_FLUSH_SLEEP;
+       } while (timeout < PKS_FLUSH_MAX_TIMEOUT);
+
+       rc = pseries_status_to_err(rc);
+
+       return rc;
+}
+
+int plpks_write_var(struct plpks_var var)
+{
+       unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = { 0 };
+       struct plpks_auth *auth;
+       struct label *label;
+       int rc;
+
+       if (!var.component || !var.data || var.datalen <= 0 ||
+           var.namelen > MAX_NAME_SIZE || var.datalen > MAX_DATA_SIZE)
+               return -EINVAL;
+
+       if (var.policy & SIGNEDUPDATE)
+               return -EINVAL;
+
+       auth = construct_auth(PKS_OS_OWNER);
+       if (IS_ERR(auth))
+               return PTR_ERR(auth);
+
+       label = construct_label(var.component, var.os, var.name, var.namelen);
+       if (IS_ERR(label)) {
+               rc = PTR_ERR(label);
+               goto out;
+       }
+
+       rc = plpar_hcall(H_PKS_WRITE_OBJECT, retbuf, virt_to_phys(auth),
+                        virt_to_phys(label), label->size, var.policy,
+                        virt_to_phys(var.data), var.datalen);
+
+       if (!rc)
+               rc = plpks_confirm_object_flushed(label, auth);
+
+       if (rc)
+               pr_err("Failed to write variable %s for component %s with error %d\n",
+                      var.name, var.component, rc);
+
+       rc = pseries_status_to_err(rc);
+       kfree(label);
+out:
+       kfree(auth);
+
+       return rc;
+}
+
+int plpks_remove_var(char *component, u8 varos, struct plpks_var_name vname)
+{
+       unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = { 0 };
+       struct plpks_auth *auth;
+       struct label *label;
+       int rc;
+
+       if (!component || vname.namelen > MAX_NAME_SIZE)
+               return -EINVAL;
+
+       auth = construct_auth(PKS_OS_OWNER);
+       if (IS_ERR(auth))
+               return PTR_ERR(auth);
+
+       label = construct_label(component, varos, vname.name, vname.namelen);
+       if (IS_ERR(label)) {
+               rc = PTR_ERR(label);
+               goto out;
+       }
+
+       rc = plpar_hcall(H_PKS_REMOVE_OBJECT, retbuf, virt_to_phys(auth),
+                        virt_to_phys(label), label->size);
+
+       if (!rc)
+               rc = plpks_confirm_object_flushed(label, auth);
+
+       if (rc)
+               pr_err("Failed to remove variable %s for component %s with error %d\n",
+                      vname.name, component, rc);
+
+       rc = pseries_status_to_err(rc);
+       kfree(label);
+out:
+       kfree(auth);
+
+       return rc;
+}
+
+static int plpks_read_var(u8 consumer, struct plpks_var *var)
+{
+       unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = { 0 };
+       struct plpks_auth *auth;
+       struct label *label;
+       u8 *output;
+       int rc;
+
+       if (var->namelen > MAX_NAME_SIZE)
+               return -EINVAL;
+
+       auth = construct_auth(PKS_OS_OWNER);
+       if (IS_ERR(auth))
+               return PTR_ERR(auth);
+
+       label = construct_label(var->component, var->os, var->name,
+                               var->namelen);
+       if (IS_ERR(label)) {
+               rc = PTR_ERR(label);
+               goto out_free_auth;
+       }
+
+       output = kzalloc(maxobjsize, GFP_KERNEL);
+       if (!output) {
+               rc = -ENOMEM;
+               goto out_free_label;
+       }
+
+       rc = plpar_hcall(H_PKS_READ_OBJECT, retbuf, virt_to_phys(auth),
+                        virt_to_phys(label), label->size, virt_to_phys(output),
+                        maxobjsize);
+
+       if (rc != H_SUCCESS) {
+               pr_err("Failed to read variable %s for component %s with error %d\n",
+                      var->name, var->component, rc);
+               rc = pseries_status_to_err(rc);
+               goto out_free_output;
+       }
+
+       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 out_free_output;
+       }
+       var->policy = retbuf[1];
+
+       memcpy(var->data, output, var->datalen);
+       rc = 0;
+
+out_free_output:
+       kfree(output);
+out_free_label:
+       kfree(label);
+out_free_auth:
+       kfree(auth);
+
+       return rc;
+}
+
+int plpks_read_os_var(struct plpks_var *var)
+{
+       return plpks_read_var(PKS_OS_OWNER, var);
+}
+
+int plpks_read_fw_var(struct plpks_var *var)
+{
+       return plpks_read_var(PKS_FW_OWNER, var);
+}
+
+int plpks_read_bootloader_var(struct plpks_var *var)
+{
+       return plpks_read_var(PKS_BOOTLOADER_OWNER, var);
+}
+
+static __init int pseries_plpks_init(void)
+{
+       int rc;
+
+       rc = _plpks_get_config();
+
+       if (rc) {
+               pr_err("POWER LPAR Platform KeyStore is not supported or enabled\n");
+               return rc;
+       }
+
+       rc = plpks_gen_password();
+       if (rc)
+               pr_err("Failed setting POWER LPAR Platform KeyStore Password\n");
+       else
+               pr_info("POWER LPAR Platform KeyStore initialized successfully\n");
+
+       return rc;
+}
+arch_initcall(pseries_plpks_init);
diff --git a/arch/powerpc/platforms/pseries/plpks.h b/arch/powerpc/platforms/pseries/plpks.h
new file mode 100644 (file)
index 0000000..c6a2913
--- /dev/null
@@ -0,0 +1,71 @@
+/* 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 *name;
+       u8 *data;
+       u32 policy;
+       u16 namelen;
+       u16 datalen;
+       u8 os;
+};
+
+struct plpks_var_name {
+       u8  *name;
+       u16 namelen;
+};
+
+struct plpks_var_name_list {
+       u32 varcount;
+       struct plpks_var_name varlist[];
+};
+
+/**
+ * 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);
+
+#endif