Bluetooth: Return RFCOMM session ptrs to avoid freed session

Unfortunately, the design retains local copies of the s RFCOMM
session pointer in various code blocks and this invites the erroneous
access to a freed RFCOMM session structure.

Therefore, return the RFCOMM session pointer back up the call stack
to avoid accessing a freed RFCOMM session structure. When the RFCOMM
session is deleted, NULL is passed up the call stack.

If active DLCs exist when the rfcomm session is terminating,
avoid a memory leak of rfcomm_dlc structures by ensuring that
rfcomm_session_close() is used instead of rfcomm_session_del().

Signed-off-by: Dean Jenkins <Dean_Jenkins@mentor.com>
Acked-by: Marcel Holtmann <marcel@holtmann.org>
Signed-off-by: Gustavo Padovan <gustavo.padovan@collabora.co.uk>
This commit is contained in:
Dean Jenkins 2013-02-28 14:21:55 +00:00 committed by Gustavo Padovan
parent c06f7d532a
commit 8ff52f7d04
2 changed files with 58 additions and 51 deletions

View file

@ -278,7 +278,8 @@ void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src,
static inline void rfcomm_session_hold(struct rfcomm_session *s) static inline void rfcomm_session_hold(struct rfcomm_session *s)
{ {
atomic_inc(&s->refcnt); if (s)
atomic_inc(&s->refcnt);
} }
/* ---- RFCOMM sockets ---- */ /* ---- RFCOMM sockets ---- */

View file

