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

From: Rafael J. Wysocki (Intel)

Date: Wed Nov 04 2026 - 16:56:16 EST


On Fri, Oct 23, 2026 at 9:46 AM Rahul Rao <shreeramrahul94@xxxxxxxxx> wrote:
>
> 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>

Please submit this change to the upstream ACPICA project on GitHub as
a pull request. If applied, Linux will receive it from there as per

Documentation/driver-api/acpi/linuxized-acpica.rst

> ---
> 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
>