]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
ib/sdp: fix null dereference of sk->sk_wq in sdp_rx_irq()
authorChuck Anderson <chuck.anderson@oracle.com>
Thu, 8 Jan 2015 00:06:49 +0000 (17:06 -0700)
committerMukesh Kacker <mukesh.kacker@oracle.com>
Tue, 6 Oct 2015 12:46:01 +0000 (05:46 -0700)
Orabug: 20070989

BUG: unable to handle kernel NULL pointer dereference at 0000000000000008
IP: [<ffffffffa02b639f>] 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:[<ffffffffa02b639f>]  [<ffffffffa02b639f>] 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 <sdp_rx_irq+56>:     mov    0xb8(%rsi),%rax
0xffffffffa02b638f <sdp_rx_irq+63>:     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 <sdp_rx_irq+68>:     mov    0xb8(%rsi),%rdx
0xffffffffa02b639b <sdp_rx_irq+75>:     lea    0x8(%rdx),%rax
0xffffffffa02b639f <sdp_rx_irq+79>:     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 <chuck.anderson@oracle.com>
Signed-off-by: John Sobecki <john.sobecki@oracle.com>
Acked-by: Chien Yen <chien.yen@oracle.com>
Signed-off-by: Guangyu Sun <guangyu.sun@oracle.com>
drivers/infiniband/ulp/sdp/sdp_rx.c

index 92cb1f8fdf9e4e1349d9785cec33595fd5b3b37d..89d0ea42d94f1f48243b3ad1d402055f6347c7fb 100644 (file)
@@ -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)