• Jason A. Donenfeld's avatar
    wireguard: noise: take lock when removing handshake entry from table · 9179ba31
    Jason A. Donenfeld authored
    Eric reported that syzkaller found a race of this variety:
    
    CPU 1                                       CPU 2
    -------------------------------------------|---------------------------------------
    wg_index_hashtable_replace(old, ...)       |
      if (hlist_unhashed(&old->index_hash))    |
                                               | wg_index_hashtable_remove(old)
                                               |   hlist_del_init_rcu(&old->index_hash)
    				           |     old->index_hash.pprev = NULL
      hlist_replace_rcu(&old->index_hash, ...) |
        *old->index_hash.pprev                 |
    
    Syzbot wasn't actually able to reproduce this more than once or create a
    reproducer, because the race window between checking "hlist_unhashed" and
    calling "hlist_replace_rcu" is just so small. Adding an mdelay(5) or
    similar there helps make this demonstrable using this simple script:
    
        #!/bin/bash
        set -ex
        trap 'kill $pid1; kill $pid2; ip link del wg0; ip link del wg1' EXIT
        ip link add wg0 type wireguard
        ip link add wg1 type wireguard
        wg set wg0 private-key <(wg genkey) listen-port 9999
        wg set wg1 private-key <(wg genkey) peer $(wg show wg0 public-key) endpoint 127.0.0.1:9999 persistent-keepalive 1
        wg set wg0 peer $(wg show wg1 public-key)
        ip link set wg0 up
        yes link set wg1 up | ip -force -batch - &
        pid1=$!
        yes link set wg1 down | ip -force -batch - &
        pid2=$!
        wait
    
    The fundumental underlying problem is that we permit calls to wg_index_
    hashtable_remove(handshake.entry) without requiring the caller to take
    the handshake mutex that is intended to protect members of handshake
    during mutations. This is consistently the case with calls to wg_index_
    hashtable_insert(handshake.entry) and wg_index_hashtable_replace(
    handshake.entry), but it's missing from a pertinent callsite of wg_
    index_hashtable_remove(handshake.entry). So, this patch makes sure that
    mutex is taken.
    
    The original code was a little bit funky though, in the form of:
    
        remove(handshake.entry)
        lock(), memzero(handshake.some_members), unlock()
        remove(handshake.entry)
    
    The original intention of that double removal pattern outside the lock
    appears to be some attempt to prevent insertions that might happen while
    locks are dropped during expensive crypto operations, but actually, all
    callers of wg_index_hashtable_insert(handshake.entry) take the write
    lock and then explicitly check handshake.state, as they should, which
    the aforementioned memzero clears, which means an insertion should
    already be impossible. And regardless, the original intention was
    necessarily racy, since it wasn't guaranteed that something else would
    run after the unlock() instead of after the remove(). So, from a
    soundness perspective, it seems positive to remove what looks like a
    hack at best.
    
    The crash from both syzbot and from the script above is as follows:
    
      general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] PREEMPT SMP KASAN
      KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
      CPU: 0 PID: 7395 Comm: kworker/0:3 Not tainted 5.9.0-rc4-syzkaller #0
      Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
      Workqueue: wg-kex-wg1 wg_packet_handshake_receive_worker
      RIP: 0010:hlist_replace_rcu include/linux/rculist.h:505 [inline]
      RIP: 0010:wg_index_hashtable_replace+0x176/0x330 drivers/net/wireguard/peerlookup.c:174
      Code: 00 fc ff df 48 89 f9 48 c1 e9 03 80 3c 01 00 0f 85 44 01 00 00 48 b9 00 00 00 00 00 fc ff df 48 8b 45 10 48 89 c6 48 c1 ee 03 <80> 3c 0e 00 0f 85 06 01 00 00 48 85 d2 4c 89 28 74 47 e8 a3 4f b5
      RSP: 0018:ffffc90006a97bf8 EFLAGS: 00010246
      RAX: 0000000000000000 RBX: ffff888050ffc4f8 RCX: dffffc0000000000
      RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff88808e04e010
      RBP: ffff88808e04e000 R08: 0000000000000001 R09: ffff8880543d0000
      R10: ffffed100a87a000 R11: 000000000000016e R12: ffff8880543d0000
      R13: ffff88808e04e008 R14: ffff888050ffc508 R15: ffff888050ffc500
      FS:  0000000000000000(0000) GS:ffff8880ae600000(0000) knlGS:0000000000000000
      CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
      CR2: 00000000f5505db0 CR3: 0000000097cf7000 CR4: 00000000001526f0
      DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
      DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
      Call Trace:
      wg_noise_handshake_begin_session+0x752/0xc9a drivers/net/wireguard/noise.c:820
      wg_receive_handshake_packet drivers/net/wireguard/receive.c:183 [inline]
      wg_packet_handshake_receive_worker+0x33b/0x730 drivers/net/wireguard/receive.c:220
      process_one_work+0x94c/0x1670 kernel/workqueue.c:2269
      worker_thread+0x64c/0x1120 kernel/workqueue.c:2415
      kthread+0x3b5/0x4a0 kernel/kthread.c:292
      ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:294
    Reported-by: default avatarsyzbot <syzkaller@googlegroups.com>
    Reported-by: default avatarEric Dumazet <edumazet@google.com>
    Link: https://lore.kernel.org/wireguard/20200908145911.4090480-1-edumazet@google.com/
    Fixes: e7096c13 ("net: WireGuard secure network tunnel")
    Signed-off-by: default avatarJason A. Donenfeld <Jason@zx2c4.com>
    Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
    9179ba31
noise.c 26.1 KB