diff --git a/libs/surfaces/contourdesign/contourdesign.cc b/libs/surfaces/contourdesign/contourdesign.cc
index bddddf6da6..0368a49651 100644
--- a/libs/surfaces/contourdesign/contourdesign.cc
+++ b/libs/surfaces/contourdesign/contourdesign.cc
@@ -369,9 +369,31 @@ ContourDesignControlProtocol::release_device ()
 		return;
 	}
 
+	/* The interrupt transfer is perpetually re-submitted in handle_event(),
+	 * so a transfer is in flight at this point. Cancel it and drain libusb
+	 * events ourselves so the callback runs (status = CANCELLED) and the
+	 * transfer is no longer in flight before we touch the handle:
+	 *   - libusb_close()  on a handle with an in-flight transfer is UB
+	 *   - libusb_release_interface() after libusb_close() is a use-after-
+	 *     free of dev_handle->lock and trips usbi_mutex_lock on exit.
+	 * The BaseUI thread that normally pumps events has already been told
+	 * to quit (stop() ran first), so we drive completion synchronously.
+	 */
+	DEBUG_TRACE (DEBUG::ContourDesignControl, "release_device: cancel pending transfer\n");
+	if (_usb_transfer) {
+		libusb_cancel_transfer (_usb_transfer);
+		for (int i = 0; i < 20; ++i) {
+			struct timeval tv = { 0, 25000 }; /* 25 ms */
+			if (libusb_handle_events_timeout_completed (NULL, &tv, NULL) < 0) {
+				break;
+			}
+		}
+	}
+
+	DEBUG_TRACE (DEBUG::ContourDesignControl, "release_device: release_interface + close\n");
+	libusb_release_interface (_dev_handle, 0);
 	libusb_close (_dev_handle);
 	libusb_free_transfer (_usb_transfer);
-	libusb_release_interface (_dev_handle, 0);
 	_usb_transfer = 0;
 	_dev_handle = 0;
 }
