2013-07-29 15:08:07

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v5 0/6] rfcomm: Implement rfcomm as a proper tty_port

This patchset addresses an issue with the rfcomm tty driver in the
current stable kernels that manifests itself as a sudden lockup of the
whole machine or as a OOPS if we are lucky enough (I wasn't).

Triggering the problem is very easy:

1) establish a bluetooth connection with a bluetooth host
2) open the tty it provides with some program
3) turn off the bluetooth host or take it out of range

After a timeout the machine freezes.

Another way to trigger these lockups is to simply release the rfcomm
tty.

This happens beacuse the underlying tty_struct objects and tty_port
objects are freed while being used: the code doesn't take proper
references to them.

The following patches address the problem by implementing a proper
tty_port driver for rfcomm.

There are still some issues left: one relevant to flow control (which is
also missing in the current code) and another relevant to a corner case
in rfcomm_dev_state_change() that I intend to fix with a future patch.
They are commented with a FIXME.

Changes from v4:
[PATCH 3/6]: left the debug message in rfcomm_tty_open()
[PATCH 5/6]: always use !test_and_set_bit() to release the tty_port

Thank you,
Gianluca

Gianluca Anzolin (6):
rfcomm: Take proper tty_struct references
rfcomm: Remove the device from the list in the destructor
rfcomm: Move the tty initialization and cleanup out of open/close
rfcomm: Implement .activate, .shutdown and .carrier_raised methods
rfcomm: Fix the reference counting of tty_port
rfcomm: Purge the dlc->tx_queue to avoid circular dependency

net/bluetooth/rfcomm/tty.c | 271 +++++++++++++++++++++------------------------
1 file changed, 126 insertions(+), 145 deletions(-)

--
1.8.3.4


2013-07-31 17:50:31

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH v5 0/6] rfcomm: Implement rfcomm as a proper tty_port

On 07/29/2013 11:08 AM, Gianluca Anzolin wrote:
> This patchset addresses an issue with the rfcomm tty driver in the
> current stable kernels that manifests itself as a sudden lockup of the
> whole machine or as a OOPS if we are lucky enough (I wasn't).
>
> Triggering the problem is very easy:
>
> 1) establish a bluetooth connection with a bluetooth host
> 2) open the tty it provides with some program
> 3) turn off the bluetooth host or take it out of range
>
> After a timeout the machine freezes.
>
> Another way to trigger these lockups is to simply release the rfcomm
> tty.
>
> This happens beacuse the underlying tty_struct objects and tty_port
> objects are freed while being used: the code doesn't take proper
> references to them.
>
> The following patches address the problem by implementing a proper
> tty_port driver for rfcomm.
>
> There are still some issues left: one relevant to flow control (which is
> also missing in the current code) and another relevant to a corner case
> in rfcomm_dev_state_change() that I intend to fix with a future patch.
> They are commented with a FIXME.
>
> Changes from v4:
> [PATCH 3/6]: left the debug message in rfcomm_tty_open()
> [PATCH 5/6]: always use !test_and_set_bit() to release the tty_port

I reviewed these changes and retested. All ok.

Regards,
Peter Hurley

>
> Thank you,
> Gianluca
>
> Gianluca Anzolin (6):
> rfcomm: Take proper tty_struct references
> rfcomm: Remove the device from the list in the destructor
> rfcomm: Move the tty initialization and cleanup out of open/close
> rfcomm: Implement .activate, .shutdown and .carrier_raised methods
> rfcomm: Fix the reference counting of tty_port
> rfcomm: Purge the dlc->tx_queue to avoid circular dependency
>
> net/bluetooth/rfcomm/tty.c | 271 +++++++++++++++++++++------------------------
> 1 file changed, 126 insertions(+), 145 deletions(-)
>

2013-07-29 15:08:13

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v5 6/6] rfcomm: Purge the dlc->tx_queue to avoid circular dependency

In rfcomm_tty_cleanup we purge the dlc->tx_queue which may contain
socket buffers referencing the tty_port and thus preventing the tty_port
destruction.

Signed-off-by: Gianluca Anzolin <[email protected]>
---
net/bluetooth/rfcomm/tty.c | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 3e078b7..6d126fa 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -668,6 +668,12 @@ static void rfcomm_tty_cleanup(struct tty_struct *tty)
tty->driver_data = NULL;
rfcomm_dlc_unlock(dev->dlc);

+ /*
+ * purge the dlc->tx_queue to avoid circular dependencies
+ * between dev and dlc
+ */
+ skb_queue_purge(&dev->dlc->tx_queue);
+
tty_port_put(&dev->port);
}

--
1.8.3.4

2013-07-29 15:08:12

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v5 5/6] rfcomm: Fix the reference counting of tty_port

The tty_port can be released in two cases: when we get a HUP in the
functions rfcomm_tty_hangup() and rfcomm_dev_state_change(). Or when the
user releases the device in rfcomm_release_dev().

