Bluetooth: RFCOMM - Fix info leak via getsockname()
[pandora-kernel.git] / net / bluetooth / l2cap_sock.c
index 3bb1611..b94abd3 100644 (file)
@@ -27,7 +27,6 @@
 
 /* Bluetooth L2CAP sockets. */
 
-#include <linux/security.h>
 #include <linux/export.h>
 
 #include <net/bluetooth/bluetooth.h>
@@ -89,8 +88,8 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int alen)
        if (err < 0)
                goto done;
 
-       if (__le16_to_cpu(la.l2_psm) == 0x0001 ||
-                               __le16_to_cpu(la.l2_psm) == 0x0003)
+       if (__le16_to_cpu(la.l2_psm) == L2CAP_PSM_SDP ||
+           __le16_to_cpu(la.l2_psm) == L2CAP_PSM_RFCOMM)
                chan->sec_level = BT_SECURITY_SDP;
 
        bacpy(&bt_sk(sk)->src, &la.l2_bdaddr);
@@ -446,6 +445,22 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch
        return err;
 }
 
+static bool l2cap_valid_mtu(struct l2cap_chan *chan, u16 mtu)
+{
+       switch (chan->scid) {
+       case L2CAP_CID_LE_DATA:
+               if (mtu < L2CAP_LE_MIN_MTU)
+                       return false;
+               break;
+
+       default:
+               if (mtu < L2CAP_DEFAULT_MIN_MTU)
+                       return false;
+       }
+
+       return true;
+}
+
 static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, char __user *optval, unsigned int optlen)
 {
        struct sock *sk = sock->sk;
@@ -484,6 +499,11 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, char __us
                        break;
                }
 
+               if (!l2cap_valid_mtu(chan, opts.imtu)) {
+                       err = -EINVAL;
+                       break;
+               }
+
                chan->mode = opts.mode;
                switch (chan->mode) {
                case L2CAP_MODE_BASIC:
@@ -873,9 +893,34 @@ static int l2cap_sock_release(struct socket *sock)
        return err;
 }
 
-static struct l2cap_chan *l2cap_sock_new_connection_cb(void *data)
+static void l2cap_sock_cleanup_listen(struct sock *parent)
 {
-       struct sock *sk, *parent = data;
+       struct sock *sk;
+
+       BT_DBG("parent %p", parent);
+
+       /* Close not yet accepted channels */
+       while ((sk = bt_accept_dequeue(parent, NULL))) {
+               struct l2cap_chan *chan = l2cap_pi(sk)->chan;
+
+               l2cap_chan_lock(chan);
+               __clear_chan_timer(chan);
+               l2cap_chan_close(chan, ECONNRESET);
+               l2cap_chan_unlock(chan);
+
+               l2cap_sock_kill(sk);
+       }
+}
+
+static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
+{
+       struct sock *sk, *parent = chan->data;
+
+       /* Check for backlog size */
+       if (sk_acceptq_is_full(parent)) {
+               BT_DBG("backlog full %d", parent->sk_ack_backlog);
+               return NULL;
+       }
 
        sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP,
                                                                GFP_ATOMIC);
@@ -889,10 +934,10 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(void *data)
        return l2cap_pi(sk)->chan;
 }
 
-static int l2cap_sock_recv_cb(void *data, struct sk_buff *skb)
+static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
 {
        int err;
-       struct sock *sk = data;
+       struct sock *sk = chan->data;
        struct l2cap_pinfo *pi = l2cap_pi(sk);
 
        lock_sock(sk);
@@ -925,16 +970,57 @@ done:
        return err;
 }
 
-static void l2cap_sock_close_cb(void *data)
+static void l2cap_sock_close_cb(struct l2cap_chan *chan)
 {
-       struct sock *sk = data;
+       struct sock *sk = chan->data;
 
        l2cap_sock_kill(sk);
 }
 
-static void l2cap_sock_state_change_cb(void *data, int state)
+static void l2cap_sock_teardown_cb(struct l2cap_chan *chan, int err)
+{
+       struct sock *sk = chan->data;
+       struct sock *parent;
+
+       lock_sock(sk);
+
+       parent = bt_sk(sk)->parent;
+
+       sock_set_flag(sk, SOCK_ZAPPED);
+
+       switch (chan->state) {
+       case BT_OPEN:
+       case BT_BOUND:
+       case BT_CLOSED:
+               break;
+       case BT_LISTEN:
+               l2cap_sock_cleanup_listen(sk);
+               sk->sk_state = BT_CLOSED;
+               chan->state = BT_CLOSED;
+
+               break;
+       default:
+               sk->sk_state = BT_CLOSED;
+               chan->state = BT_CLOSED;
+
+               sk->sk_err = err;
+
+               if (parent) {
+                       bt_accept_unlink(sk);
+                       parent->sk_data_ready(parent, 0);
+               } else {
+                       sk->sk_state_change(sk);
+               }
+
+               break;
+       }
+
+       release_sock(sk);
+}
+
+static void l2cap_sock_state_change_cb(struct l2cap_chan *chan, int state)
 {
-       struct sock *sk = data;
+       struct sock *sk = chan->data;
 
        sk->sk_state = state;
 }
@@ -955,12 +1041,34 @@ static struct sk_buff *l2cap_sock_alloc_skb_cb(struct l2cap_chan *chan,
        return skb;
 }
 
+static void l2cap_sock_ready_cb(struct l2cap_chan *chan)
+{
+       struct sock *sk = chan->data;
+       struct sock *parent;
+
+       lock_sock(sk);
+
+       parent = bt_sk(sk)->parent;
+
+       BT_DBG("sk %p, parent %p", sk, parent);
+
+       sk->sk_state = BT_CONNECTED;
+       sk->sk_state_change(sk);
+
+       if (parent)
+               parent->sk_data_ready(parent, 0);
+
+       release_sock(sk);
+}
+
 static struct l2cap_ops l2cap_chan_ops = {
        .name           = "L2CAP Socket Interface",
        .new_connection = l2cap_sock_new_connection_cb,
        .recv           = l2cap_sock_recv_cb,
        .close          = l2cap_sock_close_cb,
+       .teardown       = l2cap_sock_teardown_cb,
        .state_change   = l2cap_sock_state_change_cb,
+       .ready          = l2cap_sock_ready_cb,
        .alloc_skb      = l2cap_sock_alloc_skb_cb,
 };
 
@@ -1066,7 +1174,7 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, int p
 
        chan = l2cap_chan_create();
        if (!chan) {
-               l2cap_sock_kill(sk);
+               sk_free(sk);
                return NULL;
        }