⬢ DragonFlyBSD Kernel Audit
DF-5521 / fix.diff
← back to finding ↓ download raw
--- a/sys/bus/iicbus/iiconf.c
+++ b/sys/bus/iicbus/iiconf.c
@@ -148,6 +148,22 @@
 }
 
 /*
+ * iicbus_bus_owned()
+ *
+ * Non-zero when 'dev' currently owns 'bus', i.e. an iicbus transaction
+ * routed through 'dev' is in flight.  Bus drivers layered on top of
+ * iicbus (e.g. the iicsmb bridge) must refuse to detach while they own
+ * the bus, otherwise a caller sleeping inside iicbus_read()/write()
+ * resumes in freed module text and iicbus_softc->owner dangles.
+ */
+int
+iicbus_bus_owned(device_t bus, device_t dev)
+{
+	struct iicbus_softc *sc = (struct iicbus_softc *)device_get_softc(bus);
+
+	return (sc->owner == dev);
+}
+/*
  * iicbus_started()
  *
  * Test if the iicbus is started by the controller

--- a/sys/bus/iicbus/iiconf.h
+++ b/sys/bus/iicbus/iiconf.h
@@ -97,6 +97,7 @@
 extern int iicbus_release_bus(device_t, device_t);
 
 extern void iicbus_intr(device_t, int, char *);
+extern int	iicbus_bus_owned(device_t, device_t);
 
 extern int iicbus_null_repeated_start(device_t, u_char);
 extern int iicbus_null_callback(device_t, int, caddr_t);

--- a/sys/bus/iicbus/iicsmb.c
+++ b/sys/bus/iicbus/iicsmb.c
@@ -165,9 +165,23 @@
 iicsmb_detach(device_t dev)
 {
 	struct iicsmb_softc *sc = (struct iicsmb_softc *)device_get_softc(dev);
-
-	bus_generic_detach(dev);
-	if (sc->smbus) {
+	device_t parent = device_get_parent(dev);
+	int error;
+
+	/*
+	 * Refuse to detach (kldunload) while an SMBus client transaction
+	 * routed through this bridge is in flight: we are the iicbus owner
+	 * for the whole transaction, and the caller can be asleep inside
+	 * iicbus_read()/iicbus_write() below this bridge.  Completing the
+	 * unload frees the module text the sleeper returns into (UAF) and
+	 * leaves iicbus_softc->owner dangling for later iicbus_intr()s.
+	 */
+	if (parent != NULL && iicbus_bus_owned(parent, dev))
+		return (EBUSY);
+
+	if ((error = bus_generic_detach(dev)) != 0)
+		return (error);
+	if (sc->smbus != NULL) {
 		device_delete_child(dev, sc->smbus);
 	}
 

--- a/sys/bus/smbus/smbus.c
+++ b/sys/bus/smbus/smbus.c
@@ -106,6 +106,17 @@
 smbus_detach(device_t dev)
 {
 	int error;
+	struct smbus_softc *sc = (struct smbus_softc *)device_get_softc(dev);
+
+	/*
+	 * Refuse to detach while a client holds the bus: force-detaching here
+	 * (kldunload of a driver module registered on this bus, e.g. iicsmb's
+	 * smbus/iicsmb registration at iicsmb.c:510) frees the softc under a
+	 * caller that is asleep inside an SMBus transaction routed through
+	 * this bus, and detaches its device nodes.
+	 */
+	if (sc->owner != NULL)
+		return (EBUSY);
 
 	error = bus_generic_detach(dev);
 	if (error)