In these cases we set the flag RFCOMM_TTY_RELEASED so that no other
function can get a reference to the tty_port.
The use of !test_and_set_bit(RFCOMM_TTY_RELEASED) ensures that the
'initial' tty_port reference is only dropped once.

The rfcomm_dev_del function is removed becase it isn't used anymore.

Signed-off-by: Gianluca Anzolin <[email protected]>
---

Notes:
v5: always use !test_and_set_bit() to release the tty_port

net/bluetooth/rfcomm/tty.c | 46 ++++++++++++----------------------------------
1 file changed, 12 insertions(+), 34 deletions(-)

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 583f713..3e078b7 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -324,23 +324,6 @@ free:
return err;
}

-static void rfcomm_dev_del(struct rfcomm_dev *dev)
-{
- unsigned long flags;
- BT_DBG("dev %p", dev);
-
- BUG_ON(test_and_set_bit(RFCOMM_TTY_RELEASED, &dev->flags));
-
- spin_lock_irqsave(&dev->port.lock, flags);
- if (dev->port.count > 0) {
- spin_unlock_irqrestore(&dev->port.lock, flags);
- return;
- }
- spin_unlock_irqrestore(&dev->port.lock, flags);
-
- tty_port_put(&dev->port);
-}
-
/* ---- Send buffer ---- */
static inline unsigned int rfcomm_room(struct rfcomm_dlc *dlc)
{
@@ -454,8 +437,9 @@ static int rfcomm_release_dev(void __user *arg)
tty_kref_put(tty);
}

- if (!test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags))
- rfcomm_dev_del(dev);
+ if (!test_and_set_bit(RFCOMM_TTY_RELEASED, &dev->flags))
+ tty_port_put(&dev->port);
+
tty_port_put(&dev->port);
return 0;
}
@@ -607,6 +591,9 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
* 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) {
@@ -614,7 +601,10 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
return;
}

- rfcomm_dev_del(dev);
+ if (!test_and_set_bit(RFCOMM_TTY_RELEASED,
+ &dev->flags))
+ tty_port_put(&dev->port);
+
tty_port_put(&dev->port);
rfcomm_dlc_lock(dlc);
}
@@ -741,16 +731,10 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
{
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;

- if (!dev)
- return;
-
BT_DBG("tty %p dev %p dlc %p opened %d", tty, dev, dev->dlc,
dev->port.count);

tty_port_close(&dev->port, tty, filp);
-
- if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags))
- tty_port_put(&dev->port);
}

static int rfcomm_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
@@ -1050,17 +1034,11 @@ static void rfcomm_tty_hangup(struct tty_struct *tty)

BT_DBG("tty %p dev %p", tty, dev);

- if (!dev)
- return;
-
tty_port_hangup(&dev->port);

- if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) {
- if (rfcomm_dev_get(dev->id) == NULL)
- return;
- rfcomm_dev_del(dev);
+ if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags) &&
+ !test_and_set_bit(RFCOMM_TTY_RELEASED, &dev->flags))
tty_port_put(&dev->port);
- }
}

static int rfcomm_tty_tiocmget(struct tty_struct *tty)
--
1.8.3.4

2013-07-29 15:08:11

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v5 4/6] rfcomm: Implement .activate, .shutdown and .carrier_raised methods

Implement .activate, .shutdown and .carrier_raised methods of tty_port
to manage the dlc, moving the code from rfcomm_tty_install() and
rfcomm_tty_cleanup() functions.

At the same time the tty .open()/.close() and .hangup() methods are
changed to use the tty_port helpers that properly call the
aforementioned tty_port methods.

Signed-off-by: Gianluca Anzolin <[email protected]>
---
net/bluetooth/rfcomm/tty.c | 117 ++++++++++++++++++---------------------------
1 file changed, 47 insertions(+), 70 deletions(-)

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 73dd615..583f713 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -58,7 +58,6 @@ struct rfcomm_dev {
uint modem_status;

struct rfcomm_dlc *dlc;
- wait_queue_head_t wait;

struct device *tty_dev;

@@ -104,8 +103,39 @@ static void rfcomm_dev_destruct(struct tty_port *port)
module_put(THIS_MODULE);
}

