Commit cf3752e2 authored by James Chapman's avatar James Chapman Committed by David S. Miller

[PPPOL2TP]: Make locking calls softirq-safe

Fix locking issues in the pppol2tp driver which can cause a kernel
crash on SMP boxes. There were two problems:-

1. The driver was violating read_lock() and write_lock() scheduling
   rules because it wasn't using softirq-safe locks in softirq
   contexts. So we now consistently use the _bh variants of the lock
   functions.

2. The driver was calling sk_dst_get() in pppol2tp_xmit() which was
   taking sk_dst_lock in softirq context. We now call __sk_dst_get().
Signed-off-by: default avatarJames Chapman <jchapman@katalix.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 5a346a10
...@@ -302,14 +302,14 @@ pppol2tp_session_find(struct pppol2tp_tunnel *tunnel, u16 session_id) ...@@ -302,14 +302,14 @@ pppol2tp_session_find(struct pppol2tp_tunnel *tunnel, u16 session_id)
struct pppol2tp_session *session; struct pppol2tp_session *session;
struct hlist_node *walk; struct hlist_node *walk;
read_lock(&tunnel->hlist_lock); read_lock_bh(&tunnel->hlist_lock);
hlist_for_each_entry(session, walk, session_list, hlist) { hlist_for_each_entry(session, walk, session_list, hlist) {
if (session->tunnel_addr.s_session == session_id) { if (session->tunnel_addr.s_session == session_id) {
read_unlock(&tunnel->hlist_lock); read_unlock_bh(&tunnel->hlist_lock);
return session; return session;
} }
} }
read_unlock(&tunnel->hlist_lock); read_unlock_bh(&tunnel->hlist_lock);
return NULL; return NULL;
} }
...@@ -320,14 +320,14 @@ static struct pppol2tp_tunnel *pppol2tp_tunnel_find(u16 tunnel_id) ...@@ -320,14 +320,14 @@ static struct pppol2tp_tunnel *pppol2tp_tunnel_find(u16 tunnel_id)
{ {
struct pppol2tp_tunnel *tunnel = NULL; struct pppol2tp_tunnel *tunnel = NULL;
read_lock(&pppol2tp_tunnel_list_lock); read_lock_bh(&pppol2tp_tunnel_list_lock);
list_for_each_entry(tunnel, &pppol2tp_tunnel_list, list) { list_for_each_entry(tunnel, &pppol2tp_tunnel_list, list) {
if (tunnel->stats.tunnel_id == tunnel_id) { if (tunnel->stats.tunnel_id == tunnel_id) {
read_unlock(&pppol2tp_tunnel_list_lock); read_unlock_bh(&pppol2tp_tunnel_list_lock);
return tunnel; return tunnel;
} }
} }
read_unlock(&pppol2tp_tunnel_list_lock); read_unlock_bh(&pppol2tp_tunnel_list_lock);
return NULL; return NULL;
} }
...@@ -344,7 +344,7 @@ static void pppol2tp_recv_queue_skb(struct pppol2tp_session *session, struct sk_ ...@@ -344,7 +344,7 @@ static void pppol2tp_recv_queue_skb(struct pppol2tp_session *session, struct sk_
struct sk_buff *skbp; struct sk_buff *skbp;
u16 ns = PPPOL2TP_SKB_CB(skb)->ns; u16 ns = PPPOL2TP_SKB_CB(skb)->ns;
spin_lock(&session->reorder_q.lock); spin_lock_bh(&session->reorder_q.lock);
skb_queue_walk(&session->reorder_q, skbp) { skb_queue_walk(&session->reorder_q, skbp) {
if (PPPOL2TP_SKB_CB(skbp)->ns > ns) { if (PPPOL2TP_SKB_CB(skbp)->ns > ns) {
__skb_insert(skb, skbp->prev, skbp, &session->reorder_q); __skb_insert(skb, skbp->prev, skbp, &session->reorder_q);
...@@ -360,7 +360,7 @@ static void pppol2tp_recv_queue_skb(struct pppol2tp_session *session, struct sk_ ...@@ -360,7 +360,7 @@ static void pppol2tp_recv_queue_skb(struct pppol2tp_session *session, struct sk_
__skb_queue_tail(&session->reorder_q, skb); __skb_queue_tail(&session->reorder_q, skb);
out: out:
spin_unlock(&session->reorder_q.lock); spin_unlock_bh(&session->reorder_q.lock);
} }
/* Dequeue a single skb. /* Dequeue a single skb.
...@@ -442,7 +442,7 @@ static void pppol2tp_recv_dequeue(struct pppol2tp_session *session) ...@@ -442,7 +442,7 @@ static void pppol2tp_recv_dequeue(struct pppol2tp_session *session)
* expect to send up next, dequeue it and any other * expect to send up next, dequeue it and any other
* in-sequence packets behind it. * in-sequence packets behind it.
*/ */
spin_lock(&session->reorder_q.lock); spin_lock_bh(&session->reorder_q.lock);
skb_queue_walk_safe(&session->reorder_q, skb, tmp) { skb_queue_walk_safe(&session->reorder_q, skb, tmp) {
if (time_after(jiffies, PPPOL2TP_SKB_CB(skb)->expires)) { if (time_after(jiffies, PPPOL2TP_SKB_CB(skb)->expires)) {
session->stats.rx_seq_discards++; session->stats.rx_seq_discards++;
...@@ -470,13 +470,13 @@ static void pppol2tp_recv_dequeue(struct pppol2tp_session *session) ...@@ -470,13 +470,13 @@ static void pppol2tp_recv_dequeue(struct pppol2tp_session *session)
goto out; goto out;
} }
} }
spin_unlock(&session->reorder_q.lock); spin_unlock_bh(&session->reorder_q.lock);
pppol2tp_recv_dequeue_skb(session, skb); pppol2tp_recv_dequeue_skb(session, skb);
spin_lock(&session->reorder_q.lock); spin_lock_bh(&session->reorder_q.lock);
} }
out: out:
spin_unlock(&session->reorder_q.lock); spin_unlock_bh(&session->reorder_q.lock);
} }
/* Internal receive frame. Do the real work of receiving an L2TP data frame /* Internal receive frame. Do the real work of receiving an L2TP data frame
...@@ -1059,7 +1059,7 @@ static int pppol2tp_xmit(struct ppp_channel *chan, struct sk_buff *skb) ...@@ -1059,7 +1059,7 @@ static int pppol2tp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
/* Get routing info from the tunnel socket */ /* Get routing info from the tunnel socket */
dst_release(skb->dst); dst_release(skb->dst);
skb->dst = sk_dst_get(sk_tun); skb->dst = dst_clone(__sk_dst_get(sk_tun));
skb_orphan(skb); skb_orphan(skb);
skb->sk = sk_tun; skb->sk = sk_tun;
...@@ -1107,7 +1107,7 @@ static void pppol2tp_tunnel_closeall(struct pppol2tp_tunnel *tunnel) ...@@ -1107,7 +1107,7 @@ static void pppol2tp_tunnel_closeall(struct pppol2tp_tunnel *tunnel)
PRINTK(tunnel->debug, PPPOL2TP_MSG_CONTROL, KERN_INFO, PRINTK(tunnel->debug, PPPOL2TP_MSG_CONTROL, KERN_INFO,
"%s: closing all sessions...\n", tunnel->name); "%s: closing all sessions...\n", tunnel->name);
write_lock(&tunnel->hlist_lock); write_lock_bh(&tunnel->hlist_lock);
for (hash = 0; hash < PPPOL2TP_HASH_SIZE; hash++) { for (hash = 0; hash < PPPOL2TP_HASH_SIZE; hash++) {
again: again:
hlist_for_each_safe(walk, tmp, &tunnel->session_hlist[hash]) { hlist_for_each_safe(walk, tmp, &tunnel->session_hlist[hash]) {
...@@ -1129,7 +1129,7 @@ static void pppol2tp_tunnel_closeall(struct pppol2tp_tunnel *tunnel) ...@@ -1129,7 +1129,7 @@ static void pppol2tp_tunnel_closeall(struct pppol2tp_tunnel *tunnel)
* disappear as we're jumping between locks. * disappear as we're jumping between locks.
*/ */
sock_hold(sk); sock_hold(sk);
write_unlock(&tunnel->hlist_lock); write_unlock_bh(&tunnel->hlist_lock);
lock_sock(sk); lock_sock(sk);
if (sk->sk_state & (PPPOX_CONNECTED | PPPOX_BOUND)) { if (sk->sk_state & (PPPOX_CONNECTED | PPPOX_BOUND)) {
...@@ -1154,11 +1154,11 @@ static void pppol2tp_tunnel_closeall(struct pppol2tp_tunnel *tunnel) ...@@ -1154,11 +1154,11 @@ static void pppol2tp_tunnel_closeall(struct pppol2tp_tunnel *tunnel)
* list so we are guaranteed to make forward * list so we are guaranteed to make forward
* progress. * progress.
*/ */
write_lock(&tunnel->hlist_lock); write_lock_bh(&tunnel->hlist_lock);
goto again; goto again;
} }
} }
write_unlock(&tunnel->hlist_lock); write_unlock_bh(&tunnel->hlist_lock);
} }
/* Really kill the tunnel. /* Really kill the tunnel.
...@@ -1167,9 +1167,9 @@ static void pppol2tp_tunnel_closeall(struct pppol2tp_tunnel *tunnel) ...@@ -1167,9 +1167,9 @@ static void pppol2tp_tunnel_closeall(struct pppol2tp_tunnel *tunnel)
static void pppol2tp_tunnel_free(struct pppol2tp_tunnel *tunnel) static void pppol2tp_tunnel_free(struct pppol2tp_tunnel *tunnel)
{ {
/* Remove from socket list */ /* Remove from socket list */
write_lock(&pppol2tp_tunnel_list_lock); write_lock_bh(&pppol2tp_tunnel_list_lock);
list_del_init(&tunnel->list); list_del_init(&tunnel->list);
write_unlock(&pppol2tp_tunnel_list_lock); write_unlock_bh(&pppol2tp_tunnel_list_lock);
atomic_dec(&pppol2tp_tunnel_count); atomic_dec(&pppol2tp_tunnel_count);
kfree(tunnel); kfree(tunnel);
...@@ -1245,9 +1245,9 @@ static void pppol2tp_session_destruct(struct sock *sk) ...@@ -1245,9 +1245,9 @@ static void pppol2tp_session_destruct(struct sock *sk)
/* Delete the session socket from the /* Delete the session socket from the
* hash * hash
*/ */
write_lock(&tunnel->hlist_lock); write_lock_bh(&tunnel->hlist_lock);
hlist_del_init(&session->hlist); hlist_del_init(&session->hlist);
write_unlock(&tunnel->hlist_lock); write_unlock_bh(&tunnel->hlist_lock);
atomic_dec(&pppol2tp_session_count); atomic_dec(&pppol2tp_session_count);
} }
...@@ -1392,9 +1392,9 @@ static struct sock *pppol2tp_prepare_tunnel_socket(int fd, u16 tunnel_id, ...@@ -1392,9 +1392,9 @@ static struct sock *pppol2tp_prepare_tunnel_socket(int fd, u16 tunnel_id,
/* Add tunnel to our list */ /* Add tunnel to our list */
INIT_LIST_HEAD(&tunnel->list); INIT_LIST_HEAD(&tunnel->list);
write_lock(&pppol2tp_tunnel_list_lock); write_lock_bh(&pppol2tp_tunnel_list_lock);
list_add(&tunnel->list, &pppol2tp_tunnel_list); list_add(&tunnel->list, &pppol2tp_tunnel_list);
write_unlock(&pppol2tp_tunnel_list_lock); write_unlock_bh(&pppol2tp_tunnel_list_lock);
atomic_inc(&pppol2tp_tunnel_count); atomic_inc(&pppol2tp_tunnel_count);
/* Bump the reference count. The tunnel context is deleted /* Bump the reference count. The tunnel context is deleted
...@@ -1599,11 +1599,11 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr, ...@@ -1599,11 +1599,11 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
sk->sk_user_data = session; sk->sk_user_data = session;
/* Add session to the tunnel's hash list */ /* Add session to the tunnel's hash list */
write_lock(&tunnel->hlist_lock); write_lock_bh(&tunnel->hlist_lock);
hlist_add_head(&session->hlist, hlist_add_head(&session->hlist,
pppol2tp_session_id_hash(tunnel, pppol2tp_session_id_hash(tunnel,
session->tunnel_addr.s_session)); session->tunnel_addr.s_session));
write_unlock(&tunnel->hlist_lock); write_unlock_bh(&tunnel->hlist_lock);
atomic_inc(&pppol2tp_session_count); atomic_inc(&pppol2tp_session_count);
...@@ -2205,7 +2205,7 @@ static struct pppol2tp_session *next_session(struct pppol2tp_tunnel *tunnel, str ...@@ -2205,7 +2205,7 @@ static struct pppol2tp_session *next_session(struct pppol2tp_tunnel *tunnel, str
int next = 0; int next = 0;
int i; int i;
read_lock(&tunnel->hlist_lock); read_lock_bh(&tunnel->hlist_lock);
for (i = 0; i < PPPOL2TP_HASH_SIZE; i++) { for (i = 0; i < PPPOL2TP_HASH_SIZE; i++) {
hlist_for_each_entry(session, walk, &tunnel->session_hlist[i], hlist) { hlist_for_each_entry(session, walk, &tunnel->session_hlist[i], hlist) {
if (curr == NULL) { if (curr == NULL) {
...@@ -2223,7 +2223,7 @@ static struct pppol2tp_session *next_session(struct pppol2tp_tunnel *tunnel, str ...@@ -2223,7 +2223,7 @@ static struct pppol2tp_session *next_session(struct pppol2tp_tunnel *tunnel, str
} }
} }
out: out:
read_unlock(&tunnel->hlist_lock); read_unlock_bh(&tunnel->hlist_lock);
if (!found) if (!found)
session = NULL; session = NULL;
...@@ -2234,13 +2234,13 @@ static struct pppol2tp_tunnel *next_tunnel(struct pppol2tp_tunnel *curr) ...@@ -2234,13 +2234,13 @@ static struct pppol2tp_tunnel *next_tunnel(struct pppol2tp_tunnel *curr)
{ {
struct pppol2tp_tunnel *tunnel = NULL; struct pppol2tp_tunnel *tunnel = NULL;
read_lock(&pppol2tp_tunnel_list_lock); read_lock_bh(&pppol2tp_tunnel_list_lock);
if (list_is_last(&curr->list, &pppol2tp_tunnel_list)) { if (list_is_last(&curr->list, &pppol2tp_tunnel_list)) {
goto out; goto out;
} }
tunnel = list_entry(curr->list.next, struct pppol2tp_tunnel, list); tunnel = list_entry(curr->list.next, struct pppol2tp_tunnel, list);
out: out:
read_unlock(&pppol2tp_tunnel_list_lock); read_unlock_bh(&pppol2tp_tunnel_list_lock);
return tunnel; return tunnel;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment