2013-07-22 16:27:09

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v2 1/7] 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.

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

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index b6e44ad..6c3efb5 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -333,10 +333,11 @@ 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 +411,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 +431,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 +568,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 +578,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 +598,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 +613,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) |
@@ -674,7 +681,7 @@ static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp)

rfcomm_dlc_lock(dlc);
tty->driver_data = dev;
- dev->port.tty = tty;
+ tty_port_tty_set(&dev->port, tty);
rfcomm_dlc_unlock(dlc);
set_bit(RFCOMM_TTY_ATTACHED, &dev->flags);

@@ -742,7 +749,7 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)

rfcomm_dlc_lock(dev->dlc);
tty->driver_data = NULL;
- dev->port.tty = NULL;
+ tty_port_tty_set(&dev->port, NULL);
rfcomm_dlc_unlock(dev->dlc);

if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags)) {
--
1.8.3.3


2013-07-25 18:20:32

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH v2 2/7] rfcomm: Move the tty initialization and cleanup out of open/close

On 07/25/2013 02:07 PM, Gianluca Anzolin wrote:
> On Thu, Jul 25, 2013 at 08:59:40AM -0400, Peter Hurley wrote:
>> On 07/25/2013 01:37 AM, Gianluca Anzolin wrote:
>>> Hello,
>>>
>>> sorry I'm not very used to submit patches "the right way" and I missed the
>>> point that I have to keep the changes to the minimum.
>>
>> No need to apologize.
>> Every kernel contributor goes through this learning curve.
>>
>
> Hello,
>
> I'm trying to reach the minimal changeset to reach my target, and in the
> process I figured out that if I implement the .activate and .shutdown port
> methods before the .install and .cleanup methods I could produce less changes.
>
> However I'm stuck now because I cannot guarantee that the intermediate code
> between the patches works at all (however it compiles). What should I do?

The tty .install and .cleanup methods should be introduced first.
Then the tty_port methods.

The dev_list patch should be before both.

If you want, feel free to send me private email with questions and/or
code snippets, and I'll do my best to respond quickly.

Regards,
Peter Hurley

2013-07-25 18:07:49

by Gianluca Anzolin

[permalink] [raw]
Subject: Re: [PATCH v2 2/7] rfcomm: Move the tty initialization and cleanup out of open/close

On Thu, Jul 25, 2013 at 08:59:40AM -0400, Peter Hurley wrote:
> On 07/25/2013 01:37 AM, Gianluca Anzolin wrote:
> >Hello,
> >
> >sorry I'm not very used to submit patches "the right way" and I missed the
> >point that I have to keep the changes to the minimum.
>
> No need to apologize.
> Every kernel contributor goes through this learning curve.
>

Hello,

I'm trying to reach the minimal changeset to reach my target, and in the
process I figured out that if I implement the .activate and .shutdown port
methods before the .install and .cleanup methods I could produce less changes.

However I'm stuck now because I cannot guarantee that the intermediate code
between the patches works at all (however it compiles). What should I do?

Thanks,
Gianluca

2013-07-25 12:59:40

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH v2 2/7] rfcomm: Move the tty initialization and cleanup out of open/close

On 07/25/2013 01:37 AM, Gianluca Anzolin wrote:
> On Wed, Jul 24, 2013 at 06:28:26PM -0400, Peter Hurley wrote:
>> Gianluca,
>>
>> The code you intend to keep in rfcomm_tty_open() should not be moved to
>> rfcomm_tty_install() in this patch and then moved back to rfcomm_tty_open()
>> in 4/7. It should stay in rfcomm_tty_open().
>>
>> Likewise, the code being deleted in rfcomm_tty_install() in 4/7 should
>> remain in rfcomm_tty_open() here, and be deleted from rfcomm_tty_open() in
>> 4/7 instead.
>>
>> IOW, there's a much smaller changeset that will achieve the same end
>> product.
>
> Hello,
>
> sorry I'm not very used to submit patches "the right way" and I missed the
> point that I have to keep the changes to the minimum.

No need to apologize.
Every kernel contributor goes through this learning curve.

> I also resent the tty_port patch, I hope it's fine now,

Looks good. With Jiri's ack, that'll go into Greg's tree in the next day or
two.

> I assumed it
> was accepted but it wasn't and I also missed your last comment... I should lose
> this bad habit to read only the the visible part of the mail body. :D
>
> Ok, I'll rearrange the code and resend the rfcomm patches.

Ok.

A subtlety I overlooked regarding the .carrier_raised() method is
CLOCAL must _not_ be set in the driver's init_termios. This initialization is
done in rfcomm_init_ttys().

Regards,
Peter Hurley

2013-07-25 05:37:25

by Gianluca Anzolin

[permalink] [raw]
Subject: Re: [PATCH v2 2/7] rfcomm: Move the tty initialization and cleanup out of open/close