+/* device-specific initialization: open the dlc */
+static int rfcomm_dev_activate(struct tty_port *port, struct tty_struct *tty)
+{
+ struct rfcomm_dev *dev = container_of(port, struct rfcomm_dev, port);
+
+ return rfcomm_dlc_open(dev->dlc, &dev->src, &dev->dst, dev->channel);
+}
+
+/* we block the open until the dlc->state becomes BT_CONNECTED */
+static int rfcomm_dev_carrier_raised(struct tty_port *port)
+{
+ struct rfcomm_dev *dev = container_of(port, struct rfcomm_dev, port);
+
+ return (dev->dlc->state == BT_CONNECTED);
+}
+
+/* device-specific cleanup: close the dlc */
+static void rfcomm_dev_shutdown(struct tty_port *port)
+{
+ struct rfcomm_dev *dev = container_of(port, struct rfcomm_dev, port);
+
+ if (dev->tty_dev->parent)
+ device_move(dev->tty_dev, NULL, DPM_ORDER_DEV_LAST);
+
+ /* close the dlc */
+ rfcomm_dlc_close(dev->dlc, 0);
+}
+
static const struct tty_port_operations rfcomm_port_ops = {
.destruct = rfcomm_dev_destruct,
+ .activate = rfcomm_dev_activate,
+ .shutdown = rfcomm_dev_shutdown,
+ .carrier_raised = rfcomm_dev_carrier_raised,
};

static struct rfcomm_dev *__rfcomm_dev_get(int id)
@@ -228,7 +258,6 @@ static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc)

tty_port_init(&dev->port);
dev->port.ops = &rfcomm_port_ops;
- init_waitqueue_head(&dev->wait);

skb_queue_head_init(&dev->pending);

@@ -563,9 +592,12 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
BT_DBG("dlc %p dev %p err %d", dlc, dev, err);

dev->err = err;
- wake_up_interruptible(&dev->wait);
+ if (dlc->state == BT_CONNECTED) {
+ device_move(dev->tty_dev, rfcomm_get_device(dev),
+ DPM_ORDER_DEV_AFTER_PARENT);

- if (dlc->state == BT_CLOSED) {
+ 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)) {
@@ -640,17 +672,10 @@ static void rfcomm_tty_cleanup(struct tty_struct *tty)
{
struct rfcomm_dev *dev = tty->driver_data;

- if (dev->tty_dev->parent)
- device_move(dev->tty_dev, NULL, DPM_ORDER_DEV_LAST);
-
- /* Close DLC and dettach TTY */
- rfcomm_dlc_close(dev->dlc, 0);
-
clear_bit(RFCOMM_TTY_ATTACHED, &dev->flags);

rfcomm_dlc_lock(dev->dlc);
tty->driver_data = NULL;
- dev->port.tty = NULL;
rfcomm_dlc_unlock(dev->dlc);

tty_port_put(&dev->port);
@@ -662,7 +687,6 @@ static void rfcomm_tty_cleanup(struct tty_struct *tty)
*/
static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
- DECLARE_WAITQUEUE(wait, current);
struct rfcomm_dev *dev;
struct rfcomm_dlc *dlc;
int err;
@@ -676,72 +700,30 @@ static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty)
/* Attach TTY and open DLC */
rfcomm_dlc_lock(dlc);
tty->driver_data = dev;
- dev->port.tty = tty;
rfcomm_dlc_unlock(dlc);
set_bit(RFCOMM_TTY_ATTACHED, &dev->flags);

/* install the tty_port */
err = tty_port_install(&dev->port, driver, tty);
- if (err < 0)
- goto error_no_dlc;
-
- err = rfcomm_dlc_open(dlc, &dev->src, &dev->dst, dev->channel);
- if (err < 0)
- goto error_no_dlc;
+ if (err)
+ rfcomm_tty_cleanup(tty);

- /* Wait for DLC to connect */
- add_wait_queue(&dev->wait, &wait);
- while (1) {
- set_current_state(TASK_INTERRUPTIBLE);
-
- if (dlc->state == BT_CLOSED) {
- err = -dev->err;
- break;
- }
-
- if (dlc->state == BT_CONNECTED)
- break;
-
- if (signal_pending(current)) {
- err = -EINTR;
- break;
- }
-
- tty_unlock(tty);
- schedule();
- tty_lock(tty);
- }
- set_current_state(TASK_RUNNING);
- remove_wait_queue(&dev->wait, &wait);
-
- if (err < 0)
- goto error_no_connection;
-
- device_move(dev->tty_dev, rfcomm_get_device(dev),
- DPM_ORDER_DEV_AFTER_PARENT);
- return 0;
-
-error_no_connection:
- rfcomm_dlc_close(dlc, err);
-error_no_dlc:
- clear_bit(RFCOMM_TTY_ATTACHED, &dev->flags);
- tty_port_put(&dev->port);
return err;
}

