[PATCH] ACPI: ACPICA: Fall back to inline recursion if update state allocation fails

From: Rahul Rao

Date: Fri Oct 23 2026 - 03:49:34 EST


When acpi_ut_update_object_reference() traverses nested package elements
or field elements to increment/decrement reference counts, it allocates a
temporary union acpi_generic_state via
acpi_ut_create_update_state_and_push()
to avoid deep C stack recursion.

If memory allocation fails (for instance, under slab fault injection),
the function aborts mid-traversal. This leaves sub-object reference counts
out of sync with parent containers. Callers cannot readily roll back the
partial updates, leading to reference count underflows and slab
use-after-free bugs (such as in acpi_ns_check_package()).

Fix this by introducing acpi_ut_push_or_update_object(). When allocating
a deferred update state fails, fall back to inline recursive traversal up
to a safe bounded recursion limit (ACPI_UT_MAX_UPDATE_DEPTH = 16). This
ensures reference counts remain consistent even during severe memory
pressure.

Reported-by: syzbot+e1268acad3c273b12120@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=e1268acad3c273b12120
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Rahul Rao <shreeramrahul94@xxxxxxxxx>
---
drivers/acpi/acpica/utdelete.c | 78 +++++++++++++++++++++++-----------
1 file changed, 54 insertions(+), 24 deletions(-)

diff --git a/drivers/acpi/acpica/utdelete.c b/drivers/acpi/acpica/utdelete.c
index e8180099d01f..87ae66c7eee8 100644
--- a/drivers/acpi/acpica/utdelete.c
+++ b/drivers/acpi/acpica/utdelete.c
@@ -20,6 +20,14 @@ static void acpi_ut_delete_internal_obj(union acpi_operand_object *object);
static void
acpi_ut_update_ref_count(union acpi_operand_object *object, u32 action);

+static acpi_status
+acpi_ut_update_object_reference_depth(union acpi_operand_object *object,
+ u16 action, u32 depth);
+
+/* Max inline nesting used when a deferred update state cannot be allocated */
+
+#define ACPI_UT_MAX_UPDATE_DEPTH 16
+
/*******************************************************************************
*
* FUNCTION: acpi_ut_delete_internal_obj
@@ -457,6 +465,28 @@ acpi_ut_update_ref_count(union acpi_operand_object *object, u32 action)
}
}

+/*
+ * Queue an object for a later reference count update. If the update state
+ * cannot be allocated, update the object right away instead of failing: an
+ * update abandoned half way leaves the counts of the sub-objects out of
+ * step with the count of their parent, and callers (most of which cannot
+ * handle an error) would later drop references that were never added.
+ */
+static acpi_status
+acpi_ut_push_or_update_object(union acpi_operand_object *object, u16 action,
+ union acpi_generic_state **state_list, u32 depth)
+{
+ acpi_status status;
+
+ status = acpi_ut_create_update_state_and_push(object, action,
+ state_list);
+ if (ACPI_SUCCESS(status) || depth >= ACPI_UT_MAX_UPDATE_DEPTH)
+ return status;
+
+ return acpi_ut_update_object_reference_depth(object, action,
+ depth + 1);
+}
+
/*******************************************************************************
*
* FUNCTION: acpi_ut_update_object_reference
@@ -480,6 +510,13 @@ acpi_ut_update_ref_count(union acpi_operand_object *object, u32 action)

acpi_status
acpi_ut_update_object_reference(union acpi_operand_object *object, u16 action)
+{
+ return acpi_ut_update_object_reference_depth(object, action, 0);
+}
+
+static acpi_status
+acpi_ut_update_object_reference_depth(union acpi_operand_object *object,
+ u16 action, u32 depth)
{
acpi_status status = AE_OK;
union acpi_generic_state *state_list = NULL;
@@ -560,8 +597,9 @@ acpi_ut_update_object_reference(union acpi_operand_object *object, u16 action)
* for later processing (this eliminates recursion.)
*/
status =
- acpi_ut_create_update_state_and_push
- (next_object, action, &state_list);
+ acpi_ut_push_or_update_object
+ (next_object, action, &state_list,
+ depth);
if (ACPI_FAILURE(status)) {
goto error_exit;
}
@@ -579,31 +617,23 @@ acpi_ut_update_object_reference(union acpi_operand_object *object, u16 action)

case ACPI_TYPE_LOCAL_BANK_FIELD:

- next_object = object->bank_field.bank_obj;
- status =
- acpi_ut_create_update_state_and_push(object->
- bank_field.
- region_obj,
- action,
- &state_list);
- if (ACPI_FAILURE(status)) {
- goto error_exit;
- }
- break;
+ next_object = object->bank_field.bank_obj;
+ status = acpi_ut_push_or_update_object(
+ object->bank_field.region_obj, action,
+ &state_list, depth);
+ if (ACPI_FAILURE(status))
+ goto error_exit;
+ break;

case ACPI_TYPE_LOCAL_INDEX_FIELD:

- next_object = object->index_field.index_obj;
- status =
- acpi_ut_create_update_state_and_push(object->
- index_field.
- data_obj,
- action,
- &state_list);
- if (ACPI_FAILURE(status)) {
- goto error_exit;
- }
- break;
+ next_object = object->index_field.index_obj;
+ status = acpi_ut_push_or_update_object(
+ object->index_field.data_obj, action,
+ &state_list, depth);
+ if (ACPI_FAILURE(status))
+ goto error_exit;
+ break;

case ACPI_TYPE_LOCAL_REFERENCE:
/*
--
2.43.0