@ -69,7 +69,7 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
u8 sec_level, u8 sec_level,
int *err); int *err);
static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst); static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst);
static void rfcomm_session_del(struct rfcomm_session *s); static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s);
/* ---- RFCOMM frame parsing macros ---- */ /* ---- RFCOMM frame parsing macros ---- */
#define __get_dlci(b) ((b & 0xfc) >> 2) #define __get_dlci(b) ((b & 0xfc) >> 2)
@ -108,10 +108,12 @@ static void rfcomm_schedule(void)
wake_up_process(rfcomm_thread); wake_up_process(rfcomm_thread);
} }
static void rfcomm_session_put(struct rfcomm_session *s) static struct rfcomm_session *rfcomm_session_put(struct rfcomm_session *s)
{ {
if (atomic_dec_and_test(&s->refcnt)) if (s && atomic_dec_and_test(&s->refcnt))
rfcomm_session_del(s); s = rfcomm_session_del(s);
return s;
} }
/* ---- RFCOMM FCS computation ---- */ /* ---- RFCOMM FCS computation ---- */
@ -631,7 +633,7 @@ static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state)
return s; return s;
} }
static void rfcomm_session_del(struct rfcomm_session *s) static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s)
{ {
int state = s->state; int state = s->state;
@ -648,6 +650,8 @@ static void rfcomm_session_del(struct rfcomm_session *s)
if (state != BT_LISTEN) if (state != BT_LISTEN)
module_put(THIS_MODULE); module_put(THIS_MODULE);
return NULL;
} }
static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst) static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst)
@ -666,7 +670,8 @@ static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst)
return NULL; return NULL;
} }
static void rfcomm_session_close(struct rfcomm_session *s, int err) static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s,
int err)
{ {
struct rfcomm_dlc *d; struct rfcomm_dlc *d;
struct list_head *p, *n; struct list_head *p, *n;
@ -685,7 +690,7 @@ static void rfcomm_session_close(struct rfcomm_session *s, int err)
} }
rfcomm_session_clear_timer(s); rfcomm_session_clear_timer(s);
rfcomm_session_put(s); return rfcomm_session_put(s);
} }
static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
@ -737,8 +742,7 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
if (*err == 0 || *err == -EINPROGRESS) if (*err == 0 || *err == -EINPROGRESS)
return s; return s;
rfcomm_session_del(s); return rfcomm_session_del(s);
return NULL;
failed: failed:
sock_release(sock); sock_release(sock);
@ -1127,7 +1131,7 @@ static void rfcomm_make_uih(struct sk_buff *skb, u8 addr)
} }
/* ---- RFCOMM frame reception ---- */ /* ---- RFCOMM frame reception ---- */
static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) static struct rfcomm_session *rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci)
{ {
BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); BT_DBG("session %p state %ld dlci %d", s, s->state, dlci);
@ -1136,7 +1140,7 @@ static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci)
struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci); struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci);
if (!d) { if (!d) {
rfcomm_send_dm(s, dlci); rfcomm_send_dm(s, dlci);
return 0; return s;
} }
switch (d->state) { switch (d->state) {
@ -1172,25 +1176,14 @@ static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci)
break; break;
case BT_DISCONN: case BT_DISCONN:
/* rfcomm_session_put is called later so don't do s = rfcomm_session_close(s, ECONNRESET);
* anything here otherwise we will mess up the session
* reference counter:
*
* (a) when we are the initiator dlc_unlink will drive
* the reference counter to 0 (there is no initial put
* after session_add)
*
* (b) when we are not the initiator rfcomm_rx_process
* will explicitly call put to balance the initial hold
* done after session add.
*/
break; break;
} }
} }
return 0; return s;
} }
static int rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci) static struct rfcomm_session *rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci)
{ {
int err = 0; int err = 0;
@ -1215,12 +1208,13 @@ static int rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci)
err = ECONNRESET; err = ECONNRESET;
s->state = BT_CLOSED; s->state = BT_CLOSED;
rfcomm_session_close(s, err); s = rfcomm_session_close(s, err);
} }
return 0; return s;
} }
static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci) static struct rfcomm_session *rfcomm_recv_disc(struct rfcomm_session *s,
u8 dlci)
{ {
int err = 0; int err = 0;
@ -1250,10 +1244,9 @@ static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci)
err = ECONNRESET; err = ECONNRESET;
s->state = BT_CLOSED; s->state = BT_CLOSED;
rfcomm_session_close(s, err); s = rfcomm_session_close(s, err);
} }
return s;
return 0;
} }
void rfcomm_dlc_accept(struct rfcomm_dlc *d) void rfcomm_dlc_accept(struct rfcomm_dlc *d)
@ -1674,11 +1667,18 @@ drop:
return 0; return 0;
} }
static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb) static struct rfcomm_session *rfcomm_recv_frame(struct rfcomm_session *s,
struct sk_buff *skb)
{ {
struct rfcomm_hdr *hdr = (void *) skb->data; struct rfcomm_hdr *hdr = (void *) skb->data;
u8 type, dlci, fcs; u8 type, dlci, fcs;
if (!s) {
/* no session, so free socket data */
kfree_skb(skb);
return s;
}
dlci = __get_dlci(hdr->addr); dlci = __get_dlci(hdr->addr);
type = __get_type(hdr->ctrl); type = __get_type(hdr->ctrl);
@ -1689,7 +1689,7 @@ static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb)
if (__check_fcs(skb->data, type, fcs)) { if (__check_fcs(skb->data, type, fcs)) {
BT_ERR("bad checksum in packet"); BT_ERR("bad checksum in packet");
kfree_skb(skb); kfree_skb(skb);
return -EILSEQ; return s;
} }
if (__test_ea(hdr->len)) if (__test_ea(hdr->len))
@ -1705,22 +1705,23 @@ static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb)
case RFCOMM_DISC: case RFCOMM_DISC:
if (__test_pf(hdr->ctrl)) if (__test_pf(hdr->ctrl))
rfcomm_recv_disc(s, dlci); s = rfcomm_recv_disc(s, dlci);
break; break;
case RFCOMM_UA: case RFCOMM_UA:
if (__test_pf(hdr->ctrl)) if (__test_pf(hdr->ctrl))
rfcomm_recv_ua(s, dlci); s = rfcomm_recv_ua(s, dlci);
break; break;
case RFCOMM_DM: case RFCOMM_DM:
rfcomm_recv_dm(s, dlci); s = rfcomm_recv_dm(s, dlci);
break; break;
case RFCOMM_UIH: case RFCOMM_UIH:
if (dlci) if (dlci) {
return rfcomm_recv_data(s, dlci, __test_pf(hdr->ctrl), skb); rfcomm_recv_data(s, dlci, __test_pf(hdr->ctrl), skb);
return s;
}
rfcomm_recv_mcc(s, skb); rfcomm_recv_mcc(s, skb);
break; break;
@ -1729,7 +1730,7 @@ static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb)
break; break;
} }
kfree_skb(skb); kfree_skb(skb);
return 0; return s;
} }
/* ---- Connection and data processing ---- */ /* ---- Connection and data processing ---- */
@ -1866,7 +1867,7 @@ static void rfcomm_process_dlcs(struct rfcomm_session *s)
} }
} }
static void rfcomm_process_rx(struct rfcomm_session *s) static struct rfcomm_session *rfcomm_process_rx(struct rfcomm_session *s)
{ {
struct socket *sock = s->sock; struct socket *sock = s->sock;
struct sock *sk = sock->sk; struct sock *sk = sock->sk;
@ -1878,17 +1879,20 @@ static void rfcomm_process_rx(struct rfcomm_session *s)
while ((skb = skb_dequeue(&sk->sk_receive_queue))) { while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
skb_orphan(skb); skb_orphan(skb);
if (!skb_linearize(skb)) if (!skb_linearize(skb))
rfcomm_recv_frame(s, skb); s = rfcomm_recv_frame(s, skb);
else else
kfree_skb(skb); kfree_skb(skb);
} }
if (sk->sk_state == BT_CLOSED) { if (s && (sk->sk_state == BT_CLOSED)) {
if (!s->initiator) if (!s->initiator)
rfcomm_session_put(s); s = rfcomm_session_put(s);
rfcomm_session_close(s, sk->sk_err); if (s)
s = rfcomm_session_close(s, sk->sk_err);
} }
return s;
} }
static void rfcomm_accept_connection(struct rfcomm_session *s) static void rfcomm_accept_connection(struct rfcomm_session *s)
@ -1925,7 +1929,7 @@ static void rfcomm_accept_connection(struct rfcomm_session *s)
sock_release(nsock); sock_release(nsock);
} }
static void rfcomm_check_connection(struct rfcomm_session *s) static struct rfcomm_session *rfcomm_check_connection(struct rfcomm_session *s)
{ {
struct sock *sk = s->sock->sk; struct sock *sk = s->sock->sk;
@ -1944,9 +1948,10 @@ static void rfcomm_check_connection(struct rfcomm_session *s)
case BT_CLOSED: case BT_CLOSED:
s->state = BT_CLOSED; s->state = BT_CLOSED;
rfcomm_session_close(s, sk->sk_err); s = rfcomm_session_close(s, sk->sk_err);
break; break;
} }
return s;
} }
static void rfcomm_process_sessions(void) static void rfcomm_process_sessions(void)
@ -1975,15 +1980,16 @@ static void rfcomm_process_sessions(void)
switch (s->state) { switch (s->state) {
case BT_BOUND: case BT_BOUND:
rfcomm_check_connection(s); s = rfcomm_check_connection(s);
break; break;
default: default:
rfcomm_process_rx(s); s = rfcomm_process_rx(s);
break; break;
} }
rfcomm_process_dlcs(s); if (s)
rfcomm_process_dlcs(s);
rfcomm_session_put(s); rfcomm_session_put(s);
} }