2023-04-20 16:55:59

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 00/14] HP BIOSCFG driver

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next

History

Version 11
Only patches marked [update] changed between version 10 and 11

Patches
Documentation
biosattr-interface [update]
bioscfg
int-attributes
ordered-attributes
passwdobj-attributes [update]
string-attributes
bioscfg-h
enum-attributes
passwdattr-interface
spmobj-attributes [update]
surestart-attributes [update]
Makefile ../hp/Makefile ../hp/Kconfig
MAINTAINERS


Version 10
Break down changes to single files per patch
Removed SPM/statusbin support
Patches
Documentation
biosattr-interface
bioscfg
int-attributes
ordered-attributes
passwdobj-attributes
string-attributes
bioscfg-h
enum-attributes
passwdattr-interface
spmobj-attributes
surestart-attributes
Makefile ../hp/Makefile ../hp/Kconfig
MAINTAINERS

Version 9
Includes only sysfs-class-firmware-attributes documentation

Version 8
Includes only sysfs-class-firmware-attributes documentation

Version 7
Includes only sysfs-class-firmware-attributes documentation

Version 6
Breaks down the changes into 4 patches
SureAdmin-attributes was removed

Version 5
Remove version 4 patch 1
Address review changes proposed in Version 4
Reorganize all patches number and file order


Jorge Lopez (14):
HP BIOSCFG driver - Documentation
HP BIOSCFG driver - biosattr-interface
HP BIOSCFG driver - bioscfg
HP BIOSCFG driver - int-attributes
HP BIOSCFG driver - ordered-attributes
HP BIOSCFG driver - passwdobj-attributes
HP BIOSCFG driver - string-attributes
HP BIOSCFG driver - bioscfg-h
HP BIOSCFG driver - enum-attributes
HP BIOSCFG driver - passwdattr-interface
HP BIOSCFG driver - spmobj-attributes
HP BIOSCFG driver - surestart-attributes
HP BIOSCFG driver - Makefile
HP BIOSCFG driver - MAINTAINERS

.../testing/sysfs-class-firmware-attributes | 98 +-
MAINTAINERS | 6 +
drivers/platform/x86/hp/Kconfig | 16 +
drivers/platform/x86/hp/Makefile | 1 +
drivers/platform/x86/hp/hp-bioscfg/Makefile | 13 +
.../x86/hp/hp-bioscfg/biosattr-interface.c | 307 ++++++++++++++++++
drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 961 ++++++++++++++++++
drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++
.../x86/hp/hp-bioscfg/enum-attributes.c | 543 ++++++++++
.../x86/hp/hp-bioscfg/int-attributes.c | 474 +++++++++
.../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++
.../x86/hp/hp-bioscfg/passwdattr-interface.c | 51 +
.../x86/hp/hp-bioscfg/passwdobj-attributes.c | 669 ++++++++++++++++++
.../x86/hp/hp-bioscfg/spmobj-attributes.c | 405 ++++++++
.../x86/hp/hp-bioscfg/string-attributes.c | 451 ++++++++
.../x86/hp/hp-bioscfg/surestart-attributes.c | 130 +++
16 files changed, 5299 insertions(+), 2 deletions(-)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/Makefile
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c

--
2.34.1


2023-04-20 16:56:01

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 01/14] HP BIOSCFG driver - Documentation

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../testing/sysfs-class-firmware-attributes | 98 ++++++++++++++++++-
1 file changed, 96 insertions(+), 2 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
index 4cdba3477176..73d7b8fbc0b2 100644
--- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
+++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
@@ -22,6 +22,12 @@ Description:
- integer: a range of numerical values
- string

+ HP specific types
+ -----------------
+ - ordered-list - a set of ordered list valid values
+ - sure-start - report audit logs read from BIOS
+
+
All attribute types support the following values:

current_value:
@@ -126,6 +132,44 @@ Description:
value will not be effective through sysfs until this rule is
met.

+ HP specific class extensions
+ ------------------------------
+
+ On HP systems the following additional attributes are available:
+
+ "ordered-list"-type specific properties:
+
+ elements:
+ A file that can be read to obtain the possible
+ list of values of the <attr>. Values are separated using
+ semi-colon (``;``). The order individual elements are listed
+ according to their priority. An Element listed first has the
+ highest priority. Writing the list in a different order to
+ current_value alters the priority order for the particular
+ attribute.
+
+ "sure-start"-type specific properties:
+
+ audit_log_entries:
+ A read-only file that returns the events in the log.
+ Values are separated using semi-colon (``;``)
+
+ Audit log entry format
+
+ Byte 0-15: Requested Audit Log entry (Each Audit log is 16 bytes)
+ Byte 16-127: Unused
+
+ audit_log_entry_count:
+ A read-only file that returns the number of existing audit log events available to be read.
+ Values are separated using comma (``,``)
+
+ [No of entries],[log entry size],[Max number of entries supported]
+
+ log entry size identifies audit log size for the current BIOS version.
+ The current size is 16 bytes but it can be to up to 128 bytes long
+ in future BIOS versions.
+
+
What: /sys/class/firmware-attributes/*/authentication/
Date: February 2021
KernelVersion: 5.11
@@ -206,7 +250,7 @@ Description:
Drivers may emit a CHANGE uevent when a password is set or unset
userspace may check it again.

- On Dell and Lenovo systems, if Admin password is set, then all BIOS attributes
+ On Dell, Lenovo and HP systems, if Admin password is set, then all BIOS attributes
require password validation.
On Lenovo systems if you change the Admin password the new password is not active until
the next boot.
@@ -296,6 +340,15 @@ Description:
echo "signature" > authentication/Admin/signature
echo "password" > authentication/Admin/certificate_to_password

+ HP specific class extensions
+ --------------------------------
+
+ On HP systems the following additional settings are available:
+
+ role: enhanced-bios-auth:
+ This role is specific to Secure Platform Management (SPM) attribute.
+ It requires configuring an endorsement (kek) and signing certificate (sk).
+

What: /sys/class/firmware-attributes/*/attributes/pending_reboot
Date: February 2021
@@ -311,7 +364,7 @@ Description:
== =========================================
0 All BIOS attributes setting are current
1 A reboot is necessary to get pending BIOS
- attribute changes applied
+ attribute changes applied
== =========================================

Note, userspace applications need to follow below steps for efficient
@@ -364,3 +417,44 @@ Description:
use it to enable extra debug attributes or BIOS features for testing purposes.

Note that any changes to this attribute requires a reboot for changes to take effect.
+
+
+ HP specific class extensions - Secure Platform Manager (SPM)
+ --------------------------------
+
+What: /sys/class/firmware-attributes/*/authentication/SPM/kek
+Date: March 29
+KernelVersion: 5.18
+Contact: "Jorge Lopez" <[email protected]>
+Description: 'kek' Key-Encryption-Key is a write-only file that can be used to configure the
+ RSA public key that will be used by the BIOS to verify
+ signatures when setting the signing key. When written,
+ the bytes should correspond to the KEK certificate
+ (x509 .DER format containing an OU). The size of the
+ certificate must be less than or equal to 4095 bytes.
+
+
+What: /sys/class/firmware-attributes/*/authentication/SPM/sk
+Date: March 29
+KernelVersion: 5.18
+Contact: "Jorge Lopez" <[email protected]>
+Description: 'sk' Signature Key is a write-only file that can be used to configure the RSA
+ public key that will be used by the BIOS to verify signatures
+ when configuring BIOS settings and security features. When
+ written, the bytes should correspond to the modulus of the
+ public key. The exponent is assumed to be 0x10001.
+
+
+What: /sys/class/firmware-attributes/*/authentication/SPM/status
+Date: March 29
+KernelVersion: 5.18
+Contact: "Jorge Lopez" <[email protected]>
+Description: 'status' is a read-only file that returns ASCII text in JSON format reporting
+ the status information.
+
+ "State": "not provisioned | provisioned | provisioning in progress ",
+ "Version": " Major. Minor ",
+ "Nonce": <16-bit unsigned number display in base 10>,
+ "FeaturesInUse": <16-bit unsigned number display in base 10>,
+ "EndorsementKeyMod": "<256 bytes in base64>",
+ "SigningKeyMod": "<256 bytes in base64>"
--
2.34.1

2023-04-20 16:56:07

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 03/14] HP BIOSCFG driver - bioscfg

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 961 +++++++++++++++++++
1 file changed, 961 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
new file mode 100644
index 000000000000..4b0d4f56e65f
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
@@ -0,0 +1,961 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Common methods for use with hp-bioscfg driver
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/wmi.h>
+#include "bioscfg.h"
+#include "../../firmware_attributes_class.h"
+#include <linux/nls.h>
+#include <linux/errno.h>
+
+MODULE_AUTHOR("Jorge Lopez <[email protected]>");
+MODULE_DESCRIPTION("HP BIOS Configuration Driver");
+MODULE_LICENSE("GPL");
+
+struct bioscfg_priv bioscfg_drv = {
+ .mutex = __MUTEX_INITIALIZER(bioscfg_drv.mutex),
+};
+
+static struct class *fw_attr_class;
+
+int get_integer_from_buffer(int **buffer, u32 *buffer_size, int *integer)
+{
+ int *ptr = PTR_ALIGN(*buffer, 4);
+
+ /* Ensure there is enough space remaining to read the integer */
+ if (*buffer_size < sizeof(int))
+ return -EINVAL;
+
+ *integer = *(ptr++);
+ *buffer = ptr;
+ *buffer_size -= sizeof(int);
+
+ return 0;
+}
+
+
+int get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size)
+{
+ u16 *src = (u16 *)*buffer;
+ u16 src_size;
+
+ u16 size;
+ int i;
+ int escape = 0;
+ int conv_dst_size;
+
+ if (*buffer_size < sizeof(u16))
+ return -EINVAL;
+
+ src_size = *(src++);
+ /* size value in u16 chars */
+ size = src_size / sizeof(u16);
+
+ /* Ensure there is enough space remaining to read and convert
+ * the string
+ */
+ if (*buffer_size < src_size)
+ return -EINVAL;
+
+ for (i = 0; i < size; i++)
+ if (src[i] == '\\' ||
+ src[i] == '\r' ||
+ src[i] == '\n' ||
+ src[i] == '\t')
+ escape++;
+
+ size += escape;
+
+ /*
+ * Conversion is limited to destination string max number of
+ * bytes.
+ */
+ conv_dst_size = size;
+ if (size > dst_size)
+ conv_dst_size = dst_size - 1;
+
+ /*
+ * convert from UTF-16 unicode to ASCII
+ */
+ utf16s_to_utf8s(src, src_size, UTF16_HOST_ENDIAN, dst, conv_dst_size);
+ dst[conv_dst_size] = 0;
+
+ for (i = 0; i < size && i < conv_dst_size; i++) {
+ if (*src == '\\' ||
+ *src == '\r' ||
+ *src == '\n' ||
+ *src == '\t')
+ dst[i++] = '\\';
+
+ if (*src == '\r')
+ dst[i] = 'r';
+ else if (*src == '\n')
+ dst[i] = 'n';
+ else if (*src == '\t')
+ dst[i] = 't';
+ else if (*src == '"')
+ dst[i] = '\'';
+ else
+ dst[i] = *src;
+ src++;
+ }
+
+ *buffer = (u8 *)src;
+ *buffer_size -= size * sizeof(u16);
+
+ return size;
+}
+
+
+/*
+ * calculate_string_buffer() - determines size of string buffer for use with BIOS communication
+ * @str: the string to calculate based upon
+ */
+size_t bioscfg_calculate_string_buffer(const char *str)
+{
+ int length = strlen(str);
+ int size;
+
+ /* BIOS expects 4 bytes when an empty string is found */
+ if (!length)
+ length = 1;
+
+ /* u16 length field + one UTF16 char for each input char */
+ size = sizeof(u16) + length * sizeof(u16);
+
+ return size;
+}
+
+int bioscfg_wmi_error_and_message(int error_code)
+{
+ char *error_msg = NULL;
+ int ret;
+
+ switch (error_code) {
+ case SUCCESS:
+ error_msg = "Success";
+ ret = 0;
+ break;
+ case CMD_FAILED:
+ error_msg = "Command failed";
+ ret = -EINVAL;
+ break;
+ case INVALID_SIGN:
+ error_msg = "Invalid signature";
+ ret = -EINVAL;
+ break;
+ case INVALID_CMD_VALUE:
+ error_msg = "Invalid command value/Feature not supported";
+ ret = -EOPNOTSUPP;
+ break;
+ case INVALID_CMD_TYPE:
+ error_msg = "Invalid command type";
+ ret = -EINVAL;
+ break;
+ case INVALID_DATA_SIZE:
+ error_msg = "Invalid data size";
+ ret = -EINVAL;
+ break;
+ case INVALID_CMD_PARAM:
+ error_msg = "Invalid command parameter";
+ ret = -EINVAL;
+ break;
+ case ENCRYP_CMD_REQUIRED:
+ error_msg = "Secure/encrypted command required";
+ ret = -EACCES;
+ break;
+ case NO_SECURE_SESSION:
+ error_msg = "No secure session established";
+ ret = -EACCES;
+ break;
+ case SECURE_SESSION_FOUND:
+ error_msg = "Secure session already established";
+ ret = -EACCES;
+ break;
+ case SECURE_SESSION_FAILED:
+ error_msg = "Secure session failed";
+ ret = -EIO;
+ break;
+ case AUTH_FAILED:
+ error_msg = "Other permission/Authentication failed";
+ ret = -EACCES;
+ break;
+ case INVALID_BIOS_AUTH:
+ error_msg = "Invalid BIOS administrator password";
+ ret = -EINVAL;
+ break;
+ case NONCE_DID_NOT_MATCH:
+ error_msg = "Nonce did not match";
+ ret = -EINVAL;
+ break;
+ case GENERIC_ERROR:
+ error_msg = "Generic/Other error";
+ ret = -EIO;
+ break;
+ case BIOS_ADMIN_POLICY_NOT_MET:
+ error_msg = "BIOS Admin password does not meet password policy requirements";
+ ret = -EINVAL;
+ break;
+ case BIOS_ADMIN_NOT_SET:
+ error_msg = "BIOS Setup password is not set.";
+ ret = -EPERM;
+ break;
+ case P21_NO_PROVISIONED:
+ error_msg = "P21 is not provisioned";
+ ret = -EPERM;
+ break;
+ case P21_PROVISION_IN_PROGRESS:
+ error_msg = "P21 is already provisioned or provisioning is in progress and a signing key has already been sent.";
+ ret = -EINPROGRESS;
+ break;
+ case P21_IN_USE:
+ error_msg = "P21 in use (cannot deprovision)";
+ ret = -EPERM;
+ break;
+ case HEP_NOT_ACTIVE:
+ error_msg = "HEP not activated";
+ ret = -EPERM;
+ break;
+ case HEP_ALREADY_SET:
+ error_msg = "HEP Transport already set";
+ ret = -EINVAL;
+ break;
+ case HEP_CHECK_STATE:
+ error_msg = "Check the current HEP state";
+ ret = -EINVAL;
+ break;
+ default:
+ error_msg = "Generic/Other error";
+ ret = -EIO;
+ break;
+ }
+
+ if (error_code)
+ pr_warn_ratelimited("Returned error 0x%x, \"%s\"\n", error_code, error_msg);
+
+ return ret;
+}
+
+
+/*
+ * pending_reboot_show() - sysfs implementaton for read pending_reboot
+ * @kobj: Kernel object for this attribute
+ * @attr: Kernel object attribute
+ * @buf: The buffer to display to userspace
+ *
+ * Stores default value as 0
+ * When current_value is changed this attribute is set to 1 to notify reboot may be required
+ */
+static ssize_t pending_reboot_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%d\n", bioscfg_drv.pending_reboot);
+}
+static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
+
+/*
+ * create_attributes_level_sysfs_files() - Creates pending_reboot attributes
+ */
+static int create_attributes_level_sysfs_files(void)
+{
+ int ret;
+
+ ret = sysfs_create_file(&bioscfg_drv.main_dir_kset->kobj, &pending_reboot.attr);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+
+static void attr_name_release(struct kobject *kobj)
+{
+ kfree(kobj);
+}
+
+static const struct kobj_type attr_name_ktype = {
+ .release = attr_name_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+};
+
+/*
+ * get_wmiobj_pointer() - Get Content of WMI block for particular instance
+ *
+ * @instance_id: WMI instance ID
+ * @guid_string: WMI GUID (in str form)
+ *
+ * Fetches the content for WMI block (instance_id) under GUID (guid_string)
+ * Caller must kfree the return
+ */
+union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string)
+{
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ status = wmi_query_block(guid_string, instance_id, &out);
+ return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL;
+}
+
+/*
+ * get_instance_count() - Compute total number of instances under guid_string
+ *
+ * @guid_string: WMI GUID (in string form)
+ */
+int get_instance_count(const char *guid_string)
+{
+ union acpi_object *wmi_obj = NULL;
+ int i = 0;
+
+ do {
+ kfree(wmi_obj);
+ wmi_obj = get_wmiobj_pointer(i, guid_string);
+ i++;
+ } while (wmi_obj);
+
+ return i-1;
+}
+
+/*
+ * alloc_attributes_data() - Allocate attributes data for a particular type
+ *
+ * @attr_type: Attribute type to allocate
+ */
+static int alloc_attributes_data(int attr_type)
+{
+ int retval = 0;
+
+ switch (attr_type) {
+ case HPWMI_STRING_TYPE:
+ retval = alloc_string_data();
+ break;
+ case HPWMI_INTEGER_TYPE:
+ retval = alloc_integer_data();
+ break;
+ case HPWMI_ENUMERATION_TYPE:
+ retval = alloc_enumeration_data();
+ break;
+ case HPWMI_ORDERED_LIST_TYPE:
+ retval = alloc_ordered_list_data();
+ break;
+ case HPWMI_PASSWORD_TYPE:
+ retval = alloc_password_data();
+ break;
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+int convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len)
+{
+ int ret = 0;
+ int new_len = 0;
+ char tmp[] = "0x00";
+ char *new_str = NULL;
+ long ch;
+ int i;
+
+ if (input_len <= 0 || input == NULL || str == NULL || len == NULL)
+ return -EINVAL;
+
+ *len = 0;
+ *str = NULL;
+
+ new_str = kmalloc(input_len, GFP_KERNEL);
+ if (!new_str)
+ return -ENOMEM;
+
+ for (i = 0; i < input_len; i += 5) {
+ strncpy(tmp, input + i, strlen(tmp));
+ if (kstrtol(tmp, 16, &ch) == 0) {
+ // escape char
+ if (ch == '\\' || ch == '\r' || ch == '\n' || ch == '\t') {
+ if (ch == '\r')
+ ch = 'r';
+ else if (ch == '\n')
+ ch = 'n';
+ else if (ch == '\t')
+ ch = 't';
+ new_str[new_len++] = '\\';
+ }
+ new_str[new_len++] = ch;
+ if (ch == '\0')
+ break;
+ }
+ }
+
+ if (new_len) {
+ new_str[new_len] = '\0';
+ *str = krealloc(new_str, (new_len + 1) * sizeof(char), GFP_KERNEL);
+ if (*str)
+ *len = new_len;
+ else
+ ret = -ENOMEM;
+ } else {
+ ret = -EFAULT;
+ }
+
+ if (ret)
+ kfree(new_str);
+ return ret;
+}
+
+/* map output size to the corresponding WMI method id */
+int encode_outsize_for_pvsz(int outsize)
+{
+ if (outsize > 4096)
+ return -EINVAL;
+ if (outsize > 1024)
+ return 5;
+ if (outsize > 128)
+ return 4;
+ if (outsize > 4)
+ return 3;
+ if (outsize > 0)
+ return 2;
+ return 1;
+}
+
+/*
+ * Update friendly display name for several attributes associated to
+ * 'Schedule Power-On'
+ */
+void friendly_user_name_update(char *path, const char *attr_name,
+ char *attr_display, int attr_size)
+{
+ char *found = NULL;
+
+ found = strstr(path, SCHEDULE_POWER_ON);
+ if (found)
+ snprintf(attr_display,
+ attr_size,
+ "%s - %s",
+ SCHEDULE_POWER_ON,
+ attr_name);
+ else
+ strscpy(attr_display, attr_name, attr_size);
+}
+
+/*
+ * update_attribute_permissions() - Update attributes permissions when
+ * isReadOnly value is 1
+ *
+ * @isReadOnly: ReadOnly value
+ * @current_val: kobj_attribute corresponding to attribute.
+ *
+ */
+void update_attribute_permissions(u32 isReadOnly, struct kobj_attribute *current_val)
+{
+ if (isReadOnly)
+ current_val->attr.mode = (umode_t)0444;
+ else
+ current_val->attr.mode = (umode_t)0644;
+}
+
+
+/**
+ * destroy_attribute_objs() - Free a kset of kobjects
+ * @kset: The kset to destroy
+ *
+ * Fress kobjects created for each attribute_name under attribute type kset
+ */
+static void destroy_attribute_objs(struct kset *kset)
+{
+ struct kobject *pos, *next;
+
+ list_for_each_entry_safe(pos, next, &kset->list, entry)
+ kobject_put(pos);
+}
+
+/**
+ * release_attributes_data() - Clean-up all sysfs directories and files created
+ */
+static void release_attributes_data(void)
+{
+ mutex_lock(&bioscfg_drv.mutex);
+
+ exit_string_attributes();
+ exit_integer_attributes();
+ exit_enumeration_attributes();
+ exit_ordered_list_attributes();
+ exit_password_attributes();
+ exit_sure_start_attributes();
+ exit_secure_platform_attributes();
+
+ if (bioscfg_drv.authentication_dir_kset) {
+ destroy_attribute_objs(bioscfg_drv.authentication_dir_kset);
+ kset_unregister(bioscfg_drv.authentication_dir_kset);
+ bioscfg_drv.authentication_dir_kset = NULL;
+ }
+ if (bioscfg_drv.main_dir_kset) {
+ sysfs_remove_file(&bioscfg_drv.main_dir_kset->kobj, &pending_reboot.attr);
+ destroy_attribute_objs(bioscfg_drv.main_dir_kset);
+ kset_unregister(bioscfg_drv.main_dir_kset);
+ bioscfg_drv.main_dir_kset = NULL;
+ }
+ mutex_unlock(&bioscfg_drv.mutex);
+}
+
+
+/*
+ * hp_add_other_attributes - Initialize HP custom attributes not reported by
+ * BIOS and required to support Secure Platform, Sure Start, and Sure
+ * Admin.
+ * @attr_type: Custom HP attribute not reported by BIOS
+ *
+ * Initialiaze all 3 types of attributes: Platform, Sure Start, and Sure
+ * Admin object. Populates each attrbute types respective properties
+ * under sysfs files.
+ *
+ * Returns zero(0) if successful. Otherwise, a negative value.
+ */
+static int hp_add_other_attributes(int attr_type)
+{
+ struct kobject *attr_name_kobj;
+ union acpi_object *obj = NULL;
+ int retval = 0;
+ u8 *attr_name;
+
+ mutex_lock(&bioscfg_drv.mutex);
+
+ attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ if (!attr_name_kobj) {
+ retval = -ENOMEM;
+ goto err_other_attr_init;
+ }
+
+ /* Check if attribute type is supported */
+ switch (attr_type) {
+ case HPWMI_SECURE_PLATFORM_TYPE:
+ attr_name_kobj->kset = bioscfg_drv.authentication_dir_kset;
+ attr_name = SPM_STR;
+ break;
+
+ case HPWMI_SURE_START_TYPE:
+ attr_name_kobj->kset = bioscfg_drv.main_dir_kset;
+ attr_name = SURE_START_STR;
+ break;
+
+ default:
+ pr_err("Error: Unknown attr_type: %d\n", attr_type);
+ retval = -EINVAL;
+ goto err_other_attr_init;
+ }
+
+ retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
+ NULL, "%s", attr_name);
+ if (retval) {
+ pr_err("Error encountered [%d]\n", retval);
+ kobject_put(attr_name_kobj);
+ goto err_other_attr_init;
+ }
+
+ /* Populate attribute data */
+ switch (attr_type) {
+ case HPWMI_SECURE_PLATFORM_TYPE:
+ retval = populate_secure_platform_data(attr_name_kobj);
+ break;
+
+ case HPWMI_SURE_START_TYPE:
+ retval = populate_sure_start_data(attr_name_kobj);
+ break;
+
+ default:
+ goto err_other_attr_init;
+ }
+
+ mutex_unlock(&bioscfg_drv.mutex);
+ return 0;
+
+err_other_attr_init:
+ mutex_unlock(&bioscfg_drv.mutex);
+ kfree(obj);
+ return retval;
+}
+
+/*
+ * hp_init_bios_attributes - Initialize all attributes for a type
+ * @attr_type: The attribute type to initialize
+ * @guid: The WMI GUID associated with this type to initialize
+ *
+ * Initialiaze all 5 types of attributes: enumeration, integer,
+ * string, password, ordered list object. Populates each attrbute types
+ * respective properties under sysfs files
+ */
+static int hp_init_bios_attributes(int attr_type, const char *guid)
+{
+ struct kobject *attr_name_kobj;
+ union acpi_object *obj = NULL;
+ union acpi_object *elements;
+ struct kset *tmp_set;
+ int min_elements;
+ char str[MAX_BUFF];
+
+ char *temp_str = NULL;
+ char *str_value = NULL;
+ int str_len;
+ int ret = 0;
+
+ u8 *buffer_ptr = NULL;
+ int buffer_size;
+
+
+ /* instance_id needs to be reset for each type GUID
+ * also, instance IDs are unique within GUID but not across
+ */
+ int instance_id = 0;
+ int retval = 0;
+
+ retval = alloc_attributes_data(attr_type);
+ if (retval)
+ return retval;
+
+ switch (attr_type) {
+ case HPWMI_STRING_TYPE:
+ min_elements = 12;
+ break;
+ case HPWMI_INTEGER_TYPE:
+ min_elements = 13;
+ break;
+ case HPWMI_ENUMERATION_TYPE:
+ min_elements = 13;
+ break;
+ case HPWMI_ORDERED_LIST_TYPE:
+ min_elements = 12;
+ break;
+ case HPWMI_PASSWORD_TYPE:
+ min_elements = 15;
+ break;
+ default:
+ pr_err("Error: Unknown attr_type: %d\n", attr_type);
+ return -EINVAL;
+ }
+
+ /* need to use specific instance_id and guid combination to get right data */
+ obj = get_wmiobj_pointer(instance_id, guid);
+ if (!obj)
+ return -ENODEV;
+
+ mutex_lock(&bioscfg_drv.mutex);
+ while (obj) {
+ if (obj->type != ACPI_TYPE_PACKAGE && obj->type != ACPI_TYPE_BUFFER) {
+ pr_err("Error: Expected ACPI-package or buffer type, got: %d\n", obj->type);
+ retval = -EIO;
+ goto err_attr_init;
+ }
+
+ /* Take action appropriate to each ACPI TYPE */
+ if (obj->type == ACPI_TYPE_PACKAGE) {
+ if (obj->package.count < min_elements) {
+ pr_err("ACPI-package does not have enough elements: %d < %d\n",
+ obj->package.count, min_elements);
+ goto nextobj;
+ }
+
+ elements = obj->package.elements;
+
+ /* sanity checking */
+ if (elements[NAME].type != ACPI_TYPE_STRING) {
+ pr_debug("incorrect element type\n");
+ goto nextobj;
+ }
+ if (strlen(elements[NAME].string.pointer) == 0) {
+ pr_debug("empty attribute found\n");
+ goto nextobj;
+ }
+
+ if (attr_type == HPWMI_PASSWORD_TYPE)
+ tmp_set = bioscfg_drv.authentication_dir_kset;
+ else
+ tmp_set = bioscfg_drv.main_dir_kset;
+
+ /* convert attribute name to string */
+ retval = convert_hexstr_to_str(elements[NAME].string.pointer,
+ elements[NAME].string.length,
+ &str_value, &str_len);
+
+ if (retval) {
+ pr_debug("Failed to populate integer package data. Error [0%0x]\n", ret);
+ kfree(str_value);
+ return ret;
+ }
+
+ if (kset_find_obj(tmp_set, str_value)) {
+ pr_debug("Duplicate attribute name found - %s\n",
+ str_value);
+ goto nextobj;
+ }
+
+ /* build attribute */
+ attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ if (!attr_name_kobj) {
+ retval = -ENOMEM;
+ goto err_attr_init;
+ }
+
+ attr_name_kobj->kset = tmp_set;
+
+ retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
+ NULL, "%s", str_value);
+
+ if (retval) {
+ kobject_put(attr_name_kobj);
+ goto err_attr_init;
+ }
+
+ /* enumerate all of these attributes */
+ switch (attr_type) {
+ case HPWMI_STRING_TYPE:
+ retval = populate_string_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_INTEGER_TYPE:
+ retval = populate_integer_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_ENUMERATION_TYPE:
+ retval = populate_enumeration_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_ORDERED_LIST_TYPE:
+ retval = populate_ordered_list_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_PASSWORD_TYPE:
+ retval = populate_password_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ default:
+ break;
+ }
+
+ kfree(str_value);
+ }
+
+ if (obj->type == ACPI_TYPE_BUFFER) {
+
+ buffer_size = obj->buffer.length;
+ buffer_ptr = obj->buffer.pointer;
+
+ retval = get_string_from_buffer(&buffer_ptr, &buffer_size, str, MAX_BUFF);
+ if (retval < 0)
+ goto err_attr_init;
+
+ if (attr_type == HPWMI_PASSWORD_TYPE || attr_type == HPWMI_SECURE_PLATFORM_TYPE)
+ tmp_set = bioscfg_drv.authentication_dir_kset;
+ else
+ tmp_set = bioscfg_drv.main_dir_kset;
+
+ if (kset_find_obj(tmp_set, str)) {
+ pr_warn("Duplicate attribute name found - %s\n", str);
+ goto nextobj;
+ }
+
+ /* build attribute */
+ attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ if (!attr_name_kobj) {
+ retval = -ENOMEM;
+ goto err_attr_init;
+ }
+
+ attr_name_kobj->kset = tmp_set;
+
+ temp_str = str;
+ if (attr_type == HPWMI_SECURE_PLATFORM_TYPE)
+ temp_str = "SPM";
+
+ retval = kobject_init_and_add(attr_name_kobj,
+ &attr_name_ktype, NULL, "%s",
+ temp_str);
+ if (retval) {
+ kobject_put(attr_name_kobj);
+ goto err_attr_init;
+ }
+
+ /* enumerate all of these attributes */
+ switch (attr_type) {
+ case HPWMI_STRING_TYPE:
+ retval = populate_string_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_INTEGER_TYPE:
+ retval = populate_integer_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_ENUMERATION_TYPE:
+ retval = populate_enumeration_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_ORDERED_LIST_TYPE:
+ retval = populate_ordered_list_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_PASSWORD_TYPE:
+ retval = populate_password_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ default:
+ break;
+ }
+ }
+nextobj:
+ kfree(str_value);
+ kfree(obj);
+ instance_id++;
+ obj = get_wmiobj_pointer(instance_id, guid);
+ }
+ mutex_unlock(&bioscfg_drv.mutex);
+ return 0;
+
+err_attr_init:
+ mutex_unlock(&bioscfg_drv.mutex);
+ kfree(obj);
+ return retval;
+}
+
+static int __init bioscfg_init(void)
+{
+ int ret = 0;
+ int bios_capable = wmi_has_guid(HP_WMI_BIOS_GUID);
+
+ if (!bios_capable) {
+ pr_err("Unable to run on non-HP system\n");
+ return -ENODEV;
+ }
+
+ ret = init_bios_attr_set_interface();
+ if (ret)
+ return ret;
+
+ ret = init_bios_attr_pass_interface();
+ if (ret)
+ goto err_exit_bios_attr_set_interface;
+
+ if (!bioscfg_drv.bios_attr_wdev || !bioscfg_drv.password_attr_wdev) {
+ pr_debug("Failed to find set or pass interface\n");
+ ret = -ENODEV;
+ goto err_exit_bios_attr_pass_interface;
+ }
+
+ ret = fw_attributes_class_get(&fw_attr_class);
+ if (ret)
+ goto err_exit_bios_attr_pass_interface;
+
+ bioscfg_drv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
+ NULL, "%s", DRIVER_NAME);
+ if (IS_ERR(bioscfg_drv.class_dev)) {
+ ret = PTR_ERR(bioscfg_drv.class_dev);
+ goto err_unregister_class;
+ }
+
+ bioscfg_drv.main_dir_kset = kset_create_and_add("attributes", NULL,
+ &bioscfg_drv.class_dev->kobj);
+ if (!bioscfg_drv.main_dir_kset) {
+ ret = -ENOMEM;
+ pr_debug("Failed to create and add attributes\n");
+ goto err_destroy_classdev;
+ }
+
+ bioscfg_drv.authentication_dir_kset = kset_create_and_add("authentication", NULL,
+ &bioscfg_drv.class_dev->kobj);
+ if (!bioscfg_drv.authentication_dir_kset) {
+ ret = -ENOMEM;
+ pr_debug("Failed to create and add authentication\n");
+ goto err_release_attributes_data;
+ }
+
+ /*
+ * sysfs level attributes.
+ * - pending_reboot
+ */
+ ret = create_attributes_level_sysfs_files();
+ if (ret)
+ pr_debug("Failed to create sysfs level attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_STRING_TYPE, HP_WMI_BIOS_STRING_GUID);
+ if (ret)
+ pr_debug("Failed to populate string type attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_INTEGER_TYPE, HP_WMI_BIOS_INTEGER_GUID);
+ if (ret)
+ pr_debug("Failed to populate integer type attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_ENUMERATION_TYPE, HP_WMI_BIOS_ENUMERATION_GUID);
+ if (ret)
+ pr_debug("Failed to populate enumeration type attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_ORDERED_LIST_TYPE, HP_WMI_BIOS_ORDERED_LIST_GUID);
+ if (ret)
+ pr_debug("Failed to populate ordered list object type attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_PASSWORD_TYPE, HP_WMI_BIOS_PASSWORD_GUID);
+ if (ret)
+ pr_debug("Failed to populate password object type attributes\n");
+
+ bioscfg_drv.spm_data.attr_name_kobj = NULL;
+ ret = hp_add_other_attributes(HPWMI_SECURE_PLATFORM_TYPE);
+ if (ret)
+ pr_debug("Failed to populate secure platform object type attribute\n");
+
+ bioscfg_drv.sure_start_attr_kobj = NULL;
+ ret = hp_add_other_attributes(HPWMI_SURE_START_TYPE);
+ if (ret)
+ pr_debug("Failed to populate sure start object type attribute\n");
+
+ return 0;
+
+err_release_attributes_data:
+ release_attributes_data();
+
+err_destroy_classdev:
+ device_destroy(fw_attr_class, MKDEV(0, 0));
+
+err_unregister_class:
+ fw_attributes_class_put();
+
+err_exit_bios_attr_pass_interface:
+ exit_bios_attr_pass_interface();
+
+err_exit_bios_attr_set_interface:
+ exit_bios_attr_set_interface();
+
+ return ret;
+}
+
+static void __exit bioscfg_exit(void)
+{
+ release_attributes_data();
+ device_destroy(fw_attr_class, MKDEV(0, 0));
+
+ fw_attributes_class_put();
+ exit_bios_attr_set_interface();
+ exit_bios_attr_pass_interface();
+}
+
+module_init(bioscfg_init);
+module_exit(bioscfg_exit);
--
2.34.1

2023-04-20 16:56:09

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
1 file changed, 563 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
new file mode 100644
index 000000000000..5e5d540f728d
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to ordered list type attributes under
+ * BIOS ORDERED LIST GUID for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+
+GET_INSTANCE_ID(ordered_list);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+
+ int instance_id = get_ordered_list_instance_id(kobj);
+
+ if (instance_id < 0)
+ return -EIO;
+
+ return sysfs_emit(buf, "%s\n",
+ bioscfg_drv.ordered_list_data[instance_id].current_value);
+}
+
+/*
+ * validate_ordered_list_value -
+ * Validate input of current_value against possible values
+ *
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_ordered_list_values(int instance_id, const char *buf)
+{
+ int ret = 0;
+ int found = 0;
+ char *new_values = NULL;
+ char *value;
+ int elem;
+ int elem_found = 0;
+
+ /* Is it a read only attribute */
+ if (bioscfg_drv.ordered_list_data[instance_id].common.is_readonly)
+ return -EIO;
+
+ new_values = kstrdup(buf, GFP_KERNEL);
+
+ /*
+ * Changes to ordered list values require checking that new
+ * values are found in the list of elements.
+ */
+ elem_found = 0;
+ while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
+
+ value = strsep(&new_values, ",");
+ if (value != NULL) {
+ if (!*value)
+ continue;
+ elem_found++;
+ }
+
+ found = 0;
+ for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
+ if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {
+ found = 1;
+ break;
+ }
+ }
+
+
+ if (!found) {
+ ret = -EINVAL;
+ goto out_list_value;
+ }
+ }
+
+ if (elem_found == bioscfg_drv.ordered_list_data[instance_id].elements_size) {
+ pr_warn("Number of new values is not equal to number of ordered list elements (%d)\n",
+ bioscfg_drv.ordered_list_data[instance_id].elements_size);
+ ret = -EINVAL;
+ goto out_list_value;
+ }
+
+out_list_value:
+ kfree(new_values);
+ return ret;
+}
+
+/*
+ * validate_ordered_input() -
+ * Validate input of current_value against possible values
+ *
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_ordered_list_input(int instance_id, const char *buf)
+{
+ int ret = 0;
+
+ ret = validate_ordered_list_values(instance_id, buf);
+ if (ret < 0)
+ return -EINVAL;
+
+ /*
+ * set pending reboot flag depending on
+ * "RequiresPhysicalPresence" value
+ */
+ if (bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence)
+ bioscfg_drv.pending_reboot = true;
+
+ return ret;
+}
+
+static void update_ordered_list_value(int instance_id, char *attr_value)
+{
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
+ attr_value,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
+}
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, ordered_list);
+static struct kobj_attribute ordered_list_display_langcode =
+ __ATTR_RO(display_name_language_code);
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, ordered_list);
+static struct kobj_attribute ordered_list_display_name =
+ __ATTR_RO(display_name);
+
+ATTRIBUTE_PROPERTY_STORE(current_value, ordered_list);
+static struct kobj_attribute ordered_list_current_val =
+ __ATTR_RW_MODE(current_value, 0644);
+
+
+ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, ordered_list);
+static struct kobj_attribute ordered_list_prerequisites_size_val =
+ __ATTR_RO(prerequisites_size);
+
+ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, ordered_list);
+static struct kobj_attribute ordered_list_prerequisites_val =
+ __ATTR_RO(prerequisites);
+
+ATTRIBUTE_N_PROPERTY_SHOW(elements_size, ordered_list);
+static struct kobj_attribute ordered_list_elements_size_val =
+ __ATTR_RO(elements_size);
+
+ATTRIBUTE_VALUES_PROPERTY_SHOW(elements, ordered_list);
+static struct kobj_attribute ordered_list_elements_val =
+ __ATTR_RO(elements);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "ordered-list\n");
+}
+static struct kobj_attribute ordered_list_type =
+ __ATTR_RO(type);
+
+static struct attribute *ordered_list_attrs[] = {
+ &ordered_list_display_langcode.attr,
+ &ordered_list_display_name.attr,
+ &ordered_list_current_val.attr,
+ &ordered_list_prerequisites_size_val.attr,
+ &ordered_list_prerequisites_val.attr,
+ &ordered_list_elements_val.attr,
+ &ordered_list_elements_size_val.attr,
+ &ordered_list_type.attr,
+ NULL
+};
+
+static const struct attribute_group ordered_list_attr_group = {
+ .attrs = ordered_list_attrs,
+};
+
+int alloc_ordered_list_data(void)
+{
+ int ret = 0;
+
+ bioscfg_drv.ordered_list_instances_count =
+ get_instance_count(HP_WMI_BIOS_ORDERED_LIST_GUID);
+ bioscfg_drv.ordered_list_data = kcalloc(bioscfg_drv.ordered_list_instances_count,
+ sizeof(struct ordered_list_data), GFP_KERNEL);
+ if (!bioscfg_drv.ordered_list_data) {
+ bioscfg_drv.ordered_list_instances_count = 0;
+ ret = -ENOMEM;
+ }
+ return ret;
+}
+
+/*
+ * populate_ordered_list_package_data() -
+ * Populate all properties of an instance under ordered_list attribute
+ *
+ * @order_obj: ACPI object with ordered_list data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_ordered_list_package_data(union acpi_object *order_obj, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.ordered_list_data[instance_id].attr_name_kobj = attr_name_kobj;
+
+ populate_ordered_list_elements_from_package(order_obj,
+ order_obj->package.count,
+ instance_id);
+ update_attribute_permissions(bioscfg_drv.ordered_list_data[instance_id].common.is_readonly,
+ &ordered_list_current_val);
+ friendly_user_name_update(bioscfg_drv.ordered_list_data[instance_id].common.path,
+ attr_name_kobj->name,
+ bioscfg_drv.ordered_list_data[instance_id].common.display_name,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name));
+ return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group);
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_order_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [ORD_LIST_SIZE] = ACPI_TYPE_INTEGER,
+ [ORD_LIST_ELEMENTS] = ACPI_TYPE_STRING
+};
+
+
+int populate_ordered_list_elements_from_package(union acpi_object *order_obj,
+ int order_obj_count,
+ int instance_id)
+{
+ char *str_value = NULL;
+ int value_len;
+ int ret = 0;
+ u32 size = 0;
+ u32 int_value;
+ int elem = 0;
+ int reqs;
+ int eloc;
+ char *tmpstr = NULL;
+ char *part_tmp = NULL;
+ int tmp_len = 0;
+ char *part = NULL;
+
+ if (!order_obj)
+ return -EINVAL;
+
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1, eloc = 1; elem < order_obj_count; elem++, eloc++) {
+
+ /* ONLY look at the first ORDERED_ELEM_CNT elements */
+ if (eloc == ORDERED_ELEM_CNT)
+ goto exit_list_package;
+
+ switch (order_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+
+ if (elem != PREREQUISITES && elem != ORD_LIST_ELEMENTS) {
+ ret = convert_hexstr_to_str(order_obj[elem].string.pointer,
+ order_obj[elem].string.length,
+ &str_value, &value_len);
+ if (ret)
+ continue;
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)order_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", order_obj[elem].type);
+ continue;
+ }
+
+ /* Check that both expected and read object type match */
+ if (expected_order_types[eloc] != order_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_order_types[eloc], elem, order_obj[elem].type);
+ return -EIO;
+ }
+
+ /* Assign appropriate element value to corresponding field*/
+ switch (eloc) {
+ case VALUE:
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
+ str_value, sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].common.path, str_value,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].common.path));
+ break;
+ case IS_READONLY:
+ bioscfg_drv.ordered_list_data[instance_id].common.is_readonly = int_value;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.ordered_list_data[instance_id].common.display_in_ui = int_value;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence = int_value;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.ordered_list_data[instance_id].common.sequence = int_value;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.ordered_list_data[instance_id].common.prerequisites_size = int_value;
+ if (int_value > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ /*
+ * This HACK is needed to keep the expected
+ * element list pointing to the right obj[elem].type
+ * when the size is zero. PREREQUISITES
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+ case PREREQUISITES:
+ size = bioscfg_drv.ordered_list_data[instance_id].common.prerequisites_size;
+
+ for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
+ ret = convert_hexstr_to_str(order_obj[elem + reqs].string.pointer,
+ order_obj[elem + reqs].string.length,
+ &str_value, &value_len);
+
+ if (ret)
+ continue;
+
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].common.prerequisites[reqs],
+ str_value,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].common.prerequisites[reqs]));
+
+ kfree(str_value);
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ bioscfg_drv.ordered_list_data[instance_id].common.security_level = int_value;
+ break;
+
+ case ORD_LIST_SIZE:
+ bioscfg_drv.ordered_list_data[instance_id].elements_size = int_value;
+ if (int_value > MAX_ELEMENTS_SIZE)
+ pr_warn("Ordered List size value exceeded the maximum number of elements supported or data may be malformed\n");
+ /*
+ * This HACK is needed to keep the expected
+ * element list pointing to the right obj[elem].type
+ * when the size is zero. ORD_LIST_ELEMENTS
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+ case ORD_LIST_ELEMENTS:
+ size = bioscfg_drv.ordered_list_data[instance_id].elements_size;
+
+ /*
+ * Ordered list data is stored in hex and comma separated format
+ * Convert the data and split it to show each element
+ */
+ ret = convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len);
+ if (ret)
+ goto exit_list_package;
+
+ part_tmp = tmpstr;
+ part = strsep(&part_tmp, ",");
+ if (!part)
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].elements[0],
+ tmpstr,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].elements[0]));
+
+ for (elem = 1; elem < MAX_ELEMENTS_SIZE && part; elem++) {
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].elements[elem],
+ part,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].elements[elem]));
+ part = strsep(&part_tmp, ",");
+ }
+
+ kfree(tmpstr);
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Ordered_List attribute or data may be malformed\n", elem);
+ break;
+ }
+ kfree(tmpstr);
+ kfree(str_value);
+ }
+
+exit_list_package:
+ kfree(tmpstr);
+ kfree(str_value);
+ return 0;
+}
+
+/*
+ * populate_ordered_list_data() - Populate all properties of an
+ * instance under ordered list attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ * @enum_property_count: Total properties count under ordered list type
+ */
+int populate_ordered_list_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+
+ bioscfg_drv.ordered_list_data[instance_id].attr_name_kobj = attr_name_kobj;
+
+ /* Populate ordered list elements */
+ populate_ordered_list_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+ update_attribute_permissions(bioscfg_drv.ordered_list_data[instance_id].common.is_readonly,
+ &ordered_list_current_val);
+ friendly_user_name_update(bioscfg_drv.ordered_list_data[instance_id].common.path,
+ attr_name_kobj->name,
+ bioscfg_drv.ordered_list_data[instance_id].common.display_name,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name));
+
+ return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group);
+}
+
+int populate_ordered_list_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ int ret;
+ char *dst = NULL;
+ int elem;
+ int reqs;
+ int integer;
+ int size = 0;
+ int values;
+ int dst_size = *buffer_size / sizeof(u16);
+
+ dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
+ if (!dst)
+ return -ENOMEM;
+
+ elem = 0;
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1; elem < 3; elem++) {
+
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0)
+ continue;
+
+ switch (elem) {
+ case VALUE:
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
+ dst, sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].common.path, dst,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].common.path));
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Ordered list attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+ for (elem = 3; elem < ORDERED_ELEM_CNT; elem++) {
+
+ if (elem != PREREQUISITES && elem != ORD_LIST_ELEMENTS) {
+ ret = get_integer_from_buffer((int **)&buffer_ptr, buffer_size, (int *)&integer);
+ if (ret < 0)
+ continue;
+ }
+
+ switch (elem) {
+
+ case IS_READONLY:
+ bioscfg_drv.ordered_list_data[instance_id].common.is_readonly = integer;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.ordered_list_data[instance_id].common.display_in_ui = integer;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence = integer;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.ordered_list_data[instance_id].common.sequence = integer;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.ordered_list_data[instance_id].common.prerequisites_size = integer;
+ if (integer > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ // PREREQUISITES:
+ elem++;
+ size = bioscfg_drv.ordered_list_data[instance_id].common.prerequisites_size;
+ for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0)
+ continue;
+
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].common.prerequisites[reqs],
+ dst,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].common.prerequisites[reqs]));
+ }
+ break;
+ case SECURITY_LEVEL:
+ bioscfg_drv.ordered_list_data[instance_id].common.security_level = integer;
+ break;
+ case ORD_LIST_SIZE:
+ bioscfg_drv.ordered_list_data[instance_id].elements_size = integer;
+ if (integer > MAX_ELEMENTS_SIZE)
+ pr_warn("Ordered List size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ // ORD_LIST_ELEMENTS:
+ elem++;
+ size = bioscfg_drv.ordered_list_data[instance_id].elements_size;
+ for (values = 0; values < size && values < MAX_ELEMENTS_SIZE; values++) {
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0)
+ continue;
+
+ strscpy(bioscfg_drv.ordered_list_data[instance_id].elements[values],
+ dst,
+ sizeof(bioscfg_drv.ordered_list_data[instance_id].elements)[values]);
+ }
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Ordered list attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+ kfree(dst);
+
+ return 0;
+}
+
+
+/*
+ * exit_ordered_list_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void exit_ordered_list_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.ordered_list_instances_count; instance_id++) {
+ struct kobject *attr_name_kobj = bioscfg_drv.ordered_list_data[instance_id].attr_name_kobj;
+
+ if (attr_name_kobj)
+ sysfs_remove_group(attr_name_kobj,
+ &ordered_list_attr_group);
+ }
+ bioscfg_drv.ordered_list_instances_count = 0;
+
+ kfree(bioscfg_drv.ordered_list_data);
+ bioscfg_drv.ordered_list_data = NULL;
+}
--
2.34.1

2023-04-20 16:56:10

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 04/14] HP BIOSCFG driver - int-attributes

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../x86/hp/hp-bioscfg/int-attributes.c | 474 ++++++++++++++++++
1 file changed, 474 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/int-attributes.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
new file mode 100644
index 000000000000..d8ee39dac3f9
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to integer type attributes under
+ * BIOS Enumeration GUID for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 Hewlett-Packard Inc.
+ */
+
+#include "bioscfg.h"
+
+GET_INSTANCE_ID(integer);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_integer_instance_id(kobj);
+
+ if (instance_id < 0)
+ return instance_id;
+
+ return sysfs_emit(buf, "%d\n",
+ bioscfg_drv.integer_data[instance_id].current_value);
+}
+
+/*
+ * validate_integer_input() -
+ * Validate input of current_value against lower and upper bound
+ *
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_integer_input(int instance_id, char *buf)
+{
+ int in_val;
+ int ret;
+
+
+ /* BIOS treats it as a read only attribute */
+ if (bioscfg_drv.integer_data[instance_id].common.is_readonly)
+ return -EIO;
+
+ ret = kstrtoint(buf, 10, &in_val);
+ if (ret < 0)
+ return ret;
+
+
+ if (in_val < bioscfg_drv.integer_data[instance_id].lower_bound ||
+ in_val > bioscfg_drv.integer_data[instance_id].upper_bound)
+ return -ERANGE;
+
+ /*
+ * set pending reboot flag depending on
+ * "RequiresPhysicalPresence" value
+ */
+ if (bioscfg_drv.integer_data[instance_id].common.requires_physical_presence)
+ bioscfg_drv.pending_reboot = true;
+ return 0;
+}
+
+static void update_integer_value(int instance_id, char *attr_value)
+{
+ int in_val;
+ int ret;
+
+ ret = kstrtoint(attr_value, 10, &in_val);
+ if (ret == 0)
+ bioscfg_drv.integer_data[instance_id].current_value = in_val;
+}
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, integer);
+static struct kobj_attribute integer_display_langcode =
+ __ATTR_RO(display_name_language_code);
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, integer);
+static struct kobj_attribute integer_display_name =
+ __ATTR_RO(display_name);
+
+ATTRIBUTE_PROPERTY_STORE(current_value, integer);
+static struct kobj_attribute integer_current_val =
+ __ATTR_RW_MODE(current_value, 0644);
+
+ATTRIBUTE_N_PROPERTY_SHOW(lower_bound, integer);
+static struct kobj_attribute integer_lower_bound =
+ __ATTR_RO(lower_bound);
+
+ATTRIBUTE_N_PROPERTY_SHOW(upper_bound, integer);
+static struct kobj_attribute integer_upper_bound =
+ __ATTR_RO(upper_bound);
+
+ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, integer);
+static struct kobj_attribute integer_prerequisites_size_val =
+ __ATTR_RO(prerequisites_size);
+
+ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, integer);
+static struct kobj_attribute integer_prerequisites_val =
+ __ATTR_RO(prerequisites);
+
+ATTRIBUTE_N_PROPERTY_SHOW(scalar_increment, integer);
+static struct kobj_attribute integer_scalar_increment =
+ __ATTR_RO(scalar_increment);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "integer\n");
+}
+static struct kobj_attribute integer_type =
+ __ATTR_RO(type);
+
+static struct attribute *integer_attrs[] = {
+ &integer_display_langcode.attr,
+ &integer_display_name.attr,
+ &integer_current_val.attr,
+ &integer_lower_bound.attr,
+ &integer_upper_bound.attr,
+ &integer_scalar_increment.attr,
+ &integer_prerequisites_size_val.attr,
+ &integer_prerequisites_val.attr,
+ &integer_type.attr,
+ NULL
+};
+
+static const struct attribute_group integer_attr_group = {
+ .attrs = integer_attrs,
+};
+
+int alloc_integer_data(void)
+{
+ int ret = 0;
+
+ bioscfg_drv.integer_instances_count = get_instance_count(HP_WMI_BIOS_INTEGER_GUID);
+ bioscfg_drv.integer_data = kcalloc(bioscfg_drv.integer_instances_count,
+ sizeof(struct integer_data), GFP_KERNEL);
+
+ if (!bioscfg_drv.integer_data) {
+ bioscfg_drv.integer_instances_count = 0;
+ ret = -ENOMEM;
+ }
+ return ret;
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_integer_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [INT_LOWER_BOUND] = ACPI_TYPE_INTEGER,
+ [INT_UPPER_BOUND] = ACPI_TYPE_INTEGER,
+ [INT_SCALAR_INCREMENT] = ACPI_TYPE_INTEGER
+};
+
+/*
+ * populate_int_data() -
+ * Populate all properties of an instance under integer attribute
+ *
+ * @integer_obj: ACPI object with integer data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_integer_package_data(union acpi_object *integer_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.integer_data[instance_id].attr_name_kobj = attr_name_kobj;
+ populate_integer_elements_from_package(integer_obj,
+ integer_obj->package.count,
+ instance_id);
+ update_attribute_permissions(bioscfg_drv.integer_data[instance_id].common.is_readonly,
+ &integer_current_val);
+ friendly_user_name_update(bioscfg_drv.integer_data[instance_id].common.path,
+ attr_name_kobj->name,
+ bioscfg_drv.integer_data[instance_id].common.display_name,
+ sizeof(bioscfg_drv.integer_data[instance_id].common.display_name));
+ return sysfs_create_group(attr_name_kobj, &integer_attr_group);
+}
+
+int populate_integer_elements_from_package(union acpi_object *integer_obj,
+ int integer_obj_count,
+ int instance_id)
+{
+ char *str_value = NULL;
+ int value_len;
+ int ret = 0;
+ u32 size = 0;
+ u32 int_value;
+ int elem = 0;
+ int reqs;
+ int eloc;
+
+ if (!integer_obj)
+ return -EINVAL;
+
+ strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1, eloc = 1; elem < integer_obj_count; elem++, eloc++) {
+
+ /* ONLY look at the first INTEGER_ELEM_CNT elements */
+ if (eloc == INTEGER_ELEM_CNT)
+ goto exit_integer_package;
+
+ switch (integer_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+
+ if (elem != PREREQUISITES) {
+ ret = convert_hexstr_to_str(integer_obj[elem].string.pointer,
+ integer_obj[elem].string.length,
+ &str_value, &value_len);
+ if (ret)
+ continue;
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)integer_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", integer_obj[elem].type);
+ continue;
+ }
+ /* Check that both expected and read object type match */
+ if (expected_integer_types[eloc] != integer_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_integer_types[eloc], elem, integer_obj[elem].type);
+ return -EIO;
+ }
+ /* Assign appropriate element value to corresponding field*/
+ switch (eloc) {
+ case VALUE:
+ ret = kstrtoint(str_value, 10, &int_value);
+ if (ret)
+ continue;
+
+ bioscfg_drv.integer_data[instance_id].current_value = int_value;
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.integer_data[instance_id].common.path, str_value,
+ sizeof(bioscfg_drv.integer_data[instance_id].common.path));
+ break;
+ case IS_READONLY:
+ bioscfg_drv.integer_data[instance_id].common.is_readonly = int_value;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.integer_data[instance_id].common.display_in_ui = int_value;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.integer_data[instance_id].common.requires_physical_presence = int_value;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.integer_data[instance_id].common.sequence = int_value;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.integer_data[instance_id].common.prerequisites_size = int_value;
+
+ if (int_value > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+ /*
+ * This HACK is needed to keep the expected
+ * element list pointing to the right obj[elem].type
+ * when the size is zero. PREREQUISITES
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+ case PREREQUISITES:
+ size = bioscfg_drv.integer_data[instance_id].common.prerequisites_size;
+
+ for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
+ if (elem >= integer_obj_count) {
+ pr_err("Error elem-objects package is too small\n");
+ return -EINVAL;
+ }
+
+ ret = convert_hexstr_to_str(integer_obj[elem + reqs].string.pointer,
+ integer_obj[elem + reqs].string.length,
+ &str_value, &value_len);
+
+ if (ret)
+ continue;
+
+ strscpy(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs],
+ str_value,
+ sizeof(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs]));
+ kfree(str_value);
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ bioscfg_drv.integer_data[instance_id].common.security_level = int_value;
+ break;
+ case INT_LOWER_BOUND:
+ bioscfg_drv.integer_data[instance_id].lower_bound = int_value;
+ break;
+ case INT_UPPER_BOUND:
+ bioscfg_drv.integer_data[instance_id].upper_bound = int_value;
+ break;
+ case INT_SCALAR_INCREMENT:
+ bioscfg_drv.integer_data[instance_id].scalar_increment = int_value;
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+exit_integer_package:
+ kfree(str_value);
+ return 0;
+}
+
+
+/*
+ * populate_integer_buffer_data() -
+ * Populate all properties of an instance under integer attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.integer_data[instance_id].attr_name_kobj = attr_name_kobj;
+
+ /* Populate integer elements */
+ populate_integer_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+ update_attribute_permissions(bioscfg_drv.integer_data[instance_id].common.is_readonly,
+ &integer_current_val);
+ friendly_user_name_update(bioscfg_drv.integer_data[instance_id].common.path,
+ attr_name_kobj->name,
+ bioscfg_drv.integer_data[instance_id].common.display_name,
+ sizeof(bioscfg_drv.integer_data[instance_id].common.display_name));
+
+ return sysfs_create_group(attr_name_kobj, &integer_attr_group);
+}
+
+int populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ char *dst = NULL;
+ int elem;
+ int reqs;
+ int integer;
+ int size = 0;
+ int ret;
+ int dst_size = *buffer_size / sizeof(u16);
+
+ dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
+ if (!dst)
+ return -ENOMEM;
+
+ elem = 0;
+ strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1; elem < 3; elem++) {
+
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0)
+ continue;
+
+ switch (elem) {
+ case VALUE:
+ ret = kstrtoint(dst, 10, &integer);
+ if (ret)
+ continue;
+
+ bioscfg_drv.integer_data[instance_id].current_value = integer;
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.integer_data[instance_id].common.path, dst,
+ sizeof(bioscfg_drv.integer_data[instance_id].common.path));
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+
+ for (elem = 3; elem < INTEGER_ELEM_CNT; elem++) {
+
+ if (elem != PREREQUISITES) {
+ ret = get_integer_from_buffer((int **)&buffer_ptr, buffer_size, (int *)&integer);
+ if (ret < 0)
+ continue;
+ }
+
+ switch (elem) {
+
+ case IS_READONLY:
+ bioscfg_drv.integer_data[instance_id].common.is_readonly = integer;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.integer_data[instance_id].common.display_in_ui = integer;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.integer_data[instance_id].common.requires_physical_presence = integer;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.integer_data[instance_id].common.sequence = integer;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.integer_data[instance_id].common.prerequisites_size = integer;
+ size = integer;
+ if (size > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ // PREREQUISITES:
+ elem++;
+ for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0)
+ continue;
+
+ strscpy(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs],
+ dst,
+ sizeof(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs]));
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ bioscfg_drv.integer_data[instance_id].common.security_level = integer;
+ break;
+ case INT_LOWER_BOUND:
+ bioscfg_drv.integer_data[instance_id].lower_bound = integer;
+ break;
+ case INT_UPPER_BOUND:
+ bioscfg_drv.integer_data[instance_id].upper_bound = integer;
+ break;
+ case INT_SCALAR_INCREMENT:
+ bioscfg_drv.integer_data[instance_id].scalar_increment = integer;
+ break;
+
+ default:
+ pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+ kfree(dst);
+
+ return 0;
+}
+
+/*
+ * exit_integer_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void exit_integer_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.integer_instances_count; instance_id++) {
+
+ struct kobject *attr_name_kobj = bioscfg_drv.integer_data[instance_id].attr_name_kobj;
+
+ if (attr_name_kobj)
+ sysfs_remove_group(attr_name_kobj, &integer_attr_group);
+ }
+ bioscfg_drv.integer_instances_count = 0;
+
+ kfree(bioscfg_drv.integer_data);
+ bioscfg_drv.integer_data = NULL;
+}
--
2.34.1

2023-04-20 16:56:13

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 06/14] HP BIOSCFG driver - passwdobj-attributes

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../x86/hp/hp-bioscfg/passwdobj-attributes.c | 669 ++++++++++++++++++
1 file changed, 669 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
new file mode 100644
index 000000000000..c03b3a71e9c4
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
@@ -0,0 +1,669 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to password object type attributes under
+ * BIOS PASSWORD for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+#include <asm-generic/posix_types.h>
+
+GET_INSTANCE_ID(password);
+/*
+ * Clear all passwords copied to memory for a particular
+ * authentication instance
+ */
+int clear_passwords(const int instance)
+{
+ if (!bioscfg_drv.password_data[instance].is_enabled)
+ return 0;
+
+ memset(bioscfg_drv.password_data[instance].current_password,
+ 0, sizeof(bioscfg_drv.password_data[instance].current_password));
+ memset(bioscfg_drv.password_data[instance].new_password,
+ 0, sizeof(bioscfg_drv.password_data[instance].new_password));
+
+ return 0;
+}
+
+/*
+ * Clear all credentials copied to memory for both Power-ON and Setup
+ * BIOS instances
+ */
+int clear_all_credentials(void)
+{
+ int instance;
+
+ /* clear all passwords */
+ for (instance = 0; instance < bioscfg_drv.password_instances_count; instance++)
+ clear_passwords(instance);
+
+ /* clear auth_token */
+ kfree(bioscfg_drv.spm_data.auth_token);
+ bioscfg_drv.spm_data.auth_token = NULL;
+
+ return 0;
+}
+
+int get_password_instance_for_type(const char *name)
+{
+ int count = bioscfg_drv.password_instances_count;
+ int instance;
+
+ for (instance = 0; instance < count; instance++) {
+ if (strcmp(bioscfg_drv.password_data[instance].common.display_name, name) == 0)
+ return instance;
+ }
+ return -EINVAL;
+}
+
+int validate_password_input(int instance_id, const char *buf)
+{
+ int length;
+
+ length = strlen(buf);
+ if (buf[length-1] == '\n')
+ length--;
+
+ if (length > MAX_PASSWD_SIZE)
+ return INVALID_BIOS_AUTH;
+
+ if (bioscfg_drv.password_data[instance_id].min_password_length > length ||
+ bioscfg_drv.password_data[instance_id].max_password_length < length)
+ return INVALID_BIOS_AUTH;
+ return SUCCESS;
+}
+
+int password_is_set(const char *name)
+{
+ int id;
+
+ id = get_password_instance_for_type(name);
+ if (id < 0)
+ return 0;
+
+ return bioscfg_drv.password_data[id].is_enabled;
+}
+
+ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password);
+static struct kobj_attribute password_is_password_set = __ATTR_RO(is_enabled);
+
+static ssize_t current_password_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ char *p, *buf_cp;
+ int id, ret = 0;
+
+ buf_cp = kstrdup(buf, GFP_KERNEL);
+ if (!buf_cp) {
+ ret = -ENOMEM;
+ goto exit_password;
+ }
+
+ p = memchr(buf_cp, '\n', count);
+
+ if (p != NULL)
+ *p = '\0';
+
+ id = get_password_instance_id(kobj);
+
+ if (id >= 0)
+ ret = validate_password_input(id, buf_cp);
+
+ if (!ret) {
+ strscpy(bioscfg_drv.password_data[id].current_password,
+ buf_cp,
+ sizeof(bioscfg_drv.password_data[id].current_password));
+ /*
+ * set pending reboot flag depending on
+ * "RequiresPhysicalPresence" value
+ */
+ if (bioscfg_drv.password_data[id].common.requires_physical_presence)
+ bioscfg_drv.pending_reboot = true;
+ }
+
+exit_password:
+ kfree(buf_cp);
+ return ret ? ret : count;
+}
+static struct kobj_attribute password_current_password = __ATTR_WO(current_password);
+
+static ssize_t new_password_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ char *p, *buf_cp = NULL;
+ int id = 0;
+ int ret = -EIO;
+
+ buf_cp = kstrdup(buf, GFP_KERNEL);
+ if (!buf_cp) {
+ ret = -ENOMEM;
+ goto exit_password;
+ }
+
+ p = memchr(buf_cp, '\n', count);
+
+ if (p != NULL)
+ *p = '\0';
+
+ id = get_password_instance_id(kobj);
+
+ if (id >= 0)
+ ret = validate_password_input(id, buf_cp);
+
+ if (!ret)
+ strscpy(bioscfg_drv.password_data[id].new_password,
+ buf_cp,
+ sizeof(bioscfg_drv.password_data[id].new_password));
+
+ if (!ret)
+ ret = hp_set_attribute(kobj->name, buf_cp);
+
+exit_password:
+ /*
+ * Regardless of the results both new and current passwords
+ * will be set to zero and avoid security issues
+ */
+ clear_passwords(id);
+
+ kfree(buf_cp);
+ return ret ? ret : count;
+}
+
+static struct kobj_attribute password_new_password = __ATTR_WO(new_password);
+
+
+ATTRIBUTE_N_PROPERTY_SHOW(min_password_length, password);
+static struct kobj_attribute password_min_password_length = __ATTR_RO(min_password_length);
+
+ATTRIBUTE_N_PROPERTY_SHOW(max_password_length, password);
+static struct kobj_attribute password_max_password_length = __ATTR_RO(max_password_length);
+
+static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ if (strcmp(kobj->name, SETUP_PASSWD) == 0)
+ return sysfs_emit(buf, "%s\n", BIOS_ADMIN);
+
+ if (strcmp(kobj->name, POWER_ON_PASSWD) == 0)
+ return sysfs_emit(buf, "%s\n", POWER_ON);
+
+ return -EIO;
+}
+static struct kobj_attribute password_role = __ATTR_RO(role);
+
+static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int i = get_password_instance_id(kobj);
+
+ if (i < 0)
+ return i;
+
+ if (bioscfg_drv.password_data[i].mechanism != PASSWORD)
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", PASSWD_MECHANISM_TYPES);
+}
+static struct kobj_attribute password_mechanism = __ATTR_RO(mechanism);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "password\n");
+}
+static struct kobj_attribute password_type = __ATTR_RO(type);
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, password);
+static struct kobj_attribute password_display_name =
+ __ATTR_RO(display_name);
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, password);
+static struct kobj_attribute password_display_langcode =
+ __ATTR_RO(display_name_language_code);
+
+ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, password);
+static struct kobj_attribute password_prerequisites_size_val =
+ __ATTR_RO(prerequisites_size);
+
+ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, password);
+static struct kobj_attribute password_prerequisites_val =
+ __ATTR_RO(prerequisites);
+
+ATTRIBUTE_N_PROPERTY_SHOW(encodings_size, password);
+static struct kobj_attribute password_encodings_size_val =
+ __ATTR_RO(encodings_size);
+
+ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password);
+static struct kobj_attribute password_encodings_val =
+ __ATTR_RO(encodings);
+
+
+static struct attribute *password_attrs[] = {
+ &password_is_password_set.attr,
+ &password_min_password_length.attr,
+ &password_max_password_length.attr,
+ &password_current_password.attr,
+ &password_new_password.attr,
+ &password_role.attr,
+ &password_mechanism.attr,
+ &password_type.attr,
+ &password_display_name.attr,
+ &password_display_langcode.attr,
+ &password_prerequisites_size_val.attr,
+ &password_prerequisites_val.attr,
+ &password_encodings_val.attr,
+ &password_encodings_size_val.attr,
+ NULL
+};
+
+static const struct attribute_group bios_password_attr_group = {
+ .attrs = password_attrs
+};
+
+static const struct attribute_group system_password_attr_group = {
+ .attrs = password_attrs
+};
+
+int alloc_password_data(void)
+{
+ int ret = 0;
+
+ bioscfg_drv.password_instances_count = get_instance_count(HP_WMI_BIOS_PASSWORD_GUID);
+ bioscfg_drv.password_data = kcalloc(bioscfg_drv.password_instances_count,
+ sizeof(struct password_data), GFP_KERNEL);
+ if (!bioscfg_drv.password_data) {
+ bioscfg_drv.password_instances_count = 0;
+ ret = -ENOMEM;
+ }
+
+ return ret;
+}
+
+/*
+ * populate_password_package_data -
+ * Populate all properties for an instance under password attribute
+ *
+ * @password_obj: ACPI object with password data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_password_package_data(union acpi_object *password_obj, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.password_data[instance_id].attr_name_kobj = attr_name_kobj;
+
+ populate_password_elements_from_package(password_obj,
+ password_obj->package.count,
+ instance_id);
+
+ if (strcmp(attr_name_kobj->name, "Setup Password") == 0) {
+ /* Save system authentication instance for easy access */
+ return sysfs_create_group(attr_name_kobj, &bios_password_attr_group);
+ }
+
+ return sysfs_create_group(attr_name_kobj, &system_password_attr_group);
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_password_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [PSWD_MIN_LENGTH] = ACPI_TYPE_INTEGER,
+ [PSWD_MAX_LENGTH] = ACPI_TYPE_INTEGER,
+ [PSWD_SIZE] = ACPI_TYPE_INTEGER,
+ [PSWD_ENCODINGS] = ACPI_TYPE_STRING,
+ [PSWD_IS_SET] = ACPI_TYPE_INTEGER
+};
+
+
+int populate_password_elements_from_package(union acpi_object *password_obj,
+ int password_obj_count,
+ int instance_id)
+{
+ char *str_value = NULL;
+ int value_len;
+ int ret = 0;
+ u32 size = 0;
+ u32 int_value;
+ int elem = 0;
+ int reqs;
+ int eloc;
+ int pos_values;
+
+
+ if (!password_obj)
+ return -EINVAL;
+
+ strscpy(bioscfg_drv.password_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.password_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1, eloc = 1; elem < password_obj_count; elem++, eloc++) {
+
+ /* ONLY look at the first PASSWORD_ELEM_CNT elements */
+ if (eloc == PASSWORD_ELEM_CNT)
+ goto exit_package;
+
+ switch (password_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+
+ if (PREREQUISITES != elem && PSWD_ENCODINGS != elem) {
+ ret = convert_hexstr_to_str(password_obj[elem].string.pointer,
+ password_obj[elem].string.length,
+ &str_value, &value_len);
+ if (ret)
+ continue;
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)password_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", password_obj[elem].type);
+ continue;
+ }
+
+ /* Check that both expected and read object type match */
+ if (expected_password_types[eloc] != password_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_password_types[eloc], elem, password_obj[elem].type);
+ return -EIO;
+ }
+
+ /* Assign appropriate element value to corresponding field*/
+ switch (eloc) {
+ case VALUE:
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.password_data[instance_id].common.path, str_value,
+ sizeof(bioscfg_drv.password_data[instance_id].common.path));
+ break;
+ case IS_READONLY:
+ bioscfg_drv.password_data[instance_id].common.is_readonly = int_value;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.password_data[instance_id].common.display_in_ui = int_value;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.password_data[instance_id].common.requires_physical_presence = int_value;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.password_data[instance_id].common.sequence = int_value;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.password_data[instance_id].common.prerequisites_size = int_value;
+ if (int_value > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+ /*
+ * This HACK is needed to keep the expected
+ * element list pointing to the right obj[elem].type
+ * when the size is zero. PREREQUISITES
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+ case PREREQUISITES:
+ size = bioscfg_drv.password_data[instance_id].common.prerequisites_size;
+
+ for (reqs = 0; reqs < size; reqs++) {
+ ret = convert_hexstr_to_str(password_obj[elem + reqs].string.pointer,
+ password_obj[elem + reqs].string.length,
+ &str_value, &value_len);
+
+ if (ret)
+ break;
+
+ strscpy(bioscfg_drv.password_data[instance_id].common.prerequisites[reqs],
+ str_value,
+ sizeof(bioscfg_drv.password_data[instance_id].common.prerequisites[reqs]));
+
+ kfree(str_value);
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ bioscfg_drv.password_data[instance_id].common.security_level = int_value;
+ break;
+
+ case PSWD_MIN_LENGTH:
+ bioscfg_drv.password_data[instance_id].min_password_length = int_value;
+ break;
+ case PSWD_MAX_LENGTH:
+ bioscfg_drv.password_data[instance_id].max_password_length = int_value;
+ break;
+ case PSWD_SIZE:
+ bioscfg_drv.password_data[instance_id].encodings_size = int_value;
+ if (int_value > MAX_ENCODINGS_SIZE)
+ pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ /*
+ * This HACK is needed to keep the expected
+ * element list pointing to the right obj[elem].type
+ * when the size is zero. PSWD_ENCODINGS
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+
+ case PSWD_ENCODINGS:
+ size = bioscfg_drv.password_data[instance_id].encodings_size;
+
+ for (pos_values = 0; pos_values < size && pos_values < MAX_ENCODINGS_SIZE; pos_values++) {
+ ret = convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer,
+ password_obj[elem + pos_values].string.length,
+ &str_value, &value_len);
+ if (ret)
+ break;
+
+ strscpy(bioscfg_drv.password_data[instance_id].encodings[pos_values],
+ str_value,
+ sizeof(bioscfg_drv.password_data[instance_id].encodings[pos_values]));
+ kfree(str_value);
+ }
+ break;
+ case PSWD_IS_SET:
+ bioscfg_drv.password_data[instance_id].is_enabled = int_value;
+ break;
+
+ default:
+ pr_warn("Invalid element: %d found in Password attribute or data may be malformed\n", elem);
+ break;
+ }
+ kfree(str_value);
+ }
+
+exit_package:
+ kfree(str_value);
+ return 0;
+}
+
+/*
+ * populate_password_buffer_data -
+ * Populate all properties for an instance under password object attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.password_data[instance_id].attr_name_kobj = attr_name_kobj;
+
+ /* Populate Password attributes */
+ populate_password_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+ friendly_user_name_update(bioscfg_drv.password_data[instance_id].common.path,
+ attr_name_kobj->name,
+ bioscfg_drv.password_data[instance_id].common.display_name,
+ sizeof(bioscfg_drv.password_data[instance_id].common.display_name));
+ if (strcmp(attr_name_kobj->name, "Setup Password") == 0)
+ return sysfs_create_group(attr_name_kobj, &bios_password_attr_group);
+
+ return sysfs_create_group(attr_name_kobj, &system_password_attr_group);
+}
+
+int populate_password_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ int ret;
+ char *dst = NULL;
+ int elem;
+ int reqs;
+ int integer;
+ int size = 0;
+ int values;
+ int dst_size = *buffer_size / sizeof(u16);
+
+ dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
+ if (!dst)
+ return -ENOMEM;
+
+ elem = 0;
+ strscpy(bioscfg_drv.password_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.password_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1; elem < 3; elem++) {
+
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0)
+ continue;
+
+ switch (elem) {
+ case VALUE:
+ strscpy(bioscfg_drv.password_data[instance_id].current_password,
+ dst, sizeof(bioscfg_drv.password_data[instance_id].current_password));
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.password_data[instance_id].common.path, dst,
+ sizeof(bioscfg_drv.password_data[instance_id].common.path));
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Password attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+
+ for (elem = 3; elem < PASSWORD_ELEM_CNT; elem++) {
+
+ if (elem != PREREQUISITES && elem != PSWD_ENCODINGS) {
+ ret = get_integer_from_buffer((int **)&buffer_ptr, buffer_size, (int *)&integer);
+ if (ret)
+ continue;
+ }
+
+ switch (elem) {
+ case IS_READONLY:
+ bioscfg_drv.password_data[instance_id].common.is_readonly = integer;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.password_data[instance_id].common.display_in_ui = integer;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.password_data[instance_id].common.requires_physical_presence = integer;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.password_data[instance_id].common.sequence = integer;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.password_data[instance_id].common.prerequisites_size = integer;
+ if (integer > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ // PREREQUISITES:
+ elem++;
+ size = bioscfg_drv.password_data[instance_id].common.prerequisites_size;
+ for (reqs = 0; reqs < size && reqs > MAX_PREREQUISITES_SIZE; reqs++) {
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0)
+ continue;
+
+ strscpy(bioscfg_drv.password_data[instance_id].common.prerequisites[reqs],
+ dst,
+ sizeof(bioscfg_drv.password_data[instance_id].common.prerequisites[reqs]));
+ }
+ break;
+ case SECURITY_LEVEL:
+ bioscfg_drv.password_data[instance_id].common.security_level = integer;
+ break;
+
+ case PSWD_MIN_LENGTH:
+ bioscfg_drv.password_data[instance_id].min_password_length = integer;
+ break;
+ case PSWD_MAX_LENGTH:
+ bioscfg_drv.password_data[instance_id].max_password_length = integer;
+ break;
+ case PSWD_SIZE:
+ bioscfg_drv.password_data[instance_id].encodings_size = integer;
+ if (integer > MAX_ENCODINGS_SIZE)
+ pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ // PSWD_ENCODINGS:
+ elem++;
+ size = bioscfg_drv.password_data[instance_id].encodings_size;
+ for (values = 0; values < size && values < MAX_ENCODINGS_SIZE; values++) {
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0)
+ continue;
+
+ strscpy(bioscfg_drv.password_data[instance_id].encodings[values],
+ dst,
+ sizeof(bioscfg_drv.password_data[instance_id].encodings[values]));
+
+ }
+ break;
+ case PSWD_IS_SET:
+ bioscfg_drv.password_data[instance_id].is_enabled = integer;
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Password attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+ kfree(dst);
+ return 0;
+}
+
+/*
+ * exit_password_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void exit_password_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.password_instances_count; instance_id++) {
+ struct kobject *attr_name_kobj = bioscfg_drv.password_data[instance_id].attr_name_kobj;
+
+ if (attr_name_kobj) {
+ if (strcmp(attr_name_kobj->name, SETUP_PASSWD) == 0)
+ sysfs_remove_group(attr_name_kobj,
+ &bios_password_attr_group);
+ else
+ sysfs_remove_group(attr_name_kobj,
+ &system_password_attr_group);
+ }
+ }
+ bioscfg_drv.password_instances_count = 0;
+ kfree(bioscfg_drv.password_data);
+ bioscfg_drv.password_data = NULL;
+}
--
2.34.1

2023-04-20 16:56:47

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 02/14] HP BIOSCFG driver - biosattr-interface

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../x86/hp/hp-bioscfg/biosattr-interface.c | 307 ++++++++++++++++++
1 file changed, 307 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
new file mode 100644
index 000000000000..f09dd41867f7
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to methods under BIOS interface GUID
+ * for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 Hewlett-Packard Inc.
+ */
+
+#include <linux/wmi.h>
+#include "bioscfg.h"
+
+#define SET_DEFAULT_VALUES_METHOD_ID 0x02
+#define SET_BIOS_DEFAULTS_METHOD_ID 0x03
+#define SET_ATTRIBUTE_METHOD_ID 0x04
+
+/*
+ * set_attribute() - Update an attribute value
+ * @a_name: The attribute name
+ * @a_value: The attribute value
+ *
+ * Sets an attribute to new value
+ */
+int hp_set_attribute(const char *a_name, const char *a_value)
+{
+ size_t security_area_size;
+ size_t a_name_size, a_value_size;
+ u16 *buffer = NULL;
+ u16 *start = NULL;
+ int buffer_size;
+ int ret = 0;
+ int instance;
+ char *auth_empty_value = "";
+ char *auth_token_choice = NULL;
+
+
+ mutex_lock(&bioscfg_drv.mutex);
+ if (!bioscfg_drv.bios_attr_wdev) {
+ ret = -ENODEV;
+ goto out_set_attribute;
+ }
+
+ instance = get_password_instance_for_type(SETUP_PASSWD);
+ if (instance < 0) {
+ ret = -EINVAL;
+ goto out_set_attribute;
+ }
+
+ if (strlen(bioscfg_drv.password_data[instance].current_password) == 0)
+ strscpy(bioscfg_drv.password_data[instance].current_password,
+ auth_empty_value,
+ sizeof(bioscfg_drv.password_data[instance].current_password));
+
+ /* Select which auth token to use; password or [auth token] */
+
+ if (bioscfg_drv.spm_data.auth_token != NULL)
+ auth_token_choice = bioscfg_drv.spm_data.auth_token;
+ else
+ auth_token_choice = bioscfg_drv.password_data[instance].current_password;
+
+ a_name_size = bioscfg_calculate_string_buffer(a_name);
+ a_value_size = bioscfg_calculate_string_buffer(a_value);
+ security_area_size = calculate_security_buffer(auth_token_choice);
+ buffer_size = a_name_size + a_value_size + security_area_size;
+
+ buffer = kmalloc(buffer_size + 1, GFP_KERNEL);
+ if (!buffer) {
+ ret = -ENOMEM;
+ goto out_set_attribute;
+ }
+
+ /* build variables to set */
+ start = buffer;
+ start = ascii_to_utf16_unicode(start, a_name);
+ if (!start)
+ goto out_set_attribute;
+
+ start = ascii_to_utf16_unicode(start, a_value);
+ if (!start)
+ goto out_set_attribute;
+
+ populate_security_buffer(start, auth_token_choice);
+
+ ret = hp_wmi_set_bios_setting(buffer, buffer_size);
+
+
+out_set_attribute:
+ kfree(buffer);
+ mutex_unlock(&bioscfg_drv.mutex);
+ return ret;
+}
+
+/*
+ * hp_wmi_perform_query
+ *
+ * query: The commandtype (enum hp_wmi_commandtype)
+ * write: The command (enum hp_wmi_command)
+ * buffer: Buffer used as input and/or output
+ * insize: Size of input buffer
+ * outsize: Size of output buffer
+ *
+ * returns zero on success
+ * an HP WMI query specific error code (which is positive)
+ * -EINVAL if the query was not successful at all
+ * -EINVAL if the output buffer size exceeds buffersize
+ *
+ * Note: The buffersize must at least be the maximum of the input and output
+ * size. E.g. Battery info query is defined to have 1 byte input
+ * and 128 byte output. The caller would do:
+ * buffer = kzalloc(128, GFP_KERNEL);
+ * ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ,
+ * buffer, 1, 128)
+ */
+int hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer,
+ int insize, int outsize)
+{
+ struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL };
+ struct bios_return *bios_return;
+ union acpi_object *obj = NULL;
+ struct bios_args *args = NULL;
+ int mid, actual_outsize;
+ size_t bios_args_size;
+ int ret;
+
+ mid = encode_outsize_for_pvsz(outsize);
+ if (WARN_ON(mid < 0))
+ return mid;
+
+ bios_args_size = struct_size(args, data, insize);
+ args = kmalloc(bios_args_size, GFP_KERNEL);
+ if (!args)
+ return -ENOMEM;
+
+ input.length = bios_args_size;
+ input.pointer = args;
+
+ args->signature = 0x55434553;
+ args->command = command;
+ args->commandtype = query;
+ args->datasize = insize;
+ memcpy(args->data, buffer, flex_array_size(args, data, insize));
+
+ ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
+ bioscfg_wmi_error_and_message(ret);
+
+ if (ret)
+ goto out_free;
+
+ obj = output.pointer;
+ if (!obj) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+ if (query != HPWMI_SECUREPLATFORM_GET_STATE &&
+ command != HPWMI_SECUREPLATFORM)
+ if (obj->type != ACPI_TYPE_BUFFER ||
+ obj->buffer.length < sizeof(*bios_return)) {
+ pr_warn("query 0x%x returned wrong type or too small buffer\n", query);
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+
+ bios_return = (struct bios_return *)obj->buffer.pointer;
+ ret = bios_return->return_code;
+ bioscfg_wmi_error_and_message(ret);
+
+ if (ret) {
+ if (ret != HPWMI_RET_UNKNOWN_COMMAND &&
+ ret != HPWMI_RET_UNKNOWN_CMDTYPE)
+ pr_warn("query 0x%x returned error 0x%x\n", query, ret);
+ goto out_free;
+ }
+
+ /* Ignore output data of zero size */
+ if (!outsize)
+ goto out_free;
+
+ actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return)));
+ memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize);
+ memset(buffer + actual_outsize, 0, outsize - actual_outsize);
+
+out_free:
+ kfree(obj);
+ kfree(args);
+ return ret;
+}
+
+static void *utf16_empty_string(u16 *p)
+{
+ *p++ = 2;
+ *p++ = (u8)0x00;
+ return p;
+}
+
+/*
+ * ascii_to_utf16_unicode - Convert ascii string to UTF-16 unicode
+ *
+ * BIOS supports UTF-16 characters that are 2 bytes long. No variable
+ * multi-byte language supported.
+ *
+ * @p: Unicode buffer address
+ * @str: string to convert to unicode
+ *
+ * Returns a void pointer to the buffer containing unicode string
+ */
+void *ascii_to_utf16_unicode(u16 *p, const u8 *str)
+{
+ int len = strlen(str);
+ int ret;
+
+ /*
+ * Add null character when reading an empty string
+ * "02 00 00 00"
+ */
+ if (len == 0)
+ return utf16_empty_string(p);
+
+ /* Move pointer len * 2 number of bytes */
+ *p++ = len * 2;
+ ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, p, len);
+ if (ret < 0) {
+ dev_err(bioscfg_drv.class_dev, "UTF16 conversion failed\n");
+ goto ascii_to_utf16_unicode_out;
+ }
+
+ if ((ret * sizeof(u16)) > U16_MAX) {
+ dev_err(bioscfg_drv.class_dev, "Error string too long\n");
+ goto ascii_to_utf16_unicode_out;
+ }
+
+ascii_to_utf16_unicode_out:
+ p += len;
+ return p;
+}
+
+/*
+ * hp_wmi_set_bios_setting - Set setting's value in BIOS
+ *
+ * @input_buffer: Input buffer address
+ * @input_size: Input buffer size
+ *
+ * Returns: Count of unicode characters written to BIOS if successful, otherwise
+ * -ENOMEM unable to allocate memory
+ * -EINVAL buffer not allocated or too small
+ */
+int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size)
+{
+ union acpi_object *obj;
+ struct acpi_buffer input = {input_size, input_buffer};
+ struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
+ int ret = 0;
+
+ ret = wmi_evaluate_method(HP_WMI_SET_BIOS_SETTING_GUID, 0, 1, &input, &output);
+
+ obj = output.pointer;
+ if (!obj)
+ return -EINVAL;
+
+ if (obj->type != ACPI_TYPE_INTEGER)
+ ret = -EINVAL;
+
+ ret = obj->integer.value;
+ bioscfg_wmi_error_and_message(ret);
+
+ kfree(obj);
+ return ret;
+}
+
+static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
+{
+ mutex_lock(&bioscfg_drv.mutex);
+ bioscfg_drv.bios_attr_wdev = wdev;
+ mutex_unlock(&bioscfg_drv.mutex);
+ return 0;
+}
+
+static void bios_attr_set_interface_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&bioscfg_drv.mutex);
+ bioscfg_drv.bios_attr_wdev = NULL;
+ mutex_unlock(&bioscfg_drv.mutex);
+}
+
+static const struct wmi_device_id bios_attr_set_interface_id_table[] = {
+ { .guid_string = HP_WMI_BIOS_GUID},
+ { }
+};
+static struct wmi_driver bios_attr_set_interface_driver = {
+ .driver = {
+ .name = DRIVER_NAME
+ },
+ .probe = bios_attr_set_interface_probe,
+ .remove = bios_attr_set_interface_remove,
+ .id_table = bios_attr_set_interface_id_table
+};
+
+int init_bios_attr_set_interface(void)
+{
+ return wmi_driver_register(&bios_attr_set_interface_driver);
+}
+
+void exit_bios_attr_set_interface(void)
+{
+ wmi_driver_unregister(&bios_attr_set_interface_driver);
+}
+
+MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table);
--
2.34.1

2023-04-20 16:56:54

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 11/14] HP BIOSCFG driver - spmobj-attributes

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../x86/hp/hp-bioscfg/spmobj-attributes.c | 405 ++++++++++++++++++
1 file changed, 405 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
new file mode 100644
index 000000000000..78228f44c39b
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
@@ -0,0 +1,405 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to secure platform management object type
+ * attributes under BIOS PASSWORD for use with hp-bioscfg driver
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+
+
+static const char * const spm_state_types[] = {
+ "not provisioned",
+ "provisioned",
+ "provisioning in progress"
+};
+
+static const char * const spm_mechanism_types[] = {
+ "not provision",
+ "signing-key",
+ "endorsement-key"
+};
+
+
+int check_spm_is_enabled(void)
+{
+ /* do we need to check the admin password is also configured */
+ return bioscfg_drv.spm_data.is_enabled;
+}
+
+/*
+ * calculate_security_buffer() - determines size of security buffer
+ * for authentication scheme
+ *
+ * @authentication: the authentication content
+ *
+ * Currently only supported type is Admin password
+ */
+size_t calculate_security_buffer(const char *authentication)
+{
+ int size;
+
+ if (authentication != NULL && strlen(authentication) > 0) {
+
+ size = (sizeof(u16) + (strlen(authentication) * sizeof(u16)));
+ if (strncmp(authentication, BEAM_PREFIX, strlen(BEAM_PREFIX)) != 0)
+ size += (strlen(UTF_PREFIX) * sizeof(u16));
+
+ return size;
+ }
+
+ size = sizeof(u16) * 2;
+ return size;
+}
+
+/*
+ * populate_security_buffer() - builds a security buffer for
+ * authentication scheme
+ *
+ * @buffer: the buffer to populate
+ * @authentication: the authentication content
+ *
+ * Currently only supported type is PLAIN TEXT
+ */
+void populate_security_buffer(u16 *buffer, const char *authentication)
+{
+ u16 *auth = buffer;
+ char *strprefix = NULL;
+
+ if (strncmp(authentication, BEAM_PREFIX, strlen(BEAM_PREFIX)) == 0) {
+ /*
+ * BEAM_PREFIX is append to buffer when a signature
+ * is provided and Sure Admin is enabled in BIOS
+ */
+ // BEAM_PREFIX found, convert part to unicode
+ auth = ascii_to_utf16_unicode(auth, authentication);
+ } else {
+ /*
+ * UTF-16 prefix is append to the * buffer when a BIOS
+ * admin password is configured in BIOS
+ */
+
+ // append UTF_PREFIX to part and then convert it to unicode
+ strprefix = kasprintf(GFP_KERNEL, "%s%s", UTF_PREFIX,
+ authentication);
+ if (!strprefix)
+ goto out_buffer;
+
+ auth = ascii_to_utf16_unicode(auth, strprefix);
+ }
+out_buffer:
+
+ kfree(strprefix);
+}
+
+ssize_t update_spm_state(void)
+{
+ int ret;
+ struct secureplatform_provisioning_data *data = NULL;
+
+ data = kmalloc(sizeof(struct secureplatform_provisioning_data),
+ GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto state_exit;
+ }
+
+ ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
+ HPWMI_SECUREPLATFORM, data, 0,
+ sizeof(*data));
+ if (ret < 0)
+ goto state_exit;
+
+ bioscfg_drv.spm_data.mechanism = data->state;
+ if (bioscfg_drv.spm_data.mechanism)
+ bioscfg_drv.spm_data.is_enabled = 1;
+
+state_exit:
+ kfree(data);
+
+ return ret;
+}
+
+ssize_t statusbin(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
+ HPWMI_SECUREPLATFORM, buf, 0,
+ sizeof(struct secureplatform_provisioning_data));
+
+ return ret ? -ENODEV : sizeof(struct secureplatform_provisioning_data);
+}
+
+/*
+ * status_show - Reads SPM status
+ */
+ssize_t status_show(struct kobject *kobj, struct kobj_attribute
+ *attr, char *buf)
+{
+ int ret, i;
+ struct secureplatform_provisioning_data *data = NULL;
+
+ data = kmalloc(sizeof(struct secureplatform_provisioning_data),
+ GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto status_exit;
+ }
+
+ ret = statusbin(kobj, attr, (char *)data);
+ if (ret < 0)
+ goto status_exit;
+
+ sysfs_emit(buf, "%s{\n", buf);
+ sysfs_emit(buf, "%s\t\"State\": \"%s\",\n", buf,
+ spm_state_types[data->state]);
+ sysfs_emit(buf, "%s\t\"Version\": \"%d.%d\",\n", buf, data->version[0],
+ data->version[1]);
+
+ /*
+ * state == 0 means secure platform management
+ * feature is not configured in BIOS.
+ */
+ if (data->state == 0)
+ goto status_exit;
+
+ sysfs_emit(buf, "%s\t\"Nonce\": %d,\n", buf, data->nonce);
+ sysfs_emit(buf, "%s\t\"FeaturesInUse\": %d,\n", buf, data->features);
+ sysfs_emit(buf, "%s\t\"EndorsementKeyMod\": \"", buf);
+
+ for (i = 255; i >= 0; i--)
+ sysfs_emit(buf, "%s %u", buf, data->kek_mod[i]);
+
+ sysfs_emit(buf, "%s \",\n", buf);
+ sysfs_emit(buf, "%s\t\"SigningKeyMod\": \"", buf);
+
+ for (i = 255; i >= 0; i--)
+ sysfs_emit(buf, "%s %u", buf, data->sk_mod[i]);
+
+ /* Return buf contents */
+
+ sysfs_emit(buf, "%s \"\n", buf);
+ sysfs_emit(buf, "%s}\n", buf);
+
+status_exit:
+ kfree(data);
+
+ return strnlen(buf, PAGE_SIZE);
+}
+
+static struct kobj_attribute password_spm_status = __ATTR_RO(status);
+
+ATTRIBUTE_SPM_N_PROPERTY_SHOW(is_enabled, spm);
+static struct kobj_attribute password_spm_is_key_enabled = __ATTR_RO(is_enabled);
+
+
+static ssize_t key_mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ spm_mechanism_types[bioscfg_drv.spm_data.mechanism]);
+}
+static struct kobj_attribute password_spm_key_mechanism = __ATTR_RO(key_mechanism);
+
+static ssize_t sk_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int length;
+
+ length = count;
+ if (buf[length-1] == '\n')
+ length--;
+
+ /* allocate space and copy current signing key */
+ bioscfg_drv.spm_data.signing_key = kmalloc(length, GFP_KERNEL);
+ if (!bioscfg_drv.spm_data.signing_key) {
+ ret = -ENOMEM;
+ goto exit_sk;
+ }
+
+ memcpy(bioscfg_drv.spm_data.signing_key, buf, length);
+ bioscfg_drv.spm_data.signing_key[length] = '\0';
+
+ /* submit signing key payload */
+ ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK,
+ HPWMI_SECUREPLATFORM,
+ (void *)bioscfg_drv.spm_data.signing_key,
+ length, 0);
+
+ if (!ret) {
+ bioscfg_drv.spm_data.mechanism = SIGNING_KEY;
+ bioscfg_drv.pending_reboot = true;
+ }
+
+exit_sk:
+ kfree(bioscfg_drv.spm_data.signing_key);
+ bioscfg_drv.spm_data.signing_key = NULL;
+
+ return ret ? ret : count;
+}
+
+static struct kobj_attribute password_spm_signing_key = __ATTR_WO(sk);
+
+static ssize_t kek_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int length;
+
+ length = count;
+ if (buf[length-1] == '\n')
+ length--;
+
+ /* allocate space and copy current signing key */
+ bioscfg_drv.spm_data.endorsement_key = kmalloc(length, GFP_KERNEL);
+ if (!bioscfg_drv.spm_data.endorsement_key) {
+ ret = -ENOMEM;
+ goto exit_kek;
+ }
+
+ memcpy(bioscfg_drv.spm_data.endorsement_key, buf, length);
+ bioscfg_drv.spm_data.endorsement_key[length] = '\0';
+
+ ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK,
+ HPWMI_SECUREPLATFORM,
+ (void *)bioscfg_drv.spm_data.endorsement_key,
+ count, 0);
+
+ if (!ret) {
+ bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY;
+ bioscfg_drv.pending_reboot = true;
+ }
+
+exit_kek:
+ kfree(bioscfg_drv.spm_data.endorsement_key);
+ bioscfg_drv.spm_data.endorsement_key = NULL;
+
+ return ret ? ret : count;
+}
+static struct kobj_attribute password_spm_endorsement_key = __ATTR_WO(kek);
+
+static ssize_t display_name_language_code_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", LANG_CODE_STR);
+}
+
+static struct kobj_attribute password_spm_display_langcode =
+ __ATTR_RO(display_name_language_code);
+
+
+static ssize_t display_name_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%s\n", SPM_STR_DESC);
+}
+static struct kobj_attribute password_spm_display_name = __ATTR_RO(display_name);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "secure-platform-management\n");
+}
+static struct kobj_attribute password_spm_type = __ATTR_RO(type);
+
+static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", BIOS_SPM);
+}
+static struct kobj_attribute password_spm_role = __ATTR_RO(role);
+
+static ssize_t auth_token_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = 0;
+ int length;
+
+ length = count;
+ if (buf[length-1] == '\n')
+ length--;
+
+ /* allocate space and copy current auth token */
+ bioscfg_drv.spm_data.auth_token = kmalloc(count, GFP_KERNEL);
+ if (!bioscfg_drv.spm_data.auth_token) {
+ ret = -ENOMEM;
+ goto exit_token;
+ }
+
+ memcpy(bioscfg_drv.spm_data.auth_token, buf, count);
+ bioscfg_drv.spm_data.auth_token[length] = '\0';
+ return count;
+
+
+exit_token:
+ kfree(bioscfg_drv.spm_data.auth_token);
+ bioscfg_drv.spm_data.auth_token = NULL;
+
+ return ret;
+
+}
+static struct kobj_attribute password_spm_auth_token = __ATTR_WO(auth_token);
+
+static struct attribute *secure_platform_attrs[] = {
+ &password_spm_display_name.attr,
+ &password_spm_display_langcode.attr,
+ &password_spm_is_key_enabled.attr,
+ &password_spm_signing_key.attr,
+ &password_spm_endorsement_key.attr,
+ &password_spm_key_mechanism.attr,
+ &password_spm_status.attr,
+ &password_spm_type.attr,
+ &password_spm_role.attr,
+ &password_spm_auth_token.attr,
+ NULL,
+};
+
+static const struct attribute_group secure_platform_attr_group = {
+ .attrs = secure_platform_attrs,
+};
+
+void exit_secure_platform_attributes(void)
+{
+ /* remove secure platform sysfs entry and free key data*/
+
+ kfree(bioscfg_drv.spm_data.endorsement_key);
+ bioscfg_drv.spm_data.endorsement_key = NULL;
+
+ kfree(bioscfg_drv.spm_data.signing_key);
+ bioscfg_drv.spm_data.signing_key = NULL;
+
+ kfree(bioscfg_drv.spm_data.auth_token);
+ bioscfg_drv.spm_data.auth_token = NULL;
+
+ if (bioscfg_drv.spm_data.attr_name_kobj)
+ sysfs_remove_group(bioscfg_drv.spm_data.attr_name_kobj,
+ &secure_platform_attr_group);
+}
+
+int populate_secure_platform_data(struct kobject *attr_name_kobj)
+{
+ /* Populate data for Secure Platform Management */
+ bioscfg_drv.spm_data.attr_name_kobj = attr_name_kobj;
+
+ strscpy(bioscfg_drv.spm_data.attribute_name, SPM_STR,
+ sizeof(bioscfg_drv.spm_data.attribute_name));
+ strscpy(bioscfg_drv.spm_data.display_name, SPM_STR_DESC,
+ sizeof(bioscfg_drv.spm_data.display_name));
+
+ bioscfg_drv.spm_data.is_enabled = 0;
+ bioscfg_drv.spm_data.mechanism = 0;
+ bioscfg_drv.pending_reboot = false;
+ update_spm_state();
+
+ bioscfg_drv.spm_data.endorsement_key = NULL;
+ bioscfg_drv.spm_data.signing_key = NULL;
+ bioscfg_drv.spm_data.auth_token = NULL;
+
+ return sysfs_create_group(attr_name_kobj, &secure_platform_attr_group);
+}
--
2.34.1

2023-04-20 16:56:57

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
1 file changed, 130 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
new file mode 100644
index 000000000000..72952758ffe3
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to sure start object type attributes under
+ * BIOS for use with hp-bioscfg driver
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+#include <asm-generic/posix_types.h>
+
+#define LOG_MAX_ENTRIES 254
+#define LOG_ENTRY_SIZE 16
+
+/*
+ * audit_log_entry_count_show - Reports the number of
+ * existing audit log entries available
+ * to be read
+ */
+static ssize_t audit_log_entry_count_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret;
+ u32 count = 0;
+
+ ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
+ HPWMI_SURESTART,
+ &count, 1, sizeof(count));
+
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d,%d,%d\n", count, LOG_ENTRY_SIZE,
+ LOG_MAX_ENTRIES);
+}
+
+/*
+ * audit_log_entries_show() - Return all entries found in log file
+ */
+static ssize_t audit_log_entries_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret;
+ int i;
+ u32 count = 0;
+
+ // Get the number of event logs
+ ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
+ HPWMI_SURESTART,
+ &count, 1, sizeof(count));
+
+ /*
+ * The show() api will not work if the audit logs ever go
+ * beyond 4KB
+ */
+ if (count * LOG_ENTRY_SIZE > PAGE_SIZE)
+ return -EFAULT;
+
+ if (ret < 0)
+ return ret;
+
+ /*
+ * We are guaranteed the buffer is 4KB so today all the event
+ * logs will fit
+ */
+
+ for (i = 0; ((i < count) & (ret >= 0)); i++) {
+ *buf = (i + 1);
+ ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
+ HPWMI_SURESTART,
+ buf, 1, 128);
+ if (ret >= 0)
+ buf += LOG_ENTRY_SIZE;
+ }
+
+ return (count * LOG_ENTRY_SIZE);
+}
+
+static struct kobj_attribute sure_start_audit_log_entry_count = __ATTR_RO(audit_log_entry_count);
+static struct kobj_attribute sure_start_audit_log_entries = __ATTR_RO(audit_log_entries);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "sure-start\n");
+}
+static struct kobj_attribute sure_start_type = __ATTR_RO(type);
+
+static ssize_t display_name_language_code_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", LANG_CODE_STR);
+}
+
+static struct kobj_attribute sure_start_display_langcode =
+ __ATTR_RO(display_name_language_code);
+
+
+static ssize_t display_name_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%s\n", SURE_START_DESC);
+}
+static struct kobj_attribute sure_start_display_name = __ATTR_RO(display_name);
+
+static struct attribute *sure_start_attrs[] = {
+ &sure_start_display_name.attr,
+ &sure_start_display_langcode.attr,
+ &sure_start_audit_log_entry_count.attr,
+ &sure_start_audit_log_entries.attr,
+ &sure_start_type.attr,
+ NULL
+};
+
+static const struct attribute_group sure_start_attr_group = {
+ .attrs = sure_start_attrs,
+};
+
+void exit_sure_start_attributes(void)
+{
+ sysfs_remove_group(bioscfg_drv.sure_start_attr_kobj,
+ &sure_start_attr_group);
+}
+
+int populate_sure_start_data(struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.sure_start_attr_kobj = attr_name_kobj;
+ return sysfs_create_group(attr_name_kobj, &sure_start_attr_group);
+}
--
2.34.1

2023-04-20 16:56:59

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 07/14] HP BIOSCFG driver - string-attributes

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../x86/hp/hp-bioscfg/string-attributes.c | 451 ++++++++++++++++++
1 file changed, 451 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/string-attributes.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
new file mode 100644
index 000000000000..7a14585d5506
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to string type attributes under
+ * HP_WMI_BIOS_STRING_GUID for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+
+#define WMI_STRING_TYPE "HPBIOS_BIOSString"
+
+GET_INSTANCE_ID(string);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_string_instance_id(kobj);
+
+ if (instance_id < 0)
+ return -EIO;
+
+ return sysfs_emit(buf, "%s\n",
+ bioscfg_drv.string_data[instance_id].current_value);
+}
+
+/*
+ * validate_string_input() -
+ * Validate input of current_value against min and max lengths
+ *
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_string_input(int instance_id, const char *buf)
+{
+ int in_len = strlen(buf);
+
+ /* BIOS treats it as a read only attribute */
+ if (bioscfg_drv.string_data[instance_id].common.is_readonly)
+ return -EIO;
+
+ if ((in_len < bioscfg_drv.string_data[instance_id].min_length) ||
+ (in_len > bioscfg_drv.string_data[instance_id].max_length))
+ return -ERANGE;
+
+ /*
+ * set pending reboot flag depending on
+ * "RequiresPhysicalPresence" value
+ */
+ if (bioscfg_drv.string_data[instance_id].common.requires_physical_presence)
+ bioscfg_drv.pending_reboot = true;
+ return 0;
+}
+
+static void update_string_value(int instance_id, char *attr_value)
+{
+ /* Write settings to BIOS */
+ strscpy(bioscfg_drv.string_data[instance_id].current_value,
+ attr_value,
+ sizeof(bioscfg_drv.string_data[instance_id].current_value));
+}
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, string);
+static struct kobj_attribute string_display_langcode =
+ __ATTR_RO(display_name_language_code);
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, string);
+static struct kobj_attribute string_display_name =
+ __ATTR_RO(display_name);
+
+ATTRIBUTE_PROPERTY_STORE(current_value, string);
+static struct kobj_attribute string_current_val =
+ __ATTR_RW_MODE(current_value, 0644);
+
+ATTRIBUTE_N_PROPERTY_SHOW(min_length, string);
+static struct kobj_attribute string_min_length =
+ __ATTR_RO(min_length);
+
+ATTRIBUTE_N_PROPERTY_SHOW(max_length, string);
+static struct kobj_attribute string_max_length =
+ __ATTR_RO(max_length);
+
+ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, string);
+static struct kobj_attribute string_prerequisites_size_val =
+ __ATTR_RO(prerequisites_size);
+
+ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, string);
+static struct kobj_attribute string_prerequisites_val =
+ __ATTR_RO(prerequisites);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "string\n");
+}
+static struct kobj_attribute string_type =
+ __ATTR_RO(type);
+
+static struct attribute *string_attrs[] = {
+ &string_display_langcode.attr,
+ &string_display_name.attr,
+ &string_current_val.attr,
+ &string_min_length.attr,
+ &string_max_length.attr,
+ &string_prerequisites_size_val.attr,
+ &string_prerequisites_val.attr,
+ &string_type.attr,
+ NULL
+};
+
+static const struct attribute_group string_attr_group = {
+ .attrs = string_attrs,
+};
+
+int alloc_string_data(void)
+{
+ int ret = 0;
+
+ bioscfg_drv.string_instances_count = get_instance_count(HP_WMI_BIOS_STRING_GUID);
+ bioscfg_drv.string_data = kcalloc(bioscfg_drv.string_instances_count,
+ sizeof(struct string_data), GFP_KERNEL);
+ if (!bioscfg_drv.string_data) {
+ bioscfg_drv.string_instances_count = 0;
+ ret = -ENOMEM;
+ }
+ return ret;
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_string_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [STR_MIN_LENGTH] = ACPI_TYPE_INTEGER,
+ [STR_MAX_LENGTH] = ACPI_TYPE_INTEGER
+};
+
+/*
+ * populate_string_package_data() -
+ * Populate all properties of an instance under string attribute
+ *
+ * @string_obj: ACPI object with string data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_string_package_data(union acpi_object *string_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.string_data[instance_id].attr_name_kobj = attr_name_kobj;
+
+ populate_string_elements_from_package(string_obj,
+ string_obj->package.count,
+ instance_id);
+
+ update_attribute_permissions(bioscfg_drv.string_data[instance_id].common.is_readonly,
+ &string_current_val);
+ friendly_user_name_update(bioscfg_drv.string_data[instance_id].common.path,
+ attr_name_kobj->name,
+ bioscfg_drv.string_data[instance_id].common.display_name,
+ sizeof(bioscfg_drv.string_data[instance_id].common.display_name));
+ return sysfs_create_group(attr_name_kobj, &string_attr_group);
+}
+
+int populate_string_elements_from_package(union acpi_object *string_obj,
+ int string_obj_count,
+ int instance_id)
+{
+ char *str_value = NULL;
+ int value_len;
+ int ret = 0;
+ u32 size = 0;
+ u32 int_value;
+ int elem = 0;
+ int reqs;
+ int eloc;
+
+ if (!string_obj)
+ return -EINVAL;
+
+ strscpy(bioscfg_drv.string_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.string_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1, eloc = 1; elem < string_obj_count; elem++, eloc++) {
+
+ /* ONLY look at the first STRING_ELEM_CNT elements */
+ if (eloc == STRING_ELEM_CNT)
+ goto exit_string_package;
+
+ switch (string_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+
+ if (elem != PREREQUISITES) {
+ ret = convert_hexstr_to_str(string_obj[elem].string.pointer,
+ string_obj[elem].string.length,
+ &str_value, &value_len);
+
+ if (ret)
+ continue;
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)string_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", string_obj[elem].type);
+ continue;
+ }
+
+ /* Check that both expected and read object type match */
+ if (expected_string_types[eloc] != string_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_string_types[eloc], elem, string_obj[elem].type);
+ return -EIO;
+ }
+
+ /* Assign appropriate element value to corresponding field*/
+ switch (eloc) {
+ case VALUE:
+ strscpy(bioscfg_drv.string_data[instance_id].current_value,
+ str_value, sizeof(bioscfg_drv.string_data[instance_id].current_value));
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.string_data[instance_id].common.path, str_value,
+ sizeof(bioscfg_drv.string_data[instance_id].common.path));
+ break;
+ case IS_READONLY:
+ bioscfg_drv.string_data[instance_id].common.is_readonly = int_value;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.string_data[instance_id].common.display_in_ui = int_value;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.string_data[instance_id].common.requires_physical_presence = int_value;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.string_data[instance_id].common.sequence = int_value;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.string_data[instance_id].common.prerequisites_size = int_value;
+ if (size > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+ /*
+ * This HACK is needed to keep the expected
+ * element list pointing to the right obj[elem].type
+ * when the size is zero. PREREQUISITES
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+ case PREREQUISITES:
+ size = bioscfg_drv.string_data[instance_id].common.prerequisites_size;
+
+ for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
+ if (elem >= string_obj_count) {
+ pr_err("Error elem-objects package is too small\n");
+ return -EINVAL;
+ }
+
+ ret = convert_hexstr_to_str(string_obj[elem + reqs].string.pointer,
+ string_obj[elem + reqs].string.length,
+ &str_value, &value_len);
+
+ if (ret)
+ continue;
+
+ strscpy(bioscfg_drv.string_data[instance_id].common.prerequisites[reqs],
+ str_value,
+ sizeof(bioscfg_drv.string_data[instance_id].common.prerequisites[reqs]));
+ kfree(str_value);
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ bioscfg_drv.string_data[instance_id].common.security_level = int_value;
+ break;
+ case STR_MIN_LENGTH:
+ bioscfg_drv.string_data[instance_id].min_length = int_value;
+ break;
+ case STR_MAX_LENGTH:
+ bioscfg_drv.string_data[instance_id].max_length = int_value;
+ break;
+ default:
+ pr_warn("Invalid element: %d found in String attribute or data may be malformed\n", elem);
+ break;
+ }
+
+ kfree(str_value);
+ }
+
+exit_string_package:
+ kfree(str_value);
+ return 0;
+}
+
+/*
+ * populate_string_data() -
+ * Populate all properties of an instance under string attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.string_data[instance_id].attr_name_kobj = attr_name_kobj;
+
+ populate_string_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+
+ update_attribute_permissions(bioscfg_drv.string_data[instance_id].common.is_readonly,
+ &string_current_val);
+ friendly_user_name_update(bioscfg_drv.string_data[instance_id].common.path,
+ attr_name_kobj->name,
+ bioscfg_drv.string_data[instance_id].common.display_name,
+ sizeof(bioscfg_drv.string_data[instance_id].common.display_name));
+
+ return sysfs_create_group(attr_name_kobj, &string_attr_group);
+}
+
+int populate_string_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ int ret;
+ char *dst = NULL;
+ int elem;
+ int reqs;
+ int int_value;
+ int size = 0;
+ int dst_size = *buffer_size / sizeof(u16);
+
+ dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
+ if (!dst)
+ return -ENOMEM;
+
+ strscpy(bioscfg_drv.string_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.string_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1; elem < 3; elem++) {
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ /* Ignore. Zero length string values */
+ if (ret < 0)
+ continue;
+
+ switch (elem) {
+ case VALUE:
+ strscpy(bioscfg_drv.string_data[instance_id].current_value,
+ dst, sizeof(bioscfg_drv.string_data[instance_id].current_value));
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.string_data[instance_id].common.path, dst,
+ sizeof(bioscfg_drv.string_data[instance_id].common.path));
+ break;
+ default:
+ pr_warn("Invalid element: %d found in String attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+
+ for (elem = 3; elem < STRING_ELEM_CNT; elem++) {
+ if (elem != PREREQUISITES) {
+ ret = get_integer_from_buffer((int **)&buffer_ptr,
+ buffer_size,
+ (int *)&int_value);
+ if (ret < 0)
+ continue;
+ }
+
+ switch (elem) {
+ case IS_READONLY:
+ bioscfg_drv.string_data[instance_id].common.is_readonly = int_value;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.string_data[instance_id].common.display_in_ui = int_value;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.string_data[instance_id].common.requires_physical_presence = int_value;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.string_data[instance_id].common.sequence = int_value;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.string_data[instance_id].common.prerequisites_size = int_value;
+ if (int_value > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+ break;
+
+ case PREREQUISITES:
+ size = bioscfg_drv.string_data[instance_id].common.prerequisites_size;
+ for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ /* Ignore. Zero length string values */
+ if (ret < 0)
+ continue;
+ strscpy(bioscfg_drv.string_data[instance_id].common.prerequisites[reqs],
+ dst,
+ sizeof(bioscfg_drv.string_data[instance_id].common.prerequisites[reqs]));
+ }
+ break;
+ case SECURITY_LEVEL:
+ bioscfg_drv.string_data[instance_id].common.security_level = int_value;
+ break;
+ case STR_MIN_LENGTH:
+ bioscfg_drv.string_data[instance_id].min_length = int_value;
+ break;
+ case STR_MAX_LENGTH:
+ bioscfg_drv.string_data[instance_id].max_length = int_value;
+ break;
+ default:
+ pr_warn("Invalid element: %d found in String attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+ kfree(dst);
+ return 0;
+}
+
+/*
+ * exit_string_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void exit_string_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.string_instances_count; instance_id++) {
+
+ struct kobject *attr_name_kobj = bioscfg_drv.string_data[instance_id].attr_name_kobj;
+
+ if (attr_name_kobj)
+ sysfs_remove_group(attr_name_kobj, &string_attr_group);
+ }
+ bioscfg_drv.string_instances_count = 0;
+
+ kfree(bioscfg_drv.string_data);
+ bioscfg_drv.string_data = NULL;
+}
--
2.34.1

2023-04-20 16:57:10

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 13/14] HP BIOSCFG driver - Makefile

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
drivers/platform/x86/hp/Kconfig | 16 ++++++++++++++++
drivers/platform/x86/hp/Makefile | 1 +
drivers/platform/x86/hp/hp-bioscfg/Makefile | 13 +++++++++++++
3 files changed, 30 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/Makefile

diff --git a/drivers/platform/x86/hp/Kconfig b/drivers/platform/x86/hp/Kconfig
index ae165955311c..7fef4f12e498 100644
--- a/drivers/platform/x86/hp/Kconfig
+++ b/drivers/platform/x86/hp/Kconfig
@@ -60,4 +60,20 @@ config TC1100_WMI
This is a driver for the WMI extensions (wireless and bluetooth power
control) of the HP Compaq TC1100 tablet.

+config HP_BIOSCFG
+ tristate "HP BIOS Configuration Driver"
+ default m
+ depends on ACPI_WMI
+ select NLS
+ select FW_ATTR_CLASS
+ help
+ This driver enables administrators to securely manage BIOS settings
+ using digital certificates and public-key cryptography that eliminate
+ the need for passwords for both remote and local management. It supports
+ changing BIOS settings on many HP machines from 2018 and newer without
+ the use of any additional software.
+
+ To compile this driver as a module, choose M here: the module will
+ be called hp-bioscfg.
+
endif # X86_PLATFORM_DRIVERS_HP
diff --git a/drivers/platform/x86/hp/Makefile b/drivers/platform/x86/hp/Makefile
index db1eed4cd7c7..e4f908a61acf 100644
--- a/drivers/platform/x86/hp/Makefile
+++ b/drivers/platform/x86/hp/Makefile
@@ -8,3 +8,4 @@
obj-$(CONFIG_HP_ACCEL) += hp_accel.o
obj-$(CONFIG_HP_WMI) += hp-wmi.o
obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
+obj-$(CONFIG_HP_BIOSCFG) += hp-bioscfg/
diff --git a/drivers/platform/x86/hp/hp-bioscfg/Makefile b/drivers/platform/x86/hp/hp-bioscfg/Makefile
new file mode 100644
index 000000000000..529eba6fa47f
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/Makefile
@@ -0,0 +1,13 @@
+obj-$(CONFIG_HP_BIOSCFG) := hp-bioscfg.o
+
+hp-bioscfg-objs := bioscfg.o \
+ enum-attributes.o \
+ int-attributes.o \
+ string-attributes.o \
+ passwdobj-attributes.o \
+ biosattr-interface.o \
+ passwdattr-interface.o \
+ ordered-attributes.o \
+ surestart-attributes.o \
+ spmobj-attributes.o
+
--
2.34.1

2023-04-20 16:57:13

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 09/14] HP BIOSCFG driver - enum-attributes

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../x86/hp/hp-bioscfg/enum-attributes.c | 543 ++++++++++++++++++
1 file changed, 543 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
new file mode 100644
index 000000000000..20defa92da6f
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
@@ -0,0 +1,543 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to enumeration type attributes under
+ * BIOS Enumeration GUID for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+
+GET_INSTANCE_ID(enumeration);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_enumeration_instance_id(kobj);
+
+ if (instance_id < 0)
+ return -EIO;
+
+ return sysfs_emit(buf, "%s\n",
+ bioscfg_drv.enumeration_data[instance_id].current_value);
+}
+
+/*
+ * validate_enumeration_input() -
+ * Validate input of current_value against possible values
+ *
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_enumeration_input(int instance_id, const char *buf)
+{
+ int ret = 0;
+ int found = 0;
+ int i;
+ int possible_values;
+
+ /* Is it a read only attribute */
+ if (bioscfg_drv.enumeration_data[instance_id].common.is_readonly)
+ return -EIO;
+
+ possible_values = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
+ for (i = 0; i < possible_values && !found; i++)
+ if (!strcasecmp(bioscfg_drv.enumeration_data[instance_id].possible_values[i], buf))
+ found = 1;
+
+ if (!found) {
+ ret = -EINVAL;
+ goto exit_enum_input;
+ }
+
+ /*
+ * set pending reboot flag depending on
+ * "RequiresPhysicalPresence" value
+ */
+ if (bioscfg_drv.enumeration_data[instance_id].common.requires_physical_presence)
+ bioscfg_drv.pending_reboot = true;
+
+exit_enum_input:
+ return ret;
+}
+
+static void update_enumeration_value(int instance_id, char *attr_value)
+{
+ strscpy(bioscfg_drv.enumeration_data[instance_id].current_value,
+ attr_value,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].current_value));
+}
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, enumeration);
+static struct kobj_attribute enumeration_display_langcode =
+ __ATTR_RO(display_name_language_code);
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, enumeration);
+static struct kobj_attribute enumeration_display_name =
+ __ATTR_RO(display_name);
+
+ATTRIBUTE_PROPERTY_STORE(current_value, enumeration);
+static struct kobj_attribute enumeration_current_val =
+ __ATTR_RW_MODE(current_value, 0644);
+
+ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, enumeration);
+static struct kobj_attribute enumeration_prerequisites_size_val =
+ __ATTR_RO(prerequisites_size);
+
+ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, enumeration);
+static struct kobj_attribute enumeration_prerequisites_val =
+ __ATTR_RO(prerequisites);
+
+ATTRIBUTE_N_PROPERTY_SHOW(possible_values_size, enumeration);
+static struct kobj_attribute enumeration_possible_values_size_val =
+ __ATTR_RO(possible_values_size);
+
+ATTRIBUTE_VALUES_PROPERTY_SHOW(possible_values, enumeration);
+static struct kobj_attribute enumeration_poss_val =
+ __ATTR_RO(possible_values);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "enumeration\n");
+}
+static struct kobj_attribute enumeration_type =
+ __ATTR_RO(type);
+
+static struct attribute *enumeration_attrs[] = {
+ &enumeration_display_langcode.attr,
+ &enumeration_display_name.attr,
+ &enumeration_current_val.attr,
+ &enumeration_prerequisites_size_val.attr,
+ &enumeration_prerequisites_val.attr,
+ &enumeration_possible_values_size_val.attr,
+ &enumeration_poss_val.attr,
+ &enumeration_type.attr,
+ NULL
+};
+
+static const struct attribute_group enumeration_attr_group = {
+ .attrs = enumeration_attrs,
+};
+
+int alloc_enumeration_data(void)
+{
+ int ret = 0;
+
+ bioscfg_drv.enumeration_instances_count =
+ get_instance_count(HP_WMI_BIOS_ENUMERATION_GUID);
+
+ bioscfg_drv.enumeration_data = kcalloc(bioscfg_drv.enumeration_instances_count,
+ sizeof(struct enumeration_data), GFP_KERNEL);
+ if (!bioscfg_drv.enumeration_data) {
+ bioscfg_drv.enumeration_instances_count = 0;
+ ret = -ENOMEM;
+ }
+ return ret;
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_enum_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [ENUM_CURRENT_VALUE] = ACPI_TYPE_STRING,
+ [ENUM_SIZE] = ACPI_TYPE_INTEGER,
+ [ENUM_POSSIBLE_VALUES] = ACPI_TYPE_STRING
+};
+
+/*
+ * populate_enumeration_package_data() -
+ * Populate all properties of an instance under enumeration attribute
+ *
+ * @enum_obj: ACPI object with enumeration data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_enumeration_package_data(union acpi_object *enum_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj;
+
+ populate_enumeration_elements_from_package(enum_obj,
+ enum_obj->package.count,
+ instance_id);
+ update_attribute_permissions(bioscfg_drv.enumeration_data[instance_id].common.is_readonly,
+ &enumeration_current_val);
+ /*
+ * Several attributes have names such "MONDAY". Friendly
+ * user nane is generated to make the name more descriptive
+ */
+ friendly_user_name_update(bioscfg_drv.enumeration_data[instance_id].common.path,
+ attr_name_kobj->name,
+ bioscfg_drv.enumeration_data[instance_id].common.display_name,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].common.display_name));
+ return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
+}
+
+int populate_enumeration_elements_from_package(union acpi_object *enum_obj,
+ int enum_obj_count,
+ int instance_id)
+{
+ char *str_value = NULL;
+ int value_len;
+ u32 size = 0;
+ u32 int_value;
+ int elem = 0;
+ int reqs;
+ int pos_values;
+ int ret;
+ int eloc;
+
+ strscpy(bioscfg_drv.enumeration_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1, eloc = 1; elem < enum_obj_count; elem++, eloc++) {
+
+ /* ONLY look at the first ENUM_ELEM_CNT elements */
+ if (eloc == ENUM_ELEM_CNT)
+ goto exit_enumeration_package;
+
+ switch (enum_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+
+ if (PREREQUISITES != elem && ENUM_POSSIBLE_VALUES != elem) {
+ ret = convert_hexstr_to_str(enum_obj[elem].string.pointer,
+ enum_obj[elem].string.length,
+ &str_value, &value_len);
+ if (ret)
+ return -EINVAL;
+
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)enum_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", enum_obj[elem].type);
+ continue;
+ }
+
+ /* Check that both expected and read object type match */
+ if (expected_enum_types[eloc] != enum_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_enum_types[eloc], elem, enum_obj[elem].type);
+ return -EIO;
+ }
+
+ /* Assign appropriate element value to corresponding field */
+ switch (eloc) {
+ case NAME:
+ case VALUE:
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.enumeration_data[instance_id].common.path, str_value,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].common.path));
+ break;
+ case IS_READONLY:
+ bioscfg_drv.enumeration_data[instance_id].common.is_readonly = int_value;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.enumeration_data[instance_id].common.display_in_ui = int_value;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.enumeration_data[instance_id].common.requires_physical_presence = int_value;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.enumeration_data[instance_id].common.sequence = int_value;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.enumeration_data[instance_id].common.prerequisites_size = int_value;
+ if (int_value > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ /*
+ * This HACK is needed to keep the expected
+ * element list pointing to the right obj[elem].type
+ * when the size is zero. PREREQUISITES
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+
+ case PREREQUISITES:
+
+ size = bioscfg_drv.enumeration_data[instance_id].common.prerequisites_size;
+
+ for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
+ if (elem >= enum_obj_count) {
+ pr_err("Error enum-objects package is too small\n");
+ return -EINVAL;
+ }
+
+ ret = convert_hexstr_to_str(enum_obj[elem + reqs].string.pointer,
+ enum_obj[elem + reqs].string.length,
+ &str_value, &value_len);
+
+ if (ret)
+ return -EINVAL;
+
+ strlcpy(bioscfg_drv.enumeration_data[instance_id].common.prerequisites[reqs],
+ str_value,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].common.prerequisites[reqs]));
+
+ kfree(str_value);
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ bioscfg_drv.enumeration_data[instance_id].common.security_level = int_value;
+ break;
+
+ case ENUM_CURRENT_VALUE:
+ strscpy(bioscfg_drv.enumeration_data[instance_id].current_value,
+ str_value, sizeof(bioscfg_drv.enumeration_data[instance_id].current_value));
+ break;
+ case ENUM_SIZE:
+ bioscfg_drv.enumeration_data[instance_id].possible_values_size = int_value;
+ if (int_value > MAX_VALUES_SIZE)
+ pr_warn("Possible number values size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ /*
+ * This HACK is needed to keep the expected
+ * element list pointing to the right obj[elem].type
+ * when the size is zero. POSSIBLE_VALUES
+ * object is omitted by BIOS when the size is zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+
+ case ENUM_POSSIBLE_VALUES:
+ size = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
+
+ for (pos_values = 0; pos_values < size && pos_values < MAX_VALUES_SIZE; pos_values++) {
+ if (elem >= enum_obj_count) {
+ pr_err("Error enum-objects package is too small\n");
+ return -EINVAL;
+ }
+
+ ret = convert_hexstr_to_str(enum_obj[elem + pos_values].string.pointer,
+ enum_obj[elem + pos_values].string.length,
+ &str_value, &value_len);
+
+ if (ret)
+ return -EINVAL;
+
+ /*
+ * ignore strings when possible values size
+ * is greater than MAX_VALUES_SIZE
+ */
+ if (size < MAX_VALUES_SIZE)
+ strlcpy(bioscfg_drv.enumeration_data[instance_id].possible_values[pos_values],
+ str_value,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].possible_values[pos_values]));
+
+ kfree(str_value);
+ }
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
+ break;
+ }
+
+ kfree(str_value);
+ }
+
+exit_enumeration_package:
+ kfree(str_value);
+ return 0;
+}
+
+/*
+ * populate_enumeration_buffer_data() -
+ * Populate all properties of an instance under enumeration attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @enum_obj: ACPI object with enumeration data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ * @enumeration_property_count: Total properties count under enumeration type
+ */
+int populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+
+ bioscfg_drv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj;
+
+ /* Populate enumeration elements */
+ populate_enumeration_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+
+ update_attribute_permissions(bioscfg_drv.enumeration_data[instance_id].common.is_readonly,
+ &enumeration_current_val);
+ /*
+ * Several attributes have names such "MONDAY". A Friendlier
+ * user nane is generated to make the name more descriptive
+ */
+ friendly_user_name_update(bioscfg_drv.enumeration_data[instance_id].common.path,
+ attr_name_kobj->name,
+ bioscfg_drv.enumeration_data[instance_id].common.display_name,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].common.display_name));
+
+ return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
+}
+
+int populate_enumeration_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ char *dst = NULL;
+ int elem;
+ int reqs;
+ int integer;
+ int size = 0;
+ int values;
+ int ret;
+ int dst_size = *buffer_size / sizeof(u16);
+
+ dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
+ if (!dst)
+ return -ENOMEM;
+
+ elem = 0;
+
+ strscpy(bioscfg_drv.enumeration_data[instance_id].common.display_name_language_code,
+ LANG_CODE_STR,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].common.display_name_language_code));
+
+ for (elem = 1; elem < 3; elem++) {
+
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ /* Ignore. Zero length string values */
+ if (ret < 0)
+ continue;
+
+ switch (elem) {
+ case VALUE:
+ /* Skip 'Value' since 'CurrentValue' is reported. */
+ break;
+ case PATH:
+ strscpy(bioscfg_drv.enumeration_data[instance_id].common.path,
+ dst, sizeof(bioscfg_drv.enumeration_data[instance_id].common.path));
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+
+ for (elem = 3; elem < ENUM_ELEM_CNT; elem++) {
+ if (PREREQUISITES != elem && ENUM_CURRENT_VALUE != elem && ENUM_POSSIBLE_VALUES != elem) {
+ ret = get_integer_from_buffer((int **)&buffer_ptr, buffer_size, (int *)&integer);
+ if (ret < 0)
+ continue;
+ }
+
+ switch (elem) {
+ case IS_READONLY:
+ bioscfg_drv.enumeration_data[instance_id].common.is_readonly = integer;
+ break;
+ case DISPLAY_IN_UI:
+ bioscfg_drv.enumeration_data[instance_id].common.display_in_ui = integer;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ bioscfg_drv.enumeration_data[instance_id].common.requires_physical_presence = integer;
+ break;
+ case SEQUENCE:
+ bioscfg_drv.enumeration_data[instance_id].common.sequence = integer;
+ break;
+ case PREREQUISITES_SIZE:
+ bioscfg_drv.enumeration_data[instance_id].common.prerequisites_size = integer;
+ if (integer > MAX_PREREQUISITES_SIZE)
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ // PREREQUISITES:
+ elem++;
+
+ size = bioscfg_drv.enumeration_data[instance_id].common.prerequisites_size;
+ for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ /* Ignore. expect zero length strings at the end of prerequisite values */
+ if (ret < 0)
+ continue;
+
+ strscpy(bioscfg_drv.enumeration_data[instance_id].common.prerequisites[reqs],
+ dst,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].common.prerequisites[reqs]));
+ }
+ break;
+ case SECURITY_LEVEL:
+ bioscfg_drv.enumeration_data[instance_id].common.security_level = integer;
+ break;
+ case ENUM_CURRENT_VALUE:
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0)
+ continue;
+
+ strscpy(bioscfg_drv.enumeration_data[instance_id].current_value,
+ dst,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].current_value));
+ break;
+ case ENUM_SIZE:
+ bioscfg_drv.enumeration_data[instance_id].possible_values_size = integer;
+ if (integer > MAX_VALUES_SIZE)
+ pr_warn("Possible size value exceeded the maximum number of elements supported or data may be malformed\n");
+
+ // ENUM_POSSIBLE_VALUES:
+ elem++;
+
+ size = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
+ for (values = 0; values < size && values < MAX_VALUES_SIZE; values++) {
+ ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ /* Ignore expect zero size strings at the end of all possible values */
+ if (ret < 0)
+ continue;
+
+ strscpy(bioscfg_drv.enumeration_data[instance_id].possible_values[values],
+ dst,
+ sizeof(bioscfg_drv.enumeration_data[instance_id].possible_values[values]));
+ }
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
+ break;
+ }
+ }
+
+ kfree(dst);
+
+ return 0;
+}
+
+/**
+ * exit_enumeration_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void exit_enumeration_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.enumeration_instances_count; instance_id++) {
+ struct kobject *attr_name_kobj = bioscfg_drv.enumeration_data[instance_id].attr_name_kobj;
+
+ if (attr_name_kobj)
+ sysfs_remove_group(attr_name_kobj, &enumeration_attr_group);
+ }
+ bioscfg_drv.enumeration_instances_count = 0;
+
+ kfree(bioscfg_drv.enumeration_data);
+ bioscfg_drv.enumeration_data = NULL;
+}
--
2.34.1

2023-04-20 16:57:19

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 08/14] HP BIOSCFG driver - bioscfg-h

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++++++++++
1 file changed, 613 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h

diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
new file mode 100644
index 000000000000..3f7d33b1b032
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
@@ -0,0 +1,613 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Definitions for kernel modules using hp_bioscfg driver
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#ifndef _HP_BIOSCFG_H_
+#define _HP_BIOSCFG_H_
+
+#include <linux/wmi.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/capability.h>
+#include <linux/nls.h>
+#include <linux/printk.h>
+
+
+#define DRIVER_NAME "hp-bioscfg"
+
+#define MAX_BUFF 512
+#define MAX_KEY_MOD 256
+#define MAX_PASSWD_SIZE 64
+#define MAX_MESSAGE_SIZE 256
+#define MAX_PREREQUISITES_SIZE 20
+#define MAX_REQ_ELEM_SIZE 128
+#define MAX_VALUES_SIZE 16
+#define MAX_ENCODINGS_SIZE 16
+#define MAX_ELEMENTS_SIZE 16
+
+#define SPM_STR_DESC "Secure Platform Management"
+#define SPM_STR "SPM"
+#define SURE_START_DESC "Sure Start"
+#define SURE_START_STR "Sure_Start"
+#define SETUP_PASSWD "Setup Password"
+#define POWER_ON_PASSWD "Power-On Password"
+
+#define LANG_CODE_STR "en_US.UTF-8"
+#define SCHEDULE_POWER_ON "Scheduled Power-On"
+
+/* Sure Admin Functions */
+
+#define UTF_PREFIX "<utf-16/>"
+#define BEAM_PREFIX "<BEAM/>"
+
+/* mechanism - Authentication attribute */
+
+#define MAX_MECHANISM_TYPES 3
+
+enum mechanism_values {
+ PASSWORD = 0x00,
+ NOT_PROVISION = 0x00,
+ SIGNING_KEY = 0x01,
+ ENDORSEMENT_KEY = 0x02
+};
+
+#define BIOS_ADMIN "bios-admin"
+#define POWER_ON "power-on"
+#define BIOS_SPM "enhanced-bios-auth"
+
+#define PASSWD_MECHANISM_TYPES "password"
+
+#define HP_WMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
+
+#define HP_WMI_BIOS_STRING_GUID "988D08E3-68F4-4c35-AF3E-6A1B8106F83C"
+#define HP_WMI_BIOS_INTEGER_GUID "8232DE3D-663D-4327-A8F4-E293ADB9BF05"
+#define HP_WMI_BIOS_ENUMERATION_GUID "2D114B49-2DFB-4130-B8FE-4A3C09E75133"
+#define HP_WMI_BIOS_ORDERED_LIST_GUID "14EA9746-CE1F-4098-A0E0-7045CB4DA745"
+#define HP_WMI_BIOS_PASSWORD_GUID "322F2028-0F84-4901-988E-015176049E2D"
+#define HP_WMI_SET_BIOS_SETTING_GUID "1F4C91EB-DC5C-460b-951D-C7CB9B4B8D5E"
+
+enum hp_wmi_spm_commandtype {
+ HPWMI_SECUREPLATFORM_GET_STATE = 0x10,
+ HPWMI_SECUREPLATFORM_SET_KEK = 0x11,
+ HPWMI_SECUREPLATFORM_SET_SK = 0x12
+};
+
+enum hp_wmi_surestart_commandtype {
+ HPWMI_SURESTART_GET_LOG_COUNT = 0x01,
+ HPWMI_SURESTART_GET_LOG = 0x02
+};
+
+enum hp_wmi_command {
+ HPWMI_READ = 0x01,
+ HPWMI_WRITE = 0x02,
+ HPWMI_ODM = 0x03,
+ HPWMI_SURESTART = 0x20006,
+ HPWMI_GM = 0x20008,
+ HPWMI_SECUREPLATFORM = 0x20010
+};
+
+struct bios_return {
+ u32 sigpass;
+ u32 return_code;
+};
+
+enum hp_return_value {
+ HPWMI_RET_WRONG_SIGNATURE = 0x02,
+ HPWMI_RET_UNKNOWN_COMMAND = 0x03,
+ HPWMI_RET_UNKNOWN_CMDTYPE = 0x04,
+ HPWMI_RET_INVALID_PARAMETERS = 0x05
+};
+
+enum wmi_error_values {
+ SUCCESS = 0x00,
+ CMD_FAILED = 0x01,
+ INVALID_SIGN = 0x02,
+ INVALID_CMD_VALUE = 0x03,
+ INVALID_CMD_TYPE = 0x04,
+ INVALID_DATA_SIZE = 0x05,
+ INVALID_CMD_PARAM = 0x06,
+ ENCRYP_CMD_REQUIRED = 0x07,
+ NO_SECURE_SESSION = 0x08,
+ SECURE_SESSION_FOUND = 0x09,
+ SECURE_SESSION_FAILED = 0x0A,
+ AUTH_FAILED = 0x0B,
+ INVALID_BIOS_AUTH = 0x0E,
+ NONCE_DID_NOT_MATCH = 0x18,
+ GENERIC_ERROR = 0x1C,
+ BIOS_ADMIN_POLICY_NOT_MET = 0x28,
+ BIOS_ADMIN_NOT_SET = 0x38,
+ P21_NO_PROVISIONED = 0x1000,
+ P21_PROVISION_IN_PROGRESS = 0x1001,
+ P21_IN_USE = 0x1002,
+ HEP_NOT_ACTIVE = 0x1004,
+ HEP_ALREADY_SET = 0x1006,
+ HEP_CHECK_STATE = 0x1007
+};
+
+enum spm_features {
+ HEP_ENABLED = 0x01,
+ PLATFORM_RECOVERY = 0x02,
+ ENHANCED_BIOS_AUTH_MODE = 0x04
+};
+
+#define MAX_KEK_BLOB_SIZE 4160
+#define MAX_SK_BLOB_SIZE 516
+
+enum spm_states_values {
+ NOT_PROVISIONED = 0x00,
+ PROVISIONED = 0x01,
+ PROVISIONING_IN_PROGRESS = 0x02
+};
+
+
+
+/*
+ * struct bios_args buffer is dynamically allocated. New WMI command types
+ * were introduced that exceeds 128-byte data size. Changes to handle
+ * the data size allocation scheme were kept in hp_wmi_perform_qurey function.
+ */
+struct bios_args {
+ u32 signature;
+ u32 command;
+ u32 commandtype;
+ u32 datasize;
+ u8 data[];
+};
+
+struct secureplatform_provisioning_data {
+ u8 state;
+ u8 version[2];
+ u8 reserved1;
+ u32 features;
+ u32 nonce;
+ u8 reserved2[28];
+ u8 sk_mod[MAX_KEY_MOD];
+ u8 kek_mod[MAX_KEY_MOD];
+};
+
+struct common_data {
+ u8 display_name[MAX_BUFF];
+ u8 path[MAX_BUFF];
+ u32 is_readonly;
+ u32 display_in_ui;
+ u32 requires_physical_presence;
+ u32 sequence;
+ u32 prerequisites_size;
+ u8 prerequisites[MAX_PREREQUISITES_SIZE][MAX_BUFF];
+ u32 security_level;
+ u8 display_name_language_code[MAX_BUFF];
+};
+
+
+struct string_data {
+ struct kobject *attr_name_kobj;
+ u8 current_value[MAX_BUFF];
+ u8 new_value[MAX_BUFF];
+ u32 min_length;
+ u32 max_length;
+ struct common_data common;
+};
+
+struct integer_data {
+ struct kobject *attr_name_kobj;
+ u32 current_value;
+ u32 new_value;
+ u32 lower_bound;
+ u32 upper_bound;
+ u32 scalar_increment;
+ struct common_data common;
+};
+
+struct enumeration_data {
+ struct kobject *attr_name_kobj;
+ u8 current_value[MAX_BUFF];
+ u8 new_value[MAX_BUFF];
+ u32 possible_values_size;
+ u8 possible_values[MAX_VALUES_SIZE][MAX_BUFF];
+ struct common_data common;
+};
+
+struct ordered_list_data {
+ struct kobject *attr_name_kobj;
+ u8 current_value[MAX_BUFF];
+ u8 new_value[MAX_BUFF];
+ u32 elements_size;
+ u8 elements[MAX_ELEMENTS_SIZE][MAX_BUFF];
+ struct common_data common;
+};
+
+struct password_data {
+ struct kobject *attr_name_kobj;
+ u8 current_password[MAX_PASSWD_SIZE];
+ u8 new_password[MAX_PASSWD_SIZE];
+ u32 min_password_length;
+ u32 max_password_length;
+ u32 encodings_size;
+ u8 encodings[MAX_ENCODINGS_SIZE][MAX_BUFF];
+ u32 is_enabled;
+
+ // 'bios-admin' 'power-on'
+ u32 role;
+
+ //'password'
+ u32 mechanism;
+
+ struct common_data common;
+};
+
+struct secure_platform_data {
+ struct kobject *attr_name_kobj;
+ u8 attribute_name[MAX_BUFF];
+ u8 display_name[MAX_BUFF];
+
+ u8 *endorsement_key;
+ u8 *signing_key;
+ u8 *auth_token;
+
+ u32 is_enabled;
+ u32 mechanism;
+};
+
+struct bioscfg_priv {
+ struct wmi_device *password_attr_wdev;
+ struct wmi_device *bios_attr_wdev;
+ struct kset *authentication_dir_kset;
+ struct kset *main_dir_kset;
+ struct device *class_dev;
+ struct string_data *string_data;
+ u32 string_instances_count;
+ struct integer_data *integer_data;
+ u32 integer_instances_count;
+ struct enumeration_data *enumeration_data;
+ u32 enumeration_instances_count;
+ struct ordered_list_data *ordered_list_data;
+ u32 ordered_list_instances_count;
+ struct password_data *password_data;
+ u32 password_instances_count;
+
+ struct kobject *sure_start_attr_kobj;
+ struct secure_platform_data spm_data;
+
+ bool pending_reboot;
+ struct mutex mutex;
+};
+
+/* global structure used by multiple WMI interfaces */
+extern struct bioscfg_priv bioscfg_drv;
+
+enum hp_wmi_data_type {
+ HPWMI_STRING_TYPE = 0x00,
+ HPWMI_INTEGER_TYPE = 0x01,
+ HPWMI_ENUMERATION_TYPE = 0x02,
+ HPWMI_ORDERED_LIST_TYPE = 0x03,
+ HPWMI_PASSWORD_TYPE = 0x04,
+ HPWMI_SECURE_PLATFORM_TYPE = 0x05,
+ HPWMI_SURE_START_TYPE = 0x06
+};
+
+enum hp_wmi_data_elements {
+
+ /* Common elements */
+ NAME = 0,
+ VALUE = 1,
+ PATH = 2,
+ IS_READONLY = 3,
+ DISPLAY_IN_UI = 4,
+ REQUIRES_PHYSICAL_PRESENCE = 5,
+ SEQUENCE = 6,
+ PREREQUISITES_SIZE = 7,
+ PREREQUISITES = 8,
+ SECURITY_LEVEL = 9,
+
+ /* String elements */
+ STR_MIN_LENGTH = 10,
+ STR_MAX_LENGTH = 11,
+
+ /* Integer elements */
+ INT_LOWER_BOUND = 10,
+ INT_UPPER_BOUND = 11,
+ INT_SCALAR_INCREMENT = 12,
+
+ /* Enumeration elements */
+ ENUM_CURRENT_VALUE = 10,
+ ENUM_SIZE = 11,
+ ENUM_POSSIBLE_VALUES = 12,
+
+ /* Ordered list elements */
+ ORD_LIST_SIZE = 10,
+ ORD_LIST_ELEMENTS = 11,
+
+ /* Password elements */
+ PSWD_MIN_LENGTH = 10,
+ PSWD_MAX_LENGTH = 11,
+ PSWD_SIZE = 12,
+ PSWD_ENCODINGS = 13,
+ PSWD_IS_SET = 14
+};
+
+
+enum hp_wmi_elements_count {
+ STRING_ELEM_CNT = 12,
+ INTEGER_ELEM_CNT = 13,
+ ENUM_ELEM_CNT = 13,
+ ORDERED_ELEM_CNT = 12,
+ PASSWORD_ELEM_CNT = 15
+};
+
+#define GET_INSTANCE_ID(type) \
+ static int get_##type##_instance_id(struct kobject *kobj) \
+ { \
+ int i; \
+ \
+ for (i = 0; i <= bioscfg_drv.type##_instances_count; i++) { \
+ if (!(strcmp(kobj->name, bioscfg_drv.type##_data[i].attr_name_kobj->name))) \
+ return i; \
+ } \
+ return -EIO; \
+ }
+
+#define ATTRIBUTE_S_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
+ char *buf) \
+ { \
+ int i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].name); \
+ return -EIO; \
+ }
+
+#define ATTRIBUTE_N_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
+ char *buf) \
+ { \
+ int i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data[i].name); \
+ return -EIO; \
+ }
+
+
+#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
+ static ssize_t curr_val##_store(struct kobject *kobj, \
+ struct kobj_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ char *p = NULL; \
+ char *attr_value = NULL; \
+ int i; \
+ int ret = -EIO; \
+ \
+ attr_value = kstrdup(buf, GFP_KERNEL); \
+ if (!attr_value) \
+ return -ENOMEM; \
+ \
+ p = memchr(attr_value, '\n', count); \
+ if (p != NULL) \
+ *p = '\0'; \
+ \
+ i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ ret = validate_##type##_input(i, attr_value); \
+ if (!ret) \
+ ret = hp_set_attribute(kobj->name, attr_value); \
+ if (!ret) \
+ update_##type##_value(i, attr_value); \
+ \
+ clear_all_credentials(); \
+ kfree(attr_value); \
+ \
+ return ret ? ret : count; \
+ }
+
+#define ATTRIBUTE_SPM_N_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data.name); \
+ }
+
+#define ATTRIBUTE_SPM_S_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data.name); \
+ }
+
+#define ATTRIBUTE_VALUES_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf) \
+ { \
+ int i; \
+ int len = 0; \
+ int instance_id = get_##type##_instance_id(kobj); \
+ \
+ if (instance_id < 0) \
+ return 0; \
+ \
+ for (i = 0; i < bioscfg_drv.type##_data[instance_id].name##_size; i++) { \
+ if (i) \
+ len += sysfs_emit_at(buf, len, "%s", ";"); \
+ \
+ len += sysfs_emit_at(buf, len, "%s", \
+ bioscfg_drv.type##_data[instance_id].name[i]); \
+ } \
+ len += sysfs_emit_at(buf, len, "\n"); \
+ return len; \
+ }
+
+
+#define ATTRIBUTE_S_COMMON_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
+ char *buf) \
+ { \
+ int i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].common.name); \
+ return -EIO; \
+ }
+
+#define ATTRIBUTE_N_COMMON_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
+ char *buf) \
+ { \
+ int i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data[i].common.name); \
+ return -EIO; \
+ }
+
+#define ATTRIBUTE_V_COMMON_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf) \
+ { \
+ int i; \
+ int len = 0; \
+ int instance_id = get_##type##_instance_id(kobj); \
+ \
+ if (instance_id < 0) \
+ return 0; \
+ \
+ for (i = 0; i < bioscfg_drv.type##_data[instance_id].common.name##_size; i++) { \
+ if (i) \
+ len += sysfs_emit_at(buf, len, "%s", ";"); \
+ \
+ len += sysfs_emit_at(buf, len, "%s", \
+ bioscfg_drv.type##_data[instance_id].common.name[i]); \
+ } \
+ len += sysfs_emit_at(buf, len, "\n"); \
+ return len; \
+ }
+
+
+/*
+ * Prototypes
+ */
+union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string);
+int get_instance_count(const char *guid_string);
+void update_attribute_permissions(u32 isReadOnly, struct kobj_attribute *current_val);
+void friendly_user_name_update(char *path, const char *attr_name,
+ char *attr_display, int attr_size);
+int bioscfg_wmi_error_and_message(int error_code);
+
+/* String attributes */
+int populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+
+int populate_string_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id);
+
+//enum hp_wmi_data_type type);
+int alloc_string_data(void);
+void exit_string_attributes(void);
+int populate_string_package_data(union acpi_object *str_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int populate_string_elements_from_package(union acpi_object *str_obj,
+ int str_obj_count,
+ int instance_id);
+
+/* Integer attributes */
+int populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id);
+int alloc_integer_data(void);
+void exit_integer_attributes(void);
+int populate_integer_package_data(union acpi_object *integer_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int populate_integer_elements_from_package(union acpi_object *integer_obj,
+ int integer_obj_count,
+ int instance_id);
+
+/* Enumeration attributes */
+int populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int populate_enumeration_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id);
+int alloc_enumeration_data(void);
+void exit_enumeration_attributes(void);
+int populate_enumeration_package_data(union acpi_object *enum_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int populate_enumeration_elements_from_package(union acpi_object *enum_obj,
+ int enum_obj_count,
+ int instance_id);
+
+/* Ordered list */
+int populate_ordered_list_buffer_data(u8 *buffer_ptr,
+ u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int populate_ordered_list_elements_from_buffer(u8 *buffer_ptr,
+ u32 *buffer_size,
+ int instance_id);
+int alloc_ordered_list_data(void);
+void exit_ordered_list_attributes(void);
+int populate_ordered_list_package_data(union acpi_object *order_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int populate_ordered_list_elements_from_package(union acpi_object *order_obj,
+ int order_obj_count,
+ int instance_id);
+
+/* Password authentication attributes */
+int populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int populate_password_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id);
+int populate_password_package_data(union acpi_object *password_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int populate_password_elements_from_package(union acpi_object *password_obj,
+ int password_obj_count,
+ int instance_id);
+int alloc_password_data(void);
+int alloc_secure_platform_data(void);
+void exit_password_attributes(void);
+void exit_secure_platform_attributes(void);
+int populate_secure_platform_data(struct kobject *attr_name_kobj);
+int password_is_set(const char *auth);
+int check_spm_is_enabled(void);
+int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size);
+int hp_wmi_perform_query(int query, enum hp_wmi_command command,
+ void *buffer, int insize, int outsize);
+int validate_password_input(int instance_id, const char *buf);
+
+/* Sure Start attributes */
+void exit_sure_start_attributes(void);
+int populate_sure_start_data(struct kobject *attr_name_kobj);
+
+int set_bios_defaults(u8 defType);
+int get_password_instance_for_type(const char *name);
+int clear_all_credentials(void);
+int clear_passwords(const int instance);
+void exit_bios_attr_set_interface(void);
+int init_bios_attr_set_interface(void);
+size_t bioscfg_calculate_string_buffer(const char *str);
+size_t calculate_security_buffer(const char *authentication);
+void populate_security_buffer(u16 *buffer, const char *authentication);
+int set_new_password(const char *password_type, const char *new_password);
+int init_bios_attr_pass_interface(void);
+void exit_bios_attr_pass_interface(void);
+void *ascii_to_utf16_unicode(u16 *p, const u8 *str);
+int get_integer_from_buffer(int **buffer, u32 *buffer_size, int *integer);
+int get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size);
+int convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len);
+int encode_outsize_for_pvsz(int outsize);
+int hp_set_attribute(const char *a_name, const char *a_value);
+
+/* SPM Attributes */
+ssize_t update_spm_state(void);
+ssize_t statusbin(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
+ssize_t statusbin_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
+ssize_t status_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
+
+#endif
--
2.34.1

2023-04-20 16:57:31

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 14/14] HP BIOSCFG driver - MAINTAINERS

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
MAINTAINERS | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 0c9011f5fc17..7d1f261af539 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9367,6 +9367,12 @@ S: Obsolete
W: http://w1.fi/hostap-driver.html
F: drivers/net/wireless/intersil/hostap/

+HP BIOSCFG DRIVER
+M: Jorge Lopez <[email protected]>
+L: [email protected]
+S: Maintained
+F: drivers/platform/x86/hp/hp-bioscfg/
+
HP COMPAQ TC1100 TABLET WMI EXTRAS DRIVER
L: [email protected]
S: Orphan
--
2.34.1

2023-04-20 16:57:36

by Jorge Lopez

[permalink] [raw]
Subject: [PATCH v11 10/14] HP BIOSCFG driver - passwdattr-interface

HP BIOS Configuration driver purpose is to provide a driver supporting
the latest sysfs class firmware attributes framework allowing the user
to change BIOS settings and security solutions on HP Inc.’s commercial
notebooks.

Many features of HP Commercial notebooks can be managed using Windows
Management Instrumentation (WMI). WMI is an implementation of Web-Based
Enterprise Management (WBEM) that provides a standards-based interface
for changing and monitoring system settings. HP BIOSCFG driver provides
a native Linux solution and the exposed features facilitates the
migration to Linux environments.

The Linux security features to be provided in hp-bioscfg driver enables
managing the BIOS settings and security solutions via sysfs, a virtual
filesystem that can be used by user-mode applications. The new
documentation cover HP-specific firmware sysfs attributes such Secure
Platform Management and Sure Start. Each section provides security
feature description and identifies sysfs directories and files exposed
by the driver.

Many HP Commercial notebooks include a feature called Secure Platform
Management (SPM), which replaces older password-based BIOS settings
management with public key cryptography. PC secure product management
begins when a target system is provisioned with cryptographic keys
that are used to ensure the integrity of communications between system
management utilities and the BIOS.

HP Commercial notebooks have several BIOS settings that control its
behaviour and capabilities, many of which are related to security.
To prevent unauthorized changes to these settings, the system can
be configured to use a cryptographic signature-based authorization
string that the BIOS will use to verify authorization to modify the
setting.

Linux Security components are under development and not published yet.
The only linux component is the driver (hp bioscfg) at this time.
Other published security components are under Windows.

Signed-off-by: Jorge Lopez <[email protected]>

---
Based on the latest platform-drivers-x86.git/for-next
---
.../x86/hp/hp-bioscfg/passwdattr-interface.c | 51 +++++++++++++++++++
1 file changed, 51 insertions(+)
create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c

diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c b/drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c
new file mode 100644
index 000000000000..02fc766eb3cf
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to SET password methods under
+ * HP_WMI_SET_BIOS_SETTING_GUID for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 Hewlett-Packard Inc.
+ */
+
+#include <linux/wmi.h>
+#include "bioscfg.h"
+
+static int bios_attr_pass_interface_probe(struct wmi_device *wdev,
+ const void *context)
+{
+ mutex_lock(&bioscfg_drv.mutex);
+ bioscfg_drv.password_attr_wdev = wdev;
+ mutex_unlock(&bioscfg_drv.mutex);
+ return 0;
+}
+
+static void bios_attr_pass_interface_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&bioscfg_drv.mutex);
+ bioscfg_drv.password_attr_wdev = NULL;
+ mutex_unlock(&bioscfg_drv.mutex);
+}
+
+static const struct wmi_device_id bios_attr_pass_interface_id_table[] = {
+ { .guid_string = HP_WMI_SET_BIOS_SETTING_GUID },
+ { },
+};
+static struct wmi_driver bios_attr_pass_interface_driver = {
+ .driver = {
+ .name = DRIVER_NAME"-password"
+ },
+ .probe = bios_attr_pass_interface_probe,
+ .remove = bios_attr_pass_interface_remove,
+ .id_table = bios_attr_pass_interface_id_table,
+};
+
+int init_bios_attr_pass_interface(void)
+{
+ return wmi_driver_register(&bios_attr_pass_interface_driver);
+}
+
+void exit_bios_attr_pass_interface(void)
+{
+ wmi_driver_unregister(&bios_attr_pass_interface_driver);
+}
+
+MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table);
--
2.34.1

2023-04-22 20:58:38

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 01/14] HP BIOSCFG driver - Documentation

On 2023-04-20 11:54:41-0500, Jorge Lopez wrote:
> Based on the latest platform-drivers-x86.git/for-next
> ---
> .../testing/sysfs-class-firmware-attributes | 98 ++++++++++++++++++-
> 1 file changed, 96 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> index 4cdba3477176..73d7b8fbc0b2 100644
> --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
> +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> @@ -22,6 +22,12 @@ Description:
> - integer: a range of numerical values
> - string
>
> + HP specific types
> + -----------------
> + - ordered-list - a set of ordered list valid values
> + - sure-start - report audit logs read from BIOS

In the treevie you sent the Sure_Start attribute does not have
a "current_value".
This indicated that it's not actually a firmware attribute but just some
standalone sysfs attribute.

In this case it should not be documented here, but handled the same way
as the SPM stuff.

2023-04-22 21:47:16

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 02/14] HP BIOSCFG driver - biosattr-interface

Hi Jorge,

checkpatch.pl finds some issues on your patches.
Please make sure checkpath.pl --strict is happy.

On 2023-04-20 11:54:42-0500, Jorge Lopez wrote:
> ---
> Based on the latest platform-drivers-x86.git/for-next
> ---
> .../x86/hp/hp-bioscfg/biosattr-interface.c | 307 ++++++++++++++++++
> 1 file changed, 307 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
> new file mode 100644
> index 000000000000..f09dd41867f7
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
> @@ -0,0 +1,307 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions corresponding to methods under BIOS interface GUID
> + * for use with hp-bioscfg driver.
> + *
> + * Copyright (c) 2022 Hewlett-Packard Inc.
> + */
> +
> +#include <linux/wmi.h>
> +#include "bioscfg.h"
> +
> +#define SET_DEFAULT_VALUES_METHOD_ID 0x02
> +#define SET_BIOS_DEFAULTS_METHOD_ID 0x03
> +#define SET_ATTRIBUTE_METHOD_ID 0x04

This could be an enum.

> +
> +/*
> + * set_attribute() - Update an attribute value
> + * @a_name: The attribute name
> + * @a_value: The attribute value
> + *
> + * Sets an attribute to new value
> + */
> +int hp_set_attribute(const char *a_name, const char *a_value)
> +{
> + size_t security_area_size;
> + size_t a_name_size, a_value_size;
> + u16 *buffer = NULL;
> + u16 *start = NULL;
> + int buffer_size;
> + int ret = 0;
> + int instance;
> + char *auth_empty_value = "";
> + char *auth_token_choice = NULL;

No need to initialize auth_token_choice and start.
Consider coalescing variable declaration to avoid wasting vertical
space.

> +
> +
> + mutex_lock(&bioscfg_drv.mutex);
> + if (!bioscfg_drv.bios_attr_wdev) {
> + ret = -ENODEV;
> + goto out_set_attribute;
> + }
> +
> + instance = get_password_instance_for_type(SETUP_PASSWD);
> + if (instance < 0) {
> + ret = -EINVAL;
> + goto out_set_attribute;
> + }
> +
> + if (strlen(bioscfg_drv.password_data[instance].current_password) == 0)
> + strscpy(bioscfg_drv.password_data[instance].current_password,
> + auth_empty_value,
> + sizeof(bioscfg_drv.password_data[instance].current_password));

This essentially does

if (current_password[0] == '\0')
current_password[0] = '\0';

... nothing.


In the driver there is a lot of dereferencing substructures of
bioscfg_drv going on. This makes the code harder to read.

> +
> + /* Select which auth token to use; password or [auth token] */
> +
> + if (bioscfg_drv.spm_data.auth_token != NULL)
> + auth_token_choice = bioscfg_drv.spm_data.auth_token;
> + else
> + auth_token_choice = bioscfg_drv.password_data[instance].current_password;
> +
> + a_name_size = bioscfg_calculate_string_buffer(a_name);
> + a_value_size = bioscfg_calculate_string_buffer(a_value);
> + security_area_size = calculate_security_buffer(auth_token_choice);
> + buffer_size = a_name_size + a_value_size + security_area_size;
> +
> + buffer = kmalloc(buffer_size + 1, GFP_KERNEL);
> + if (!buffer) {
> + ret = -ENOMEM;
> + goto out_set_attribute;
> + }
> +
> + /* build variables to set */
> + start = buffer;
> + start = ascii_to_utf16_unicode(start, a_name);
> + if (!start)
> + goto out_set_attribute;

ret is 0 here. Is this success?

> +
> + start = ascii_to_utf16_unicode(start, a_value);
> + if (!start)
> + goto out_set_attribute;

Same as above.

> +
> + populate_security_buffer(start, auth_token_choice);
> +
> + ret = hp_wmi_set_bios_setting(buffer, buffer_size);
> +
> +
> +out_set_attribute:
> + kfree(buffer);
> + mutex_unlock(&bioscfg_drv.mutex);
> + return ret;
> +}
> +
> +/*
> + * hp_wmi_perform_query
> + *
> + * query: The commandtype (enum hp_wmi_commandtype)
> + * write: The command (enum hp_wmi_command)
> + * buffer: Buffer used as input and/or output
> + * insize: Size of input buffer
> + * outsize: Size of output buffer
> + *
> + * returns zero on success
> + * an HP WMI query specific error code (which is positive)
> + * -EINVAL if the query was not successful at all
> + * -EINVAL if the output buffer size exceeds buffersize

How is the caller supposed to distinguish those?

> + *
> + * Note: The buffersize must at least be the maximum of the input and output
> + * size. E.g. Battery info query is defined to have 1 byte input
> + * and 128 byte output. The caller would do:
> + * buffer = kzalloc(128, GFP_KERNEL);
> + * ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ,
> + * buffer, 1, 128)
> + */
> +int hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer,
> + int insize, int outsize)

Can insize and outsize ever be negative?
Maybe use u32 or size_t.

> +{
> + struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL };
> + struct bios_return *bios_return;
> + union acpi_object *obj = NULL;
> + struct bios_args *args = NULL;
> + int mid, actual_outsize;
> + size_t bios_args_size;
> + int ret;
> +
> + mid = encode_outsize_for_pvsz(outsize);
> + if (WARN_ON(mid < 0))
> + return mid;
> +
> + bios_args_size = struct_size(args, data, insize);
> + args = kmalloc(bios_args_size, GFP_KERNEL);
> + if (!args)
> + return -ENOMEM;
> +
> + input.length = bios_args_size;
> + input.pointer = args;
> +
> + args->signature = 0x55434553;

What does this number mean?

> + args->command = command;
> + args->commandtype = query;
> + args->datasize = insize;
> + memcpy(args->data, buffer, flex_array_size(args, data, insize));
> +
> + ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);

The driver is mixing calls to the UUID based APIs and the wmi_device
ones.
wmi_devices is newer and preferred.

> + bioscfg_wmi_error_and_message(ret);
> +
> + if (ret)
> + goto out_free;
> +
> + obj = output.pointer;
> + if (!obj) {
> + ret = -EINVAL;
> + goto out_free;
> + }
> + if (query != HPWMI_SECUREPLATFORM_GET_STATE &&
> + command != HPWMI_SECUREPLATFORM)
> + if (obj->type != ACPI_TYPE_BUFFER ||
> + obj->buffer.length < sizeof(*bios_return)) {
> + pr_warn("query 0x%x returned wrong type or too small buffer\n", query);
> + ret = -EINVAL;
> + goto out_free;
> + }
> +
> +
> + bios_return = (struct bios_return *)obj->buffer.pointer;

For query == HPWMI_SECUREPLATFORM_GET_STATE && command == HPWMI_SECUREPLATFORM
this is not guaranteed to be a buffer.

> + ret = bios_return->return_code;
> + bioscfg_wmi_error_and_message(ret);
> +
> + if (ret) {
> + if (ret != HPWMI_RET_UNKNOWN_COMMAND &&
> + ret != HPWMI_RET_UNKNOWN_CMDTYPE)
> + pr_warn("query 0x%x returned error 0x%x\n", query, ret);
> + goto out_free;
> + }
> +
> + /* Ignore output data of zero size */
> + if (!outsize)
> + goto out_free;
> +
> + actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return)));

actual_outsize could be negative, which will underflow in the call to
memcpy().

> + memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize);
> + memset(buffer + actual_outsize, 0, outsize - actual_outsize);

memcpy_and_pad()

> +
> +out_free:
> + kfree(obj);
> + kfree(args);
> + return ret;
> +}
> +
> +static void *utf16_empty_string(u16 *p)
> +{
> + *p++ = 2;
> + *p++ = (u8)0x00;
> + return p;
> +}
> +
> +/*
> + * ascii_to_utf16_unicode - Convert ascii string to UTF-16 unicode
> + *
> + * BIOS supports UTF-16 characters that are 2 bytes long. No variable
> + * multi-byte language supported.
> + *
> + * @p: Unicode buffer address
> + * @str: string to convert to unicode
> + *
> + * Returns a void pointer to the buffer containing unicode string

This returns a pointer to the end of the written string.

> + */
> +void *ascii_to_utf16_unicode(u16 *p, const u8 *str)
> +{
> + int len = strlen(str);
> + int ret;
> +
> + /*
> + * Add null character when reading an empty string
> + * "02 00 00 00"
> + */
> + if (len == 0)
> + return utf16_empty_string(p);
> +
> + /* Move pointer len * 2 number of bytes */
> + *p++ = len * 2;
> + ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, p, len);
> + if (ret < 0) {
> + dev_err(bioscfg_drv.class_dev, "UTF16 conversion failed\n");
> + goto ascii_to_utf16_unicode_out;
> + }

What if ret != len ?

> +
> + if ((ret * sizeof(u16)) > U16_MAX) {
> + dev_err(bioscfg_drv.class_dev, "Error string too long\n");
> + goto ascii_to_utf16_unicode_out;
> + }
> +
> +ascii_to_utf16_unicode_out:
> + p += len;

In cases of errors this will still point to the end of the data that
should have been written but was not actually written.
The caller has no way to recognize the error case.

> + return p;
> +}
> +
> +/*

kernel-doc comments start with "/**". Note the two asterisks.

> + * hp_wmi_set_bios_setting - Set setting's value in BIOS
> + *
> + * @input_buffer: Input buffer address
> + * @input_size: Input buffer size
> + *
> + * Returns: Count of unicode characters written to BIOS if successful, otherwise
> + * -ENOMEM unable to allocate memory
> + * -EINVAL buffer not allocated or too small
> + */
> +int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size)
> +{
> + union acpi_object *obj;
> + struct acpi_buffer input = {input_size, input_buffer};
> + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
> + int ret = 0;

No need to initialize "ret".

> +
> + ret = wmi_evaluate_method(HP_WMI_SET_BIOS_SETTING_GUID, 0, 1, &input, &output);
> +
> + obj = output.pointer;
> + if (!obj)
> + return -EINVAL;

This skips the bioscfg_wmi_error_and_message call.

> +
> + if (obj->type != ACPI_TYPE_INTEGER)
> + ret = -EINVAL;
> +
> + ret = obj->integer.value;

This overwrites the "ret = -EINVAL" from above.
Add an "else" branch.

> + bioscfg_wmi_error_and_message(ret);
> +
> + kfree(obj);
> + return ret;
> +}
> +
> +static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
> +{
> + mutex_lock(&bioscfg_drv.mutex);
> + bioscfg_drv.bios_attr_wdev = wdev;
> + mutex_unlock(&bioscfg_drv.mutex);
> + return 0;
> +}

Technically a WMI UUID can be present multiple times.
This would lead to the driver being loaded multiple times, each driver
clobbering the bios_attr_wdev of the other drivers.

Maybe check the pointer and return -EEXIST.

This applies to all subdrivers.

> +
> +static void bios_attr_set_interface_remove(struct wmi_device *wdev)
> +{
> + mutex_lock(&bioscfg_drv.mutex);
> + bioscfg_drv.bios_attr_wdev = NULL;
> + mutex_unlock(&bioscfg_drv.mutex);
> +}
> +
> +static const struct wmi_device_id bios_attr_set_interface_id_table[] = {
> + { .guid_string = HP_WMI_BIOS_GUID},
> + { }
> +};
> +static struct wmi_driver bios_attr_set_interface_driver = {
> + .driver = {
> + .name = DRIVER_NAME
> + },
> + .probe = bios_attr_set_interface_probe,
> + .remove = bios_attr_set_interface_remove,
> + .id_table = bios_attr_set_interface_id_table

Put a comma here and above after DRIVER_NAME to reduce future diffs.

> +};
> +
> +int init_bios_attr_set_interface(void)
> +{
> + return wmi_driver_register(&bios_attr_set_interface_driver);
> +}
> +
> +void exit_bios_attr_set_interface(void)
> +{
> + wmi_driver_unregister(&bios_attr_set_interface_driver);
> +}
> +
> +MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table);

2023-04-22 23:11:30

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 04/14] HP BIOSCFG driver - int-attributes

Hi Jorge,

one thing I noticed:

When I recommended to cut down the driver to a minimal feature set I
meant the minimal feature set currently known to the kernel.

This would *not* be sufficient for your HP-specific agent software.
Just the standard type attributes, ordered-list and authentication.

But in the end it would most probably still be a faster path to a
solution that *does* include the full featureset of your requirements.


Also when fixing review comments please try to fix them everywhere where
applicable. Most comments affect many parts of the code.

On 2023-04-20 11:54:44-0500, Jorge Lopez wrote:
> ---
> Based on the latest platform-drivers-x86.git/for-next
> ---
> .../x86/hp/hp-bioscfg/int-attributes.c | 474 ++++++++++++++++++
> 1 file changed, 474 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
> new file mode 100644
> index 000000000000..d8ee39dac3f9
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
> @@ -0,0 +1,474 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions corresponding to integer type attributes under
> + * BIOS Enumeration GUID for use with hp-bioscfg driver.
> + *
> + * Copyright (c) 2022 Hewlett-Packard Inc.
> + */
> +
> +#include "bioscfg.h"
> +
> +GET_INSTANCE_ID(integer);
> +
> +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> +{
> + int instance_id = get_integer_instance_id(kobj);
> +
> + if (instance_id < 0)
> + return instance_id;

Some attributes return -EIO here, some instance_id. This should be
consistent.

> +
> + return sysfs_emit(buf, "%d\n",
> + bioscfg_drv.integer_data[instance_id].current_value);
> +}
> +
> +/*
> + * validate_integer_input() -
> + * Validate input of current_value against lower and upper bound
> + *
> + * @instance_id: The instance on which input is validated
> + * @buf: Input value
> + */
> +static int validate_integer_input(int instance_id, char *buf)
> +{
> + int in_val;
> + int ret;
> +
> +
> + /* BIOS treats it as a read only attribute */
> + if (bioscfg_drv.integer_data[instance_id].common.is_readonly)
> + return -EIO;
> +
> + ret = kstrtoint(buf, 10, &in_val);
> + if (ret < 0)
> + return ret;
> +
> +
> + if (in_val < bioscfg_drv.integer_data[instance_id].lower_bound ||
> + in_val > bioscfg_drv.integer_data[instance_id].upper_bound)
> + return -ERANGE;
> +
> + /*
> + * set pending reboot flag depending on
> + * "RequiresPhysicalPresence" value
> + */
> + if (bioscfg_drv.integer_data[instance_id].common.requires_physical_presence)
> + bioscfg_drv.pending_reboot = true;
> + return 0;
> +}
> +
> +static void update_integer_value(int instance_id, char *attr_value)
> +{
> + int in_val;
> + int ret;
> +
> + ret = kstrtoint(attr_value, 10, &in_val);
> + if (ret == 0)
> + bioscfg_drv.integer_data[instance_id].current_value = in_val;

If the error is not propagated there should be at least be a warning.

> +}
> +
> +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, integer);
> +static struct kobj_attribute integer_display_langcode =
> + __ATTR_RO(display_name_language_code);
> +
> +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, integer);
> +static struct kobj_attribute integer_display_name =
> + __ATTR_RO(display_name);
> +
> +ATTRIBUTE_PROPERTY_STORE(current_value, integer);
> +static struct kobj_attribute integer_current_val =
> + __ATTR_RW_MODE(current_value, 0644);
> +
> +ATTRIBUTE_N_PROPERTY_SHOW(lower_bound, integer);
> +static struct kobj_attribute integer_lower_bound =
> + __ATTR_RO(lower_bound);
> +
> +ATTRIBUTE_N_PROPERTY_SHOW(upper_bound, integer);
> +static struct kobj_attribute integer_upper_bound =
> + __ATTR_RO(upper_bound);
> +
> +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, integer);
> +static struct kobj_attribute integer_prerequisites_size_val =
> + __ATTR_RO(prerequisites_size);
> +
> +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, integer);
> +static struct kobj_attribute integer_prerequisites_val =
> + __ATTR_RO(prerequisites);
> +
> +ATTRIBUTE_N_PROPERTY_SHOW(scalar_increment, integer);
> +static struct kobj_attribute integer_scalar_increment =
> + __ATTR_RO(scalar_increment);
> +
> +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "integer\n");
> +}
> +static struct kobj_attribute integer_type =
> + __ATTR_RO(type);
> +
> +static struct attribute *integer_attrs[] = {
> + &integer_display_langcode.attr,
> + &integer_display_name.attr,
> + &integer_current_val.attr,
> + &integer_lower_bound.attr,
> + &integer_upper_bound.attr,
> + &integer_scalar_increment.attr,
> + &integer_prerequisites_size_val.attr,
> + &integer_prerequisites_val.attr,
> + &integer_type.attr,
> + NULL
> +};
> +
> +static const struct attribute_group integer_attr_group = {
> + .attrs = integer_attrs,
> +};
> +
> +int alloc_integer_data(void)
> +{
> + int ret = 0;

No need for the ret variable.

> +
> + bioscfg_drv.integer_instances_count = get_instance_count(HP_WMI_BIOS_INTEGER_GUID);
> + bioscfg_drv.integer_data = kcalloc(bioscfg_drv.integer_instances_count,
> + sizeof(struct integer_data), GFP_KERNEL);

sizeof(bioscfg_drv.integer_data);

> +
> + if (!bioscfg_drv.integer_data) {
> + bioscfg_drv.integer_instances_count = 0;
> + ret = -ENOMEM;
> + }
> + return ret;
> +}
> +
> +/* Expected Values types associated with each element */
> +static const acpi_object_type expected_integer_types[] = {
> + [NAME] = ACPI_TYPE_STRING,
> + [VALUE] = ACPI_TYPE_STRING,
> + [PATH] = ACPI_TYPE_STRING,
> + [IS_READONLY] = ACPI_TYPE_INTEGER,
> + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
> + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
> + [SEQUENCE] = ACPI_TYPE_INTEGER,
> + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
> + [PREREQUISITES] = ACPI_TYPE_STRING,
> + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
> + [INT_LOWER_BOUND] = ACPI_TYPE_INTEGER,
> + [INT_UPPER_BOUND] = ACPI_TYPE_INTEGER,
> + [INT_SCALAR_INCREMENT] = ACPI_TYPE_INTEGER

Trailing comma.

> +};
> +
> +/*
> + * populate_int_data() -
> + * Populate all properties of an instance under integer attribute
> + *
> + * @integer_obj: ACPI object with integer data
> + * @instance_id: The instance to enumerate
> + * @attr_name_kobj: The parent kernel object
> + */
> +int populate_integer_package_data(union acpi_object *integer_obj,
> + int instance_id,
> + struct kobject *attr_name_kobj)
> +{
> + bioscfg_drv.integer_data[instance_id].attr_name_kobj = attr_name_kobj;
> + populate_integer_elements_from_package(integer_obj,
> + integer_obj->package.count,
> + instance_id);
> + update_attribute_permissions(bioscfg_drv.integer_data[instance_id].common.is_readonly,
> + &integer_current_val);
> + friendly_user_name_update(bioscfg_drv.integer_data[instance_id].common.path,
> + attr_name_kobj->name,
> + bioscfg_drv.integer_data[instance_id].common.display_name,
> + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name));
> + return sysfs_create_group(attr_name_kobj, &integer_attr_group);
> +}
> +
> +int populate_integer_elements_from_package(union acpi_object *integer_obj,
> + int integer_obj_count,
> + int instance_id)
> +{
> + char *str_value = NULL;
> + int value_len;
> + int ret = 0;
> + u32 size = 0;
> + u32 int_value;
> + int elem = 0;
> + int reqs;
> + int eloc;
> +
> + if (!integer_obj)
> + return -EINVAL;
> +
> + strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
> + LANG_CODE_STR,
> + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
> +
> + for (elem = 1, eloc = 1; elem < integer_obj_count; elem++, eloc++) {
> +
> + /* ONLY look at the first INTEGER_ELEM_CNT elements */

Why?

> + if (eloc == INTEGER_ELEM_CNT)
> + goto exit_integer_package;
> +
> + switch (integer_obj[elem].type) {
> + case ACPI_TYPE_STRING:
> +
> + if (elem != PREREQUISITES) {
> + ret = convert_hexstr_to_str(integer_obj[elem].string.pointer,
> + integer_obj[elem].string.length,
> + &str_value, &value_len);
> + if (ret)
> + continue;
> + }
> + break;
> + case ACPI_TYPE_INTEGER:
> + int_value = (u32)integer_obj[elem].integer.value;
> + break;
> + default:
> + pr_warn("Unsupported object type [%d]\n", integer_obj[elem].type);
> + continue;
> + }
> + /* Check that both expected and read object type match */
> + if (expected_integer_types[eloc] != integer_obj[elem].type) {
> + pr_err("Error expected type %d for elem %d, but got type %d instead\n",
> + expected_integer_types[eloc], elem, integer_obj[elem].type);
> + return -EIO;
> + }
> + /* Assign appropriate element value to corresponding field*/
> + switch (eloc) {
> + case VALUE:
> + ret = kstrtoint(str_value, 10, &int_value);
> + if (ret)
> + continue;
> +
> + bioscfg_drv.integer_data[instance_id].current_value = int_value;
> + break;
> + case PATH:
> + strscpy(bioscfg_drv.integer_data[instance_id].common.path, str_value,
> + sizeof(bioscfg_drv.integer_data[instance_id].common.path));
> + break;
> + case IS_READONLY:
> + bioscfg_drv.integer_data[instance_id].common.is_readonly = int_value;
> + break;
> + case DISPLAY_IN_UI:
> + bioscfg_drv.integer_data[instance_id].common.display_in_ui = int_value;
> + break;
> + case REQUIRES_PHYSICAL_PRESENCE:
> + bioscfg_drv.integer_data[instance_id].common.requires_physical_presence = int_value;
> + break;
> + case SEQUENCE:
> + bioscfg_drv.integer_data[instance_id].common.sequence = int_value;
> + break;
> + case PREREQUISITES_SIZE:
> + bioscfg_drv.integer_data[instance_id].common.prerequisites_size = int_value;
> +
> + if (int_value > MAX_PREREQUISITES_SIZE)
> + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> + /*
> + * This HACK is needed to keep the expected
> + * element list pointing to the right obj[elem].type
> + * when the size is zero. PREREQUISITES
> + * object is omitted by BIOS when the size is
> + * zero.
> + */
> + if (int_value == 0)
> + eloc++;
> + break;
> + case PREREQUISITES:
> + size = bioscfg_drv.integer_data[instance_id].common.prerequisites_size;
> +
> + for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
> + if (elem >= integer_obj_count) {
> + pr_err("Error elem-objects package is too small\n");
> + return -EINVAL;
> + }
> +
> + ret = convert_hexstr_to_str(integer_obj[elem + reqs].string.pointer,
> + integer_obj[elem + reqs].string.length,
> + &str_value, &value_len);
> +
> + if (ret)
> + continue;
> +
> + strscpy(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs],
> + str_value,
> + sizeof(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs]));
> + kfree(str_value);
> + }
> + break;
> +
> + case SECURITY_LEVEL:
> + bioscfg_drv.integer_data[instance_id].common.security_level = int_value;
> + break;
> + case INT_LOWER_BOUND:
> + bioscfg_drv.integer_data[instance_id].lower_bound = int_value;
> + break;
> + case INT_UPPER_BOUND:
> + bioscfg_drv.integer_data[instance_id].upper_bound = int_value;
> + break;
> + case INT_SCALAR_INCREMENT:
> + bioscfg_drv.integer_data[instance_id].scalar_increment = int_value;
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
> + break;
> + }
> + }
> +exit_integer_package:
> + kfree(str_value);
> + return 0;
> +}
> +
> +
> +/*
> + * populate_integer_buffer_data() -
> + * Populate all properties of an instance under integer attribute
> + *
> + * @buffer_ptr: Buffer pointer
> + * @buffer_size: Buffer size
> + * @instance_id: The instance to enumerate
> + * @attr_name_kobj: The parent kernel object
> + */
> +int populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
> + struct kobject *attr_name_kobj)
> +{
> + bioscfg_drv.integer_data[instance_id].attr_name_kobj = attr_name_kobj;
> +
> + /* Populate integer elements */
> + populate_integer_elements_from_buffer(buffer_ptr, buffer_size,
> + instance_id);
> + update_attribute_permissions(bioscfg_drv.integer_data[instance_id].common.is_readonly,
> + &integer_current_val);
> + friendly_user_name_update(bioscfg_drv.integer_data[instance_id].common.path,
> + attr_name_kobj->name,
> + bioscfg_drv.integer_data[instance_id].common.display_name,
> + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name));
> +
> + return sysfs_create_group(attr_name_kobj, &integer_attr_group);
> +}
> +
> +int populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id)
> +{
> + char *dst = NULL;
> + int elem;
> + int reqs;
> + int integer;
> + int size = 0;
> + int ret;
> + int dst_size = *buffer_size / sizeof(u16);
> +
> + dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
> + if (!dst)
> + return -ENOMEM;
> +
> + elem = 0;
> + strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
> + LANG_CODE_STR,
> + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
> +
> + for (elem = 1; elem < 3; elem++) {
> +
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + if (ret < 0)
> + continue;
> +
> + switch (elem) {
> + case VALUE:
> + ret = kstrtoint(dst, 10, &integer);
> + if (ret)
> + continue;
> +
> + bioscfg_drv.integer_data[instance_id].current_value = integer;
> + break;
> + case PATH:
> + strscpy(bioscfg_drv.integer_data[instance_id].common.path, dst,
> + sizeof(bioscfg_drv.integer_data[instance_id].common.path));
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
> + break;
> + }
> + }
> +
> + for (elem = 3; elem < INTEGER_ELEM_CNT; elem++) {

This loop pattern seems weird to me.
It is not obvious that the values are read in the order of the switch()
branches from the buffer.

Something more obvious would be:

instance.common.is_readonly = read_int_from_buf(&buffer_ptr);
instance.common.display_in_ui = read_int_from_buf(&buffer_ptr);
instance.common.requires_physical_presence = read_int_from_buf(&buffer_ptr);

This would make it clear that these are fields read in order from the
buffer. Without having to also look at the numeric values of the
defines.

Furthermore it would make the code shorter and errorhandling would be
clearer and the API similar to the netlink APIs.

Or maybe with error reporting:

ret = read_int_from_buf(&buffer_ptr, &instance.common.is_readonly);
if (ret)
...
ret = read_int_from_buf(&buffer_ptr, &instance.common.display_in_ui);
if (ret)
...

> +
> + if (elem != PREREQUISITES) {
> + ret = get_integer_from_buffer((int **)&buffer_ptr, buffer_size, (int *)&integer);
> + if (ret < 0)
> + continue;
> + }
> +
> + switch (elem) {
> +
> + case IS_READONLY:
> + bioscfg_drv.integer_data[instance_id].common.is_readonly = integer;
> + break;
> + case DISPLAY_IN_UI:
> + bioscfg_drv.integer_data[instance_id].common.display_in_ui = integer;
> + break;
> + case REQUIRES_PHYSICAL_PRESENCE:
> + bioscfg_drv.integer_data[instance_id].common.requires_physical_presence = integer;
> + break;
> + case SEQUENCE:
> + bioscfg_drv.integer_data[instance_id].common.sequence = integer;
> + break;
> + case PREREQUISITES_SIZE:
> + bioscfg_drv.integer_data[instance_id].common.prerequisites_size = integer;
> + size = integer;
> + if (size > MAX_PREREQUISITES_SIZE)
> + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + // PREREQUISITES:
> + elem++;
> + for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + if (ret < 0)
> + continue;
> +
> + strscpy(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs],
> + dst,
> + sizeof(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs]));
> + }
> + break;
> +
> + case SECURITY_LEVEL:
> + bioscfg_drv.integer_data[instance_id].common.security_level = integer;
> + break;
> + case INT_LOWER_BOUND:
> + bioscfg_drv.integer_data[instance_id].lower_bound = integer;
> + break;
> + case INT_UPPER_BOUND:
> + bioscfg_drv.integer_data[instance_id].upper_bound = integer;
> + break;
> + case INT_SCALAR_INCREMENT:
> + bioscfg_drv.integer_data[instance_id].scalar_increment = integer;
> + break;
> +
> + default:
> + pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
> + break;
> + }
> + }
> + kfree(dst);
> +
> + return 0;
> +}
> +
> +/*
> + * exit_integer_attributes() - Clear all attribute data
> + *
> + * Clears all data allocated for this group of attributes
> + */
> +void exit_integer_attributes(void)
> +{
> + int instance_id;
> +
> + for (instance_id = 0; instance_id < bioscfg_drv.integer_instances_count; instance_id++) {
> +
> + struct kobject *attr_name_kobj = bioscfg_drv.integer_data[instance_id].attr_name_kobj;
> +
> + if (attr_name_kobj)
> + sysfs_remove_group(attr_name_kobj, &integer_attr_group);
> + }
> + bioscfg_drv.integer_instances_count = 0;
> +
> + kfree(bioscfg_drv.integer_data);
> + bioscfg_drv.integer_data = NULL;
> +}

2023-04-22 23:11:30

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 03/14] HP BIOSCFG driver - bioscfg

On 2023-04-20 11:54:43-0500, Jorge Lopez wrote:
> ---
> drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 961 +++++++++++++++++++
> 1 file changed, 961 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
> new file mode 100644
> index 000000000000..4b0d4f56e65f
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
> @@ -0,0 +1,961 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Common methods for use with hp-bioscfg driver
> + *
> + * Copyright (c) 2022 HP Development Company, L.P.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/fs.h>
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/wmi.h>
> +#include "bioscfg.h"
> +#include "../../firmware_attributes_class.h"
> +#include <linux/nls.h>
> +#include <linux/errno.h>
> +
> +MODULE_AUTHOR("Jorge Lopez <[email protected]>");
> +MODULE_DESCRIPTION("HP BIOS Configuration Driver");
> +MODULE_LICENSE("GPL");
> +
> +struct bioscfg_priv bioscfg_drv = {
> + .mutex = __MUTEX_INITIALIZER(bioscfg_drv.mutex),
> +};
> +
> +static struct class *fw_attr_class;
> +
> +int get_integer_from_buffer(int **buffer, u32 *buffer_size, int *integer)
> +{

It would be clearer to use u32 or s32 instead of int/integer.

This should also take a "u8 **buffer" to avoid casts in all the callers.

> + int *ptr = PTR_ALIGN(*buffer, 4);
> +
> + /* Ensure there is enough space remaining to read the integer */
> + if (*buffer_size < sizeof(int))
> + return -EINVAL;
> +
> + *integer = *(ptr++);
> + *buffer = ptr;
> + *buffer_size -= sizeof(int);
> +
> + return 0;
> +}
> +
> +
> +int get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size)
> +{
> + u16 *src = (u16 *)*buffer;
> + u16 src_size;
> +
> + u16 size;
> + int i;
> + int escape = 0;
> + int conv_dst_size;
> +
> + if (*buffer_size < sizeof(u16))
> + return -EINVAL;
> +
> + src_size = *(src++);
> + /* size value in u16 chars */
> + size = src_size / sizeof(u16);
> +
> + /* Ensure there is enough space remaining to read and convert
> + * the string
> + */
> + if (*buffer_size < src_size)
> + return -EINVAL;
> +
> + for (i = 0; i < size; i++)
> + if (src[i] == '\\' ||
> + src[i] == '\r' ||
> + src[i] == '\n' ||
> + src[i] == '\t')
> + escape++;

Do size++ above and get rid of the variable "escape".

> +
> + size += escape;
> +
> + /*
> + * Conversion is limited to destination string max number of
> + * bytes.
> + */
> + conv_dst_size = size;
> + if (size > dst_size)
> + conv_dst_size = dst_size - 1;
> +
> + /*
> + * convert from UTF-16 unicode to ASCII
> + */
> + utf16s_to_utf8s(src, src_size, UTF16_HOST_ENDIAN, dst, conv_dst_size);

The return value is ignored.

> + dst[conv_dst_size] = 0;
> +
> + for (i = 0; i < size && i < conv_dst_size; i++) {
> + if (*src == '\\' ||
> + *src == '\r' ||
> + *src == '\n' ||
> + *src == '\t')
> + dst[i++] = '\\';
> +
> + if (*src == '\r')
> + dst[i] = 'r';
> + else if (*src == '\n')
> + dst[i] = 'n';
> + else if (*src == '\t')
> + dst[i] = 't';
> + else if (*src == '"')
> + dst[i] = '\'';
> + else
> + dst[i] = *src;
> + src++;
> + }
> +
> + *buffer = (u8 *)src;
> + *buffer_size -= size * sizeof(u16);
> +
> + return size;
> +}
> +
> +
> +/*
> + * calculate_string_buffer() - determines size of string buffer for use with BIOS communication
> + * @str: the string to calculate based upon
> + */
> +size_t bioscfg_calculate_string_buffer(const char *str)
> +{
> + int length = strlen(str);
> + int size;
> +
> + /* BIOS expects 4 bytes when an empty string is found */
> + if (!length)
> + length = 1;
> +
> + /* u16 length field + one UTF16 char for each input char */
> + size = sizeof(u16) + length * sizeof(u16);
> +
> + return size;
> +}

This full function could be:

{
/* BIOS expects 4 bytes when an empty string is found */
if (length == 0)
return 4;

/* u16 length field + one UTF16 char for each input char */
return sizeof(u16) + strlen(str) * sizeof(u16);
}

> +
> +int bioscfg_wmi_error_and_message(int error_code)
> +{
> + char *error_msg = NULL;
> + int ret;
> +
> + switch (error_code) {
> + case SUCCESS:
> + error_msg = "Success";
> + ret = 0;
> + break;
> + case CMD_FAILED:
> + error_msg = "Command failed";
> + ret = -EINVAL;
> + break;
> + case INVALID_SIGN:
> + error_msg = "Invalid signature";
> + ret = -EINVAL;
> + break;
> + case INVALID_CMD_VALUE:
> + error_msg = "Invalid command value/Feature not supported";
> + ret = -EOPNOTSUPP;
> + break;
> + case INVALID_CMD_TYPE:
> + error_msg = "Invalid command type";
> + ret = -EINVAL;
> + break;
> + case INVALID_DATA_SIZE:
> + error_msg = "Invalid data size";
> + ret = -EINVAL;
> + break;
> + case INVALID_CMD_PARAM:
> + error_msg = "Invalid command parameter";
> + ret = -EINVAL;
> + break;
> + case ENCRYP_CMD_REQUIRED:
> + error_msg = "Secure/encrypted command required";
> + ret = -EACCES;
> + break;
> + case NO_SECURE_SESSION:
> + error_msg = "No secure session established";
> + ret = -EACCES;
> + break;
> + case SECURE_SESSION_FOUND:
> + error_msg = "Secure session already established";
> + ret = -EACCES;
> + break;
> + case SECURE_SESSION_FAILED:
> + error_msg = "Secure session failed";
> + ret = -EIO;
> + break;
> + case AUTH_FAILED:
> + error_msg = "Other permission/Authentication failed";
> + ret = -EACCES;
> + break;
> + case INVALID_BIOS_AUTH:
> + error_msg = "Invalid BIOS administrator password";
> + ret = -EINVAL;
> + break;
> + case NONCE_DID_NOT_MATCH:
> + error_msg = "Nonce did not match";
> + ret = -EINVAL;
> + break;
> + case GENERIC_ERROR:
> + error_msg = "Generic/Other error";
> + ret = -EIO;
> + break;
> + case BIOS_ADMIN_POLICY_NOT_MET:
> + error_msg = "BIOS Admin password does not meet password policy requirements";
> + ret = -EINVAL;
> + break;
> + case BIOS_ADMIN_NOT_SET:
> + error_msg = "BIOS Setup password is not set.";
> + ret = -EPERM;
> + break;
> + case P21_NO_PROVISIONED:
> + error_msg = "P21 is not provisioned";
> + ret = -EPERM;
> + break;
> + case P21_PROVISION_IN_PROGRESS:
> + error_msg = "P21 is already provisioned or provisioning is in progress and a signing key has already been sent.";
> + ret = -EINPROGRESS;
> + break;
> + case P21_IN_USE:
> + error_msg = "P21 in use (cannot deprovision)";
> + ret = -EPERM;
> + break;
> + case HEP_NOT_ACTIVE:
> + error_msg = "HEP not activated";
> + ret = -EPERM;
> + break;
> + case HEP_ALREADY_SET:
> + error_msg = "HEP Transport already set";
> + ret = -EINVAL;
> + break;
> + case HEP_CHECK_STATE:
> + error_msg = "Check the current HEP state";
> + ret = -EINVAL;
> + break;
> + default:
> + error_msg = "Generic/Other error";
> + ret = -EIO;
> + break;
> + }
> +
> + if (error_code)
> + pr_warn_ratelimited("Returned error 0x%x, \"%s\"\n", error_code, error_msg);
> +
> + return ret;
> +}
> +
> +
> +/*
> + * pending_reboot_show() - sysfs implementaton for read pending_reboot
> + * @kobj: Kernel object for this attribute
> + * @attr: Kernel object attribute
> + * @buf: The buffer to display to userspace

As said before, no need to document the API of sysfs ATTR callbacks.
They are well-known.

> + *
> + * Stores default value as 0
> + * When current_value is changed this attribute is set to 1 to notify reboot may be required
> + */
> +static ssize_t pending_reboot_show(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "%d\n", bioscfg_drv.pending_reboot);
> +}
> +static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
> +
> +/*
> + * create_attributes_level_sysfs_files() - Creates pending_reboot attributes
> + */
> +static int create_attributes_level_sysfs_files(void)
> +{
> + int ret;
> +
> + ret = sysfs_create_file(&bioscfg_drv.main_dir_kset->kobj, &pending_reboot.attr);
> + if (ret)
> + return ret;
> +
> + return 0;

Just:
return sysfs_create_file(&bioscfg_drv.main_dir_kset->kobj, &pending_reboot.attr);

> +}
> +
> +
> +static void attr_name_release(struct kobject *kobj)
> +{
> + kfree(kobj);
> +}
> +
> +static const struct kobj_type attr_name_ktype = {
> + .release = attr_name_release,
> + .sysfs_ops = &kobj_sysfs_ops,
> +};
> +
> +/*
> + * get_wmiobj_pointer() - Get Content of WMI block for particular instance
> + *
> + * @instance_id: WMI instance ID
> + * @guid_string: WMI GUID (in str form)
> + *
> + * Fetches the content for WMI block (instance_id) under GUID (guid_string)
> + * Caller must kfree the return
> + */
> +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string)
> +{
> + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
> + acpi_status status;
> +
> + status = wmi_query_block(guid_string, instance_id, &out);
> + return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL;
> +}
> +
> +/*
> + * get_instance_count() - Compute total number of instances under guid_string
> + *
> + * @guid_string: WMI GUID (in string form)
> + */
> +int get_instance_count(const char *guid_string)
> +{
> + union acpi_object *wmi_obj = NULL;
> + int i = 0;
> +
> + do {
> + kfree(wmi_obj);
> + wmi_obj = get_wmiobj_pointer(i, guid_string);
> + i++;
> + } while (wmi_obj);
> +
> + return i-1;
> +}
> +
> +/*
> + * alloc_attributes_data() - Allocate attributes data for a particular type
> + *
> + * @attr_type: Attribute type to allocate
> + */
> +static int alloc_attributes_data(int attr_type)
> +{
> + int retval = 0;

No need for this intermediate variable.

> +
> + switch (attr_type) {
> + case HPWMI_STRING_TYPE:
> + retval = alloc_string_data();
> + break;
> + case HPWMI_INTEGER_TYPE:
> + retval = alloc_integer_data();
> + break;
> + case HPWMI_ENUMERATION_TYPE:
> + retval = alloc_enumeration_data();
> + break;
> + case HPWMI_ORDERED_LIST_TYPE:
> + retval = alloc_ordered_list_data();
> + break;
> + case HPWMI_PASSWORD_TYPE:
> + retval = alloc_password_data();
> + break;
> + default:
> + break;
> + }
> +
> + return retval;
> +}
> +
> +int convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len)

Could this be replaced by "string_unescape(UNESCAPE_SPACE)" ?

> +{
> + int ret = 0;
> + int new_len = 0;
> + char tmp[] = "0x00";
> + char *new_str = NULL;
> + long ch;
> + int i;
> +
> + if (input_len <= 0 || input == NULL || str == NULL || len == NULL)
> + return -EINVAL;
> +
> + *len = 0;
> + *str = NULL;
> +
> + new_str = kmalloc(input_len, GFP_KERNEL);
> + if (!new_str)
> + return -ENOMEM;
> +
> + for (i = 0; i < input_len; i += 5) {
> + strncpy(tmp, input + i, strlen(tmp));
> + if (kstrtol(tmp, 16, &ch) == 0) {
> + // escape char
> + if (ch == '\\' || ch == '\r' || ch == '\n' || ch == '\t') {
> + if (ch == '\r')
> + ch = 'r';
> + else if (ch == '\n')
> + ch = 'n';
> + else if (ch == '\t')
> + ch = 't';
> + new_str[new_len++] = '\\';
> + }
> + new_str[new_len++] = ch;
> + if (ch == '\0')
> + break;
> + }
> + }
> +
> + if (new_len) {
> + new_str[new_len] = '\0';
> + *str = krealloc(new_str, (new_len + 1) * sizeof(char), GFP_KERNEL);
> + if (*str)
> + *len = new_len;
> + else
> + ret = -ENOMEM;
> + } else {
> + ret = -EFAULT;
> + }
> +
> + if (ret)
> + kfree(new_str);
> + return ret;
> +}
> +
> +/* map output size to the corresponding WMI method id */
> +int encode_outsize_for_pvsz(int outsize)
> +{
> + if (outsize > 4096)
> + return -EINVAL;
> + if (outsize > 1024)
> + return 5;
> + if (outsize > 128)
> + return 4;
> + if (outsize > 4)
> + return 3;
> + if (outsize > 0)
> + return 2;
> + return 1;
> +}
> +
> +/*
> + * Update friendly display name for several attributes associated to
> + * 'Schedule Power-On'
> + */
> +void friendly_user_name_update(char *path, const char *attr_name,
> + char *attr_display, int attr_size)
> +{
> + char *found = NULL;

No need for this variable.

> +
> + found = strstr(path, SCHEDULE_POWER_ON);
> + if (found)
> + snprintf(attr_display,
> + attr_size,
> + "%s - %s",
> + SCHEDULE_POWER_ON,
> + attr_name);
> + else
> + strscpy(attr_display, attr_name, attr_size);
> +}
> +
> +/*
> + * update_attribute_permissions() - Update attributes permissions when
> + * isReadOnly value is 1
> + *
> + * @isReadOnly: ReadOnly value
> + * @current_val: kobj_attribute corresponding to attribute.
> + *
> + */
> +void update_attribute_permissions(u32 isReadOnly, struct kobj_attribute *current_val)
> +{
> + if (isReadOnly)
> + current_val->attr.mode = (umode_t)0444;
> + else
> + current_val->attr.mode = (umode_t)0644;

No need for the casts.

isReadOnly does not use the correct naming scheme.

> +}
> +
> +
> +/**
> + * destroy_attribute_objs() - Free a kset of kobjects
> + * @kset: The kset to destroy
> + *
> + * Fress kobjects created for each attribute_name under attribute type kset
> + */
> +static void destroy_attribute_objs(struct kset *kset)
> +{
> + struct kobject *pos, *next;
> +
> + list_for_each_entry_safe(pos, next, &kset->list, entry)
> + kobject_put(pos);
> +}
> +
> +/**
> + * release_attributes_data() - Clean-up all sysfs directories and files created
> + */
> +static void release_attributes_data(void)
> +{
> + mutex_lock(&bioscfg_drv.mutex);
> +
> + exit_string_attributes();
> + exit_integer_attributes();
> + exit_enumeration_attributes();
> + exit_ordered_list_attributes();
> + exit_password_attributes();
> + exit_sure_start_attributes();
> + exit_secure_platform_attributes();
> +
> + if (bioscfg_drv.authentication_dir_kset) {
> + destroy_attribute_objs(bioscfg_drv.authentication_dir_kset);
> + kset_unregister(bioscfg_drv.authentication_dir_kset);
> + bioscfg_drv.authentication_dir_kset = NULL;
> + }
> + if (bioscfg_drv.main_dir_kset) {
> + sysfs_remove_file(&bioscfg_drv.main_dir_kset->kobj, &pending_reboot.attr);
> + destroy_attribute_objs(bioscfg_drv.main_dir_kset);
> + kset_unregister(bioscfg_drv.main_dir_kset);
> + bioscfg_drv.main_dir_kset = NULL;
> + }
> + mutex_unlock(&bioscfg_drv.mutex);
> +}
> +
> +
> +/*
> + * hp_add_other_attributes - Initialize HP custom attributes not reported by
> + * BIOS and required to support Secure Platform, Sure Start, and Sure
> + * Admin.
> + * @attr_type: Custom HP attribute not reported by BIOS
> + *
> + * Initialiaze all 3 types of attributes: Platform, Sure Start, and Sure
> + * Admin object. Populates each attrbute types respective properties
> + * under sysfs files.

Typos in this comment. checkpatch.pl can use codespell to check for
typos.

> + *
> + * Returns zero(0) if successful. Otherwise, a negative value.
> + */
> +static int hp_add_other_attributes(int attr_type)

The naming scheme is inconsistent. The prefixes "bioscfg_" and "hp_" are
used but many symbols don't use any prefix.

> +{
> + struct kobject *attr_name_kobj;
> + union acpi_object *obj = NULL;

This obj is never used except to be freed.

> + int retval = 0;
> + u8 *attr_name;

const char *

> +
> + mutex_lock(&bioscfg_drv.mutex);
> +
> + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
> + if (!attr_name_kobj) {
> + retval = -ENOMEM;
> + goto err_other_attr_init;
> + }
> +
> + /* Check if attribute type is supported */
> + switch (attr_type) {
> + case HPWMI_SECURE_PLATFORM_TYPE:
> + attr_name_kobj->kset = bioscfg_drv.authentication_dir_kset;
> + attr_name = SPM_STR;
> + break;
> +
> + case HPWMI_SURE_START_TYPE:
> + attr_name_kobj->kset = bioscfg_drv.main_dir_kset;
> + attr_name = SURE_START_STR;
> + break;
> +
> + default:
> + pr_err("Error: Unknown attr_type: %d\n", attr_type);
> + retval = -EINVAL;
> + goto err_other_attr_init;
> + }
> +
> + retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
> + NULL, "%s", attr_name);
> + if (retval) {
> + pr_err("Error encountered [%d]\n", retval);
> + kobject_put(attr_name_kobj);
> + goto err_other_attr_init;
> + }
> +
> + /* Populate attribute data */
> + switch (attr_type) {
> + case HPWMI_SECURE_PLATFORM_TYPE:
> + retval = populate_secure_platform_data(attr_name_kobj);
> + break;
> +
> + case HPWMI_SURE_START_TYPE:
> + retval = populate_sure_start_data(attr_name_kobj);
> + break;
> +
> + default:
> + goto err_other_attr_init;
> + }
> +
> + mutex_unlock(&bioscfg_drv.mutex);
> + return 0;
> +
> +err_other_attr_init:

As mentioned before, there is no need to encode the function name into
the jump labels.

> + mutex_unlock(&bioscfg_drv.mutex);
> + kfree(obj);
> + return retval;
> +}
> +
> +/*
> + * hp_init_bios_attributes - Initialize all attributes for a type
> + * @attr_type: The attribute type to initialize
> + * @guid: The WMI GUID associated with this type to initialize
> + *
> + * Initialiaze all 5 types of attributes: enumeration, integer,
> + * string, password, ordered list object. Populates each attrbute types
> + * respective properties under sysfs files
> + */
> +static int hp_init_bios_attributes(int attr_type, const char *guid)
> +{
> + struct kobject *attr_name_kobj;
> + union acpi_object *obj = NULL;
> + union acpi_object *elements;
> + struct kset *tmp_set;
> + int min_elements;
> + char str[MAX_BUFF];
> +
> + char *temp_str = NULL;

temp_str vs tmp_set

It's using different names.

Also this does not give any indication about what it does.

Maybe "unescaped_name".

> + char *str_value = NULL;
> + int str_len;
> + int ret = 0;
> +
> + u8 *buffer_ptr = NULL;
> + int buffer_size;
> +
> +
> + /* instance_id needs to be reset for each type GUID
> + * also, instance IDs are unique within GUID but not across
> + */
> + int instance_id = 0;
> + int retval = 0;

"retval" and "ret"?

> +
> + retval = alloc_attributes_data(attr_type);
> + if (retval)
> + return retval;
> +
> + switch (attr_type) {
> + case HPWMI_STRING_TYPE:
> + min_elements = 12;
> + break;
> + case HPWMI_INTEGER_TYPE:
> + min_elements = 13;
> + break;
> + case HPWMI_ENUMERATION_TYPE:
> + min_elements = 13;
> + break;
> + case HPWMI_ORDERED_LIST_TYPE:
> + min_elements = 12;
> + break;
> + case HPWMI_PASSWORD_TYPE:
> + min_elements = 15;
> + break;
> + default:
> + pr_err("Error: Unknown attr_type: %d\n", attr_type);
> + return -EINVAL;
> + }
> +
> + /* need to use specific instance_id and guid combination to get right data */
> + obj = get_wmiobj_pointer(instance_id, guid);
> + if (!obj)
> + return -ENODEV;
> +
> + mutex_lock(&bioscfg_drv.mutex);
> + while (obj) {
> + if (obj->type != ACPI_TYPE_PACKAGE && obj->type != ACPI_TYPE_BUFFER) {
> + pr_err("Error: Expected ACPI-package or buffer type, got: %d\n", obj->type);
> + retval = -EIO;
> + goto err_attr_init;
> + }
> +
> + /* Take action appropriate to each ACPI TYPE */
> + if (obj->type == ACPI_TYPE_PACKAGE) {
> + if (obj->package.count < min_elements) {
> + pr_err("ACPI-package does not have enough elements: %d < %d\n",
> + obj->package.count, min_elements);
> + goto nextobj;
> + }
> +
> + elements = obj->package.elements;
> +
> + /* sanity checking */
> + if (elements[NAME].type != ACPI_TYPE_STRING) {
> + pr_debug("incorrect element type\n");
> + goto nextobj;
> + }
> + if (strlen(elements[NAME].string.pointer) == 0) {
> + pr_debug("empty attribute found\n");
> + goto nextobj;
> + }
> +
> + if (attr_type == HPWMI_PASSWORD_TYPE)
> + tmp_set = bioscfg_drv.authentication_dir_kset;
> + else
> + tmp_set = bioscfg_drv.main_dir_kset;
> +
> + /* convert attribute name to string */
> + retval = convert_hexstr_to_str(elements[NAME].string.pointer,
> + elements[NAME].string.length,
> + &str_value, &str_len);
> +
> + if (retval) {
> + pr_debug("Failed to populate integer package data. Error [0%0x]\n", ret);
> + kfree(str_value);

convert_hexstr_to_str should make sure that the data is freed on error
on its own.

> + return ret;
> + }
> +
> + if (kset_find_obj(tmp_set, str_value)) {
> + pr_debug("Duplicate attribute name found - %s\n",
> + str_value);
> + goto nextobj;
> + }
> +
> + /* build attribute */
> + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
> + if (!attr_name_kobj) {
> + retval = -ENOMEM;
> + goto err_attr_init;
> + }
> +
> + attr_name_kobj->kset = tmp_set;
> +
> + retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
> + NULL, "%s", str_value);
> +
> + if (retval) {
> + kobject_put(attr_name_kobj);

The kobj was not created, why does it need the kobj_put() ?

> + goto err_attr_init;
> + }
> +
> + /* enumerate all of these attributes */
> + switch (attr_type) {
> + case HPWMI_STRING_TYPE:
> + retval = populate_string_package_data(elements,
> + instance_id,
> + attr_name_kobj);
> + break;
> + case HPWMI_INTEGER_TYPE:
> + retval = populate_integer_package_data(elements,
> + instance_id,
> + attr_name_kobj);
> + break;
> + case HPWMI_ENUMERATION_TYPE:
> + retval = populate_enumeration_package_data(elements,
> + instance_id,
> + attr_name_kobj);
> + break;
> + case HPWMI_ORDERED_LIST_TYPE:
> + retval = populate_ordered_list_package_data(elements,
> + instance_id,
> + attr_name_kobj);
> + break;
> + case HPWMI_PASSWORD_TYPE:
> + retval = populate_password_package_data(elements,
> + instance_id,
> + attr_name_kobj);
> + break;
> + default:
> + break;

This default does nothing.

> + }
> +
> + kfree(str_value);

Why is str_value only freed down here? It has not been used for half a
screen of code.

> + }

else

> +
> + if (obj->type == ACPI_TYPE_BUFFER) {
> +
> + buffer_size = obj->buffer.length;
> + buffer_ptr = obj->buffer.pointer;
> +
> + retval = get_string_from_buffer(&buffer_ptr, &buffer_size, str, MAX_BUFF);
> + if (retval < 0)
> + goto err_attr_init;
> +
> + if (attr_type == HPWMI_PASSWORD_TYPE || attr_type == HPWMI_SECURE_PLATFORM_TYPE)
> + tmp_set = bioscfg_drv.authentication_dir_kset;
> + else
> + tmp_set = bioscfg_drv.main_dir_kset;

There is a bunch of common logic duplicated in both the buffer and
package branches.

> +
> + if (kset_find_obj(tmp_set, str)) {
> + pr_warn("Duplicate attribute name found - %s\n", str);

Also mention that it is being ignored.

> + goto nextobj;
> + }
> +
> + /* build attribute */
> + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
> + if (!attr_name_kobj) {
> + retval = -ENOMEM;
> + goto err_attr_init;
> + }
> +
> + attr_name_kobj->kset = tmp_set;
> +
> + temp_str = str;
> + if (attr_type == HPWMI_SECURE_PLATFORM_TYPE)
> + temp_str = "SPM";
> +
> + retval = kobject_init_and_add(attr_name_kobj,
> + &attr_name_ktype, NULL, "%s",
> + temp_str);
> + if (retval) {
> + kobject_put(attr_name_kobj);
> + goto err_attr_init;
> + }
> +
> + /* enumerate all of these attributes */
> + switch (attr_type) {
> + case HPWMI_STRING_TYPE:
> + retval = populate_string_buffer_data(buffer_ptr,
> + &buffer_size,
> + instance_id,
> + attr_name_kobj);
> + break;
> + case HPWMI_INTEGER_TYPE:
> + retval = populate_integer_buffer_data(buffer_ptr,
> + &buffer_size,
> + instance_id,
> + attr_name_kobj);
> + break;
> + case HPWMI_ENUMERATION_TYPE:
> + retval = populate_enumeration_buffer_data(buffer_ptr,
> + &buffer_size,
> + instance_id,
> + attr_name_kobj);
> + break;
> + case HPWMI_ORDERED_LIST_TYPE:
> + retval = populate_ordered_list_buffer_data(buffer_ptr,
> + &buffer_size,
> + instance_id,
> + attr_name_kobj);
> + break;
> + case HPWMI_PASSWORD_TYPE:
> + retval = populate_password_buffer_data(buffer_ptr,
> + &buffer_size,
> + instance_id,
> + attr_name_kobj);
> + break;
> + default:
> + break;
> + }
> + }

What if it's neither a package nor a buffer?

> +nextobj:
> + kfree(str_value);
> + kfree(obj);
> + instance_id++;
> + obj = get_wmiobj_pointer(instance_id, guid);
> + }
> + mutex_unlock(&bioscfg_drv.mutex);
> + return 0;
> +
> +err_attr_init:

This can leak str_value.

> + mutex_unlock(&bioscfg_drv.mutex);
> + kfree(obj);
> + return retval;
> +}

This function *really* needs to split up.

> +
> +static int __init bioscfg_init(void)
> +{
> + int ret = 0;

No need to initialize.

> + int bios_capable = wmi_has_guid(HP_WMI_BIOS_GUID);
> +
> + if (!bios_capable) {
> + pr_err("Unable to run on non-HP system\n");
> + return -ENODEV;
> + }
> +
> + ret = init_bios_attr_set_interface();
> + if (ret)
> + return ret;
> +
> + ret = init_bios_attr_pass_interface();
> + if (ret)
> + goto err_exit_bios_attr_set_interface;
> +
> + if (!bioscfg_drv.bios_attr_wdev || !bioscfg_drv.password_attr_wdev) {
> + pr_debug("Failed to find set or pass interface\n");
> + ret = -ENODEV;
> + goto err_exit_bios_attr_pass_interface;
> + }

Can this ever happen?

> +
> + ret = fw_attributes_class_get(&fw_attr_class);
> + if (ret)
> + goto err_exit_bios_attr_pass_interface;
> +
> + bioscfg_drv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> + NULL, "%s", DRIVER_NAME);
> + if (IS_ERR(bioscfg_drv.class_dev)) {
> + ret = PTR_ERR(bioscfg_drv.class_dev);
> + goto err_unregister_class;
> + }
> +
> + bioscfg_drv.main_dir_kset = kset_create_and_add("attributes", NULL,
> + &bioscfg_drv.class_dev->kobj);
> + if (!bioscfg_drv.main_dir_kset) {
> + ret = -ENOMEM;
> + pr_debug("Failed to create and add attributes\n");
> + goto err_destroy_classdev;
> + }
> +
> + bioscfg_drv.authentication_dir_kset = kset_create_and_add("authentication", NULL,
> + &bioscfg_drv.class_dev->kobj);
> + if (!bioscfg_drv.authentication_dir_kset) {
> + ret = -ENOMEM;
> + pr_debug("Failed to create and add authentication\n");
> + goto err_release_attributes_data;
> + }
> +
> + /*
> + * sysfs level attributes.
> + * - pending_reboot
> + */
> + ret = create_attributes_level_sysfs_files();
> + if (ret)
> + pr_debug("Failed to create sysfs level attributes\n");

Why continue when these fail?

> + ret = hp_init_bios_attributes(HPWMI_STRING_TYPE, HP_WMI_BIOS_STRING_GUID);
> + if (ret)
> + pr_debug("Failed to populate string type attributes\n");
> +
> + ret = hp_init_bios_attributes(HPWMI_INTEGER_TYPE, HP_WMI_BIOS_INTEGER_GUID);
> + if (ret)
> + pr_debug("Failed to populate integer type attributes\n");
> +
> + ret = hp_init_bios_attributes(HPWMI_ENUMERATION_TYPE, HP_WMI_BIOS_ENUMERATION_GUID);
> + if (ret)
> + pr_debug("Failed to populate enumeration type attributes\n");
> +
> + ret = hp_init_bios_attributes(HPWMI_ORDERED_LIST_TYPE, HP_WMI_BIOS_ORDERED_LIST_GUID);
> + if (ret)
> + pr_debug("Failed to populate ordered list object type attributes\n");
> +
> + ret = hp_init_bios_attributes(HPWMI_PASSWORD_TYPE, HP_WMI_BIOS_PASSWORD_GUID);
> + if (ret)
> + pr_debug("Failed to populate password object type attributes\n");
> +
> + bioscfg_drv.spm_data.attr_name_kobj = NULL;
> + ret = hp_add_other_attributes(HPWMI_SECURE_PLATFORM_TYPE);
> + if (ret)
> + pr_debug("Failed to populate secure platform object type attribute\n");
> +
> + bioscfg_drv.sure_start_attr_kobj = NULL;
> + ret = hp_add_other_attributes(HPWMI_SURE_START_TYPE);
> + if (ret)
> + pr_debug("Failed to populate sure start object type attribute\n");
> +
> + return 0;
> +
> +err_release_attributes_data:
> + release_attributes_data();
> +
> +err_destroy_classdev:
> + device_destroy(fw_attr_class, MKDEV(0, 0));
> +
> +err_unregister_class:
> + fw_attributes_class_put();
> +
> +err_exit_bios_attr_pass_interface:
> + exit_bios_attr_pass_interface();
> +
> +err_exit_bios_attr_set_interface:
> + exit_bios_attr_set_interface();
> +
> + return ret;
> +}
> +
> +static void __exit bioscfg_exit(void)
> +{
> + release_attributes_data();
> + device_destroy(fw_attr_class, MKDEV(0, 0));
> +
> + fw_attributes_class_put();
> + exit_bios_attr_set_interface();
> + exit_bios_attr_pass_interface();
> +}
> +
> +module_init(bioscfg_init);
> +module_exit(bioscfg_exit);
> --
> 2.34.1
>

2023-04-23 07:27:22

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

On 2023-04-20 11:54:45-0500, Jorge Lopez wrote:
> .../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
> 1 file changed, 563 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> new file mode 100644
> index 000000000000..5e5d540f728d
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> @@ -0,0 +1,563 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions corresponding to ordered list type attributes under
> + * BIOS ORDERED LIST GUID for use with hp-bioscfg driver.
> + *
> + * Copyright (c) 2022 HP Development Company, L.P.
> + */
> +
> +#include "bioscfg.h"
> +
> +GET_INSTANCE_ID(ordered_list);
> +
> +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> +{
> +
> + int instance_id = get_ordered_list_instance_id(kobj);
> +
> + if (instance_id < 0)
> + return -EIO;
> +
> + return sysfs_emit(buf, "%s\n",
> + bioscfg_drv.ordered_list_data[instance_id].current_value);
> +}
> +
> +/*
> + * validate_ordered_list_value -
> + * Validate input of current_value against possible values

Does the firmware not also validate this?

If so it may be easier to just let it do so and remove the validations
from the driver.

> + *
> + * @instance_id: The instance on which input is validated
> + * @buf: Input value
> + */
> +static int validate_ordered_list_values(int instance_id, const char *buf)
> +{
> + int ret = 0;
> + int found = 0;
> + char *new_values = NULL;
> + char *value;
> + int elem;
> + int elem_found = 0;
> +
> + /* Is it a read only attribute */
> + if (bioscfg_drv.ordered_list_data[instance_id].common.is_readonly)
> + return -EIO;
> +
> + new_values = kstrdup(buf, GFP_KERNEL);
> +
> + /*
> + * Changes to ordered list values require checking that new
> + * values are found in the list of elements.
> + */
> + elem_found = 0;
> + while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> +
> + value = strsep(&new_values, ",");

The docs say the separator is semicolon.

> + if (value != NULL) {
> + if (!*value)
> + continue;
> + elem_found++;
> + }
> +
> + found = 0;
> + for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
> + if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {

It's surprising that this is case-insensitive.

> + found = 1;
> + break;
> + }
> + }
> +
> +
> + if (!found) {
> + ret = -EINVAL;
> + goto out_list_value;
> + }
> + }
> +
> + if (elem_found == bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> + pr_warn("Number of new values is not equal to number of ordered list elements (%d)\n",
> + bioscfg_drv.ordered_list_data[instance_id].elements_size);
> + ret = -EINVAL;
> + goto out_list_value;
> + }
> +
> +out_list_value:
> + kfree(new_values);
> + return ret;
> +}

This algorithm does not seem to validate that different values are
provided.

So if "possible_values" is "foo,bar,baz" this function would accept
"foo,foo,foo".

> +
> +/*
> + * validate_ordered_input() -
> + * Validate input of current_value against possible values
> + *
> + * @instance_id: The instance on which input is validated
> + * @buf: Input value
> + */
> +static int validate_ordered_list_input(int instance_id, const char *buf)
> +{
> + int ret = 0;
> +
> + ret = validate_ordered_list_values(instance_id, buf);
> + if (ret < 0)
> + return -EINVAL;
> +
> + /*
> + * set pending reboot flag depending on
> + * "RequiresPhysicalPresence" value
> + */
> + if (bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence)
> + bioscfg_drv.pending_reboot = true;
> +
> + return ret;
> +}
> +
> +static void update_ordered_list_value(int instance_id, char *attr_value)
> +{
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
> + attr_value,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
> +}
> +
> +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, ordered_list);
> +static struct kobj_attribute ordered_list_display_langcode =
> + __ATTR_RO(display_name_language_code);
> +
> +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, ordered_list);
> +static struct kobj_attribute ordered_list_display_name =
> + __ATTR_RO(display_name);
> +
> +ATTRIBUTE_PROPERTY_STORE(current_value, ordered_list);
> +static struct kobj_attribute ordered_list_current_val =
> + __ATTR_RW_MODE(current_value, 0644);
> +
> +
> +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, ordered_list);
> +static struct kobj_attribute ordered_list_prerequisites_size_val =
> + __ATTR_RO(prerequisites_size);
> +
> +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, ordered_list);
> +static struct kobj_attribute ordered_list_prerequisites_val =
> + __ATTR_RO(prerequisites);
> +
> +ATTRIBUTE_N_PROPERTY_SHOW(elements_size, ordered_list);
> +static struct kobj_attribute ordered_list_elements_size_val =
> + __ATTR_RO(elements_size);

"size" and "length" attributes are fairly useless to userspace.
They can't be trusted to provide information about another attribute as
the information can be out of date when the other attribute is read.

> +
> +ATTRIBUTE_VALUES_PROPERTY_SHOW(elements, ordered_list);
> +static struct kobj_attribute ordered_list_elements_val =
> + __ATTR_RO(elements);
> +
> +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "ordered-list\n");
> +}
> +static struct kobj_attribute ordered_list_type =
> + __ATTR_RO(type);
> +
> +static struct attribute *ordered_list_attrs[] = {
> + &ordered_list_display_langcode.attr,
> + &ordered_list_display_name.attr,
> + &ordered_list_current_val.attr,
> + &ordered_list_prerequisites_size_val.attr,
> + &ordered_list_prerequisites_val.attr,
> + &ordered_list_elements_val.attr,
> + &ordered_list_elements_size_val.attr,
> + &ordered_list_type.attr,
> + NULL
> +};
> +
> +static const struct attribute_group ordered_list_attr_group = {
> + .attrs = ordered_list_attrs,
> +};
> +
> +int alloc_ordered_list_data(void)
> +{
> + int ret = 0;
> +
> + bioscfg_drv.ordered_list_instances_count =
> + get_instance_count(HP_WMI_BIOS_ORDERED_LIST_GUID);
> + bioscfg_drv.ordered_list_data = kcalloc(bioscfg_drv.ordered_list_instances_count,
> + sizeof(struct ordered_list_data), GFP_KERNEL);
> + if (!bioscfg_drv.ordered_list_data) {
> + bioscfg_drv.ordered_list_instances_count = 0;
> + ret = -ENOMEM;
> + }
> + return ret;
> +}
> +
> +/*
> + * populate_ordered_list_package_data() -
> + * Populate all properties of an instance under ordered_list attribute
> + *
> + * @order_obj: ACPI object with ordered_list data
> + * @instance_id: The instance to enumerate
> + * @attr_name_kobj: The parent kernel object
> + */
> +int populate_ordered_list_package_data(union acpi_object *order_obj, int instance_id,
> + struct kobject *attr_name_kobj)
> +{
> + bioscfg_drv.ordered_list_data[instance_id].attr_name_kobj = attr_name_kobj;
> +
> + populate_ordered_list_elements_from_package(order_obj,
> + order_obj->package.count,
> + instance_id);
> + update_attribute_permissions(bioscfg_drv.ordered_list_data[instance_id].common.is_readonly,
> + &ordered_list_current_val);
> + friendly_user_name_update(bioscfg_drv.ordered_list_data[instance_id].common.path,
> + attr_name_kobj->name,
> + bioscfg_drv.ordered_list_data[instance_id].common.display_name,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name));
> + return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group);
> +}
> +
> +/* Expected Values types associated with each element */
> +static const acpi_object_type expected_order_types[] = {
> + [NAME] = ACPI_TYPE_STRING,
> + [VALUE] = ACPI_TYPE_STRING,
> + [PATH] = ACPI_TYPE_STRING,
> + [IS_READONLY] = ACPI_TYPE_INTEGER,
> + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
> + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
> + [SEQUENCE] = ACPI_TYPE_INTEGER,
> + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
> + [PREREQUISITES] = ACPI_TYPE_STRING,
> + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
> + [ORD_LIST_SIZE] = ACPI_TYPE_INTEGER,
> + [ORD_LIST_ELEMENTS] = ACPI_TYPE_STRING
> +};
> +
> +
> +int populate_ordered_list_elements_from_package(union acpi_object *order_obj,
> + int order_obj_count,
> + int instance_id)
> +{
> + char *str_value = NULL;
> + int value_len;
> + int ret = 0;
> + u32 size = 0;
> + u32 int_value;
> + int elem = 0;
> + int reqs;
> + int eloc;
> + char *tmpstr = NULL;
> + char *part_tmp = NULL;
> + int tmp_len = 0;
> + char *part = NULL;
> +
> + if (!order_obj)
> + return -EINVAL;
> +
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> + LANG_CODE_STR,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));

This seems to be the same for every type. Can it not be moved into
common code?

> +
> + for (elem = 1, eloc = 1; elem < order_obj_count; elem++, eloc++) {
> +
> + /* ONLY look at the first ORDERED_ELEM_CNT elements */
> + if (eloc == ORDERED_ELEM_CNT)
> + goto exit_list_package;
> +
> + switch (order_obj[elem].type) {
> + case ACPI_TYPE_STRING:
> +
> + if (elem != PREREQUISITES && elem != ORD_LIST_ELEMENTS) {
> + ret = convert_hexstr_to_str(order_obj[elem].string.pointer,
> + order_obj[elem].string.length,
> + &str_value, &value_len);
> + if (ret)
> + continue;
> + }
> + break;
> + case ACPI_TYPE_INTEGER:
> + int_value = (u32)order_obj[elem].integer.value;
> + break;
> + default:
> + pr_warn("Unsupported object type [%d]\n", order_obj[elem].type);
> + continue;
> + }
> +
> + /* Check that both expected and read object type match */
> + if (expected_order_types[eloc] != order_obj[elem].type) {
> + pr_err("Error expected type %d for elem %d, but got type %d instead\n",
> + expected_order_types[eloc], elem, order_obj[elem].type);
> + return -EIO;
> + }
> +
> + /* Assign appropriate element value to corresponding field*/
> + switch (eloc) {
> + case VALUE:
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
> + str_value, sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
> + break;
> + case PATH:
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.path, str_value,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.path));
> + break;
> + case IS_READONLY:
> + bioscfg_drv.ordered_list_data[instance_id].common.is_readonly = int_value;
> + break;
> + case DISPLAY_IN_UI:
> + bioscfg_drv.ordered_list_data[instance_id].common.display_in_ui = int_value;
> + break;
> + case REQUIRES_PHYSICAL_PRESENCE:
> + bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence = int_value;
> + break;
> + case SEQUENCE:
> + bioscfg_drv.ordered_list_data[instance_id].common.sequence = int_value;
> + break;
> + case PREREQUISITES_SIZE:
> + bioscfg_drv.ordered_list_data[instance_id].common.prerequisites_size = int_value;
> + if (int_value > MAX_PREREQUISITES_SIZE)
> + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + /*
> + * This HACK is needed to keep the expected
> + * element list pointing to the right obj[elem].type
> + * when the size is zero. PREREQUISITES
> + * object is omitted by BIOS when the size is
> + * zero.
> + */
> + if (int_value == 0)
> + eloc++;
> + break;
> + case PREREQUISITES:
> + size = bioscfg_drv.ordered_list_data[instance_id].common.prerequisites_size;
> +
> + for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
> + ret = convert_hexstr_to_str(order_obj[elem + reqs].string.pointer,
> + order_obj[elem + reqs].string.length,
> + &str_value, &value_len);
> +
> + if (ret)
> + continue;
> +
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.prerequisites[reqs],
> + str_value,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.prerequisites[reqs]));
> +
> + kfree(str_value);
> + }
> + break;
> +
> + case SECURITY_LEVEL:
> + bioscfg_drv.ordered_list_data[instance_id].common.security_level = int_value;
> + break;
> +
> + case ORD_LIST_SIZE:
> + bioscfg_drv.ordered_list_data[instance_id].elements_size = int_value;
> + if (int_value > MAX_ELEMENTS_SIZE)
> + pr_warn("Ordered List size value exceeded the maximum number of elements supported or data may be malformed\n");
> + /*
> + * This HACK is needed to keep the expected
> + * element list pointing to the right obj[elem].type
> + * when the size is zero. ORD_LIST_ELEMENTS
> + * object is omitted by BIOS when the size is
> + * zero.
> + */
> + if (int_value == 0)
> + eloc++;
> + break;
> + case ORD_LIST_ELEMENTS:
> + size = bioscfg_drv.ordered_list_data[instance_id].elements_size;
> +
> + /*
> + * Ordered list data is stored in hex and comma separated format
> + * Convert the data and split it to show each element
> + */
> + ret = convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len);
> + if (ret)
> + goto exit_list_package;
> +
> + part_tmp = tmpstr;
> + part = strsep(&part_tmp, ",");
> + if (!part)
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].elements[0],
> + tmpstr,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].elements[0]));
> +
> + for (elem = 1; elem < MAX_ELEMENTS_SIZE && part; elem++) {
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].elements[elem],
> + part,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].elements[elem]));
> + part = strsep(&part_tmp, ",");
> + }
> +
> + kfree(tmpstr);
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Ordered_List attribute or data may be malformed\n", elem);
> + break;
> + }
> + kfree(tmpstr);
> + kfree(str_value);
> + }
> +
> +exit_list_package:
> + kfree(tmpstr);
> + kfree(str_value);
> + return 0;
> +}
> +
> +/*
> + * populate_ordered_list_data() - Populate all properties of an
> + * instance under ordered list attribute
> + *
> + * @buffer_ptr: Buffer pointer
> + * @buffer_size: Buffer size
> + * @instance_id: The instance to enumerate
> + * @attr_name_kobj: The parent kernel object
> + * @enum_property_count: Total properties count under ordered list type
> + */
> +int populate_ordered_list_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
> + struct kobject *attr_name_kobj)
> +{
> +
> + bioscfg_drv.ordered_list_data[instance_id].attr_name_kobj = attr_name_kobj;
> +
> + /* Populate ordered list elements */
> + populate_ordered_list_elements_from_buffer(buffer_ptr, buffer_size,
> + instance_id);
> + update_attribute_permissions(bioscfg_drv.ordered_list_data[instance_id].common.is_readonly,
> + &ordered_list_current_val);
> + friendly_user_name_update(bioscfg_drv.ordered_list_data[instance_id].common.path,
> + attr_name_kobj->name,
> + bioscfg_drv.ordered_list_data[instance_id].common.display_name,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name));
> +
> + return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group);
> +}
> +
> +int populate_ordered_list_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id)
> +{
> + int ret;
> + char *dst = NULL;
> + int elem;
> + int reqs;
> + int integer;
> + int size = 0;
> + int values;
> + int dst_size = *buffer_size / sizeof(u16);
> +
> + dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
> + if (!dst)
> + return -ENOMEM;
> +
> + elem = 0;
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> + LANG_CODE_STR,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
> +
> + for (elem = 1; elem < 3; elem++) {
> +
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + if (ret < 0)
> + continue;
> +
> + switch (elem) {
> + case VALUE:
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
> + dst, sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
> + break;
> + case PATH:
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.path, dst,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.path));
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Ordered list attribute or data may be malformed\n", elem);
> + break;
> + }
> + }
> + for (elem = 3; elem < ORDERED_ELEM_CNT; elem++) {
> +
> + if (elem != PREREQUISITES && elem != ORD_LIST_ELEMENTS) {
> + ret = get_integer_from_buffer((int **)&buffer_ptr, buffer_size, (int *)&integer);
> + if (ret < 0)
> + continue;
> + }
> +
> + switch (elem) {
> +
> + case IS_READONLY:
> + bioscfg_drv.ordered_list_data[instance_id].common.is_readonly = integer;
> + break;
> + case DISPLAY_IN_UI:
> + bioscfg_drv.ordered_list_data[instance_id].common.display_in_ui = integer;
> + break;
> + case REQUIRES_PHYSICAL_PRESENCE:
> + bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence = integer;
> + break;
> + case SEQUENCE:
> + bioscfg_drv.ordered_list_data[instance_id].common.sequence = integer;
> + break;
> + case PREREQUISITES_SIZE:
> + bioscfg_drv.ordered_list_data[instance_id].common.prerequisites_size = integer;
> + if (integer > MAX_PREREQUISITES_SIZE)
> + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + // PREREQUISITES:
> + elem++;
> + size = bioscfg_drv.ordered_list_data[instance_id].common.prerequisites_size;
> + for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + if (ret < 0)
> + continue;
> +
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.prerequisites[reqs],
> + dst,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.prerequisites[reqs]));
> + }
> + break;
> + case SECURITY_LEVEL:
> + bioscfg_drv.ordered_list_data[instance_id].common.security_level = integer;
> + break;
> + case ORD_LIST_SIZE:
> + bioscfg_drv.ordered_list_data[instance_id].elements_size = integer;
> + if (integer > MAX_ELEMENTS_SIZE)
> + pr_warn("Ordered List size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + // ORD_LIST_ELEMENTS:
> + elem++;
> + size = bioscfg_drv.ordered_list_data[instance_id].elements_size;
> + for (values = 0; values < size && values < MAX_ELEMENTS_SIZE; values++) {
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + if (ret < 0)
> + continue;
> +
> + strscpy(bioscfg_drv.ordered_list_data[instance_id].elements[values],
> + dst,
> + sizeof(bioscfg_drv.ordered_list_data[instance_id].elements)[values]);
> + }
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Ordered list attribute or data may be malformed\n", elem);
> + break;
> + }
> + }
> + kfree(dst);
> +
> + return 0;
> +}
> +
> +
> +/*
> + * exit_ordered_list_attributes() - Clear all attribute data
> + *
> + * Clears all data allocated for this group of attributes
> + */
> +void exit_ordered_list_attributes(void)
> +{
> + int instance_id;
> +
> + for (instance_id = 0; instance_id < bioscfg_drv.ordered_list_instances_count; instance_id++) {
> + struct kobject *attr_name_kobj = bioscfg_drv.ordered_list_data[instance_id].attr_name_kobj;
> +
> + if (attr_name_kobj)
> + sysfs_remove_group(attr_name_kobj,
> + &ordered_list_attr_group);
> + }
> + bioscfg_drv.ordered_list_instances_count = 0;
> +
> + kfree(bioscfg_drv.ordered_list_data);
> + bioscfg_drv.ordered_list_data = NULL;
> +}
> --
> 2.34.1
>

2023-04-23 09:25:25

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 06/14] HP BIOSCFG driver - passwdobj-attributes

On 2023-04-20 11:54:46-0500, Jorge Lopez wrote:
> ---
> .../x86/hp/hp-bioscfg/passwdobj-attributes.c | 669 ++++++++++++++++++
> 1 file changed, 669 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> new file mode 100644
> index 000000000000..c03b3a71e9c4
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> @@ -0,0 +1,669 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions corresponding to password object type attributes under
> + * BIOS PASSWORD for use with hp-bioscfg driver.
> + *
> + * Copyright (c) 2022 HP Development Company, L.P.
> + */
> +
> +#include "bioscfg.h"
> +#include <asm-generic/posix_types.h>
> +
> +GET_INSTANCE_ID(password);
> +/*
> + * Clear all passwords copied to memory for a particular
> + * authentication instance
> + */
> +int clear_passwords(const int instance)
> +{
> + if (!bioscfg_drv.password_data[instance].is_enabled)
> + return 0;
> +
> + memset(bioscfg_drv.password_data[instance].current_password,
> + 0, sizeof(bioscfg_drv.password_data[instance].current_password));
> + memset(bioscfg_drv.password_data[instance].new_password,
> + 0, sizeof(bioscfg_drv.password_data[instance].new_password));
> +
> + return 0;
> +}
> +
> +/*
> + * Clear all credentials copied to memory for both Power-ON and Setup
> + * BIOS instances
> + */
> +int clear_all_credentials(void)
> +{
> + int instance;
> +
> + /* clear all passwords */
> + for (instance = 0; instance < bioscfg_drv.password_instances_count; instance++)
> + clear_passwords(instance);
> +
> + /* clear auth_token */
> + kfree(bioscfg_drv.spm_data.auth_token);
> + bioscfg_drv.spm_data.auth_token = NULL;
> +
> + return 0;
> +}
> +
> +int get_password_instance_for_type(const char *name)
> +{
> + int count = bioscfg_drv.password_instances_count;
> + int instance;
> +
> + for (instance = 0; instance < count; instance++) {
> + if (strcmp(bioscfg_drv.password_data[instance].common.display_name, name) == 0)
> + return instance;
> + }
> + return -EINVAL;
> +}
> +
> +int validate_password_input(int instance_id, const char *buf)
> +{
> + int length;
> +
> + length = strlen(buf);
> + if (buf[length-1] == '\n')
> + length--;
> +
> + if (length > MAX_PASSWD_SIZE)
> + return INVALID_BIOS_AUTH;
> +
> + if (bioscfg_drv.password_data[instance_id].min_password_length > length ||
> + bioscfg_drv.password_data[instance_id].max_password_length < length)
> + return INVALID_BIOS_AUTH;
> + return SUCCESS;
> +}
> +
> +int password_is_set(const char *name)

bool is_password_set(const char *name)

> +{
> + int id;
> +
> + id = get_password_instance_for_type(name);
> + if (id < 0)
> + return 0;
> +
> + return bioscfg_drv.password_data[id].is_enabled;
> +}
> +
> +ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password);
> +static struct kobj_attribute password_is_password_set = __ATTR_RO(is_enabled);
> +
> +static ssize_t current_password_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + char *p, *buf_cp;
> + int id, ret = 0;
> +
> + buf_cp = kstrdup(buf, GFP_KERNEL);
> + if (!buf_cp) {
> + ret = -ENOMEM;
> + goto exit_password;
> + }
> +
> + p = memchr(buf_cp, '\n', count);
> +
> + if (p != NULL)
> + *p = '\0';

This will also accept input like "foo\nbar" and truncate away the "bar".

For something like a password it seems errorprone to try to munge the
value.

> +
> + id = get_password_instance_id(kobj);
> +
> + if (id >= 0)
> + ret = validate_password_input(id, buf_cp);
> +
> + if (!ret) {
> + strscpy(bioscfg_drv.password_data[id].current_password,
> + buf_cp,
> + sizeof(bioscfg_drv.password_data[id].current_password));
> + /*
> + * set pending reboot flag depending on
> + * "RequiresPhysicalPresence" value
> + */
> + if (bioscfg_drv.password_data[id].common.requires_physical_presence)
> + bioscfg_drv.pending_reboot = true;

Just setting this to true does not emit the necessary KOBJ_CHANGE event
on the class dev kobj which is necessary for userspace to be able to
react.

> + }
> +
> +exit_password:
> + kfree(buf_cp);
> + return ret ? ret : count;
> +}
> +static struct kobj_attribute password_current_password = __ATTR_WO(current_password);
> +
> +static ssize_t new_password_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + char *p, *buf_cp = NULL;
> + int id = 0;
> + int ret = -EIO;
> +
> + buf_cp = kstrdup(buf, GFP_KERNEL);
> + if (!buf_cp) {
> + ret = -ENOMEM;
> + goto exit_password;
> + }
> +
> + p = memchr(buf_cp, '\n', count);
> +
> + if (p != NULL)
> + *p = '\0';

Same as above.

> +
> + id = get_password_instance_id(kobj);
> +
> + if (id >= 0)
> + ret = validate_password_input(id, buf_cp);
> +
> + if (!ret)
> + strscpy(bioscfg_drv.password_data[id].new_password,
> + buf_cp,
> + sizeof(bioscfg_drv.password_data[id].new_password));
> +
> + if (!ret)
> + ret = hp_set_attribute(kobj->name, buf_cp);
> +
> +exit_password:
> + /*
> + * Regardless of the results both new and current passwords
> + * will be set to zero and avoid security issues
> + */
> + clear_passwords(id);
> +
> + kfree(buf_cp);
> + return ret ? ret : count;
> +}
> +
> +static struct kobj_attribute password_new_password = __ATTR_WO(new_password);
> +
> +
> +ATTRIBUTE_N_PROPERTY_SHOW(min_password_length, password);
> +static struct kobj_attribute password_min_password_length = __ATTR_RO(min_password_length);
> +
> +ATTRIBUTE_N_PROPERTY_SHOW(max_password_length, password);
> +static struct kobj_attribute password_max_password_length = __ATTR_RO(max_password_length);
> +
> +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + if (strcmp(kobj->name, SETUP_PASSWD) == 0)
> + return sysfs_emit(buf, "%s\n", BIOS_ADMIN);
> +
> + if (strcmp(kobj->name, POWER_ON_PASSWD) == 0)
> + return sysfs_emit(buf, "%s\n", POWER_ON);
> +
> + return -EIO;
> +}
> +static struct kobj_attribute password_role = __ATTR_RO(role);
> +
> +static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + int i = get_password_instance_id(kobj);
> +
> + if (i < 0)
> + return i;
> +
> + if (bioscfg_drv.password_data[i].mechanism != PASSWORD)
> + return -EINVAL;
> +
> + return sysfs_emit(buf, "%s\n", PASSWD_MECHANISM_TYPES);
> +}
> +static struct kobj_attribute password_mechanism = __ATTR_RO(mechanism);
> +
> +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "password\n");
> +}
> +static struct kobj_attribute password_type = __ATTR_RO(type);
> +
> +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, password);
> +static struct kobj_attribute password_display_name =
> + __ATTR_RO(display_name);
> +
> +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, password);
> +static struct kobj_attribute password_display_langcode =
> + __ATTR_RO(display_name_language_code);
> +
> +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, password);
> +static struct kobj_attribute password_prerequisites_size_val =
> + __ATTR_RO(prerequisites_size);
> +
> +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, password);
> +static struct kobj_attribute password_prerequisites_val =
> + __ATTR_RO(prerequisites);
> +
> +ATTRIBUTE_N_PROPERTY_SHOW(encodings_size, password);
> +static struct kobj_attribute password_encodings_size_val =
> + __ATTR_RO(encodings_size);

As before, these size attribute are fairly pointless for userspace as
they can't be relied on.

> +
> +ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password);
> +static struct kobj_attribute password_encodings_val =
> + __ATTR_RO(encodings);
> +
> +
> +static struct attribute *password_attrs[] = {
> + &password_is_password_set.attr,
> + &password_min_password_length.attr,
> + &password_max_password_length.attr,
> + &password_current_password.attr,
> + &password_new_password.attr,
> + &password_role.attr,
> + &password_mechanism.attr,
> + &password_type.attr,
> + &password_display_name.attr,
> + &password_display_langcode.attr,
> + &password_prerequisites_size_val.attr,
> + &password_prerequisites_val.attr,
> + &password_encodings_val.attr,
> + &password_encodings_size_val.attr,
> + NULL
> +};

Many of these attributes are not documented.

> +
> +static const struct attribute_group bios_password_attr_group = {
> + .attrs = password_attrs
> +};
> +
> +static const struct attribute_group system_password_attr_group = {
> + .attrs = password_attrs
> +};

These groups are the same, are both needed?

> +
> +int alloc_password_data(void)
> +{
> + int ret = 0;
> +
> + bioscfg_drv.password_instances_count = get_instance_count(HP_WMI_BIOS_PASSWORD_GUID);
> + bioscfg_drv.password_data = kcalloc(bioscfg_drv.password_instances_count,
> + sizeof(struct password_data), GFP_KERNEL);

sizeof(bioscfg_drv.password_data)

> + if (!bioscfg_drv.password_data) {
> + bioscfg_drv.password_instances_count = 0;
> + ret = -ENOMEM;
> + }
> +
> + return ret;
> +}
> +
> +/*
> + * populate_password_package_data -
> + * Populate all properties for an instance under password attribute
> + *
> + * @password_obj: ACPI object with password data
> + * @instance_id: The instance to enumerate
> + * @attr_name_kobj: The parent kernel object
> + */
> +int populate_password_package_data(union acpi_object *password_obj, int instance_id,
> + struct kobject *attr_name_kobj)
> +{
> + bioscfg_drv.password_data[instance_id].attr_name_kobj = attr_name_kobj;
> +
> + populate_password_elements_from_package(password_obj,
> + password_obj->package.count,
> + instance_id);
> +
> + if (strcmp(attr_name_kobj->name, "Setup Password") == 0) {

SETUP_PASSWD

> + /* Save system authentication instance for easy access */
> + return sysfs_create_group(attr_name_kobj, &bios_password_attr_group);
> + }
> +
> + return sysfs_create_group(attr_name_kobj, &system_password_attr_group);
> +}
> +
> +/* Expected Values types associated with each element */
> +static const acpi_object_type expected_password_types[] = {
> + [NAME] = ACPI_TYPE_STRING,
> + [VALUE] = ACPI_TYPE_STRING,
> + [PATH] = ACPI_TYPE_STRING,
> + [IS_READONLY] = ACPI_TYPE_INTEGER,
> + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
> + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
> + [SEQUENCE] = ACPI_TYPE_INTEGER,
> + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
> + [PREREQUISITES] = ACPI_TYPE_STRING,
> + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
> + [PSWD_MIN_LENGTH] = ACPI_TYPE_INTEGER,
> + [PSWD_MAX_LENGTH] = ACPI_TYPE_INTEGER,
> + [PSWD_SIZE] = ACPI_TYPE_INTEGER,
> + [PSWD_ENCODINGS] = ACPI_TYPE_STRING,
> + [PSWD_IS_SET] = ACPI_TYPE_INTEGER
> +};
> +
> +
> +int populate_password_elements_from_package(union acpi_object *password_obj,
> + int password_obj_count,
> + int instance_id)
> +{
> + char *str_value = NULL;
> + int value_len;
> + int ret = 0;
> + u32 size = 0;
> + u32 int_value;
> + int elem = 0;
> + int reqs;
> + int eloc;
> + int pos_values;
> +
> +
> + if (!password_obj)
> + return -EINVAL;
> +
> + strscpy(bioscfg_drv.password_data[instance_id].common.display_name_language_code,
> + LANG_CODE_STR,
> + sizeof(bioscfg_drv.password_data[instance_id].common.display_name_language_code));
> +
> + for (elem = 1, eloc = 1; elem < password_obj_count; elem++, eloc++) {
> +
> + /* ONLY look at the first PASSWORD_ELEM_CNT elements */
> + if (eloc == PASSWORD_ELEM_CNT)
> + goto exit_package;
> +
> + switch (password_obj[elem].type) {
> + case ACPI_TYPE_STRING:
> +
> + if (PREREQUISITES != elem && PSWD_ENCODINGS != elem) {
> + ret = convert_hexstr_to_str(password_obj[elem].string.pointer,
> + password_obj[elem].string.length,
> + &str_value, &value_len);
> + if (ret)
> + continue;
> + }
> + break;
> + case ACPI_TYPE_INTEGER:
> + int_value = (u32)password_obj[elem].integer.value;
> + break;
> + default:
> + pr_warn("Unsupported object type [%d]\n", password_obj[elem].type);
> + continue;
> + }
> +
> + /* Check that both expected and read object type match */
> + if (expected_password_types[eloc] != password_obj[elem].type) {
> + pr_err("Error expected type %d for elem %d, but got type %d instead\n",
> + expected_password_types[eloc], elem, password_obj[elem].type);
> + return -EIO;
> + }
> +
> + /* Assign appropriate element value to corresponding field*/
> + switch (eloc) {
> + case VALUE:
> + break;
> + case PATH:
> + strscpy(bioscfg_drv.password_data[instance_id].common.path, str_value,
> + sizeof(bioscfg_drv.password_data[instance_id].common.path));
> + break;
> + case IS_READONLY:
> + bioscfg_drv.password_data[instance_id].common.is_readonly = int_value;
> + break;
> + case DISPLAY_IN_UI:
> + bioscfg_drv.password_data[instance_id].common.display_in_ui = int_value;
> + break;
> + case REQUIRES_PHYSICAL_PRESENCE:
> + bioscfg_drv.password_data[instance_id].common.requires_physical_presence = int_value;
> + break;
> + case SEQUENCE:
> + bioscfg_drv.password_data[instance_id].common.sequence = int_value;
> + break;
> + case PREREQUISITES_SIZE:
> + bioscfg_drv.password_data[instance_id].common.prerequisites_size = int_value;
> + if (int_value > MAX_PREREQUISITES_SIZE)
> + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> + /*
> + * This HACK is needed to keep the expected
> + * element list pointing to the right obj[elem].type
> + * when the size is zero. PREREQUISITES
> + * object is omitted by BIOS when the size is
> + * zero.
> + */
> + if (int_value == 0)
> + eloc++;
> + break;
> + case PREREQUISITES:
> + size = bioscfg_drv.password_data[instance_id].common.prerequisites_size;
> +
> + for (reqs = 0; reqs < size; reqs++) {
> + ret = convert_hexstr_to_str(password_obj[elem + reqs].string.pointer,
> + password_obj[elem + reqs].string.length,
> + &str_value, &value_len);
> +
> + if (ret)
> + break;
> +
> + strscpy(bioscfg_drv.password_data[instance_id].common.prerequisites[reqs],
> + str_value,
> + sizeof(bioscfg_drv.password_data[instance_id].common.prerequisites[reqs]));
> +
> + kfree(str_value);
> + }
> + break;
> +
> + case SECURITY_LEVEL:
> + bioscfg_drv.password_data[instance_id].common.security_level = int_value;
> + break;
> +
> + case PSWD_MIN_LENGTH:
> + bioscfg_drv.password_data[instance_id].min_password_length = int_value;
> + break;
> + case PSWD_MAX_LENGTH:
> + bioscfg_drv.password_data[instance_id].max_password_length = int_value;
> + break;
> + case PSWD_SIZE:
> + bioscfg_drv.password_data[instance_id].encodings_size = int_value;
> + if (int_value > MAX_ENCODINGS_SIZE)
> + pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + /*
> + * This HACK is needed to keep the expected
> + * element list pointing to the right obj[elem].type
> + * when the size is zero. PSWD_ENCODINGS
> + * object is omitted by BIOS when the size is
> + * zero.
> + */
> + if (int_value == 0)
> + eloc++;
> + break;
> +
> + case PSWD_ENCODINGS:
> + size = bioscfg_drv.password_data[instance_id].encodings_size;
> +
> + for (pos_values = 0; pos_values < size && pos_values < MAX_ENCODINGS_SIZE; pos_values++) {
> + ret = convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer,
> + password_obj[elem + pos_values].string.length,
> + &str_value, &value_len);
> + if (ret)
> + break;
> +
> + strscpy(bioscfg_drv.password_data[instance_id].encodings[pos_values],
> + str_value,
> + sizeof(bioscfg_drv.password_data[instance_id].encodings[pos_values]));
> + kfree(str_value);
> + }
> + break;
> + case PSWD_IS_SET:
> + bioscfg_drv.password_data[instance_id].is_enabled = int_value;
> + break;
> +
> + default:
> + pr_warn("Invalid element: %d found in Password attribute or data may be malformed\n", elem);
> + break;
> + }
> + kfree(str_value);
> + }
> +
> +exit_package:
> + kfree(str_value);
> + return 0;
> +}
> +
> +/*
> + * populate_password_buffer_data -
> + * Populate all properties for an instance under password object attribute
> + *
> + * @buffer_ptr: Buffer pointer
> + * @buffer_size: Buffer size
> + * @instance_id: The instance to enumerate
> + * @attr_name_kobj: The parent kernel object
> + */
> +int populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
> + struct kobject *attr_name_kobj)
> +{
> + bioscfg_drv.password_data[instance_id].attr_name_kobj = attr_name_kobj;
> +
> + /* Populate Password attributes */
> + populate_password_elements_from_buffer(buffer_ptr, buffer_size,
> + instance_id);
> + friendly_user_name_update(bioscfg_drv.password_data[instance_id].common.path,
> + attr_name_kobj->name,
> + bioscfg_drv.password_data[instance_id].common.display_name,
> + sizeof(bioscfg_drv.password_data[instance_id].common.display_name));
> + if (strcmp(attr_name_kobj->name, "Setup Password") == 0)
> + return sysfs_create_group(attr_name_kobj, &bios_password_attr_group);
> +
> + return sysfs_create_group(attr_name_kobj, &system_password_attr_group);
> +}
> +
> +int populate_password_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id)
> +{
> + int ret;
> + char *dst = NULL;
> + int elem;
> + int reqs;
> + int integer;
> + int size = 0;
> + int values;
> + int dst_size = *buffer_size / sizeof(u16);
> +
> + dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
> + if (!dst)
> + return -ENOMEM;
> +
> + elem = 0;
> + strscpy(bioscfg_drv.password_data[instance_id].common.display_name_language_code,
> + LANG_CODE_STR,
> + sizeof(bioscfg_drv.password_data[instance_id].common.display_name_language_code));
> +
> + for (elem = 1; elem < 3; elem++) {
> +
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + if (ret < 0)
> + continue;
> +
> + switch (elem) {
> + case VALUE:
> + strscpy(bioscfg_drv.password_data[instance_id].current_password,
> + dst, sizeof(bioscfg_drv.password_data[instance_id].current_password));
> + break;
> + case PATH:
> + strscpy(bioscfg_drv.password_data[instance_id].common.path, dst,
> + sizeof(bioscfg_drv.password_data[instance_id].common.path));
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Password attribute or data may be malformed\n", elem);
> + break;
> + }
> + }
> +
> + for (elem = 3; elem < PASSWORD_ELEM_CNT; elem++) {
> +
> + if (elem != PREREQUISITES && elem != PSWD_ENCODINGS) {
> + ret = get_integer_from_buffer((int **)&buffer_ptr, buffer_size, (int *)&integer);
> + if (ret)
> + continue;
> + }
> +
> + switch (elem) {
> + case IS_READONLY:
> + bioscfg_drv.password_data[instance_id].common.is_readonly = integer;
> + break;
> + case DISPLAY_IN_UI:
> + bioscfg_drv.password_data[instance_id].common.display_in_ui = integer;
> + break;
> + case REQUIRES_PHYSICAL_PRESENCE:
> + bioscfg_drv.password_data[instance_id].common.requires_physical_presence = integer;
> + break;
> + case SEQUENCE:
> + bioscfg_drv.password_data[instance_id].common.sequence = integer;
> + break;
> + case PREREQUISITES_SIZE:
> + bioscfg_drv.password_data[instance_id].common.prerequisites_size = integer;
> + if (integer > MAX_PREREQUISITES_SIZE)
> + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + // PREREQUISITES:
> + elem++;
> + size = bioscfg_drv.password_data[instance_id].common.prerequisites_size;
> + for (reqs = 0; reqs < size && reqs > MAX_PREREQUISITES_SIZE; reqs++) {
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + if (ret < 0)
> + continue;
> +
> + strscpy(bioscfg_drv.password_data[instance_id].common.prerequisites[reqs],
> + dst,
> + sizeof(bioscfg_drv.password_data[instance_id].common.prerequisites[reqs]));
> + }
> + break;
> + case SECURITY_LEVEL:
> + bioscfg_drv.password_data[instance_id].common.security_level = integer;
> + break;
> +
> + case PSWD_MIN_LENGTH:
> + bioscfg_drv.password_data[instance_id].min_password_length = integer;
> + break;
> + case PSWD_MAX_LENGTH:
> + bioscfg_drv.password_data[instance_id].max_password_length = integer;
> + break;
> + case PSWD_SIZE:
> + bioscfg_drv.password_data[instance_id].encodings_size = integer;
> + if (integer > MAX_ENCODINGS_SIZE)
> + pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + // PSWD_ENCODINGS:
> + elem++;
> + size = bioscfg_drv.password_data[instance_id].encodings_size;
> + for (values = 0; values < size && values < MAX_ENCODINGS_SIZE; values++) {
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + if (ret < 0)
> + continue;
> +
> + strscpy(bioscfg_drv.password_data[instance_id].encodings[values],
> + dst,
> + sizeof(bioscfg_drv.password_data[instance_id].encodings[values]));
> +
> + }
> + break;
> + case PSWD_IS_SET:
> + bioscfg_drv.password_data[instance_id].is_enabled = integer;
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Password attribute or data may be malformed\n", elem);
> + break;
> + }
> + }
> + kfree(dst);
> + return 0;
> +}
> +
> +/*
> + * exit_password_attributes() - Clear all attribute data
> + *
> + * Clears all data allocated for this group of attributes
> + */
> +void exit_password_attributes(void)
> +{
> + int instance_id;
> +
> + for (instance_id = 0; instance_id < bioscfg_drv.password_instances_count; instance_id++) {
> + struct kobject *attr_name_kobj = bioscfg_drv.password_data[instance_id].attr_name_kobj;
> +
> + if (attr_name_kobj) {
> + if (strcmp(attr_name_kobj->name, SETUP_PASSWD) == 0)
> + sysfs_remove_group(attr_name_kobj,
> + &bios_password_attr_group);
> + else
> + sysfs_remove_group(attr_name_kobj,
> + &system_password_attr_group);
> + }
> + }
> + bioscfg_drv.password_instances_count = 0;
> + kfree(bioscfg_drv.password_data);
> + bioscfg_drv.password_data = NULL;
> +}
> --
> 2.34.1
>

2023-04-23 09:57:51

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 11/14] HP BIOSCFG driver - spmobj-attributes

On 2023-04-20 11:54:51-0500, Jorge Lopez wrote:
> .../x86/hp/hp-bioscfg/spmobj-attributes.c | 405 ++++++++++++++++++
> 1 file changed, 405 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
> new file mode 100644
> index 000000000000..78228f44c39b
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
> @@ -0,0 +1,405 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions corresponding to secure platform management object type
> + * attributes under BIOS PASSWORD for use with hp-bioscfg driver
> + *
> + * Copyright (c) 2022 HP Development Company, L.P.
> + */
> +
> +#include "bioscfg.h"
> +
> +
> +static const char * const spm_state_types[] = {
> + "not provisioned",
> + "provisioned",
> + "provisioning in progress"
> +};
> +
> +static const char * const spm_mechanism_types[] = {
> + "not provision",

"not provisioned" as above?

> + "signing-key",
> + "endorsement-key"

Trailing commas please.

> +};
> +
> +
> +int check_spm_is_enabled(void)
> +{
> + /* do we need to check the admin password is also configured */
> + return bioscfg_drv.spm_data.is_enabled;
> +}
> +
> +/*
> + * calculate_security_buffer() - determines size of security buffer
> + * for authentication scheme
> + *
> + * @authentication: the authentication content
> + *
> + * Currently only supported type is Admin password
> + */
> +size_t calculate_security_buffer(const char *authentication)
> +{
> + int size;
> +
> + if (authentication != NULL && strlen(authentication) > 0) {
> +
> + size = (sizeof(u16) + (strlen(authentication) * sizeof(u16)));
> + if (strncmp(authentication, BEAM_PREFIX, strlen(BEAM_PREFIX)) != 0)

strstarts()

> + size += (strlen(UTF_PREFIX) * sizeof(u16));

No need for braces.

> +
> + return size;
> + }
> +
> + size = sizeof(u16) * 2;
> + return size;
> +}
> +
> +/*
> + * populate_security_buffer() - builds a security buffer for
> + * authentication scheme
> + *
> + * @buffer: the buffer to populate
> + * @authentication: the authentication content
> + *
> + * Currently only supported type is PLAIN TEXT
> + */
> +void populate_security_buffer(u16 *buffer, const char *authentication)
> +{
> + u16 *auth = buffer;
> + char *strprefix = NULL;
> +
> + if (strncmp(authentication, BEAM_PREFIX, strlen(BEAM_PREFIX)) == 0) {

strstarts()

> + /*
> + * BEAM_PREFIX is append to buffer when a signature
> + * is provided and Sure Admin is enabled in BIOS
> + */
> + // BEAM_PREFIX found, convert part to unicode
> + auth = ascii_to_utf16_unicode(auth, authentication);
> + } else {
> + /*
> + * UTF-16 prefix is append to the * buffer when a BIOS
> + * admin password is configured in BIOS
> + */
> +
> + // append UTF_PREFIX to part and then convert it to unicode
> + strprefix = kasprintf(GFP_KERNEL, "%s%s", UTF_PREFIX,
> + authentication);
> + if (!strprefix)
> + goto out_buffer;
> +
> + auth = ascii_to_utf16_unicode(auth, strprefix);
> + }
> +out_buffer:
> +
> + kfree(strprefix);
> +}
> +
> +ssize_t update_spm_state(void)
> +{
> + int ret;
> + struct secureplatform_provisioning_data *data = NULL;

This can be allocated on the stack.

> +
> + data = kmalloc(sizeof(struct secureplatform_provisioning_data),
> + GFP_KERNEL);
> + if (!data) {
> + ret = -ENOMEM;
> + goto state_exit;
> + }
> +
> + ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
> + HPWMI_SECUREPLATFORM, data, 0,
> + sizeof(*data));
> + if (ret < 0)
> + goto state_exit;
> +
> + bioscfg_drv.spm_data.mechanism = data->state;
> + if (bioscfg_drv.spm_data.mechanism)
> + bioscfg_drv.spm_data.is_enabled = 1;
> +
> +state_exit:
> + kfree(data);
> +
> + return ret;
> +}
> +
> +ssize_t statusbin(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)

This can be static.

If it is known that a struct secureplatform_provisioning_data is to be
passed, then the type can reflect that.

> +{
> + int ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
> + HPWMI_SECUREPLATFORM, buf, 0,
> + sizeof(struct secureplatform_provisioning_data));
> +
> + return ret ? -ENODEV : sizeof(struct secureplatform_provisioning_data);

Why not return "ret" on error?

> +}
> +
> +/*
> + * status_show - Reads SPM status
> + */
> +ssize_t status_show(struct kobject *kobj, struct kobj_attribute
> + *attr, char *buf)
> +{
> + int ret, i;
> + struct secureplatform_provisioning_data *data = NULL;

Can also be on-stack.

> +
> + data = kmalloc(sizeof(struct secureplatform_provisioning_data),
> + GFP_KERNEL);
> + if (!data) {
> + ret = -ENOMEM;
> + goto status_exit;
> + }
> +
> + ret = statusbin(kobj, attr, (char *)data);
> + if (ret < 0)
> + goto status_exit;
> +
> + sysfs_emit(buf, "%s{\n", buf);
> + sysfs_emit(buf, "%s\t\"State\": \"%s\",\n", buf,
> + spm_state_types[data->state]);
> + sysfs_emit(buf, "%s\t\"Version\": \"%d.%d\",\n", buf, data->version[0],
> + data->version[1]);
> +
> + /*
> + * state == 0 means secure platform management
> + * feature is not configured in BIOS.
> + */
> + if (data->state == 0)
> + goto status_exit;
> +
> + sysfs_emit(buf, "%s\t\"Nonce\": %d,\n", buf, data->nonce);
> + sysfs_emit(buf, "%s\t\"FeaturesInUse\": %d,\n", buf, data->features);
> + sysfs_emit(buf, "%s\t\"EndorsementKeyMod\": \"", buf);
> +
> + for (i = 255; i >= 0; i--)
> + sysfs_emit(buf, "%s %u", buf, data->kek_mod[i]);
> +
> + sysfs_emit(buf, "%s \",\n", buf);
> + sysfs_emit(buf, "%s\t\"SigningKeyMod\": \"", buf);
> +
> + for (i = 255; i >= 0; i--)
> + sysfs_emit(buf, "%s %u", buf, data->sk_mod[i]);
> +
> + /* Return buf contents */
> +
> + sysfs_emit(buf, "%s \"\n", buf);
> + sysfs_emit(buf, "%s}\n", buf);
> +
> +status_exit:
> + kfree(data);
> +
> + return strnlen(buf, PAGE_SIZE);
> +}
> +
> +static struct kobj_attribute password_spm_status = __ATTR_RO(status);
> +
> +ATTRIBUTE_SPM_N_PROPERTY_SHOW(is_enabled, spm);
> +static struct kobj_attribute password_spm_is_key_enabled = __ATTR_RO(is_enabled);
> +
> +
> +static ssize_t key_mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "%s\n",
> + spm_mechanism_types[bioscfg_drv.spm_data.mechanism]);
> +}
> +static struct kobj_attribute password_spm_key_mechanism = __ATTR_RO(key_mechanism);
> +
> +static ssize_t sk_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + int length;
> +
> + length = count;
> + if (buf[length-1] == '\n')
> + length--;
> +
> + /* allocate space and copy current signing key */
> + bioscfg_drv.spm_data.signing_key = kmalloc(length, GFP_KERNEL);
> + if (!bioscfg_drv.spm_data.signing_key) {
> + ret = -ENOMEM;
> + goto exit_sk;
> + }
> +
> + memcpy(bioscfg_drv.spm_data.signing_key, buf, length);
> + bioscfg_drv.spm_data.signing_key[length] = '\0';

Is this supposed to handle zero-bytes in the input?
If yes: Did you test this with zero bytes, I don't think the normal
attribute APIs handle this.
If no: Why not use strscpy?

> +
> + /* submit signing key payload */
> + ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK,
> + HPWMI_SECUREPLATFORM,
> + (void *)bioscfg_drv.spm_data.signing_key,
> + length, 0);
> +
> + if (!ret) {
> + bioscfg_drv.spm_data.mechanism = SIGNING_KEY;
> + bioscfg_drv.pending_reboot = true;
> + }
> +
> +exit_sk:
> + kfree(bioscfg_drv.spm_data.signing_key);
> + bioscfg_drv.spm_data.signing_key = NULL;
> +
> + return ret ? ret : count;
> +}
> +
> +static struct kobj_attribute password_spm_signing_key = __ATTR_WO(sk);
> +
> +static ssize_t kek_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + int length;
> +
> + length = count;
> + if (buf[length-1] == '\n')
> + length--;
> +
> + /* allocate space and copy current signing key */
> + bioscfg_drv.spm_data.endorsement_key = kmalloc(length, GFP_KERNEL);
> + if (!bioscfg_drv.spm_data.endorsement_key) {
> + ret = -ENOMEM;
> + goto exit_kek;
> + }
> +
> + memcpy(bioscfg_drv.spm_data.endorsement_key, buf, length);
> + bioscfg_drv.spm_data.endorsement_key[length] = '\0';
> +
> + ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK,
> + HPWMI_SECUREPLATFORM,
> + (void *)bioscfg_drv.spm_data.endorsement_key,
> + count, 0);
> +
> + if (!ret) {
> + bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY;
> + bioscfg_drv.pending_reboot = true;
> + }
> +
> +exit_kek:
> + kfree(bioscfg_drv.spm_data.endorsement_key);
> + bioscfg_drv.spm_data.endorsement_key = NULL;
> +
> + return ret ? ret : count;
> +}
> +static struct kobj_attribute password_spm_endorsement_key = __ATTR_WO(kek);
> +
> +static ssize_t display_name_language_code_show(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "%s\n", LANG_CODE_STR);
> +}
> +
> +static struct kobj_attribute password_spm_display_langcode =
> + __ATTR_RO(display_name_language_code);
> +
> +
> +static ssize_t display_name_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + return sysfs_emit(buf, "%s\n", SPM_STR_DESC);
> +}
> +static struct kobj_attribute password_spm_display_name = __ATTR_RO(display_name);
> +
> +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "secure-platform-management\n");
> +}
> +static struct kobj_attribute password_spm_type = __ATTR_RO(type);
> +
> +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "%s\n", BIOS_SPM);
> +}
> +static struct kobj_attribute password_spm_role = __ATTR_RO(role);
> +
> +static ssize_t auth_token_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret = 0;
> + int length;
> +
> + length = count;
> + if (buf[length-1] == '\n')
> + length--;
> +
> + /* allocate space and copy current auth token */
> + bioscfg_drv.spm_data.auth_token = kmalloc(count, GFP_KERNEL);
> + if (!bioscfg_drv.spm_data.auth_token) {
> + ret = -ENOMEM;
> + goto exit_token;
> + }
> +
> + memcpy(bioscfg_drv.spm_data.auth_token, buf, count);
> + bioscfg_drv.spm_data.auth_token[length] = '\0';
> + return count;
> +
> +
> +exit_token:
> + kfree(bioscfg_drv.spm_data.auth_token);
> + bioscfg_drv.spm_data.auth_token = NULL;
> +
> + return ret;
> +
> +}
> +static struct kobj_attribute password_spm_auth_token = __ATTR_WO(auth_token);
> +
> +static struct attribute *secure_platform_attrs[] = {
> + &password_spm_display_name.attr,
> + &password_spm_display_langcode.attr,
> + &password_spm_is_key_enabled.attr,
> + &password_spm_signing_key.attr,
> + &password_spm_endorsement_key.attr,
> + &password_spm_key_mechanism.attr,
> + &password_spm_status.attr,
> + &password_spm_type.attr,
> + &password_spm_role.attr,
> + &password_spm_auth_token.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group secure_platform_attr_group = {
> + .attrs = secure_platform_attrs,
> +};
> +
> +void exit_secure_platform_attributes(void)
> +{
> + /* remove secure platform sysfs entry and free key data*/
> +
> + kfree(bioscfg_drv.spm_data.endorsement_key);
> + bioscfg_drv.spm_data.endorsement_key = NULL;
> +
> + kfree(bioscfg_drv.spm_data.signing_key);
> + bioscfg_drv.spm_data.signing_key = NULL;
> +
> + kfree(bioscfg_drv.spm_data.auth_token);
> + bioscfg_drv.spm_data.auth_token = NULL;
> +
> + if (bioscfg_drv.spm_data.attr_name_kobj)
> + sysfs_remove_group(bioscfg_drv.spm_data.attr_name_kobj,
> + &secure_platform_attr_group);
> +}
> +
> +int populate_secure_platform_data(struct kobject *attr_name_kobj)
> +{
> + /* Populate data for Secure Platform Management */
> + bioscfg_drv.spm_data.attr_name_kobj = attr_name_kobj;
> +
> + strscpy(bioscfg_drv.spm_data.attribute_name, SPM_STR,
> + sizeof(bioscfg_drv.spm_data.attribute_name));
> + strscpy(bioscfg_drv.spm_data.display_name, SPM_STR_DESC,
> + sizeof(bioscfg_drv.spm_data.display_name));
> +
> + bioscfg_drv.spm_data.is_enabled = 0;
> + bioscfg_drv.spm_data.mechanism = 0;
> + bioscfg_drv.pending_reboot = false;
> + update_spm_state();
> +
> + bioscfg_drv.spm_data.endorsement_key = NULL;
> + bioscfg_drv.spm_data.signing_key = NULL;
> + bioscfg_drv.spm_data.auth_token = NULL;
> +
> + return sysfs_create_group(attr_name_kobj, &secure_platform_attr_group);
> +}
> --
> 2.34.1
>

2023-04-23 12:14:41

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 08/14] HP BIOSCFG driver - bioscfg-h

On 2023-04-20 11:54:48-0500, Jorge Lopez wrote:
> ---
> drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++++++++++
> 1 file changed, 613 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
> new file mode 100644
> index 000000000000..3f7d33b1b032
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
> @@ -0,0 +1,613 @@
> +/* SPDX-License-Identifier: GPL-2.0
> + *
> + * Definitions for kernel modules using hp_bioscfg driver
> + *
> + * Copyright (c) 2022 HP Development Company, L.P.
> + */
> +
> +#ifndef _HP_BIOSCFG_H_
> +#define _HP_BIOSCFG_H_
> +
> +#include <linux/wmi.h>
> +#include <linux/types.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/capability.h>
> +#include <linux/nls.h>
> +#include <linux/printk.h>
> +
> +
> +#define DRIVER_NAME "hp-bioscfg"
> +
> +#define MAX_BUFF 512
> +#define MAX_KEY_MOD 256
> +#define MAX_PASSWD_SIZE 64
> +#define MAX_MESSAGE_SIZE 256
> +#define MAX_PREREQUISITES_SIZE 20
> +#define MAX_REQ_ELEM_SIZE 128
> +#define MAX_VALUES_SIZE 16
> +#define MAX_ENCODINGS_SIZE 16
> +#define MAX_ELEMENTS_SIZE 16
> +
> +#define SPM_STR_DESC "Secure Platform Management"
> +#define SPM_STR "SPM"
> +#define SURE_START_DESC "Sure Start"
> +#define SURE_START_STR "Sure_Start"
> +#define SETUP_PASSWD "Setup Password"
> +#define POWER_ON_PASSWD "Power-On Password"
> +
> +#define LANG_CODE_STR "en_US.UTF-8"
> +#define SCHEDULE_POWER_ON "Scheduled Power-On"
> +
> +/* Sure Admin Functions */
> +
> +#define UTF_PREFIX "<utf-16/>"
> +#define BEAM_PREFIX "<BEAM/>"
> +
> +/* mechanism - Authentication attribute */
> +
> +#define MAX_MECHANISM_TYPES 3
> +
> +enum mechanism_values {
> + PASSWORD = 0x00,
> + NOT_PROVISION = 0x00,
> + SIGNING_KEY = 0x01,
> + ENDORSEMENT_KEY = 0x02
> +};
> +
> +#define BIOS_ADMIN "bios-admin"
> +#define POWER_ON "power-on"
> +#define BIOS_SPM "enhanced-bios-auth"
> +
> +#define PASSWD_MECHANISM_TYPES "password"
> +
> +#define HP_WMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
> +
> +#define HP_WMI_BIOS_STRING_GUID "988D08E3-68F4-4c35-AF3E-6A1B8106F83C"
> +#define HP_WMI_BIOS_INTEGER_GUID "8232DE3D-663D-4327-A8F4-E293ADB9BF05"
> +#define HP_WMI_BIOS_ENUMERATION_GUID "2D114B49-2DFB-4130-B8FE-4A3C09E75133"
> +#define HP_WMI_BIOS_ORDERED_LIST_GUID "14EA9746-CE1F-4098-A0E0-7045CB4DA745"
> +#define HP_WMI_BIOS_PASSWORD_GUID "322F2028-0F84-4901-988E-015176049E2D"
> +#define HP_WMI_SET_BIOS_SETTING_GUID "1F4C91EB-DC5C-460b-951D-C7CB9B4B8D5E"
> +
> +enum hp_wmi_spm_commandtype {
> + HPWMI_SECUREPLATFORM_GET_STATE = 0x10,
> + HPWMI_SECUREPLATFORM_SET_KEK = 0x11,
> + HPWMI_SECUREPLATFORM_SET_SK = 0x12
> +};
> +
> +enum hp_wmi_surestart_commandtype {
> + HPWMI_SURESTART_GET_LOG_COUNT = 0x01,
> + HPWMI_SURESTART_GET_LOG = 0x02
> +};
> +
> +enum hp_wmi_command {
> + HPWMI_READ = 0x01,
> + HPWMI_WRITE = 0x02,
> + HPWMI_ODM = 0x03,
> + HPWMI_SURESTART = 0x20006,
> + HPWMI_GM = 0x20008,
> + HPWMI_SECUREPLATFORM = 0x20010
> +};
> +
> +struct bios_return {
> + u32 sigpass;
> + u32 return_code;
> +};
> +
> +enum hp_return_value {
> + HPWMI_RET_WRONG_SIGNATURE = 0x02,
> + HPWMI_RET_UNKNOWN_COMMAND = 0x03,
> + HPWMI_RET_UNKNOWN_CMDTYPE = 0x04,
> + HPWMI_RET_INVALID_PARAMETERS = 0x05
> +};

This seems to be same as wmi_error_values below.

> +
> +enum wmi_error_values {
> + SUCCESS = 0x00,
> + CMD_FAILED = 0x01,
> + INVALID_SIGN = 0x02,
> + INVALID_CMD_VALUE = 0x03,
> + INVALID_CMD_TYPE = 0x04,
> + INVALID_DATA_SIZE = 0x05,
> + INVALID_CMD_PARAM = 0x06,
> + ENCRYP_CMD_REQUIRED = 0x07,
> + NO_SECURE_SESSION = 0x08,
> + SECURE_SESSION_FOUND = 0x09,
> + SECURE_SESSION_FAILED = 0x0A,
> + AUTH_FAILED = 0x0B,
> + INVALID_BIOS_AUTH = 0x0E,
> + NONCE_DID_NOT_MATCH = 0x18,
> + GENERIC_ERROR = 0x1C,
> + BIOS_ADMIN_POLICY_NOT_MET = 0x28,
> + BIOS_ADMIN_NOT_SET = 0x38,
> + P21_NO_PROVISIONED = 0x1000,
> + P21_PROVISION_IN_PROGRESS = 0x1001,
> + P21_IN_USE = 0x1002,
> + HEP_NOT_ACTIVE = 0x1004,
> + HEP_ALREADY_SET = 0x1006,
> + HEP_CHECK_STATE = 0x1007
> +};
> +
> +enum spm_features {
> + HEP_ENABLED = 0x01,
> + PLATFORM_RECOVERY = 0x02,
> + ENHANCED_BIOS_AUTH_MODE = 0x04

Trailing commas please everywhere.

> +};
> +
> +#define MAX_KEK_BLOB_SIZE 4160
> +#define MAX_SK_BLOB_SIZE 516

These are unused.

> +
> +enum spm_states_values {
> + NOT_PROVISIONED = 0x00,
> + PROVISIONED = 0x01,
> + PROVISIONING_IN_PROGRESS = 0x02
> +};
> +
> +
> +
> +/*
> + * struct bios_args buffer is dynamically allocated. New WMI command types
> + * were introduced that exceeds 128-byte data size. Changes to handle
> + * the data size allocation scheme were kept in hp_wmi_perform_qurey function.
> + */
> +struct bios_args {
> + u32 signature;
> + u32 command;
> + u32 commandtype;
> + u32 datasize;
> + u8 data[];
> +};
> +
> +struct secureplatform_provisioning_data {
> + u8 state;
> + u8 version[2];
> + u8 reserved1;
> + u32 features;
> + u32 nonce;
> + u8 reserved2[28];
> + u8 sk_mod[MAX_KEY_MOD];
> + u8 kek_mod[MAX_KEY_MOD];
> +};

bios_args and secureplatform_provisioning_data are only used from a
single .c file. There is no need to define them in the shared header.

> +
> +struct common_data {
> + u8 display_name[MAX_BUFF];
> + u8 path[MAX_BUFF];
> + u32 is_readonly;
> + u32 display_in_ui;
> + u32 requires_physical_presence;
> + u32 sequence;
> + u32 prerequisites_size;
> + u8 prerequisites[MAX_PREREQUISITES_SIZE][MAX_BUFF];
> + u32 security_level;
> + u8 display_name_language_code[MAX_BUFF];
> +};
> +
> +
> +struct string_data {
> + struct kobject *attr_name_kobj;
> + u8 current_value[MAX_BUFF];
> + u8 new_value[MAX_BUFF];
> + u32 min_length;
> + u32 max_length;
> + struct common_data common;

It would be nicer to read if the common_data was at the start of the
struct.

> +};
> +
> +struct integer_data {
> + struct kobject *attr_name_kobj;
> + u32 current_value;
> + u32 new_value;
> + u32 lower_bound;
> + u32 upper_bound;
> + u32 scalar_increment;
> + struct common_data common;
> +};
> +
> +struct enumeration_data {
> + struct kobject *attr_name_kobj;
> + u8 current_value[MAX_BUFF];
> + u8 new_value[MAX_BUFF];
> + u32 possible_values_size;
> + u8 possible_values[MAX_VALUES_SIZE][MAX_BUFF];
> + struct common_data common;
> +};
> +
> +struct ordered_list_data {
> + struct kobject *attr_name_kobj;
> + u8 current_value[MAX_BUFF];
> + u8 new_value[MAX_BUFF];
> + u32 elements_size;
> + u8 elements[MAX_ELEMENTS_SIZE][MAX_BUFF];
> + struct common_data common;
> +};
> +
> +struct password_data {
> + struct kobject *attr_name_kobj;
> + u8 current_password[MAX_PASSWD_SIZE];
> + u8 new_password[MAX_PASSWD_SIZE];
> + u32 min_password_length;
> + u32 max_password_length;
> + u32 encodings_size;
> + u8 encodings[MAX_ENCODINGS_SIZE][MAX_BUFF];
> + u32 is_enabled;
> +
> + // 'bios-admin' 'power-on'
> + u32 role;
> +
> + //'password'
> + u32 mechanism;
> +
> + struct common_data common;
> +};
> +
> +struct secure_platform_data {
> + struct kobject *attr_name_kobj;
> + u8 attribute_name[MAX_BUFF];
> + u8 display_name[MAX_BUFF];
> +
> + u8 *endorsement_key;
> + u8 *signing_key;
> + u8 *auth_token;
> +
> + u32 is_enabled;
> + u32 mechanism;
> +};
> +
> +struct bioscfg_priv {
> + struct wmi_device *password_attr_wdev;
> + struct wmi_device *bios_attr_wdev;
> + struct kset *authentication_dir_kset;
> + struct kset *main_dir_kset;
> + struct device *class_dev;
> + struct string_data *string_data;
> + u32 string_instances_count;
> + struct integer_data *integer_data;
> + u32 integer_instances_count;
> + struct enumeration_data *enumeration_data;
> + u32 enumeration_instances_count;
> + struct ordered_list_data *ordered_list_data;
> + u32 ordered_list_instances_count;
> + struct password_data *password_data;
> + u32 password_instances_count;
> +
> + struct kobject *sure_start_attr_kobj;
> + struct secure_platform_data spm_data;
> +
> + bool pending_reboot;
> + struct mutex mutex;
> +};
> +
> +/* global structure used by multiple WMI interfaces */
> +extern struct bioscfg_priv bioscfg_drv;
> +
> +enum hp_wmi_data_type {
> + HPWMI_STRING_TYPE = 0x00,
> + HPWMI_INTEGER_TYPE = 0x01,
> + HPWMI_ENUMERATION_TYPE = 0x02,
> + HPWMI_ORDERED_LIST_TYPE = 0x03,
> + HPWMI_PASSWORD_TYPE = 0x04,
> + HPWMI_SECURE_PLATFORM_TYPE = 0x05,
> + HPWMI_SURE_START_TYPE = 0x06
> +};

Unused.

> +
> +enum hp_wmi_data_elements {
> +
> + /* Common elements */
> + NAME = 0,
> + VALUE = 1,
> + PATH = 2,
> + IS_READONLY = 3,
> + DISPLAY_IN_UI = 4,
> + REQUIRES_PHYSICAL_PRESENCE = 5,
> + SEQUENCE = 6,
> + PREREQUISITES_SIZE = 7,
> + PREREQUISITES = 8,
> + SECURITY_LEVEL = 9,
> +
> + /* String elements */
> + STR_MIN_LENGTH = 10,
> + STR_MAX_LENGTH = 11,
> +
> + /* Integer elements */
> + INT_LOWER_BOUND = 10,
> + INT_UPPER_BOUND = 11,
> + INT_SCALAR_INCREMENT = 12,
> +
> + /* Enumeration elements */
> + ENUM_CURRENT_VALUE = 10,
> + ENUM_SIZE = 11,
> + ENUM_POSSIBLE_VALUES = 12,
> +
> + /* Ordered list elements */
> + ORD_LIST_SIZE = 10,
> + ORD_LIST_ELEMENTS = 11,
> +
> + /* Password elements */
> + PSWD_MIN_LENGTH = 10,
> + PSWD_MAX_LENGTH = 11,
> + PSWD_SIZE = 12,
> + PSWD_ENCODINGS = 13,
> + PSWD_IS_SET = 14
> +};
> +
> +
> +enum hp_wmi_elements_count {
> + STRING_ELEM_CNT = 12,
> + INTEGER_ELEM_CNT = 13,
> + ENUM_ELEM_CNT = 13,
> + ORDERED_ELEM_CNT = 12,
> + PASSWORD_ELEM_CNT = 15
> +};

To make it clearer where these values come from you could put them into
the enum hp_wmi_data_elements.

...
ORD_LIST_ELEMENTS = 11,
ORD_LIST_ELEM_CNT = 12,
...

But replacing the loop logic would remove the need for these enums
completely.

> +
> +#define GET_INSTANCE_ID(type) \
> + static int get_##type##_instance_id(struct kobject *kobj) \
> + { \
> + int i; \
> + \
> + for (i = 0; i <= bioscfg_drv.type##_instances_count; i++) { \
> + if (!(strcmp(kobj->name, bioscfg_drv.type##_data[i].attr_name_kobj->name))) \

No need for braces after "!".

> + return i; \
> + } \
> + return -EIO; \
> + }
> +
> +#define ATTRIBUTE_S_PROPERTY_SHOW(name, type) \
> + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
> + char *buf) \
> + { \
> + int i = get_##type##_instance_id(kobj); \
> + if (i >= 0) \
> + return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].name); \
> + return -EIO; \
> + }
> +
> +#define ATTRIBUTE_N_PROPERTY_SHOW(name, type) \
> + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
> + char *buf) \
> + { \
> + int i = get_##type##_instance_id(kobj); \
> + if (i >= 0) \
> + return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data[i].name); \
> + return -EIO; \
> + }
> +
> +
> +#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
> + static ssize_t curr_val##_store(struct kobject *kobj, \
> + struct kobj_attribute *attr, \
> + const char *buf, size_t count) \
> + { \
> + char *p = NULL; \
> + char *attr_value = NULL; \
> + int i; \
> + int ret = -EIO; \
> + \
> + attr_value = kstrdup(buf, GFP_KERNEL); \
> + if (!attr_value) \
> + return -ENOMEM; \
> + \
> + p = memchr(attr_value, '\n', count); \
> + if (p != NULL) \
> + *p = '\0'; \

This can also truncate the string if there is data after the newline.

> + \
> + i = get_##type##_instance_id(kobj); \
> + if (i >= 0) \
> + ret = validate_##type##_input(i, attr_value); \
> + if (!ret) \
> + ret = hp_set_attribute(kobj->name, attr_value); \
> + if (!ret) \
> + update_##type##_value(i, attr_value); \
> + \
> + clear_all_credentials(); \
> + kfree(attr_value); \
> + \
> + return ret ? ret : count; \
> + }
> +
> +#define ATTRIBUTE_SPM_N_PROPERTY_SHOW(name, type) \
> + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> + { \
> + return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data.name); \
> + }
> +
> +#define ATTRIBUTE_SPM_S_PROPERTY_SHOW(name, type) \
> + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> + { \
> + return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data.name); \
> + }
> +
> +#define ATTRIBUTE_VALUES_PROPERTY_SHOW(name, type) \
> + static ssize_t name##_show(struct kobject *kobj, \
> + struct kobj_attribute *attr, char *buf) \
> + { \
> + int i; \
> + int len = 0; \
> + int instance_id = get_##type##_instance_id(kobj); \
> + \
> + if (instance_id < 0) \
> + return 0; \
> + \
> + for (i = 0; i < bioscfg_drv.type##_data[instance_id].name##_size; i++) { \
> + if (i) \
> + len += sysfs_emit_at(buf, len, "%s", ";"); \
> + \
> + len += sysfs_emit_at(buf, len, "%s", \
> + bioscfg_drv.type##_data[instance_id].name[i]); \
> + } \
> + len += sysfs_emit_at(buf, len, "\n"); \
> + return len; \
> + }
> +
> +
> +#define ATTRIBUTE_S_COMMON_PROPERTY_SHOW(name, type) \
> + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
> + char *buf) \
> + { \
> + int i = get_##type##_instance_id(kobj); \
> + if (i >= 0) \
> + return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].common.name); \
> + return -EIO; \
> + }
> +
> +#define ATTRIBUTE_N_COMMON_PROPERTY_SHOW(name, type) \
> + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
> + char *buf) \
> + { \
> + int i = get_##type##_instance_id(kobj); \
> + if (i >= 0) \
> + return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data[i].common.name); \
> + return -EIO; \
> + }
> +
> +#define ATTRIBUTE_V_COMMON_PROPERTY_SHOW(name, type) \
> + static ssize_t name##_show(struct kobject *kobj, \
> + struct kobj_attribute *attr, char *buf) \
> + { \
> + int i; \
> + int len = 0; \
> + int instance_id = get_##type##_instance_id(kobj); \
> + \
> + if (instance_id < 0) \
> + return 0; \
> + \
> + for (i = 0; i < bioscfg_drv.type##_data[instance_id].common.name##_size; i++) { \
> + if (i) \
> + len += sysfs_emit_at(buf, len, "%s", ";"); \

No need for the "%s" here.

> + \
> + len += sysfs_emit_at(buf, len, "%s", \
> + bioscfg_drv.type##_data[instance_id].common.name[i]); \
> + } \
> + len += sysfs_emit_at(buf, len, "\n"); \
> + return len; \
> + }
> +
> +
> +/*
> + * Prototypes
> + */
> +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string);
> +int get_instance_count(const char *guid_string);
> +void update_attribute_permissions(u32 isReadOnly, struct kobj_attribute *current_val);
> +void friendly_user_name_update(char *path, const char *attr_name,
> + char *attr_display, int attr_size);
> +int bioscfg_wmi_error_and_message(int error_code);
> +
> +/* String attributes */
> +int populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +
> +int populate_string_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id);
> +
> +//enum hp_wmi_data_type type);
> +int alloc_string_data(void);
> +void exit_string_attributes(void);
> +int populate_string_package_data(union acpi_object *str_obj,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +int populate_string_elements_from_package(union acpi_object *str_obj,
> + int str_obj_count,
> + int instance_id);
> +
> +/* Integer attributes */
> +int populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +int populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id);
> +int alloc_integer_data(void);
> +void exit_integer_attributes(void);
> +int populate_integer_package_data(union acpi_object *integer_obj,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +int populate_integer_elements_from_package(union acpi_object *integer_obj,
> + int integer_obj_count,
> + int instance_id);
> +
> +/* Enumeration attributes */
> +int populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +int populate_enumeration_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id);
> +int alloc_enumeration_data(void);
> +void exit_enumeration_attributes(void);
> +int populate_enumeration_package_data(union acpi_object *enum_obj,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +int populate_enumeration_elements_from_package(union acpi_object *enum_obj,
> + int enum_obj_count,
> + int instance_id);
> +
> +/* Ordered list */
> +int populate_ordered_list_buffer_data(u8 *buffer_ptr,
> + u32 *buffer_size,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +int populate_ordered_list_elements_from_buffer(u8 *buffer_ptr,
> + u32 *buffer_size,
> + int instance_id);
> +int alloc_ordered_list_data(void);
> +void exit_ordered_list_attributes(void);
> +int populate_ordered_list_package_data(union acpi_object *order_obj,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +int populate_ordered_list_elements_from_package(union acpi_object *order_obj,
> + int order_obj_count,
> + int instance_id);

There are a lot of these per-type setup and teardown functions.
They are cluttering the API and need a lot of code when called.

Instead there could be a struct with function pointers:

struct type_ops {
populate_data;
from_buffer;
alloc;
exit;
...
}

const struct string_ops {
.alloc = alloc_string_data;
...
};

const struct type_ops[] = {
&string_ops,
...
};

And then the global setup code can just loop over these structs.

> +
> +/* Password authentication attributes */
> +int populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +int populate_password_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id);
> +int populate_password_package_data(union acpi_object *password_obj,
> + int instance_id,
> + struct kobject *attr_name_kobj);
> +int populate_password_elements_from_package(union acpi_object *password_obj,
> + int password_obj_count,
> + int instance_id);
> +int alloc_password_data(void);
> +int alloc_secure_platform_data(void);
> +void exit_password_attributes(void);
> +void exit_secure_platform_attributes(void);
> +int populate_secure_platform_data(struct kobject *attr_name_kobj);
> +int password_is_set(const char *auth);
> +int check_spm_is_enabled(void);
> +int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size);
> +int hp_wmi_perform_query(int query, enum hp_wmi_command command,
> + void *buffer, int insize, int outsize);
> +int validate_password_input(int instance_id, const char *buf);
> +
> +/* Sure Start attributes */
> +void exit_sure_start_attributes(void);
> +int populate_sure_start_data(struct kobject *attr_name_kobj);
> +
> +int set_bios_defaults(u8 defType);
> +int get_password_instance_for_type(const char *name);
> +int clear_all_credentials(void);
> +int clear_passwords(const int instance);
> +void exit_bios_attr_set_interface(void);
> +int init_bios_attr_set_interface(void);
> +size_t bioscfg_calculate_string_buffer(const char *str);
> +size_t calculate_security_buffer(const char *authentication);
> +void populate_security_buffer(u16 *buffer, const char *authentication);
> +int set_new_password(const char *password_type, const char *new_password);
> +int init_bios_attr_pass_interface(void);
> +void exit_bios_attr_pass_interface(void);
> +void *ascii_to_utf16_unicode(u16 *p, const u8 *str);
> +int get_integer_from_buffer(int **buffer, u32 *buffer_size, int *integer);
> +int get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size);
> +int convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len);
> +int encode_outsize_for_pvsz(int outsize);
> +int hp_set_attribute(const char *a_name, const char *a_value);
> +
> +/* SPM Attributes */
> +ssize_t update_spm_state(void);
> +ssize_t statusbin(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
> +ssize_t statusbin_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
> +ssize_t status_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);

None of these SPM functions are used outside of their compilation
units. They should be static and removed from the header.

Please also validate all the other non-static symbols, enums and
#defines.

> +
> +#endif
> --
> 2.34.1
>

2023-04-23 12:18:32

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

On 2023-04-20 11:54:52-0500, Jorge Lopez wrote:
> .../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
> 1 file changed, 130 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> new file mode 100644
> index 000000000000..72952758ffe3
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> @@ -0,0 +1,130 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions corresponding to sure start object type attributes under
> + * BIOS for use with hp-bioscfg driver
> + *
> + * Copyright (c) 2022 HP Development Company, L.P.
> + */
> +
> +#include "bioscfg.h"
> +#include <asm-generic/posix_types.h>

Is the asm include needed?
If yes, why not use linux/types.h?

> +
> +#define LOG_MAX_ENTRIES 254

A comment on how this values came to be would be good.

> +#define LOG_ENTRY_SIZE 16
> +
> +/*
> + * audit_log_entry_count_show - Reports the number of
> + * existing audit log entries available
> + * to be read
> + */
> +static ssize_t audit_log_entry_count_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + int ret;
> + u32 count = 0;
> +
> + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
> + HPWMI_SURESTART,
> + &count, 1, sizeof(count));
> +
> + if (ret < 0)
> + return ret;
> +
> + return sysfs_emit(buf, "%d,%d,%d\n", count, LOG_ENTRY_SIZE,
> + LOG_MAX_ENTRIES);
> +}
> +
> +/*
> + * audit_log_entries_show() - Return all entries found in log file
> + */
> +static ssize_t audit_log_entries_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + int ret;
> + int i;
> + u32 count = 0;
> +
> + // Get the number of event logs
> + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
> + HPWMI_SURESTART,
> + &count, 1, sizeof(count));
> +
> + /*
> + * The show() api will not work if the audit logs ever go
> + * beyond 4KB
> + */
> + if (count * LOG_ENTRY_SIZE > PAGE_SIZE)
> + return -EFAULT;

The error code seems not to match.

Instead of not returning any data, why not show as many results as
possible?

> +
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * We are guaranteed the buffer is 4KB so today all the event
> + * logs will fit
> + */
> +
> + for (i = 0; ((i < count) & (ret >= 0)); i++) {

&&

Better yet, pull the condition ret >= 0 into the body, as an else-branch
for the existing check.

> + *buf = (i + 1);

Isn't this directly overwritten by the query below?

> + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
> + HPWMI_SURESTART,
> + buf, 1, 128);
> + if (ret >= 0)
> + buf += LOG_ENTRY_SIZE;

So 128 bytes are read but only the first 16 bytes are preserved?

The documentation says that each entry has 128 bytes in the file.
And that they are separated by ";", which is not implemented.

Can the audit-log not contain all-zero bytes?
If it does this would need to be a bin_attribute.

> + }
> +
> + return (count * LOG_ENTRY_SIZE);

No need for braces.

> +}
> +
> +static struct kobj_attribute sure_start_audit_log_entry_count = __ATTR_RO(audit_log_entry_count);
> +static struct kobj_attribute sure_start_audit_log_entries = __ATTR_RO(audit_log_entries);
> +
> +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "sure-start\n");
> +}
> +static struct kobj_attribute sure_start_type = __ATTR_RO(type);
> +
> +static ssize_t display_name_language_code_show(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "%s\n", LANG_CODE_STR);
> +}
> +
> +static struct kobj_attribute sure_start_display_langcode =
> + __ATTR_RO(display_name_language_code);
> +
> +
> +static ssize_t display_name_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + return sysfs_emit(buf, "%s\n", SURE_START_DESC);
> +}
> +static struct kobj_attribute sure_start_display_name = __ATTR_RO(display_name);
> +
> +static struct attribute *sure_start_attrs[] = {
> + &sure_start_display_name.attr,
> + &sure_start_display_langcode.attr,
> + &sure_start_audit_log_entry_count.attr,
> + &sure_start_audit_log_entries.attr,
> + &sure_start_type.attr,
> + NULL
> +};
> +
> +static const struct attribute_group sure_start_attr_group = {
> + .attrs = sure_start_attrs,
> +};
> +
> +void exit_sure_start_attributes(void)
> +{
> + sysfs_remove_group(bioscfg_drv.sure_start_attr_kobj,
> + &sure_start_attr_group);
> +}
> +
> +int populate_sure_start_data(struct kobject *attr_name_kobj)
> +{
> + bioscfg_drv.sure_start_attr_kobj = attr_name_kobj;
> + return sysfs_create_group(attr_name_kobj, &sure_start_attr_group);
> +}
> --
> 2.34.1
>

2023-04-23 13:12:52

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 10/14] HP BIOSCFG driver - passwdattr-interface

On 2023-04-20 11:54:50-0500, Jorge Lopez wrote:
> ---
> .../x86/hp/hp-bioscfg/passwdattr-interface.c | 51 +++++++++++++++++++
> 1 file changed, 51 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c b/drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c
> new file mode 100644
> index 000000000000..02fc766eb3cf
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c
> @@ -0,0 +1,51 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions corresponding to SET password methods under
> + * HP_WMI_SET_BIOS_SETTING_GUID for use with hp-bioscfg driver.
> + *
> + * Copyright (c) 2022 Hewlett-Packard Inc.
> + */
> +
> +#include <linux/wmi.h>
> +#include "bioscfg.h"
> +
> +static int bios_attr_pass_interface_probe(struct wmi_device *wdev,
> + const void *context)
> +{
> + mutex_lock(&bioscfg_drv.mutex);
> + bioscfg_drv.password_attr_wdev = wdev;

This wdev is never used. It seems this full subdriver can be dropped.
Or the one UUID-based callsite using the same UUID can use the driver.

> + mutex_unlock(&bioscfg_drv.mutex);
> + return 0;
> +}
> +
> +static void bios_attr_pass_interface_remove(struct wmi_device *wdev)
> +{
> + mutex_lock(&bioscfg_drv.mutex);
> + bioscfg_drv.password_attr_wdev = NULL;
> + mutex_unlock(&bioscfg_drv.mutex);
> +}
> +
> +static const struct wmi_device_id bios_attr_pass_interface_id_table[] = {
> + { .guid_string = HP_WMI_SET_BIOS_SETTING_GUID },
> + { },

No comma after end-of-array marker.

> +};
> +static struct wmi_driver bios_attr_pass_interface_driver = {
> + .driver = {
> + .name = DRIVER_NAME"-password"
> + },
> + .probe = bios_attr_pass_interface_probe,
> + .remove = bios_attr_pass_interface_remove,
> + .id_table = bios_attr_pass_interface_id_table,
> +};
> +
> +int init_bios_attr_pass_interface(void)
> +{
> + return wmi_driver_register(&bios_attr_pass_interface_driver);
> +}
> +
> +void exit_bios_attr_pass_interface(void)
> +{
> + wmi_driver_unregister(&bios_attr_pass_interface_driver);
> +}
> +
> +MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table);
> --
> 2.34.1
>

2023-04-23 13:15:39

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 09/14] HP BIOSCFG driver - enum-attributes

On 2023-04-20 11:54:49-0500, Jorge Lopez wrote:
> .../x86/hp/hp-bioscfg/enum-attributes.c | 543 ++++++++++++++++++
> 1 file changed, 543 insertions(+)
> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
>
> diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
> new file mode 100644
> index 000000000000..20defa92da6f
> --- /dev/null
> +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
> @@ -0,0 +1,543 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions corresponding to enumeration type attributes under
> + * BIOS Enumeration GUID for use with hp-bioscfg driver.
> + *
> + * Copyright (c) 2022 HP Development Company, L.P.
> + */
> +
> +#include "bioscfg.h"
> +
> +GET_INSTANCE_ID(enumeration);
> +
> +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> +{
> + int instance_id = get_enumeration_instance_id(kobj);
> +
> + if (instance_id < 0)
> + return -EIO;
> +
> + return sysfs_emit(buf, "%s\n",
> + bioscfg_drv.enumeration_data[instance_id].current_value);
> +}
> +
> +/*
> + * validate_enumeration_input() -
> + * Validate input of current_value against possible values
> + *
> + * @instance_id: The instance on which input is validated
> + * @buf: Input value
> + */
> +static int validate_enumeration_input(int instance_id, const char *buf)
> +{
> + int ret = 0;
> + int found = 0;
> + int i;
> + int possible_values;
> +
> + /* Is it a read only attribute */
> + if (bioscfg_drv.enumeration_data[instance_id].common.is_readonly)
> + return -EIO;
> +
> + possible_values = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
> + for (i = 0; i < possible_values && !found; i++)
> + if (!strcasecmp(bioscfg_drv.enumeration_data[instance_id].possible_values[i], buf))

Is this also intentionally case-insensitive?

> + found = 1;
> +
> + if (!found) {
> + ret = -EINVAL;
> + goto exit_enum_input;
> + }
> +
> + /*
> + * set pending reboot flag depending on
> + * "RequiresPhysicalPresence" value
> + */
> + if (bioscfg_drv.enumeration_data[instance_id].common.requires_physical_presence)
> + bioscfg_drv.pending_reboot = true;
> +
> +exit_enum_input:
> + return ret;
> +}
> +
> +static void update_enumeration_value(int instance_id, char *attr_value)
> +{
> + strscpy(bioscfg_drv.enumeration_data[instance_id].current_value,
> + attr_value,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].current_value));
> +}
> +
> +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, enumeration);
> +static struct kobj_attribute enumeration_display_langcode =
> + __ATTR_RO(display_name_language_code);
> +
> +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, enumeration);
> +static struct kobj_attribute enumeration_display_name =
> + __ATTR_RO(display_name);
> +
> +ATTRIBUTE_PROPERTY_STORE(current_value, enumeration);
> +static struct kobj_attribute enumeration_current_val =
> + __ATTR_RW_MODE(current_value, 0644);

0644 is the default for __ATTR_RW(), use that instead.

> +
> +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, enumeration);
> +static struct kobj_attribute enumeration_prerequisites_size_val =
> + __ATTR_RO(prerequisites_size);
> +
> +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, enumeration);
> +static struct kobj_attribute enumeration_prerequisites_val =
> + __ATTR_RO(prerequisites);
> +
> +ATTRIBUTE_N_PROPERTY_SHOW(possible_values_size, enumeration);
> +static struct kobj_attribute enumeration_possible_values_size_val =
> + __ATTR_RO(possible_values_size);
> +
> +ATTRIBUTE_VALUES_PROPERTY_SHOW(possible_values, enumeration);
> +static struct kobj_attribute enumeration_poss_val =
> + __ATTR_RO(possible_values);
> +
> +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "enumeration\n");
> +}
> +static struct kobj_attribute enumeration_type =
> + __ATTR_RO(type);
> +
> +static struct attribute *enumeration_attrs[] = {
> + &enumeration_display_langcode.attr,
> + &enumeration_display_name.attr,
> + &enumeration_current_val.attr,
> + &enumeration_prerequisites_size_val.attr,
> + &enumeration_prerequisites_val.attr,
> + &enumeration_possible_values_size_val.attr,
> + &enumeration_poss_val.attr,
> + &enumeration_type.attr,
> + NULL
> +};
> +
> +static const struct attribute_group enumeration_attr_group = {
> + .attrs = enumeration_attrs,
> +};
> +
> +int alloc_enumeration_data(void)
> +{
> + int ret = 0;
> +
> + bioscfg_drv.enumeration_instances_count =
> + get_instance_count(HP_WMI_BIOS_ENUMERATION_GUID);
> +
> + bioscfg_drv.enumeration_data = kcalloc(bioscfg_drv.enumeration_instances_count,
> + sizeof(struct enumeration_data), GFP_KERNEL);
> + if (!bioscfg_drv.enumeration_data) {
> + bioscfg_drv.enumeration_instances_count = 0;
> + ret = -ENOMEM;
> + }
> + return ret;
> +}
> +
> +/* Expected Values types associated with each element */
> +static const acpi_object_type expected_enum_types[] = {
> + [NAME] = ACPI_TYPE_STRING,
> + [VALUE] = ACPI_TYPE_STRING,
> + [PATH] = ACPI_TYPE_STRING,
> + [IS_READONLY] = ACPI_TYPE_INTEGER,
> + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
> + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
> + [SEQUENCE] = ACPI_TYPE_INTEGER,
> + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
> + [PREREQUISITES] = ACPI_TYPE_STRING,
> + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
> + [ENUM_CURRENT_VALUE] = ACPI_TYPE_STRING,
> + [ENUM_SIZE] = ACPI_TYPE_INTEGER,
> + [ENUM_POSSIBLE_VALUES] = ACPI_TYPE_STRING
> +};
> +
> +/*
> + * populate_enumeration_package_data() -
> + * Populate all properties of an instance under enumeration attribute
> + *
> + * @enum_obj: ACPI object with enumeration data
> + * @instance_id: The instance to enumerate
> + * @attr_name_kobj: The parent kernel object
> + */
> +int populate_enumeration_package_data(union acpi_object *enum_obj,
> + int instance_id,
> + struct kobject *attr_name_kobj)
> +{
> + bioscfg_drv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj;
> +
> + populate_enumeration_elements_from_package(enum_obj,
> + enum_obj->package.count,
> + instance_id);
> + update_attribute_permissions(bioscfg_drv.enumeration_data[instance_id].common.is_readonly,
> + &enumeration_current_val);
> + /*
> + * Several attributes have names such "MONDAY". Friendly
> + * user nane is generated to make the name more descriptive
> + */
> + friendly_user_name_update(bioscfg_drv.enumeration_data[instance_id].common.path,
> + attr_name_kobj->name,
> + bioscfg_drv.enumeration_data[instance_id].common.display_name,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].common.display_name));
> + return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
> +}
> +
> +int populate_enumeration_elements_from_package(union acpi_object *enum_obj,
> + int enum_obj_count,
> + int instance_id)
> +{
> + char *str_value = NULL;
> + int value_len;
> + u32 size = 0;
> + u32 int_value;
> + int elem = 0;
> + int reqs;
> + int pos_values;
> + int ret;
> + int eloc;
> +
> + strscpy(bioscfg_drv.enumeration_data[instance_id].common.display_name_language_code,
> + LANG_CODE_STR,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].common.display_name_language_code));
> +
> + for (elem = 1, eloc = 1; elem < enum_obj_count; elem++, eloc++) {
> +
> + /* ONLY look at the first ENUM_ELEM_CNT elements */
> + if (eloc == ENUM_ELEM_CNT)
> + goto exit_enumeration_package;
> +
> + switch (enum_obj[elem].type) {
> + case ACPI_TYPE_STRING:
> +
> + if (PREREQUISITES != elem && ENUM_POSSIBLE_VALUES != elem) {
> + ret = convert_hexstr_to_str(enum_obj[elem].string.pointer,
> + enum_obj[elem].string.length,
> + &str_value, &value_len);
> + if (ret)
> + return -EINVAL;
> +
> + }
> + break;
> + case ACPI_TYPE_INTEGER:
> + int_value = (u32)enum_obj[elem].integer.value;
> + break;
> + default:
> + pr_warn("Unsupported object type [%d]\n", enum_obj[elem].type);
> + continue;
> + }
> +
> + /* Check that both expected and read object type match */
> + if (expected_enum_types[eloc] != enum_obj[elem].type) {
> + pr_err("Error expected type %d for elem %d, but got type %d instead\n",
> + expected_enum_types[eloc], elem, enum_obj[elem].type);
> + return -EIO;
> + }
> +
> + /* Assign appropriate element value to corresponding field */
> + switch (eloc) {
> + case NAME:
> + case VALUE:
> + break;
> + case PATH:
> + strscpy(bioscfg_drv.enumeration_data[instance_id].common.path, str_value,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].common.path));
> + break;
> + case IS_READONLY:
> + bioscfg_drv.enumeration_data[instance_id].common.is_readonly = int_value;
> + break;
> + case DISPLAY_IN_UI:
> + bioscfg_drv.enumeration_data[instance_id].common.display_in_ui = int_value;
> + break;
> + case REQUIRES_PHYSICAL_PRESENCE:
> + bioscfg_drv.enumeration_data[instance_id].common.requires_physical_presence = int_value;
> + break;
> + case SEQUENCE:
> + bioscfg_drv.enumeration_data[instance_id].common.sequence = int_value;
> + break;
> + case PREREQUISITES_SIZE:
> + bioscfg_drv.enumeration_data[instance_id].common.prerequisites_size = int_value;
> + if (int_value > MAX_PREREQUISITES_SIZE)
> + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + /*
> + * This HACK is needed to keep the expected
> + * element list pointing to the right obj[elem].type
> + * when the size is zero. PREREQUISITES
> + * object is omitted by BIOS when the size is
> + * zero.
> + */
> + if (int_value == 0)
> + eloc++;
> + break;
> +
> + case PREREQUISITES:
> +
> + size = bioscfg_drv.enumeration_data[instance_id].common.prerequisites_size;
> +
> + for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
> + if (elem >= enum_obj_count) {
> + pr_err("Error enum-objects package is too small\n");
> + return -EINVAL;
> + }
> +
> + ret = convert_hexstr_to_str(enum_obj[elem + reqs].string.pointer,
> + enum_obj[elem + reqs].string.length,
> + &str_value, &value_len);
> +
> + if (ret)
> + return -EINVAL;
> +
> + strlcpy(bioscfg_drv.enumeration_data[instance_id].common.prerequisites[reqs],
> + str_value,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].common.prerequisites[reqs]));
> +
> + kfree(str_value);
> + }
> + break;
> +
> + case SECURITY_LEVEL:
> + bioscfg_drv.enumeration_data[instance_id].common.security_level = int_value;
> + break;
> +
> + case ENUM_CURRENT_VALUE:
> + strscpy(bioscfg_drv.enumeration_data[instance_id].current_value,
> + str_value, sizeof(bioscfg_drv.enumeration_data[instance_id].current_value));
> + break;
> + case ENUM_SIZE:
> + bioscfg_drv.enumeration_data[instance_id].possible_values_size = int_value;
> + if (int_value > MAX_VALUES_SIZE)
> + pr_warn("Possible number values size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + /*
> + * This HACK is needed to keep the expected
> + * element list pointing to the right obj[elem].type
> + * when the size is zero. POSSIBLE_VALUES
> + * object is omitted by BIOS when the size is zero.
> + */
> + if (int_value == 0)
> + eloc++;
> + break;
> +
> + case ENUM_POSSIBLE_VALUES:
> + size = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
> +
> + for (pos_values = 0; pos_values < size && pos_values < MAX_VALUES_SIZE; pos_values++) {
> + if (elem >= enum_obj_count) {
> + pr_err("Error enum-objects package is too small\n");
> + return -EINVAL;
> + }
> +
> + ret = convert_hexstr_to_str(enum_obj[elem + pos_values].string.pointer,
> + enum_obj[elem + pos_values].string.length,
> + &str_value, &value_len);
> +
> + if (ret)
> + return -EINVAL;
> +
> + /*
> + * ignore strings when possible values size
> + * is greater than MAX_VALUES_SIZE
> + */
> + if (size < MAX_VALUES_SIZE)
> + strlcpy(bioscfg_drv.enumeration_data[instance_id].possible_values[pos_values],
> + str_value,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].possible_values[pos_values]));
> +
> + kfree(str_value);
> + }
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
> + break;
> + }
> +
> + kfree(str_value);
> + }
> +
> +exit_enumeration_package:
> + kfree(str_value);
> + return 0;
> +}
> +
> +/*
> + * populate_enumeration_buffer_data() -
> + * Populate all properties of an instance under enumeration attribute
> + *
> + * @buffer_ptr: Buffer pointer
> + * @buffer_size: Buffer size
> + * @enum_obj: ACPI object with enumeration data
> + * @instance_id: The instance to enumerate
> + * @attr_name_kobj: The parent kernel object
> + * @enumeration_property_count: Total properties count under enumeration type
> + */
> +int populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id,
> + struct kobject *attr_name_kobj)
> +{
> +
> + bioscfg_drv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj;
> +
> + /* Populate enumeration elements */
> + populate_enumeration_elements_from_buffer(buffer_ptr, buffer_size,
> + instance_id);
> +
> + update_attribute_permissions(bioscfg_drv.enumeration_data[instance_id].common.is_readonly,
> + &enumeration_current_val);
> + /*
> + * Several attributes have names such "MONDAY". A Friendlier
> + * user nane is generated to make the name more descriptive
> + */
> + friendly_user_name_update(bioscfg_drv.enumeration_data[instance_id].common.path,
> + attr_name_kobj->name,
> + bioscfg_drv.enumeration_data[instance_id].common.display_name,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].common.display_name));
> +
> + return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
> +}
> +
> +int populate_enumeration_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> + int instance_id)
> +{
> + char *dst = NULL;
> + int elem;
> + int reqs;
> + int integer;
> + int size = 0;
> + int values;
> + int ret;
> + int dst_size = *buffer_size / sizeof(u16);
> +
> + dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
> + if (!dst)
> + return -ENOMEM;
> +
> + elem = 0;
> +
> + strscpy(bioscfg_drv.enumeration_data[instance_id].common.display_name_language_code,
> + LANG_CODE_STR,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].common.display_name_language_code));
> +
> + for (elem = 1; elem < 3; elem++) {
> +
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + /* Ignore. Zero length string values */
> + if (ret < 0)
> + continue;
> +
> + switch (elem) {
> + case VALUE:
> + /* Skip 'Value' since 'CurrentValue' is reported. */
> + break;
> + case PATH:
> + strscpy(bioscfg_drv.enumeration_data[instance_id].common.path,
> + dst, sizeof(bioscfg_drv.enumeration_data[instance_id].common.path));
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
> + break;
> + }
> + }
> +
> + for (elem = 3; elem < ENUM_ELEM_CNT; elem++) {
> + if (PREREQUISITES != elem && ENUM_CURRENT_VALUE != elem && ENUM_POSSIBLE_VALUES != elem) {
> + ret = get_integer_from_buffer((int **)&buffer_ptr, buffer_size, (int *)&integer);
> + if (ret < 0)
> + continue;
> + }
> +
> + switch (elem) {
> + case IS_READONLY:
> + bioscfg_drv.enumeration_data[instance_id].common.is_readonly = integer;
> + break;
> + case DISPLAY_IN_UI:
> + bioscfg_drv.enumeration_data[instance_id].common.display_in_ui = integer;
> + break;
> + case REQUIRES_PHYSICAL_PRESENCE:
> + bioscfg_drv.enumeration_data[instance_id].common.requires_physical_presence = integer;
> + break;
> + case SEQUENCE:
> + bioscfg_drv.enumeration_data[instance_id].common.sequence = integer;
> + break;
> + case PREREQUISITES_SIZE:
> + bioscfg_drv.enumeration_data[instance_id].common.prerequisites_size = integer;
> + if (integer > MAX_PREREQUISITES_SIZE)
> + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + // PREREQUISITES:
> + elem++;
> +
> + size = bioscfg_drv.enumeration_data[instance_id].common.prerequisites_size;
> + for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + /* Ignore. expect zero length strings at the end of prerequisite values */
> + if (ret < 0)
> + continue;
> +
> + strscpy(bioscfg_drv.enumeration_data[instance_id].common.prerequisites[reqs],
> + dst,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].common.prerequisites[reqs]));
> + }
> + break;
> + case SECURITY_LEVEL:
> + bioscfg_drv.enumeration_data[instance_id].common.security_level = integer;
> + break;
> + case ENUM_CURRENT_VALUE:
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + if (ret < 0)
> + continue;
> +
> + strscpy(bioscfg_drv.enumeration_data[instance_id].current_value,
> + dst,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].current_value));
> + break;
> + case ENUM_SIZE:
> + bioscfg_drv.enumeration_data[instance_id].possible_values_size = integer;
> + if (integer > MAX_VALUES_SIZE)
> + pr_warn("Possible size value exceeded the maximum number of elements supported or data may be malformed\n");
> +
> + // ENUM_POSSIBLE_VALUES:
> + elem++;
> +
> + size = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
> + for (values = 0; values < size && values < MAX_VALUES_SIZE; values++) {
> + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> + /* Ignore expect zero size strings at the end of all possible values */
> + if (ret < 0)
> + continue;
> +
> + strscpy(bioscfg_drv.enumeration_data[instance_id].possible_values[values],
> + dst,
> + sizeof(bioscfg_drv.enumeration_data[instance_id].possible_values[values]));
> + }
> + break;
> + default:
> + pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
> + break;
> + }
> + }
> +
> + kfree(dst);
> +
> + return 0;
> +}
> +
> +/**
> + * exit_enumeration_attributes() - Clear all attribute data
> + *
> + * Clears all data allocated for this group of attributes
> + */
> +void exit_enumeration_attributes(void)
> +{
> + int instance_id;
> +
> + for (instance_id = 0; instance_id < bioscfg_drv.enumeration_instances_count; instance_id++) {
> + struct kobject *attr_name_kobj = bioscfg_drv.enumeration_data[instance_id].attr_name_kobj;
> +
> + if (attr_name_kobj)
> + sysfs_remove_group(attr_name_kobj, &enumeration_attr_group);
> + }
> + bioscfg_drv.enumeration_instances_count = 0;
> +
> + kfree(bioscfg_drv.enumeration_data);
> + bioscfg_drv.enumeration_data = NULL;
> +}
> --
> 2.34.1
>

2023-04-24 16:17:53

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 01/14] HP BIOSCFG driver - Documentation

Hi Thomas,


On Sat, Apr 22, 2023 at 3:51 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-20 11:54:41-0500, Jorge Lopez wrote:
> > Based on the latest platform-drivers-x86.git/for-next
> > ---
> > .../testing/sysfs-class-firmware-attributes | 98 ++++++++++++++++++-
> > 1 file changed, 96 insertions(+), 2 deletions(-)
> >
> > diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> > index 4cdba3477176..73d7b8fbc0b2 100644
> > --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
> > +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> > @@ -22,6 +22,12 @@ Description:
> > - integer: a range of numerical values
> > - string
> >
> > + HP specific types
> > + -----------------
> > + - ordered-list - a set of ordered list valid values
> > + - sure-start - report audit logs read from BIOS
>
> In the treevie you sent the Sure_Start attribute does not have
> a "current_value".
> This indicated that it's not actually a firmware attribute but just some
> standalone sysfs attribute.
>
> In this case it should not be documented here, but handled the same way
> as the SPM stuff.

I will update the documentation as indicated.

Regards,

Jorge

2023-04-24 20:43:16

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 02/14] HP BIOSCFG driver - biosattr-interface

Hi Thomas,

Please see my comments below.

On Sat, Apr 22, 2023 at 4:30 PM Thomas Weißschuh <[email protected]> wrote:
>
> Hi Jorge,
>
> checkpatch.pl finds some issues on your patches.
> Please make sure checkpath.pl --strict is happy.
>
I wasn't aware of the '--strict' parameter. It is not part of the
help information for checkpath.pl tool.
Nonetheless, I will use it forward.
Thanks


> On 2023-04-20 11:54:42-0500, Jorge Lopez wrote:
> > ---
> > Based on the latest platform-drivers-x86.git/for-next
> > ---
> > .../x86/hp/hp-bioscfg/biosattr-interface.c | 307 ++++++++++++++++++
> > 1 file changed, 307 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
> >
> > diff --git a/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
> > new file mode 100644
> > index 000000000000..f09dd41867f7
> > --- /dev/null
> > +++ b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
> > @@ -0,0 +1,307 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Functions corresponding to methods under BIOS interface GUID
> > + * for use with hp-bioscfg driver.
> > + *
> > + * Copyright (c) 2022 Hewlett-Packard Inc.
> > + */
> > +
> > +#include <linux/wmi.h>
> > +#include "bioscfg.h"
> > +
> > +#define SET_DEFAULT_VALUES_METHOD_ID 0x02
> > +#define SET_BIOS_DEFAULTS_METHOD_ID 0x03
> > +#define SET_ATTRIBUTE_METHOD_ID 0x04
>
> This could be an enum.

Define lines are not in use. They will be removed.
>
> > +
> > +/*
> > + * set_attribute() - Update an attribute value
> > + * @a_name: The attribute name
> > + * @a_value: The attribute value
> > + *
> > + * Sets an attribute to new value
> > + */
> > +int hp_set_attribute(const char *a_name, const char *a_value)
> > +{
> > + size_t security_area_size;
> > + size_t a_name_size, a_value_size;
> > + u16 *buffer = NULL;
> > + u16 *start = NULL;
> > + int buffer_size;
> > + int ret = 0;
> > + int instance;
> > + char *auth_empty_value = "";
> > + char *auth_token_choice = NULL;
>
> No need to initialize auth_token_choice and start.
> Consider coalescing variable declaration to avoid wasting vertical
> space.
>
Done!

> > +
> > +
> > + mutex_lock(&bioscfg_drv.mutex);
> > + if (!bioscfg_drv.bios_attr_wdev) {
> > + ret = -ENODEV;
> > + goto out_set_attribute;
> > + }
> > +
> > + instance = get_password_instance_for_type(SETUP_PASSWD);
> > + if (instance < 0) {
> > + ret = -EINVAL;
> > + goto out_set_attribute;
> > + }
> > +
> > + if (strlen(bioscfg_drv.password_data[instance].current_password) == 0)
> > + strscpy(bioscfg_drv.password_data[instance].current_password,
> > + auth_empty_value,
> > + sizeof(bioscfg_drv.password_data[instance].current_password));
>
> This essentially does
>
> if (current_password[0] == '\0')
> current_password[0] = '\0';
>
> ... nothing.
>
The statement was intended as part of early testing and failed to
remove it during cleanup.
It will be removed.

>
> In the driver there is a lot of dereferencing substructures of
> bioscfg_drv going on. This makes the code harder to read.
>
> > +
> > + /* Select which auth token to use; password or [auth token] */
> > +
> > + if (bioscfg_drv.spm_data.auth_token != NULL)
> > + auth_token_choice = bioscfg_drv.spm_data.auth_token;
> > + else
> > + auth_token_choice = bioscfg_drv.password_data[instance].current_password;
> > +
> > + a_name_size = bioscfg_calculate_string_buffer(a_name);
> > + a_value_size = bioscfg_calculate_string_buffer(a_value);
> > + security_area_size = calculate_security_buffer(auth_token_choice);
> > + buffer_size = a_name_size + a_value_size + security_area_size;
> > +
> > + buffer = kmalloc(buffer_size + 1, GFP_KERNEL);
> > + if (!buffer) {
> > + ret = -ENOMEM;
> > + goto out_set_attribute;
> > + }
> > +
> > + /* build variables to set */
> > + start = buffer;
> > + start = ascii_to_utf16_unicode(start, a_name);
> > + if (!start)
> > + goto out_set_attribute;
>
> ret is 0 here. Is this success?
>
> > +
> > + start = ascii_to_utf16_unicode(start, a_value);
> > + if (!start)
> > + goto out_set_attribute;
>
> Same as above.

These conditions are not successful. ret value will be reset to
indicate the appropriate failure.
>
> > +
> > + populate_security_buffer(start, auth_token_choice);
> > +
> > + ret = hp_wmi_set_bios_setting(buffer, buffer_size);
> > +
> > +
> > +out_set_attribute:
> > + kfree(buffer);
> > + mutex_unlock(&bioscfg_drv.mutex);
> > + return ret;
> > +}
> > +
> > +/*
> > + * hp_wmi_perform_query
> > + *
> > + * query: The commandtype (enum hp_wmi_commandtype)
> > + * write: The command (enum hp_wmi_command)
> > + * buffer: Buffer used as input and/or output
> > + * insize: Size of input buffer
> > + * outsize: Size of output buffer
> > + *
> > + * returns zero on success
> > + * an HP WMI query specific error code (which is positive)
> > + * -EINVAL if the query was not successful at all
> > + * -EINVAL if the output buffer size exceeds buffersize
>
> How is the caller supposed to distinguish those?
This is a piece of legacy code from early development. 'ret' value is
set to -EIO and the line 98 will read

" -EIO if the output buffer size exceeds buffersize "

>
> > + *
> > + * Note: The buffersize must at least be the maximum of the input and output
> > + * size. E.g. Battery info query is defined to have 1 byte input
> > + * and 128 byte output. The caller would do:
> > + * buffer = kzalloc(128, GFP_KERNEL);
> > + * ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ,
> > + * buffer, 1, 128)
> > + */
> > +int hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer,
> > + int insize, int outsize)
>
> Can insize and outsize ever be negative?
> Maybe use u32 or size_t.

The values are positive but there is no check in the event a negative
value is passed.
I will use u32 instead as precaution.

>
> > +{
> > + struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL };
> > + struct bios_return *bios_return;
> > + union acpi_object *obj = NULL;
> > + struct bios_args *args = NULL;
> > + int mid, actual_outsize;
> > + size_t bios_args_size;
> > + int ret;
> > +
> > + mid = encode_outsize_for_pvsz(outsize);
> > + if (WARN_ON(mid < 0))
> > + return mid;
> > +
> > + bios_args_size = struct_size(args, data, insize);
> > + args = kmalloc(bios_args_size, GFP_KERNEL);
> > + if (!args)
> > + return -ENOMEM;
> > +
> > + input.length = bios_args_size;
> > + input.pointer = args;
> > +
> > + args->signature = 0x55434553;
>
> What does this number mean?
This is a HEX representation of the word 'SECU' expected by BIOS as a signa.

>
> > + args->command = command;
> > + args->commandtype = query;
> > + args->datasize = insize;
> > + memcpy(args->data, buffer, flex_array_size(args, data, insize));
> > +
> > + ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
>
> The driver is mixing calls to the UUID based APIs and the wmi_device
> ones.
> wmi_devices is newer and preferred.

The driver calls wmi_evaluate_method when initiating an WMI call.
Where is the driver mixing calls to the UUID based APIs and the
wmi_device one?
WMI calls are made by calling hp_wmi_perform_query() which invokes
wmi_evaluate_method().
Did I miss something?

>
> > + bioscfg_wmi_error_and_message(ret);
> > +
> > + if (ret)
> > + goto out_free;
> > +
> > + obj = output.pointer;
> > + if (!obj) {
> > + ret = -EINVAL;
> > + goto out_free;
> > + }
> > + if (query != HPWMI_SECUREPLATFORM_GET_STATE &&
> > + command != HPWMI_SECUREPLATFORM)
> > + if (obj->type != ACPI_TYPE_BUFFER ||
> > + obj->buffer.length < sizeof(*bios_return)) {
> > + pr_warn("query 0x%x returned wrong type or too small buffer\n", query);
> > + ret = -EINVAL;
> > + goto out_free;
> > + }
> > +
> > +
> > + bios_return = (struct bios_return *)obj->buffer.pointer;
>
> For query == HPWMI_SECUREPLATFORM_GET_STATE && command == HPWMI_SECUREPLATFORM
> this is not guaranteed to be a buffer.

BIOS ensures the output is of buffer type and buffer of 1024 bytes in
size. This check also help us validate that BIOS only returns a
buffer type for this query/command type.
>
> > + ret = bios_return->return_code;
> > + bioscfg_wmi_error_and_message(ret);
> > +
> > + if (ret) {
> > + if (ret != HPWMI_RET_UNKNOWN_COMMAND &&
> > + ret != HPWMI_RET_UNKNOWN_CMDTYPE)
> > + pr_warn("query 0x%x returned error 0x%x\n", query, ret);
> > + goto out_free;
> > + }
> > +
> > + /* Ignore output data of zero size */
> > + if (!outsize)
> > + goto out_free;
> > +
> > + actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return)));
>
> actual_outsize could be negative, which will underflow in the call to
> memcpy().

I will evaluate the two size values prior calling memcpy and report an
error if needed.
>
> > + memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize);
> > + memset(buffer + actual_outsize, 0, outsize - actual_outsize);
>
> memcpy_and_pad()
>
I will replace the two calls with the single proposed memcpy_and_pad call.

> > +
> > +out_free:
> > + kfree(obj);
> > + kfree(args);
> > + return ret;
> > +}
> > +
> > +static void *utf16_empty_string(u16 *p)
> > +{
> > + *p++ = 2;
> > + *p++ = (u8)0x00;
> > + return p;
> > +}
> > +
> > +/*
> > + * ascii_to_utf16_unicode - Convert ascii string to UTF-16 unicode
> > + *
> > + * BIOS supports UTF-16 characters that are 2 bytes long. No variable
> > + * multi-byte language supported.
> > + *
> > + * @p: Unicode buffer address
> > + * @str: string to convert to unicode
> > + *
> > + * Returns a void pointer to the buffer containing unicode string
>
> This returns a pointer to the end of the written string.

Done
>
> > + */
> > +void *ascii_to_utf16_unicode(u16 *p, const u8 *str)
> > +{
> > + int len = strlen(str);
> > + int ret;
> > +
> > + /*
> > + * Add null character when reading an empty string
> > + * "02 00 00 00"
> > + */
> > + if (len == 0)
> > + return utf16_empty_string(p);
> > +
> > + /* Move pointer len * 2 number of bytes */
> > + *p++ = len * 2;
> > + ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, p, len);
> > + if (ret < 0) {
> > + dev_err(bioscfg_drv.class_dev, "UTF16 conversion failed\n");
> > + goto ascii_to_utf16_unicode_out;
> > + }
>
> What if ret != len ?

only in conditions where utf8s_to_utf16s an error, we can state ret != len.
ret == len when utf8s_to_utf16s() is successful.
>
> > +
> > + if ((ret * sizeof(u16)) > U16_MAX) {
> > + dev_err(bioscfg_drv.class_dev, "Error string too long\n");
> > + goto ascii_to_utf16_unicode_out;
> > + }
> > +
> > +ascii_to_utf16_unicode_out:
> > + p += len;
>
> In cases of errors this will still point to the end of the data that
> should have been written but was not actually written.
> The caller has no way to recognize the error case.
>
That is correct. If an error occurs, we only provide an error message
for those conditions.

> > + return p;
> > +}
> > +
> > +/*
>
> kernel-doc comments start with "/**". Note the two asterisks.
Done
>
> > + * hp_wmi_set_bios_setting - Set setting's value in BIOS
> > + *
> > + * @input_buffer: Input buffer address
> > + * @input_size: Input buffer size
> > + *
> > + * Returns: Count of unicode characters written to BIOS if successful, otherwise
> > + * -ENOMEM unable to allocate memory
> > + * -EINVAL buffer not allocated or too small
> > + */
> > +int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size)
> > +{
> > + union acpi_object *obj;
> > + struct acpi_buffer input = {input_size, input_buffer};
> > + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
> > + int ret = 0;
>
> No need to initialize "ret".
Done!
>
> > +
> > + ret = wmi_evaluate_method(HP_WMI_SET_BIOS_SETTING_GUID, 0, 1, &input, &output);
> > +
> > + obj = output.pointer;
> > + if (!obj)
> > + return -EINVAL;
>
> This skips the bioscfg_wmi_error_and_message call.
done!
>
> > +
> > + if (obj->type != ACPI_TYPE_INTEGER)
> > + ret = -EINVAL;
> > +
> > + ret = obj->integer.value;
>
> This overwrites the "ret = -EINVAL" from above.
> Add an "else" branch.

done!
>
> > + bioscfg_wmi_error_and_message(ret);
> > +
> > + kfree(obj);
> > + return ret;
> > +}
> > +
> > +static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
> > +{
> > + mutex_lock(&bioscfg_drv.mutex);
> > + bioscfg_drv.bios_attr_wdev = wdev;
> > + mutex_unlock(&bioscfg_drv.mutex);
> > + return 0;
> > +}
>
> Technically a WMI UUID can be present multiple times.
> This would lead to the driver being loaded multiple times, each driver
> clobbering the bios_attr_wdev of the other drivers.
>
> Maybe check the pointer and return -EEXIST.
>
> This applies to all subdrivers.

Done!
>
> > +
> > +static void bios_attr_set_interface_remove(struct wmi_device *wdev)
> > +{
> > + mutex_lock(&bioscfg_drv.mutex);
> > + bioscfg_drv.bios_attr_wdev = NULL;
> > + mutex_unlock(&bioscfg_drv.mutex);
> > +}
> > +
> > +static const struct wmi_device_id bios_attr_set_interface_id_table[] = {
> > + { .guid_string = HP_WMI_BIOS_GUID},
> > + { }
> > +};
> > +static struct wmi_driver bios_attr_set_interface_driver = {
> > + .driver = {
> > + .name = DRIVER_NAME
> > + },
> > + .probe = bios_attr_set_interface_probe,
> > + .remove = bios_attr_set_interface_remove,
> > + .id_table = bios_attr_set_interface_id_table
>
> Put a comma here and above after DRIVER_NAME to reduce future diffs.

Done!
>
> > +};
> > +
> > +int init_bios_attr_set_interface(void)
> > +{
> > + return wmi_driver_register(&bios_attr_set_interface_driver);
> > +}
> > +
> > +void exit_bios_attr_set_interface(void)
> > +{
> > + wmi_driver_unregister(&bios_attr_set_interface_driver);
> > +}
> > +
> > +MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table);

2023-04-24 20:59:19

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 01/14] HP BIOSCFG driver - Documentation

Hi Jorge,

On 2023-04-24 11:11:41-0500, Jorge Lopez wrote:
> On Sat, Apr 22, 2023 at 3:51 PM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-04-20 11:54:41-0500, Jorge Lopez wrote:
> > > Based on the latest platform-drivers-x86.git/for-next
> > > ---
> > > .../testing/sysfs-class-firmware-attributes | 98 ++++++++++++++++++-
> > > 1 file changed, 96 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> > > index 4cdba3477176..73d7b8fbc0b2 100644
> > > --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
> > > +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> > > @@ -22,6 +22,12 @@ Description:
> > > - integer: a range of numerical values
> > > - string
> > >
> > > + HP specific types
> > > + -----------------
> > > + - ordered-list - a set of ordered list valid values
> > > + - sure-start - report audit logs read from BIOS
> >
> > In the treevie you sent the Sure_Start attribute does not have
> > a "current_value".
> > This indicated that it's not actually a firmware attribute but just some
> > standalone sysfs attribute.
> >
> > In this case it should not be documented here, but handled the same way
> > as the SPM stuff.
>
> I will update the documentation as indicated.

This will also need changes to the code.
Only the audit log stuff is needed then.

2023-04-24 21:12:24

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 02/14] HP BIOSCFG driver - biosattr-interface

On 2023-04-24 15:33:26-0500, Jorge Lopez wrote:
> Hi Thomas,
>
> Please see my comments below.
>
> On Sat, Apr 22, 2023 at 4:30 PM Thomas Weißschuh <[email protected]> wrote:
> >
> > Hi Jorge,
> >
> > checkpatch.pl finds some issues on your patches.
> > Please make sure checkpath.pl --strict is happy.
> >
> I wasn't aware of the '--strict' parameter. It is not part of the
> help information for checkpath.pl tool.
> Nonetheless, I will use it forward.
> Thanks

It's an alias to --subjective. But indeed, it's hard to see in the help
output.

> > On 2023-04-20 11:54:42-0500, Jorge Lopez wrote:
> > > ---
> > > Based on the latest platform-drivers-x86.git/for-next
> > No need to initialize auth_token_choice and start.
> > Consider coalescing variable declaration to avoid wasting vertical
> > space.
> >
> Done!

Please note that this affects many parts of the driver,
try to fix it everywhere.

> > > +{
> > > + struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL };
> > > + struct bios_return *bios_return;
> > > + union acpi_object *obj = NULL;
> > > + struct bios_args *args = NULL;
> > > + int mid, actual_outsize;
> > > + size_t bios_args_size;
> > > + int ret;
> > > +
> > > + mid = encode_outsize_for_pvsz(outsize);
> > > + if (WARN_ON(mid < 0))
> > > + return mid;
> > > +
> > > + bios_args_size = struct_size(args, data, insize);
> > > + args = kmalloc(bios_args_size, GFP_KERNEL);
> > > + if (!args)
> > > + return -ENOMEM;
> > > +
> > > + input.length = bios_args_size;
> > > + input.pointer = args;
> > > +
> > > + args->signature = 0x55434553;
> >
> > What does this number mean?
> This is a HEX representation of the word 'SECU' expected by BIOS as a signa.

Sounds like a good thing to comment or put into a #define.

> >
> > > + args->command = command;
> > > + args->commandtype = query;
> > > + args->datasize = insize;
> > > + memcpy(args->data, buffer, flex_array_size(args, data, insize));
> > > +
> > > + ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
> >
> > The driver is mixing calls to the UUID based APIs and the wmi_device
> > ones.
> > wmi_devices is newer and preferred.
>
> The driver calls wmi_evaluate_method when initiating an WMI call.
> Where is the driver mixing calls to the UUID based APIs and the
> wmi_device one?
> WMI calls are made by calling hp_wmi_perform_query() which invokes
> wmi_evaluate_method().
> Did I miss something?

wmi_evaluate_method() is UUID-based.
struct wmi_driver is wmi_device based.

The wmi_driver/wmi_device code essentially does nothing and is only used
to validate that a device is present.
The same can be done more easily wmi_has_guid().

> >
> > > + bioscfg_wmi_error_and_message(ret);
> > > +
> > > + if (ret)
> > > + goto out_free;
> > > +
> > > + obj = output.pointer;
> > > + if (!obj) {
> > > + ret = -EINVAL;
> > > + goto out_free;
> > > + }
> > > + if (query != HPWMI_SECUREPLATFORM_GET_STATE &&
> > > + command != HPWMI_SECUREPLATFORM)
> > > + if (obj->type != ACPI_TYPE_BUFFER ||
> > > + obj->buffer.length < sizeof(*bios_return)) {
> > > + pr_warn("query 0x%x returned wrong type or too small buffer\n", query);
> > > + ret = -EINVAL;
> > > + goto out_free;
> > > + }
> > > +
> > > +
> > > + bios_return = (struct bios_return *)obj->buffer.pointer;
> >
> > For query == HPWMI_SECUREPLATFORM_GET_STATE && command == HPWMI_SECUREPLATFORM
> > this is not guaranteed to be a buffer.
>
> BIOS ensures the output is of buffer type and buffer of 1024 bytes in
> size. This check also help us validate that BIOS only returns a
> buffer type for this query/command type.

The kernel does not trust the BIOS :-)
It trusts nothing and nobody.

All cases should be validated.

> >
> > > + */
> > > +void *ascii_to_utf16_unicode(u16 *p, const u8 *str)
> > > +{
> > > + int len = strlen(str);
> > > + int ret;
> > > +
> > > + /*
> > > + * Add null character when reading an empty string
> > > + * "02 00 00 00"
> > > + */
> > > + if (len == 0)
> > > + return utf16_empty_string(p);
> > > +
> > > + /* Move pointer len * 2 number of bytes */
> > > + *p++ = len * 2;
> > > + ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, p, len);
> > > + if (ret < 0) {
> > > + dev_err(bioscfg_drv.class_dev, "UTF16 conversion failed\n");
> > > + goto ascii_to_utf16_unicode_out;
> > > + }
> >
> > What if ret != len ?
>
> only in conditions where utf8s_to_utf16s an error, we can state ret != len.
> ret == len when utf8s_to_utf16s() is successful.
> >
> > > +
> > > + if ((ret * sizeof(u16)) > U16_MAX) {
> > > + dev_err(bioscfg_drv.class_dev, "Error string too long\n");
> > > + goto ascii_to_utf16_unicode_out;
> > > + }
> > > +
> > > +ascii_to_utf16_unicode_out:
> > > + p += len;
> >
> > In cases of errors this will still point to the end of the data that
> > should have been written but was not actually written.
> > The caller has no way to recognize the error case.
> >
> That is correct. If an error occurs, we only provide an error message
> for those conditions.

But the caller has no idea that this error occurred and will try to use
the garbage buffer.
The error should be communicated to the caller, and the caller has to
validate the result.
Maybe return NULL?

>
> > > + return p;
> > > +}
> > > +
> > > +/*
> >
> > kernel-doc comments start with "/**". Note the two asterisks.
> Done

This also needs to be done all over the driver.

2023-04-24 21:46:08

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 01/14] HP BIOSCFG driver - Documentation

Hi Thomas,

On Mon, Apr 24, 2023 at 3:52 PM Thomas Weißschuh <[email protected]> wrote:
>
> Hi Jorge,
>
> On 2023-04-24 11:11:41-0500, Jorge Lopez wrote:
> > On Sat, Apr 22, 2023 at 3:51 PM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-20 11:54:41-0500, Jorge Lopez wrote:
> > > > Based on the latest platform-drivers-x86.git/for-next
> > > > ---
> > > > .../testing/sysfs-class-firmware-attributes | 98 ++++++++++++++++++-
> > > > 1 file changed, 96 insertions(+), 2 deletions(-)
> > > >
> > > > diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> > > > index 4cdba3477176..73d7b8fbc0b2 100644
> > > > --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
> > > > +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> > > > @@ -22,6 +22,12 @@ Description:
> > > > - integer: a range of numerical values
> > > > - string
> > > >
> > > > + HP specific types
> > > > + -----------------
> > > > + - ordered-list - a set of ordered list valid values
> > > > + - sure-start - report audit logs read from BIOS
> > >
> > > In the treevie you sent the Sure_Start attribute does not have
> > > a "current_value".
> > > This indicated that it's not actually a firmware attribute but just some
> > > standalone sysfs attribute.
> > >
> > > In this case it should not be documented here, but handled the same way
> > > as the SPM stuff.
> >
> > I will update the documentation as indicated.
>
> This will also need changes to the code.
> Only the audit log stuff is needed then.

Ok. Sure_Start will only report two entries; audit_log_entries and
audit_log_entry_count.
Other entries such display_name, type and display_name_language will be removed

2023-04-24 22:04:32

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 02/14] HP BIOSCFG driver - biosattr-interface

Hi Thomas,

On Mon, Apr 24, 2023 at 4:04 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-24 15:33:26-0500, Jorge Lopez wrote:
> > Hi Thomas,
> >
> > Please see my comments below.
> >
> > On Sat, Apr 22, 2023 at 4:30 PM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > Hi Jorge,
> > >
> > > checkpatch.pl finds some issues on your patches.
> > > Please make sure checkpath.pl --strict is happy.
> > >
> > I wasn't aware of the '--strict' parameter. It is not part of the
> > help information for checkpath.pl tool.
> > Nonetheless, I will use it forward.
> > Thanks
>
> It's an alias to --subjective. But indeed, it's hard to see in the help
> output.
Thanks
>
> > > On 2023-04-20 11:54:42-0500, Jorge Lopez wrote:
> > > > ---
> > > > Based on the latest platform-drivers-x86.git/for-next
> > > No need to initialize auth_token_choice and start.
> > > Consider coalescing variable declaration to avoid wasting vertical
> > > space.
> > >
> > Done!
>
> Please note that this affects many parts of the driver,
> try to fix it everywhere.

It will be done across all files

>
> > > > +{
> > > > + struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL };
> > > > + struct bios_return *bios_return;
> > > > + union acpi_object *obj = NULL;
> > > > + struct bios_args *args = NULL;
> > > > + int mid, actual_outsize;
> > > > + size_t bios_args_size;
> > > > + int ret;
> > > > +
> > > > + mid = encode_outsize_for_pvsz(outsize);
> > > > + if (WARN_ON(mid < 0))
> > > > + return mid;
> > > > +
> > > > + bios_args_size = struct_size(args, data, insize);
> > > > + args = kmalloc(bios_args_size, GFP_KERNEL);
> > > > + if (!args)
> > > > + return -ENOMEM;
> > > > +
> > > > + input.length = bios_args_size;
> > > > + input.pointer = args;
> > > > +
> > > > + args->signature = 0x55434553;
> > >
> > > What does this number mean?
> > This is a HEX representation of the word 'SECU' expected by BIOS as a signa.
>
> Sounds like a good thing to comment or put into a #define.

I will add a comment since it is only used here.
>
> > >
> > > > + args->command = command;
> > > > + args->commandtype = query;
> > > > + args->datasize = insize;
> > > > + memcpy(args->data, buffer, flex_array_size(args, data, insize));
> > > > +
> > > > + ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
> > >
> > > The driver is mixing calls to the UUID based APIs and the wmi_device
> > > ones.
> > > wmi_devices is newer and preferred.
> >
> > The driver calls wmi_evaluate_method when initiating an WMI call.
> > Where is the driver mixing calls to the UUID based APIs and the
> > wmi_device one?
> > WMI calls are made by calling hp_wmi_perform_query() which invokes
> > wmi_evaluate_method().
> > Did I miss something?
>
> wmi_evaluate_method() is UUID-based.
> struct wmi_driver is wmi_device based.
>
> The wmi_driver/wmi_device code essentially does nothing and is only used
> to validate that a device is present.
> The same can be done more easily wmi_has_guid().
>

Thank you for the clarification.
> > >
> > > > + bioscfg_wmi_error_and_message(ret);
> > > > +
> > > > + if (ret)
> > > > + goto out_free;
> > > > +
> > > > + obj = output.pointer;
> > > > + if (!obj) {
> > > > + ret = -EINVAL;
> > > > + goto out_free;
> > > > + }
> > > > + if (query != HPWMI_SECUREPLATFORM_GET_STATE &&
> > > > + command != HPWMI_SECUREPLATFORM)
> > > > + if (obj->type != ACPI_TYPE_BUFFER ||
> > > > + obj->buffer.length < sizeof(*bios_return)) {
> > > > + pr_warn("query 0x%x returned wrong type or too small buffer\n", query);
> > > > + ret = -EINVAL;
> > > > + goto out_free;
> > > > + }
> > > > +
> > > > +
> > > > + bios_return = (struct bios_return *)obj->buffer.pointer;
> > >
> > > For query == HPWMI_SECUREPLATFORM_GET_STATE && command == HPWMI_SECUREPLATFORM
> > > this is not guaranteed to be a buffer.
> >
> > BIOS ensures the output is of buffer type and buffer of 1024 bytes in
> > size. This check also help us validate that BIOS only returns a
> > buffer type for this query/command type.
>
> The kernel does not trust the BIOS :-)
> It trusts nothing and nobody.
>
> All cases should be validated.

Additional validation will be added to cover all cases.

>
> > >
> > > > + */
> > > > +void *ascii_to_utf16_unicode(u16 *p, const u8 *str)
> > > > +{
> > > > + int len = strlen(str);
> > > > + int ret;
> > > > +
> > > > + /*
> > > > + * Add null character when reading an empty string
> > > > + * "02 00 00 00"
> > > > + */
> > > > + if (len == 0)
> > > > + return utf16_empty_string(p);
> > > > +
> > > > + /* Move pointer len * 2 number of bytes */
> > > > + *p++ = len * 2;
> > > > + ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, p, len);
> > > > + if (ret < 0) {
> > > > + dev_err(bioscfg_drv.class_dev, "UTF16 conversion failed\n");
> > > > + goto ascii_to_utf16_unicode_out;
> > > > + }
> > >
> > > What if ret != len ?
> >
> > only in conditions where utf8s_to_utf16s an error, we can state ret != len.
> > ret == len when utf8s_to_utf16s() is successful.
> > >
> > > > +
> > > > + if ((ret * sizeof(u16)) > U16_MAX) {
> > > > + dev_err(bioscfg_drv.class_dev, "Error string too long\n");
> > > > + goto ascii_to_utf16_unicode_out;
> > > > + }
> > > > +
> > > > +ascii_to_utf16_unicode_out:
> > > > + p += len;
> > >
> > > In cases of errors this will still point to the end of the data that
> > > should have been written but was not actually written.
> > > The caller has no way to recognize the error case.
> > >
> > That is correct. If an error occurs, we only provide an error message
> > for those conditions.
>
> But the caller has no idea that this error occurred and will try to use
> the garbage buffer.
> The error should be communicated to the caller, and the caller has to
> validate the result.
> Maybe return NULL?

returning NULL will be a good option so I will review what the impact
will be across the driver
>
> >
> > > > + return p;
> > > > +}
> > > > +
> > > > +/*
> > >
> > > kernel-doc comments start with "/**". Note the two asterisks.
> > Done
>
> This also needs to be done all over the driver.

It will be done across all files.

2023-04-24 22:23:01

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 02/14] HP BIOSCFG driver - biosattr-interface

HI Thomas,

Sorry for asking again. I just want to be understand exactly what I must do.


> > > >
> > > > > + args->command = command;
> > > > > + args->commandtype = query;
> > > > > + args->datasize = insize;
> > > > > + memcpy(args->data, buffer, flex_array_size(args, data, insize));
> > > > > +
> > > > > + ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
> > > >
> > > > The driver is mixing calls to the UUID based APIs and the wmi_device
> > > > ones.
> > > > wmi_devices is newer and preferred.
> > >
> > > The driver calls wmi_evaluate_method when initiating an WMI call.
> > > Where is the driver mixing calls to the UUID based APIs and the
> > > wmi_device one?
> > > WMI calls are made by calling hp_wmi_perform_query() which invokes
> > > wmi_evaluate_method().
> > > Did I miss something?
> >
> > wmi_evaluate_method() is UUID-based.
> > struct wmi_driver is wmi_device based.
> >
> > The wmi_driver/wmi_device code essentially does nothing and is only used
> > to validate that a device is present.
> > The same can be done more easily wmi_has_guid().
> >
>

Are you asking to replace all calls to wmi_evaluate_method() which is
UUID based API with calls to wmidev_evaluate_method() which is
wmi_device based? Correct?

2023-04-25 05:35:33

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 02/14] HP BIOSCFG driver - biosattr-interface

On 2023-04-24 17:14:57-0500, Jorge Lopez wrote:
> Sorry for asking again. I just want to be understand exactly what I must do.

No problem!

> > > > >
> > > > > > + args->command = command;
> > > > > > + args->commandtype = query;
> > > > > > + args->datasize = insize;
> > > > > > + memcpy(args->data, buffer, flex_array_size(args, data, insize));
> > > > > > +
> > > > > > + ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
> > > > >
> > > > > The driver is mixing calls to the UUID based APIs and the wmi_device
> > > > > ones.
> > > > > wmi_devices is newer and preferred.
> > > >
> > > > The driver calls wmi_evaluate_method when initiating an WMI call.
> > > > Where is the driver mixing calls to the UUID based APIs and the
> > > > wmi_device one?
> > > > WMI calls are made by calling hp_wmi_perform_query() which invokes
> > > > wmi_evaluate_method().
> > > > Did I miss something?
> > >
> > > wmi_evaluate_method() is UUID-based.
> > > struct wmi_driver is wmi_device based.
> > >
> > > The wmi_driver/wmi_device code essentially does nothing and is only used
> > > to validate that a device is present.
> > > The same can be done more easily wmi_has_guid().
> > >
> >
>
> Are you asking to replace all calls to wmi_evaluate_method() which is
> UUID based API with calls to wmidev_evaluate_method() which is
> wmi_device based? Correct?

To be honest I'm not 100% sure.

wmi_device is great and perferct for simple drivers binding to a single
UUID.

But it does not handle multi-UUID logic as your driver needs very well.

I would argue to stick to the legacy calls as it allows you to drop a
bunch of code and makes the initialization flow more straightforward.

But I don't know if somebody else won't ask for wmi_device later.

2023-04-25 13:46:47

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 02/14] HP BIOSCFG driver - biosattr-interface

On Tue, Apr 25, 2023 at 12:28 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-24 17:14:57-0500, Jorge Lopez wrote:
> > Sorry for asking again. I just want to be understand exactly what I must do.
>
> No problem!
>
> > > > > >
> > > > > > > + args->command = command;
> > > > > > > + args->commandtype = query;
> > > > > > > + args->datasize = insize;
> > > > > > > + memcpy(args->data, buffer, flex_array_size(args, data, insize));
> > > > > > > +
> > > > > > > + ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
> > > > > >
> > > > > > The driver is mixing calls to the UUID based APIs and the wmi_device
> > > > > > ones.
> > > > > > wmi_devices is newer and preferred.
> > > > >
> > > > > The driver calls wmi_evaluate_method when initiating an WMI call.
> > > > > Where is the driver mixing calls to the UUID based APIs and the
> > > > > wmi_device one?
> > > > > WMI calls are made by calling hp_wmi_perform_query() which invokes
> > > > > wmi_evaluate_method().
> > > > > Did I miss something?
> > > >
> > > > wmi_evaluate_method() is UUID-based.
> > > > struct wmi_driver is wmi_device based.
> > > >
> > > > The wmi_driver/wmi_device code essentially does nothing and is only used
> > > > to validate that a device is present.
> > > > The same can be done more easily wmi_has_guid().
> > > >
> > >
> >
> > Are you asking to replace all calls to wmi_evaluate_method() which is
> > UUID based API with calls to wmidev_evaluate_method() which is
> > wmi_device based? Correct?
>
> To be honest I'm not 100% sure.
>
> wmi_device is great and perferct for simple drivers binding to a single
> UUID.
>
> But it does not handle multi-UUID logic as your driver needs very well.
>
> I would argue to stick to the legacy calls as it allows you to drop a
> bunch of code and makes the initialization flow more straightforward.
>
> But I don't know if somebody else won't ask for wmi_device later.

I understand. I will keep the legacy code because the driver handles
multiple UUID logic.
Thank you for the clarification

2023-04-26 13:06:24

by Hans de Goede

[permalink] [raw]
Subject: Re: [PATCH v11 01/14] HP BIOSCFG driver - Documentation

Hi Jorge, Thomas,

Thank you both so much for all your work on this!

The userspace API of this looks like it is pretty much
done now (after the discussed changes for
the "Sure_Start" attribute), which is great.

I have one small remark below (inline).

On 4/20/23 18:54, Jorge Lopez wrote:

<snip>

> diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> index 4cdba3477176..73d7b8fbc0b2 100644
> --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
> +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> @@ -22,6 +22,12 @@ Description:
> - integer: a range of numerical values
> - string
>
> + HP specific types
> + -----------------
> + - ordered-list - a set of ordered list valid values
> + - sure-start - report audit logs read from BIOS
> +
> +
> All attribute types support the following values:
>
> current_value:
> @@ -126,6 +132,44 @@ Description:
> value will not be effective through sysfs until this rule is
> met.
>
> + HP specific class extensions
> + ------------------------------
> +
> + On HP systems the following additional attributes are available:
> +
> + "ordered-list"-type specific properties:
> +
> + elements:
> + A file that can be read to obtain the possible
> + list of values of the <attr>. Values are separated using
> + semi-colon (``;``). The order individual elements are listed
> + according to their priority. An Element listed first has the
> + highest priority. Writing the list in a different order to
> + current_value alters the priority order for the particular
> + attribute.
> +
> + "sure-start"-type specific properties:
> +
> + audit_log_entries:
> + A read-only file that returns the events in the log.
> + Values are separated using semi-colon (``;``)

Looking at the documented format which seems to be 128 raw bytes per entry, I think
that the "Values are separated using semi-colon (``;``)" line is not correct here
and that line should not removed here ?

But maybe I'm misunderstanding things here. Do you have an example
of what catting (or "hexdump -C"-ing if binary)
the "audit_log_entries" sysfs file looks like ?



> +
> + Audit log entry format
> +
> + Byte 0-15: Requested Audit Log entry (Each Audit log is 16 bytes)
> + Byte 16-127: Unused
> +
> + audit_log_entry_count:
> + A read-only file that returns the number of existing audit log events available to be read.
> + Values are separated using comma (``,``)
> +
> + [No of entries],[log entry size],[Max number of entries supported]
> +
> + log entry size identifies audit log size for the current BIOS version.
> + The current size is 16 bytes but it can be to up to 128 bytes long
> + in future BIOS versions.
> +
> +
> What: /sys/class/firmware-attributes/*/authentication/
> Date: February 2021
> KernelVersion: 5.11

<snip>

> @@ -311,7 +364,7 @@ Description:
> == =========================================
> 0 All BIOS attributes setting are current
> 1 A reboot is necessary to get pending BIOS
> - attribute changes applied
> + attribute changes applied
> == =========================================
>
> Note, userspace applications need to follow below steps for efficient

This seems like an unrelated whitespace change which
has accidentally ended up in this patch.

Regards,

Hans


p.s.

I'll also read / catch up with all the comments on the actual implementation
(patches 2-14) and I'll let you know if I have any remarks there.


2023-04-26 13:17:15

by Hans de Goede

[permalink] [raw]
Subject: Re: [PATCH v11 06/14] HP BIOSCFG driver - passwdobj-attributes

Hi,

On 4/23/23 11:07, [email protected] wrote:
> On 2023-04-20 11:54:46-0500, Jorge Lopez wrote:
>> ---
>> .../x86/hp/hp-bioscfg/passwdobj-attributes.c | 669 ++++++++++++++++++
>> 1 file changed, 669 insertions(+)
>> create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
>>
>> diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
>> new file mode 100644
>> index 000000000000..c03b3a71e9c4
>> --- /dev/null
>> +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c

<snip>

>> +static ssize_t current_password_store(struct kobject *kobj,
>> + struct kobj_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + char *p, *buf_cp;
>> + int id, ret = 0;
>> +
>> + buf_cp = kstrdup(buf, GFP_KERNEL);
>> + if (!buf_cp) {
>> + ret = -ENOMEM;
>> + goto exit_password;
>> + }
>> +
>> + p = memchr(buf_cp, '\n', count);
>> +
>> + if (p != NULL)
>> + *p = '\0';
>
> This will also accept input like "foo\nbar" and truncate away the "bar".

That is true, but stripping '\n' at the end is a pretty standard
pattern for sysfs attr store functions since users will e.g.
often do:

echo one-string-out-of-a-few-valid-strings > /sys/.../some-enum-attr

Where to actually write the real valid string the user should do:

echo -n one-string-out-of-a-few-valid-strings > /sys/.../some-enum-attr

See e.g.:

drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c new_password_store()

which does the exact same thing.

The stripping of '\n' is often taken care of by various kernel
helpers for sysfs attr.

> For something like a password it seems errorprone to try to munge the
> value.

Almost all password input dialogs including the actual BIOS password
input dialog will consider the enter key / a new-line to mean
"end-of-password, please validate the password inputted so far"

So I don't think this is really a problem. With that said we
could make this more robust (and maybe also change the Dell
code to match) by doing:

len = strlen(buf_cp);
if (len && buf_cp[len - 1] == '\n')
buf_cp[len - 1] = 0;

To ensure that we only ever strip a leading '\n'
and not one in the middle of the buffer.

Regards,

Hans




2023-04-27 22:20:44

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

On Sun, Apr 23, 2023 at 7:16 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-20 11:54:52-0500, Jorge Lopez wrote:
> > .../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
> > 1 file changed, 130 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> >
> > diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > new file mode 100644
> > index 000000000000..72952758ffe3
> > --- /dev/null
> > +++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > @@ -0,0 +1,130 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Functions corresponding to sure start object type attributes under
> > + * BIOS for use with hp-bioscfg driver
> > + *
> > + * Copyright (c) 2022 HP Development Company, L.P.
> > + */
> > +
> > +#include "bioscfg.h"
> > +#include <asm-generic/posix_types.h>
>
> Is the asm include needed?
> If yes, why not use linux/types.h?
>

Will change in Version 12

> > +
> > +#define LOG_MAX_ENTRIES 254
>
> A comment on how this values came to be would be good.
>

Done!

> > +#define LOG_ENTRY_SIZE 16
> > +
> > +/*
> > + * audit_log_entry_count_show - Reports the number of
> > + * existing audit log entries available
> > + * to be read
> > + */
> > +static ssize_t audit_log_entry_count_show(struct kobject *kobj,
> > + struct kobj_attribute *attr, char *buf)
> > +{
> > + int ret;
> > + u32 count = 0;
> > +
> > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
> > + HPWMI_SURESTART,
> > + &count, 1, sizeof(count));
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + return sysfs_emit(buf, "%d,%d,%d\n", count, LOG_ENTRY_SIZE,
> > + LOG_MAX_ENTRIES);
> > +}
> > +
> > +/*
> > + * audit_log_entries_show() - Return all entries found in log file
> > + */
> > +static ssize_t audit_log_entries_show(struct kobject *kobj,
> > + struct kobj_attribute *attr, char *buf)
> > +{
> > + int ret;
> > + int i;
> > + u32 count = 0;
> > +
> > + // Get the number of event logs
> > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
> > + HPWMI_SURESTART,
> > + &count, 1, sizeof(count));
> > +
> > + /*
> > + * The show() api will not work if the audit logs ever go
> > + * beyond 4KB
> > + */
> > + if (count * LOG_ENTRY_SIZE > PAGE_SIZE)
> > + return -EFAULT;
>
> The error code seems not to match.
>

Changing error to -EINVAL

> Instead of not returning any data, why not show as many results as
> possible?
>

if count * LOG_ENTRY_SIZE > PAGE_SIZE then I prefer to return an error.
if the count is correct but a failure occurs while reading individual
audit logs then we will return a partial list of all audit logs
This changes will be included in Version 12

> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + /*
> > + * We are guaranteed the buffer is 4KB so today all the event
> > + * logs will fit
> > + */
> > +
> > + for (i = 0; ((i < count) & (ret >= 0)); i++) {
>
> &&
>
> Better yet, pull the condition ret >= 0 into the body, as an else-branch
> for the existing check.
>

Done!

> > + *buf = (i + 1);
>
> Isn't this directly overwritten by the query below?

buf input value indicates the audit log to be read hence the reason
why it is overwritten.
This is an expected behavior.
>
> > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
> > + HPWMI_SURESTART,
> > + buf, 1, 128);
> > + if (ret >= 0)
> > + buf += LOG_ENTRY_SIZE;
>
> So 128 bytes are read but only the first 16 bytes are preserved?
>
> The documentation says that each entry has 128 bytes in the file.
> And that they are separated by ";", which is not implemented.

The statement will be removed from documentation (separated by ";")
audit log size is 16 bytes.
>
> Can the audit-log not contain all-zero bytes?
> If it does this would need to be a bin_attribute.

Bytes 16-127 are ignored and not used at this time. If the audit log
changes, then the driver will need to change to accommodate the new
audit log size.
The audit log file cannot contain all zero bytes.
>
> > + }
> > +
> > + return (count * LOG_ENTRY_SIZE);
>
> No need for braces.

Done!
>
<snip>

2023-04-28 06:10:48

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

On 2023-04-27 17:17:57-0500, Jorge Lopez wrote:
> On Sun, Apr 23, 2023 at 7:16 AM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-04-20 11:54:52-0500, Jorge Lopez wrote:
> > > .../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
> > > 1 file changed, 130 insertions(+)
> > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > >
> > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > new file mode 100644
> > > index 000000000000..72952758ffe3
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > @@ -0,0 +1,130 @@

<snip>

> > > +
> > > +/*
> > > + * audit_log_entries_show() - Return all entries found in log file
> > > + */
> > > +static ssize_t audit_log_entries_show(struct kobject *kobj,
> > > + struct kobj_attribute *attr, char *buf)
> > > +{
> > > + int ret;
> > > + int i;
> > > + u32 count = 0;
> > > +
> > > + // Get the number of event logs
> > > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
> > > + HPWMI_SURESTART,
> > > + &count, 1, sizeof(count));
> > > +
> > > + /*
> > > + * The show() api will not work if the audit logs ever go
> > > + * beyond 4KB
> > > + */
> > > + if (count * LOG_ENTRY_SIZE > PAGE_SIZE)
> > > + return -EFAULT;
> >
> > The error code seems not to match.
> >
>
> Changing error to -EINVAL

-EIO seems better.

The problem is not due to some value a user passed but an unhandled from
the hardware.

>
> > Instead of not returning any data, why not show as many results as
> > possible?
> >
>
> if count * LOG_ENTRY_SIZE > PAGE_SIZE then I prefer to return an error.
> if the count is correct but a failure occurs while reading individual
> audit logs then we will return a partial list of all audit logs
> This changes will be included in Version 12

What prevents the firmware from having more log entries?
Wouldn't these audit log entries not accumulate for each logged
operation over the lifetime of the device / boot?

This would make the interface unusable as soon as there are more
entries.

> > > +
> > > + if (ret < 0)
> > > + return ret;

And this should first validate ret and then count.

> > > +
> > > + /*
> > > + * We are guaranteed the buffer is 4KB so today all the event
> > > + * logs will fit
> > > + */
> > > +
> > > + for (i = 0; ((i < count) & (ret >= 0)); i++) {
> >
> > &&
> >
> > Better yet, pull the condition ret >= 0 into the body, as an else-branch
> > for the existing check.
> >
>
> Done!
>
> > > + *buf = (i + 1);
> >
> > Isn't this directly overwritten by the query below?
>
> buf input value indicates the audit log to be read hence the reason
> why it is overwritten.
> This is an expected behavior.

So this is read by the HPWMI_SURESTART_GET_LOG method in the firmware?

Make sense but needs a comment.

> >
> > > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
> > > + HPWMI_SURESTART,
> > > + buf, 1, 128);
> > > + if (ret >= 0)
> > > + buf += LOG_ENTRY_SIZE;
> >
> > So 128 bytes are read but only the first 16 bytes are preserved?
> >
> > The documentation says that each entry has 128 bytes in the file.
> > And that they are separated by ";", which is not implemented.
>
> The statement will be removed from documentation (separated by ";")
> audit log size is 16 bytes.
> >
> > Can the audit-log not contain all-zero bytes?
> > If it does this would need to be a bin_attribute.
>
> Bytes 16-127 are ignored and not used at this time. If the audit log
> changes, then the driver will need to change to accommodate the new
> audit log size.

buf is not guaranteed to have 128 bytes left for this data.

For example if this is entry number 253 we are at offset 253 * 16 = 4048
in the sysfs buffer. Now hw_wmi_perform_query may try to write to 4048 +
127 = 4175 which is out of bounds for the buf of size 4096.

Writing first to a stack buffer would be better,
or pass outsize = LOG_ENTRY_SIZE.

> The audit log file cannot contain all zero bytes.

I doublechecked this and zero bytes seem to also be fine in normal text
attributes.

> > > + return (count * LOG_ENTRY_SIZE);

If one of the calls to hp_wmi_perform_query() fails this return value is wrong,
it does not reflect the amount of actually written data.

2023-04-28 15:07:50

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

On Fri, Apr 28, 2023 at 1:03 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-27 17:17:57-0500, Jorge Lopez wrote:
> > On Sun, Apr 23, 2023 at 7:16 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-20 11:54:52-0500, Jorge Lopez wrote:
> > > > .../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
> > > > 1 file changed, 130 insertions(+)
> > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > >
> > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > new file mode 100644
> > > > index 000000000000..72952758ffe3
> > > > --- /dev/null
> > > > +++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > @@ -0,0 +1,130 @@
>
> <snip>
>
> > > > +
> > > > +/*
> > > > + * audit_log_entries_show() - Return all entries found in log file
> > > > + */
> > > > +static ssize_t audit_log_entries_show(struct kobject *kobj,
> > > > + struct kobj_attribute *attr, char *buf)
> > > > +{
> > > > + int ret;
> > > > + int i;
> > > > + u32 count = 0;
> > > > +
> > > > + // Get the number of event logs
> > > > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
> > > > + HPWMI_SURESTART,
> > > > + &count, 1, sizeof(count));
> > > > +
> > > > + /*
> > > > + * The show() api will not work if the audit logs ever go
> > > > + * beyond 4KB
> > > > + */
> > > > + if (count * LOG_ENTRY_SIZE > PAGE_SIZE)
> > > > + return -EFAULT;
> > >
> > > The error code seems not to match.
> > >
> >
> > Changing error to -EINVAL
>
> -EIO seems better.

Done!
>
> The problem is not due to some value a user passed but an unhandled from
> the hardware.
>
> >
> > > Instead of not returning any data, why not show as many results as
> > > possible?
> > >
> >
> > if count * LOG_ENTRY_SIZE > PAGE_SIZE then I prefer to return an error.
> > if the count is correct but a failure occurs while reading individual
> > audit logs then we will return a partial list of all audit logs
> > This changes will be included in Version 12
>
> What prevents the firmware from having more log entries?
> Wouldn't these audit log entries not accumulate for each logged
> operation over the lifetime of the device / boot?
>
> This would make the interface unusable as soon as there are more
> entries.

BIOS stores a max number of audit logs appropriate to the current
audit log size.The first audit logs are kept in a FIFO queue by BIOS
so when the queue is full and a new audit log arrives, then the first
audit log will be deleted.

>
> > > > +
> > > > + if (ret < 0)
> > > > + return ret;
>
> And this should first validate ret and then count.

Done!

>
> > > > +
> > > > + /*
> > > > + * We are guaranteed the buffer is 4KB so today all the event
> > > > + * logs will fit
> > > > + */
> > > > +
> > > > + for (i = 0; ((i < count) & (ret >= 0)); i++) {
> > >
> > > &&
> > >
> > > Better yet, pull the condition ret >= 0 into the body, as an else-branch
> > > for the existing check.
> > >
> >
> > Done!
> >
> > > > + *buf = (i + 1);
> > >
> > > Isn't this directly overwritten by the query below?
> >
> > buf input value indicates the audit log to be read hence the reason
> > why it is overwritten.
> > This is an expected behavior.
>
> So this is read by the HPWMI_SURESTART_GET_LOG method in the firmware?
>
> Make sense but need a comment.

Done!

>
> > >
> > > > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
> > > > + HPWMI_SURESTART,
> > > > + buf, 1, 128);
> > > > + if (ret >= 0)
> > > > + buf += LOG_ENTRY_SIZE;
> > >
> > > So 128 bytes are read but only the first 16 bytes are preserved?
> > >
> > > The documentation says that each entry has 128 bytes in the file.
> > > And that they are separated by ";", which is not implemented.
> >
> > The statement will be removed from documentation (separated by ";")
> > audit log size is 16 bytes.
> > >
> > > Can the audit-log not contain all-zero bytes?
> > > If it does this would need to be a bin_attribute.
> >
> > Bytes 16-127 are ignored and not used at this time. If the audit log
> > changes, then the driver will need to change to accommodate the new
> > audit log size.
>
> buf is not guaranteed to have 128 bytes left for this data.
>
> For example if this is entry number 253 we are at offset 253 * 16 = 4048
> in the sysfs buffer. Now hw_wmi_perform_query may try to write to 4048 +
> 127 = 4175 which is out of bounds for the buf of size 4096.
>
> Writing first to a stack buffer would be better,
> or pass outsize = LOG_ENTRY_SIZE.
>
BIOS currently stores 16 bytes for each audit log although the WMI
query reads 128 bytes. The 128 bytes size is set to provide support
in future BIOS for audit log sizes >= 16 and < 128 bytes.

> > The audit log file cannot contain all zero bytes.
>
> I doublechecked this and zero bytes seem to also be fine in normal text
> attributes.
>
> > > > + return (count * LOG_ENTRY_SIZE);
>
> If one of the calls to hp_wmi_perform_query() fails this return value is wrong,
> it does not reflect the amount of actually written data.

Version 12 of the code takes such a condition in consideration and
recalculates the value of 'count' to match the one number of valid
audit logs read.

2023-04-28 15:23:39

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

On 2023-04-28 09:58:01-0500, Jorge Lopez wrote:
> On Fri, Apr 28, 2023 at 1:03 AM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-04-27 17:17:57-0500, Jorge Lopez wrote:
> > > On Sun, Apr 23, 2023 at 7:16 AM Thomas Weißschuh <[email protected]> wrote:
> > > >
> > > > On 2023-04-20 11:54:52-0500, Jorge Lopez wrote:
> > > > > .../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
> > > > > 1 file changed, 130 insertions(+)
> > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > >
> > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > > new file mode 100644

<snip>

> > > > Instead of not returning any data, why not show as many results as
> > > > possible?
> > > >
> > >
> > > if count * LOG_ENTRY_SIZE > PAGE_SIZE then I prefer to return an error.
> > > if the count is correct but a failure occurs while reading individual
> > > audit logs then we will return a partial list of all audit logs
> > > This changes will be included in Version 12
> >
> > What prevents the firmware from having more log entries?
> > Wouldn't these audit log entries not accumulate for each logged
> > operation over the lifetime of the device / boot?
> >
> > This would make the interface unusable as soon as there are more
> > entries.
>
> BIOS stores a max number of audit logs appropriate to the current
> audit log size.The first audit logs are kept in a FIFO queue by BIOS
> so when the queue is full and a new audit log arrives, then the first
> audit log will be deleted.

How does it determine "appropriate"?
This would also be great in a comment.

If the BIOS is just using FIFO the driver could return the first
LOG_MAX_ENTRIES entries.
This would avoid trusting the firmware for a reasonable definition of
"appropriate".

> >
> > > > > +
> > > > > + if (ret < 0)
> > > > > + return ret;
> >
> > And this should first validate ret and then count.
>
> Done!
>
> >
> > > > > +
> > > > > + /*
> > > > > + * We are guaranteed the buffer is 4KB so today all the event
> > > > > + * logs will fit
> > > > > + */
> > > > > +
> > > > > + for (i = 0; ((i < count) & (ret >= 0)); i++) {
> > > >
> > > > &&
> > > >
> > > > Better yet, pull the condition ret >= 0 into the body, as an else-branch
> > > > for the existing check.
> > > >
> > >
> > > Done!
> > >
> > > > > + *buf = (i + 1);
> > > >
> > > > Isn't this directly overwritten by the query below?
> > >
> > > buf input value indicates the audit log to be read hence the reason
> > > why it is overwritten.
> > > This is an expected behavior.
> >
> > So this is read by the HPWMI_SURESTART_GET_LOG method in the firmware?
> >
> > Make sense but need a comment.
>
> Done!
>
> >
> > > >
> > > > > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
> > > > > + HPWMI_SURESTART,
> > > > > + buf, 1, 128);
> > > > > + if (ret >= 0)
> > > > > + buf += LOG_ENTRY_SIZE;
> > > >
> > > > So 128 bytes are read but only the first 16 bytes are preserved?
> > > >
> > > > The documentation says that each entry has 128 bytes in the file.
> > > > And that they are separated by ";", which is not implemented.
> > >
> > > The statement will be removed from documentation (separated by ";")
> > > audit log size is 16 bytes.
> > > >
> > > > Can the audit-log not contain all-zero bytes?
> > > > If it does this would need to be a bin_attribute.
> > >
> > > Bytes 16-127 are ignored and not used at this time. If the audit log
> > > changes, then the driver will need to change to accommodate the new
> > > audit log size.
> >
> > buf is not guaranteed to have 128 bytes left for this data.
> >
> > For example if this is entry number 253 we are at offset 253 * 16 = 4048
> > in the sysfs buffer. Now hw_wmi_perform_query may try to write to 4048 +
> > 127 = 4175 which is out of bounds for the buf of size 4096.
> >
> > Writing first to a stack buffer would be better,
> > or pass outsize = LOG_ENTRY_SIZE.
> >
> BIOS currently stores 16 bytes for each audit log although the WMI
> query reads 128 bytes. The 128 bytes size is set to provide support
> in future BIOS for audit log sizes >= 16 and < 128 bytes.

And if an old driver is running on a new BIOS then this would write out
of bounds.
Or if the BIOS is buggy.

If the current driver can only handle 16 byte sized log entries then the
this should be used in the call to HPWMI_SURESTART_GET_LOG.

Storing it in a 128 byte stackvariable would also sidestep the issue.

2023-04-28 15:27:22

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 08/14] HP BIOSCFG driver - bioscfg-h

On Sun, Apr 23, 2023 at 7:01 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-20 11:54:48-0500, Jorge Lopez wrote:
> > ---
> > drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++++++++++
> > 1 file changed, 613 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h

<snip>
> > +enum hp_wmi_spm_commandtype {
> > + HPWMI_SECUREPLATFORM_GET_STATE = 0x10,
> > + HPWMI_SECUREPLATFORM_SET_KEK = 0x11,
> > + HPWMI_SECUREPLATFORM_SET_SK = 0x12
> > +};
> > +
> > +enum hp_wmi_surestart_commandtype {
> > + HPWMI_SURESTART_GET_LOG_COUNT = 0x01,
> > + HPWMI_SURESTART_GET_LOG = 0x02
> > +};
> > +
> > +enum hp_wmi_command {
> > + HPWMI_READ = 0x01,
> > + HPWMI_WRITE = 0x02,
> > + HPWMI_ODM = 0x03,
> > + HPWMI_SURESTART = 0x20006,
> > + HPWMI_GM = 0x20008,
> > + HPWMI_SECUREPLATFORM = 0x20010
> > +};
> > +
> > +struct bios_return {
> > + u32 sigpass;
> > + u32 return_code;
> > +};
> > +
> > +enum hp_return_value {
> > + HPWMI_RET_WRONG_SIGNATURE = 0x02,
> > + HPWMI_RET_UNKNOWN_COMMAND = 0x03,
> > + HPWMI_RET_UNKNOWN_CMDTYPE = 0x04,
> > + HPWMI_RET_INVALID_PARAMETERS = 0x05
> > +};
>
> This seems to be same as wmi_error_values below.

Done! Deleted enum hp_return_value
>
> > +
> > +enum wmi_error_values {
> > + SUCCESS = 0x00,
> > + CMD_FAILED = 0x01,
> > + INVALID_SIGN = 0x02,
> > + INVALID_CMD_VALUE = 0x03,
> > + INVALID_CMD_TYPE = 0x04,
> > + INVALID_DATA_SIZE = 0x05,
> > + INVALID_CMD_PARAM = 0x06,
> > + ENCRYP_CMD_REQUIRED = 0x07,
> > + NO_SECURE_SESSION = 0x08,
> > + SECURE_SESSION_FOUND = 0x09,
> > + SECURE_SESSION_FAILED = 0x0A,
> > + AUTH_FAILED = 0x0B,
> > + INVALID_BIOS_AUTH = 0x0E,
> > + NONCE_DID_NOT_MATCH = 0x18,
> > + GENERIC_ERROR = 0x1C,
> > + BIOS_ADMIN_POLICY_NOT_MET = 0x28,
> > + BIOS_ADMIN_NOT_SET = 0x38,
> > + P21_NO_PROVISIONED = 0x1000,
> > + P21_PROVISION_IN_PROGRESS = 0x1001,
> > + P21_IN_USE = 0x1002,
> > + HEP_NOT_ACTIVE = 0x1004,
> > + HEP_ALREADY_SET = 0x1006,
> > + HEP_CHECK_STATE = 0x1007
> > +};
> > +
> > +enum spm_features {
> > + HEP_ENABLED = 0x01,
> > + PLATFORM_RECOVERY = 0x02,
> > + ENHANCED_BIOS_AUTH_MODE = 0x04
>
> Trailing commas please everywhere.

Done! Added all missing trailing commas across the file.
>
> > +};
> > +
> > +#define MAX_KEK_BLOB_SIZE 4160
> > +#define MAX_SK_BLOB_SIZE 516
>
> These are unused.

Done! deleted
>
> > +
> > +enum spm_states_values {
> > + NOT_PROVISIONED = 0x00,
> > + PROVISIONED = 0x01,
> > + PROVISIONING_IN_PROGRESS = 0x02
> > +};
> > +
> > +
> > +
> > +/*
> > + * struct bios_args buffer is dynamically allocated. New WMI command types
> > + * were introduced that exceed 128-byte data size. Changes to handle
> > + * the data size allocation scheme were kept in hp_wmi_perform_qurey function.
> > + */
> > +struct bios_args {
> > + u32 signature;
> > + u32 command;
> > + u32 commandtype;
> > + u32 datasize;
> > + u8 data[];
> > +};
> > +
> > +struct secureplatform_provisioning_data {
> > + u8 state;
> > + u8 version[2];
> > + u8 reserved1;
> > + u32 features;
> > + u32 nonce;
> > + u8 reserved2[28];
> > + u8 sk_mod[MAX_KEY_MOD];
> > + u8 kek_mod[MAX_KEY_MOD];
> > +};
>
> bios_args and secureplatform_provisioning_data are only used from a
> single .c file. There is no need to define them in the shared header.
>

Done! bios_args and secureplatform_provisioning_dataStructures move
to .c file where they are used.

> > +
> > +struct common_data {
> > + u8 display_name[MAX_BUFF];
> > + u8 path[MAX_BUFF];
> > + u32 is_readonly;
> > + u32 display_in_ui;
> > + u32 requires_physical_presence;
> > + u32 sequence;
> > + u32 prerequisites_size;
> > + u8 prerequisites[MAX_PREREQUISITES_SIZE][MAX_BUFF];
> > + u32 security_level;
> > + u8 display_name_language_code[MAX_BUFF];
> > +};
> > +
> > +
> > +struct string_data {
> > + struct kobject *attr_name_kobj;
> > + u8 current_value[MAX_BUFF];
> > + u8 new_value[MAX_BUFF];
> > + u32 min_length;
> > + u32 max_length;
> > + struct common_data common;
>
> It would be nicer to read if the common_data was at the start of the
> struct.

Agree. Done!

<snip>

> > +/* global structure used by multiple WMI interfaces */
> > +extern struct bioscfg_priv bioscfg_drv;
> > +
> > +enum hp_wmi_data_type {
> > + HPWMI_STRING_TYPE = 0x00,
> > + HPWMI_INTEGER_TYPE = 0x01,
> > + HPWMI_ENUMERATION_TYPE = 0x02,
> > + HPWMI_ORDERED_LIST_TYPE = 0x03,
> > + HPWMI_PASSWORD_TYPE = 0x04,
> > + HPWMI_SECURE_PLATFORM_TYPE = 0x05,
> > + HPWMI_SURE_START_TYPE = 0x06
> > +};
>
> Unused.

Both hp_wmi_data_type and hp_wmi_data_elements are used
for instance HP_WMI_STRING_TYPE

bioscfg.c:338: case HPWMI_STRING_TYPE:
bioscfg.c:626: case HPWMI_STRING_TYPE:
bioscfg.c:722: case HPWMI_STRING_TYPE:
bioscfg.c:798: case HPWMI_STRING_TYPE:
bioscfg.c:906: ret = hp_init_bios_attributes(HPWMI_STRING_TYPE,
HP_WMI_BIOS_STRING_GUID);
bioscfg.h:247: HPWMI_STRING_TYPE
>
> > +
> > +enum hp_wmi_data_elements {
> > +
> > + /* Common elements */
> > + NAME = 0,
> > + VALUE = 1,
> > + PATH = 2,
> > + IS_READONLY = 3,
> > + DISPLAY_IN_UI = 4,
> > + REQUIRES_PHYSICAL_PRESENCE = 5,
> > + SEQUENCE = 6,
> > + PREREQUISITES_SIZE = 7,
> > + PREREQUISITES = 8,
> > + SECURITY_LEVEL = 9,
> > +
> > + /* String elements */
> > + STR_MIN_LENGTH = 10,
> > + STR_MAX_LENGTH = 11,
> > +
> > + /* Integer elements */
> > + INT_LOWER_BOUND = 10,
> > + INT_UPPER_BOUND = 11,
> > + INT_SCALAR_INCREMENT = 12,
> > +
> > + /* Enumeration elements */
> > + ENUM_CURRENT_VALUE = 10,
> > + ENUM_SIZE = 11,
> > + ENUM_POSSIBLE_VALUES = 12,
> > +
> > + /* Ordered list elements */
> > + ORD_LIST_SIZE = 10,
> > + ORD_LIST_ELEMENTS = 11,
> > +
> > + /* Password elements */
> > + PSWD_MIN_LENGTH = 10,
> > + PSWD_MAX_LENGTH = 11,
> > + PSWD_SIZE = 12,
> > + PSWD_ENCODINGS = 13,
> > + PSWD_IS_SET = 14
> > +};
> > +
> > +
> > +enum hp_wmi_elements_count {
> > + STRING_ELEM_CNT = 12,
> > + INTEGER_ELEM_CNT = 13,
> > + ENUM_ELEM_CNT = 13,
> > + ORDERED_ELEM_CNT = 12,
> > + PASSWORD_ELEM_CNT = 15
> > +};
>
> To make it clearer where these values come from you could put them into
> the enum hp_wmi_data_elements.
>
> ...
> ORD_LIST_ELEMENTS = 11,
> ORD_LIST_ELEM_CNT = 12,
> ...

Done! changes provided across all files affected.

>
> But replacing the loop logic would remove the need for these enums
> completely.
>

_CNT values are necessary when elements are read from a buffer (
populate_string_elements_from_buffer).
_CNT values are not needed when elements are read from a package
(populate_string_package_data)

> > +
> > +#define GET_INSTANCE_ID(type) \
> > + static int get_##type##_instance_id(struct kobject *kobj) \
> > + { \
> > + int i; \
> > + \
> > + for (i = 0; i <= bioscfg_drv.type##_instances_count; i++) { \
> > + if (!(strcmp(kobj->name, bioscfg_drv.type##_data[i].attr_name_kobj->name))) \
>
> No need for braces after "!".

Done!
>
> > + return i; \
> > + } \
> > + return -EIO; \
> > + }
> > +
> > +#define ATTRIBUTE_S_PROPERTY_SHOW(name, type) \
> > + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
> > + char *buf) \
> > + { \
> > + int i = get_##type##_instance_id(kobj); \
> > + if (i >= 0) \
> > + return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].name); \
> > + return -EIO; \
> > + }
> > +
> > +#define ATTRIBUTE_N_PROPERTY_SHOW(name, type) \
> > + static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
> > + char *buf) \
> > + { \
> > + int i = get_##type##_instance_id(kobj); \
> > + if (i >= 0) \
> > + return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data[i].name); \
> > + return -EIO; \
> > + }
> > +
> > +
> > +#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
> > + static ssize_t curr_val##_store(struct kobject *kobj, \
> > + struct kobj_attribute *attr, \
> > + const char *buf, size_t count) \
> > + { \
> > + char *p = NULL; \
> > + char *attr_value = NULL; \
> > + int i; \
> > + int ret = -EIO; \
> > + \
> > + attr_value = kstrdup(buf, GFP_KERNEL); \
> > + if (!attr_value) \
> > + return -ENOMEM; \
> > + \
> > + p = memchr(attr_value, '\n', count); \
> > + if (p != NULL) \
> > + *p = '\0'; \
>
> This can also truncate the string if there is data after the newline.

This is a expected behavior as described by Hans in a later email

<snip>
> > +
> > +#define ATTRIBUTE_V_COMMON_PROPERTY_SHOW(name, type) \
> > + static ssize_t name##_show(struct kobject *kobj, \
> > + struct kobj_attribute *attr, char *buf) \
> > + { \
> > + int i; \
> > + int len = 0; \
> > + int instance_id = get_##type##_instance_id(kobj); \
> > + \
> > + if (instance_id < 0) \
> > + return 0; \
> > + \
> > + for (i = 0; i < bioscfg_drv.type##_data[instance_id].common.name##_size; i++) { \
> > + if (i) \
> > + len += sysfs_emit_at(buf, len, "%s", ";"); \
>
> No need for the "%s" here.

Done!

<snip>

> > + * Prototypes
> > + */
> > +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string);
> > +int get_instance_count(const char *guid_string);
> > +void update_attribute_permissions(u32 isReadOnly, struct kobj_attribute *current_val);
> > +void friendly_user_name_update(char *path, const char *attr_name,
> > + char *attr_display, int attr_size);
> > +int bioscfg_wmi_error_and_message(int error_code);
> > +
> > +/* String attributes */
> > +int populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +
> > +int populate_string_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> > + int instance_id);
> > +
> > +//enum hp_wmi_data_type type);
> > +int alloc_string_data(void);
> > +void exit_string_attributes(void);
> > +int populate_string_package_data(union acpi_object *str_obj,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +int populate_string_elements_from_package(union acpi_object *str_obj,
> > + int str_obj_count,
> > + int instance_id);
> > +
> > +/* Integer attributes */
> > +int populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +int populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> > + int instance_id);
> > +int alloc_integer_data(void);
> > +void exit_integer_attributes(void);
> > +int populate_integer_package_data(union acpi_object *integer_obj,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +int populate_integer_elements_from_package(union acpi_object *integer_obj,
> > + int integer_obj_count,
> > + int instance_id);
> > +
> > +/* Enumeration attributes */
> > +int populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +int populate_enumeration_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> > + int instance_id);
> > +int alloc_enumeration_data(void);
> > +void exit_enumeration_attributes(void);
> > +int populate_enumeration_package_data(union acpi_object *enum_obj,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +int populate_enumeration_elements_from_package(union acpi_object *enum_obj,
> > + int enum_obj_count,
> > + int instance_id);
> > +
> > +/* Ordered list */
> > +int populate_ordered_list_buffer_data(u8 *buffer_ptr,
> > + u32 *buffer_size,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +int populate_ordered_list_elements_from_buffer(u8 *buffer_ptr,
> > + u32 *buffer_size,
> > + int instance_id);
> > +int alloc_ordered_list_data(void);
> > +void exit_ordered_list_attributes(void);
> > +int populate_ordered_list_package_data(union acpi_object *order_obj,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +int populate_ordered_list_elements_from_package(union acpi_object *order_obj,
> > + int order_obj_count,
> > + int instance_id);
>
> There are a lot of these per-type setup and teardown functions.
> They are cluttering the API and need a lot of code when called.
>
> Instead there could be a struct with function pointers:
>
> struct type_ops {
> populate_data;
> from_buffer;
> alloc;
> exit;
> ...
> }
>
> const struct string_ops {
> .alloc = alloc_string_data;
> ...
> };
>
> const struct type_ops[] = {
> &string_ops,
> ...
> };
>
> And then the global setup code can just loop over these structs.

package or buffer data are exposed by BIOS but never both. Agree, the
new interface is an improvement and we will refactor the driver after
initial release upstream.
>
> > +
> > +/* Password authentication attributes */
> > +int populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +int populate_password_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> > + int instance_id);
> > +int populate_password_package_data(union acpi_object *password_obj,
> > + int instance_id,
> > + struct kobject *attr_name_kobj);
> > +int populate_password_elements_from_package(union acpi_object *password_obj,
> > + int password_obj_count,
> > + int instance_id);
> > +int alloc_password_data(void);
> > +int alloc_secure_platform_data(void);
> > +void exit_password_attributes(void);
> > +void exit_secure_platform_attributes(void);
> > +int populate_secure_platform_data(struct kobject *attr_name_kobj);
> > +int password_is_set(const char *auth);
> > +int check_spm_is_enabled(void);
> > +int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size);
> > +int hp_wmi_perform_query(int query, enum hp_wmi_command command,
> > + void *buffer, int insize, int outsize);
> > +int validate_password_input(int instance_id, const char *buf);
> > +
> > +/* Sure Start attributes */
> > +void exit_sure_start_attributes(void);
> > +int populate_sure_start_data(struct kobject *attr_name_kobj);
> > +
> > +int set_bios_defaults(u8 defType);
> > +int get_password_instance_for_type(const char *name);
> > +int clear_all_credentials(void);
> > +int clear_passwords(const int instance);
> > +void exit_bios_attr_set_interface(void);
> > +int init_bios_attr_set_interface(void);
> > +size_t bioscfg_calculate_string_buffer(const char *str);
> > +size_t calculate_security_buffer(const char *authentication);
> > +void populate_security_buffer(u16 *buffer, const char *authentication);
> > +int set_new_password(const char *password_type, const char *new_password);
> > +int init_bios_attr_pass_interface(void);
> > +void exit_bios_attr_pass_interface(void);
> > +void *ascii_to_utf16_unicode(u16 *p, const u8 *str);
> > +int get_integer_from_buffer(int **buffer, u32 *buffer_size, int *integer);
> > +int get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size);
> > +int convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len);
> > +int encode_outsize_for_pvsz(int outsize);
> > +int hp_set_attribute(const char *a_name, const char *a_value);
> > +
> > +/* SPM Attributes */
> > +ssize_t update_spm_state(void);
> > +ssize_t statusbin(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
> > +ssize_t statusbin_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
> > +ssize_t status_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
>
> None of these SPM functions are used outside of their compilation
> units. They should be static and removed from the header.
>
> Please also validate all the other non-static symbols, enums and
> #defines.

Done!
>
> > +
> > +#endif
> > --
> > 2.34.1
> >

2023-04-28 15:38:10

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 08/14] HP BIOSCFG driver - bioscfg-h

On 2023-04-28 10:24:40-0500, Jorge Lopez wrote:
> On Sun, Apr 23, 2023 at 7:01 AM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-04-20 11:54:48-0500, Jorge Lopez wrote:
> > > ---
> > > drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++++++++++
> > > 1 file changed, 613 insertions(+)
> > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h

<snip>

> > > +/* global structure used by multiple WMI interfaces */
> > > +extern struct bioscfg_priv bioscfg_drv;
> > > +
> > > +enum hp_wmi_data_type {
> > > + HPWMI_STRING_TYPE = 0x00,
> > > + HPWMI_INTEGER_TYPE = 0x01,
> > > + HPWMI_ENUMERATION_TYPE = 0x02,
> > > + HPWMI_ORDERED_LIST_TYPE = 0x03,
> > > + HPWMI_PASSWORD_TYPE = 0x04,
> > > + HPWMI_SECURE_PLATFORM_TYPE = 0x05,
> > > + HPWMI_SURE_START_TYPE = 0x06
> > > +};
> >
> > Unused.
>
> Both hp_wmi_data_type and hp_wmi_data_elements are used
> for instance HP_WMI_STRING_TYPE
>
> bioscfg.c:338: case HPWMI_STRING_TYPE:
> bioscfg.c:626: case HPWMI_STRING_TYPE:
> bioscfg.c:722: case HPWMI_STRING_TYPE:
> bioscfg.c:798: case HPWMI_STRING_TYPE:
> bioscfg.c:906: ret = hp_init_bios_attributes(HPWMI_STRING_TYPE,
> HP_WMI_BIOS_STRING_GUID);
> bioscfg.h:247: HPWMI_STRING_TYPE

Indeed. I think I just searched for "hp_wmi_data_type".

The proper enum hp_wmi_data_type type should be used instead of
"int attr_type".

<snip>

> > > +
> > > +enum hp_wmi_elements_count {
> > > + STRING_ELEM_CNT = 12,
> > > + INTEGER_ELEM_CNT = 13,
> > > + ENUM_ELEM_CNT = 13,
> > > + ORDERED_ELEM_CNT = 12,
> > > + PASSWORD_ELEM_CNT = 15
> > > +};
> >
> > To make it clearer where these values come from you could put them into
> > the enum hp_wmi_data_elements.
> >
> > ...
> > ORD_LIST_ELEMENTS = 11,
> > ORD_LIST_ELEM_CNT = 12,
> > ...
>
> Done! changes provided across all files affected.
>
> >
> > But replacing the loop logic would remove the need for these enums
> > completely.
> >
>
> _CNT values are necessary when elements are read from a buffer (
> populate_string_elements_from_buffer).
> _CNT values are not needed when elements are read from a package
> (populate_string_package_data)

Hm, I don't see why populate_string_elements_from_buffer() would need
the _CNT define.

(In another review mail I wrote down how I would expect it to look
without the loop)

<snip>

> > > +
> > > +#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
> > > + static ssize_t curr_val##_store(struct kobject *kobj, \
> > > + struct kobj_attribute *attr, \
> > > + const char *buf, size_t count) \
> > > + { \
> > > + char *p = NULL; \
> > > + char *attr_value = NULL; \
> > > + int i; \
> > > + int ret = -EIO; \
> > > + \
> > > + attr_value = kstrdup(buf, GFP_KERNEL); \
> > > + if (!attr_value) \
> > > + return -ENOMEM; \
> > > + \
> > > + p = memchr(attr_value, '\n', count); \
> > > + if (p != NULL) \
> > > + *p = '\0'; \
> >
> > This can also truncate the string if there is data after the newline.
>
> This is a expected behavior as described by Hans in a later email

I'm fine with stripping a trailing newline.

But this truncates the string at the first newline.

"foo\nbar" -> "foo"
"\nfoo" -> ""

<snip>

2023-04-28 15:46:00

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

On Fri, Apr 28, 2023 at 10:21 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-28 09:58:01-0500, Jorge Lopez wrote:
> > On Fri, Apr 28, 2023 at 1:03 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-27 17:17:57-0500, Jorge Lopez wrote:
> > > > On Sun, Apr 23, 2023 at 7:16 AM Thomas Weißschuh <[email protected]> wrote:
> > > > >
> > > > > On 2023-04-20 11:54:52-0500, Jorge Lopez wrote:
> > > > > > .../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
> > > > > > 1 file changed, 130 insertions(+)
> > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > > >
> > > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > > > new file mode 100644
>
> <snip>
>
> > > > > Instead of not returning any data, why not show as many results as
> > > > > possible?
> > > > >
> > > >
> > > > if count * LOG_ENTRY_SIZE > PAGE_SIZE then I prefer to return an error.
> > > > if the count is correct but a failure occurs while reading individual
> > > > audit logs then we will return a partial list of all audit logs
> > > > This changes will be included in Version 12
> > >
> > > What prevents the firmware from having more log entries?
> > > Wouldn't these audit log entries not accumulate for each logged
> > > operation over the lifetime of the device / boot?
> > >
> > > This would make the interface unusable as soon as there are more
> > > entries.
> >
> > BIOS stores a max number of audit logs appropriate to the current
> > audit log size.The first audit logs are kept in a FIFO queue by BIOS
> > so when the queue is full and a new audit log arrives, then the first
> > audit log will be deleted.
>
> How does it determine "appropriate"?
> This would also be great in a comment.
>
> If the BIOS is just using FIFO the driver could return the first
> LOG_MAX_ENTRIES entries.
> This would avoid trusting the firmware for a reasonable definition of
> "appropriate".
>
> > >
> > > > > > +
> > > > > > + if (ret < 0)
> > > > > > + return ret;
> > >
> > > And this should first validate ret and then count.
> >
> > Done!
> >
> > >
> > > > > > +
> > > > > > + /*
> > > > > > + * We are guaranteed the buffer is 4KB so today all the event
> > > > > > + * logs will fit
> > > > > > + */
> > > > > > +
> > > > > > + for (i = 0; ((i < count) & (ret >= 0)); i++) {
> > > > >
> > > > > &&
> > > > >
> > > > > Better yet, pull the condition ret >= 0 into the body, as an else-branch
> > > > > for the existing check.
> > > > >
> > > >
> > > > Done!
> > > >
> > > > > > + *buf = (i + 1);
> > > > >
> > > > > Isn't this directly overwritten by the query below?
> > > >
> > > > buf input value indicates the audit log to be read hence the reason
> > > > why it is overwritten.
> > > > This is an expected behavior.
> > >
> > > So this is read by the HPWMI_SURESTART_GET_LOG method in the firmware?
> > >
> > > Make sense but need a comment.
> >
> > Done!
> >
> > >
> > > > >
> > > > > > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
> > > > > > + HPWMI_SURESTART,
> > > > > > + buf, 1, 128);
> > > > > > + if (ret >= 0)
> > > > > > + buf += LOG_ENTRY_SIZE;
> > > > >
> > > > > So 128 bytes are read but only the first 16 bytes are preserved?
> > > > >
> > > > > The documentation says that each entry has 128 bytes in the file.
> > > > > And that they are separated by ";", which is not implemented.
> > > >
> > > > The statement will be removed from documentation (separated by ";")
> > > > audit log size is 16 bytes.
> > > > >
> > > > > Can the audit-log not contain all-zero bytes?
> > > > > If it does this would need to be a bin_attribute.
> > > >
> > > > Bytes 16-127 are ignored and not used at this time. If the audit log
> > > > changes, then the driver will need to change to accommodate the new
> > > > audit log size.
> > >
> > > buf is not guaranteed to have 128 bytes left for this data.
> > >
> > > For example if this is entry number 253 we are at offset 253 * 16 = 4048
> > > in the sysfs buffer. Now hw_wmi_perform_query may try to write to 4048 +
> > > 127 = 4175 which is out of bounds for the buf of size 4096.
> > >
> > > Writing first to a stack buffer would be better,
> > > or pass outsize = LOG_ENTRY_SIZE.
> > >
> > BIOS currently stores 16 bytes for each audit log although the WMI
> > query reads 128 bytes. The 128 bytes size is set to provide support
> > in future BIOS for audit log sizes >= 16 and < 128 bytes.
>
> And if an old driver is running on a new BIOS then this would write out
> of bounds.
> Or if the BIOS is buggy.
>
> If the current driver can only handle 16 byte sized log entries then the
> this should be used in the call to HPWMI_SURESTART_GET_LOG.

BIOS WMI specification indicates that the HPWMI_SURESTART_GET_LOG call
expects a 128 byte size output buffer regardless of the actual audit
log size currently supported.

Return Values:
Byte 0-15: a requested Audit Log entry (Each Audit log is 16 bytes)
Byte 16-127: Unused
>
> Storing it in a 128 byte stackvariable would also sidestep the issue.

The driver hardcodes the audit log size to 16 bytes. If the new BIOS
provides an audit log that is larger than 16 bytes, then the logs
provided to the user application by the old driver will be truncated.

2023-04-28 16:07:21

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 08/14] HP BIOSCFG driver - bioscfg-h

On Fri, Apr 28, 2023 at 10:36 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-28 10:24:40-0500, Jorge Lopez wrote:
> > On Sun, Apr 23, 2023 at 7:01 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-20 11:54:48-0500, Jorge Lopez wrote:
> > > > ---
> > > > drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++++++++++
> > > > 1 file changed, 613 insertions(+)
> > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
>
> <snip>
>
> > > > +/* global structure used by multiple WMI interfaces */
> > > > +extern struct bioscfg_priv bioscfg_drv;
> > > > +
> > > > +enum hp_wmi_data_type {
> > > > + HPWMI_STRING_TYPE = 0x00,
> > > > + HPWMI_INTEGER_TYPE = 0x01,
> > > > + HPWMI_ENUMERATION_TYPE = 0x02,
> > > > + HPWMI_ORDERED_LIST_TYPE = 0x03,
> > > > + HPWMI_PASSWORD_TYPE = 0x04,
> > > > + HPWMI_SECURE_PLATFORM_TYPE = 0x05,
> > > > + HPWMI_SURE_START_TYPE = 0x06
> > > > +};
> > >
> > > Unused.
> >
> > Both hp_wmi_data_type and hp_wmi_data_elements are used
> > for instance HP_WMI_STRING_TYPE
> >
> > bioscfg.c:338: case HPWMI_STRING_TYPE:
> > bioscfg.c:626: case HPWMI_STRING_TYPE:
> > bioscfg.c:722: case HPWMI_STRING_TYPE:
> > bioscfg.c:798: case HPWMI_STRING_TYPE:
> > bioscfg.c:906: ret = hp_init_bios_attributes(HPWMI_STRING_TYPE,
> > HP_WMI_BIOS_STRING_GUID);
> > bioscfg.h:247: HPWMI_STRING_TYPE
>
> Indeed. I think I just searched for "hp_wmi_data_type".
>
> The proper enum hp_wmi_data_type type should be used instead of
> "int attr_type".
>

Done!

> <snip>
>
> > > > +
> > > > +enum hp_wmi_elements_count {
> > > > + STRING_ELEM_CNT = 12,
> > > > + INTEGER_ELEM_CNT = 13,
> > > > + ENUM_ELEM_CNT = 13,
> > > > + ORDERED_ELEM_CNT = 12,
> > > > + PASSWORD_ELEM_CNT = 15
> > > > +};
> > >
> > > To make it clearer where these values come from you could put them into
> > > the enum hp_wmi_data_elements.
> > >
> > > ...
> > > ORD_LIST_ELEMENTS = 11,
> > > ORD_LIST_ELEM_CNT = 12,
> > > ...
> >
> > Done! changes provided across all files affected.
> >
> > >
> > > But replacing the loop logic would remove the need for these enums
> > > completely.
> > >
> >
> > _CNT values are necessary when elements are read from a buffer (
> > populate_string_elements_from_buffer).
> > _CNT values are not needed when elements are read from a package
> > (populate_string_package_data)
>
> Hm, I don't see why populate_string_elements_from_buffer() would need
> the _CNT define.
>
> (In another review mail I wrote down how I would expect it to look
> without the loop)
>
> <snip>
>
> > > > +
> > > > +#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
> > > > + static ssize_t curr_val##_store(struct kobject *kobj, \
> > > > + struct kobj_attribute *attr, \
> > > > + const char *buf, size_t count) \
> > > > + { \
> > > > + char *p = NULL; \
> > > > + char *attr_value = NULL; \
> > > > + int i; \
> > > > + int ret = -EIO; \
> > > > + \
> > > > + attr_value = kstrdup(buf, GFP_KERNEL); \
> > > > + if (!attr_value) \
> > > > + return -ENOMEM; \
> > > > + \
> > > > + p = memchr(attr_value, '\n', count); \
> > > > + if (p != NULL) \
> > > > + *p = '\0'; \
> > >
> > > This can also truncate the string if there is data after the newline.
> >
> > This is a expected behavior as described by Hans in a later email
>
> I'm fine with stripping a trailing newline.
>
> But this truncates the string at the first newline.
>
> "foo\nbar" -> "foo"
> "\nfoo" -> ""
>
All inputs expected by this driver and respectively by BIOS are a
single line. For this reason, '\n' will cause the string to be
truncated.
I propose reporting a warning message indicating that the data entered
has a '\n' character and will be truncated in addition to failing the
operation with -EINVAL


> <snip>

2023-04-28 16:09:01

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

On 2023-04-28 10:40:59-0500, Jorge Lopez wrote:
> On Fri, Apr 28, 2023 at 10:21 AM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-04-28 09:58:01-0500, Jorge Lopez wrote:
> > > On Fri, Apr 28, 2023 at 1:03 AM Thomas Weißschuh <[email protected]> wrote:
> > > >
> > > > On 2023-04-27 17:17:57-0500, Jorge Lopez wrote:
> > > > > On Sun, Apr 23, 2023 at 7:16 AM Thomas Weißschuh <[email protected]> wrote:
> > > > > >
> > > > > > On 2023-04-20 11:54:52-0500, Jorge Lopez wrote:
> > > > > > > .../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
> > > > > > > 1 file changed, 130 insertions(+)
> > > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > > > >
> > > > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > > > > new file mode 100644
> >
> > <snip>
> >
> > > > > > Instead of not returning any data, why not show as many results as
> > > > > > possible?
> > > > > >
> > > > >
> > > > > if count * LOG_ENTRY_SIZE > PAGE_SIZE then I prefer to return an error.
> > > > > if the count is correct but a failure occurs while reading individual
> > > > > audit logs then we will return a partial list of all audit logs
> > > > > This changes will be included in Version 12
> > > >
> > > > What prevents the firmware from having more log entries?
> > > > Wouldn't these audit log entries not accumulate for each logged
> > > > operation over the lifetime of the device / boot?
> > > >
> > > > This would make the interface unusable as soon as there are more
> > > > entries.
> > >
> > > BIOS stores a max number of audit logs appropriate to the current
> > > audit log size.The first audit logs are kept in a FIFO queue by BIOS
> > > so when the queue is full and a new audit log arrives, then the first
> > > audit log will be deleted.
> >
> > How does it determine "appropriate"?
> > This would also be great in a comment.
> >
> > If the BIOS is just using FIFO the driver could return the first
> > LOG_MAX_ENTRIES entries.
> > This would avoid trusting the firmware for a reasonable definition of
> > "appropriate".
> >
> > > >
> > > > > > > +
> > > > > > > + if (ret < 0)
> > > > > > > + return ret;
> > > >
> > > > And this should first validate ret and then count.
> > >
> > > Done!
> > >
> > > >
> > > > > > > +
> > > > > > > + /*
> > > > > > > + * We are guaranteed the buffer is 4KB so today all the event
> > > > > > > + * logs will fit
> > > > > > > + */
> > > > > > > +
> > > > > > > + for (i = 0; ((i < count) & (ret >= 0)); i++) {
> > > > > >
> > > > > > &&
> > > > > >
> > > > > > Better yet, pull the condition ret >= 0 into the body, as an else-branch
> > > > > > for the existing check.
> > > > > >
> > > > >
> > > > > Done!
> > > > >
> > > > > > > + *buf = (i + 1);
> > > > > >
> > > > > > Isn't this directly overwritten by the query below?
> > > > >
> > > > > buf input value indicates the audit log to be read hence the reason
> > > > > why it is overwritten.
> > > > > This is an expected behavior.
> > > >
> > > > So this is read by the HPWMI_SURESTART_GET_LOG method in the firmware?
> > > >
> > > > Make sense but need a comment.
> > >
> > > Done!
> > >
> > > >
> > > > > >
> > > > > > > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
> > > > > > > + HPWMI_SURESTART,
> > > > > > > + buf, 1, 128);
> > > > > > > + if (ret >= 0)
> > > > > > > + buf += LOG_ENTRY_SIZE;
> > > > > >
> > > > > > So 128 bytes are read but only the first 16 bytes are preserved?
> > > > > >
> > > > > > The documentation says that each entry has 128 bytes in the file.
> > > > > > And that they are separated by ";", which is not implemented.
> > > > >
> > > > > The statement will be removed from documentation (separated by ";")
> > > > > audit log size is 16 bytes.
> > > > > >
> > > > > > Can the audit-log not contain all-zero bytes?
> > > > > > If it does this would need to be a bin_attribute.
> > > > >
> > > > > Bytes 16-127 are ignored and not used at this time. If the audit log
> > > > > changes, then the driver will need to change to accommodate the new
> > > > > audit log size.
> > > >
> > > > buf is not guaranteed to have 128 bytes left for this data.
> > > >
> > > > For example if this is entry number 253 we are at offset 253 * 16 = 4048
> > > > in the sysfs buffer. Now hw_wmi_perform_query may try to write to 4048 +
> > > > 127 = 4175 which is out of bounds for the buf of size 4096.
> > > >
> > > > Writing first to a stack buffer would be better,
> > > > or pass outsize = LOG_ENTRY_SIZE.
> > > >
> > > BIOS currently stores 16 bytes for each audit log although the WMI
> > > query reads 128 bytes. The 128 bytes size is set to provide support
> > > in future BIOS for audit log sizes >= 16 and < 128 bytes.
> >
> > And if an old driver is running on a new BIOS then this would write out
> > of bounds.
> > Or if the BIOS is buggy.
> >
> > If the current driver can only handle 16 byte sized log entries then the
> > this should be used in the call to HPWMI_SURESTART_GET_LOG.
>
> BIOS WMI specification indicates that the HPWMI_SURESTART_GET_LOG call
> expects a 128 byte size output buffer regardless of the actual audit
> log size currently supported.
>
> Return Values:
> Byte 0-15: a requested Audit Log entry (Each Audit log is 16 bytes)
> Byte 16-127: Unused
> >
> > Storing it in a 128 byte stackvariable would also sidestep the issue.
>
> The driver hardcodes the audit log size to 16 bytes. If the new BIOS
> provides an audit log that is larger than 16 bytes, then the logs
> provided to the user application by the old driver will be truncated.

HPWMI_SURESTART_GET_LOG is directly passed a pointer into "buf" which
comes from sysfs core and is one page, 4096 bytes large.
It is told to write 128 bytes into it at a given offset.

In the loop if i == 253 then this offset will be LOG_ENTRY_SIZE * 253 = 4048.

So on a new BIOS the driver may write 128 bytes at offset 4048.
This goes up to 4175 which is larger than the 4096 buffer.

(See also the calculation in the previous mail)

Just use a 128 byte stack buffer and copy 16 bytes of it to the output
buffer.
(After having validated that the BIOS actually returned 16 bytes)

2023-04-28 16:21:07

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

On Fri, Apr 28, 2023 at 11:06 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-28 10:40:59-0500, Jorge Lopez wrote:
> > On Fri, Apr 28, 2023 at 10:21 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-28 09:58:01-0500, Jorge Lopez wrote:
> > > > On Fri, Apr 28, 2023 at 1:03 AM Thomas Weißschuh <[email protected]> wrote:
> > > > >
> > > > > On 2023-04-27 17:17:57-0500, Jorge Lopez wrote:
> > > > > > On Sun, Apr 23, 2023 at 7:16 AM Thomas Weißschuh <[email protected]> wrote:
> > > > > > >
> > > > > > > On 2023-04-20 11:54:52-0500, Jorge Lopez wrote:
> > > > > > > > .../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
> > > > > > > > 1 file changed, 130 insertions(+)
> > > > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > > > > >
> > > > > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > > > > > new file mode 100644
> > >
> > > <snip>
> > >
> > > > > > > Instead of not returning any data, why not show as many results as
> > > > > > > possible?
> > > > > > >
> > > > > >
> > > > > > if count * LOG_ENTRY_SIZE > PAGE_SIZE then I prefer to return an error.
> > > > > > if the count is correct but a failure occurs while reading individual
> > > > > > audit logs then we will return a partial list of all audit logs
> > > > > > This changes will be included in Version 12
> > > > >
> > > > > What prevents the firmware from having more log entries?
> > > > > Wouldn't these audit log entries not accumulate for each logged
> > > > > operation over the lifetime of the device / boot?
> > > > >
> > > > > This would make the interface unusable as soon as there are more
> > > > > entries.
> > > >
> > > > BIOS stores a max number of audit logs appropriate to the current
> > > > audit log size.The first audit logs are kept in a FIFO queue by BIOS
> > > > so when the queue is full and a new audit log arrives, then the first
> > > > audit log will be deleted.
> > >
> > > How does it determine "appropriate"?
> > > This would also be great in a comment.
> > >
> > > If the BIOS is just using FIFO the driver could return the first
> > > LOG_MAX_ENTRIES entries.
> > > This would avoid trusting the firmware for a reasonable definition of
> > > "appropriate".
> > >
> > > > >
> > > > > > > > +
> > > > > > > > + if (ret < 0)
> > > > > > > > + return ret;
> > > > >
> > > > > And this should first validate ret and then count.
> > > >
> > > > Done!
> > > >
> > > > >
> > > > > > > > +
> > > > > > > > + /*
> > > > > > > > + * We are guaranteed the buffer is 4KB so today all the event
> > > > > > > > + * logs will fit
> > > > > > > > + */
> > > > > > > > +
> > > > > > > > + for (i = 0; ((i < count) & (ret >= 0)); i++) {
> > > > > > >
> > > > > > > &&
> > > > > > >
> > > > > > > Better yet, pull the condition ret >= 0 into the body, as an else-branch
> > > > > > > for the existing check.
> > > > > > >
> > > > > >
> > > > > > Done!
> > > > > >
> > > > > > > > + *buf = (i + 1);
> > > > > > >
> > > > > > > Isn't this directly overwritten by the query below?
> > > > > >
> > > > > > buf input value indicates the audit log to be read hence the reason
> > > > > > why it is overwritten.
> > > > > > This is an expected behavior.
> > > > >
> > > > > So this is read by the HPWMI_SURESTART_GET_LOG method in the firmware?
> > > > >
> > > > > Make sense but need a comment.
> > > >
> > > > Done!
> > > >
> > > > >
> > > > > > >
> > > > > > > > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
> > > > > > > > + HPWMI_SURESTART,
> > > > > > > > + buf, 1, 128);
> > > > > > > > + if (ret >= 0)
> > > > > > > > + buf += LOG_ENTRY_SIZE;
> > > > > > >
> > > > > > > So 128 bytes are read but only the first 16 bytes are preserved?
> > > > > > >
> > > > > > > The documentation says that each entry has 128 bytes in the file.
> > > > > > > And that they are separated by ";", which is not implemented.
> > > > > >
> > > > > > The statement will be removed from documentation (separated by ";")
> > > > > > audit log size is 16 bytes.
> > > > > > >
> > > > > > > Can the audit-log not contain all-zero bytes?
> > > > > > > If it does this would need to be a bin_attribute.
> > > > > >
> > > > > > Bytes 16-127 are ignored and not used at this time. If the audit log
> > > > > > changes, then the driver will need to change to accommodate the new
> > > > > > audit log size.
> > > > >
> > > > > buf is not guaranteed to have 128 bytes left for this data.
> > > > >
> > > > > For example if this is entry number 253 we are at offset 253 * 16 = 4048
> > > > > in the sysfs buffer. Now hw_wmi_perform_query may try to write to 4048 +
> > > > > 127 = 4175 which is out of bounds for the buf of size 4096.
> > > > >
> > > > > Writing first to a stack buffer would be better,
> > > > > or pass outsize = LOG_ENTRY_SIZE.
> > > > >
> > > > BIOS currently stores 16 bytes for each audit log although the WMI
> > > > query reads 128 bytes. The 128 bytes size is set to provide support
> > > > in future BIOS for audit log sizes >= 16 and < 128 bytes.
> > >
> > > And if an old driver is running on a new BIOS then this would write out
> > > of bounds.
> > > Or if the BIOS is buggy.
> > >
> > > If the current driver can only handle 16 byte sized log entries then the
> > > this should be used in the call to HPWMI_SURESTART_GET_LOG.
> >
> > BIOS WMI specification indicates that the HPWMI_SURESTART_GET_LOG call
> > expects a 128 byte size output buffer regardless of the actual audit
> > log size currently supported.
> >
> > Return Values:
> > Byte 0-15: a requested Audit Log entry (Each Audit log is 16 bytes)
> > Byte 16-127: Unused
> > >
> > > Storing it in a 128 byte stackvariable would also sidestep the issue.
> >
> > The driver hardcodes the audit log size to 16 bytes. If the new BIOS
> > provides an audit log that is larger than 16 bytes, then the logs
> > provided to the user application by the old driver will be truncated.
>
> HPWMI_SURESTART_GET_LOG is directly passed a pointer into "buf" which
> comes from sysfs core and is one page, 4096 bytes large.
> It is told to write 128 bytes into it at a given offset.
>
> In the loop if i == 253 then this offset will be LOG_ENTRY_SIZE * 253 = 4048.
>
> So on a new BIOS the driver may write 128 bytes at offset 4048.
> This goes up to 4175 which is larger than the 4096 buffer.
>
> (See also the calculation in the previous mail)
>
> Just use a 128 byte stack buffer and copy 16 bytes of it to the output
> buffer.
> (After having validated that the BIOS actually returned 16 bytes)

Thank you for the clarification.
Done!

2023-04-28 16:23:59

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 08/14] HP BIOSCFG driver - bioscfg-h

On 2023-04-28 11:03:56-0500, Jorge Lopez wrote:
> On Fri, Apr 28, 2023 at 10:36 AM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-04-28 10:24:40-0500, Jorge Lopez wrote:
> > > On Sun, Apr 23, 2023 at 7:01 AM Thomas Weißschuh <[email protected]> wrote:
> > > >
> > > > On 2023-04-20 11:54:48-0500, Jorge Lopez wrote:
> > > > > ---
> > > > > drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++++++++++
> > > > > 1 file changed, 613 insertions(+)
> > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h

<snip>

> > > > > +#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
> > > > > + static ssize_t curr_val##_store(struct kobject *kobj, \
> > > > > + struct kobj_attribute *attr, \
> > > > > + const char *buf, size_t count) \
> > > > > + { \
> > > > > + char *p = NULL; \
> > > > > + char *attr_value = NULL; \
> > > > > + int i; \
> > > > > + int ret = -EIO; \
> > > > > + \
> > > > > + attr_value = kstrdup(buf, GFP_KERNEL); \
> > > > > + if (!attr_value) \
> > > > > + return -ENOMEM; \
> > > > > + \
> > > > > + p = memchr(attr_value, '\n', count); \
> > > > > + if (p != NULL) \
> > > > > + *p = '\0'; \
> > > >
> > > > This can also truncate the string if there is data after the newline.
> > >
> > > This is a expected behavior as described by Hans in a later email
> >
> > I'm fine with stripping a trailing newline.
> >
> > But this truncates the string at the first newline.
> >
> > "foo\nbar" -> "foo"
> > "\nfoo" -> ""
> >
> All inputs expected by this driver and respectively by BIOS are a
> single line. For this reason, '\n' will cause the string to be
> truncated.
> I propose reporting a warning message indicating that the data entered
> has a '\n' character and will be truncated in addition to failing the
> operation with -EINVAL

EINVAL sounds good, but a warning is overkill IMO.

Whoever put in the garbage value will see the error.

Stripping a trailing newline still seems fine though.
This would be a very good candidate for a helper function.

2023-04-28 16:29:59

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 08/14] HP BIOSCFG driver - bioscfg-h

On Fri, Apr 28, 2023 at 11:09 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-28 11:03:56-0500, Jorge Lopez wrote:
> > On Fri, Apr 28, 2023 at 10:36 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-28 10:24:40-0500, Jorge Lopez wrote:
> > > > On Sun, Apr 23, 2023 at 7:01 AM Thomas Weißschuh <[email protected]> wrote:
> > > > >
> > > > > On 2023-04-20 11:54:48-0500, Jorge Lopez wrote:
> > > > > > ---
> > > > > > drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++++++++++
> > > > > > 1 file changed, 613 insertions(+)
> > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
>
> <snip>
>
> > > > > > +#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
> > > > > > + static ssize_t curr_val##_store(struct kobject *kobj, \
> > > > > > + struct kobj_attribute *attr, \
> > > > > > + const char *buf, size_t count) \
> > > > > > + { \
> > > > > > + char *p = NULL; \
> > > > > > + char *attr_value = NULL; \
> > > > > > + int i; \
> > > > > > + int ret = -EIO; \
> > > > > > + \
> > > > > > + attr_value = kstrdup(buf, GFP_KERNEL); \
> > > > > > + if (!attr_value) \
> > > > > > + return -ENOMEM; \
> > > > > > + \
> > > > > > + p = memchr(attr_value, '\n', count); \
> > > > > > + if (p != NULL) \
> > > > > > + *p = '\0'; \
> > > > >
> > > > > This can also truncate the string if there is data after the newline.
> > > >
> > > > This is a expected behavior as described by Hans in a later email
> > >
> > > I'm fine with stripping a trailing newline.
> > >
> > > But this truncates the string at the first newline.
> > >
> > > "foo\nbar" -> "foo"
> > > "\nfoo" -> ""
> > >
> > All inputs expected by this driver and respectively by BIOS are a
> > single line. For this reason, '\n' will cause the string to be
> > truncated.
> > I propose reporting a warning message indicating that the data entered
> > has a '\n' character and will be truncated in addition to failing the
> > operation with -EINVAL
>
> EINVAL sounds good, but a warning is overkill IMO.
>
> Whoever put in the garbage value will see the error.
>
> Stripping a trailing newline still seems fine though.

So. should the driver return an -EINVAL error or truncate the line,
report a warning message, and allow it to proceed.?
Please advice

> This would be a very good candidate for a helper function.

2023-04-28 16:41:37

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 08/14] HP BIOSCFG driver - bioscfg-h

On 2023-04-28 11:19:04-0500, Jorge Lopez wrote:
> On Fri, Apr 28, 2023 at 11:09 AM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-04-28 11:03:56-0500, Jorge Lopez wrote:
> > > On Fri, Apr 28, 2023 at 10:36 AM Thomas Weißschuh <[email protected]> wrote:
> > > >
> > > > On 2023-04-28 10:24:40-0500, Jorge Lopez wrote:
> > > > > On Sun, Apr 23, 2023 at 7:01 AM Thomas Weißschuh <[email protected]> wrote:
> > > > > >
> > > > > > On 2023-04-20 11:54:48-0500, Jorge Lopez wrote:
> > > > > > > ---
> > > > > > > drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++++++++++
> > > > > > > 1 file changed, 613 insertions(+)
> > > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
> >
> > <snip>
> >
> > > > > > > +#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
> > > > > > > + static ssize_t curr_val##_store(struct kobject *kobj, \
> > > > > > > + struct kobj_attribute *attr, \
> > > > > > > + const char *buf, size_t count) \
> > > > > > > + { \
> > > > > > > + char *p = NULL; \
> > > > > > > + char *attr_value = NULL; \
> > > > > > > + int i; \
> > > > > > > + int ret = -EIO; \
> > > > > > > + \
> > > > > > > + attr_value = kstrdup(buf, GFP_KERNEL); \
> > > > > > > + if (!attr_value) \
> > > > > > > + return -ENOMEM; \
> > > > > > > + \
> > > > > > > + p = memchr(attr_value, '\n', count); \
> > > > > > > + if (p != NULL) \
> > > > > > > + *p = '\0'; \
> > > > > >
> > > > > > This can also truncate the string if there is data after the newline.
> > > > >
> > > > > This is a expected behavior as described by Hans in a later email
> > > >
> > > > I'm fine with stripping a trailing newline.
> > > >
> > > > But this truncates the string at the first newline.
> > > >
> > > > "foo\nbar" -> "foo"
> > > > "\nfoo" -> ""
> > > >
> > > All inputs expected by this driver and respectively by BIOS are a
> > > single line. For this reason, '\n' will cause the string to be
> > > truncated.
> > > I propose reporting a warning message indicating that the data entered
> > > has a '\n' character and will be truncated in addition to failing the
> > > operation with -EINVAL
> >
> > EINVAL sounds good, but a warning is overkill IMO.
> >
> > Whoever put in the garbage value will see the error.
> >
> > Stripping a trailing newline still seems fine though.
>
> So. should the driver return an -EINVAL error or truncate the line,
> report a warning message, and allow it to proceed.?
> Please advice

p = memchr(attr_value, '\n', count)
if (p == attr_value + count - 1) {
*p = '\0'; /* strip trailing newline */
count--;
} else if (p) {
return -EINVAL; /* enforce single line input */
}

(untested)

When putting it into a helper you may need to adapt it a bit.

> > This would be a very good candidate for a helper function.

2023-04-28 19:54:59

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 08/14] HP BIOSCFG driver - bioscfg-h

On Fri, Apr 28, 2023 at 11:30 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-28 11:19:04-0500, Jorge Lopez wrote:
> > On Fri, Apr 28, 2023 at 11:09 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-28 11:03:56-0500, Jorge Lopez wrote:
> > > > On Fri, Apr 28, 2023 at 10:36 AM Thomas Weißschuh <[email protected]> wrote:
> > > > >
> > > > > On 2023-04-28 10:24:40-0500, Jorge Lopez wrote:
> > > > > > On Sun, Apr 23, 2023 at 7:01 AM Thomas Weißschuh <[email protected]> wrote:
> > > > > > >
> > > > > > > On 2023-04-20 11:54:48-0500, Jorge Lopez wrote:
> > > > > > > > ---
> > > > > > > > drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 613 +++++++++++++++++++
> > > > > > > > 1 file changed, 613 insertions(+)
> > > > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
> > >
> > > <snip>
> > >
> > > > > > > > +#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
> > > > > > > > + static ssize_t curr_val##_store(struct kobject *kobj, \
> > > > > > > > + struct kobj_attribute *attr, \
> > > > > > > > + const char *buf, size_t count) \
> > > > > > > > + { \
> > > > > > > > + char *p = NULL; \
> > > > > > > > + char *attr_value = NULL; \
> > > > > > > > + int i; \
> > > > > > > > + int ret = -EIO; \
> > > > > > > > + \
> > > > > > > > + attr_value = kstrdup(buf, GFP_KERNEL); \
> > > > > > > > + if (!attr_value) \
> > > > > > > > + return -ENOMEM; \
> > > > > > > > + \
> > > > > > > > + p = memchr(attr_value, '\n', count); \
> > > > > > > > + if (p != NULL) \
> > > > > > > > + *p = '\0'; \
> > > > > > >
> > > > > > > This can also truncate the string if there is data after the newline.
> > > > > >
> > > > > > This is a expected behavior as described by Hans in a later email
> > > > >
> > > > > I'm fine with stripping a trailing newline.
> > > > >
> > > > > But this truncates the string at the first newline.
> > > > >
> > > > > "foo\nbar" -> "foo"
> > > > > "\nfoo" -> ""
> > > > >
> > > > All inputs expected by this driver and respectively by BIOS are a
> > > > single line. For this reason, '\n' will cause the string to be
> > > > truncated.
> > > > I propose reporting a warning message indicating that the data entered
> > > > has a '\n' character and will be truncated in addition to failing the
> > > > operation with -EINVAL
> > >
> > > EINVAL sounds good, but a warning is overkill IMO.
> > >
> > > Whoever put in the garbage value will see the error.
> > >
> > > Stripping a trailing newline still seems fine though.
> >
> > So. should the driver return an -EINVAL error or truncate the line,
> > report a warning message, and allow it to proceed.?
> > Please advice
>
> p = memchr(attr_value, '\n', count)
> if (p == attr_value + count - 1) {
> *p = '\0'; /* strip trailing newline */
> count--;
> } else if (p) {
> return -EINVAL; /* enforce single line input */
> }
>
> (untested)
>
> When putting it into a helper you may need to adapt it a bit.
>
> > > This would be a very good candidate for a helper function.

Done! Helper function created.

2023-04-28 20:56:27

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 12/14] HP BIOSCFG driver - surestart-attributes

On Fri, Apr 28, 2023 at 11:06 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-28 10:40:59-0500, Jorge Lopez wrote:
> > On Fri, Apr 28, 2023 at 10:21 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-28 09:58:01-0500, Jorge Lopez wrote:
> > > > On Fri, Apr 28, 2023 at 1:03 AM Thomas Weißschuh <[email protected]> wrote:
> > > > >
> > > > > On 2023-04-27 17:17:57-0500, Jorge Lopez wrote:
> > > > > > On Sun, Apr 23, 2023 at 7:16 AM Thomas Weißschuh <[email protected]> wrote:
> > > > > > >
> > > > > > > On 2023-04-20 11:54:52-0500, Jorge Lopez wrote:
> > > > > > > > .../x86/hp/hp-bioscfg/surestart-attributes.c | 130 ++++++++++++++++++
> > > > > > > > 1 file changed, 130 insertions(+)
> > > > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > > > > >
> > > > > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
> > > > > > > > new file mode 100644
> > >
> > > <snip>
> > >
> > > > > > > Instead of not returning any data, why not show as many results as
> > > > > > > possible?
> > > > > > >
> > > > > >
> > > > > > if count * LOG_ENTRY_SIZE > PAGE_SIZE then I prefer to return an error.
> > > > > > if the count is correct but a failure occurs while reading individual
> > > > > > audit logs then we will return a partial list of all audit logs
> > > > > > This changes will be included in Version 12
> > > > >
> > > > > What prevents the firmware from having more log entries?
> > > > > Wouldn't these audit log entries not accumulate for each logged
> > > > > operation over the lifetime of the device / boot?
> > > > >
> > > > > This would make the interface unusable as soon as there are more
> > > > > entries.
> > > >
> > > > BIOS stores a max number of audit logs appropriate to the current
> > > > audit log size.The first audit logs are kept in a FIFO queue by BIOS
> > > > so when the queue is full and a new audit log arrives, then the first
> > > > audit log will be deleted.
> > >
> > > How does it determine "appropriate"?
> > > This would also be great in a comment.
> > >
> > > If the BIOS is just using FIFO the driver could return the first
> > > LOG_MAX_ENTRIES entries.
> > > This would avoid trusting the firmware for a reasonable definition of
> > > "appropriate".
> > >
> > > > >
> > > > > > > > +
> > > > > > > > + if (ret < 0)
> > > > > > > > + return ret;
> > > > >
> > > > > And this should first validate ret and then count.
> > > >
> > > > Done!
> > > >
> > > > >
> > > > > > > > +
> > > > > > > > + /*
> > > > > > > > + * We are guaranteed the buffer is 4KB so today all the event
> > > > > > > > + * logs will fit
> > > > > > > > + */
> > > > > > > > +
> > > > > > > > + for (i = 0; ((i < count) & (ret >= 0)); i++) {
> > > > > > >
> > > > > > > &&
> > > > > > >
> > > > > > > Better yet, pull the condition ret >= 0 into the body, as an else-branch
> > > > > > > for the existing check.
> > > > > > >
> > > > > >
> > > > > > Done!
> > > > > >
> > > > > > > > + *buf = (i + 1);
> > > > > > >
> > > > > > > Isn't this directly overwritten by the query below?
> > > > > >
> > > > > > buf input value indicates the audit log to be read hence the reason
> > > > > > why it is overwritten.
> > > > > > This is an expected behavior.
> > > > >
> > > > > So this is read by the HPWMI_SURESTART_GET_LOG method in the firmware?
> > > > >
> > > > > Make sense but need a comment.
> > > >
> > > > Done!
> > > >
> > > > >
> > > > > > >
> > > > > > > > + ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
> > > > > > > > + HPWMI_SURESTART,
> > > > > > > > + buf, 1, 128);
> > > > > > > > + if (ret >= 0)
> > > > > > > > + buf += LOG_ENTRY_SIZE;
> > > > > > >
> > > > > > > So 128 bytes are read but only the first 16 bytes are preserved?
> > > > > > >
> > > > > > > The documentation says that each entry has 128 bytes in the file.
> > > > > > > And that they are separated by ";", which is not implemented.
> > > > > >
> > > > > > The statement will be removed from documentation (separated by ";")
> > > > > > audit log size is 16 bytes.
> > > > > > >
> > > > > > > Can the audit-log not contain all-zero bytes?
> > > > > > > If it does this would need to be a bin_attribute.
> > > > > >
> > > > > > Bytes 16-127 are ignored and not used at this time. If the audit log
> > > > > > changes, then the driver will need to change to accommodate the new
> > > > > > audit log size.
> > > > >
> > > > > buf is not guaranteed to have 128 bytes left for this data.
> > > > >
> > > > > For example if this is entry number 253 we are at offset 253 * 16 = 4048
> > > > > in the sysfs buffer. Now hw_wmi_perform_query may try to write to 4048 +
> > > > > 127 = 4175 which is out of bounds for the buf of size 4096.
> > > > >
> > > > > Writing first to a stack buffer would be better,
> > > > > or pass outsize = LOG_ENTRY_SIZE.
> > > > >
> > > > BIOS currently stores 16 bytes for each audit log although the WMI
> > > > query reads 128 bytes. The 128 bytes size is set to provide support
> > > > in future BIOS for audit log sizes >= 16 and < 128 bytes.
> > >
> > > And if an old driver is running on a new BIOS then this would write out
> > > of bounds.
> > > Or if the BIOS is buggy.
> > >
> > > If the current driver can only handle 16 byte sized log entries then the
> > > this should be used in the call to HPWMI_SURESTART_GET_LOG.
> >
> > BIOS WMI specification indicates that the HPWMI_SURESTART_GET_LOG call
> > expects a 128 byte size output buffer regardless of the actual audit
> > log size currently supported.
> >
> > Return Values:
> > Byte 0-15: a requested Audit Log entry (Each Audit log is 16 bytes)
> > Byte 16-127: Unused
> > >
> > > Storing it in a 128 byte stackvariable would also sidestep the issue.
> >
> > The driver hardcodes the audit log size to 16 bytes. If the new BIOS
> > provides an audit log that is larger than 16 bytes, then the logs
> > provided to the user application by the old driver will be truncated.
>
> HPWMI_SURESTART_GET_LOG is directly passed a pointer into "buf" which
> comes from sysfs core and is one page, 4096 bytes large.
> It is told to write 128 bytes into it at a given offset.
>
> In the loop if i == 253 then this offset will be LOG_ENTRY_SIZE * 253 = 4048.
>
> So on a new BIOS the driver may write 128 bytes at offset 4048.
> This goes up to 4175 which is larger than the 4096 buffer.
>
> (See also the calculation in the previous mail)
>
> Just use a 128 byte stack buffer and copy 16 bytes of it to the output
> buffer.
> (After having validated that the BIOS actually returned 16 bytes)

Thank you for the clarification. Done!

2023-05-02 20:00:15

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 03/14] HP BIOSCFG driver - bioscfg

On Sat, Apr 22, 2023 at 5:16 PM <[email protected]> wrote:
>
> On 2023-04-20 11:54:43-0500, Jorge Lopez wrote:
> > ---
> > drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 961 +++++++++++++++++++
> > 1 file changed, 961 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
> >
> > diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
> > new file mode 100644
> > index 000000000000..4b0d4f56e65f
> > --- /dev/null
> > +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
> > @@ -0,0 +1,961 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Common methods for use with hp-bioscfg driver
> > + *
> > + * Copyright (c) 2022 HP Development Company, L.P.
> > + */
> > +
> > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> > +
> > +#include <linux/fs.h>
> > +#include <linux/module.h>
> > +#include <linux/kernel.h>
> > +#include <linux/wmi.h>
> > +#include "bioscfg.h"
> > +#include "../../firmware_attributes_class.h"
> > +#include <linux/nls.h>
> > +#include <linux/errno.h>
> > +
> > +MODULE_AUTHOR("Jorge Lopez <[email protected]>");
> > +MODULE_DESCRIPTION("HP BIOS Configuration Driver");
> > +MODULE_LICENSE("GPL");
> > +
> > +struct bioscfg_priv bioscfg_drv = {
> > + .mutex = __MUTEX_INITIALIZER(bioscfg_drv.mutex),
> > +};
> > +
> > +static struct class *fw_attr_class;
> > +
> > +int get_integer_from_buffer(int **buffer, u32 *buffer_size, int *integer)
> > +{
>
> It would be clearer to use u32 or s32 instead of int/integer.
>
> This should also take a "u8 **buffer" to avoid casts in all the callers.

Done!
>
> > + int *ptr = PTR_ALIGN(*buffer, 4);
> > +
> > + /* Ensure there is enough space remaining to read the integer */
> > + if (*buffer_size < sizeof(int))
> > + return -EINVAL;
> > +
> > + *integer = *(ptr++);
> > + *buffer = ptr;
> > + *buffer_size -= sizeof(int);
> > +
> > + return 0;
> > +}
> > +
> > +
> > +int get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size)
> > +{
> > + u16 *src = (u16 *)*buffer;
> > + u16 src_size;
> > +
> > + u16 size;
> > + int i;
> > + int escape = 0;
> > + int conv_dst_size;
> > +
> > + if (*buffer_size < sizeof(u16))
> > + return -EINVAL;
> > +
> > + src_size = *(src++);
> > + /* size value in u16 chars */
> > + size = src_size / sizeof(u16);
> > +
> > + /* Ensure there is enough space remaining to read and convert
> > + * the string
> > + */
> > + if (*buffer_size < src_size)
> > + return -EINVAL;
> > +
> > + for (i = 0; i < size; i++)
> > + if (src[i] == '\\' ||
> > + src[i] == '\r' ||
> > + src[i] == '\n' ||
> > + src[i] == '\t')
> > + escape++;
>
> Do size++ above and get rid of the variable "escape".

Done!
>
> > +
> > + size += escape;
> > +
> > + /*
> > + * Conversion is limited to destination string max number of
> > + * bytes.
> > + */
> > + conv_dst_size = size;
> > + if (size > dst_size)
> > + conv_dst_size = dst_size - 1;
> > +
> > + /*
> > + * convert from UTF-16 unicode to ASCII
> > + */
> > + utf16s_to_utf8s(src, src_size, UTF16_HOST_ENDIAN, dst, conv_dst_size);
>
> The return value is ignored.
>
> > + dst[conv_dst_size] = 0;
> > +
> > + for (i = 0; i < size && i < conv_dst_size; i++) {
> > + if (*src == '\\' ||
> > + *src == '\r' ||
> > + *src == '\n' ||
> > + *src == '\t')
> > + dst[i++] = '\\';
> > +
> > + if (*src == '\r')
> > + dst[i] = 'r';
> > + else if (*src == '\n')
> > + dst[i] = 'n';
> > + else if (*src == '\t')
> > + dst[i] = 't';
> > + else if (*src == '"')
> > + dst[i] = '\'';
> > + else
> > + dst[i] = *src;
> > + src++;
> > + }
> > +
> > + *buffer = (u8 *)src;
> > + *buffer_size -= size * sizeof(u16);
> > +
> > + return size;
> > +}
> > +
> > +
> > +/*
> > + * calculate_string_buffer() - determines size of string buffer for use with BIOS communication
> > + * @str: the string to calculate based upon
> > + */
> > +size_t bioscfg_calculate_string_buffer(const char *str)
> > +{
> > + int length = strlen(str);
> > + int size;
> > +
> > + /* BIOS expects 4 bytes when an empty string is found */
> > + if (!length)
> > + length = 1;
> > +
> > + /* u16 length field + one UTF16 char for each input char */
> > + size = sizeof(u16) + length * sizeof(u16);
> > +
> > + return size;
> > +}
>
> This full function could be:
>
> {
> /* BIOS expects 4 bytes when an empty string is found */
> if (length == 0)
> return 4;
>
> /* u16 length field + one UTF16 char for each input char */
> return sizeof(u16) + strlen(str) * sizeof(u16);
> }
>
Done!

<snip>
> > +
> > +/*
> > + * pending_reboot_show() - sysfs implementaton for read pending_reboot
> > + * @kobj: Kernel object for this attribute
> > + * @attr: Kernel object attribute
> > + * @buf: The buffer to display to userspace
>
> As said before, no need to document the API of sysfs ATTR callbacks.
> They are well-known.
>
Done across all files.

> > + *
> > + * Stores default value as 0
> > + * When current_value is changed this attribute is set to 1 to notify reboot may be required
> > + */
> > +static ssize_t pending_reboot_show(struct kobject *kobj,
> > + struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "%d\n", bioscfg_drv.pending_reboot);
> > +}
> > +static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
> > +
> > +/*
> > + * create_attributes_level_sysfs_files() - Creates pending_reboot attributes
> > + */
> > +static int create_attributes_level_sysfs_files(void)
> > +{
> > + int ret;
> > +
> > + ret = sysfs_create_file(&bioscfg_drv.main_dir_kset->kobj, &pending_reboot.attr);
> > + if (ret)
> > + return ret;
> > +
> > + return 0;
>
> Just:
> return sysfs_create_file(&bioscfg_drv.main_dir_kset->kobj, &pending_reboot.attr);

Done!
>
> > +}
> > +
> > +
<snip>

> > +
> > +/*
> > + * alloc_attributes_data() - Allocate attributes data for a particular type
> > + *
> > + * @attr_type: Attribute type to allocate
> > + */
> > +static int alloc_attributes_data(int attr_type)
> > +{
> > + int retval = 0;
>
> No need for this intermediate variable.
>
> > +
> > + switch (attr_type) {
> > + case HPWMI_STRING_TYPE:
> > + retval = alloc_string_data();
> > + break;
> > + case HPWMI_INTEGER_TYPE:
> > + retval = alloc_integer_data();
> > + break;
> > + case HPWMI_ENUMERATION_TYPE:
> > + retval = alloc_enumeration_data();
> > + break;
> > + case HPWMI_ORDERED_LIST_TYPE:
> > + retval = alloc_ordered_list_data();
> > + break;
> > + case HPWMI_PASSWORD_TYPE:
> > + retval = alloc_password_data();
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + return retval;
> > +}
> > +
Done!

> > +int convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len)
>
> Could this be replaced by "string_unescape(UNESCAPE_SPACE)" ?
>
> > +{
> > + int ret = 0;
> > + int new_len = 0;
> > + char tmp[] = "0x00";
> > + char *new_str = NULL;
> > + long ch;
> > + int i;
> > +
> > + if (input_len <= 0 || input == NULL || str == NULL || len == NULL)
> > + return -EINVAL;
> > +
> > + *len = 0;
> > + *str = NULL;
> > +
> > + new_str = kmalloc(input_len, GFP_KERNEL);
> > + if (!new_str)
> > + return -ENOMEM;
> > +
> > + for (i = 0; i < input_len; i += 5) {
> > + strncpy(tmp, input + i, strlen(tmp));
> > + if (kstrtol(tmp, 16, &ch) == 0) {
> > + // escape char
> > + if (ch == '\\' || ch == '\r' || ch == '\n' || ch == '\t') {
> > + if (ch == '\r')
> > + ch = 'r';
> > + else if (ch == '\n')
> > + ch = 'n';
> > + else if (ch == '\t')
> > + ch = 't';
> > + new_str[new_len++] = '\\';
> > + }
> > + new_str[new_len++] = ch;
> > + if (ch == '\0')
> > + break;
> > + }
> > + }
> > +
> > + if (new_len) {
> > + new_str[new_len] = '\0';
> > + *str = krealloc(new_str, (new_len + 1) * sizeof(char), GFP_KERNEL);
> > + if (*str)
> > + *len = new_len;
> > + else
> > + ret = -ENOMEM;
> > + } else {
> > + ret = -EFAULT;
> > + }
> > +
> > + if (ret)
> > + kfree(new_str);
> > + return ret;
> > +}
> > +
> > +/* map output size to the corresponding WMI method id */
> > +int encode_outsize_for_pvsz(int outsize)
> > +{
> > + if (outsize > 4096)
> > + return -EINVAL;
> > + if (outsize > 1024)
> > + return 5;
> > + if (outsize > 128)
> > + return 4;
> > + if (outsize > 4)
> > + return 3;
> > + if (outsize > 0)
> > + return 2;
> > + return 1;
> > +}
> > +
> > +/*
> > + * Update friendly display name for several attributes associated to
> > + * 'Schedule Power-On'
> > + */
> > +void friendly_user_name_update(char *path, const char *attr_name,
> > + char *attr_display, int attr_size)
> > +{
> > + char *found = NULL;
>
> No need for this variable.

Done!

>
> > +
> > + found = strstr(path, SCHEDULE_POWER_ON);
> > + if (found)
> > + snprintf(attr_display,
> > + attr_size,
> > + "%s - %s",
> > + SCHEDULE_POWER_ON,
> > + attr_name);
> > + else
> > + strscpy(attr_display, attr_name, attr_size);
> > +}
> > +
> > +/*
> > + * update_attribute_permissions() - Update attributes permissions when
> > + * isReadOnly value is 1
> > + *
> > + * @isReadOnly: ReadOnly value
> > + * @current_val: kobj_attribute corresponding to attribute.
> > + *
> > + */
> > +void update_attribute_permissions(u32 isReadOnly, struct kobj_attribute *current_val)
> > +{
> > + if (isReadOnly)
> > + current_val->attr.mode = (umode_t)0444;
> > + else
> > + current_val->attr.mode = (umode_t)0644;
>
> No need for the casts.
>
> isReadOnly does not use the correct naming scheme.
>
Done!

> > +}
> > +
<snip>

> > + * hp_add_other_attributes - Initialize HP custom attributes not reported by
> > + * BIOS and required to support Secure Platform, Sure Start, and Sure
> > + * Admin.
> > + * @attr_type: Custom HP attribute not reported by BIOS
> > + *
> > + * Initialiaze all 3 types of attributes: Platform, Sure Start, and Sure
> > + * Admin object. Populates each attrbute types respective properties
> > + * under sysfs files.
>
> Typos in this comment. checkpatch.pl can use codespell to check for
> typos.
>
Done!

> > + *
> > + * Returns zero(0) if successful. Otherwise, a negative value.
> > + */
> > +static int hp_add_other_attributes(int attr_type)
>
> The naming scheme is inconsistent. The prefixes "bioscfg_" and "hp_" are
> used but many symbols don't use any prefix.

Done! Prefixes set to bioscfg_ for all functions.
>
> > +{
> > + struct kobject *attr_name_kobj;
> > + union acpi_object *obj = NULL;
>
> This obj is never used except to be freed.
>
> > + int retval = 0;
> > + u8 *attr_name;
>
> const char *

Cannot define attr_name as 'const char *'. attr_name value is set
within the function
>
> > +
> > + mutex_lock(&bioscfg_drv.mutex);
> > +
> > + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
> > + if (!attr_name_kobj) {
> > + retval = -ENOMEM;
> > + goto err_other_attr_init;
> > + }
> > +
> > + /* Check if attribute type is supported */
> > + switch (attr_type) {
> > + case HPWMI_SECURE_PLATFORM_TYPE:
> > + attr_name_kobj->kset = bioscfg_drv.authentication_dir_kset;
> > + attr_name = SPM_STR;
> > + break;
> > +
> > + case HPWMI_SURE_START_TYPE:
> > + attr_name_kobj->kset = bioscfg_drv.main_dir_kset;
> > + attr_name = SURE_START_STR;
> > + break;
> > +
> > + default:
> > + pr_err("Error: Unknown attr_type: %d\n", attr_type);
> > + retval = -EINVAL;
> > + goto err_other_attr_init;
> > + }
> > +
> > + retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
> > + NULL, "%s", attr_name);
> > + if (retval) {
> > + pr_err("Error encountered [%d]\n", retval);
> > + kobject_put(attr_name_kobj);
> > + goto err_other_attr_init;
> > + }
> > +
> > + /* Populate attribute data */
> > + switch (attr_type) {
> > + case HPWMI_SECURE_PLATFORM_TYPE:
> > + retval = populate_secure_platform_data(attr_name_kobj);
> > + break;
> > +
> > + case HPWMI_SURE_START_TYPE:
> > + retval = populate_sure_start_data(attr_name_kobj);
> > + break;
> > +
> > + default:
> > + goto err_other_attr_init;
> > + }
> > +
> > + mutex_unlock(&bioscfg_drv.mutex);
> > + return 0;
> > +
> > +err_other_attr_init:
>
> As mentioned before, there is no need to encode the function name into
> the jump labels.
>

Done across all files

> > + mutex_unlock(&bioscfg_drv.mutex);
> > + kfree(obj);
> > + return retval;
> > +}
> > +
> > +/*
> > + * hp_init_bios_attributes - Initialize all attributes for a type
> > + * @attr_type: The attribute type to initialize
> > + * @guid: The WMI GUID associated with this type to initialize
> > + *
> > + * Initialiaze all 5 types of attributes: enumeration, integer,
> > + * string, password, ordered list object. Populates each attrbute types
> > + * respective properties under sysfs files
> > + */
> > +static int hp_init_bios_attributes(int attr_type, const char *guid)
> > +{
> > + struct kobject *attr_name_kobj;
> > + union acpi_object *obj = NULL;
> > + union acpi_object *elements;
> > + struct kset *tmp_set;
> > + int min_elements;
> > + char str[MAX_BUFF];
> > +
> > + char *temp_str = NULL;
>
> temp_str vs tmp_set
>
> It's using different names.
>
> Also this does not give any indication about what it does.
>
> Maybe "unescaped_name".

tmp_set name is incomplete. It should read temp_kset. The correct
name will clearly differentiate temp_str from temp_kset.
>
> > + char *str_value = NULL;
> > + int str_len;
> > + int ret = 0;
> > +
> > + u8 *buffer_ptr = NULL;
> > + int buffer_size;
> > +
> > +
> > + /* instance_id needs to be reset for each type GUID
> > + * also, instance IDs are unique within GUID but not across
> > + */
> > + int instance_id = 0;
> > + int retval = 0;
>
> "retval" and "ret"?

Only retval should be present.
Done!

>
> > +
> > + retval = alloc_attributes_data(attr_type);
> > + if (retval)
> > + return retval;
> > +
> > + switch (attr_type) {
> > + case HPWMI_STRING_TYPE:
> > + min_elements = 12;
> > + break;
> > + case HPWMI_INTEGER_TYPE:
> > + min_elements = 13;
> > + break;
> > + case HPWMI_ENUMERATION_TYPE:
> > + min_elements = 13;
> > + break;
> > + case HPWMI_ORDERED_LIST_TYPE:
> > + min_elements = 12;
> > + break;
> > + case HPWMI_PASSWORD_TYPE:
> > + min_elements = 15;
> > + break;
> > + default:
> > + pr_err("Error: Unknown attr_type: %d\n", attr_type);
> > + return -EINVAL;
> > + }
> > +
> > + /* need to use specific instance_id and guid combination to get right data */
> > + obj = get_wmiobj_pointer(instance_id, guid);
> > + if (!obj)
> > + return -ENODEV;
> > +
> > + mutex_lock(&bioscfg_drv.mutex);
> > + while (obj) {
> > + if (obj->type != ACPI_TYPE_PACKAGE && obj->type != ACPI_TYPE_BUFFER) {
> > + pr_err("Error: Expected ACPI-package or buffer type, got: %d\n", obj->type);
> > + retval = -EIO;
> > + goto err_attr_init;
> > + }
> > +
> > + /* Take action appropriate to each ACPI TYPE */
> > + if (obj->type == ACPI_TYPE_PACKAGE) {
> > + if (obj->package.count < min_elements) {
> > + pr_err("ACPI-package does not have enough elements: %d < %d\n",
> > + obj->package.count, min_elements);
> > + goto nextobj;
> > + }
> > +
> > + elements = obj->package.elements;
> > +
> > + /* sanity checking */
> > + if (elements[NAME].type != ACPI_TYPE_STRING) {
> > + pr_debug("incorrect element type\n");
> > + goto nextobj;
> > + }
> > + if (strlen(elements[NAME].string.pointer) == 0) {
> > + pr_debug("empty attribute found\n");
> > + goto nextobj;
> > + }
> > +
> > + if (attr_type == HPWMI_PASSWORD_TYPE)
> > + tmp_set = bioscfg_drv.authentication_dir_kset;
> > + else
> > + tmp_set = bioscfg_drv.main_dir_kset;
> > +
> > + /* convert attribute name to string */
> > + retval = convert_hexstr_to_str(elements[NAME].string.pointer,
> > + elements[NAME].string.length,
> > + &str_value, &str_len);
> > +
> > + if (retval) {
> > + pr_debug("Failed to populate integer package data. Error [0%0x]\n", ret);
> > + kfree(str_value);
>
> convert_hexstr_to_str should make sure that the data is freed on error
> on its own.

Confirmed the allocated data is freed by convert_hexstr_to_str clears
on error on its own.
>
> > + return ret;
> > + }
> > +
> > + if (kset_find_obj(tmp_set, str_value)) {
> > + pr_debug("Duplicate attribute name found - %s\n",
> > + str_value);
> > + goto nextobj;
> > + }
> > +
> > + /* build attribute */
> > + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
> > + if (!attr_name_kobj) {
> > + retval = -ENOMEM;
> > + goto err_attr_init;
> > + }
> > +
> > + attr_name_kobj->kset = tmp_set;
> > +
> > + retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
> > + NULL, "%s", str_value);
> > +
> > + if (retval) {
> > + kobject_put(attr_name_kobj);
>
> The kobj was not created, why does it need the kobj_put() ?
As indicated by kobject_init_and_add ...

* This function combines the call to kobject_init() and kobject_add().
*
* If this function returns an error, kobject_put() must be called to
* properly clean up the memory associated with the object. This is the
* same type of error handling after a call to kobject_add() and kobject
* lifetime rules are the same here.

>
> > + goto err_attr_init;
> > + }
> > +
> > + /* enumerate all of these attributes */
> > + switch (attr_type) {
> > + case HPWMI_STRING_TYPE:
> > + retval = populate_string_package_data(elements,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + case HPWMI_INTEGER_TYPE:
> > + retval = populate_integer_package_data(elements,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + case HPWMI_ENUMERATION_TYPE:
> > + retval = populate_enumeration_package_data(elements,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + case HPWMI_ORDERED_LIST_TYPE:
> > + retval = populate_ordered_list_package_data(elements,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + case HPWMI_PASSWORD_TYPE:
> > + retval = populate_password_package_data(elements,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + default:
> > + break;
>
> This default does nothing.
>
> > + }
> > +
> > + kfree(str_value);
>
> Why is str_value only freed down here? It has not been used for half a
> screen of code.

Added early in the development process and failed to clean up here.
>
> > + }
>
> else
>
> > +
> > + if (obj->type == ACPI_TYPE_BUFFER) {
> > +
> > + buffer_size = obj->buffer.length;
> > + buffer_ptr = obj->buffer.pointer;
> > +
> > + retval = get_string_from_buffer(&buffer_ptr, &buffer_size, str, MAX_BUFF);
> > + if (retval < 0)
> > + goto err_attr_init;
> > +
> > + if (attr_type == HPWMI_PASSWORD_TYPE || attr_type == HPWMI_SECURE_PLATFORM_TYPE)
> > + tmp_set = bioscfg_drv.authentication_dir_kset;
> > + else
> > + tmp_set = bioscfg_drv.main_dir_kset;
>
> There is a bunch of common logic duplicated in both the buffer and
> package branches.

Older BIOS reports the ACPI data as objects of type ACPI_TYPE_PACKAGE
and the associated data is reported as elements.
Newer BIOS reports the ACPI data as objects of type ACPI_TYPE_BUFFER.
(actypes.h union acpi_object)
The code follows a common logic although the data is acquired
differently according to the ACPI object type
>
> > +
> > + if (kset_find_obj(tmp_set, str)) {
> > + pr_warn("Duplicate attribute name found - %s\n", str);
>
> Also mention that it is being ignored.
>
> > + goto nextobj;
> > + }
> > +
> > + /* build attribute */
> > + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
> > + if (!attr_name_kobj) {
> > + retval = -ENOMEM;
> > + goto err_attr_init;
> > + }
> > +
> > + attr_name_kobj->kset = tmp_set;
> > +
> > + temp_str = str;
> > + if (attr_type == HPWMI_SECURE_PLATFORM_TYPE)
> > + temp_str = "SPM";
> > +
> > + retval = kobject_init_and_add(attr_name_kobj,
> > + &attr_name_ktype, NULL, "%s",
> > + temp_str);
> > + if (retval) {
> > + kobject_put(attr_name_kobj);
> > + goto err_attr_init;
> > + }
> > +
> > + /* enumerate all of these attributes */
> > + switch (attr_type) {
> > + case HPWMI_STRING_TYPE:
> > + retval = populate_string_buffer_data(buffer_ptr,
> > + &buffer_size,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + case HPWMI_INTEGER_TYPE:
> > + retval = populate_integer_buffer_data(buffer_ptr,
> > + &buffer_size,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + case HPWMI_ENUMERATION_TYPE:
> > + retval = populate_enumeration_buffer_data(buffer_ptr,
> > + &buffer_size,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + case HPWMI_ORDERED_LIST_TYPE:
> > + retval = populate_ordered_list_buffer_data(buffer_ptr,
> > + &buffer_size,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + case HPWMI_PASSWORD_TYPE:
> > + retval = populate_password_buffer_data(buffer_ptr,
> > + &buffer_size,
> > + instance_id,
> > + attr_name_kobj);
> > + break;
> > + default:
> > + break;
> > + }
> > + }
>
> What if it's neither a package nor a buffer?

we return an error if it is neither a package nor a buffer.

if (obj->type != ACPI_TYPE_PACKAGE && obj->type != ACPI_TYPE_BUFFER) {
pr_err("Error: Expected ACPI-package or buffer type, got:
%d\n", obj->type);
retval = -EIO;
goto err_attr_init;
}


>
> > +nextobj:
> > + kfree(str_value);
> > + kfree(obj);
> > + instance_id++;
> > + obj = get_wmiobj_pointer(instance_id, guid);
> > + }
> > + mutex_unlock(&bioscfg_drv.mutex);
> > + return 0;
> > +
> > +err_attr_init:
>
> This can leak str_value.

Done!
>
> > + mutex_unlock(&bioscfg_drv.mutex);
> > + kfree(obj);
> > + return retval;
> > +}
>
> This function *really* needs to split up.

Done! function hp_init_bios_attributes() will split into two;
package and buffer objects.
>
> > +
> > +static int __init bioscfg_init(void)
> > +{
> > + int ret = 0;
>
> No need to initialize.
Done!
>
> > + int bios_capable = wmi_has_guid(HP_WMI_BIOS_GUID);
> > +
> > + if (!bios_capable) {
> > + pr_err("Unable to run on non-HP system\n");
> > + return -ENODEV;
> > + }
> > +
> > + ret = init_bios_attr_set_interface();
> > + if (ret)
> > + return ret;
> > +
> > + ret = init_bios_attr_pass_interface();
> > + if (ret)
> > + goto err_exit_bios_attr_set_interface;
> > +
> > + if (!bioscfg_drv.bios_attr_wdev || !bioscfg_drv.password_attr_wdev) {
> > + pr_debug("Failed to find set or pass interface\n");
> > + ret = -ENODEV;
> > + goto err_exit_bios_attr_pass_interface;
> > + }
>
> Can this ever happen?

The initial steps prior to this piece of code have changed. This code
is no longer needed nor condition will occur.
>
> > +
> > + ret = fw_attributes_class_get(&fw_attr_class);
> > + if (ret)
> > + goto err_exit_bios_attr_pass_interface;
> > +
> > + bioscfg_drv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> > + NULL, "%s", DRIVER_NAME);
> > + if (IS_ERR(bioscfg_drv.class_dev)) {
> > + ret = PTR_ERR(bioscfg_drv.class_dev);
> > + goto err_unregister_class;
> > + }
> > +
> > + bioscfg_drv.main_dir_kset = kset_create_and_add("attributes", NULL,
> > + &bioscfg_drv.class_dev->kobj);
> > + if (!bioscfg_drv.main_dir_kset) {
> > + ret = -ENOMEM;
> > + pr_debug("Failed to create and add attributes\n");
> > + goto err_destroy_classdev;
> > + }
> > +
> > + bioscfg_drv.authentication_dir_kset = kset_create_and_add("authentication", NULL,
> > + &bioscfg_drv.class_dev->kobj);
> > + if (!bioscfg_drv.authentication_dir_kset) {
> > + ret = -ENOMEM;
> > + pr_debug("Failed to create and add authentication\n");
> > + goto err_release_attributes_data;
> > + }
> > +
> > + /*
> > + * sysfs level attributes.
> > + * - pending_reboot
> > + */
> > + ret = create_attributes_level_sysfs_files();
> > + if (ret)
> > + pr_debug("Failed to create sysfs level attributes\n");
>
> Why continue when these fail?

The failures will be documented but will not stop the driver from
searching for other attributes and adding them.
BIOS could easily remove attributes associated with
HP_WMI_BIOS_INTEGER_GUID. In that case, the driver will report
INTEGER attributes were not populated and allows the driver to
continue loading.


>
> > + ret = hp_init_bios_attributes(HPWMI_STRING_TYPE, HP_WMI_BIOS_STRING_GUID);
> > + if (ret)
> > + pr_debug("Failed to populate string type attributes\n");
> > +
> > + ret = hp_init_bios_attributes(HPWMI_INTEGER_TYPE, HP_WMI_BIOS_INTEGER_GUID);
> > + if (ret)
> > + pr_debug("Failed to populate integer type attributes\n");
> > +
> > + ret = hp_init_bios_attributes(HPWMI_ENUMERATION_TYPE, HP_WMI_BIOS_ENUMERATION_GUID);
> > + if (ret)
> > + pr_debug("Failed to populate enumeration type attributes\n");
> > +
> > + ret = hp_init_bios_attributes(HPWMI_ORDERED_LIST_TYPE, HP_WMI_BIOS_ORDERED_LIST_GUID);
> > + if (ret)
> > + pr_debug("Failed to populate ordered list object type attributes\n");
> > +
> > + ret = hp_init_bios_attributes(HPWMI_PASSWORD_TYPE, HP_WMI_BIOS_PASSWORD_GUID);
> > + if (ret)
> > + pr_debug("Failed to populate password object type attributes\n");
> > +
> > + bioscfg_drv.spm_data.attr_name_kobj = NULL;
> > + ret = hp_add_other_attributes(HPWMI_SECURE_PLATFORM_TYPE);
> > + if (ret)
> > + pr_debug("Failed to populate secure platform object type attribute\n");
> > +
> > + bioscfg_drv.sure_start_attr_kobj = NULL;
> > + ret = hp_add_other_attributes(HPWMI_SURE_START_TYPE);
> > + if (ret)
> > + pr_debug("Failed to populate sure start object type attribute\n");
> > +
> > + return 0;
> > +
> > +err_release_attributes_data:
> > + release_attributes_data();
> > +
> > +err_destroy_classdev:
> > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > +
> > +err_unregister_class:
> > + fw_attributes_class_put();
> > +
> > +err_exit_bios_attr_pass_interface:
> > + exit_bios_attr_pass_interface();
> > +
> > +err_exit_bios_attr_set_interface:
> > + exit_bios_attr_set_interface();
> > +
> > + return ret;
> > +}
> > +
> > +static void __exit bioscfg_exit(void)
> > +{
> > + release_attributes_data();
> > + device_destroy(fw_attr_class, MKDEV(0, 0));
> > +
> > + fw_attributes_class_put();
> > + exit_bios_attr_set_interface();
> > + exit_bios_attr_pass_interface();
> > +}
> > +
> > +module_init(bioscfg_init);
> > +module_exit(bioscfg_exit);
> > --
> > 2.34.1
> >

2023-05-02 21:06:24

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 04/14] HP BIOSCFG driver - int-attributes

On Sat, Apr 22, 2023 at 5:43 PM Thomas Weißschuh <[email protected]> wrote:
>
> Hi Jorge,
>
> one thing I noticed:
>
> When I recommended to cut down the driver to a minimal feature set I
> meant the minimal feature set currently known to the kernel.
>
> This would *not* be sufficient for your HP-specific agent software.
> Just the standard type attributes, ordered-list and authentication.
>
> But in the end it would most probably still be a faster path to a
> solution that *does* include the full featureset of your requirements.
>
>
> Also when fixing review comments please try to fix them everywhere where
> applicable. Most comments affect many parts of the code.

Agree!

>
> On 2023-04-20 11:54:44-0500, Jorge Lopez wrote:
> > ---
> > Based on the latest platform-drivers-x86.git/for-next
> > ---
> > .../x86/hp/hp-bioscfg/int-attributes.c | 474 ++++++++++++++++++
> > 1 file changed, 474 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
> >
> > diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
> > new file mode 100644
> > index 000000000000..d8ee39dac3f9
> > --- /dev/null
> > +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
> > @@ -0,0 +1,474 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Functions corresponding to integer type attributes under
> > + * BIOS Enumeration GUID for use with hp-bioscfg driver.
> > + *
> > + * Copyright (c) 2022 Hewlett-Packard Inc.
> > + */
> > +
> > +#include "bioscfg.h"
> > +
> > +GET_INSTANCE_ID(integer);
> > +
> > +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> > +{
> > + int instance_id = get_integer_instance_id(kobj);
> > +
> > + if (instance_id < 0)
> > + return instance_id;
>
> Some attributes return -EIO here, some instance_id. This should be
> consistent.

Done!
>
> > +
> > + return sysfs_emit(buf, "%d\n",
> > + bioscfg_drv.integer_data[instance_id].current_value);
> > +}
> > +
> > +/*
> > + * validate_integer_input() -
> > + * Validate input of current_value against lower and upper bound
> > + *
> > + * @instance_id: The instance on which input is validated
> > + * @buf: Input value
> > + */
> > +static int validate_integer_input(int instance_id, char *buf)
> > +{
> > + int in_val;
> > + int ret;
> > +
> > +
> > + /* BIOS treats it as a read only attribute */
> > + if (bioscfg_drv.integer_data[instance_id].common.is_readonly)
> > + return -EIO;
> > +
> > + ret = kstrtoint(buf, 10, &in_val);
> > + if (ret < 0)
> > + return ret;
> > +
> > +
> > + if (in_val < bioscfg_drv.integer_data[instance_id].lower_bound ||
> > + in_val > bioscfg_drv.integer_data[instance_id].upper_bound)
> > + return -ERANGE;
> > +
> > + /*
> > + * set pending reboot flag depending on
> > + * "RequiresPhysicalPresence" value
> > + */
> > + if (bioscfg_drv.integer_data[instance_id].common.requires_physical_presence)
> > + bioscfg_drv.pending_reboot = true;
> > + return 0;
> > +}
> > +
> > +static void update_integer_value(int instance_id, char *attr_value)
> > +{
> > + int in_val;
> > + int ret;
> > +
> > + ret = kstrtoint(attr_value, 10, &in_val);
> > + if (ret == 0)
> > + bioscfg_drv.integer_data[instance_id].current_value = in_val;
>
> If the error is not propagated there should be at least be a warning.

Done!
>
> > +}
> > +
> > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, integer);
> > +static struct kobj_attribute integer_display_langcode =
> > + __ATTR_RO(display_name_language_code);
> > +
> > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, integer);
> > +static struct kobj_attribute integer_display_name =
> > + __ATTR_RO(display_name);
> > +
> > +ATTRIBUTE_PROPERTY_STORE(current_value, integer);
> > +static struct kobj_attribute integer_current_val =
> > + __ATTR_RW_MODE(current_value, 0644);
> > +
> > +ATTRIBUTE_N_PROPERTY_SHOW(lower_bound, integer);
> > +static struct kobj_attribute integer_lower_bound =
> > + __ATTR_RO(lower_bound);
> > +
> > +ATTRIBUTE_N_PROPERTY_SHOW(upper_bound, integer);
> > +static struct kobj_attribute integer_upper_bound =
> > + __ATTR_RO(upper_bound);
> > +
> > +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, integer);
> > +static struct kobj_attribute integer_prerequisites_size_val =
> > + __ATTR_RO(prerequisites_size);
> > +
> > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, integer);
> > +static struct kobj_attribute integer_prerequisites_val =
> > + __ATTR_RO(prerequisites);
> > +
> > +ATTRIBUTE_N_PROPERTY_SHOW(scalar_increment, integer);
> > +static struct kobj_attribute integer_scalar_increment =
> > + __ATTR_RO(scalar_increment);
> > +
> > +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "integer\n");
> > +}
> > +static struct kobj_attribute integer_type =
> > + __ATTR_RO(type);
> > +
> > +static struct attribute *integer_attrs[] = {
> > + &integer_display_langcode.attr,
> > + &integer_display_name.attr,
> > + &integer_current_val.attr,
> > + &integer_lower_bound.attr,
> > + &integer_upper_bound.attr,
> > + &integer_scalar_increment.attr,
> > + &integer_prerequisites_size_val.attr,
> > + &integer_prerequisites_val.attr,
> > + &integer_type.attr,
> > + NULL
> > +};
> > +
> > +static const struct attribute_group integer_attr_group = {
> > + .attrs = integer_attrs,
> > +};
> > +
> > +int alloc_integer_data(void)
> > +{
> > + int ret = 0;
>
> No need for the ret variable.

Done!
>
> > +
> > + bioscfg_drv.integer_instances_count = get_instance_count(HP_WMI_BIOS_INTEGER_GUID);
> > + bioscfg_drv.integer_data = kcalloc(bioscfg_drv.integer_instances_count,
> > + sizeof(struct integer_data), GFP_KERNEL);
>
> sizeof(bioscfg_drv.integer_data);

Done!
>
> > +
> > + if (!bioscfg_drv.integer_data) {
> > + bioscfg_drv.integer_instances_count = 0;
> > + ret = -ENOMEM;
> > + }
> > + return ret;
> > +}
> > +
> > +/* Expected Values types associated with each element */
> > +static const acpi_object_type expected_integer_types[] = {
> > + [NAME] = ACPI_TYPE_STRING,
> > + [VALUE] = ACPI_TYPE_STRING,
> > + [PATH] = ACPI_TYPE_STRING,
> > + [IS_READONLY] = ACPI_TYPE_INTEGER,
> > + [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
> > + [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
> > + [SEQUENCE] = ACPI_TYPE_INTEGER,
> > + [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
> > + [PREREQUISITES] = ACPI_TYPE_STRING,
> > + [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
> > + [INT_LOWER_BOUND] = ACPI_TYPE_INTEGER,
> > + [INT_UPPER_BOUND] = ACPI_TYPE_INTEGER,
> > + [INT_SCALAR_INCREMENT] = ACPI_TYPE_INTEGER
>
> Trailing comma.

Done!
>
> > +};
> > +
> > +/*
> > + * populate_int_data() -
> > + * Populate all properties of an instance under integer attribute
> > + *
> > + * @integer_obj: ACPI object with integer data
> > + * @instance_id: The instance to enumerate
> > + * @attr_name_kobj: The parent kernel object
> > + */
> > +int populate_integer_package_data(union acpi_object *integer_obj,
> > + int instance_id,
> > + struct kobject *attr_name_kobj)
> > +{
> > + bioscfg_drv.integer_data[instance_id].attr_name_kobj = attr_name_kobj;
> > + populate_integer_elements_from_package(integer_obj,
> > + integer_obj->package.count,
> > + instance_id);
> > + update_attribute_permissions(bioscfg_drv.integer_data[instance_id].common.is_readonly,
> > + &integer_current_val);
> > + friendly_user_name_update(bioscfg_drv.integer_data[instance_id].common.path,
> > + attr_name_kobj->name,
> > + bioscfg_drv.integer_data[instance_id].common.display_name,
> > + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name));
> > + return sysfs_create_group(attr_name_kobj, &integer_attr_group);
> > +}
> > +
> > +int populate_integer_elements_from_package(union acpi_object *integer_obj,
> > + int integer_obj_count,
> > + int instance_id)
> > +{
> > + char *str_value = NULL;
> > + int value_len;
> > + int ret = 0;
> > + u32 size = 0;
> > + u32 int_value;
> > + int elem = 0;
> > + int reqs;
> > + int eloc;
> > +
> > + if (!integer_obj)
> > + return -EINVAL;
> > +
> > + strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
> > + LANG_CODE_STR,
> > + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
> > +
> > + for (elem = 1, eloc = 1; elem < integer_obj_count; elem++, eloc++) {
> > +
> > + /* ONLY look at the first INTEGER_ELEM_CNT elements */
>
> Why?
The information provided in element 0 from the package is ignored as
directed by the BIOS team.
Similar action is taken when reading the information from ACPI Buffer
(populate_integer_elements_from_buffer())

>
> > + if (eloc == INTEGER_ELEM_CNT)
> > + goto exit_integer_package;
> > +
> > + switch (integer_obj[elem].type) {
> > + case ACPI_TYPE_STRING:
> > +
> > + if (elem != PREREQUISITES) {
> > + ret = convert_hexstr_to_str(integer_obj[elem].string.pointer,
> > + integer_obj[elem].string.length,
> > + &str_value, &value_len);
> > + if (ret)
> > + continue;
> > + }
> > + break;
> > + case ACPI_TYPE_INTEGER:
> > + int_value = (u32)integer_obj[elem].integer.value;
> > + break;
> > + default:
> > + pr_warn("Unsupported object type [%d]\n", integer_obj[elem].type);
> > + continue;
> > + }
> > + /* Check that both expected and read object type match */
> > + if (expected_integer_types[eloc] != integer_obj[elem].type) {
> > + pr_err("Error expected type %d for elem %d, but got type %d instead\n",
> > + expected_integer_types[eloc], elem, integer_obj[elem].type);
> > + return -EIO;
> > + }
> > + /* Assign appropriate element value to corresponding field*/
> > + switch (eloc) {
> > + case VALUE:
> > + ret = kstrtoint(str_value, 10, &int_value);
> > + if (ret)
> > + continue;
> > +
> > + bioscfg_drv.integer_data[instance_id].current_value = int_value;
> > + break;
> > + case PATH:
> > + strscpy(bioscfg_drv.integer_data[instance_id].common.path, str_value,
> > + sizeof(bioscfg_drv.integer_data[instance_id].common.path));
> > + break;
> > + case IS_READONLY:
> > + bioscfg_drv.integer_data[instance_id].common.is_readonly = int_value;
> > + break;
> > + case DISPLAY_IN_UI:
> > + bioscfg_drv.integer_data[instance_id].common.display_in_ui = int_value;
> > + break;
> > + case REQUIRES_PHYSICAL_PRESENCE:
> > + bioscfg_drv.integer_data[instance_id].common.requires_physical_presence = int_value;
> > + break;
> > + case SEQUENCE:
> > + bioscfg_drv.integer_data[instance_id].common.sequence = int_value;
> > + break;
> > + case PREREQUISITES_SIZE:
> > + bioscfg_drv.integer_data[instance_id].common.prerequisites_size = int_value;
> > +
> > + if (int_value > MAX_PREREQUISITES_SIZE)
> > + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> > + /*
> > + * This HACK is needed to keep the expected
> > + * element list pointing to the right obj[elem].type
> > + * when the size is zero. PREREQUISITES
> > + * object is omitted by BIOS when the size is
> > + * zero.
> > + */
> > + if (int_value == 0)
> > + eloc++;
> > + break;
> > + case PREREQUISITES:
> > + size = bioscfg_drv.integer_data[instance_id].common.prerequisites_size;
> > +
> > + for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
> > + if (elem >= integer_obj_count) {
> > + pr_err("Error elem-objects package is too small\n");
> > + return -EINVAL;
> > + }
> > +
> > + ret = convert_hexstr_to_str(integer_obj[elem + reqs].string.pointer,
> > + integer_obj[elem + reqs].string.length,
> > + &str_value, &value_len);
> > +
> > + if (ret)
> > + continue;
> > +
> > + strscpy(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs],
> > + str_value,
> > + sizeof(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs]));
> > + kfree(str_value);
> > + }
> > + break;
> > +
> > + case SECURITY_LEVEL:
> > + bioscfg_drv.integer_data[instance_id].common.security_level = int_value;
> > + break;
> > + case INT_LOWER_BOUND:
> > + bioscfg_drv.integer_data[instance_id].lower_bound = int_value;
> > + break;
> > + case INT_UPPER_BOUND:
> > + bioscfg_drv.integer_data[instance_id].upper_bound = int_value;
> > + break;
> > + case INT_SCALAR_INCREMENT:
> > + bioscfg_drv.integer_data[instance_id].scalar_increment = int_value;
> > + break;
> > + default:
> > + pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
> > + break;
> > + }
> > + }
> > +exit_integer_package:
> > + kfree(str_value);
> > + return 0;
> > +}
> > +
> > +
> > +/*
> > + * populate_integer_buffer_data() -
> > + * Populate all properties of an instance under integer attribute
> > + *
> > + * @buffer_ptr: Buffer pointer
> > + * @buffer_size: Buffer size
> > + * @instance_id: The instance to enumerate
> > + * @attr_name_kobj: The parent kernel object
> > + */
> > +int populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
> > + struct kobject *attr_name_kobj)
> > +{
> > + bioscfg_drv.integer_data[instance_id].attr_name_kobj = attr_name_kobj;
> > +
> > + /* Populate integer elements */
> > + populate_integer_elements_from_buffer(buffer_ptr, buffer_size,
> > + instance_id);
> > + update_attribute_permissions(bioscfg_drv.integer_data[instance_id].common.is_readonly,
> > + &integer_current_val);
> > + friendly_user_name_update(bioscfg_drv.integer_data[instance_id].common.path,
> > + attr_name_kobj->name,
> > + bioscfg_drv.integer_data[instance_id].common.display_name,
> > + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name));
> > +
> > + return sysfs_create_group(attr_name_kobj, &integer_attr_group);
> > +}
> > +
> > +int populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> > + int instance_id)
> > +{
> > + char *dst = NULL;
> > + int elem;
> > + int reqs;
> > + int integer;
> > + int size = 0;
> > + int ret;
> > + int dst_size = *buffer_size / sizeof(u16);
> > +
> > + dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
> > + if (!dst)
> > + return -ENOMEM;
> > +
> > + elem = 0;
> > + strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
> > + LANG_CODE_STR,
> > + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
> > +
> > + for (elem = 1; elem < 3; elem++) {
> > +
> > + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> > + if (ret < 0)
> > + continue;
> > +
> > + switch (elem) {
> > + case VALUE:
> > + ret = kstrtoint(dst, 10, &integer);
> > + if (ret)
> > + continue;
> > +
> > + bioscfg_drv.integer_data[instance_id].current_value = integer;
> > + break;
> > + case PATH:
> > + strscpy(bioscfg_drv.integer_data[instance_id].common.path, dst,
> > + sizeof(bioscfg_drv.integer_data[instance_id].common.path));
> > + break;
> > + default:
> > + pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
> > + break;
> > + }
> > + }
> > +
> > + for (elem = 3; elem < INTEGER_ELEM_CNT; elem++) {
>
> This loop pattern seems weird to me.
> It is not obvious that the values are read in the order of the switch()
> branches from the buffer.
>

The order in which the data is read from the buffer is set by BIOS.
The switch statement was used to enforce the reading order of the
elements and provide additional clarity

> Something more obvious would be:
>
> instance.common.is_readonly = read_int_from_buf(&buffer_ptr);
> instance.common.display_in_ui = read_int_from_buf(&buffer_ptr);
> instance.common.requires_physical_presence = read_int_from_buf(&buffer_ptr);
>
> This would make it clear that these are fields read in order from the
> buffer. Without having to also look at the numeric values of the
> defines.
>
> Furthermore it would make the code shorter and errorhandling would be
> clearer and the API similar to the netlink APIs.
>
> Or maybe with error reporting:
>
> ret = read_int_from_buf(&buffer_ptr, &instance.common.is_readonly);
> if (ret)
> ...

Instance.common.is_readonly is only evaluated when the user attempt to
update an attribute current value

> ret = read_int_from_buf(&buffer_ptr, &instance.common.display_in_ui);
> if (ret)
> ...

Instance.common.display_in_ui has no specific use at this time.

The code was made shorter and easier to understand by replacing the
long statements with

struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
...
integer_data->common.is_readonly = integer;

Same approach was taken for all attribute files.
>
> > +
> > + if (elem != PREREQUISITES) {
> > + ret = get_integer_from_buffer((int **)&buffer_ptr, buffer_size, (int *)&integer);
> > + if (ret < 0)
> > + continue;
> > + }
> > +
> > + switch (elem) {
> > +
> > + case IS_READONLY:
> > + bioscfg_drv.integer_data[instance_id].common.is_readonly = integer;
> > + break;
> > + case DISPLAY_IN_UI:
> > + bioscfg_drv.integer_data[instance_id].common.display_in_ui = integer;
> > + break;
> > + case REQUIRES_PHYSICAL_PRESENCE:
> > + bioscfg_drv.integer_data[instance_id].common.requires_physical_presence = integer;
> > + break;
> > + case SEQUENCE:
> > + bioscfg_drv.integer_data[instance_id].common.sequence = integer;
> > + break;
> > + case PREREQUISITES_SIZE:
> > + bioscfg_drv.integer_data[instance_id].common.prerequisites_size = integer;
> > + size = integer;
> > + if (size > MAX_PREREQUISITES_SIZE)
> > + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
> > +
> > + // PREREQUISITES:
> > + elem++;
> > + for (reqs = 0; reqs < size && reqs < MAX_PREREQUISITES_SIZE; reqs++) {
> > + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> > + if (ret < 0)
> > + continue;
> > +
> > + strscpy(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs],
> > + dst,
> > + sizeof(bioscfg_drv.integer_data[instance_id].common.prerequisites[reqs]));
> > + }
> > + break;
> > +
> > + case SECURITY_LEVEL:
> > + bioscfg_drv.integer_data[instance_id].common.security_level = integer;
> > + break;
> > + case INT_LOWER_BOUND:
> > + bioscfg_drv.integer_data[instance_id].lower_bound = integer;
> > + break;
> > + case INT_UPPER_BOUND:
> > + bioscfg_drv.integer_data[instance_id].upper_bound = integer;
> > + break;
> > + case INT_SCALAR_INCREMENT:
> > + bioscfg_drv.integer_data[instance_id].scalar_increment = integer;
> > + break;
> > +
> > + default:
> > + pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
> > + break;
> > + }
> > + }
> > + kfree(dst);
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * exit_integer_attributes() - Clear all attribute data
> > + *
> > + * Clears all data allocated for this group of attributes
> > + */
> > +void exit_integer_attributes(void)
> > +{
> > + int instance_id;
> > +
> > + for (instance_id = 0; instance_id < bioscfg_drv.integer_instances_count; instance_id++) {
> > +
> > + struct kobject *attr_name_kobj = bioscfg_drv.integer_data[instance_id].attr_name_kobj;
> > +
> > + if (attr_name_kobj)
> > + sysfs_remove_group(attr_name_kobj, &integer_attr_group);
> > + }
> > + bioscfg_drv.integer_instances_count = 0;
> > +
> > + kfree(bioscfg_drv.integer_data);
> > + bioscfg_drv.integer_data = NULL;
> > +}

2023-05-02 21:18:40

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 03/14] HP BIOSCFG driver - bioscfg

On 2023-05-02 14:52:14-0500, Jorge Lopez wrote:
> On Sat, Apr 22, 2023 at 5:16 PM <[email protected]> wrote:
> >
> > On 2023-04-20 11:54:43-0500, Jorge Lopez wrote:
> > > ---
> > > drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 961 +++++++++++++++++++
> > > 1 file changed, 961 insertions(+)
> > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
> > >
> > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
> > > new file mode 100644
> > > index 000000000000..4b0d4f56e65f
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c

<snip>

> > > + int retval = 0;
> > > + u8 *attr_name;
> >
> > const char *
>
> Cannot define attr_name as 'const char *'. attr_name value is set
> within the function

Then use "char *". This is pointing to a NULL-terminated string,
not a number or binary data.

This is also the type used by the functions this value is passed to.

<snip>

> > > + retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
> > > + NULL, "%s", str_value);
> > > +
> > > + if (retval) {
> > > + kobject_put(attr_name_kobj);
> >
> > The kobj was not created, why does it need the kobj_put() ?
> As indicated by kobject_init_and_add ...
>
> * This function combines the call to kobject_init() and kobject_add().
> *
> * If this function returns an error, kobject_put() must be called to
> * properly clean up the memory associated with the object. This is the
> * same type of error handling after a call to kobject_add() and kobject
> * lifetime rules are the same here.

I stand corrected, thanks!

> >
> > > + goto err_attr_init;
> > > + }
> > > +
> > > + /* enumerate all of these attributes */
> > > + switch (attr_type) {
> > > + case HPWMI_STRING_TYPE:
> > > + retval = populate_string_package_data(elements,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + case HPWMI_INTEGER_TYPE:
> > > + retval = populate_integer_package_data(elements,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + case HPWMI_ENUMERATION_TYPE:
> > > + retval = populate_enumeration_package_data(elements,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + case HPWMI_ORDERED_LIST_TYPE:
> > > + retval = populate_ordered_list_package_data(elements,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + case HPWMI_PASSWORD_TYPE:
> > > + retval = populate_password_package_data(elements,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + default:
> > > + break;
> >
> > This default does nothing.
> >
> > > + }
> > > +
> > > + kfree(str_value);
> >
> > Why is str_value only freed down here? It has not been used for half a
> > screen of code.
>
> Added early in the development process and failed to clean up here.
> >
> > > + }
> >
> > else
> >
> > > +
> > > + if (obj->type == ACPI_TYPE_BUFFER) {
> > > +
> > > + buffer_size = obj->buffer.length;
> > > + buffer_ptr = obj->buffer.pointer;
> > > +
> > > + retval = get_string_from_buffer(&buffer_ptr, &buffer_size, str, MAX_BUFF);
> > > + if (retval < 0)
> > > + goto err_attr_init;
> > > +
> > > + if (attr_type == HPWMI_PASSWORD_TYPE || attr_type == HPWMI_SECURE_PLATFORM_TYPE)
> > > + tmp_set = bioscfg_drv.authentication_dir_kset;
> > > + else
> > > + tmp_set = bioscfg_drv.main_dir_kset;
> >
> > There is a bunch of common logic duplicated in both the buffer and
> > package branches.
>
> Older BIOS reports the ACPI data as objects of type ACPI_TYPE_PACKAGE
> and the associated data is reported as elements.
> Newer BIOS reports the ACPI data as objects of type ACPI_TYPE_BUFFER.
> (actypes.h union acpi_object)
> The code follows a common logic although the data is acquired
> differently according to the ACPI object type
> >
> > > +
> > > + if (kset_find_obj(tmp_set, str)) {
> > > + pr_warn("Duplicate attribute name found - %s\n", str);
> >
> > Also mention that it is being ignored.
> >
> > > + goto nextobj;
> > > + }
> > > +
> > > + /* build attribute */
> > > + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
> > > + if (!attr_name_kobj) {
> > > + retval = -ENOMEM;
> > > + goto err_attr_init;
> > > + }
> > > +
> > > + attr_name_kobj->kset = tmp_set;
> > > +
> > > + temp_str = str;
> > > + if (attr_type == HPWMI_SECURE_PLATFORM_TYPE)
> > > + temp_str = "SPM";
> > > +
> > > + retval = kobject_init_and_add(attr_name_kobj,
> > > + &attr_name_ktype, NULL, "%s",
> > > + temp_str);
> > > + if (retval) {
> > > + kobject_put(attr_name_kobj);
> > > + goto err_attr_init;
> > > + }
> > > +
> > > + /* enumerate all of these attributes */
> > > + switch (attr_type) {
> > > + case HPWMI_STRING_TYPE:
> > > + retval = populate_string_buffer_data(buffer_ptr,
> > > + &buffer_size,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + case HPWMI_INTEGER_TYPE:
> > > + retval = populate_integer_buffer_data(buffer_ptr,
> > > + &buffer_size,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + case HPWMI_ENUMERATION_TYPE:
> > > + retval = populate_enumeration_buffer_data(buffer_ptr,
> > > + &buffer_size,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + case HPWMI_ORDERED_LIST_TYPE:
> > > + retval = populate_ordered_list_buffer_data(buffer_ptr,
> > > + &buffer_size,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + case HPWMI_PASSWORD_TYPE:
> > > + retval = populate_password_buffer_data(buffer_ptr,
> > > + &buffer_size,
> > > + instance_id,
> > > + attr_name_kobj);
> > > + break;
> > > + default:
> > > + break;
> > > + }
> > > + }
> >
> > What if it's neither a package nor a buffer?
>
> we return an error if it is neither a package nor a buffer.
>
> if (obj->type != ACPI_TYPE_PACKAGE && obj->type != ACPI_TYPE_BUFFER) {
> pr_err("Error: Expected ACPI-package or buffer type, got:
> %d\n", obj->type);
> retval = -EIO;
> goto err_attr_init;
> }

Indeed, thanks.

This could be pulled together into:

if (package)
...
else if (buffer)
...
else
report error

Note:

The "Error: " prefix is unnecessary. It's already logged as pr_err().

<snip>

2023-05-02 21:40:26

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 04/14] HP BIOSCFG driver - int-attributes

Hi Jorge,

thanks for incorporating my feedback, I'm curious for the next revision!

The review comments are very terse but that is only to bring across
their points better. Your effort is appreciated.

On 2023-05-02 15:56:22-0500, Jorge Lopez wrote:

<snip>

> > On 2023-04-20 11:54:44-0500, Jorge Lopez wrote:
> > > ---
> > > Based on the latest platform-drivers-x86.git/for-next
> > > ---
> > > .../x86/hp/hp-bioscfg/int-attributes.c | 474 ++++++++++++++++++
> > > 1 file changed, 474 insertions(+)
> > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
> > >
> > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
> > > new file mode 100644
> > > index 000000000000..d8ee39dac3f9
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c

<snip>

> > > +int populate_integer_elements_from_package(union acpi_object *integer_obj,
> > > + int integer_obj_count,
> > > + int instance_id)
> > > +{
> > > + char *str_value = NULL;
> > > + int value_len;
> > > + int ret = 0;
> > > + u32 size = 0;
> > > + u32 int_value;
> > > + int elem = 0;
> > > + int reqs;
> > > + int eloc;
> > > +
> > > + if (!integer_obj)
> > > + return -EINVAL;
> > > +
> > > + strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
> > > + LANG_CODE_STR,
> > > + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
> > > +
> > > + for (elem = 1, eloc = 1; elem < integer_obj_count; elem++, eloc++) {
> > > +
> > > + /* ONLY look at the first INTEGER_ELEM_CNT elements */
> >
> > Why?
> The information provided in element 0 from the package is ignored as
> directed by the BIOS team.
> Similar action is taken when reading the information from ACPI Buffer
> (populate_integer_elements_from_buffer())

This should be mentioned somewhere.

But my question was more why to we only look at INTEGER_ELEM_CNT?
It is clear to me now, but this is very convulted. See below.

<snip>

> >
> > > +
> > > +int populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> > > + int instance_id)
> > > +{
> > > + char *dst = NULL;
> > > + int elem;
> > > + int reqs;
> > > + int integer;
> > > + int size = 0;
> > > + int ret;
> > > + int dst_size = *buffer_size / sizeof(u16);
> > > +
> > > + dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
> > > + if (!dst)
> > > + return -ENOMEM;
> > > +
> > > + elem = 0;
> > > + strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
> > > + LANG_CODE_STR,
> > > + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
> > > +
> > > + for (elem = 1; elem < 3; elem++) {
> > > +
> > > + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> > > + if (ret < 0)
> > > + continue;
> > > +
> > > + switch (elem) {
> > > + case VALUE:
> > > + ret = kstrtoint(dst, 10, &integer);
> > > + if (ret)
> > > + continue;
> > > +
> > > + bioscfg_drv.integer_data[instance_id].current_value = integer;
> > > + break;
> > > + case PATH:
> > > + strscpy(bioscfg_drv.integer_data[instance_id].common.path, dst,
> > > + sizeof(bioscfg_drv.integer_data[instance_id].common.path));
> > > + break;
> > > + default:
> > > + pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
> > > + break;
> > > + }
> > > + }
> > > +
> > > + for (elem = 3; elem < INTEGER_ELEM_CNT; elem++) {
> >
> > This loop pattern seems weird to me.
> > It is not obvious that the values are read in the order of the switch()
> > branches from the buffer.
> >
>
> The order in which the data is read from the buffer is set by BIOS.

This I understand.

> The switch statement was used to enforce the reading order of the
> elements and provide additional clarity

This is not clear from the code alone. One also needs to know the
concrete values of the enums.

> > Something more obvious would be:
> >
> > instance.common.is_readonly = read_int_from_buf(&buffer_ptr);
> > instance.common.display_in_ui = read_int_from_buf(&buffer_ptr);
> > instance.common.requires_physical_presence = read_int_from_buf(&buffer_ptr);

The proposed pattern above, just regular function calls, are also
executed in the correct order, the order in which they are written.

For a reader it is clear that the order is important and part of the
ABI of the BIOS.

> > This would make it clear that these are fields read in order from the
> > buffer. Without having to also look at the numeric values of the
> > defines.
> >
> > Furthermore it would make the code shorter and errorhandling would be
> > clearer and the API similar to the netlink APIs.
> >
> > Or maybe with error reporting:
> >
> > ret = read_int_from_buf(&buffer_ptr, &instance.common.is_readonly);
> > if (ret)
> > ...
>
> Instance.common.is_readonly is only evaluated when the user attempt to
> update an attribute current value

is_readonly was only an example on how to more nicely read the data from
the buffer. It applies to all values of all attribute types.

> > ret = read_int_from_buf(&buffer_ptr, &instance.common.display_in_ui);
> > if (ret)
> > ...
>
> Instance.common.display_in_ui has no specific use at this time.
>
> The code was made shorter and easier to understand by replacing the
> long statements with
>
> struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
> ...
> integer_data->common.is_readonly = integer;
>
> Same approach was taken for all attribute files.

Thanks!

Please do try to use the "plain functioncall" pattern as outlined above.
I think it can make the code much shorter and idiomatic.

2023-05-02 21:42:10

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 03/14] HP BIOSCFG driver - bioscfg

On Tue, May 2, 2023 at 4:14 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-05-02 14:52:14-0500, Jorge Lopez wrote:
> > On Sat, Apr 22, 2023 at 5:16 PM <[email protected]> wrote:
> > >
> > > On 2023-04-20 11:54:43-0500, Jorge Lopez wrote:
> > > > ---
> > > > drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 961 +++++++++++++++++++
> > > > 1 file changed, 961 insertions(+)
> > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
> > > >
> > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
> > > > new file mode 100644
> > > > index 000000000000..4b0d4f56e65f
> > > > --- /dev/null
> > > > +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
>
> <snip>
>
> > > > + int retval = 0;
> > > > + u8 *attr_name;
> > >
> > > const char *
> >
> > Cannot define attr_name as 'const char *'. attr_name value is set
> > within the function
>
> Then use "char *". This is pointing to a NULL-terminated string,
> not a number or binary data.
>
> This is also the type used by the functions this value is passed to.

Done!

>
> <snip>
>
> > > > + retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
> > > > + NULL, "%s", str_value);
> > > > +
> > > > + if (retval) {
> > > > + kobject_put(attr_name_kobj);
> > >
> > > The kobj was not created, why does it need the kobj_put() ?
> > As indicated by kobject_init_and_add ...
> >
> > * This function combines the call to kobject_init() and kobject_add().
> > *
> > * If this function returns an error, kobject_put() must be called to
> > * properly clean up the memory associated with the object. This is the
> > * same type of error handling after a call to kobject_add() and kobject
> > * lifetime rules are the same here.
>
> I stand corrected, thanks!
>
> > >
> > > > + goto err_attr_init;
> > > > + }
> > > > +
> > > > + /* enumerate all of these attributes */
> > > > + switch (attr_type) {
> > > > + case HPWMI_STRING_TYPE:
> > > > + retval = populate_string_package_data(elements,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + case HPWMI_INTEGER_TYPE:
> > > > + retval = populate_integer_package_data(elements,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + case HPWMI_ENUMERATION_TYPE:
> > > > + retval = populate_enumeration_package_data(elements,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + case HPWMI_ORDERED_LIST_TYPE:
> > > > + retval = populate_ordered_list_package_data(elements,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + case HPWMI_PASSWORD_TYPE:
> > > > + retval = populate_password_package_data(elements,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + default:
> > > > + break;
> > >
> > > This default does nothing.
> > >
> > > > + }
> > > > +
> > > > + kfree(str_value);
> > >
> > > Why is str_value only freed down here? It has not been used for half a
> > > screen of code.
> >
> > Added early in the development process and failed to clean up here.
> > >
> > > > + }
> > >
> > > else
> > >
> > > > +
> > > > + if (obj->type == ACPI_TYPE_BUFFER) {
> > > > +
> > > > + buffer_size = obj->buffer.length;
> > > > + buffer_ptr = obj->buffer.pointer;
> > > > +
> > > > + retval = get_string_from_buffer(&buffer_ptr, &buffer_size, str, MAX_BUFF);
> > > > + if (retval < 0)
> > > > + goto err_attr_init;
> > > > +
> > > > + if (attr_type == HPWMI_PASSWORD_TYPE || attr_type == HPWMI_SECURE_PLATFORM_TYPE)
> > > > + tmp_set = bioscfg_drv.authentication_dir_kset;
> > > > + else
> > > > + tmp_set = bioscfg_drv.main_dir_kset;
> > >
> > > There is a bunch of common logic duplicated in both the buffer and
> > > package branches.
> >
> > Older BIOS reports the ACPI data as objects of type ACPI_TYPE_PACKAGE
> > and the associated data is reported as elements.
> > Newer BIOS reports the ACPI data as objects of type ACPI_TYPE_BUFFER.
> > (actypes.h union acpi_object)
> > The code follows a common logic although the data is acquired
> > differently according to the ACPI object type
> > >
> > > > +
> > > > + if (kset_find_obj(tmp_set, str)) {
> > > > + pr_warn("Duplicate attribute name found - %s\n", str);
> > >
> > > Also mention that it is being ignored.
> > >
> > > > + goto nextobj;
> > > > + }
> > > > +
> > > > + /* build attribute */
> > > > + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
> > > > + if (!attr_name_kobj) {
> > > > + retval = -ENOMEM;
> > > > + goto err_attr_init;
> > > > + }
> > > > +
> > > > + attr_name_kobj->kset = tmp_set;
> > > > +
> > > > + temp_str = str;
> > > > + if (attr_type == HPWMI_SECURE_PLATFORM_TYPE)
> > > > + temp_str = "SPM";
> > > > +
> > > > + retval = kobject_init_and_add(attr_name_kobj,
> > > > + &attr_name_ktype, NULL, "%s",
> > > > + temp_str);
> > > > + if (retval) {
> > > > + kobject_put(attr_name_kobj);
> > > > + goto err_attr_init;
> > > > + }
> > > > +
> > > > + /* enumerate all of these attributes */
> > > > + switch (attr_type) {
> > > > + case HPWMI_STRING_TYPE:
> > > > + retval = populate_string_buffer_data(buffer_ptr,
> > > > + &buffer_size,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + case HPWMI_INTEGER_TYPE:
> > > > + retval = populate_integer_buffer_data(buffer_ptr,
> > > > + &buffer_size,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + case HPWMI_ENUMERATION_TYPE:
> > > > + retval = populate_enumeration_buffer_data(buffer_ptr,
> > > > + &buffer_size,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + case HPWMI_ORDERED_LIST_TYPE:
> > > > + retval = populate_ordered_list_buffer_data(buffer_ptr,
> > > > + &buffer_size,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + case HPWMI_PASSWORD_TYPE:
> > > > + retval = populate_password_buffer_data(buffer_ptr,
> > > > + &buffer_size,
> > > > + instance_id,
> > > > + attr_name_kobj);
> > > > + break;
> > > > + default:
> > > > + break;
> > > > + }
> > > > + }
> > >
> > > What if it's neither a package nor a buffer?
> >
> > we return an error if it is neither a package nor a buffer.
> >
> > if (obj->type != ACPI_TYPE_PACKAGE && obj->type != ACPI_TYPE_BUFFER) {
> > pr_err("Error: Expected ACPI-package or buffer type, got:
> > %d\n", obj->type);
> > retval = -EIO;
> > goto err_attr_init;
> > }
>
> Indeed, thanks.
>
> This could be pulled together into:
>
> if (package)
> ...
> else if (buffer)
> ...
> else
> report error

Done!

> Note:
>
> The "Error: " prefix is unnecessary. It's already logged as pr_err().
>
Good catch. Thank you. Done!
> <snip>

2023-05-03 15:42:29

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 04/14] HP BIOSCFG driver - int-attributes

On Tue, May 2, 2023 at 4:30 PM Thomas Weißschuh <[email protected]> wrote:
>
> Hi Jorge,
>
> thanks for incorporating my feedback, I'm curious for the next revision!
>
> The review comments are very terse but that is only to bring across
> their points better. Your effort is appreciated.
>
> On 2023-05-02 15:56:22-0500, Jorge Lopez wrote:
>
> <snip>
>
> > > On 2023-04-20 11:54:44-0500, Jorge Lopez wrote:
> > > > ---
> > > > Based on the latest platform-drivers-x86.git/for-next
> > > > ---
> > > > .../x86/hp/hp-bioscfg/int-attributes.c | 474 ++++++++++++++++++
> > > > 1 file changed, 474 insertions(+)
> > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
> > > >
> > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
> > > > new file mode 100644
> > > > index 000000000000..d8ee39dac3f9
> > > > --- /dev/null
> > > > +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
>
> <snip>
>
> > > > +int populate_integer_elements_from_package(union acpi_object *integer_obj,
> > > > + int integer_obj_count,
> > > > + int instance_id)
> > > > +{
> > > > + char *str_value = NULL;
> > > > + int value_len;
> > > > + int ret = 0;
> > > > + u32 size = 0;
> > > > + u32 int_value;
> > > > + int elem = 0;
> > > > + int reqs;
> > > > + int eloc;
> > > > +
> > > > + if (!integer_obj)
> > > > + return -EINVAL;
> > > > +
> > > > + strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
> > > > + LANG_CODE_STR,
> > > > + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
> > > > +
> > > > + for (elem = 1, eloc = 1; elem < integer_obj_count; elem++, eloc++) {
> > > > +
> > > > + /* ONLY look at the first INTEGER_ELEM_CNT elements */
> > >
> > > Why?
> > The information provided in element 0 from the package is ignored as
> > directed by the BIOS team.
> > Similar action is taken when reading the information from ACPI Buffer
> > (populate_integer_elements_from_buffer())
>
> This should be mentioned somewhere.
>
> But my question was more why to we only look at INTEGER_ELEM_CNT?
> It is clear to me now, but this is very convulted. See below.

I am adding the following information to each attribute file for clarification.

"The total number of elements (INT_ELEM_CNT) read include only data
relevant to this driver and its functionality. BIOS defines the order
in which each element is read. Element 0 data is not relevant to this
driver hence it is ignored. For clarity, The switch statement list
all element names (DISPLAY_IN_UI) which defines the order in which is
read and the name matches the variable where the data is stored".

>
> <snip>
>
> > >
> > > > +
> > > > +int populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
> > > > + int instance_id)
> > > > +{
> > > > + char *dst = NULL;
> > > > + int elem;
> > > > + int reqs;
> > > > + int integer;
> > > > + int size = 0;
> > > > + int ret;
> > > > + int dst_size = *buffer_size / sizeof(u16);
> > > > +
> > > > + dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
> > > > + if (!dst)
> > > > + return -ENOMEM;
> > > > +
> > > > + elem = 0;
> > > > + strscpy(bioscfg_drv.integer_data[instance_id].common.display_name_language_code,
> > > > + LANG_CODE_STR,
> > > > + sizeof(bioscfg_drv.integer_data[instance_id].common.display_name_language_code));
> > > > +
> > > > + for (elem = 1; elem < 3; elem++) {
> > > > +
> > > > + ret = get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
> > > > + if (ret < 0)
> > > > + continue;
> > > > +
> > > > + switch (elem) {
> > > > + case VALUE:
> > > > + ret = kstrtoint(dst, 10, &integer);
> > > > + if (ret)
> > > > + continue;
> > > > +
> > > > + bioscfg_drv.integer_data[instance_id].current_value = integer;
> > > > + break;
> > > > + case PATH:
> > > > + strscpy(bioscfg_drv.integer_data[instance_id].common.path, dst,
> > > > + sizeof(bioscfg_drv.integer_data[instance_id].common.path));
> > > > + break;
> > > > + default:
> > > > + pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
> > > > + break;
> > > > + }
> > > > + }
> > > > +
> > > > + for (elem = 3; elem < INTEGER_ELEM_CNT; elem++) {
> > >
> > > This loop pattern seems weird to me.
> > > It is not obvious that the values are read in the order of the switch()
> > > branches from the buffer.
> > >
> >
> > The order in which the data is read from the buffer is set by BIOS.
>
> This I understand.
>
> > The switch statement was used to enforce the reading order of the
> > elements and provide additional clarity
>
> This is not clear from the code alone. One also needs to know the
> concrete values of the enums.
>
> > > Something more obvious would be:
> > >
> > > instance.common.is_readonly = read_int_from_buf(&buffer_ptr);
> > > instance.common.display_in_ui = read_int_from_buf(&buffer_ptr);
> > > instance.common.requires_physical_presence = read_int_from_buf(&buffer_ptr);
>
> The proposed pattern above, just regular function calls, are also
> executed in the correct order, the order in which they are written.
>
The code will be easier to follow and will not require checking of the
results because failing conditions are ignored.
This will be a good option for functions such
populate_integer_elements_from_buffer(). Buffer elements.
Refactoring package function,
populate_integer_elements_from_package(), will introduce additional
complexity and obfuscation.


> For a reader it is clear that the order is important and part of the
> ABI of the BIOS.
>
> > > This would make it clear that these are fields read in order from the
> > > buffer. Without having to also look at the numeric values of the
> > > defines.
> > >
> > > Furthermore it would make the code shorter and errorhandling would be
> > > clearer and the API similar to the netlink APIs.
> > >
> > > Or maybe with error reporting:
> > >
> > > ret = read_int_from_buf(&buffer_ptr, &instance.common.is_readonly);
> > > if (ret)
> > > ...
> >
> > Instance.common.is_readonly is only evaluated when the user attempt to
> > update an attribute current value
>
> is_readonly was only an example on how to more nicely read the data from
> the buffer. It applies to all values of all attribute types.
>
> > > ret = read_int_from_buf(&buffer_ptr, &instance.common.display_in_ui);
> > > if (ret)
> > > ...
> >
> > Instance.common.display_in_ui has no specific use at this time.
> >
> > The code was made shorter and easier to understand by replacing the
> > long statements with
> >
> > struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
> > ...
> > integer_data->common.is_readonly = integer;
> >
> > Same approach was taken for all attribute files.
>
> Thanks!
>
> Please do try to use the "plain functioncall" pattern as outlined above.
> I think it can make the code much shorter and idiomatic.

Understood!

2023-05-03 19:36:10

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 11/14] HP BIOSCFG driver - spmobj-attributes

On Sun, Apr 23, 2023 at 4:24 AM <[email protected]> wrote:
>
> On 2023-04-20 11:54:51-0500, Jorge Lopez wrote:
> > .../x86/hp/hp-bioscfg/spmobj-attributes.c | 405 ++++++++++++++++++
> > 1 file changed, 405 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
> >
> > diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
> > new file mode 100644
> > index 000000000000..78228f44c39b
> > --- /dev/null
> > +++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
> > @@ -0,0 +1,405 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Functions corresponding to secure platform management object type
> > + * attributes under BIOS PASSWORD for use with hp-bioscfg driver
> > + *
> > + * Copyright (c) 2022 HP Development Company, L.P.
> > + */
> > +
> > +#include "bioscfg.h"
> > +
> > +
> > +static const char * const spm_state_types[] = {
> > + "not provisioned",
> > + "provisioned",
> > + "provisioning in progress"
> > +};
> > +
> > +static const char * const spm_mechanism_types[] = {
> > + "not provision",
>
> "not provisioned" as above?

Good catch. Done!
>
> > + "signing-key",
> > + "endorsement-key"
>
> Trailing commas please.
>
Done!
> > +};
> > +
> > +
> > +int check_spm_is_enabled(void)
> > +{
> > + /* do we need to check the admin password is also configured */
> > + return bioscfg_drv.spm_data.is_enabled;
> > +}
> > +
> > +/*
> > + * calculate_security_buffer() - determines size of security buffer
> > + * for authentication scheme
> > + *
> > + * @authentication: the authentication content
> > + *
> > + * Currently only supported type is Admin password
> > + */
> > +size_t calculate_security_buffer(const char *authentication)
> > +{
> > + int size;
> > +
> > + if (authentication != NULL && strlen(authentication) > 0) {
> > +
> > + size = (sizeof(u16) + (strlen(authentication) * sizeof(u16)));
> > + if (strncmp(authentication, BEAM_PREFIX, strlen(BEAM_PREFIX)) != 0)
>
> strstarts()

Done!
>
> > + size += (strlen(UTF_PREFIX) * sizeof(u16));
>
> No need for braces.

Done!
>
> > +
> > + return size;
> > + }
> > +
> > + size = sizeof(u16) * 2;
> > + return size;
> > +}
> > +
> > +/*
> > + * populate_security_buffer() - builds a security buffer for
> > + * authentication scheme
> > + *
> > + * @buffer: the buffer to populate
> > + * @authentication: the authentication content
> > + *
> > + * Currently only supported type is PLAIN TEXT
> > + */
> > +void populate_security_buffer(u16 *buffer, const char *authentication)
> > +{
> > + u16 *auth = buffer;
> > + char *strprefix = NULL;
> > +
> > + if (strncmp(authentication, BEAM_PREFIX, strlen(BEAM_PREFIX)) == 0) {
>
> strstarts()

Done!
>
> > + /*
> > + * BEAM_PREFIX is append to buffer when a signature
> > + * is provided and Sure Admin is enabled in BIOS
> > + */
> > + // BEAM_PREFIX found, convert part to unicode
> > + auth = ascii_to_utf16_unicode(auth, authentication);
> > + } else {
> > + /*
> > + * UTF-16 prefix is append to the * buffer when a BIOS
> > + * admin password is configured in BIOS
> > + */
> > +
> > + // append UTF_PREFIX to part and then convert it to unicode
> > + strprefix = kasprintf(GFP_KERNEL, "%s%s", UTF_PREFIX,
> > + authentication);
> > + if (!strprefix)
> > + goto out_buffer;
> > +
> > + auth = ascii_to_utf16_unicode(auth, strprefix);
> > + }
> > +out_buffer:
> > +
> > + kfree(strprefix);
> > +}
> > +
> > +ssize_t update_spm_state(void)
> > +{
> > + int ret;
> > + struct secureplatform_provisioning_data *data = NULL;
>
> This can be allocated on the stack.

Done!
>
> > +
> > + data = kmalloc(sizeof(struct secureplatform_provisioning_data),
> > + GFP_KERNEL);
> > + if (!data) {
> > + ret = -ENOMEM;
> > + goto state_exit;
> > + }
> > +
> > + ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
> > + HPWMI_SECUREPLATFORM, data, 0,
> > + sizeof(*data));
> > + if (ret < 0)
> > + goto state_exit;
> > +
> > + bioscfg_drv.spm_data.mechanism = data->state;
> > + if (bioscfg_drv.spm_data.mechanism)
> > + bioscfg_drv.spm_data.is_enabled = 1;
> > +
> > +state_exit:
> > + kfree(data);
> > +
> > + return ret;
> > +}
> > +
> > +ssize_t statusbin(struct kobject *kobj,
> > + struct kobj_attribute *attr, char *buf)
>
> This can be static.

Done!
>
> If it is known that a struct secureplatform_provisioning_data is to be
> passed, then the type can reflect that.

Done!
>
> > +{
> > + int ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
> > + HPWMI_SECUREPLATFORM, buf, 0,
> > + sizeof(struct secureplatform_provisioning_data));
> > +
> > + return ret ? -ENODEV : sizeof(struct secureplatform_provisioning_data);
>
> Why not return "ret" on error?

Good point. Done!
>
> > +}
> > +
> > +/*
> > + * status_show - Reads SPM status
> > + */
> > +ssize_t status_show(struct kobject *kobj, struct kobj_attribute
> > + *attr, char *buf)
> > +{
> > + int ret, i;
> > + struct secureplatform_provisioning_data *data = NULL;
>
> Can also be on-stack.

Done!
>
<snip>
> > +static struct kobj_attribute password_spm_key_mechanism = __ATTR_RO(key_mechanism);
> > +
> > +static ssize_t sk_store(struct kobject *kobj,
> > + struct kobj_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + int length;
> > +
> > + length = count;
> > + if (buf[length-1] == '\n')
> > + length--;
> > +
> > + /* allocate space and copy current signing key */
> > + bioscfg_drv.spm_data.signing_key = kmalloc(length, GFP_KERNEL);
> > + if (!bioscfg_drv.spm_data.signing_key) {
> > + ret = -ENOMEM;
> > + goto exit_sk;
> > + }
> > +
> > + memcpy(bioscfg_drv.spm_data.signing_key, buf, length);
> > + bioscfg_drv.spm_data.signing_key[length] = '\0';
>
> Is this supposed to handle zero-bytes in the input?
> If yes: Did you test this with zero bytes, I don't think the normal
> attribute APIs handle this.
> If no: Why not use strscpy?
>

Done!

<snip>

2023-05-03 19:54:02

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 09/14] HP BIOSCFG driver - enum-attributes

On Sun, Apr 23, 2023 at 7:55 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-20 11:54:49-0500, Jorge Lopez wrote:
> > .../x86/hp/hp-bioscfg/enum-attributes.c | 543 ++++++++++++++++++
> > 1 file changed, 543 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
> >
> > diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
> > new file mode 100644
> > index 000000000000..20defa92da6f
> > --- /dev/null
> > +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
> > @@ -0,0 +1,543 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Functions corresponding to enumeration type attributes under
> > + * BIOS Enumeration GUID for use with hp-bioscfg driver.
> > + *
> > + * Copyright (c) 2022 HP Development Company, L.P.
> > + */
> > +
> > +#include "bioscfg.h"
> > +
> > +GET_INSTANCE_ID(enumeration);
> > +
> > +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> > +{
> > + int instance_id = get_enumeration_instance_id(kobj);
> > +
> > + if (instance_id < 0)
> > + return -EIO;
> > +
> > + return sysfs_emit(buf, "%s\n",
> > + bioscfg_drv.enumeration_data[instance_id].current_value);
> > +}
> > +
> > +/*
> > + * validate_enumeration_input() -
> > + * Validate input of current_value against possible values
> > + *
> > + * @instance_id: The instance on which input is validated
> > + * @buf: Input value
> > + */
> > +static int validate_enumeration_input(int instance_id, const char *buf)
> > +{
> > + int ret = 0;
> > + int found = 0;
> > + int i;
> > + int possible_values;
> > +
> > + /* Is it a read only attribute */
> > + if (bioscfg_drv.enumeration_data[instance_id].common.is_readonly)
> > + return -EIO;
> > +
> > + possible_values = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
> > + for (i = 0; i < possible_values && !found; i++)
> > + if (!strcasecmp(bioscfg_drv.enumeration_data[instance_id].possible_values[i], buf))
>
> Is this also intentionally case-insensitive?

Yes

>
> > + found = 1;
> > +
> > + if (!found) {
> > + ret = -EINVAL;
> > + goto exit_enum_input;
> > + }
> > +
> > + /*
> > + * set pending reboot flag depending on
> > + * "RequiresPhysicalPresence" value
> > + */
> > + if (bioscfg_drv.enumeration_data[instance_id].common.requires_physical_presence)
> > + bioscfg_drv.pending_reboot = true;
> > +
> > +exit_enum_input:
> > + return ret;
> > +}
> > +
> > +static void update_enumeration_value(int instance_id, char *attr_value)
> > +{
> > + strscpy(bioscfg_drv.enumeration_data[instance_id].current_value,
> > + attr_value,
> > + sizeof(bioscfg_drv.enumeration_data[instance_id].current_value));
> > +}
> > +
> > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, enumeration);
> > +static struct kobj_attribute enumeration_display_langcode =
> > + __ATTR_RO(display_name_language_code);
> > +
> > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, enumeration);
> > +static struct kobj_attribute enumeration_display_name =
> > + __ATTR_RO(display_name);
> > +
> > +ATTRIBUTE_PROPERTY_STORE(current_value, enumeration);
> > +static struct kobj_attribute enumeration_current_val =
> > + __ATTR_RW_MODE(current_value, 0644);
>
> 0644 is the default for __ATTR_RW(), use that instead.

Done!
>

<snip>

2023-05-03 20:11:21

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 09/14] HP BIOSCFG driver - enum-attributes

On 2023-05-03 14:42:37-0500, Jorge Lopez wrote:
> On Sun, Apr 23, 2023 at 7:55 AM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-04-20 11:54:49-0500, Jorge Lopez wrote:
> > > .../x86/hp/hp-bioscfg/enum-attributes.c | 543 ++++++++++++++++++
> > > 1 file changed, 543 insertions(+)
> > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
> > >
> > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c

<snip>

> > > +/*
> > > + * validate_enumeration_input() -
> > > + * Validate input of current_value against possible values
> > > + *
> > > + * @instance_id: The instance on which input is validated
> > > + * @buf: Input value
> > > + */
> > > +static int validate_enumeration_input(int instance_id, const char *buf)
> > > +{
> > > + int ret = 0;
> > > + int found = 0;
> > > + int i;
> > > + int possible_values;
> > > +
> > > + /* Is it a read only attribute */
> > > + if (bioscfg_drv.enumeration_data[instance_id].common.is_readonly)
> > > + return -EIO;
> > > +
> > > + possible_values = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
> > > + for (i = 0; i < possible_values && !found; i++)
> > > + if (!strcasecmp(bioscfg_drv.enumeration_data[instance_id].possible_values[i], buf))
> >
> > Is this also intentionally case-insensitive?
>
> Yes

Why? It is surprising.

The behavior differs from sysfs_match_string() and friends.
Thinking about it, this function should be able to use
__sysfs_match_string().

(Same for the ordered list type)

<snip>

2023-05-03 21:14:06

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 09/14] HP BIOSCFG driver - enum-attributes

On Wed, May 3, 2023 at 3:10 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-05-03 14:42:37-0500, Jorge Lopez wrote:
> > On Sun, Apr 23, 2023 at 7:55 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-20 11:54:49-0500, Jorge Lopez wrote:
> > > > .../x86/hp/hp-bioscfg/enum-attributes.c | 543 ++++++++++++++++++
> > > > 1 file changed, 543 insertions(+)
> > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
> > > >
> > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
>
> <snip>
>
> > > > +/*
> > > > + * validate_enumeration_input() -
> > > > + * Validate input of current_value against possible values
> > > > + *
> > > > + * @instance_id: The instance on which input is validated
> > > > + * @buf: Input value
> > > > + */
> > > > +static int validate_enumeration_input(int instance_id, const char *buf)
> > > > +{
> > > > + int ret = 0;
> > > > + int found = 0;
> > > > + int i;
> > > > + int possible_values;
> > > > +
> > > > + /* Is it a read only attribute */
> > > > + if (bioscfg_drv.enumeration_data[instance_id].common.is_readonly)
> > > > + return -EIO;
> > > > +
> > > > + possible_values = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
> > > > + for (i = 0; i < possible_values && !found; i++)
> > > > + if (!strcasecmp(bioscfg_drv.enumeration_data[instance_id].possible_values[i], buf))
> > >
> > > Is this also intentionally case-insensitive?
> >
> > Yes
>
> Why? It is surprising.
>
> The behavior differs from sysfs_match_string() and friends.
> Thinking about it, this function should be able to use
> __sysfs_match_string().
>
sysfs_match_string will compare the current value against all possible
values in the array.
I will use the recommended sysfs call. I will need to do some testing
since the documentation is not explicitly of what comparison it does.

> (Same for the ordered list type)

Will do.
>
> <snip>

2023-05-03 21:22:14

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 10/14] HP BIOSCFG driver - passwdattr-interface

On Sun, Apr 23, 2023 at 7:58 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-04-20 11:54:50-0500, Jorge Lopez wrote:
> > ---
> > .../x86/hp/hp-bioscfg/passwdattr-interface.c | 51 +++++++++++++++++++
> > 1 file changed, 51 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c
> >
> > diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c b/drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c
> > new file mode 100644
> > index 000000000000..02fc766eb3cf
> > --- /dev/null
> > +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.c
> > @@ -0,0 +1,51 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Functions corresponding to SET password methods under
> > + * HP_WMI_SET_BIOS_SETTING_GUID for use with hp-bioscfg driver.
> > + *
> > + * Copyright (c) 2022 Hewlett-Packard Inc.
> > + */
> > +
> > +#include <linux/wmi.h>
> > +#include "bioscfg.h"
> > +
> > +static int bios_attr_pass_interface_probe(struct wmi_device *wdev,
> > + const void *context)
> > +{
> > + mutex_lock(&bioscfg_drv.mutex);
> > + bioscfg_drv.password_attr_wdev = wdev;
>
> This wdev is never used. It seems this full subdriver can be dropped.
> Or the one UUID-based callsite using the same UUID can use the driver.

I will remove the subdriver.

>
> > + mutex_unlock(&bioscfg_drv.mutex);
> > + return 0;
> > +}
> > +
> > +static void bios_attr_pass_interface_remove(struct wmi_device *wdev)
> > +{
> > + mutex_lock(&bioscfg_drv.mutex);
> > + bioscfg_drv.password_attr_wdev = NULL;
> > + mutex_unlock(&bioscfg_drv.mutex);
> > +}
> > +
> > +static const struct wmi_device_id bios_attr_pass_interface_id_table[] = {
> > + { .guid_string = HP_WMI_SET_BIOS_SETTING_GUID },
> > + { },
>
> No comma after end-of-array marker.
>
> > +};
> > +static struct wmi_driver bios_attr_pass_interface_driver = {
> > + .driver = {
> > + .name = DRIVER_NAME"-password"
> > + },
> > + .probe = bios_attr_pass_interface_probe,
> > + .remove = bios_attr_pass_interface_remove,
> > + .id_table = bios_attr_pass_interface_id_table,
> > +};
> > +
> > +int init_bios_attr_pass_interface(void)
> > +{
> > + return wmi_driver_register(&bios_attr_pass_interface_driver);
> > +}
> > +
> > +void exit_bios_attr_pass_interface(void)
> > +{
> > + wmi_driver_unregister(&bios_attr_pass_interface_driver);
> > +}
> > +
> > +MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table);
> > --
> > 2.34.1
> >

2023-05-04 12:02:27

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v11 13/14] HP BIOSCFG driver - Makefile

Hi Jorge,

kernel test robot noticed the following build errors:

[auto build test ERROR on linus/master]
[also build test ERROR on v6.3 next-20230428]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Jorge-Lopez/HP-BIOSCFG-driver-Documentation/20230421-012956
base: linus/master
patch link: https://lore.kernel.org/r/20230420165454.9517-14-jorge.lopez2%40hp.com
patch subject: [PATCH v11 13/14] HP BIOSCFG driver - Makefile
config: i386-allyesconfig (https://download.01.org/0day-ci/archive/20230504/[email protected]/config)
compiler: gcc-11 (Debian 11.3.0-12) 11.3.0
reproduce (this is a W=1 build):
# https://github.com/intel-lab-lkp/linux/commit/55e860a942040b0d0f1b58362dc4885b1a8acaf2
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Jorge-Lopez/HP-BIOSCFG-driver-Documentation/20230421-012956
git checkout 55e860a942040b0d0f1b58362dc4885b1a8acaf2
# save the config file
mkdir build_dir && cp config build_dir/.config
make W=1 O=build_dir ARCH=i386 olddefconfig
make W=1 O=build_dir ARCH=i386 SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
| Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>):

ld: drivers/platform/x86/hp/hp-bioscfg/bioscfg.o: in function `get_wmiobj_pointer':
>> bioscfg.c:(.text+0x750): multiple definition of `get_wmiobj_pointer'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:sysman.c:(.text+0x830): first defined here
ld: drivers/platform/x86/hp/hp-bioscfg/bioscfg.o: in function `get_instance_count':
>> bioscfg.c:(.text+0x7ac): multiple definition of `get_instance_count'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:sysman.c:(.text+0x88c): first defined here
ld: drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.o: in function `init_bios_attr_set_interface':
>> biosattr-interface.c:(.text+0x4dc): multiple definition of `init_bios_attr_set_interface'; drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.o:biosattr-interface.c:(.text+0x33c): first defined here
ld: drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.o: in function `exit_bios_attr_set_interface':
>> biosattr-interface.c:(.text+0x4f8): multiple definition of `exit_bios_attr_set_interface'; drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.o:biosattr-interface.c:(.text+0x358): first defined here
ld: drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.o: in function `init_bios_attr_pass_interface':
>> passwdattr-interface.c:(.text+0x68): multiple definition of `init_bios_attr_pass_interface'; drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.o:passwordattr-interface.c:(.text+0x338): first defined here
ld: drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.o: in function `exit_bios_attr_pass_interface':
>> passwdattr-interface.c:(.text+0x84): multiple definition of `exit_bios_attr_pass_interface'; drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.o:passwordattr-interface.c:(.text+0x354): first defined here
ld: drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.o: in function `calculate_security_buffer':
>> spmobj-attributes.c:(.text+0x4c4): multiple definition of `calculate_security_buffer'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:sysman.c:(.text+0x714): first defined here
ld: drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.o: in function `populate_security_buffer':
>> spmobj-attributes.c:(.text+0x524): multiple definition of `populate_security_buffer'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:sysman.c:(.text+0x744): first defined here

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests

2023-05-04 13:56:50

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 13/14] HP BIOSCFG driver - Makefile

Hi

A quick note letting you know that the failures are reported when
trying to compile HP-BIOSCFG driver and it is including compile
sources from a DELL driver. No DELL portions of driver are and will
be included in HP-BIOSCFG driver.

ld: drivers/platform/x86/hp/hp-bioscfg/bioscfg.o: in function
`get_wmiobj_pointer':
>> bioscfg.c:(.text+0x750): multiple definition of `get_wmiobj_pointer'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:sysman.c:(.text+0x830): first defined here

Additionally, functions init_bios_attr_pass_interface() and
exit_bios_attr_pass_interface() were renamed and the new definition
will be included in patch v13 for HP-BIOSCFG driver.

Regards,

Jorge

On Thu, May 4, 2023 at 6:59 AM kernel test robot <[email protected]> wrote:
>
> Hi Jorge,
>
> kernel test robot noticed the following build errors:
>
> [auto build test ERROR on linus/master]
> [also build test ERROR on v6.3 next-20230428]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch#_base_tree_information]
>
> url: https://github.com/intel-lab-lkp/linux/commits/Jorge-Lopez/HP-BIOSCFG-driver-Documentation/20230421-012956
> base: linus/master
> patch link: https://lore.kernel.org/r/20230420165454.9517-14-jorge.lopez2%40hp.com
> patch subject: [PATCH v11 13/14] HP BIOSCFG driver - Makefile
> config: i386-allyesconfig (https://download.01.org/0day-ci/archive/20230504/[email protected]/config)
> compiler: gcc-11 (Debian 11.3.0-12) 11.3.0
> reproduce (this is a W=1 build):
> # https://github.com/intel-lab-lkp/linux/commit/55e860a942040b0d0f1b58362dc4885b1a8acaf2
> git remote add linux-review https://github.com/intel-lab-lkp/linux
> git fetch --no-tags linux-review Jorge-Lopez/HP-BIOSCFG-driver-Documentation/20230421-012956
> git checkout 55e860a942040b0d0f1b58362dc4885b1a8acaf2
> # save the config file
> mkdir build_dir && cp config build_dir/.config
> make W=1 O=build_dir ARCH=i386 olddefconfig
> make W=1 O=build_dir ARCH=i386 SHELL=/bin/bash
>
> If you fix the issue, kindly add following tag where applicable
> | Reported-by: kernel test robot <[email protected]>
> | Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/
>
> All errors (new ones prefixed by >>):
>
> ld: drivers/platform/x86/hp/hp-bioscfg/bioscfg.o: in function `get_wmiobj_pointer':
> >> bioscfg.c:(.text+0x750): multiple definition of `get_wmiobj_pointer'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:sysman.c:(.text+0x830): first defined here
> ld: drivers/platform/x86/hp/hp-bioscfg/bioscfg.o: in function `get_instance_count':
> >> bioscfg.c:(.text+0x7ac): multiple definition of `get_instance_count'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:sysman.c:(.text+0x88c): first defined here
> ld: drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.o: in function `init_bios_attr_set_interface':
> >> biosattr-interface.c:(.text+0x4dc): multiple definition of `init_bios_attr_set_interface'; drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.o:biosattr-interface.c:(.text+0x33c): first defined here
> ld: drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.o: in function `exit_bios_attr_set_interface':
> >> biosattr-interface.c:(.text+0x4f8): multiple definition of `exit_bios_attr_set_interface'; drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.o:biosattr-interface.c:(.text+0x358): first defined here
> ld: drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.o: in function `init_bios_attr_pass_interface':
> >> passwdattr-interface.c:(.text+0x68): multiple definition of `init_bios_attr_pass_interface'; drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.o:passwordattr-interface.c:(.text+0x338): first defined here
> ld: drivers/platform/x86/hp/hp-bioscfg/passwdattr-interface.o: in function `exit_bios_attr_pass_interface':
> >> passwdattr-interface.c:(.text+0x84): multiple definition of `exit_bios_attr_pass_interface'; drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.o:passwordattr-interface.c:(.text+0x354): first defined here
> ld: drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.o: in function `calculate_security_buffer':
> >> spmobj-attributes.c:(.text+0x4c4): multiple definition of `calculate_security_buffer'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:sysman.c:(.text+0x714): first defined here
> ld: drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.o: in function `populate_security_buffer':
> >> spmobj-attributes.c:(.text+0x524): multiple definition of `populate_security_buffer'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:sysman.c:(.text+0x744): first defined here
>
> --
> 0-DAY CI Kernel Test Service
> https://github.com/intel/lkp-tests

2023-05-04 16:18:34

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 09/14] HP BIOSCFG driver - enum-attributes

On Wed, May 3, 2023 at 3:10 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-05-03 14:42:37-0500, Jorge Lopez wrote:
> > On Sun, Apr 23, 2023 at 7:55 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-04-20 11:54:49-0500, Jorge Lopez wrote:
> > > > .../x86/hp/hp-bioscfg/enum-attributes.c | 543 ++++++++++++++++++
> > > > 1 file changed, 543 insertions(+)
> > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
> > > >
> > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
>
> <snip>
>
> > > > +/*
> > > > + * validate_enumeration_input() -
> > > > + * Validate input of current_value against possible values
> > > > + *
> > > > + * @instance_id: The instance on which input is validated
> > > > + * @buf: Input value
> > > > + */
> > > > +static int validate_enumeration_input(int instance_id, const char *buf)
> > > > +{
> > > > + int ret = 0;
> > > > + int found = 0;
> > > > + int i;
> > > > + int possible_values;
> > > > +
> > > > + /* Is it a read only attribute */
> > > > + if (bioscfg_drv.enumeration_data[instance_id].common.is_readonly)
> > > > + return -EIO;
> > > > +
> > > > + possible_values = bioscfg_drv.enumeration_data[instance_id].possible_values_size;
> > > > + for (i = 0; i < possible_values && !found; i++)
> > > > + if (!strcasecmp(bioscfg_drv.enumeration_data[instance_id].possible_values[i], buf))
> > >
> > > Is this also intentionally case-insensitive?
> >
> > Yes
>
> Why? It is surprising.
>
> The behavior differs from sysfs_match_string() and friends.
> Thinking about it, this function should be able to use
> __sysfs_match_string().
>
> (Same for the ordered list type)
>

I will not be able to use sysfs_match_string() for the conditions on
how the possible values are read and allocated by hp-bioscfg driver.

1. sysfs_match_string() expects the array to be defined as const char
* const array. This definition is possible if the values are
statically assigned but not when the values are dynamically allocated.

const char * const possible_values[] = {
[VALUE1] = "Enable",
[VALUE2] = "Disable",
};
...
ret = sysfs_match_string(possible_values, buf);

2. sysfs_match_string() returns error EINVAL if the string case does
not match. sysfs_match_string() string comparison is case sensitive.

3. BIOS rejects data if the data is not case sensitive nor matches
either one of the reported possible values.


> <snip>

2023-05-04 20:54:48

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 06/14] HP BIOSCFG driver - passwdobj-attributes

On Sun, Apr 23, 2023 at 4:07 AM <[email protected]> wrote:
>
> On 2023-04-20 11:54:46-0500, Jorge Lopez wrote:
> > ---
> > .../x86/hp/hp-bioscfg/passwdobj-attributes.c | 669 ++++++++++++++++++
> > 1 file changed, 669 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> >
> > diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> > new file mode 100644
> > index 000000000000..c03b3a71e9c4
> > --- /dev/null
> > +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> > @@ -0,0 +1,669 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*

<snip>

> > +int validate_password_input(int instance_id, const char *buf)
> > +{
> > + int length;
> > +
> > + length = strlen(buf);
> > + if (buf[length-1] == '\n')
> > + length--;
> > +
> > + if (length > MAX_PASSWD_SIZE)
> > + return INVALID_BIOS_AUTH;
> > +
> > + if (bioscfg_drv.password_data[instance_id].min_password_length > length ||
> > + bioscfg_drv.password_data[instance_id].max_password_length < length)
> > + return INVALID_BIOS_AUTH;
> > + return SUCCESS;
> > +}
> > +
> > +int password_is_set(const char *name)
>
> bool is_password_set(const char *name)

Function is invoked nowhere. It will be removed.
>
> > +{
> > + int id;
> > +
> > + id = get_password_instance_for_type(name);
> > + if (id < 0)
> > + return 0;
> > +
> > + return bioscfg_drv.password_data[id].is_enabled;
> > +}
> > +
> > +ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password);
> > +static struct kobj_attribute password_is_password_set = __ATTR_RO(is_enabled);
> > +
> > +static ssize_t current_password_store(struct kobject *kobj,
> > + struct kobj_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + char *p, *buf_cp;
> > + int id, ret = 0;
> > +
> > + buf_cp = kstrdup(buf, GFP_KERNEL);
> > + if (!buf_cp) {
> > + ret = -ENOMEM;
> > + goto exit_password;
> > + }
> > +
> > + p = memchr(buf_cp, '\n', count);
> > +
> > + if (p != NULL)
> > + *p = '\0';
>
> This will also accept input like "foo\nbar" and truncate away the "bar".
>
> For something like a password it seems errorprone to try to munge the
> value.

This is an expected behavior. If the user enters '\n' as part of the
password, the buffer data will be truncated since only one line per
sysfs file is permitted.

>
> > +
> > + id = get_password_instance_id(kobj);
> > +
> > + if (id >= 0)
> > + ret = validate_password_input(id, buf_cp);
> > +
> > + if (!ret) {
> > + strscpy(bioscfg_drv.password_data[id].current_password,
> > + buf_cp,
> > + sizeof(bioscfg_drv.password_data[id].current_password));
> > + /*
> > + * set pending reboot flag depending on
> > + * "RequiresPhysicalPresence" value
> > + */
> > + if (bioscfg_drv.password_data[id].common.requires_physical_presence)
> > + bioscfg_drv.pending_reboot = true;
>
> Just setting this to true does not emit the necessary KOBJ_CHANGE event
> on the class dev kobj which is necessary for userspace to be able to
> react.

This feature was added outside of the original design specification to
be used at a later time.
Changes to the value to true does not emit a KOBJ_CHANGE event.

>
> > + }
> > +
> > +exit_password:
> > + kfree(buf_cp);
> > + return ret ? ret : count;
> > +}
> > +static struct kobj_attribute password_current_password = __ATTR_WO(current_password);
> > +
> > +static ssize_t new_password_store(struct kobject *kobj,
> > + struct kobj_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + char *p, *buf_cp = NULL;
> > + int id = 0;
> > + int ret = -EIO;
> > +
> > + buf_cp = kstrdup(buf, GFP_KERNEL);
> > + if (!buf_cp) {
> > + ret = -ENOMEM;
> > + goto exit_password;
> > + }
> > +
> > + p = memchr(buf_cp, '\n', count);
> > +
> > + if (p != NULL)
> > + *p = '\0';
>
> Same as above.

This is an expected behavior. If the user enters '\n' as part of the
password, the buffer data will be truncated since only one line per
sysfs file is permitted.


>
> > +
> > + id = get_password_instance_id(kobj);
> > +
> > + if (id >= 0)
> > + ret = validate_password_input(id, buf_cp);
> > +
> > + if (!ret)
> > + strscpy(bioscfg_drv.password_data[id].new_password,
> > + buf_cp,
> > + sizeof(bioscfg_drv.password_data[id].new_password));
> > +
> > + if (!ret)
> > + ret = hp_set_attribute(kobj->name, buf_cp);
> > +
> > +exit_password:
> > + /*
> > + * Regardless of the results both new and current passwords
> > + * will be set to zero and avoid security issues
> > + */
> > + clear_passwords(id);
> > +
> > + kfree(buf_cp);
> > + return ret ? ret : count;
> > +}
> > +
> > +static struct kobj_attribute password_new_password = __ATTR_WO(new_password);
> > +
> > +
> > +ATTRIBUTE_N_PROPERTY_SHOW(min_password_length, password);
> > +static struct kobj_attribute password_min_password_length = __ATTR_RO(min_password_length);
> > +
> > +ATTRIBUTE_N_PROPERTY_SHOW(max_password_length, password);
> > +static struct kobj_attribute password_max_password_length = __ATTR_RO(max_password_length);
> > +
> > +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + if (strcmp(kobj->name, SETUP_PASSWD) == 0)
> > + return sysfs_emit(buf, "%s\n", BIOS_ADMIN);
> > +
> > + if (strcmp(kobj->name, POWER_ON_PASSWD) == 0)
> > + return sysfs_emit(buf, "%s\n", POWER_ON);
> > +
> > + return -EIO;
> > +}
> > +static struct kobj_attribute password_role = __ATTR_RO(role);
> > +
> > +static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + int i = get_password_instance_id(kobj);
> > +
> > + if (i < 0)
> > + return i;
> > +
> > + if (bioscfg_drv.password_data[i].mechanism != PASSWORD)
> > + return -EINVAL;
> > +
> > + return sysfs_emit(buf, "%s\n", PASSWD_MECHANISM_TYPES);
> > +}
> > +static struct kobj_attribute password_mechanism = __ATTR_RO(mechanism);
> > +
> > +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "password\n");
> > +}
> > +static struct kobj_attribute password_type = __ATTR_RO(type);
> > +
> > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, password);
> > +static struct kobj_attribute password_display_name =
> > + __ATTR_RO(display_name);
> > +
> > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, password);
> > +static struct kobj_attribute password_display_langcode =
> > + __ATTR_RO(display_name_language_code);
> > +
> > +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, password);
> > +static struct kobj_attribute password_prerequisites_size_val =
> > + __ATTR_RO(prerequisites_size);
> > +
> > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, password);
> > +static struct kobj_attribute password_prerequisites_val =
> > + __ATTR_RO(prerequisites);
> > +
> > +ATTRIBUTE_N_PROPERTY_SHOW(encodings_size, password);
> > +static struct kobj_attribute password_encodings_size_val =
> > + __ATTR_RO(encodings_size);
>
> As before, these size attribute are fairly pointless for userspace as
> they can't be relied on.

I will remove the attribute from being reported in sysfs but they will
be kept as part of the driver internal data

>
> > +
> > +ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password);
> > +static struct kobj_attribute password_encodings_val =
> > + __ATTR_RO(encodings);
> > +
> > +
> > +static struct attribute *password_attrs[] = {
> > + &password_is_password_set.attr,
> > + &password_min_password_length.attr,
> > + &password_max_password_length.attr,
> > + &password_current_password.attr,
> > + &password_new_password.attr,
> > + &password_role.attr,
> > + &password_mechanism.attr,
> > + &password_type.attr,
> > + &password_display_name.attr,
> > + &password_display_langcode.attr,
> > + &password_prerequisites_size_val.attr,
> > + &password_prerequisites_val.attr,
> > + &password_encodings_val.attr,
> > + &password_encodings_size_val.attr,
> > + NULL
> > +};
>
> Many of these attributes are not documented.

Those attributes are documented under authentication section, lines 150-329

What: /sys/class/firmware-attributes/*/authentication/
Date: February 2021
KernelVersion: 5.11
Contact: Divya Bharathi <[email protected]>,
Prasanth KSR <[email protected]>
[email protected]
Description:
Devices support various authentication mechanisms which can be exposed
as a separate configuration object.



>
> > +
> > +static const struct attribute_group bios_password_attr_group = {
> > + .attrs = password_attrs
> > +};
> > +
> > +static const struct attribute_group system_password_attr_group = {
> > + .attrs = password_attrs
> > +};
>
> These groups are the same, are both needed?

Yes. They will show under 'Setup Password' and 'Power-on password'

>
> > +
> > +int alloc_password_data(void)
> > +{
> > + int ret = 0;
> > +
> > + bioscfg_drv.password_instances_count = get_instance_count(HP_WMI_BIOS_PASSWORD_GUID);
> > + bioscfg_drv.password_data = kcalloc(bioscfg_drv.password_instances_count,
> > + sizeof(struct password_data), GFP_KERNEL);
>
> sizeof(bioscfg_drv.password_data)
>
> > + if (!bioscfg_drv.password_data) {
> > + bioscfg_drv.password_instances_count = 0;
> > + ret = -ENOMEM;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +/*
> > + * populate_password_package_data -
> > + * Populate all properties for an instance under password attribute
> > + *
> > + * @password_obj: ACPI object with password data
> > + * @instance_id: The instance to enumerate
> > + * @attr_name_kobj: The parent kernel object
> > + */
> > +int populate_password_package_data(union acpi_object *password_obj, int instance_id,
> > + struct kobject *attr_name_kobj)
> > +{
> > + bioscfg_drv.password_data[instance_id].attr_name_kobj = attr_name_kobj;
> > +
> > + populate_password_elements_from_package(password_obj,
> > + password_obj->package.count,
> > + instance_id);
> > +
> > + if (strcmp(attr_name_kobj->name, "Setup Password") == 0) {
>
> SETUP_PASSWD

Done!

>
<snip>

2023-05-04 21:16:48

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 06/14] HP BIOSCFG driver - passwdobj-attributes

On 2023-05-04 15:29:21-0500, Jorge Lopez wrote:
> On Sun, Apr 23, 2023 at 4:07 AM <[email protected]> wrote:
> >
> > On 2023-04-20 11:54:46-0500, Jorge Lopez wrote:
> > > ---
> > > .../x86/hp/hp-bioscfg/passwdobj-attributes.c | 669 ++++++++++++++++++
> > > 1 file changed, 669 insertions(+)
> > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> > >
> > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> > > new file mode 100644
> > > index 000000000000..c03b3a71e9c4
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> > > @@ -0,0 +1,669 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
>
> <snip>
>
> > > +int validate_password_input(int instance_id, const char *buf)
> > > +{
> > > + int length;
> > > +
> > > + length = strlen(buf);
> > > + if (buf[length-1] == '\n')
> > > + length--;
> > > +
> > > + if (length > MAX_PASSWD_SIZE)
> > > + return INVALID_BIOS_AUTH;
> > > +
> > > + if (bioscfg_drv.password_data[instance_id].min_password_length > length ||
> > > + bioscfg_drv.password_data[instance_id].max_password_length < length)
> > > + return INVALID_BIOS_AUTH;
> > > + return SUCCESS;
> > > +}
> > > +
> > > +int password_is_set(const char *name)
> >
> > bool is_password_set(const char *name)
>
> Function is invoked nowhere. It will be removed.
> >
> > > +{
> > > + int id;
> > > +
> > > + id = get_password_instance_for_type(name);
> > > + if (id < 0)
> > > + return 0;
> > > +
> > > + return bioscfg_drv.password_data[id].is_enabled;
> > > +}
> > > +
> > > +ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password);
> > > +static struct kobj_attribute password_is_password_set = __ATTR_RO(is_enabled);
> > > +
> > > +static ssize_t current_password_store(struct kobject *kobj,
> > > + struct kobj_attribute *attr,
> > > + const char *buf, size_t count)
> > > +{
> > > + char *p, *buf_cp;
> > > + int id, ret = 0;
> > > +
> > > + buf_cp = kstrdup(buf, GFP_KERNEL);
> > > + if (!buf_cp) {
> > > + ret = -ENOMEM;
> > > + goto exit_password;
> > > + }
> > > +
> > > + p = memchr(buf_cp, '\n', count);
> > > +
> > > + if (p != NULL)
> > > + *p = '\0';
> >
> > This will also accept input like "foo\nbar" and truncate away the "bar".
> >
> > For something like a password it seems errorprone to try to munge the
> > value.
>
> This is an expected behavior. If the user enters '\n' as part of the
> password, the buffer data will be truncated since only one line per
> sysfs file is permitted.

As discussed in another patch this would silently truncate the input on
the first newline character; even if it is not the last character of the
input string.

This should use the same helper as the other files to only strip a
newline at the end of the input.

> >
> > > +
> > > + id = get_password_instance_id(kobj);
> > > +
> > > + if (id >= 0)
> > > + ret = validate_password_input(id, buf_cp);
> > > +
> > > + if (!ret) {
> > > + strscpy(bioscfg_drv.password_data[id].current_password,
> > > + buf_cp,
> > > + sizeof(bioscfg_drv.password_data[id].current_password));
> > > + /*
> > > + * set pending reboot flag depending on
> > > + * "RequiresPhysicalPresence" value
> > > + */
> > > + if (bioscfg_drv.password_data[id].common.requires_physical_presence)
> > > + bioscfg_drv.pending_reboot = true;
> >
> > Just setting this to true does not emit the necessary KOBJ_CHANGE event
> > on the class dev kobj which is necessary for userspace to be able to
> > react.
>
> This feature was added outside of the original design specification to
> be used at a later time.
> Changes to the value to true does not emit a KOBJ_CHANGE event.

This contradicts the documentation:

A read-only attribute reads 1 if a reboot is necessary to apply
pending BIOS attribute changes. Also, an uevent_KOBJ_CHANGE is
generated when it changes to 1.

This will confuse userspace, there are generic userspace applications
waiting for those events.
If there is a reason for not emitting them it should be good and
documented.

Also according to the docs the authentication attributes should
KOBJ_CHANGE events. I think this also affects this driver and should be
implemented.

> >
> > > + }
> > > +
> > > +exit_password:
> > > + kfree(buf_cp);
> > > + return ret ? ret : count;
> > > +}
> > > +static struct kobj_attribute password_current_password = __ATTR_WO(current_password);
> > > +
> > > +static ssize_t new_password_store(struct kobject *kobj,
> > > + struct kobj_attribute *attr,
> > > + const char *buf, size_t count)
> > > +{
> > > + char *p, *buf_cp = NULL;
> > > + int id = 0;
> > > + int ret = -EIO;
> > > +
> > > + buf_cp = kstrdup(buf, GFP_KERNEL);
> > > + if (!buf_cp) {
> > > + ret = -ENOMEM;
> > > + goto exit_password;
> > > + }
> > > +
> > > + p = memchr(buf_cp, '\n', count);
> > > +
> > > + if (p != NULL)
> > > + *p = '\0';
> >
> > Same as above.
>
> This is an expected behavior. If the user enters '\n' as part of the
> password, the buffer data will be truncated since only one line per
> sysfs file is permitted.

If a user accidentally presses enter before entering a password this
may set the password to the empty string; surprising.

This should really use the helper.

> >
> > > +
> > > + id = get_password_instance_id(kobj);
> > > +
> > > + if (id >= 0)
> > > + ret = validate_password_input(id, buf_cp);
> > > +
> > > + if (!ret)
> > > + strscpy(bioscfg_drv.password_data[id].new_password,
> > > + buf_cp,
> > > + sizeof(bioscfg_drv.password_data[id].new_password));
> > > +
> > > + if (!ret)
> > > + ret = hp_set_attribute(kobj->name, buf_cp);
> > > +
> > > +exit_password:
> > > + /*
> > > + * Regardless of the results both new and current passwords
> > > + * will be set to zero and avoid security issues
> > > + */
> > > + clear_passwords(id);
> > > +
> > > + kfree(buf_cp);
> > > + return ret ? ret : count;
> > > +}
> > > +
> > > +static struct kobj_attribute password_new_password = __ATTR_WO(new_password);
> > > +
> > > +
> > > +ATTRIBUTE_N_PROPERTY_SHOW(min_password_length, password);
> > > +static struct kobj_attribute password_min_password_length = __ATTR_RO(min_password_length);
> > > +
> > > +ATTRIBUTE_N_PROPERTY_SHOW(max_password_length, password);
> > > +static struct kobj_attribute password_max_password_length = __ATTR_RO(max_password_length);
> > > +
> > > +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
> > > + char *buf)
> > > +{
> > > + if (strcmp(kobj->name, SETUP_PASSWD) == 0)
> > > + return sysfs_emit(buf, "%s\n", BIOS_ADMIN);
> > > +
> > > + if (strcmp(kobj->name, POWER_ON_PASSWD) == 0)
> > > + return sysfs_emit(buf, "%s\n", POWER_ON);
> > > +
> > > + return -EIO;
> > > +}
> > > +static struct kobj_attribute password_role = __ATTR_RO(role);
> > > +
> > > +static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
> > > + char *buf)
> > > +{
> > > + int i = get_password_instance_id(kobj);
> > > +
> > > + if (i < 0)
> > > + return i;
> > > +
> > > + if (bioscfg_drv.password_data[i].mechanism != PASSWORD)
> > > + return -EINVAL;
> > > +
> > > + return sysfs_emit(buf, "%s\n", PASSWD_MECHANISM_TYPES);
> > > +}
> > > +static struct kobj_attribute password_mechanism = __ATTR_RO(mechanism);
> > > +
> > > +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> > > + char *buf)
> > > +{
> > > + return sysfs_emit(buf, "password\n");
> > > +}
> > > +static struct kobj_attribute password_type = __ATTR_RO(type);
> > > +
> > > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, password);
> > > +static struct kobj_attribute password_display_name =
> > > + __ATTR_RO(display_name);
> > > +
> > > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, password);
> > > +static struct kobj_attribute password_display_langcode =
> > > + __ATTR_RO(display_name_language_code);
> > > +
> > > +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, password);
> > > +static struct kobj_attribute password_prerequisites_size_val =
> > > + __ATTR_RO(prerequisites_size);
> > > +
> > > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, password);
> > > +static struct kobj_attribute password_prerequisites_val =
> > > + __ATTR_RO(prerequisites);
> > > +
> > > +ATTRIBUTE_N_PROPERTY_SHOW(encodings_size, password);
> > > +static struct kobj_attribute password_encodings_size_val =
> > > + __ATTR_RO(encodings_size);
> >
> > As before, these size attribute are fairly pointless for userspace as
> > they can't be relied on.
>
> I will remove the attribute from being reported in sysfs but they will
> be kept as part of the driver internal data
>
> >
> > > +
> > > +ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password);
> > > +static struct kobj_attribute password_encodings_val =
> > > + __ATTR_RO(encodings);
> > > +
> > > +
> > > +static struct attribute *password_attrs[] = {
> > > + &password_is_password_set.attr,
> > > + &password_min_password_length.attr,
> > > + &password_max_password_length.attr,
> > > + &password_current_password.attr,
> > > + &password_new_password.attr,
> > > + &password_role.attr,
> > > + &password_mechanism.attr,
> > > + &password_type.attr,
> > > + &password_display_name.attr,
> > > + &password_display_langcode.attr,
> > > + &password_prerequisites_size_val.attr,
> > > + &password_prerequisites_val.attr,
> > > + &password_encodings_val.attr,
> > > + &password_encodings_size_val.attr,
> > > + NULL
> > > +};
> >
> > Many of these attributes are not documented.
>
> Those attributes are documented under authentication section, lines 150-329
>
> What: /sys/class/firmware-attributes/*/authentication/
> Date: February 2021
> KernelVersion: 5.11
> Contact: Divya Bharathi <[email protected]>,
> Prasanth KSR <[email protected]>
> [email protected]
> Description:
> Devices support various authentication mechanisms which can be exposed
> as a separate configuration object.

If I read that correctly the authentication attributes are not "normal"
attributes.
So they don't need "type", "display_name", "display_langcode".

Do they need prerequisites?

>
>
> >
> > > +
> > > +static const struct attribute_group bios_password_attr_group = {
> > > + .attrs = password_attrs
> > > +};
> > > +
> > > +static const struct attribute_group system_password_attr_group = {
> > > + .attrs = password_attrs
> > > +};
> >
> > These groups are the same, are both needed?
>
> Yes. They will show under 'Setup Password' and 'Power-on password'

These are identical constant structures. It should be possible to have
only one and use it for both usecases.

<snip>

2023-05-04 22:15:12

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 06/14] HP BIOSCFG driver - passwdobj-attributes

On Thu, May 4, 2023 at 3:59 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-05-04 15:29:21-0500, Jorge Lopez wrote:
> > On Sun, Apr 23, 2023 at 4:07 AM <[email protected]> wrote:
> > >
> > > On 2023-04-20 11:54:46-0500, Jorge Lopez wrote:
> > > > ---
> > > > .../x86/hp/hp-bioscfg/passwdobj-attributes.c | 669 ++++++++++++++++++
> > > > 1 file changed, 669 insertions(+)
> > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> > > >
> > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> > > > new file mode 100644
> > > > index 000000000000..c03b3a71e9c4
> > > > --- /dev/null
> > > > +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
> > > > @@ -0,0 +1,669 @@
> > > > +// SPDX-License-Identifier: GPL-2.0
> > > > +/*
> >
> > <snip>
> >
> > > > +int validate_password_input(int instance_id, const char *buf)
> > > > +{
> > > > + int length;
> > > > +
> > > > + length = strlen(buf);
> > > > + if (buf[length-1] == '\n')
> > > > + length--;
> > > > +
> > > > + if (length > MAX_PASSWD_SIZE)
> > > > + return INVALID_BIOS_AUTH;
> > > > +
> > > > + if (bioscfg_drv.password_data[instance_id].min_password_length > length ||
> > > > + bioscfg_drv.password_data[instance_id].max_password_length < length)
> > > > + return INVALID_BIOS_AUTH;
> > > > + return SUCCESS;
> > > > +}
> > > > +
> > > > +int password_is_set(const char *name)
> > >
> > > bool is_password_set(const char *name)
> >
> > Function is invoked nowhere. It will be removed.
> > >
> > > > +{
> > > > + int id;
> > > > +
> > > > + id = get_password_instance_for_type(name);
> > > > + if (id < 0)
> > > > + return 0;
> > > > +
> > > > + return bioscfg_drv.password_data[id].is_enabled;
> > > > +}
> > > > +
> > > > +ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password);
> > > > +static struct kobj_attribute password_is_password_set = __ATTR_RO(is_enabled);
> > > > +
> > > > +static ssize_t current_password_store(struct kobject *kobj,
> > > > + struct kobj_attribute *attr,
> > > > + const char *buf, size_t count)
> > > > +{
> > > > + char *p, *buf_cp;
> > > > + int id, ret = 0;
> > > > +
> > > > + buf_cp = kstrdup(buf, GFP_KERNEL);
> > > > + if (!buf_cp) {
> > > > + ret = -ENOMEM;
> > > > + goto exit_password;
> > > > + }
> > > > +
> > > > + p = memchr(buf_cp, '\n', count);
> > > > +
> > > > + if (p != NULL)
> > > > + *p = '\0';
> > >
> > > This will also accept input like "foo\nbar" and truncate away the "bar".
> > >
> > > For something like a password it seems errorprone to try to munge the
> > > value.
> >
> > This is an expected behavior. If the user enters '\n' as part of the
> > password, the buffer data will be truncated since only one line per
> > sysfs file is permitted.
>
> As discussed in another patch this would silently truncate the input on
> the first newline character; even if it is not the last character of the
> input string.
>
> This should use the same helper as the other files to only strip a
> newline at the end of the input.
>

Done! enforce_single_line_input() function is used.

> > >
> > > > +
> > > > + id = get_password_instance_id(kobj);
> > > > +
> > > > + if (id >= 0)
> > > > + ret = validate_password_input(id, buf_cp);
> > > > +
> > > > + if (!ret) {
> > > > + strscpy(bioscfg_drv.password_data[id].current_password,
> > > > + buf_cp,
> > > > + sizeof(bioscfg_drv.password_data[id].current_password));
> > > > + /*
> > > > + * set pending reboot flag depending on
> > > > + * "RequiresPhysicalPresence" value
> > > > + */
> > > > + if (bioscfg_drv.password_data[id].common.requires_physical_presence)
> > > > + bioscfg_drv.pending_reboot = true;
> > >
> > > Just setting this to true does not emit the necessary KOBJ_CHANGE event
> > > on the class dev kobj which is necessary for userspace to be able to
> > > react.
> >
> > This feature was added outside of the original design specification to
> > be used at a later time.
> > Changes to the value to true does not emit a KOBJ_CHANGE event.
>
> This contradicts the documentation:
>
> A read-only attribute reads 1 if a reboot is necessary to apply
> pending BIOS attribute changes. Also, an uevent_KOBJ_CHANGE is
> generated when it changes to 1.
>
> This will confuse userspace, there are generic userspace applications
> waiting for those events.
> If there is a reason for not emitting them it should be good and
> documented.
>
> Also according to the docs the authentication attributes should
> KOBJ_CHANGE events. I think this also affects this driver and should be
> implemented.
>

HP-BIOSCFG initially is not intended for the use of fwupd tool nor was
it tested.
This does not mean the driver will interface with fwupd and other
tools in the future.

> > >
> > > > + }
> > > > +
> > > > +exit_password:
> > > > + kfree(buf_cp);
> > > > + return ret ? ret : count;
> > > > +}
> > > > +static struct kobj_attribute password_current_password = __ATTR_WO(current_password);
> > > > +
> > > > +static ssize_t new_password_store(struct kobject *kobj,
> > > > + struct kobj_attribute *attr,
> > > > + const char *buf, size_t count)
> > > > +{
> > > > + char *p, *buf_cp = NULL;
> > > > + int id = 0;
> > > > + int ret = -EIO;
> > > > +
> > > > + buf_cp = kstrdup(buf, GFP_KERNEL);
> > > > + if (!buf_cp) {
> > > > + ret = -ENOMEM;
> > > > + goto exit_password;
> > > > + }
> > > > +
> > > > + p = memchr(buf_cp, '\n', count);
> > > > +
> > > > + if (p != NULL)
> > > > + *p = '\0';
> > >
> > > Same as above.
> >
> > This is an expected behavior. If the user enters '\n' as part of the
> > password, the buffer data will be truncated since only one line per
> > sysfs file is permitted.
>
> If a user accidentally presses enter before entering a password this
> may set the password to the empty string; surprising.
>
> This should really use the helper.

Done! enforce_single_line_input() function is used.
>
> > >
<snip>
> > > > +
> > > > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, password);
> > > > +static struct kobj_attribute password_prerequisites_val =
> > > > + __ATTR_RO(prerequisites);
> > > > +
> > > > +ATTRIBUTE_N_PROPERTY_SHOW(encodings_size, password);
> > > > +static struct kobj_attribute password_encodings_size_val =
> > > > + __ATTR_RO(encodings_size);
> > >
> > > As before, these size attribute are fairly pointless for userspace as
> > > they can't be relied on.
> >
> > I will remove the attribute from being reported in sysfs but they will
> > be kept as part of the driver internal data
> >
> > >
> > > > +
> > > > +ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password);
> > > > +static struct kobj_attribute password_encodings_val =
> > > > + __ATTR_RO(encodings);
> > > > +
> > > > +
> > > > +static struct attribute *password_attrs[] = {
> > > > + &password_is_password_set.attr,
> > > > + &password_min_password_length.attr,
> > > > + &password_max_password_length.attr,
> > > > + &password_current_password.attr,
> > > > + &password_new_password.attr,
> > > > + &password_role.attr,
> > > > + &password_mechanism.attr,
> > > > + &password_type.attr,
> > > > + &password_display_name.attr,
> > > > + &password_display_langcode.attr,
> > > > + &password_prerequisites_size_val.attr,
> > > > + &password_prerequisites_val.attr,
> > > > + &password_encodings_val.attr,
> > > > + &password_encodings_size_val.attr,
> > > > + NULL
> > > > +};
> > >
> > > Many of these attributes are not documented.
> >
> > Those attributes are documented under authentication section, lines 150-329
> >
> > What: /sys/class/firmware-attributes/*/authentication/
> > Date: February 2021
> > KernelVersion: 5.11
> > Contact: Divya Bharathi <[email protected]>,
> > Prasanth KSR <[email protected]>
> > [email protected]
> > Description:
> > Devices support various authentication mechanisms which can be exposed
> > as a separate configuration object.
>
> If I read that correctly the authentication attributes are not "normal"
> attributes.
> So they don't need "type", "display_name", "display_langcode".
>

Type, display_name, and display_langcode are required and default settings.
See documentation lines 15-52

type:
A file that can be read to obtain the type of attribute.
This attribute is mandatory.

display_name:
A file that can be read to obtain a user friendly
description of the at <attr>

display_name_language_code:
A file that can be read to obtain
the IETF language tag corresponding to the
"display_name" of the <attr>

> Do they need prerequisites?

Prerequisites is optional and not documented. I will remove it from
the list of items reported within sysfs
>
> >
> >
> > >
> > > > +
> > > > +static const struct attribute_group bios_password_attr_group = {
> > > > + .attrs = password_attrs
> > > > +};
> > > > +
> > > > +static const struct attribute_group system_password_attr_group = {
> > > > + .attrs = password_attrs
> > > > +};
> > >
> > > These groups are the same, are both needed?
> >
> > Yes. They will show under 'Setup Password' and 'Power-on password'
>
> These are identical constant structures. It should be possible to have
> only one and use it for both usecases.
>

Both 'Setup Password' and 'Power-on password' need to coexist hence
the reason for keeping them separate.
Both attributes share the same helper routines. Unifying both
passwords into a single structure adds unnecessary complexity.



> <snip>

2023-05-04 22:42:37

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 06/14] HP BIOSCFG driver - passwdobj-attributes

On 2023-05-04 16:34:06-0500, Jorge Lopez wrote:
> On Thu, May 4, 2023 at 3:59 PM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-05-04 15:29:21-0500, Jorge Lopez wrote:
> > > On Sun, Apr 23, 2023 at 4:07 AM <[email protected]> wrote:
> > > >
> > > > On 2023-04-20 11:54:46-0500, Jorge Lopez wrote:
> > > > > ---
> > > > > .../x86/hp/hp-bioscfg/passwdobj-attributes.c | 669 ++++++++++++++++++
> > > > > 1 file changed, 669 insertions(+)
> > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c

<snip>

> > > > > +
> > > > > + id = get_password_instance_id(kobj);
> > > > > +
> > > > > + if (id >= 0)
> > > > > + ret = validate_password_input(id, buf_cp);
> > > > > +
> > > > > + if (!ret) {
> > > > > + strscpy(bioscfg_drv.password_data[id].current_password,
> > > > > + buf_cp,
> > > > > + sizeof(bioscfg_drv.password_data[id].current_password));
> > > > > + /*
> > > > > + * set pending reboot flag depending on
> > > > > + * "RequiresPhysicalPresence" value
> > > > > + */
> > > > > + if (bioscfg_drv.password_data[id].common.requires_physical_presence)
> > > > > + bioscfg_drv.pending_reboot = true;
> > > >
> > > > Just setting this to true does not emit the necessary KOBJ_CHANGE event
> > > > on the class dev kobj which is necessary for userspace to be able to
> > > > react.
> > >
> > > This feature was added outside of the original design specification to
> > > be used at a later time.
> > > Changes to the value to true does not emit a KOBJ_CHANGE event.
> >
> > This contradicts the documentation:
> >
> > A read-only attribute reads 1 if a reboot is necessary to apply
> > pending BIOS attribute changes. Also, an uevent_KOBJ_CHANGE is
> > generated when it changes to 1.
> >
> > This will confuse userspace, there are generic userspace applications
> > waiting for those events.
> > If there is a reason for not emitting them it should be good and
> > documented.
> >
> > Also according to the docs the authentication attributes should
> > KOBJ_CHANGE events. I think this also affects this driver and should be
> > implemented.
> >
>
> HP-BIOSCFG initially is not intended for the use of fwupd tool nor was
> it tested.
> This does not mean the driver will interface with fwupd and other
> tools in the future.

There are probably more tools than fwupd using this ABI.

The driver is implementing a well-known ABI and users of this ABI expect
it to work as documented.

Emitting these events seems straigtforward and simple.

Maybe Hans can give more guidance on it.

> > > >
> > > > > + }
> > > > > +
> > > > > +exit_password:
> > > > > + kfree(buf_cp);
> > > > > + return ret ? ret : count;
> > > > > +}
> > > > > +static struct kobj_attribute password_current_password = __ATTR_WO(current_password);
> > > > > +
> > > > > +static ssize_t new_password_store(struct kobject *kobj,
> > > > > + struct kobj_attribute *attr,
> > > > > + const char *buf, size_t count)
> > > > > +{
> > > > > + char *p, *buf_cp = NULL;
> > > > > + int id = 0;
> > > > > + int ret = -EIO;
> > > > > +
> > > > > + buf_cp = kstrdup(buf, GFP_KERNEL);
> > > > > + if (!buf_cp) {
> > > > > + ret = -ENOMEM;
> > > > > + goto exit_password;
> > > > > + }
> > > > > +
> > > > > + p = memchr(buf_cp, '\n', count);
> > > > > +
> > > > > + if (p != NULL)
> > > > > + *p = '\0';
> > > >
> > > > Same as above.
> > >
> > > This is an expected behavior. If the user enters '\n' as part of the
> > > password, the buffer data will be truncated since only one line per
> > > sysfs file is permitted.
> >
> > If a user accidentally presses enter before entering a password this
> > may set the password to the empty string; surprising.
> >
> > This should really use the helper.
>
> Done! enforce_single_line_input() function is used.
> >
> > > >
> <snip>
> > > > > +
> > > > > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, password);
> > > > > +static struct kobj_attribute password_prerequisites_val =
> > > > > + __ATTR_RO(prerequisites);
> > > > > +
> > > > > +ATTRIBUTE_N_PROPERTY_SHOW(encodings_size, password);
> > > > > +static struct kobj_attribute password_encodings_size_val =
> > > > > + __ATTR_RO(encodings_size);
> > > >
> > > > As before, these size attribute are fairly pointless for userspace as
> > > > they can't be relied on.
> > >
> > > I will remove the attribute from being reported in sysfs but they will
> > > be kept as part of the driver internal data
> > >
> > > >
> > > > > +
> > > > > +ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password);
> > > > > +static struct kobj_attribute password_encodings_val =
> > > > > + __ATTR_RO(encodings);
> > > > > +
> > > > > +
> > > > > +static struct attribute *password_attrs[] = {
> > > > > + &password_is_password_set.attr,
> > > > > + &password_min_password_length.attr,
> > > > > + &password_max_password_length.attr,
> > > > > + &password_current_password.attr,
> > > > > + &password_new_password.attr,
> > > > > + &password_role.attr,
> > > > > + &password_mechanism.attr,
> > > > > + &password_type.attr,
> > > > > + &password_display_name.attr,
> > > > > + &password_display_langcode.attr,
> > > > > + &password_prerequisites_size_val.attr,
> > > > > + &password_prerequisites_val.attr,
> > > > > + &password_encodings_val.attr,
> > > > > + &password_encodings_size_val.attr,
> > > > > + NULL
> > > > > +};
> > > >
> > > > Many of these attributes are not documented.
> > >
> > > Those attributes are documented under authentication section, lines 150-329
> > >
> > > What: /sys/class/firmware-attributes/*/authentication/
> > > Date: February 2021
> > > KernelVersion: 5.11
> > > Contact: Divya Bharathi <[email protected]>,
> > > Prasanth KSR <[email protected]>
> > > [email protected]
> > > Description:
> > > Devices support various authentication mechanisms which can be exposed
> > > as a separate configuration object.
> >
> > If I read that correctly the authentication attributes are not "normal"
> > attributes.
> > So they don't need "type", "display_name", "display_langcode".
> >
>
> Type, display_name, and display_langcode are required and default settings.
> See documentation lines 15-52
>
> type:
> A file that can be read to obtain the type of attribute.
> This attribute is mandatory.
>
> display_name:
> A file that can be read to obtain a user friendly
> description of the at <attr>
>
> display_name_language_code:
> A file that can be read to obtain
> the IETF language tag corresponding to the
> "display_name" of the <attr>

They are required for

/sys/class/firmware-attributes/*/attributes/*/

but here we implement

/sys/class/firmware-attributes/*/authentication/*/

which is a different ABI.

>
> > Do they need prerequisites?
>
> Prerequisites is optional and not documented. I will remove it from
> the list of items reported within sysfs
> >
> > >
> > >
> > > >
> > > > > +
> > > > > +static const struct attribute_group bios_password_attr_group = {
> > > > > + .attrs = password_attrs
> > > > > +};
> > > > > +
> > > > > +static const struct attribute_group system_password_attr_group = {
> > > > > + .attrs = password_attrs
> > > > > +};
> > > >
> > > > These groups are the same, are both needed?
> > >
> > > Yes. They will show under 'Setup Password' and 'Power-on password'
> >
> > These are identical constant structures. It should be possible to have
> > only one and use it for both usecases.
> >
>
> Both 'Setup Password' and 'Power-on password' need to coexist hence
> the reason for keeping them separate.
> Both attributes share the same helper routines. Unifying both
> passwords into a single structure adds unnecessary complexity.

They are already sharing the "password_attrs" array and all the
attributes listed in it.

It seems they could also share the attribute_group which does not really
contain any data.

2023-05-05 14:50:35

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 06/14] HP BIOSCFG driver - passwdobj-attributes

On Thu, May 4, 2023 at 5:21 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-05-04 16:34:06-0500, Jorge Lopez wrote:
> > On Thu, May 4, 2023 at 3:59 PM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-05-04 15:29:21-0500, Jorge Lopez wrote:
> > > > On Sun, Apr 23, 2023 at 4:07 AM <[email protected]> wrote:
> > > > >
> > > > > On 2023-04-20 11:54:46-0500, Jorge Lopez wrote:
> > > > > > ---
> > > > > > .../x86/hp/hp-bioscfg/passwdobj-attributes.c | 669 ++++++++++++++++++
> > > > > > 1 file changed, 669 insertions(+)
> > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
>
> <snip>
>
> > > > > > +
> > > > > > + id = get_password_instance_id(kobj);
> > > > > > +
> > > > > > + if (id >= 0)
> > > > > > + ret = validate_password_input(id, buf_cp);
> > > > > > +
> > > > > > + if (!ret) {
> > > > > > + strscpy(bioscfg_drv.password_data[id].current_password,
> > > > > > + buf_cp,
> > > > > > + sizeof(bioscfg_drv.password_data[id].current_password));
> > > > > > + /*
> > > > > > + * set pending reboot flag depending on
> > > > > > + * "RequiresPhysicalPresence" value
> > > > > > + */
> > > > > > + if (bioscfg_drv.password_data[id].common.requires_physical_presence)
> > > > > > + bioscfg_drv.pending_reboot = true;
> > > > >
> > > > > Just setting this to true does not emit the necessary KOBJ_CHANGE event
> > > > > on the class dev kobj which is necessary for userspace to be able to
> > > > > react.
> > > >
> > > > This feature was added outside of the original design specification to
> > > > be used at a later time.
> > > > Changes to the value to true does not emit a KOBJ_CHANGE event.
> > >
> > > This contradicts the documentation:
> > >
> > > A read-only attribute reads 1 if a reboot is necessary to apply
> > > pending BIOS attribute changes. Also, an uevent_KOBJ_CHANGE is
> > > generated when it changes to 1.
> > >
> > > This will confuse userspace, there are generic userspace applications
> > > waiting for those events.
> > > If there is a reason for not emitting them it should be good and
> > > documented.
> > >
> > > Also according to the docs the authentication attributes should
> > > KOBJ_CHANGE events. I think this also affects this driver and should be
> > > implemented.
> > >
> >
> > HP-BIOSCFG initially is not intended for the use of fwupd tool nor was
> > it tested.
> > This does not mean the driver will interface with fwupd and other
> > tools in the future.
>
> There are probably more tools than fwupd using this ABI.
>
> The driver is implementing a well-known ABI and users of this ABI expect
> it to work as documented.
>
> Emitting these events seems straigtforward and simple.
>
> Maybe Hans can give more guidance on it.
>

Let me see how I can implement it by following the sample of other drivers.
I will reachout to Hans as my last resource.

Done!
> > > > >
> > > > > > + }
> > > > > > +
> > > > > > +exit_password:
> > > > > > + kfree(buf_cp);
> > > > > > + return ret ? ret : count;
> > > > > > +}
> > > > > > +static struct kobj_attribute password_current_password = __ATTR_WO(current_password);
> > > > > > +
> > > > > > +static ssize_t new_password_store(struct kobject *kobj,
> > > > > > + struct kobj_attribute *attr,
> > > > > > + const char *buf, size_t count)
> > > > > > +{
> > > > > > + char *p, *buf_cp = NULL;
> > > > > > + int id = 0;
> > > > > > + int ret = -EIO;
> > > > > > +
> > > > > > + buf_cp = kstrdup(buf, GFP_KERNEL);
> > > > > > + if (!buf_cp) {
> > > > > > + ret = -ENOMEM;
> > > > > > + goto exit_password;
> > > > > > + }
> > > > > > +
> > > > > > + p = memchr(buf_cp, '\n', count);
> > > > > > +
> > > > > > + if (p != NULL)
> > > > > > + *p = '\0';
> > > > >
> > > > > Same as above.
> > > >
> > > > This is an expected behavior. If the user enters '\n' as part of the
> > > > password, the buffer data will be truncated since only one line per
> > > > sysfs file is permitted.
> > >
> > > If a user accidentally presses enter before entering a password this
> > > may set the password to the empty string; surprising.
> > >
> > > This should really use the helper.
> >
> > Done! enforce_single_line_input() function is used.
> > >
> > > > >
> > <snip>
> > > > > > +
> > > > > > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, password);
> > > > > > +static struct kobj_attribute password_prerequisites_val =
> > > > > > + __ATTR_RO(prerequisites);
> > > > > > +
> > > > > > +ATTRIBUTE_N_PROPERTY_SHOW(encodings_size, password);
> > > > > > +static struct kobj_attribute password_encodings_size_val =
> > > > > > + __ATTR_RO(encodings_size);
> > > > >
> > > > > As before, these size attribute are fairly pointless for userspace as
> > > > > they can't be relied on.
> > > >
> > > > I will remove the attribute from being reported in sysfs but they will
> > > > be kept as part of the driver internal data
> > > >
> > > > >
> > > > > > +
> > > > > > +ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password);
> > > > > > +static struct kobj_attribute password_encodings_val =
> > > > > > + __ATTR_RO(encodings);
> > > > > > +
> > > > > > +
> > > > > > +static struct attribute *password_attrs[] = {
> > > > > > + &password_is_password_set.attr,
> > > > > > + &password_min_password_length.attr,
> > > > > > + &password_max_password_length.attr,
> > > > > > + &password_current_password.attr,
> > > > > > + &password_new_password.attr,
> > > > > > + &password_role.attr,
> > > > > > + &password_mechanism.attr,
> > > > > > + &password_type.attr,
> > > > > > + &password_display_name.attr,
> > > > > > + &password_display_langcode.attr,
> > > > > > + &password_prerequisites_size_val.attr,
> > > > > > + &password_prerequisites_val.attr,
> > > > > > + &password_encodings_val.attr,
> > > > > > + &password_encodings_size_val.attr,
> > > > > > + NULL
> > > > > > +};
> > > > >
> > > > > Many of these attributes are not documented.
> > > >
> > > > Those attributes are documented under authentication section, lines 150-329
> > > >
> > > > What: /sys/class/firmware-attributes/*/authentication/
> > > > Date: February 2021
> > > > KernelVersion: 5.11
> > > > Contact: Divya Bharathi <[email protected]>,
> > > > Prasanth KSR <[email protected]>
> > > > [email protected]
> > > > Description:
> > > > Devices support various authentication mechanisms which can be exposed
> > > > as a separate configuration object.
> > >
> > > If I read that correctly the authentication attributes are not "normal"
> > > attributes.
> > > So they don't need "type", "display_name", "display_langcode".
> > >
> >
> > Type, display_name, and display_langcode are required and default settings.
> > See documentation lines 15-52
> >
> > type:
> > A file that can be read to obtain the type of attribute.
> > This attribute is mandatory.
> >
> > display_name:
> > A file that can be read to obtain a user friendly
> > description of the at <attr>
> >
> > display_name_language_code:
> > A file that can be read to obtain
> > the IETF language tag corresponding to the
> > "display_name" of the <attr>
>
> They are required for
>
> /sys/class/firmware-attributes/*/attributes/*/
>
> but here we implement
>
> /sys/class/firmware-attributes/*/authentication/*/
>
> which is a different ABI.
>
> >
> > > Do they need prerequisites?
> >
> > Prerequisites is optional and not documented. I will remove it from
> > the list of items reported within sysfs
> > >
> > > >

Fair enough. I will remove "type", "display_name" and "display_langcode".
The same three entries will be removed from spmobj-attributes (SPM)
which belong to authentication attributes.

> > > >
> > > > >
> > > > > > +
> > > > > > +static const struct attribute_group bios_password_attr_group = {
> > > > > > + .attrs = password_attrs
> > > > > > +};
> > > > > > +
> > > > > > +static const struct attribute_group system_password_attr_group = {
> > > > > > + .attrs = password_attrs
> > > > > > +};
> > > > >
> > > > > These groups are the same, are both needed?
> > > >
> > > > Yes. They will show under 'Setup Password' and 'Power-on password'
> > >
> > > These are identical constant structures. It should be possible to have
> > > only one and use it for both usecases.
> > >
> >
> > Both 'Setup Password' and 'Power-on password' need to coexist hence
> > the reason for keeping them separate.
> > Both attributes share the same helper routines. Unifying both
> > passwords into a single structure adds unnecessary complexity.
>
> They are already sharing the "password_attrs" array and all the
> attributes listed in it.
>
> It seems they could also share the attribute_group which does not really
> contain any data.

Ah. That makes sense. Thank you for the clarification.
Done!

2023-05-05 16:25:13

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

On Sun, Apr 23, 2023 at 1:55 AM <[email protected]> wrote:
>
> On 2023-04-20 11:54:45-0500, Jorge Lopez wrote:
> > .../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
> > 1 file changed, 563 insertions(+)
> > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> >
> > diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > new file mode 100644
> > index 000000000000..5e5d540f728d
> > --- /dev/null
> > +++ b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > @@ -0,0 +1,563 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Functions corresponding to ordered list type attributes under
> > + * BIOS ORDERED LIST GUID for use with hp-bioscfg driver.
> > + *
> > + * Copyright (c) 2022 HP Development Company, L.P.
> > + */
> > +
> > +#include "bioscfg.h"
> > +
> > +GET_INSTANCE_ID(ordered_list);
> > +
> > +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> > +{
> > +
> > + int instance_id = get_ordered_list_instance_id(kobj);
> > +
> > + if (instance_id < 0)
> > + return -EIO;
> > +
> > + return sysfs_emit(buf, "%s\n",
> > + bioscfg_drv.ordered_list_data[instance_id].current_value);
> > +}
> > +
> > +/*
> > + * validate_ordered_list_value -
> > + * Validate input of current_value against possible values
>
> Does the firmware not also validate this?
>
> If so it may be easier to just let it do so and remove the validations
> from the driver.

Yes. the firmware validates the data.
Will remove the validation
>
> > + *
> > + * @instance_id: The instance on which input is validated
> > + * @buf: Input value
> > + */
> > +static int validate_ordered_list_values(int instance_id, const char *buf)
> > +{
> > + int ret = 0;
> > + int found = 0;
> > + char *new_values = NULL;
> > + char *value;
> > + int elem;
> > + int elem_found = 0;
> > +
> > + /* Is it a read only attribute */
> > + if (bioscfg_drv.ordered_list_data[instance_id].common.is_readonly)
> > + return -EIO;
> > +
> > + new_values = kstrdup(buf, GFP_KERNEL);
> > +
> > + /*
> > + * Changes to ordered list values require checking that new
> > + * values are found in the list of elements.
> > + */
> > + elem_found = 0;
> > + while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > +
> > + value = strsep(&new_values, ",");
>
> The docs say the separator is semicolon.

BIOS reports the current value using ',' as separator instead of ";".

./hp-bioscfg/attributes/UEFI Boot Order/current_value
HDD:M.2:3,HDD:USB:1(Disabled),HDD:M.2:4,HDD:M.2:1,HDD:M.2:2,NETWORK
IPV4:EMBEDDED:1,NETWORK IPV6:EMBEDDED:1,NETWORK
IPV4:EXPANSION:1,NETWORK IPV6:EXPANSION:1

To avoid having to convert from "," to ";" and vice versa, I will
update the documentation to reflect the use of "'," commas as the
separator

>
> > + if (value != NULL) {
> > + if (!*value)
> > + continue;
> > + elem_found++;
> > + }
> > +
> > + found = 0;
> > + for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
> > + if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {
>
> It's surprising that this is case-insensitive.

As validated in earlier reviews, BIOS rejects strings that do not
match the internal values.

>
> > + found = 1;
> > + break;
> > + }
> > + }
> > +
> > +
> > + if (!found) {
> > + ret = -EINVAL;
> > + goto out_list_value;
> > + }
> > + }
> > +
> > + if (elem_found == bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > + pr_warn("Number of new values is not equal to number of ordered list elements (%d)\n",
> > + bioscfg_drv.ordered_list_data[instance_id].elements_size);
> > + ret = -EINVAL;
> > + goto out_list_value;
> > + }
> > +
> > +out_list_value:
> > + kfree(new_values);
> > + return ret;
> > +}
>
> This algorithm does not seem to validate that different values are
> provided.
>
> So if "possible_values" is "foo,bar,baz" this function would accept
> "foo,foo,foo".
>

BIOS will reject strings such as "foo,foo,foo" when the current value
is "foo,bar,baz". It is ok to provide a string which items are
ordered differently. i.e. "baz,bar,foo"
validate_ordered_list_values() function will be removed as indicated earlier.

> > +
> > +/*
> > + * validate_ordered_input() -
> > + * Validate input of current_value against possible values
> > + *
> > + * @instance_id: The instance on which input is validated
> > + * @buf: Input value
> > + */
> > +static int validate_ordered_list_input(int instance_id, const char *buf)
> > +{
> > + int ret = 0;
> > +
> > + ret = validate_ordered_list_values(instance_id, buf);
> > + if (ret < 0)
> > + return -EINVAL;
> > +
> > + /*
> > + * set pending reboot flag depending on
> > + * "RequiresPhysicalPresence" value
> > + */
> > + if (bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence)
> > + bioscfg_drv.pending_reboot = true;
> > +
> > + return ret;
> > +}
> > +
> > +static void update_ordered_list_value(int instance_id, char *attr_value)
> > +{
> > + strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
> > + attr_value,
> > + sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
> > +}
> > +
> > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, ordered_list);
> > +static struct kobj_attribute ordered_list_display_langcode =
> > + __ATTR_RO(display_name_language_code);
> > +
> > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, ordered_list);
> > +static struct kobj_attribute ordered_list_display_name =
> > + __ATTR_RO(display_name);
> > +
> > +ATTRIBUTE_PROPERTY_STORE(current_value, ordered_list);
> > +static struct kobj_attribute ordered_list_current_val =
> > + __ATTR_RW_MODE(current_value, 0644);
> > +
> > +
> > +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, ordered_list);
> > +static struct kobj_attribute ordered_list_prerequisites_size_val =
> > + __ATTR_RO(prerequisites_size);
> > +
> > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, ordered_list);
> > +static struct kobj_attribute ordered_list_prerequisites_val =
> > + __ATTR_RO(prerequisites);
> > +
> > +ATTRIBUTE_N_PROPERTY_SHOW(elements_size, ordered_list);
> > +static struct kobj_attribute ordered_list_elements_size_val =
> > + __ATTR_RO(elements_size);
>
> "size" and "length" attributes are fairly useless to userspace.
> They can't be trusted to provide information about another attribute as
> the information can be out of date when the other attribute is read.
>

Prerequisites, prerequisites_size and elements_size will be removed

> > +
> > +ATTRIBUTE_VALUES_PROPERTY_SHOW(elements, ordered_list);
> > +static struct kobj_attribute ordered_list_elements_val =
> > + __ATTR_RO(elements);
> > +

<snip>

> > +
> > +
> > +int populate_ordered_list_elements_from_package(union acpi_object *order_obj,
> > + int order_obj_count,
> > + int instance_id)
> > +{
> > + char *str_value = NULL;
> > + int value_len;
> > + int ret = 0;
> > + u32 size = 0;
> > + u32 int_value;
> > + int elem = 0;
> > + int reqs;
> > + int eloc;
> > + char *tmpstr = NULL;
> > + char *part_tmp = NULL;
> > + int tmp_len = 0;
> > + char *part = NULL;
> > +
> > + if (!order_obj)
> > + return -EINVAL;
> > +
> > + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> > + LANG_CODE_STR,
> > + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
>
> This seems to be the same for every type. Can it not be moved into
> common code?

Each instance requires to report 'display_name_language_code' hence it
cannot be moved to a common code.
>

<snip>

2023-05-05 21:41:55

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

On 2023-05-05 11:09:55-0500, Jorge Lopez wrote:
> On Sun, Apr 23, 2023 at 1:55 AM <[email protected]> wrote:
> >
> > On 2023-04-20 11:54:45-0500, Jorge Lopez wrote:
> > > .../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
> > > 1 file changed, 563 insertions(+)
> > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > >
> > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > new file mode 100644
> > > index 000000000000..5e5d540f728d
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > @@ -0,0 +1,563 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Functions corresponding to ordered list type attributes under
> > > + * BIOS ORDERED LIST GUID for use with hp-bioscfg driver.
> > > + *
> > > + * Copyright (c) 2022 HP Development Company, L.P.
> > > + */
> > > +
> > > +#include "bioscfg.h"
> > > +
> > > +GET_INSTANCE_ID(ordered_list);
> > > +
> > > +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> > > +{
> > > +
> > > + int instance_id = get_ordered_list_instance_id(kobj);
> > > +
> > > + if (instance_id < 0)
> > > + return -EIO;
> > > +
> > > + return sysfs_emit(buf, "%s\n",
> > > + bioscfg_drv.ordered_list_data[instance_id].current_value);
> > > +}
> > > +
> > > +/*
> > > + * validate_ordered_list_value -
> > > + * Validate input of current_value against possible values
> >
> > Does the firmware not also validate this?
> >
> > If so it may be easier to just let it do so and remove the validations
> > from the driver.
>
> Yes. the firmware validates the data.
> Will remove the validation
> >
> > > + *
> > > + * @instance_id: The instance on which input is validated
> > > + * @buf: Input value
> > > + */
> > > +static int validate_ordered_list_values(int instance_id, const char *buf)
> > > +{
> > > + int ret = 0;
> > > + int found = 0;
> > > + char *new_values = NULL;
> > > + char *value;
> > > + int elem;
> > > + int elem_found = 0;
> > > +
> > > + /* Is it a read only attribute */
> > > + if (bioscfg_drv.ordered_list_data[instance_id].common.is_readonly)
> > > + return -EIO;
> > > +
> > > + new_values = kstrdup(buf, GFP_KERNEL);
> > > +
> > > + /*
> > > + * Changes to ordered list values require checking that new
> > > + * values are found in the list of elements.
> > > + */
> > > + elem_found = 0;
> > > + while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > +
> > > + value = strsep(&new_values, ",");
> >
> > The docs say the separator is semicolon.
>
> BIOS reports the current value using ',' as separator instead of ";".
>
> ./hp-bioscfg/attributes/UEFI Boot Order/current_value
> HDD:M.2:3,HDD:USB:1(Disabled),HDD:M.2:4,HDD:M.2:1,HDD:M.2:2,NETWORK
> IPV4:EMBEDDED:1,NETWORK IPV6:EMBEDDED:1,NETWORK
> IPV4:EXPANSION:1,NETWORK IPV6:EXPANSION:1
>
> To avoid having to convert from "," to ";" and vice versa, I will
> update the documentation to reflect the use of "'," commas as the
> separator

The enum data format uses ";". Therefore it makes sense to also stick to
";".
The implementation detail of the BIOS using ',' should not matter to
users.

> > > + if (value != NULL) {
> > > + if (!*value)
> > > + continue;
> > > + elem_found++;
> > > + }
> > > +
> > > + found = 0;
> > > + for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
> > > + if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {
> >
> > It's surprising that this is case-insensitive.
>
> As validated in earlier reviews, BIOS rejects strings that do not
> match the internal values.
>
> >
> > > + found = 1;
> > > + break;
> > > + }
> > > + }
> > > +
> > > +
> > > + if (!found) {
> > > + ret = -EINVAL;
> > > + goto out_list_value;
> > > + }
> > > + }
> > > +
> > > + if (elem_found == bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > + pr_warn("Number of new values is not equal to number of ordered list elements (%d)\n",
> > > + bioscfg_drv.ordered_list_data[instance_id].elements_size);
> > > + ret = -EINVAL;
> > > + goto out_list_value;
> > > + }
> > > +
> > > +out_list_value:
> > > + kfree(new_values);
> > > + return ret;
> > > +}
> >
> > This algorithm does not seem to validate that different values are
> > provided.
> >
> > So if "possible_values" is "foo,bar,baz" this function would accept
> > "foo,foo,foo".
> >
>
> BIOS will reject strings such as "foo,foo,foo" when the current value
> is "foo,bar,baz". It is ok to provide a string which items are
> ordered differently. i.e. "baz,bar,foo"
> validate_ordered_list_values() function will be removed as indicated earlier.
>
> > > +
> > > +/*
> > > + * validate_ordered_input() -
> > > + * Validate input of current_value against possible values
> > > + *
> > > + * @instance_id: The instance on which input is validated
> > > + * @buf: Input value
> > > + */
> > > +static int validate_ordered_list_input(int instance_id, const char *buf)
> > > +{
> > > + int ret = 0;
> > > +
> > > + ret = validate_ordered_list_values(instance_id, buf);
> > > + if (ret < 0)
> > > + return -EINVAL;
> > > +
> > > + /*
> > > + * set pending reboot flag depending on
> > > + * "RequiresPhysicalPresence" value
> > > + */
> > > + if (bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence)
> > > + bioscfg_drv.pending_reboot = true;
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static void update_ordered_list_value(int instance_id, char *attr_value)
> > > +{
> > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
> > > + attr_value,
> > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
> > > +}
> > > +
> > > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, ordered_list);
> > > +static struct kobj_attribute ordered_list_display_langcode =
> > > + __ATTR_RO(display_name_language_code);
> > > +
> > > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, ordered_list);
> > > +static struct kobj_attribute ordered_list_display_name =
> > > + __ATTR_RO(display_name);
> > > +
> > > +ATTRIBUTE_PROPERTY_STORE(current_value, ordered_list);
> > > +static struct kobj_attribute ordered_list_current_val =
> > > + __ATTR_RW_MODE(current_value, 0644);
> > > +
> > > +
> > > +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, ordered_list);
> > > +static struct kobj_attribute ordered_list_prerequisites_size_val =
> > > + __ATTR_RO(prerequisites_size);
> > > +
> > > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, ordered_list);
> > > +static struct kobj_attribute ordered_list_prerequisites_val =
> > > + __ATTR_RO(prerequisites);
> > > +
> > > +ATTRIBUTE_N_PROPERTY_SHOW(elements_size, ordered_list);
> > > +static struct kobj_attribute ordered_list_elements_size_val =
> > > + __ATTR_RO(elements_size);
> >
> > "size" and "length" attributes are fairly useless to userspace.
> > They can't be trusted to provide information about another attribute as
> > the information can be out of date when the other attribute is read.
> >
>
> Prerequisites, prerequisites_size and elements_size will be removed
>
> > > +
> > > +ATTRIBUTE_VALUES_PROPERTY_SHOW(elements, ordered_list);
> > > +static struct kobj_attribute ordered_list_elements_val =
> > > + __ATTR_RO(elements);
> > > +
>
> <snip>
>
> > > +
> > > +
> > > +int populate_ordered_list_elements_from_package(union acpi_object *order_obj,
> > > + int order_obj_count,
> > > + int instance_id)
> > > +{
> > > + char *str_value = NULL;
> > > + int value_len;
> > > + int ret = 0;
> > > + u32 size = 0;
> > > + u32 int_value;
> > > + int elem = 0;
> > > + int reqs;
> > > + int eloc;
> > > + char *tmpstr = NULL;
> > > + char *part_tmp = NULL;
> > > + int tmp_len = 0;
> > > + char *part = NULL;
> > > +
> > > + if (!order_obj)
> > > + return -EINVAL;
> > > +
> > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> > > + LANG_CODE_STR,
> > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
> >
> > This seems to be the same for every type. Can it not be moved into
> > common code?
>
> Each instance requires to report 'display_name_language_code' hence it
> cannot be moved to a common code.

Can it every be different from LANG_CODE_STR?

If not instead of having one kobj_attribute diplay_langcode for each
string/enum/integer/etc you could have one kobj_attribute defined in
bioscfg.c and then added to each string_attrs, enum_attrs...

The _show() callback for this attribute would just return the constant
string.

This removes the need for many attribute definition, members in the data
struct, initialization of these member...

2023-05-05 22:01:36

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

On Fri, May 5, 2023 at 4:11 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-05-05 11:09:55-0500, Jorge Lopez wrote:
> > On Sun, Apr 23, 2023 at 1:55 AM <[email protected]> wrote:
> > >
> > > On 2023-04-20 11:54:45-0500, Jorge Lopez wrote:
> > > > .../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
> > > > 1 file changed, 563 insertions(+)
> > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > >
> > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > new file mode 100644
> > > > index 000000000000..5e5d540f728d
> > > > --- /dev/null
> > > > +++ b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > @@ -0,0 +1,563 @@
> > > > +// SPDX-License-Identifier: GPL-2.0
> > > > +/*
> > > > + * Functions corresponding to ordered list type attributes under
> > > > + * BIOS ORDERED LIST GUID for use with hp-bioscfg driver.
> > > > + *
> > > > + * Copyright (c) 2022 HP Development Company, L.P.
> > > > + */
> > > > +
> > > > +#include "bioscfg.h"
> > > > +
> > > > +GET_INSTANCE_ID(ordered_list);
> > > > +
> > > > +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> > > > +{
> > > > +
> > > > + int instance_id = get_ordered_list_instance_id(kobj);
> > > > +
> > > > + if (instance_id < 0)
> > > > + return -EIO;
> > > > +
> > > > + return sysfs_emit(buf, "%s\n",
> > > > + bioscfg_drv.ordered_list_data[instance_id].current_value);
> > > > +}
> > > > +
> > > > +/*
> > > > + * validate_ordered_list_value -
> > > > + * Validate input of current_value against possible values
> > >
> > > Does the firmware not also validate this?
> > >
> > > If so it may be easier to just let it do so and remove the validations
> > > from the driver.
> >
> > Yes. the firmware validates the data.
> > Will remove the validation
> > >
> > > > + *
> > > > + * @instance_id: The instance on which input is validated
> > > > + * @buf: Input value
> > > > + */
> > > > +static int validate_ordered_list_values(int instance_id, const char *buf)
> > > > +{
> > > > + int ret = 0;
> > > > + int found = 0;
> > > > + char *new_values = NULL;
> > > > + char *value;
> > > > + int elem;
> > > > + int elem_found = 0;
> > > > +
> > > > + /* Is it a read only attribute */
> > > > + if (bioscfg_drv.ordered_list_data[instance_id].common.is_readonly)
> > > > + return -EIO;
> > > > +
> > > > + new_values = kstrdup(buf, GFP_KERNEL);
> > > > +
> > > > + /*
> > > > + * Changes to ordered list values require checking that new
> > > > + * values are found in the list of elements.
> > > > + */
> > > > + elem_found = 0;
> > > > + while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > > +
> > > > + value = strsep(&new_values, ",");
> > >
> > > The docs say the separator is semicolon.
> >
> > BIOS reports the current value using ',' as separator instead of ";".
> >
> > ./hp-bioscfg/attributes/UEFI Boot Order/current_value
> > HDD:M.2:3,HDD:USB:1(Disabled),HDD:M.2:4,HDD:M.2:1,HDD:M.2:2,NETWORK
> > IPV4:EMBEDDED:1,NETWORK IPV6:EMBEDDED:1,NETWORK
> > IPV4:EXPANSION:1,NETWORK IPV6:EXPANSION:1
> >
> > To avoid having to convert from "," to ";" and vice versa, I will
> > update the documentation to reflect the use of "'," commas as the
> > separator
>
> The enum data format uses ";". Therefore it makes sense to also stick to
> ";".
> The implementation detail of the BIOS using ',' should not matter to
> users.

The use of ',' does matter because BIOS expects the updated string to
use commas as separators
current_value is reported by BIOS using commas. Any changes to the
order of items in that string needs to use commas.

The difference with enum is the fact the user needs to enter only one
value out of those possible values available and no separators are
needed.
For ordered attributes...

when the current value is "foo,bar,baz". the user provides a string
which items are ordered differently. i.e. "baz,bar,foo"
if the new string is using semicolon instead of comma for the
separator, BIOS will reject the data.


> > > > + if (value != NULL) {
> > > > + if (!*value)
> > > > + continue;
> > > > + elem_found++;
> > > > + }
> > > > +
> > > > + found = 0;
> > > > + for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
> > > > + if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {
> > >
> > > It's surprising that this is case-insensitive.
> >
> > As validated in earlier reviews, BIOS rejects strings that do not
> > match the internal values.
> >
> > >
> > > > + found = 1;
> > > > + break;
> > > > + }
> > > > + }
> > > > +
> > > > +
> > > > + if (!found) {
> > > > + ret = -EINVAL;
> > > > + goto out_list_value;
> > > > + }
> > > > + }
> > > > +
> > > > + if (elem_found == bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > > + pr_warn("Number of new values is not equal to number of ordered list elements (%d)\n",
> > > > + bioscfg_drv.ordered_list_data[instance_id].elements_size);
> > > > + ret = -EINVAL;
> > > > + goto out_list_value;
> > > > + }
> > > > +
> > > > +out_list_value:
> > > > + kfree(new_values);
> > > > + return ret;
> > > > +}
> > >
> > > This algorithm does not seem to validate that different values are
> > > provided.
> > >
> > > So if "possible_values" is "foo,bar,baz" this function would accept
> > > "foo,foo,foo".
> > >
> >
> > BIOS will reject strings such as "foo,foo,foo" when the current value
> > is "foo,bar,baz". It is ok to provide a string which items are
> > ordered differently. i.e. "baz,bar,foo"
> > validate_ordered_list_values() function will be removed as indicated earlier.
> >
> > > > +
> > > > +/*
> > > > + * validate_ordered_input() -
> > > > + * Validate input of current_value against possible values
> > > > + *
> > > > + * @instance_id: The instance on which input is validated
> > > > + * @buf: Input value
> > > > + */
> > > > +static int validate_ordered_list_input(int instance_id, const char *buf)
> > > > +{
> > > > + int ret = 0;
> > > > +
> > > > + ret = validate_ordered_list_values(instance_id, buf);
> > > > + if (ret < 0)
> > > > + return -EINVAL;
> > > > +
> > > > + /*
> > > > + * set pending reboot flag depending on
> > > > + * "RequiresPhysicalPresence" value
> > > > + */
> > > > + if (bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence)
> > > > + bioscfg_drv.pending_reboot = true;
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static void update_ordered_list_value(int instance_id, char *attr_value)
> > > > +{
> > > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
> > > > + attr_value,
> > > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
> > > > +}
> > > > +
> > > > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, ordered_list);
> > > > +static struct kobj_attribute ordered_list_display_langcode =
> > > > + __ATTR_RO(display_name_language_code);
> > > > +
> > > > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, ordered_list);
> > > > +static struct kobj_attribute ordered_list_display_name =
> > > > + __ATTR_RO(display_name);
> > > > +
> > > > +ATTRIBUTE_PROPERTY_STORE(current_value, ordered_list);
> > > > +static struct kobj_attribute ordered_list_current_val =
> > > > + __ATTR_RW_MODE(current_value, 0644);
> > > > +
> > > > +
> > > > +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, ordered_list);
> > > > +static struct kobj_attribute ordered_list_prerequisites_size_val =
> > > > + __ATTR_RO(prerequisites_size);
> > > > +
> > > > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, ordered_list);
> > > > +static struct kobj_attribute ordered_list_prerequisites_val =
> > > > + __ATTR_RO(prerequisites);
> > > > +
> > > > +ATTRIBUTE_N_PROPERTY_SHOW(elements_size, ordered_list);
> > > > +static struct kobj_attribute ordered_list_elements_size_val =
> > > > + __ATTR_RO(elements_size);
> > >
> > > "size" and "length" attributes are fairly useless to userspace.
> > > They can't be trusted to provide information about another attribute as
> > > the information can be out of date when the other attribute is read.
> > >
> >
> > Prerequisites, prerequisites_size and elements_size will be removed
> >
> > > > +
> > > > +ATTRIBUTE_VALUES_PROPERTY_SHOW(elements, ordered_list);
> > > > +static struct kobj_attribute ordered_list_elements_val =
> > > > + __ATTR_RO(elements);
> > > > +
> >
> > <snip>
> >
> > > > +
> > > > +
> > > > +int populate_ordered_list_elements_from_package(union acpi_object *order_obj,
> > > > + int order_obj_count,
> > > > + int instance_id)
> > > > +{
> > > > + char *str_value = NULL;
> > > > + int value_len;
> > > > + int ret = 0;
> > > > + u32 size = 0;
> > > > + u32 int_value;
> > > > + int elem = 0;
> > > > + int reqs;
> > > > + int eloc;
> > > > + char *tmpstr = NULL;
> > > > + char *part_tmp = NULL;
> > > > + int tmp_len = 0;
> > > > + char *part = NULL;
> > > > +
> > > > + if (!order_obj)
> > > > + return -EINVAL;
> > > > +
> > > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> > > > + LANG_CODE_STR,
> > > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
> > >
> > > This seems to be the same for every type. Can it not be moved into
> > > common code?
> >
> > Each instance requires to report 'display_name_language_code' hence it
> > cannot be moved to a common code.
>
> Can it every be different from LANG_CODE_STR?

Yes. The string currently is LANG_CODE_STR (en_US.UTF-8) but it
will change as the BIOS provides additional translations at a later
time.
This is a future enhancement for the driver.

>
> If not instead of having one kobj_attribute diplay_langcode for each
> string/enum/integer/etc you could have one kobj_attribute defined in
> bioscfg.c and then added to each string_attrs, enum_attrs...
>
> The _show() callback for this attribute would just return the constant
> string.
>
> This removes the need for many attribute definition, members in the data
> struct, initialization of these member...

2023-05-06 06:03:00

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

On 2023-05-05 16:57:59-0500, Jorge Lopez wrote:
> On Fri, May 5, 2023 at 4:11 PM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-05-05 11:09:55-0500, Jorge Lopez wrote:
> > > On Sun, Apr 23, 2023 at 1:55 AM <[email protected]> wrote:
> > > >
> > > > On 2023-04-20 11:54:45-0500, Jorge Lopez wrote:
> > > > > .../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
> > > > > 1 file changed, 563 insertions(+)
> > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > >
> > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > new file mode 100644
> > > > > index 000000000000..5e5d540f728d
> > > > > --- /dev/null
> > > > > +++ b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > @@ -0,0 +1,563 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0
> > > > > +/*
> > > > > + * Functions corresponding to ordered list type attributes under
> > > > > + * BIOS ORDERED LIST GUID for use with hp-bioscfg driver.
> > > > > + *
> > > > > + * Copyright (c) 2022 HP Development Company, L.P.
> > > > > + */
> > > > > +
> > > > > +#include "bioscfg.h"
> > > > > +
> > > > > +GET_INSTANCE_ID(ordered_list);
> > > > > +
> > > > > +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> > > > > +{
> > > > > +
> > > > > + int instance_id = get_ordered_list_instance_id(kobj);
> > > > > +
> > > > > + if (instance_id < 0)
> > > > > + return -EIO;
> > > > > +
> > > > > + return sysfs_emit(buf, "%s\n",
> > > > > + bioscfg_drv.ordered_list_data[instance_id].current_value);
> > > > > +}
> > > > > +
> > > > > +/*
> > > > > + * validate_ordered_list_value -
> > > > > + * Validate input of current_value against possible values
> > > >
> > > > Does the firmware not also validate this?
> > > >
> > > > If so it may be easier to just let it do so and remove the validations
> > > > from the driver.
> > >
> > > Yes. the firmware validates the data.
> > > Will remove the validation
> > > >
> > > > > + *
> > > > > + * @instance_id: The instance on which input is validated
> > > > > + * @buf: Input value
> > > > > + */
> > > > > +static int validate_ordered_list_values(int instance_id, const char *buf)
> > > > > +{
> > > > > + int ret = 0;
> > > > > + int found = 0;
> > > > > + char *new_values = NULL;
> > > > > + char *value;
> > > > > + int elem;
> > > > > + int elem_found = 0;
> > > > > +
> > > > > + /* Is it a read only attribute */
> > > > > + if (bioscfg_drv.ordered_list_data[instance_id].common.is_readonly)
> > > > > + return -EIO;
> > > > > +
> > > > > + new_values = kstrdup(buf, GFP_KERNEL);
> > > > > +
> > > > > + /*
> > > > > + * Changes to ordered list values require checking that new
> > > > > + * values are found in the list of elements.
> > > > > + */
> > > > > + elem_found = 0;
> > > > > + while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > > > +
> > > > > + value = strsep(&new_values, ",");
> > > >
> > > > The docs say the separator is semicolon.
> > >
> > > BIOS reports the current value using ',' as separator instead of ";".
> > >
> > > ./hp-bioscfg/attributes/UEFI Boot Order/current_value
> > > HDD:M.2:3,HDD:USB:1(Disabled),HDD:M.2:4,HDD:M.2:1,HDD:M.2:2,NETWORK
> > > IPV4:EMBEDDED:1,NETWORK IPV6:EMBEDDED:1,NETWORK
> > > IPV4:EXPANSION:1,NETWORK IPV6:EXPANSION:1
> > >
> > > To avoid having to convert from "," to ";" and vice versa, I will
> > > update the documentation to reflect the use of "'," commas as the
> > > separator
> >
> > The enum data format uses ";". Therefore it makes sense to also stick to
> > ";".
> > The implementation detail of the BIOS using ',' should not matter to
> > users.
>
> The use of ',' does matter because BIOS expects the updated string to
> use commas as separators
> current_value is reported by BIOS using commas. Any changes to the
> order of items in that string needs to use commas.
>
> The difference with enum is the fact the user needs to enter only one
> value out of those possible values available and no separators are
> needed.
> For ordered attributes...
>
> when the current value is "foo,bar,baz". the user provides a string
> which items are ordered differently. i.e. "baz,bar,foo"
> if the new string is using semicolon instead of comma for the
> separator, BIOS will reject the data.

Of course the BIOS expects the format with comma.

But the users of this API should not have to care about implementation
details like that.
They want a consistent API. As the ordered-list type is fairly general
it may be promoted to be a general attribute type later.

If this happens the API can't be changed as that would break users of
hp-bioscfg. So either there would be two APIs for the ordered-list, one
for hp-bioscfg and one for all other drivers, or different attribute
types would use different kinds of separators.

Was there an issue with the previous replacement logic?

The whole point of device drivers is to translate general kernel APIs
into whatever a specific device needs, switching around ',' and ';'
seems not so bad.

> > > > > + if (value != NULL) {
> > > > > + if (!*value)
> > > > > + continue;
> > > > > + elem_found++;
> > > > > + }
> > > > > +
> > > > > + found = 0;
> > > > > + for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
> > > > > + if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {
> > > >
> > > > It's surprising that this is case-insensitive.
> > >
> > > As validated in earlier reviews, BIOS rejects strings that do not
> > > match the internal values.
> > >
> > > >
> > > > > + found = 1;
> > > > > + break;
> > > > > + }
> > > > > + }
> > > > > +
> > > > > +
> > > > > + if (!found) {
> > > > > + ret = -EINVAL;
> > > > > + goto out_list_value;
> > > > > + }
> > > > > + }
> > > > > +
> > > > > + if (elem_found == bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > > > + pr_warn("Number of new values is not equal to number of ordered list elements (%d)\n",
> > > > > + bioscfg_drv.ordered_list_data[instance_id].elements_size);
> > > > > + ret = -EINVAL;
> > > > > + goto out_list_value;
> > > > > + }
> > > > > +
> > > > > +out_list_value:
> > > > > + kfree(new_values);
> > > > > + return ret;
> > > > > +}
> > > >
> > > > This algorithm does not seem to validate that different values are
> > > > provided.
> > > >
> > > > So if "possible_values" is "foo,bar,baz" this function would accept
> > > > "foo,foo,foo".
> > > >
> > >
> > > BIOS will reject strings such as "foo,foo,foo" when the current value
> > > is "foo,bar,baz". It is ok to provide a string which items are
> > > ordered differently. i.e. "baz,bar,foo"
> > > validate_ordered_list_values() function will be removed as indicated earlier.
> > >
> > > > > +
> > > > > +/*
> > > > > + * validate_ordered_input() -
> > > > > + * Validate input of current_value against possible values
> > > > > + *
> > > > > + * @instance_id: The instance on which input is validated
> > > > > + * @buf: Input value
> > > > > + */
> > > > > +static int validate_ordered_list_input(int instance_id, const char *buf)
> > > > > +{
> > > > > + int ret = 0;
> > > > > +
> > > > > + ret = validate_ordered_list_values(instance_id, buf);
> > > > > + if (ret < 0)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + /*
> > > > > + * set pending reboot flag depending on
> > > > > + * "RequiresPhysicalPresence" value
> > > > > + */
> > > > > + if (bioscfg_drv.ordered_list_data[instance_id].common.requires_physical_presence)
> > > > > + bioscfg_drv.pending_reboot = true;
> > > > > +
> > > > > + return ret;
> > > > > +}
> > > > > +
> > > > > +static void update_ordered_list_value(int instance_id, char *attr_value)
> > > > > +{
> > > > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].current_value,
> > > > > + attr_value,
> > > > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].current_value));
> > > > > +}
> > > > > +
> > > > > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, ordered_list);
> > > > > +static struct kobj_attribute ordered_list_display_langcode =
> > > > > + __ATTR_RO(display_name_language_code);
> > > > > +
> > > > > +ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, ordered_list);
> > > > > +static struct kobj_attribute ordered_list_display_name =
> > > > > + __ATTR_RO(display_name);
> > > > > +
> > > > > +ATTRIBUTE_PROPERTY_STORE(current_value, ordered_list);
> > > > > +static struct kobj_attribute ordered_list_current_val =
> > > > > + __ATTR_RW_MODE(current_value, 0644);
> > > > > +
> > > > > +
> > > > > +ATTRIBUTE_N_COMMON_PROPERTY_SHOW(prerequisites_size, ordered_list);
> > > > > +static struct kobj_attribute ordered_list_prerequisites_size_val =
> > > > > + __ATTR_RO(prerequisites_size);
> > > > > +
> > > > > +ATTRIBUTE_V_COMMON_PROPERTY_SHOW(prerequisites, ordered_list);
> > > > > +static struct kobj_attribute ordered_list_prerequisites_val =
> > > > > + __ATTR_RO(prerequisites);
> > > > > +
> > > > > +ATTRIBUTE_N_PROPERTY_SHOW(elements_size, ordered_list);
> > > > > +static struct kobj_attribute ordered_list_elements_size_val =
> > > > > + __ATTR_RO(elements_size);
> > > >
> > > > "size" and "length" attributes are fairly useless to userspace.
> > > > They can't be trusted to provide information about another attribute as
> > > > the information can be out of date when the other attribute is read.
> > > >
> > >
> > > Prerequisites, prerequisites_size and elements_size will be removed
> > >
> > > > > +
> > > > > +ATTRIBUTE_VALUES_PROPERTY_SHOW(elements, ordered_list);
> > > > > +static struct kobj_attribute ordered_list_elements_val =
> > > > > + __ATTR_RO(elements);
> > > > > +
> > >
> > > <snip>
> > >
> > > > > +
> > > > > +
> > > > > +int populate_ordered_list_elements_from_package(union acpi_object *order_obj,
> > > > > + int order_obj_count,
> > > > > + int instance_id)
> > > > > +{
> > > > > + char *str_value = NULL;
> > > > > + int value_len;
> > > > > + int ret = 0;
> > > > > + u32 size = 0;
> > > > > + u32 int_value;
> > > > > + int elem = 0;
> > > > > + int reqs;
> > > > > + int eloc;
> > > > > + char *tmpstr = NULL;
> > > > > + char *part_tmp = NULL;
> > > > > + int tmp_len = 0;
> > > > > + char *part = NULL;
> > > > > +
> > > > > + if (!order_obj)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> > > > > + LANG_CODE_STR,
> > > > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
> > > >
> > > > This seems to be the same for every type. Can it not be moved into
> > > > common code?
> > >
> > > Each instance requires to report 'display_name_language_code' hence it
> > > cannot be moved to a common code.
> >
> > Can it every be different from LANG_CODE_STR?
>
> Yes. The string currently is LANG_CODE_STR (en_US.UTF-8) but it
> will change as the BIOS provides additional translations at a later
> time.
> This is a future enhancement for the driver.

Is this planned for the near future?
And/Or already implemented in the BIOS?

If there are no concrete plans to implement this soon, in my opinion,
it would be better to only introduce this code when it does something
useful.

> > If not instead of having one kobj_attribute diplay_langcode for each
> > string/enum/integer/etc you could have one kobj_attribute defined in
> > bioscfg.c and then added to each string_attrs, enum_attrs...
> >
> > The _show() callback for this attribute would just return the constant
> > string.
> >
> > This removes the need for many attribute definition, members in the data
> > struct, initialization of these member...

2023-05-08 14:07:59

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

On Sat, May 6, 2023 at 12:51 AM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-05-05 16:57:59-0500, Jorge Lopez wrote:
> > On Fri, May 5, 2023 at 4:11 PM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-05-05 11:09:55-0500, Jorge Lopez wrote:
> > > > On Sun, Apr 23, 2023 at 1:55 AM <[email protected]> wrote:
> > > > >
> > > > > On 2023-04-20 11:54:45-0500, Jorge Lopez wrote:
> > > > > > .../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
> > > > > > 1 file changed, 563 insertions(+)
> > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > >
> > > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > > new file mode 100644
> > > > > > index 000000000000..5e5d540f728d
> > > > > > --- /dev/null

<snip>

> > > > > > + elem_found = 0;
> > > > > > + while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > > > > +
> > > > > > + value = strsep(&new_values, ",");
> > > > >
> > > > > The docs say the separator is semicolon.
> > > >
> > > > BIOS reports the current value using ',' as separator instead of ";".
> > > >
> > > > ./hp-bioscfg/attributes/UEFI Boot Order/current_value
> > > > HDD:M.2:3,HDD:USB:1(Disabled),HDD:M.2:4,HDD:M.2:1,HDD:M.2:2,NETWORK
> > > > IPV4:EMBEDDED:1,NETWORK IPV6:EMBEDDED:1,NETWORK
> > > > IPV4:EXPANSION:1,NETWORK IPV6:EXPANSION:1
> > > >
> > > > To avoid having to convert from "," to ";" and vice versa, I will
> > > > update the documentation to reflect the use of "'," commas as the
> > > > separator
> > >
> > > The enum data format uses ";". Therefore it makes sense to also stick to
> > > ";".
> > > The implementation detail of the BIOS using ',' should not matter to
> > > users.
> >
> > The use of ',' does matter because BIOS expects the updated string to
> > use commas as separators
> > current_value is reported by BIOS using commas. Any changes to the
> > order of items in that string needs to use commas.
> >
> > The difference with enum is the fact the user needs to enter only one
> > value out of those possible values available and no separators are
> > needed.
> > For ordered attributes...
> >
> > when the current value is "foo,bar,baz". the user provides a string
> > which items are ordered differently. i.e. "baz,bar,foo"
> > if the new string is using semicolon instead of comma for the
> > separator, BIOS will reject the data.
>
> Of course the BIOS expects the format with comma.
>
> But the users of this API should not have to care about implementation
> details like that.
> They want a consistent API. As the ordered-list type is fairly general
> it may be promoted to be a general attribute type later.
>
> If this happens the API can't be changed as that would break users of
> hp-bioscfg. So either there would be two APIs for the ordered-list, one
> for hp-bioscfg and one for all other drivers, or different attribute
> types would use different kinds of separators.
>
> Was there an issue with the previous replacement logic?

No issue. I was anticipating a potential problem/confusion and created
a solution. Customers will start using this driver when it becomes
part of the upstream code.
Users primarily will use Powershell scripts to interact with the
driver. The use of powershell will require the user to have the
knowledge and convert ';' semicolon to ',' commas prior to submitting
the request to the driver. AFIK, the boot order is one of those
attributes that is not configured often by the user. Just my opinion.
>
> The whole point of device drivers is to translate general kernel APIs
> into whatever a specific device needs, switching around ',' and ';'
> seems not so bad.

So, are you saying, it is ok for the driver to convert ';' semicolon
to ',' commas prior to submitting a request to change any attribute
of type 'ordered-list' or when reading current value ?
If that is correct, then we can change the documentation back to use
';' semicolons.
Please confirm.
>
> > > > > > + if (value != NULL) {
> > > > > > + if (!*value)
> > > > > > + continue;
> > > > > > + elem_found++;
> > > > > > + }
> > > > > > +
> > > > > > + found = 0;
> > > > > > + for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
> > > > > > + if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {
> > > > >
> > > > > It's surprising that this is case-insensitive.
> > > >
> > > > As validated in earlier reviews, BIOS rejects strings that do not
> > > > match the internal values.
> > > >

<snip>

> > > > > > +
> > > > > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> > > > > > + LANG_CODE_STR,
> > > > > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
> > > > >
> > > > > This seems to be the same for every type. Can it not be moved into
> > > > > common code?
> > > >
> > > > Each instance requires to report 'display_name_language_code' hence it
> > > > cannot be moved to a common code.
> > >
> > > Can it every be different from LANG_CODE_STR?
> >
> > Yes. The string currently is LANG_CODE_STR (en_US.UTF-8) but it
> > will change as the BIOS provides additional translations at a later
> > time.
> > This is a future enhancement for the driver.
>
> Is this planned for the near future?
> And/Or already implemented in the BIOS?
>
> If there are no concrete plans to implement this soon, in my opinion,
> it would be better to only introduce this code when it does something
> useful.

There are no concrete plans yet for the driver. BIOS provides
translations for strings (F10) UI but the attributes are reported in
English regardless of the language configured.
firmware-attributes requires 'display_name_language_code' to be
reported. At this time, the driver can provide a common helper to
assign the LANG_CODE_STR to the corresponding attribute.

>
> > > If not instead of having one kobj_attribute diplay_langcode for each
> > > string/enum/integer/etc you could have one kobj_attribute defined in
> > > bioscfg.c and then added to each string_attrs, enum_attrs...
> > >
> > > The _show() callback for this attribute would just return the constant
> > > string.
> > >
> > > This removes the need for many attribute definition, members in the data
> > > struct, initialization of these member...

2023-05-08 20:58:30

by Thomas Weißschuh

[permalink] [raw]
Subject: Re: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

On 2023-05-08 08:56:55-0500, Jorge Lopez wrote:
> On Sat, May 6, 2023 at 12:51 AM Thomas Weißschuh <[email protected]> wrote:
> >
> > On 2023-05-05 16:57:59-0500, Jorge Lopez wrote:
> > > On Fri, May 5, 2023 at 4:11 PM Thomas Weißschuh <[email protected]> wrote:
> > > >
> > > > On 2023-05-05 11:09:55-0500, Jorge Lopez wrote:
> > > > > On Sun, Apr 23, 2023 at 1:55 AM <[email protected]> wrote:
> > > > > >
> > > > > > On 2023-04-20 11:54:45-0500, Jorge Lopez wrote:
> > > > > > > .../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
> > > > > > > 1 file changed, 563 insertions(+)
> > > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > > >
> > > > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > > > new file mode 100644
> > > > > > > index 000000000000..5e5d540f728d
> > > > > > > --- /dev/null
>
> <snip>
>
> > > > > > > + elem_found = 0;
> > > > > > > + while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > > > > > +
> > > > > > > + value = strsep(&new_values, ",");
> > > > > >
> > > > > > The docs say the separator is semicolon.
> > > > >
> > > > > BIOS reports the current value using ',' as separator instead of ";".
> > > > >
> > > > > ./hp-bioscfg/attributes/UEFI Boot Order/current_value
> > > > > HDD:M.2:3,HDD:USB:1(Disabled),HDD:M.2:4,HDD:M.2:1,HDD:M.2:2,NETWORK
> > > > > IPV4:EMBEDDED:1,NETWORK IPV6:EMBEDDED:1,NETWORK
> > > > > IPV4:EXPANSION:1,NETWORK IPV6:EXPANSION:1
> > > > >
> > > > > To avoid having to convert from "," to ";" and vice versa, I will
> > > > > update the documentation to reflect the use of "'," commas as the
> > > > > separator
> > > >
> > > > The enum data format uses ";". Therefore it makes sense to also stick to
> > > > ";".
> > > > The implementation detail of the BIOS using ',' should not matter to
> > > > users.
> > >
> > > The use of ',' does matter because BIOS expects the updated string to
> > > use commas as separators
> > > current_value is reported by BIOS using commas. Any changes to the
> > > order of items in that string needs to use commas.
> > >
> > > The difference with enum is the fact the user needs to enter only one
> > > value out of those possible values available and no separators are
> > > needed.
> > > For ordered attributes...
> > >
> > > when the current value is "foo,bar,baz". the user provides a string
> > > which items are ordered differently. i.e. "baz,bar,foo"
> > > if the new string is using semicolon instead of comma for the
> > > separator, BIOS will reject the data.
> >
> > Of course the BIOS expects the format with comma.
> >
> > But the users of this API should not have to care about implementation
> > details like that.
> > They want a consistent API. As the ordered-list type is fairly general
> > it may be promoted to be a general attribute type later.
> >
> > If this happens the API can't be changed as that would break users of
> > hp-bioscfg. So either there would be two APIs for the ordered-list, one
> > for hp-bioscfg and one for all other drivers, or different attribute
> > types would use different kinds of separators.
> >
> > Was there an issue with the previous replacement logic?
>
> No issue. I was anticipating a potential problem/confusion and created
> a solution. Customers will start using this driver when it becomes
> part of the upstream code.
> Users primarily will use Powershell scripts to interact with the
> driver. The use of powershell will require the user to have the
> knowledge and convert ';' semicolon to ',' commas prior to submitting
> the request to the driver. AFIK, the boot order is one of those
> attributes that is not configured often by the user. Just my opinion.

If this conversion is done by the driver why would the users have to
care?

> >
> > The whole point of device drivers is to translate general kernel APIs
> > into whatever a specific device needs, switching around ',' and ';'
> > seems not so bad.
>
> So, are you saying, it is ok for the driver to convert ';' semicolon
> to ',' commas prior to submitting a request to change any attribute
> of type 'ordered-list' or when reading current value ?
> If that is correct, then we can change the documentation back to use
> ';' semicolons.
> Please confirm.

A device drivers task it to translate from the domain of the kernel and
its unified APIs to whatever a specific device needs.

If we agree that it is more consistent to use semicolons for class
firmware-attribute APIs then the driver would make sure that userspace
always sees semicolons while the ACPI calls always use commas.

This means translating semicolon->comma when writing to and
comma->semicolon when reading from ACPI.
Userspace should not be exposed to the implementation details but just
the consistent and documented kernel API.

> > > > > > > + if (value != NULL) {
> > > > > > > + if (!*value)
> > > > > > > + continue;
> > > > > > > + elem_found++;
> > > > > > > + }
> > > > > > > +
> > > > > > > + found = 0;
> > > > > > > + for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
> > > > > > > + if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {
> > > > > >
> > > > > > It's surprising that this is case-insensitive.
> > > > >
> > > > > As validated in earlier reviews, BIOS rejects strings that do not
> > > > > match the internal values.
> > > > >
>
> <snip>
>
> > > > > > > +
> > > > > > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> > > > > > > + LANG_CODE_STR,
> > > > > > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
> > > > > >
> > > > > > This seems to be the same for every type. Can it not be moved into
> > > > > > common code?
> > > > >
> > > > > Each instance requires to report 'display_name_language_code' hence it
> > > > > cannot be moved to a common code.
> > > >
> > > > Can it every be different from LANG_CODE_STR?
> > >
> > > Yes. The string currently is LANG_CODE_STR (en_US.UTF-8) but it
> > > will change as the BIOS provides additional translations at a later
> > > time.
> > > This is a future enhancement for the driver.
> >
> > Is this planned for the near future?
> > And/Or already implemented in the BIOS?
> >
> > If there are no concrete plans to implement this soon, in my opinion,
> > it would be better to only introduce this code when it does something
> > useful.
>
> There are no concrete plans yet for the driver. BIOS provides
> translations for strings (F10) UI but the attributes are reported in
> English regardless of the language configured.
> firmware-attributes requires 'display_name_language_code' to be
> reported. At this time, the driver can provide a common helper to
> assign the LANG_CODE_STR to the corresponding attribute.

Yes please.

Create an attribute in bioscfg.c that can be used in all the other
attribute_groups.

> > > > If not instead of having one kobj_attribute diplay_langcode for each
> > > > string/enum/integer/etc you could have one kobj_attribute defined in
> > > > bioscfg.c and then added to each string_attrs, enum_attrs...
> > > >
> > > > The _show() callback for this attribute would just return the constant
> > > > string.
> > > >
> > > > This removes the need for many attribute definition, members in the data
> > > > struct, initialization of these member...

2023-05-08 21:32:46

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

On Mon, May 8, 2023 at 3:50 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-05-08 08:56:55-0500, Jorge Lopez wrote:
> > On Sat, May 6, 2023 at 12:51 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-05-05 16:57:59-0500, Jorge Lopez wrote:
> > > > On Fri, May 5, 2023 at 4:11 PM Thomas Weißschuh <[email protected]> wrote:
> > > > >
> > > > > On 2023-05-05 11:09:55-0500, Jorge Lopez wrote:
> > > > > > On Sun, Apr 23, 2023 at 1:55 AM <[email protected]> wrote:
> > > > > > >
> > > > > > > On 2023-04-20 11:54:45-0500, Jorge Lopez wrote:
> > > > > > > > .../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
> > > > > > > > 1 file changed, 563 insertions(+)
> > > > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > > > >
> > > > > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > > > > new file mode 100644
> > > > > > > > index 000000000000..5e5d540f728d
> > > > > > > > --- /dev/null
> >
> > <snip>
> >
> > > > > > > > + elem_found = 0;
> > > > > > > > + while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > > > > > > +
> > > > > > > > + value = strsep(&new_values, ",");
> > > > > > >
> > > > > > > The docs say the separator is semicolon.
> > > > > >
> > > > > > BIOS reports the current value using ',' as separator instead of ";".
> > > > > >
> > > > > > ./hp-bioscfg/attributes/UEFI Boot Order/current_value
> > > > > > HDD:M.2:3,HDD:USB:1(Disabled),HDD:M.2:4,HDD:M.2:1,HDD:M.2:2,NETWORK
> > > > > > IPV4:EMBEDDED:1,NETWORK IPV6:EMBEDDED:1,NETWORK
> > > > > > IPV4:EXPANSION:1,NETWORK IPV6:EXPANSION:1
> > > > > >
> > > > > > To avoid having to convert from "," to ";" and vice versa, I will
> > > > > > update the documentation to reflect the use of "'," commas as the
> > > > > > separator
> > > > >
> > > > > The enum data format uses ";". Therefore it makes sense to also stick to
> > > > > ";".
> > > > > The implementation detail of the BIOS using ',' should not matter to
> > > > > users.
> > > >
> > > > The use of ',' does matter because BIOS expects the updated string to
> > > > use commas as separators
> > > > current_value is reported by BIOS using commas. Any changes to the
> > > > order of items in that string needs to use commas.
> > > >
> > > > The difference with enum is the fact the user needs to enter only one
> > > > value out of those possible values available and no separators are
> > > > needed.
> > > > For ordered attributes...
> > > >
> > > > when the current value is "foo,bar,baz". the user provides a string
> > > > which items are ordered differently. i.e. "baz,bar,foo"
> > > > if the new string is using semicolon instead of comma for the
> > > > separator, BIOS will reject the data.
> > >
> > > Of course the BIOS expects the format with comma.
> > >
> > > But the users of this API should not have to care about implementation
> > > details like that.
> > > They want a consistent API. As the ordered-list type is fairly general
> > > it may be promoted to be a general attribute type later.
> > >
> > > If this happens the API can't be changed as that would break users of
> > > hp-bioscfg. So either there would be two APIs for the ordered-list, one
> > > for hp-bioscfg and one for all other drivers, or different attribute
> > > types would use different kinds of separators.
> > >
> > > Was there an issue with the previous replacement logic?
> >
> > No issue. I was anticipating a potential problem/confusion and created
> > a solution. Customers will start using this driver when it becomes
> > part of the upstream code.
> > Users primarily will use Powershell scripts to interact with the
> > driver. The use of powershell will require the user to have the
> > knowledge and convert ';' semicolon to ',' commas prior to submitting
> > the request to the driver. AFIK, the boot order is one of those
> > attributes that is not configured often by the user. Just my opinion.
>
> If this conversion is done by the driver why would the users have to
> care?

The driver currently does not do any conversion hence the reason for
my concerns.
I will reinstate the conversion which was removed in an earlier review.
>
> > >
> > > The whole point of device drivers is to translate general kernel APIs
> > > into whatever a specific device needs, switching around ',' and ';'
> > > seems not so bad.
> >
> > So, are you saying, it is ok for the driver to convert ';' semicolon
> > to ',' commas prior to submitting a request to change any attribute
> > of type 'ordered-list' or when reading current value ?
> > If that is correct, then we can change the documentation back to use
> > ';' semicolons.
> > Please confirm.
>
> A device drivers task it to translate from the domain of the kernel and
> its unified APIs to whatever a specific device needs.
>
> If we agree that it is more consistent to use semicolons for class
> firmware-attribute APIs then the driver would make sure that userspace
> always sees semicolons while the ACPI calls always use commas.
>
> This means translating semicolon->comma when writing to and
> comma->semicolon when reading from ACPI.
> Userspace should not be exposed to the implementation details but just
> the consistent and documented kernel API.
>
> > > > > > > > + if (value != NULL) {
> > > > > > > > + if (!*value)
> > > > > > > > + continue;
> > > > > > > > + elem_found++;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + found = 0;
> > > > > > > > + for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
> > > > > > > > + if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {
> > > > > > >
> > > > > > > It's surprising that this is case-insensitive.
> > > > > >
> > > > > > As validated in earlier reviews, BIOS rejects strings that do not
> > > > > > match the internal values.
> > > > > >
> >
> > <snip>
> >
> > > > > > > > +
> > > > > > > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> > > > > > > > + LANG_CODE_STR,
> > > > > > > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
> > > > > > >
> > > > > > > This seems to be the same for every type. Can it not be moved into
> > > > > > > common code?
> > > > > >
> > > > > > Each instance requires to report 'display_name_language_code' hence it
> > > > > > cannot be moved to a common code.
> > > > >
> > > > > Can it every be different from LANG_CODE_STR?
> > > >
> > > > Yes. The string currently is LANG_CODE_STR (en_US.UTF-8) but it
> > > > will change as the BIOS provides additional translations at a later
> > > > time.
> > > > This is a future enhancement for the driver.
> > >
> > > Is this planned for the near future?
> > > And/Or already implemented in the BIOS?
> > >
> > > If there are no concrete plans to implement this soon, in my opinion,
> > > it would be better to only introduce this code when it does something
> > > useful.
> >
> > There are no concrete plans yet for the driver. BIOS provides
> > translations for strings (F10) UI but the attributes are reported in
> > English regardless of the language configured.
> > firmware-attributes requires 'display_name_language_code' to be
> > reported. At this time, the driver can provide a common helper to
> > assign the LANG_CODE_STR to the corresponding attribute.
>
> Yes please.
>
> Create an attribute in bioscfg.c that can be used in all the other
> attribute_groups.

Will do!
>
> > > > > If not instead of having one kobj_attribute diplay_langcode for each
> > > > > string/enum/integer/etc you could have one kobj_attribute defined in
> > > > > bioscfg.c and then added to each string_attrs, enum_attrs...
> > > > >
> > > > > The _show() callback for this attribute would just return the constant
> > > > > string.
> > > > >
> > > > > This removes the need for many attribute definition, members in the data
> > > > > struct, initialization of these member...

2023-05-09 18:56:07

by Jorge Lopez

[permalink] [raw]
Subject: Re: [PATCH v11 05/14] HP BIOSCFG driver - ordered-attributes

On Mon, May 8, 2023 at 3:50 PM Thomas Weißschuh <[email protected]> wrote:
>
> On 2023-05-08 08:56:55-0500, Jorge Lopez wrote:
> > On Sat, May 6, 2023 at 12:51 AM Thomas Weißschuh <[email protected]> wrote:
> > >
> > > On 2023-05-05 16:57:59-0500, Jorge Lopez wrote:
> > > > On Fri, May 5, 2023 at 4:11 PM Thomas Weißschuh <[email protected]> wrote:
> > > > >
> > > > > On 2023-05-05 11:09:55-0500, Jorge Lopez wrote:
> > > > > > On Sun, Apr 23, 2023 at 1:55 AM <[email protected]> wrote:
> > > > > > >
> > > > > > > On 2023-04-20 11:54:45-0500, Jorge Lopez wrote:
> > > > > > > > .../x86/hp/hp-bioscfg/ordered-attributes.c | 563 ++++++++++++++++++
> > > > > > > > 1 file changed, 563 insertions(+)
> > > > > > > > create mode 100644 drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > > > >
> > > > > > > > diff --git a/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/ordered-attributes.c
> > > > > > > > new file mode 100644
> > > > > > > > index 000000000000..5e5d540f728d
> > > > > > > > --- /dev/null
> >
> > <snip>
> >
> > > > > > > > + elem_found = 0;
> > > > > > > > + while (elem_found < bioscfg_drv.ordered_list_data[instance_id].elements_size) {
> > > > > > > > +
> > > > > > > > + value = strsep(&new_values, ",");
> > > > > > >
> > > > > > > The docs say the separator is semicolon.
> > > > > >
> > > > > > BIOS reports the current value using ',' as separator instead of ";".
> > > > > >
> > > > > > ./hp-bioscfg/attributes/UEFI Boot Order/current_value
> > > > > > HDD:M.2:3,HDD:USB:1(Disabled),HDD:M.2:4,HDD:M.2:1,HDD:M.2:2,NETWORK
> > > > > > IPV4:EMBEDDED:1,NETWORK IPV6:EMBEDDED:1,NETWORK
> > > > > > IPV4:EXPANSION:1,NETWORK IPV6:EXPANSION:1
> > > > > >
> > > > > > To avoid having to convert from "," to ";" and vice versa, I will
> > > > > > update the documentation to reflect the use of "'," commas as the
> > > > > > separator
> > > > >
> > > > > The enum data format uses ";". Therefore it makes sense to also stick to
> > > > > ";".
> > > > > The implementation detail of the BIOS using ',' should not matter to
> > > > > users.
> > > >
> > > > The use of ',' does matter because BIOS expects the updated string to
> > > > use commas as separators
> > > > current_value is reported by BIOS using commas. Any changes to the
> > > > order of items in that string needs to use commas.
> > > >
> > > > The difference with enum is the fact the user needs to enter only one
> > > > value out of those possible values available and no separators are
> > > > needed.
> > > > For ordered attributes...
> > > >
> > > > when the current value is "foo,bar,baz". the user provides a string
> > > > which items are ordered differently. i.e. "baz,bar,foo"
> > > > if the new string is using semicolon instead of comma for the
> > > > separator, BIOS will reject the data.
> > >
> > > Of course the BIOS expects the format with comma.
> > >
> > > But the users of this API should not have to care about implementation
> > > details like that.
> > > They want a consistent API. As the ordered-list type is fairly general
> > > it may be promoted to be a general attribute type later.
> > >
> > > If this happens the API can't be changed as that would break users of
> > > hp-bioscfg. So either there would be two APIs for the ordered-list, one
> > > for hp-bioscfg and one for all other drivers, or different attribute
> > > types would use different kinds of separators.
> > >
> > > Was there an issue with the previous replacement logic?
> >
> > No issue. I was anticipating a potential problem/confusion and created
> > a solution. Customers will start using this driver when it becomes
> > part of the upstream code.
> > Users primarily will use Powershell scripts to interact with the
> > driver. The use of powershell will require the user to have the
> > knowledge and convert ';' semicolon to ',' commas prior to submitting
> > the request to the driver. AFIK, the boot order is one of those
> > attributes that is not configured often by the user. Just my opinion.
>
> If this conversion is done by the driver why would the users have to
> care?
>
> > >
> > > The whole point of device drivers is to translate general kernel APIs
> > > into whatever a specific device needs, switching around ',' and ';'
> > > seems not so bad.
> >
> > So, are you saying, it is ok for the driver to convert ';' semicolon
> > to ',' commas prior to submitting a request to change any attribute
> > of type 'ordered-list' or when reading current value ?
> > If that is correct, then we can change the documentation back to use
> > ';' semicolons.
> > Please confirm.
>
> A device drivers task it to translate from the domain of the kernel and
> its unified APIs to whatever a specific device needs.
>
> If we agree that it is more consistent to use semicolons for class
> firmware-attribute APIs then the driver would make sure that userspace
> always sees semicolons while the ACPI calls always use commas.
>
> This means translating semicolon->comma when writing to and
> comma->semicolon when reading from ACPI.
> Userspace should not be exposed to the implementation details but just
> the consistent and documented kernel API.

Added conversion routine (replace commas to semicolon and vice versa).
Documentation was updated to reflect that semicolons are used to
separate values.
Userspace is not expose to the implementation details but just the
consistent and documented API

>
> > > > > > > > + if (value != NULL) {
> > > > > > > > + if (!*value)
> > > > > > > > + continue;
> > > > > > > > + elem_found++;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + found = 0;
> > > > > > > > + for (elem = 0; elem < bioscfg_drv.ordered_list_data[instance_id].elements_size; elem++) {
> > > > > > > > + if (!strcasecmp(bioscfg_drv.ordered_list_data[instance_id].elements[elem], value)) {
> > > > > > >
> > > > > > > It's surprising that this is case-insensitive.
> > > > > >
> > > > > > As validated in earlier reviews, BIOS rejects strings that do not
> > > > > > match the internal values.
> > > > > >
> >
> > <snip>
> >
> > > > > > > > +
> > > > > > > > + strscpy(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code,
> > > > > > > > + LANG_CODE_STR,
> > > > > > > > + sizeof(bioscfg_drv.ordered_list_data[instance_id].common.display_name_language_code));
> > > > > > >
> > > > > > > This seems to be the same for every type. Can it not be moved into
> > > > > > > common code?
> > > > > >
> > > > > > Each instance requires to report 'display_name_language_code' hence it
> > > > > > cannot be moved to a common code.
> > > > >
> > > > > Can it every be different from LANG_CODE_STR?
> > > >
> > > > Yes. The string currently is LANG_CODE_STR (en_US.UTF-8) but it
> > > > will change as the BIOS provides additional translations at a later
> > > > time.
> > > > This is a future enhancement for the driver.
> > >
> > > Is this planned for the near future?
> > > And/Or already implemented in the BIOS?
> > >
> > > If there are no concrete plans to implement this soon, in my opinion,
> > > it would be better to only introduce this code when it does something
> > > useful.
> >
> > There are no concrete plans yet for the driver. BIOS provides
> > translations for strings (F10) UI but the attributes are reported in
> > English regardless of the language configured.
> > firmware-attributes requires 'display_name_language_code' to be
> > reported. At this time, the driver can provide a common helper to
> > assign the LANG_CODE_STR to the corresponding attribute.
>
> Yes please.
>
> Create an attribute in bioscfg.c that can be used in all the other
> attribute_groups.

Done!
>
> > > > > If not instead of having one kobj_attribute diplay_langcode for each
> > > > > string/enum/integer/etc you could have one kobj_attribute defined in
> > > > > bioscfg.c and then added to each string_attrs, enum_attrs...
> > > > >
> > > > > The _show() callback for this attribute would just return the constant
> > > > > string.
> > > > >
> > > > > This removes the need for many attribute definition, members in the data
> > > > > struct, initialization of these member...