⬢ DragonFlyBSD Kernel Audit
DF-2307 / fix.diff
← back to finding ↓ download raw
--- a/sys/bus/gpio/gpio_acpi/gpio_acpi.c
+++ b/sys/bus/gpio/gpio_acpi/gpio_acpi.c
@@ -56,6 +56,7 @@
 	u_int pin;
 	void *cookie;
 	int trigger;
+	volatile int inflight;	/* DF-2307: queued AcpiOsExecute items */
 };
 
 struct acpi_gpio_handler_data {
@@ -314,6 +315,13 @@
 	ACPI_STATUS s;
 	char buf[5];
 
+	/*
+	 * DF-2307: if unmap_aei leaked this info because we would not drain
+	 * in time, dev is NULL -- evaluate nothing and leave.
+	 */
+	if (info->dev == NULL)
+		goto out;
+
 	handle = acpi_get_handle(device_get_parent(info->dev));
 	if (info->trigger == ACPI_EDGE_SENSITIVE) {
 		ksnprintf(buf, sizeof(buf), "_E%02X", info->pin);
@@ -336,6 +344,10 @@
 		if (ACPI_FAILURE(s))
 			device_printf(info->dev, "evaluating _EVT failed\n");
 	}
+out:
+	/* DF-2307: unmap_aei waits on this before freeing the info. */
+	atomic_subtract_int(&info->inflight, 1);
+	wakeup(info);
 }
 
 static void
@@ -344,8 +356,12 @@
 	struct acpi_event_info *info = (struct acpi_event_info *)arg;
 	ACPI_STATUS s;
 
+	/* DF-2307: account the item BEFORE queueing so unmap_aei can never
+	 * observe a false zero. */
+	atomic_add_int(&info->inflight, 1);
 	s = AcpiOsExecute(OSL_GPE_HANDLER, gpio_acpi_handle_event, arg);
 	if (ACPI_FAILURE(s)) {
+		atomic_subtract_int(&info->inflight, 1);
 		device_printf(info->dev,
 		    "AcpiOsExecute for Acpi Event handler failed\n");
 	}
@@ -449,7 +465,23 @@
 		KKASSERT(info->dev != NULL);
 		GPIO_TEARDOWN_INTR(sc->parent, info->cookie);
 		GPIO_FREE_INTR(sc->parent, info->cookie);
-		/* XXX Wait until ACPI Event handler has finished */
+		/*
+		 * DF-2307: TEARDOWN stops NEW dispatches but does not drain
+		 * items gpio_acpi_aei_handler already queued through
+		 * AcpiOsExecute.  Wait for them; if the ACPI task queue is
+		 * stalled past the grace period, LEAK this info instead of
+		 * freeing memory the deferred handler still dereferences.
+		 */
+		{
+			int spin;
+
+			for (spin = 0;
+			     spin < 10 * hz && info->inflight != 0;
+			     spin++)
+				tsleep(info, 0, "gpioaei", 1);
+			if (info->inflight != 0)
+				continue;	/* leak: never memset/free */
+		}
 		memset(info, 0, sizeof(*info));
 	}
 	kfree(sc->infos, M_DEVBUF);