static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp)
{
struct rfcomm_dev *dev = tty->driver_data;
- unsigned long flags;
+ int err;

BT_DBG("tty %p id %d", tty, tty->index);

BT_DBG("dev %p dst %pMR channel %d opened %d", dev, &dev->dst,
dev->channel, dev->port.count);

- spin_lock_irqsave(&dev->port.lock, flags);
- dev->port.count++;
- spin_unlock_irqrestore(&dev->port.lock, flags);
+ err = tty_port_open(&dev->port, tty, filp);
+ if (err)
+ return err;

/*
* FIXME: rfcomm should use proper flow control for
@@ -758,7 +740,6 @@ static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp)
static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
{
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
- unsigned long flags;

if (!dev)
return;
@@ -766,14 +747,10 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
BT_DBG("tty %p dev %p dlc %p opened %d", tty, dev, dev->dlc,
dev->port.count);

- spin_lock_irqsave(&dev->port.lock, flags);
- if (!--dev->port.count) {
- spin_unlock_irqrestore(&dev->port.lock, flags);
+ tty_port_close(&dev->port, tty, filp);

- if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags))
- tty_port_put(&dev->port);
- } else
- spin_unlock_irqrestore(&dev->port.lock, flags);
+ if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags))
+ tty_port_put(&dev->port);
}

static int rfcomm_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
@@ -1076,7 +1053,7 @@ static void rfcomm_tty_hangup(struct tty_struct *tty)
if (!dev)
return;

- rfcomm_tty_flush_buffer(tty);
+ tty_port_hangup(&dev->port);

if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) {
if (rfcomm_dev_get(dev->id) == NULL)
@@ -1166,7 +1143,7 @@ int __init rfcomm_init_ttys(void)
rfcomm_tty_driver->subtype = SERIAL_TYPE_NORMAL;
rfcomm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
rfcomm_tty_driver->init_termios = tty_std_termios;
- rfcomm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ rfcomm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
rfcomm_tty_driver->init_termios.c_lflag &= ~ICANON;
tty_set_operations(rfcomm_tty_driver, &rfcomm_ops);

--
1.8.3.4

2013-07-29 15:08:10

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v5 3/6] rfcomm: Move the tty initialization and cleanup out of open/close

Move the tty_struct initialization from rfcomm_tty_open() to
rfcomm_tty_install() and do the same for the cleanup moving the code from
rfcomm_tty_close() to rfcomm_tty_cleanup().

Add also extra error handling in rfcomm_tty_install() because, unlike
.open()/.close(), .cleanup() is not called if .install() fails.

Signed-off-by: Gianluca Anzolin <[email protected]>
---

Notes:
v5: left the debug message in rfcomm_tty_open()

net/bluetooth/rfcomm/tty.c | 114 ++++++++++++++++++++++++++++-----------------
1 file changed, 72 insertions(+), 42 deletions(-)

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 9c0e142..73dd615 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -633,49 +633,61 @@ static void rfcomm_tty_copy_pending(struct rfcomm_dev *dev)
tty_flip_buffer_push(&dev->port);
}

-static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp)
+/* do the reverse of install, clearing the tty fields and releasing the
+ * reference to tty_port
+ */
+static void rfcomm_tty_cleanup(struct tty_struct *tty)
+{
+ struct rfcomm_dev *dev = tty->driver_data;
+
+ if (dev->tty_dev->parent)
+ device_move(dev->tty_dev, NULL, DPM_ORDER_DEV_LAST);
+
+ /* Close DLC and dettach TTY */
+ rfcomm_dlc_close(dev->dlc, 0);
+
+ clear_bit(RFCOMM_TTY_ATTACHED, &dev->flags);
+
+ rfcomm_dlc_lock(dev->dlc);
+ tty->driver_data = NULL;
+ dev->port.tty = NULL;
+ rfcomm_dlc_unlock(dev->dlc);
+
+ tty_port_put(&dev->port);
+}
+
+/* we acquire the tty_port reference since it's here the tty is first used
+ * by setting the termios. We also populate the driver_data field and install
+ * the tty port
+ */
+static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
DECLARE_WAITQUEUE(wait, current);
struct rfcomm_dev *dev;
struct rfcomm_dlc *dlc;
- unsigned long flags;
- int err, id;
-
- id = tty->index;
+ int err;

- BT_DBG("tty %p id %d", tty, id);
-
- /* We don't leak this refcount. For reasons which are not entirely
- clear, the TTY layer will call our ->close() method even if the
- open fails. We decrease the refcount there, and decreasing it
- here too would cause breakage. */
- dev = rfcomm_dev_get(id);
+ dev = rfcomm_dev_get(tty->index);
if (!dev)
return -ENODEV;

- BT_DBG("dev %p dst %pMR channel %d opened %d", dev, &dev->dst,
- dev->channel, dev->port.count);
-
- spin_lock_irqsave(&dev->port.lock, flags);
- if (++dev->port.count > 1) {
- spin_unlock_irqrestore(&dev->port.lock, flags);
- return 0;
- }
- spin_unlock_irqrestore(&dev->port.lock, flags);
-
dlc = dev->dlc;

/* Attach TTY and open DLC */
-
rfcomm_dlc_lock(dlc);
tty->driver_data = dev;
dev->port.tty = tty;
rfcomm_dlc_unlock(dlc);
set_bit(RFCOMM_TTY_ATTACHED, &dev->flags);

+ /* install the tty_port */
+ err = tty_port_install(&dev->port, driver, tty);
+ if (err < 0)
+ goto error_no_dlc;
+
err = rfcomm_dlc_open(dlc, &dev->src, &dev->dst, dev->channel);
if (err < 0)
- return err;
+ goto error_no_dlc;

/* Wait for DLC to connect */
add_wait_queue(&dev->wait, &wait);
@@ -702,15 +714,45 @@ static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp)
set_current_state(TASK_RUNNING);
remove_wait_queue(&dev->wait, &wait);

- if (err == 0)
- device_move(dev->tty_dev, rfcomm_get_device(dev),
- DPM_ORDER_DEV_AFTER_PARENT);
+ if (err < 0)
+ goto error_no_connection;
+
+ device_move(dev->tty_dev, rfcomm_get_device(dev),
+ DPM_ORDER_DEV_AFTER_PARENT);
+ return 0;
+
+error_no_connection:
+ rfcomm_dlc_close(dlc, err);
+error_no_dlc:
+ clear_bit(RFCOMM_TTY_ATTACHED, &dev->flags);
+ tty_port_put(&dev->port);
+ return err;
+}
+
+static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ struct rfcomm_dev *dev = tty->driver_data;
+ unsigned long flags;
+
+ BT_DBG("tty %p id %d", tty, tty->index);

+ BT_DBG("dev %p dst %pMR channel %d opened %d", dev, &dev->dst,
+ dev->channel, dev->port.count);
+
+ spin_lock_irqsave(&dev->port.lock, flags);
+ dev->port.count++;
+ spin_unlock_irqrestore(&dev->port.lock, flags);
+
+ /*
+ * FIXME: rfcomm should use proper flow control for
+ * received data. This hack will be unnecessary and can
+ * be removed when that's implemented
+ */
rfcomm_tty_copy_pending(dev);

rfcomm_dlc_unthrottle(dev->dlc);

- return err;
+ return 0;
}

static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
@@ -727,25 +769,11 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
spin_lock_irqsave(&dev->port.lock, flags);
if (!--dev->port.count) {
spin_unlock_irqrestore(&dev->port.lock, flags);
- if (dev->tty_dev->parent)
- device_move(dev->tty_dev, NULL, DPM_ORDER_DEV_LAST);
-
- /* Close DLC and dettach TTY */
- rfcomm_dlc_close(dev->dlc, 0);
-
- clear_bit(RFCOMM_TTY_ATTACHED, &dev->flags);
-
- rfcomm_dlc_lock(dev->dlc);
- tty->driver_data = NULL;
- dev->port.tty = NULL;
- rfcomm_dlc_unlock(dev->dlc);

if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags))
tty_port_put(&dev->port);
} else
spin_unlock_irqrestore(&dev->port.lock, flags);
-
- tty_port_put(&dev->port);
}

static int rfcomm_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
@@ -1118,6 +1146,8 @@ static const struct tty_operations rfcomm_ops = {
.wait_until_sent = rfcomm_tty_wait_until_sent,
.tiocmget = rfcomm_tty_tiocmget,
.tiocmset = rfcomm_tty_tiocmset,
+ .install = rfcomm_tty_install,
+ .cleanup = rfcomm_tty_cleanup,
};

int __init rfcomm_init_ttys(void)
--
1.8.3.4

2013-07-29 15:08:09

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v5 2/6] rfcomm: Remove the device from the list in the destructor

The current code removes the device from the device list in several
places. Do it only in the destructor instead and in the error path of
rfcomm_add_dev() if the device couldn't be initialized.

Signed-off-by: Gianluca Anzolin <[email protected]>
---
net/bluetooth/rfcomm/tty.c | 27 ++++++---------------------
1 file changed, 6 insertions(+), 21 deletions(-)

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index cd7ff37..9c0e142 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -76,13 +76,6 @@ static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig);

/* ---- Device functions ---- */

-/*
- * The reason this isn't actually a race, as you no doubt have a little voice
- * screaming at you in your head, is that the refcount should never actually
- * reach zero unless the device has already been taken off the list, in
- * rfcomm_dev_del(). And if that's not true, we'll hit the BUG() in
- * rfcomm_dev_destruct() anyway.
- */
static void rfcomm_dev_destruct(struct tty_port *port)
{
struct rfcomm_dev *dev = container_of(port, struct rfcomm_dev, port);
@@ -90,10 +83,9 @@ static void rfcomm_dev_destruct(struct tty_port *port)

BT_DBG("dev %p dlc %p", dev, dlc);

- /* Refcount should only hit zero when called from rfcomm_dev_del()
- which will have taken us off the list. Everything else are
- refcounting bugs. */
- BUG_ON(!list_empty(&dev->list));
+ spin_lock(&rfcomm_dev_lock);
+ list_del(&dev->list);
+ spin_unlock(&rfcomm_dev_lock);

rfcomm_dlc_lock(dlc);
/* Detach DLC if it's owned by this dev */
@@ -282,7 +274,9 @@ out:
dev->id, NULL);
if (IS_ERR(dev->tty_dev)) {
err = PTR_ERR(dev->tty_dev);
+ spin_lock(&rfcomm_dev_lock);
list_del(&dev->list);
+ spin_unlock(&rfcomm_dev_lock);
goto free;
}

@@ -315,10 +309,6 @@ static void rfcomm_dev_del(struct rfcomm_dev *dev)
}
spin_unlock_irqrestore(&dev->port.lock, flags);

- spin_lock(&rfcomm_dev_lock);
- list_del_init(&dev->list);
- spin_unlock(&rfcomm_dev_lock);
-
tty_port_put(&dev->port);
}

@@ -750,13 +740,8 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
dev->port.tty = NULL;
rfcomm_dlc_unlock(dev->dlc);

- if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags)) {
- spin_lock(&rfcomm_dev_lock);
- list_del_init(&dev->list);
- spin_unlock(&rfcomm_dev_lock);
-
+ if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags))
tty_port_put(&dev->port);
- }
} else
spin_unlock_irqrestore(&dev->port.lock, flags);

--
1.8.3.4

2013-07-29 15:08:08

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v5 1/6] rfcomm: Take proper tty_struct references

In net/bluetooth/rfcomm/tty.c the struct tty_struct is used without
taking references. This may lead to a use-after-free of the rfcomm tty.

Fix this by taking references properly, using the tty_port_* helpers
when possible.

The raw assignments of dev->port.tty in rfcomm_tty_open/close are
addressed in the later commit 'rfcomm: Implement .activate, .shutdown
and .carrier_raised methods'.

Signed-off-by: Gianluca Anzolin <[email protected]>
---
net/bluetooth/rfcomm/tty.c | 29 +++++++++++++++++------------
1 file changed, 17 insertions(+), 12 deletions(-)

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index b6e44ad..cd7ff37 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -333,10 +333,9 @@ static inline unsigned int rfcomm_room(struct rfcomm_dlc *dlc)
static void rfcomm_wfree(struct sk_buff *skb)
{
struct rfcomm_dev *dev = (void *) skb->sk;
- struct tty_struct *tty = dev->port.tty;
atomic_sub(skb->truesize, &dev->wmem_alloc);
- if (test_bit(RFCOMM_TTY_ATTACHED, &dev->flags) && tty)
- tty_wakeup(tty);
+ if (test_bit(RFCOMM_TTY_ATTACHED, &dev->flags))
+ tty_port_tty_wakeup(&dev->port);
tty_port_put(&dev->port);
}

@@ -410,6 +409,7 @@ static int rfcomm_release_dev(void __user *arg)
{
struct rfcomm_dev_req req;
struct rfcomm_dev *dev;
+ struct tty_struct *tty;

if (copy_from_user(&req, arg, sizeof(req)))
return -EFAULT;
@@ -429,8 +429,11 @@ static int rfcomm_release_dev(void __user *arg)
rfcomm_dlc_close(dev->dlc, 0);

/* Shut down TTY synchronously before freeing rfcomm_dev */
- if (dev->port.tty)
- tty_vhangup(dev->port.tty);
+ tty = tty_port_tty_get(&dev->port);
+ if (tty) {
+ tty_vhangup(tty);
+ tty_kref_put(tty);
+ }

if (!test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags))
rfcomm_dev_del(dev);
@@ -563,6 +566,7 @@ static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb)
static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
{
struct rfcomm_dev *dev = dlc->owner;
+ struct tty_struct *tty;
if (!dev)
return;

@@ -572,7 +576,8 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
wake_up_interruptible(&dev->wait);

if (dlc->state == BT_CLOSED) {
- if (!dev->port.tty) {
+ 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
@@ -591,8 +596,10 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
tty_port_put(&dev->port);
rfcomm_dlc_lock(dlc);
}
- } else
- tty_hangup(dev->port.tty);
+ } else {
+ tty_hangup(tty);
+ tty_kref_put(tty);
+ }
}
}

@@ -604,10 +611,8 @@ static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig)

BT_DBG("dlc %p dev %p v24_sig 0x%02x", dlc, dev, v24_sig);

- if ((dev->modem_status & TIOCM_CD) && !(v24_sig & RFCOMM_V24_DV)) {
- if (dev->port.tty && !C_CLOCAL(dev->port.tty))
- tty_hangup(dev->port.tty);
- }
+ if ((dev->modem_status & TIOCM_CD) && !(v24_sig & RFCOMM_V24_DV))
+ tty_port_tty_hangup(&dev->port, true);

dev->modem_status =
((v24_sig & RFCOMM_V24_RTC) ? (TIOCM_DSR | TIOCM_DTR) : 0) |
--
1.8.3.4

2013-08-30 22:02:31

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH v5 0/6] rfcomm: Implement rfcomm as a proper tty_port

On 08/30/2013 01:49 PM, Gianluca Anzolin wrote:
> On Tue, Aug 27, 2013 at 01:50:25PM -0400, Peter Hurley wrote:
>> On 08/27/2013 09:57 AM, Alexander Holler wrote:
>>> Am 19.08.2013 22:20, schrieb Peter Hurley:
>>>> On 07/31/2013 01:50 PM, Peter Hurley wrote:
>>>
>>>>> I reviewed these changes and retested. All ok.
>>>>
>>>> Gustavo,
>>>>
>>>> This series fixes a crashing regression from 3.10+ forward.
>>>> Why is this not in linux-next yet?
>>>
>>> 3.8+ to be exact, just in case someone has to deal with older kernels.
>>
>> Thanks for the reminder.
>>
>> This series is too extensive to consider for -stable. Any ideas for
>> how to address this crash for -stable?
>>
>> Regards,
>> Peter Hurley
>>
>
> The tty refcount patch is needed but clearly not sufficient because the system
> locks up when the device is released.
>
> Unfortunately I cannot capture any oops, this is why I went and implemented the
> tty port methods in the first place, inspired by the usb serial code.
>
> I agree the patches are extensive and a solution is needed not only for 3.10
> kernels but also for 3.11.
>
> It's not clear for me how to debug the old code further. A couple of patches
> to make the debug process easier were mentioned before but I cannot find
> them. Could anybody point me at them?

I was thinking more along the lines of what we could go back and revert,
rather than re-engineering another solution.

Regards,
Peter Hurley

2013-08-30 21:26:33

by Alexander Holler

[permalink] [raw]
Subject: Re: [PATCH v5 0/6] rfcomm: Implement rfcomm as a proper tty_port

Am 30.08.2013 19:49, schrieb Gianluca Anzolin:
> It's not clear for me how to debug the old code further. A couple of patches
> to make the debug process easier were mentioned before but I cannot find
> them. Could anybody point me at them?

I think that is the thread you are searching for:

https://lkml.org/lkml/2013/5/16/55

Regards,

Alexander Holler

2013-08-30 17:49:16

by Gianluca Anzolin

[permalink] [raw]
Subject: Re: [PATCH v5 0/6] rfcomm: Implement rfcomm as a proper tty_port

On Tue, Aug 27, 2013 at 01:50:25PM -0400, Peter Hurley wrote:
> On 08/27/2013 09:57 AM, Alexander Holler wrote:
> >Am 19.08.2013 22:20, schrieb Peter Hurley:
> >>On 07/31/2013 01:50 PM, Peter Hurley wrote:
> >
> >>>I reviewed these changes and retested. All ok.
> >>
> >>Gustavo,
> >>
> >>This series fixes a crashing regression from 3.10+ forward.
> >>Why is this not in linux-next yet?
> >
> >3.8+ to be exact, just in case someone has to deal with older kernels.
>
> Thanks for the reminder.
>
> This series is too extensive to consider for -stable. Any ideas for
> how to address this crash for -stable?
>
> Regards,
> Peter Hurley
>

The tty refcount patch is needed but clearly not sufficient because the system
locks up when the device is released.

Unfortunately I cannot capture any oops, this is why I went and implemented the
tty port methods in the first place, inspired by the usb serial code.

I agree the patches are extensive and a solution is needed not only for 3.10
kernels but also for 3.11.

It's not clear for me how to debug the old code further. A couple of patches
to make the debug process easier were mentioned before but I cannot find
them. Could anybody point me at them?

Thank you,

Gianluca

2013-08-28 11:24:33

by Alexander Holler

[permalink] [raw]
Subject: Re: [PATCH v5 0/6] rfcomm: Implement rfcomm as a proper tty_port

Am 27.08.2013 19:50, schrieb Peter Hurley:
> On 08/27/2013 09:57 AM, Alexander Holler wrote:
>> Am 19.08.2013 22:20, schrieb Peter Hurley:
>>> On 07/31/2013 01:50 PM, Peter Hurley wrote:
>>
>>>> I reviewed these changes and retested. All ok.
>>>
>>> Gustavo,
>>>
>>> This series fixes a crashing regression from 3.10+ forward.
>>> Why is this not in linux-next yet?
>>
>> 3.8+ to be exact, just in case someone has to deal with older kernels.
>
> Thanks for the reminder.
>
> This series is too extensive to consider for -stable. Any ideas for
> how to address this crash for -stable?

Besides the BUG_ON() to prevent that something really bad happens, no.

Btw, I've just tested the series with 3.10.9, seems to work.

Thanks a lot,

Alexander Holler

2013-08-27 17:50:25

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH v5 0/6] rfcomm: Implement rfcomm as a proper tty_port

On 08/27/2013 09:57 AM, Alexander Holler wrote:
> Am 19.08.2013 22:20, schrieb Peter Hurley:
>> On 07/31/2013 01:50 PM, Peter Hurley wrote:
>
>>> I reviewed these changes and retested. All ok.
>>
>> Gustavo,
>>
>> This series fixes a crashing regression from 3.10+ forward.
>> Why is this not in linux-next yet?
>
> 3.8+ to be exact, just in case someone has to deal with older kernels.

