Subject: [RFC PATCH 2/2] pseries: define sysfs interface to expose PKS variables

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

PowerVM guest secure boot intend to use Platform Keystore(PKS) for the
purpose of storing public keys to verify digital signature.

Define sysfs interface to expose PKS variables to userspace to allow
read/write/add/delete operations. Each variable is shown as a read/write
attribute file. The size of the file represents the size of the current
content of the variable.

create_var and delete_var attribute files are always present which allow
users to create/delete variables. These are write only attributes.The
design has tried to be compliant with sysfs semantic to represent single
value per attribute. Thus, rather than mapping a complete data structure
representation to create_var, it only accepts a single formatted string
to create an empty variable.

The sysfs interface is designed such as to expose PKS configuration
properties, operating system variables and firmware variables.
Current version exposes configuration and operating system variables.
The support for exposing firmware variables will be added in the future
version.

Example of pksvar sysfs interface:

# cd /sys/firmware/pksvar/
# ls
config  os

# cd config

# ls -ltrh
total 0
-r--r--r-- 1 root root 64K Jan 21 17:55 version
-r--r--r-- 1 root root 64K Jan 21 17:55 used_space
-r--r--r-- 1 root root 64K Jan 21 17:55 total_size
-r--r--r-- 1 root root 64K Jan 21 17:55 supported_policies
-r--r--r-- 1 root root 64K Jan 21 17:55 max_object_size
-r--r--r-- 1 root root 64K Jan 21 17:55 max_object_label_size
-r--r--r-- 1 root root 64K Jan 21 17:55 flags

# cd os

# ls -ltrh
total 0
-rw------- 1 root root 104 Jan 21 17:56 var4
-rw------- 1 root root 104 Jan 21 17:56 var3
-rw------- 1 root root 831 Jan 21 17:56 GLOBAL_PK
-rw------- 1 root root 831 Jan 21 17:56 GLOBAL_KEK
-rw------- 1 root root  76 Jan 21 17:56 GLOBAL_dbx
-rw------- 1 root root 831 Jan 21 17:56 GLOBAL_db
--w------- 1 root root 64K Jan 21 17:56 delete_var
--w------- 1 root root 64K Jan 21 17:56 create_var

1. Read variable

# hexdump -C GLOBAL_db
00000000  00 00 00 00 a1 59 c0 a5  e4 94 a7 4a 87 b5 ab 15  |.....Y.....J....|
00000010  5c 2b f0 72 3f 03 00 00  00 00 00 00 23 03 00 00  |\+.r?.......#...|
....
00000330  02 a8 e8 ed 0f 20 60 3f  40 04 7c a8 91 21 37 eb  |..... `?@.|..!7.|
00000340  f3 f1 4e                                          |..N|
00000343

2. Write variable

cat /tmp/data.bin > <variable_name>

3. Create variable

# echo "var1" > create_var
# ls -ltrh
total 0
-rw------- 1 root root 104 Jan 21 17:56 var4
-rw------- 1 root root 104 Jan 21 17:56 var3
-rw------- 1 root root 831 Jan 21 17:56 GLOBAL_PK
-rw------- 1 root root 831 Jan 21 17:56 GLOBAL_KEK
-rw------- 1 root root  76 Jan 21 17:56 GLOBAL_dbx
-rw------- 1 root root 831 Jan 21 17:56 GLOBAL_db
--w------- 1 root root 64K Jan 21 17:56 delete_var
--w------- 1 root root 64K Jan 21 17:57 create_var
-rw------- 1 root root   0 Jan 21 17:57 var1.tmp

Current design creates a zero size temporary variable. This implies
it is not yet persisted to PKS. Only once data is written to newly
created temporary variable and if it is successfully stored in the
PKS, that the variable is permanent. The temporary variable will get
removed on reboot. The code currently doesn't remove .tmp suffix
immediately when persisted. The future version will fix this.

To avoid the additional .tmp semantic, alternative option is to consider
any zero size variable as temporary variable. This option is under
evaluation. This would avoid any runtime sysfs magic to replace .tmp
variable with real variable.

Also, the formatted string to pass to create_var will have following
format in the future version:
<variable_name>:<comma-separated-policy strings>

4. Delete variable
# echo "var3" > delete_var
# ls -ltrh
total 0
-rw------- 1 root root 104 Jan 21 17:56 var4
-rw------- 1 root root 831 Jan 21 17:56 GLOBAL_PK
-rw------- 1 root root 831 Jan 21 17:56 GLOBAL_KEK
-rw------- 1 root root  76 Jan 21 17:56 GLOBAL_dbx
-rw------- 1 root root 831 Jan 21 17:56 GLOBAL_db
--w------- 1 root root 64K Jan 21 17:57 create_var
-rw------- 1 root root   0 Jan 21 17:57 var1.tmp
--w------- 1 root root 64K Jan 21 17:58 delete_var

The var3 file is removed at runtime, if variable is successfully
removed from the PKS storage.

NOTE: We are evaluating two design for userspace interface: using the
sysfs or defining a new filesystem based. Any feedback on this sysfs based
approach would be highly appreciated. We have tried to follow one value
per attribute semantic. If that or any other semantics aren't followed
properly, please let us know.

Signed-off-by: Nayna Jain <[email-masked]>
---
 Documentation/ABI/testing/sysfs-pksvar        |  77 ++++
 arch/powerpc/platforms/pseries/Kconfig        |   7 +
 arch/powerpc/platforms/pseries/Makefile       |   1 +
 arch/powerpc/platforms/pseries/pksvar-sysfs.c | 356 ++++++++++++++++++
 4 files changed, 441 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-pksvar
 create mode 100644 arch/powerpc/platforms/pseries/pksvar-sysfs.c

diff --git a/Documentation/ABI/testing/sysfs-pksvar b/Documentation/ABI/testing/sysfs-pksvar
new file mode 100644
index 000000000000..fe5327f5bd70
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-pksvar
@@ -0,0 +1,77 @@
+What:		/sys/firmware/pksvar
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:	This directory is created if the POWER firmware supports
+		Platform Keystore (PKS). It exposes interface for reading/writing
+		variables which are persisted in PKS.
+
+What:		/sys/firmware/pksvar/config
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:	This directory lists the configuration properties of Platform
+		Keystore.
+
+What:		/sys/firmware/pksvar/os
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:	This directory lists all the variables owned by operating system
+		users and stored in Platform Keystore.
+
+What:		/sys/firmware/pksvar/config/version
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:	A value indicating version of Firmware Platform Keystore.
+
+What:		/sys/firmware/pksvar/config/used_space
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:	The value specifying amount of space used in Platform Keystore.
+
+What:		/sys/firmware/pksvar/config/total_size
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:	The value specifying total size of Platform Keystore.
+
+What:		/sys/firmware/pksvar/config/supported_policies
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:	The value specifying policies supported by Platform Keystore semantics.
+
+What:		/sys/firmware/pksvar/config/max_object_size
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:	The value specifies the maximum object size that can be supported.
+
+What:		/sys/firmware/pksvar/config/max_object_label_size
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:    The value specifies the maximum object label size that is supported.
+
+What:		/sys/firmware/pksvar/config/flags
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:    The value specifies the flags supported by Platform Keystore.
+
+What:		/sys/firmware/pksvar/os/<attribute_file>
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:    A read-write file indicating the variable stored in the Platform Keystore.
+		The size of the file represents the size of the actual data stored for
+		this variable in PKS.
+
+What:		/sys/firmware/pksvar/os/create_var
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:    A write only file used to create new variables. The user should write
+		formatted string containing name to this file. It creates at runtime
+		zero size read-write <variable-name>.tmp temporary attribute file. The
+		temporary variable name is not persisted to Platform Keystore until
+		data is updated to it.
+
+What:		/sys/firmware/pksvar/os/delete_var
+Date:		August 2022
+Contact:	Nayna Jain <[email-masked]>
+Description:    A write only file used to delete existing variable. The user should
+		write variable name to this file. If the variable is successfully
+		deleted from Platform Keystore, the corresponding attribute file is
+		also removed at runtime.
diff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig
index 32d0df84e611..9310876d201d 100644
--- a/arch/powerpc/platforms/pseries/Kconfig
+++ b/arch/powerpc/platforms/pseries/Kconfig
@@ -157,6 +157,13 @@ config PSERIES_PKS
 	  this config to enable operating system interface to hypervisor to
 	  access this space.
 
+config PSERIES_PKS_SYSFS
+	depends on PSERIES_PKS
+	tristate "Support for sysfs interface the Platform Key Storage"
+	help
+	  Enable sysfs based user interace to add/delete/modify variables
+	  stored in Platform Keystore.
+
 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 83eb665a742f..27f9aafbd08b 100644
--- a/arch/powerpc/platforms/pseries/Makefile
+++ b/arch/powerpc/platforms/pseries/Makefile
@@ -34,3 +34,4 @@ obj-$(CONFIG_PPC_VAS)		+= vas.o
 
 obj-$(CONFIG_ARCH_HAS_CC_PLATFORM)	+= cc_platform.o
 obj-$(CONFIG_PSERIES_PKS)      += pks.o
+obj-$(CONFIG_PSERIES_PKS_SYSFS) += pksvar-sysfs.o
diff --git a/arch/powerpc/platforms/pseries/pksvar-sysfs.c b/arch/powerpc/platforms/pseries/pksvar-sysfs.c
new file mode 100644
index 000000000000..2e53daaf6e9f
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/pksvar-sysfs.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 IBM Corporation <[email-masked]>
+ *
+ * This code exposes variables stored in Platform Keystore via sysfs
+ */
+
+#define pr_fmt(fmt) "pksvar-sysfs: " fmt
+
+#include <linux/slab.h>
+#include <linux/compat.h>
+#include <linux/string.h>
+#include <linux/of.h>
+#include <asm/pks.h>
+
+static struct kobject *pks_kobj;
+static struct kobject *prop_kobj;
+static struct kobject *os_kobj;
+
+static struct pks_config *config;
+
+struct osvar_sysfs_attr {
+	struct bin_attribute bin_attr;
+	struct list_head node;
+};
+
+static LIST_HEAD(osvar_sysfs_list);
+
+
+static ssize_t osvar_sysfs_read(struct file *file, struct kobject *kobj,
+				struct bin_attribute *bin_attr, char *buf,
+				loff_t off, size_t count)
+{
+	struct pks_var var;
+	char *out;
+	u32 outlen;
+	int rc;
+
+	var.name = (char *)bin_attr->attr.name;
+	var.namelen = strlen(var.name) + 1;
+	var.prefix = NULL;
+	rc = pks_read_var(&var);
+	if (rc) {
+		pr_err("Error reading object %d\n", rc);
+		return rc;
+	}
+
+	outlen = sizeof(var.policy) + var.datalen;
+	out = kzalloc(outlen, GFP_KERNEL);
+	memcpy(out, &var.policy, sizeof(var.policy));
+	memcpy(out + sizeof(var.policy), var.data, var.datalen);
+
+	count = outlen;
+	memcpy(buf, out, outlen);
+
+	kfree(out);
+	return count;
+}
+
+static ssize_t osvar_sysfs_write(struct file *file, struct kobject *kobj,
+				 struct bin_attribute *bin_attr, char *buf,
+				 loff_t off, size_t count)
+{
+	struct pks_var *var = NULL;
+	int rc = 0;
+	char *p;
+	char *name = (char *)bin_attr->attr.name;
+	struct osvar_sysfs_attr *osvar_sysfs = NULL;
+
+	list_for_each_entry(osvar_sysfs, &osvar_sysfs_list, node) {
+		if (strncmp(name, osvar_sysfs->bin_attr.attr.name,
+			    strlen(name)) == 0) {
+			var = osvar_sysfs->bin_attr.private;
+			break;
+		}
+	}
+
+	p = strsep(&name, ".");
+
+	var->datalen = count;
+	var->data = kzalloc(count, GFP_KERNEL);
+	if (!var->data)
+		return -ENOMEM;
+
+	memcpy(var->data, buf, count);
+	var->name = p;
+	var->namelen = strlen(p) + 1;
+
+	pr_info("var %s of length %d to be written\n", var->name, var->namelen);
+	var->prefix = NULL;
+	rc = pks_update_signed_var(*var);
+
+	if (rc) {
+		pr_err(" write failed with rc is %d\n", rc);
+		var->datalen = 0;
+		count = rc;
+		goto err;
+	}
+
+err:
+	kfree(var->data);
+	return count;
+}
+
+
+
+static ssize_t version_show(struct kobject *kobj, struct kobj_attribute *attr,
+			    char *buf)
+{
+	return sprintf(buf, "%d\n", config->version);
+}
+
+static ssize_t flags_show(struct kobject *kobj, struct kobj_attribute *attr,
+			  char *buf)
+{
+	return sprintf(buf, "%02x\n", config->flags);
+}
+
+static ssize_t max_object_label_size_show(struct kobject *kobj,
+					  struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", config->maxobjlabelsize);
+}
+
+static ssize_t max_object_size_show(struct kobject *kobj,
+				    struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", config->maxobjsize);
+}
+
+static ssize_t total_size_show(struct kobject *kobj,
+			       struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", config->totalsize);
+}
+
+static ssize_t used_space_show(struct kobject *kobj,
+			       struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", config->usedspace);
+}
+
+static ssize_t supported_policies_show(struct kobject *kobj,
+				       struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", config->supportedpolicies);
+}
+
+static ssize_t create_var_store(struct kobject *kobj,
+				struct kobj_attribute *attr,
+				const char *buf, size_t count)
+{
+	int rc;
+	struct pks_var *var;
+	char *suffix = ".tmp";
+	char *name;
+	u16 namelen = 0;
+	struct osvar_sysfs_attr *osvar_sysfs = NULL;
+
+	namelen = count + strlen(suffix);
+	name = kzalloc(namelen, GFP_KERNEL);
+	if (!name)
+		return -ENOMEM;
+	memcpy(name, buf, count-1);
+	memcpy(name + (count-1), suffix, strlen(suffix));
+	name[namelen] = '\0';
+
+	pr_debug("var %s of length %d to be added\n", name, namelen);
+
+	osvar_sysfs = kzalloc(sizeof(struct osvar_sysfs_attr), GFP_KERNEL);
+	if (!osvar_sysfs) {
+		rc = -ENOMEM;
+		goto err;
+	}
+
+	var = kzalloc(sizeof(struct pks_var), GFP_KERNEL);
+
+	if (!var) {
+		rc = -ENOMEM;
+		goto err;
+	}
+
+	var->name = name;
+	var->namelen = namelen;
+	var->prefix = NULL;
+	var->policy = 0;
+
+	sysfs_bin_attr_init(&osvar_sysfs->bin_attr);
+	osvar_sysfs->bin_attr.private = var;
+	osvar_sysfs->bin_attr.attr.name = name;
+	osvar_sysfs->bin_attr.attr.mode = 0600;
+	osvar_sysfs->bin_attr.size = 0;
+	osvar_sysfs->bin_attr.read = osvar_sysfs_read;
+	osvar_sysfs->bin_attr.write = osvar_sysfs_write;
+
+	rc = sysfs_create_bin_file(os_kobj,
+			&osvar_sysfs->bin_attr);
+	if (rc)
+		goto err;
+
+	list_add_tail(&osvar_sysfs->node, &osvar_sysfs_list);
+	rc = count;
+err:
+	return rc;
+}
+
+static ssize_t delete_var_store(struct kobject *kobj,
+				struct kobj_attribute *attr,
+				const char *buf, size_t count)
+{
+	int rc;
+	struct pks_var_name vname;
+	struct osvar_sysfs_attr *osvar_sysfs = NULL;
+
+	vname.name = kzalloc(count, GFP_KERNEL);
+	if (!vname.name)
+		return -ENOMEM;
+
+	memcpy(vname.name, buf, count-1);
+	vname.name[count] = '\0';
+	vname.namelen = count;
+
+	pr_debug("var %s of length %lu to be deleted\n", buf, count);
+
+	rc = pks_remove_var(NULL, vname);
+
+	if (!rc) {
+		list_for_each_entry(osvar_sysfs, &osvar_sysfs_list, node) {
+			if (strncmp(vname.name, osvar_sysfs->bin_attr.attr.name,
+				    strlen(vname.name)) == 0) {
+				list_del(&osvar_sysfs->node);
+				sysfs_remove_bin_file(os_kobj, &osvar_sysfs->bin_attr);
+				break;
+			}
+		}
+		rc = count;
+	}
+
+	return rc;
+}
+
+static struct kobj_attribute version_attr = __ATTR_RO(version);
+static struct kobj_attribute flags_attr = __ATTR_RO(flags);
+static struct kobj_attribute max_object_label_size_attr = __ATTR_RO(max_object_label_size);
+static struct kobj_attribute max_object_size_attr = __ATTR_RO(max_object_size);
+static struct kobj_attribute total_size_attr = __ATTR_RO(total_size);
+static struct kobj_attribute used_space_attr = __ATTR_RO(used_space);
+static struct kobj_attribute supported_policies_attr = __ATTR_RO(supported_policies);
+static struct kobj_attribute create_var_attr = __ATTR_WO(create_var);
+static struct kobj_attribute delete_var_attr = __ATTR_WO(delete_var);
+
+static int __init pks_sysfs_prop_load(void)
+{
+	int rc;
+
+	config = pks_get_config();
+	if (!config)
+		return -ENODEV;
+
+	rc = sysfs_create_file(prop_kobj, &version_attr.attr);
+	rc = sysfs_create_file(prop_kobj, &flags_attr.attr);
+	rc = sysfs_create_file(prop_kobj, &max_object_label_size_attr.attr);
+	rc = sysfs_create_file(prop_kobj, &max_object_size_attr.attr);
+	rc = sysfs_create_file(prop_kobj, &total_size_attr.attr);
+	rc = sysfs_create_file(prop_kobj, &used_space_attr.attr);
+	rc = sysfs_create_file(prop_kobj, &supported_policies_attr.attr);
+
+	return 0;
+}
+
+static int __init pks_sysfs_os_load(void)
+{
+	struct osvar_sysfs_attr *osvar_sysfs = NULL;
+	struct pks_var_name_list namelist;
+	struct pks_var *var;
+	int rc;
+	int i;
+
+	rc = sysfs_create_file(os_kobj, &create_var_attr.attr);
+	rc = sysfs_create_file(os_kobj, &delete_var_attr.attr);
+	rc = pks_get_var_ids_for_type(NULL, &namelist);
+	if (rc)
+		return rc;
+
+	for (i = 0; i < namelist.varcount; i++) {
+		var = kzalloc(sizeof(struct pks_var), GFP_KERNEL);
+		var->name = namelist.varlist[i].name;
+		var->namelen = namelist.varlist[i].namelen;
+		var->prefix = NULL;
+		rc = pks_read_var(var);
+		if (rc) {
+			pr_err("Error %d reading object %s\n", rc, var->name);
+			continue;
+		}
+
+		osvar_sysfs = kzalloc(sizeof(struct osvar_sysfs_attr), GFP_KERNEL);
+		if (!osvar_sysfs) {
+			rc = -ENOMEM;
+			break;
+		}
+
+		sysfs_bin_attr_init(&osvar_sysfs->bin_attr);
+		osvar_sysfs->bin_attr.private = var;
+		osvar_sysfs->bin_attr.attr.name = namelist.varlist[i].name;
+		osvar_sysfs->bin_attr.attr.mode = 0600;
+		osvar_sysfs->bin_attr.size = var->datalen;
+		osvar_sysfs->bin_attr.read = osvar_sysfs_read;
+		osvar_sysfs->bin_attr.write = osvar_sysfs_write;
+
+		rc = sysfs_create_bin_file(os_kobj,
+				&osvar_sysfs->bin_attr);
+
+		if (rc)
+			continue;
+
+		list_add_tail(&osvar_sysfs->node, &osvar_sysfs_list);
+	}
+
+	return rc;
+}
+
+static int pks_sysfs_init(void)
+{
+	int rc;
+
+	pks_kobj = kobject_create_and_add("pksvar", firmware_kobj);
+	if (!pks_kobj) {
+		pr_err("pksvar: Failed to create pks kobj\n");
+		return -ENOMEM;
+	}
+
+	prop_kobj = kobject_create_and_add("config", pks_kobj);
+	if (!prop_kobj) {
+		pr_err("secvar: config kobject registration failed.\n");
+		kobject_put(pks_kobj);
+		return -ENOMEM;
+	}
+
+	rc = pks_sysfs_prop_load();
+	if (rc)
+		return rc;
+
+	os_kobj = kobject_create_and_add("os", pks_kobj);
+	if (!os_kobj) {
+		pr_err("pksvar: os kobject registration failed.\n");
+		kobject_put(os_kobj);
+		return -ENOMEM;
+	}
+
+	rc = pks_sysfs_os_load();
+	if (rc)
+		return rc;
+
+	return 0;
+}
+late_initcall(pks_sysfs_init);
-- 
2.27.0