On Wed, Jul 24, 2013 at 06:28:26PM -0400, Peter Hurley wrote:
> Gianluca,
>
> The code you intend to keep in rfcomm_tty_open() should not be moved to
> rfcomm_tty_install() in this patch and then moved back to rfcomm_tty_open()
> in 4/7. It should stay in rfcomm_tty_open().
>
> Likewise, the code being deleted in rfcomm_tty_install() in 4/7 should
> remain in rfcomm_tty_open() here, and be deleted from rfcomm_tty_open() in
> 4/7 instead.
>
> IOW, there's a much smaller changeset that will achieve the same end
> product.

Hello,

sorry I'm not very used to submit patches "the right way" and I missed the
point that I have to keep the changes to the minimum.

I also resent the tty_port patch, I hope it's fine now, I assumed it
was accepted but it wasn't and I also missed your last comment... I should lose
this bad habit to read only the the visible part of the mail body. :D

Ok, I'll rearrange the code and resend the rfcomm patches.

Thank you,
Gianluca

2013-07-24 22:28:26

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH v2 2/7] rfcomm: Move the tty initialization and cleanup out of open/close

On 07/22/2013 12:27 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.
>
> Signed-off-by: Gianluca Anzolin <[email protected]>
> ---
> net/bluetooth/rfcomm/tty.c | 100 +++++++++++++++++++++++++++------------------
> 1 file changed, 61 insertions(+), 39 deletions(-)
>
> diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
> index 6c3efb5..c9913dd 100644
> --- a/net/bluetooth/rfcomm/tty.c
> +++ b/net/bluetooth/rfcomm/tty.c
> @@ -645,49 +645,60 @@ 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)

Gianluca,

The code you intend to keep in rfcomm_tty_open() should not be moved to
rfcomm_tty_install() in this patch and then moved back to rfcomm_tty_open()
in 4/7. It should stay in rfcomm_tty_open().

Likewise, the code being deleted in rfcomm_tty_install() in 4/7 should
remain in rfcomm_tty_open() here, and be deleted from rfcomm_tty_open() in
4/7 instead.

IOW, there's a much smaller changeset that will achieve the same end
product.


> +/* 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;
> + rfcomm_dlc_unlock(dev->dlc);
> +
> + tty_port_put(&dev->port);
> +}
> +
> +/* here 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;
> -
> - BT_DBG("tty %p id %d", tty, id);
> + int err;
>
> - /* 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;
> - tty_port_tty_set(&dev->port, 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);
> @@ -714,17 +725,40 @@ 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);
>
> rfcomm_tty_copy_pending(dev);
>
> rfcomm_dlc_unthrottle(dev->dlc);
>
> + 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);
> +
> + return 0;
> +}
> +
> static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
> {
> struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
> @@ -739,18 +773,6 @@ 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;
> - tty_port_tty_set(&dev->port, NULL);
> - rfcomm_dlc_unlock(dev->dlc);
>
> if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags)) {
> spin_lock(&rfcomm_dev_lock);
> @@ -761,8 +783,6 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
> }
> } 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)
> @@ -1135,6 +1155,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)
>

2013-07-24 22:04:10

by Peter Hurley

[permalink] [raw]
Subject: Re: [PATCH v2 5/7] rfcomm: Remove the device from the dev_list in the destructor

On 07/22/2013 12:27 PM, Gianluca Anzolin wrote:
> Remove the device from rfcomm_dev_list in the tty_port destructor.
>
> 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 1258678..17e5faa 100644
> --- a/net/bluetooth/rfcomm/tty.c
> +++ b/net/bluetooth/rfcomm/tty.c
> @@ -75,13 +75,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);
> @@ -89,10 +82,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 */
> @@ -295,7 +287,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;
> }
>
> @@ -328,10 +322,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);
> }
>
> @@ -753,13 +743,8 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
>
> tty_port_close(&dev->port, tty, filp);
>
> - if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags)) {
> - spin_lock(&rfcomm_dev_lock);
> - list_del_init(&dev->list);
> - spin_unlock(&rfcomm_dev_lock);
> -

Gianluca,

I think you may have missed my comment that this patch should
be earlier in the series. That way you wouldn't be adding code
in 4/7 that you're removing here.

Regards,
Peter Hurley


> + 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)
>


2013-07-22 16:27:15

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v2 7/7] 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 that could prevent the tty_port destruction.

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

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 428543e..50b38d7 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -668,6 +668,10 @@ 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.3

2013-07-22 16:27:14

by Gianluca Anzolin

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

The tty_port can be released in two cases: when we get a HUP in the fuctions
rfcomm_tty_hangup or 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 because it isn't used anymore.

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

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 17e5faa..428543e 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -308,23 +308,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)
{
@@ -440,8 +423,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;
}
@@ -609,6 +593,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) {
@@ -616,7 +603,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);
}
@@ -742,9 +731,6 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
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)
@@ -1047,9 +1033,7 @@ static void rfcomm_tty_hangup(struct tty_struct *tty)
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.3

