[RFC PATCH] rfcomm: avoid the nested locks in rfcomm_dev_add and fix rfcomm_dev_state_change

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



By setting the bit RFCOMM_TTY_RELEASED in the dev->flags we prevent the code to
take any reference to the device. This way we can unnest the rfcomm_dev_lock and
the rfcomm_dlc_lock in rfcomm_dev_add().

In rfcomm_dev_state_change we can now take a reference to the device without
having to drop the dlc lock: this way we ensure the dev cannot be used after
being freed.

Signed-off-by: Gianluca Anzolin <gianluca@xxxxxxxxxxxxxx>
---
 net/bluetooth/rfcomm/tty.c | 67 ++++++++++++++++++++++++----------------------
 1 file changed, 35 insertions(+), 32 deletions(-)

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 50b38d7..6ae7882e 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -196,6 +196,9 @@ static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc)
 	if (!dev)
 		return -ENOMEM;
 
+	/* avoid taking references to dev if not initialized */
+	set_bit(RFCOMM_TTY_RELEASED, &dev->flags);
+
 	spin_lock(&rfcomm_dev_lock);
 
 	if (req->dev_id < 0) {
@@ -233,6 +236,8 @@ static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc)
 
 	list_add(&dev->list, head);
 
+	spin_unlock(&rfcomm_dev_lock);
+
 	bacpy(&dev->src, &req->src);
 	bacpy(&dev->dst, &req->dst);
 	dev->channel = req->channel;
@@ -277,19 +282,25 @@ static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc)
 	   holds reference to this module. */
 	__module_get(THIS_MODULE);
 
-out:
-	spin_unlock(&rfcomm_dev_lock);
-
-	if (err < 0)
-		goto free;
-
 	dev->tty_dev = tty_port_register_device(&dev->port, rfcomm_tty_driver,
 			dev->id, NULL);
 	if (IS_ERR(dev->tty_dev)) {
 		err = PTR_ERR(dev->tty_dev);
+
+		module_put(THIS_MODULE);
+
 		spin_lock(&rfcomm_dev_lock);
 		list_del(&dev->list);
 		spin_unlock(&rfcomm_dev_lock);
+
+		skb_queue_purge(&dev->pending);
+
+		rfcomm_dlc_lock(dlc);
+		dlc->data_ready   = NULL;
+		dlc->state_change = NULL;
+		dlc->modem_status = NULL;
+		dlc->owner = NULL;
+		rfcomm_dlc_unlock(dlc);
 		goto free;
 	}
 
@@ -301,8 +312,12 @@ out:
 	if (device_create_file(dev->tty_dev, &dev_attr_channel) < 0)
 		BT_ERR("Failed to create channel attribute");
 
-	return dev->id;
+	/* the device is initialized, we can take references to it */
+	clear_bit(RFCOMM_TTY_RELEASED, &dev->flags);
 
+	return dev->id;
+out:
+	spin_unlock(&rfcomm_dev_lock);
 free:
 	kfree(dev);
 	return err;
@@ -585,33 +600,21 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
 		wake_up_interruptible(&dev->port.open_wait);
 	} else if (dlc->state == BT_CLOSED) {
 		tty = tty_port_tty_get(&dev->port);
-		if (!tty) {
-			if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) {
-				/* Drop DLC lock here to avoid deadlock
-				 * 1. rfcomm_dev_get will take rfcomm_dev_lock
-				 *    but in rfcomm_dev_add there's lock order:
-				 *    rfcomm_dev_lock -> dlc lock
-				 * 2. tty_port_put will deadlock if it's
-				 *    the last reference
-				 *
-				 * FIXME: when we release the lock anything
-				 * could happen to dev, even its destruction
-				 */
-				rfcomm_dlc_unlock(dlc);
-				if (rfcomm_dev_get(dev->id) == NULL) {
-					rfcomm_dlc_lock(dlc);
-					return;
-				}
-
-				set_bit(RFCOMM_TTY_RELEASED, &dev->flags);
-				tty_port_put(&dev->port);
-
-				tty_port_put(&dev->port);
-				rfcomm_dlc_lock(dlc);
-			}
-		} else {
+		if (tty) {
 			tty_hangup(tty);
 			tty_kref_put(tty);
+		} else if (!test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags))
+			;
+		else if (rfcomm_dev_get(dev->id)) {
+			if (!test_and_set_bit(RFCOMM_TTY_RELEASED, &dev->flags))
+				tty_port_put(&dev->port);
+
+			/* tty_port_put will deadlock if it is
+			 * the last reference
+			 */
+			rfcomm_dlc_unlock(dlc);
+			tty_port_put(&dev->port);
+			rfcomm_dlc_lock(dlc);
 		}
 	}
 }
-- 
1.8.3.3

--
To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux