2013-07-26 17:18:52

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v4 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 because the underlying tty_struct objects and tty_port
objects are freed while being used: the code doesn't take references
properly.

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.

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 | 263 +++++++++++++++++++++------------------------
1 file changed, 122 insertions(+), 141 deletions(-)

--
1.8.3.4


2013-07-27 12:07:05

by Peter Hurley

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

On 07/27/2013 12:48 AM, Gianluca Anzolin wrote:
> On Fri, Jul 26, 2013 at 08:20:47PM -0400, Peter Hurley wrote:
>> On 07/26/2013 01:18 PM, Gianluca Anzolin wrote:
>>> @@ -614,7 +601,9 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
>>> return;
>>> }
>>>
>>> - rfcomm_dev_del(dev);
>>> + set_bit(RFCOMM_TTY_RELEASED, &dev->flags);
>>> + tty_port_put(&dev->port);
>>
>> Since this code can execute concurrently with rfcomm_release_dev(),
>> and the 'initial' port reference must only be dropped once, this should be
>>
>> if (!test_and_set_bit(RFCOMM_TTY_RELEASED, &dev->flags)
>> tty_port_put(&dev->port);
>>
>> Regards,
>> Peter Hurley
>
> I somehow convinced myself that it was safe but clearly it wasn't. Should I
> also change the same way the code in rfcomm_tty_hangup()?

Yes, I think that's wise. For example,

CPU 0 | CPU 1
|
rfcomm_dev_state_change |
tty_port_tty_get |
tty_hangup | rfcomm_release_dev
. | .
. | .
. | .
do_tty_hangup | .
__tty_hangup | .
rfcomm_tty_hangup | .
tty_port_hangup | .
| if (!test_and_set_bit(RFCOMM_TTY_RELEASED))
| tty_port_put
set_bit(RFCOMM_TTY_RELEASED) |
tty_port_put |

Also, in the commit message you should explain that
!test_and_set_bit(RFCOMM_TTY_RELEASED) ensures that the 'initial'
tty_port reference is only dropped once.

On a side note, I think this exposes a flaw in the tty layer hangup handling.
It seems possible that an async hangup could be scheduled while a
sync hangup is in-progress (and vice versa). While the two hangups would
not execute concurrently (because the tty lock serializes hangups),
they would execute sequentially and that would be bad.

I'm testing a mock-up now.

Regards,
Peter Hurley

2013-07-27 04:48:09

by Gianluca Anzolin

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

On Fri, Jul 26, 2013 at 08:20:47PM -0400, Peter Hurley wrote:
> On 07/26/2013 01:18 PM, Gianluca Anzolin wrote:
> >@@ -614,7 +601,9 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
> > return;
> > }
> >
> >- rfcomm_dev_del(dev);
> >+ set_bit(RFCOMM_TTY_RELEASED, &dev->flags);
> >+ tty_port_put(&dev->port);
>
> Since this code can execute concurrently with rfcomm_release_dev(),
> and the 'initial' port reference must only be dropped once, this should be
>
> if (!test_and_set_bit(RFCOMM_TTY_RELEASED, &dev->flags)
> tty_port_put(&dev->port);
>
> Regards,
> Peter Hurley

I somehow convinced myself that it was safe but clearly it wasn't. Should I
also change the same way the code in rfcomm_tty_hangup()?

Thanks,

Gianluca

2013-07-27 00:20:47

by Peter Hurley

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

On 07/26/2013 01:18 PM, Gianluca Anzolin wrote:
> 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 rfcomm_dev_del function is removed becase it isn't used anymore.

While reviewing your RFC patch for fixing rfcomm_dev_state_change(),
I realized I missed a problem in this patch.

> @@ -614,7 +601,9 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
> return;
> }
>
> - rfcomm_dev_del(dev);
> + set_bit(RFCOMM_TTY_RELEASED, &dev->flags);
> + tty_port_put(&dev->port);

Since this code can execute concurrently with rfcomm_release_dev(),
and the 'initial' port reference must only be dropped once, this should be

if (!test_and_set_bit(RFCOMM_TTY_RELEASED, &dev->flags)
tty_port_put(&dev->port);

Regards,
Peter Hurley

2013-07-26 20:04:32

by Peter Hurley

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

On 07/26/2013 01:18 PM, Gianluca Anzolin wrote:
> 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]>
> ---
> net/bluetooth/rfcomm/tty.c | 108 +++++++++++++++++++++++++++++----------------
> 1 file changed, 69 insertions(+), 39 deletions(-)
>
> diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
> index 9c0e142..bfaa444 100644
> --- a/net/bluetooth/rfcomm/tty.c
> +++ b/net/bluetooth/rfcomm/tty.c
> @@ -633,49 +633,64 @@ 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);

This debug message is probably more useful in rfcomm_tty_open().
But if you decide to leave it here, the port count will always be 0 so
that field should be removed.

Regards,
Peter Hurley

2013-07-26 19:56:25

by Peter Hurley

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

On 07/26/2013 01:18 PM, 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 because the underlying tty_struct objects and tty_port
> objects are freed while being used: the code doesn't take references
> properly.
>
> 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.
>
> Thank you,
> Gianluca

Gianluca,

Looks good, nice job :) [ Thanks for the cover letter. ]

I tested device disconnect with i/o both in-progress and while idle.
I also tested remote disconnect and local device release.
Teardown was clean and complete with all tests.

[ Sorry that it took me a little while. I first had to review the
tools/rfcomm.c code to figure out the parameters expected! since
there were no examples on the man page. Then I had to disable the
pnat plugin because the error path when it's not installed closes
the wrong file! ]

I have a minor comment on 4/6 regarding one of the debug printks.
Other than that;

Reviewed-and-Tested-by: Peter Hurley <[email protected]>

If Gustavo has you change stuff, please note changes in the cover
letter and in the patch itself below your sign-off line [example below],
so that I can follow along without re-reviewing the entire patch.

Regards,
Peter Hurley

--- >% ----
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]>
---

v5 - Fixed debug message in rfcomm_tty_activate()

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
.....

2013-07-26 17:18:58

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v4 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 9def67a..1c6bd90 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -667,6 +667,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-26 17:18:57

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v4 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 rfcomm_dev_del function is removed becase it isn't used anymore.

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

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 24a7c8c..9def67a 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,9 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
return;
}

- rfcomm_dev_del(dev);
+ set_bit(RFCOMM_TTY_RELEASED, &dev->flags);
+ tty_port_put(&dev->port);
+
tty_port_put(&dev->port);
rfcomm_dlc_lock(dlc);
}
@@ -741,16 +730,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,15 +1033,10 @@ 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);
+ set_bit(RFCOMM_TTY_RELEASED, &dev->flags);
tty_port_put(&dev->port);
}
}
--
1.8.3.4

2013-07-26 17:18:56

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v4 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 bfaa444..24a7c8c 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;
@@ -679,69 +703,27 @@ 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);

- 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-26 17:18:55

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v4 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]>
---
net/bluetooth/rfcomm/tty.c | 108 +++++++++++++++++++++++++++++----------------
1 file changed, 69 insertions(+), 39 deletions(-)

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 9c0e142..bfaa444 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -633,49 +633,64 @@ 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 +717,42 @@ 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);
+
+ 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-26 17:18:54

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v4 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-26 17:18:53

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v4 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