From: Chuck Anderson Date: Thu, 8 Jan 2015 00:06:49 +0000 (-0700) Subject: ib/sdp: fix null dereference of sk->sk_wq in sdp_rx_irq() X-Git-Tag: v4.1.12-92~264^2~3^2~1 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=6aa28cad6cb7c6b639cf4f62d4d8f3e7c5de8d88;p=users%2Fjedix%2Flinux-maple.git ib/sdp: fix null dereference of sk->sk_wq in sdp_rx_irq() Orabug: 20070989 BUG: unable to handle kernel NULL pointer dereference at 0000000000000008 IP: [] sdp_rx_irq+0x4f/0x160 [ib_sdp] PGD 1d7fd14067 PUD 190984b067 PMD 0 Oops: 0000 [#1] SMP ... Pid: 61889, comm: oracle Not tainted 2.6.39-400.128.20.el5uek #1 Oracle Corporation SUN FIRE X4170 M3 /ASSY,MOTHERBOARD,1U RIP: 0010:[] [] sdp_rx_irq+0x4f/0x160 [ib_sdp] ... Crash occurs in the call to sdp_sk_sleep(sk) through waitqueue_active(): drivers/infiniband/ulp/sdp/sdp_rx.c static void sdp_rx_irq(struct ib_cq *cq, void *cq_context) if (should_wake_up(sk)) { drivers/infiniband/ulp/sdp/sdp_rx.c static inline int should_wake_up(struct sock *sk) { return sdp_sk_sleep(sk) && waitqueue_active(sdp_sk_sleep(sk)) && (posts_handler(sdp_sk(sk)) || somebody_is_waiting(sk)); } drivers/infiniband/ulp/sdp/sdp.h: #define sdp_sk_sleep(sk) sk_sleep(sk) include/net/sock.h static inline wait_queue_head_t *sk_sleep(struct sock *sk) { BUILD_BUG_ON(offsetof(struct socket_wq, wait) != 0); return &rcu_dereference_raw(sk->sk_wq)->wait; } We know the first call to sdp_sk_sleep(sk) finds a non-null sk->sk_wq because we don't crash: 0xffffffffa02b6388 : mov 0xb8(%rsi),%rax 0xffffffffa02b638f : test %rax,%rax *** struct sock sk+0xb8 == sk->sk_wq (sk_wq is at offset 0xb8) *** we didn't crash at sdp_rx_irq+56 so sk->sk_wq was apparently valid 0xffffffffa02b6394 : mov 0xb8(%rsi),%rdx 0xffffffffa02b639b : lea 0x8(%rdx),%rax 0xffffffffa02b639f : cmp %rax,0x8(%rdx) *** RDX is NULL causing the null dereference of address 0x8 at sdp_rx_irq+79. Fix is to check if sk->sk_wq is NULL before dereferencing it to get the address of sk->sk_wq->wait. Also, do the RCU dereference of sk->sk_wq once, not twice as we may get a different answer (NULL) the second time. Signed-off-by: Chuck Anderson Signed-off-by: John Sobecki Acked-by: Chien Yen Signed-off-by: Guangyu Sun --- diff --git a/drivers/infiniband/ulp/sdp/sdp_rx.c b/drivers/infiniband/ulp/sdp/sdp_rx.c index 92cb1f8fdf9e4..89d0ea42d94f1 100644 --- a/drivers/infiniband/ulp/sdp/sdp_rx.c +++ b/drivers/infiniband/ulp/sdp/sdp_rx.c @@ -817,16 +817,30 @@ void sdp_do_posts(struct sdp_sock *ssk) } -static inline int should_wake_up(struct sock *sk) +/* + * Should be called with rcu_read_lock() held for the reference to wq. + * Returns: + * 1: there is probably a waiter + * 0: no waiter + */ +static inline int should_wake_up(struct sock *sk, struct socket_wq *wq) { - return sdp_sk_sleep(sk) && waitqueue_active(sdp_sk_sleep(sk)) && - (posts_handler(sdp_sk(sk)) || somebody_is_waiting(sk)); + int rc; + + if (unlikely(wq)) + rc = waitqueue_active(&wq->wait) && + (posts_handler(sdp_sk(sk)) || somebody_is_waiting(sk)); + else + rc = 0; + + return rc; } static void sdp_rx_irq(struct ib_cq *cq, void *cq_context) { struct sock *sk = cq_context; struct sdp_sock *ssk = sdp_sk(sk); + struct socket_wq *wq; if (unlikely(cq != ssk->rx_ring.cq)) { sdp_warn(sk, "cq = %p, ssk->cq = %p\n", cq, ssk->rx_ring.cq); @@ -837,8 +851,10 @@ static void sdp_rx_irq(struct ib_cq *cq, void *cq_context) sdp_prf(sk, NULL, "rx irq"); - if (should_wake_up(sk)) { - wake_up_interruptible(sdp_sk_sleep(sk)); + rcu_read_lock(); + wq = rcu_dereference(sk->sk_wq); + if (should_wake_up(sk, wq)) { + wake_up_interruptible(&wq->wait); SDPSTATS_COUNTER_INC(rx_int_wake_up); } else { if (queue_work_on(ssk->cpu, rx_comp_wq, &ssk->rx_comp_work)) @@ -846,6 +862,7 @@ static void sdp_rx_irq(struct ib_cq *cq, void *cq_context) else SDPSTATS_COUNTER_INC(rx_int_no_op); } + rcu_read_unlock(); } static void sdp_rx_ring_purge(struct sdp_sock *ssk)