2013-07-22 16:27:13

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v2 5/7] rfcomm: Remove the device from the dev_list in the destructor

Remove the device from rfcomm_dev_list in the tty_port destructor.

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 1258678..17e5faa 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -75,13 +75,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);
@@ -89,10 +82,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 */
@@ -295,7 +287,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;
}

@@ -328,10 +322,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);
}

@@ -753,13 +743,8 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)

tty_port_close(&dev->port, tty, filp);

- 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);
- }
}

static int rfcomm_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
--
1.8.3.3

2013-07-22 16:27:12

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v2 4/7] rfcomm: Implement activate/shutdown/carrier tty_port methods

Implement .activate .shutdown and .carrier_raised tty_port methods that manage
the dlc by moving the relevant code from the rfcomm_tty_install and
rfcomm_tty_cleanup functions.

The tty_open/close/hangup functions are changed to use the tty_port functions
that properly call the tty_port methods.

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

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index fb5dde1..1258678 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;

@@ -112,8 +111,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)
@@ -220,7 +250,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);

@@ -575,9 +604,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)) {
@@ -651,12 +683,6 @@ 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);
@@ -671,7 +697,6 @@ static void rfcomm_tty_cleanup(struct tty_struct *tty)
* 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;
int err;
@@ -693,96 +718,48 @@ static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty)

/* 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;
-
- /* 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);
-
- rfcomm_tty_copy_pending(dev);
+ if (err)
+ rfcomm_tty_cleanup(tty);

- rfcomm_dlc_unthrottle(dev->dlc);
-
- 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
+ * 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 0;
}

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;

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)) {
- spin_lock(&rfcomm_dev_lock);
- list_del_init(&dev->list);
- spin_unlock(&rfcomm_dev_lock);
+ if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags)) {
+ spin_lock(&rfcomm_dev_lock);
+ list_del_init(&dev->list);
+ spin_unlock(&rfcomm_dev_lock);

- 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)
@@ -1082,10 +1059,7 @@ static void rfcomm_tty_hangup(struct tty_struct *tty)

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

- 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)
--
1.8.3.3

2013-07-22 16:27:11

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v2 3/7] rfcomm: Move rfcomm_get_device before rfcomm_dev_state_change

Move rfcomm_get_device before rfcomm_dev_state_change. This function will be
called later to move the device under the remote host when the connection is
established.

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

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index c9913dd..fb5dde1 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -147,22 +147,6 @@ static struct rfcomm_dev *rfcomm_dev_get(int id)
return dev;
}

-static struct device *rfcomm_get_device(struct rfcomm_dev *dev)
-{
- struct hci_dev *hdev;
- struct hci_conn *conn;
-
- hdev = hci_get_route(&dev->dst, &dev->src);
- if (!hdev)
- return NULL;
-
- conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &dev->dst);
-
- hci_dev_put(hdev);
-
- return conn ? &conn->dev : NULL;
-}
-
static ssize_t show_address(struct device *tty_dev, struct device_attribute *attr, char *buf)
{
struct rfcomm_dev *dev = dev_get_drvdata(tty_dev);
@@ -565,6 +549,22 @@ static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb)
kfree_skb(skb);
}

+static struct device *rfcomm_get_device(struct rfcomm_dev *dev)
+{
+ struct hci_dev *hdev;
+ struct hci_conn *conn;
+
+ hdev = hci_get_route(&dev->dst, &dev->src);
+ if (!hdev)
+ return NULL;
+
+ conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &dev->dst);
+
+ hci_dev_put(hdev);
+
+ return conn ? &conn->dev : NULL;
+}
+
static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
{
struct rfcomm_dev *dev = dlc->owner;
--
1.8.3.3

2013-07-22 16:27:10

by Gianluca Anzolin

[permalink] [raw]
Subject: [PATCH v2 2/7] 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.

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

diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c
index 6c3efb5..c9913dd 100644
--- a/net/bluetooth/rfcomm/tty.c
+++ b/net/bluetooth/rfcomm/tty.c
@@ -645,49 +645,60 @@ 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;
+ rfcomm_dlc_unlock(dev->dlc);
+
+ tty_port_put(&dev->port);
+}
+
+/* here 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;
-
- BT_DBG("tty %p id %d", tty, id);
+ int err;

- /* 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;
- tty_port_tty_set(&dev->port, 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);
@@ -714,17 +725,40 @@ 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);

rfcomm_tty_copy_pending(dev);

rfcomm_dlc_unthrottle(dev->dlc);

+ 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);
+
+ return 0;
+}
+
static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
{
struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
@@ -739,18 +773,6 @@ 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;
- tty_port_tty_set(&dev->port, NULL);
- rfcomm_dlc_unlock(dev->dlc);

if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags)) {
spin_lock(&rfcomm_dev_lock);
@@ -761,8 +783,6 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
}
} 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)
@@ -1135,6 +1155,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.3