• Herton R. Krzesinski's avatar
    net/rds: fix possible double free on sock tear down · 593cbb3e
    Herton R. Krzesinski authored
    I got a report of a double free happening at RDS slab cache. One
    suspicion was that may be somewhere we were doing a sock_hold/sock_put
    on an already freed sock. Thus after providing a kernel with the
    following change:
    
     static inline void sock_hold(struct sock *sk)
     {
    -       atomic_inc(&sk->sk_refcnt);
    +       if (!atomic_inc_not_zero(&sk->sk_refcnt))
    +               WARN(1, "Trying to hold sock already gone: %p (family: %hd)\n",
    +                       sk, sk->sk_family);
     }
    
    The warning successfuly triggered:
    
    Trying to hold sock already gone: ffff81f6dda61280 (family: 21)
    WARNING: at include/net/sock.h:350 sock_hold()
    Call Trace:
    <IRQ>  [<ffffffff8adac135>] :rds:rds_send_remove_from_sock+0xf0/0x21b
    [<ffffffff8adad35c>] :rds:rds_send_drop_acked+0xbf/0xcf
    [<ffffffff8addf546>] :rds_rdma:rds_ib_recv_tasklet_fn+0x256/0x2dc
    [<ffffffff8009899a>] tasklet_action+0x8f/0x12b
    [<ffffffff800125a2>] __do_softirq+0x89/0x133
    [<ffffffff8005f30c>] call_softirq+0x1c/0x28
    [<ffffffff8006e644>] do_softirq+0x2c/0x7d
    [<ffffffff8006e4d4>] do_IRQ+0xee/0xf7
    [<ffffffff8005e625>] ret_from_intr+0x0/0xa
    <EOI>
    
    Looking at the call chain above, the only way I think this would be
    possible is if somewhere we already released the same socket->sock which
    is assigned to the rds_message at rds_send_remove_from_sock. Which seems
    only possible to happen after the tear down done on rds_release.
    
    rds_release properly calls rds_send_drop_to to drop the socket from any
    rds_message, and some proper synchronization is in place to avoid race
    with rds_send_drop_acked/rds_send_remove_from_sock. However, I still see
    a very narrow window where it may be possible we touch a sock already
    released: when rds_release races with rds_send_drop_acked, we check
    RDS_MSG_ON_CONN to avoid cleanup on the same rds_message, but in this
    specific case we don't clear rm->m_rs. In this case, it seems we could
    then go on at rds_send_drop_to and after it returns, the sock is freed
    by last sock_put on rds_release, with concurrently we being at
    rds_send_remove_from_sock; then at some point in the loop at
    rds_send_remove_from_sock we process an rds_message which didn't have
    rm->m_rs unset for a freed sock, and a possible sock_hold on an sock
    already gone at rds_release happens.
    
    This hopefully address the described condition above and avoids a double
    free on "second last" sock_put. In addition, I removed the comment about
    socket destruction on top of rds_send_drop_acked: we call rds_send_drop_to
    in rds_release and we should have things properly serialized there, thus
    I can't see the comment being accurate there.
    Signed-off-by: default avatarHerton R. Krzesinski <herton@redhat.com>
    Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
    593cbb3e
send.c 30 KB