Thanks for the reminder.

This series is too extensive to consider for -stable. Any ideas for
how to address this crash for -stable?

Regards,
Peter Hurley

2013-08-27 13:57:15

by Alexander Holler

[permalink] [raw]
Subject: Re: [PATCH v5 0/6] rfcomm: Implement rfcomm as a proper tty_port

Am 19.08.2013 22:20, schrieb Peter Hurley:
> On 07/31/2013 01:50 PM, Peter Hurley wrote:

>> I reviewed these changes and retested. All ok.
>
> Gustavo,
>
> This series fixes a crashing regression from 3.10+ forward.
> Why is this not in linux-next yet?

3.8+ to be exact, just in case someone has to deal with older kernels.

Regards,

Alexander Holler

2013-08-21 08:41:55

by Gianluca Anzolin

[permalink] [raw]
Subject: Re: [PATCH v5 6/6] rfcomm: Purge the dlc->tx_queue to avoid circular dependency

On Tue, Aug 20, 2013 at 11:21:27AM +0200, Gustavo Padovan wrote:
> Hi Gianluca,
>
> 2013-07-29 Gianluca Anzolin <[email protected]>:
>
> > In rfcomm_tty_cleanup we purge the dlc->tx_queue which may contain
> > socket buffers referencing the tty_port and thus preventing the tty_port
> > destruction.
> >
> > Signed-off-by: Gianluca Anzolin <[email protected]>
> > ---
> > net/bluetooth/rfcomm/tty.c | 6 ++++++
> > 1 file changed, 6 insertions(+)
>
> all patches have been applied to bluetooth-next. Thanks.
>
> Gustavo

Hello,

Thank you!

Gianluca

2013-08-20 09:21:27

by Gustavo Padovan

[permalink] [raw]
Subject: Re: [PATCH v5 6/6] rfcomm: Purge the dlc->tx_queue to avoid circular dependency

Hi Gianluca,

2013-07-29 Gianluca Anzolin <[email protected]>:

> In rfcomm_tty_cleanup we purge the dlc->tx_queue which may contain
> socket buffers referencing the tty_port and thus preventing the tty_port
> destruction.
>
> Signed-off-by: Gianluca Anzolin <[email protected]>
> ---
> net/bluetooth/rfcomm/tty.c | 6 ++++++
> 1 file changed, 6 insertions(+)

all patches have been applied to bluetooth-next. Thanks.

Gustavo

2013-08-19 20:20:26

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH v5 0/6] rfcomm: Implement rfcomm as a proper tty_port

On 07/31/2013 01:50 PM, Peter Hurley wrote:
> On 07/29/2013 11:08 AM, Gianluca Anzolin wrote:
>> This patchset addresses an issue with the rfcomm tty driver in the
>> current stable kernels that manifests itself as a sudden lockup of the
>> whole machine or as a OOPS if we are lucky enough (I wasn't).
>>
>> Triggering the problem is very easy:
>>
>> 1) establish a bluetooth connection with a bluetooth host
>> 2) open the tty it provides with some program
>> 3) turn off the bluetooth host or take it out of range
>>
>> After a timeout the machine freezes.
>>
>> Another way to trigger these lockups is to simply release the rfcomm
>> tty.
>>
>> This happens beacuse the underlying tty_struct objects and tty_port
>> objects are freed while being used: the code doesn't take proper
>> references to them.
>>
>> The following patches address the problem by implementing a proper
>> tty_port driver for rfcomm.
>>
>> There are still some issues left: one relevant to flow control (which is
>> also missing in the current code) and another relevant to a corner case
>> in rfcomm_dev_state_change() that I intend to fix with a future patch.
>> They are commented with a FIXME.
>>
>> Changes from v4:
>> [PATCH 3/6]: left the debug message in rfcomm_tty_open()
>> [PATCH 5/6]: always use !test_and_set_bit() to release the tty_port
>
> I reviewed these changes and retested. All ok.

Gustavo,

This series fixes a crashing regression from 3.10+ forward.
Why is this not in linux-next yet?

Regards,
Peter Hurley

>> Gianluca Anzolin (6):
>> rfcomm: Take proper tty_struct references
>> rfcomm: Remove the device from the list in the destructor
>> rfcomm: Move the tty initialization and cleanup out of open/close
>> rfcomm: Implement .activate, .shutdown and .carrier_raised methods
>> rfcomm: Fix the reference counting of tty_port
>> rfcomm: Purge the dlc->tx_queue to avoid circular dependency
>>
>> net/bluetooth/rfcomm/tty.c | 271 +++++++++++++++++++++------------------------
>> 1 file changed, 126 insertions(+), 145 deletions(-)
>>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html