[PATCH v1 08/30] ACPICA: Events: Track in-flight notify handlers to prevent use-after-free on table unload
From: Pawel Chmielewski
Date: Tue Oct 20 2026 - 10:37:11 EST
From: ikaros <void0red@xxxxxxxxx>
Introduce per-OwnerId notify dispatch tracking with a spinlock to gate
table unload against concurrent notify execution. Before queueing, a new
notify checks whether its owning table is being unloaded and rejects the
request if so. AcpiTbUnloadTable sets the unloading gate before namespace
deletion and aborts with AE_ABORT_METHOD if any notify handler for the
table is still running, preventing use of freed namespace nodes.
Link: https://github.com/acpica/acpica/commit/3212963b406e
Closes: https://github.com/open-acpica/acpica/issues/1162
Signed-off-by: ikaros <void0red@xxxxxxxxx>
Signed-off-by: Pawel Chmielewski <pawel.chmielewski@xxxxxxxxx>
---
drivers/acpi/acpica/acglobal.h | 7 +++++++
drivers/acpi/acpica/aclocal.h | 1 +
drivers/acpi/acpica/evmisc.c | 32 +++++++++++++++++++++++++++++++
drivers/acpi/acpica/tbdata.c | 35 ++++++++++++++++++++++++++++++++++
drivers/acpi/acpica/utmutex.c | 6 ++++++
5 files changed, 81 insertions(+)
diff --git a/drivers/acpi/acpica/acglobal.h b/drivers/acpi/acpica/acglobal.h
index b6e31517650e..08b8270b10de 100644
--- a/drivers/acpi/acpica/acglobal.h
+++ b/drivers/acpi/acpica/acglobal.h
@@ -127,6 +127,13 @@ ACPI_GLOBAL(acpi_interface_handler, acpi_gbl_interface_handler);
ACPI_GLOBAL(struct acpi_sci_handler_info *, acpi_gbl_sci_handler_list);
ACPI_GLOBAL(struct acpi_ged_handler_info *, acpi_gbl_ged_handler_list);
+/* Notify dispatch tracking: per-owner_id counters with spinlock */
+
+#define ACPI_NOTIFY_COUNT_MAX_OWNER (ACPI_OWNER_ID_MAX + 1) /* 4096 */
+ACPI_GLOBAL(u16, acpi_gbl_notify_execute_count[ACPI_NOTIFY_COUNT_MAX_OWNER]);
+ACPI_GLOBAL(u8, acpi_gbl_notify_unloading[ACPI_NOTIFY_COUNT_MAX_OWNER]);
+ACPI_GLOBAL(acpi_spinlock, acpi_gbl_notify_lock);
+
/* Owner ID support */
ACPI_GLOBAL(u32, acpi_gbl_owner_id_mask[ACPI_NUM_OWNERID_MASKS]);
diff --git a/drivers/acpi/acpica/aclocal.h b/drivers/acpi/acpica/aclocal.h
index 1ecc700f6e88..e140c27513a0 100644
--- a/drivers/acpi/acpica/aclocal.h
+++ b/drivers/acpi/acpica/aclocal.h
@@ -673,6 +673,7 @@ struct acpi_notify_info {
struct acpi_namespace_node *node;
union acpi_operand_object *handler_list_head;
struct acpi_global_notify_handler *global;
+ acpi_owner_id owner_id;
};
/* Generic state is union of structs above */
diff --git a/drivers/acpi/acpica/evmisc.c b/drivers/acpi/acpica/evmisc.c
index 723db68626ea..8401aee6dd3e 100644
--- a/drivers/acpi/acpica/evmisc.c
+++ b/drivers/acpi/acpica/evmisc.c
@@ -72,6 +72,7 @@ acpi_ev_queue_notify_request(struct acpi_namespace_node *node, u32 notify_value)
union acpi_generic_state *info;
u8 handler_list_id = 0;
acpi_status status = AE_OK;
+ acpi_cpu_flags flags;
ACPI_FUNCTION_NAME(ev_queue_notify_request);
@@ -128,6 +129,7 @@ acpi_ev_queue_notify_request(struct acpi_namespace_node *node, u32 notify_value)
info->notify.handler_list_id = handler_list_id;
info->notify.handler_list_head = handler_list_head;
info->notify.global = &acpi_gbl_global_notify[handler_list_id];
+ info->notify.owner_id = node->owner_id;
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Dispatching Notify on [%4.4s] (%s) Value 0x%2.2X (%s) Node %p\n",
@@ -136,9 +138,31 @@ acpi_ev_queue_notify_request(struct acpi_namespace_node *node, u32 notify_value)
acpi_ut_get_notify_name(notify_value, ACPI_TYPE_ANY),
node));
+ if (node->owner_id > 0 &&
+ node->owner_id < ACPI_NOTIFY_COUNT_MAX_OWNER) {
+ flags = acpi_os_acquire_lock(acpi_gbl_notify_lock);
+
+ /* Reject if table is being unloaded to avoid use-after-free of Node */
+
+ if (acpi_gbl_notify_unloading[node->owner_id]) {
+ acpi_os_release_lock(acpi_gbl_notify_lock, flags);
+ acpi_ut_delete_generic_state(info);
+ return (AE_OK);
+ }
+
+ acpi_gbl_notify_execute_count[node->owner_id]++;
+ acpi_os_release_lock(acpi_gbl_notify_lock, flags);
+ }
+
status = acpi_os_execute(OSL_NOTIFY_HANDLER,
acpi_ev_notify_dispatch, info);
if (ACPI_FAILURE(status)) {
+ if (node->owner_id > 0 &&
+ node->owner_id < ACPI_NOTIFY_COUNT_MAX_OWNER) {
+ flags = acpi_os_acquire_lock(acpi_gbl_notify_lock);
+ acpi_gbl_notify_execute_count[node->owner_id]--;
+ acpi_os_release_lock(acpi_gbl_notify_lock, flags);
+ }
acpi_ut_delete_generic_state(info);
}
@@ -162,6 +186,7 @@ static void ACPI_SYSTEM_XFACE acpi_ev_notify_dispatch(void *context)
{
union acpi_generic_state *info = (union acpi_generic_state *)context;
union acpi_operand_object *handler_obj;
+ acpi_cpu_flags flags;
ACPI_FUNCTION_ENTRY();
@@ -187,6 +212,13 @@ static void ACPI_SYSTEM_XFACE acpi_ev_notify_dispatch(void *context)
/* All done with the info object */
+ if (info->notify.owner_id > 0 &&
+ info->notify.owner_id < ACPI_NOTIFY_COUNT_MAX_OWNER) {
+ flags = acpi_os_acquire_lock(acpi_gbl_notify_lock);
+ acpi_gbl_notify_execute_count[info->notify.owner_id]--;
+ acpi_os_release_lock(acpi_gbl_notify_lock, flags);
+ }
+
acpi_ut_delete_generic_state(info);
}
diff --git a/drivers/acpi/acpica/tbdata.c b/drivers/acpi/acpica/tbdata.c
index 144f15011eff..1388d484787f 100644
--- a/drivers/acpi/acpica/tbdata.c
+++ b/drivers/acpi/acpica/tbdata.c
@@ -1043,6 +1043,8 @@ acpi_status acpi_tb_unload_table(u32 table_index)
{
acpi_status status = AE_OK;
struct acpi_table_header *table;
+ acpi_owner_id owner_id;
+ acpi_cpu_flags flags;
ACPI_FUNCTION_TRACE(tb_unload_table);
@@ -1072,9 +1074,42 @@ acpi_status acpi_tb_unload_table(u32 table_index)
}
#endif /* !ACPI_REDUCED_HARDWARE */
+ /*
+ * Gate notify dispatch for this table's owner_id so no new notifies
+ * are queued against namespace nodes that are about to be freed.
+ * If any notify dispatch is in-flight, abort the unload; the caller
+ * must retry after the dispatch completes.
+ */
+ owner_id = acpi_gbl_root_table_list.tables[table_index].owner_id;
+ if (owner_id > 0 && owner_id < ACPI_NOTIFY_COUNT_MAX_OWNER) {
+ flags = acpi_os_acquire_lock(acpi_gbl_notify_lock);
+ acpi_gbl_notify_unloading[owner_id] = TRUE;
+
+ if (acpi_gbl_notify_execute_count[owner_id] > 0) {
+ acpi_gbl_notify_unloading[owner_id] = FALSE;
+ acpi_os_release_lock(acpi_gbl_notify_lock, flags);
+ return_ACPI_STATUS(AE_ABORT_METHOD);
+ }
+
+ acpi_os_release_lock(acpi_gbl_notify_lock, flags);
+ }
+
/* Delete the portion of the namespace owned by this table */
status = acpi_tb_delete_namespace_by_owner(table_index);
+
+ /*
+ * Clear the unloading gate. If namespace deletion failed the table
+ * remains loaded so notifies must be allowed again. If it succeeded
+ * the owner_id is about to be released and may be reused, so clear
+ * the gate before release to avoid a stale flag on the new owner.
+ */
+ if (owner_id > 0 && owner_id < ACPI_NOTIFY_COUNT_MAX_OWNER) {
+ flags = acpi_os_acquire_lock(acpi_gbl_notify_lock);
+ acpi_gbl_notify_unloading[owner_id] = FALSE;
+ acpi_os_release_lock(acpi_gbl_notify_lock, flags);
+ }
+
if (ACPI_FAILURE(status)) {
return_ACPI_STATUS(status);
}
diff --git a/drivers/acpi/acpica/utmutex.c b/drivers/acpi/acpica/utmutex.c
index 2e465e6a0ab6..36d1533ecef2 100644
--- a/drivers/acpi/acpica/utmutex.c
+++ b/drivers/acpi/acpica/utmutex.c
@@ -62,6 +62,11 @@ acpi_status acpi_ut_mutex_initialize(void)
return_ACPI_STATUS(status);
}
+ status = acpi_os_create_lock(&acpi_gbl_notify_lock);
+ if (ACPI_FAILURE(status)) {
+ return_ACPI_STATUS(status);
+ }
+
/* Mutex for _OSI support */
status = acpi_os_create_mutex(&acpi_gbl_osi_mutex);
@@ -111,6 +116,7 @@ void acpi_ut_mutex_terminate(void)
acpi_os_delete_lock(acpi_gbl_gpe_lock);
acpi_os_delete_raw_lock(acpi_gbl_hardware_lock);
acpi_os_delete_lock(acpi_gbl_reference_count_lock);
+ acpi_os_delete_lock(acpi_gbl_notify_lock);
/* Delete the reader/writer lock */
--
2.50.1