Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

vhost/vsock: fix use-after-free in network stack callers

If the network stack calls .send_pkt()/.cancel_pkt() during .release(),
a struct vhost_vsock use-after-free is possible. This occurs because
.release() does not wait for other CPUs to stop using struct
vhost_vsock.

Switch to an RCU-enabled hashtable (indexed by guest CID) so that
.release() can wait for other CPUs by calling synchronize_rcu(). This
also eliminates vhost_vsock_lock acquisition in the data path so it
could have a positive effect on performance.

This is CVE-2018-14625 "kernel: use-after-free Read in vhost_transport_send_pkt".

Cc: stable@vger.kernel.org
Reported-and-tested-by: syzbot+bd391451452fb0b93039@syzkaller.appspotmail.com
Reported-by: syzbot+e3e074963495f92a89ed@syzkaller.appspotmail.com
Reported-by: syzbot+d5a0a170c5069658b141@syzkaller.appspotmail.com
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Jason Wang <jasowang@redhat.com>

authored by

Stefan Hajnoczi and committed by
Michael S. Tsirkin
834e772c 78b1a52e

+33 -24
+33 -24
drivers/vhost/vsock.c
··· 15 15 #include <net/sock.h> 16 16 #include <linux/virtio_vsock.h> 17 17 #include <linux/vhost.h> 18 + #include <linux/hashtable.h> 18 19 19 20 #include <net/af_vsock.h> 20 21 #include "vhost.h" ··· 28 27 29 28 /* Used to track all the vhost_vsock instances on the system. */ 30 29 static DEFINE_SPINLOCK(vhost_vsock_lock); 31 - static LIST_HEAD(vhost_vsock_list); 30 + static DEFINE_READ_MOSTLY_HASHTABLE(vhost_vsock_hash, 8); 32 31 33 32 struct vhost_vsock { 34 33 struct vhost_dev dev; 35 34 struct vhost_virtqueue vqs[2]; 36 35 37 - /* Link to global vhost_vsock_list, protected by vhost_vsock_lock */ 38 - struct list_head list; 36 + /* Link to global vhost_vsock_hash, writes use vhost_vsock_lock */ 37 + struct hlist_node hash; 39 38 40 39 struct vhost_work send_pkt_work; 41 40 spinlock_t send_pkt_list_lock; ··· 51 50 return VHOST_VSOCK_DEFAULT_HOST_CID; 52 51 } 53 52 54 - static struct vhost_vsock *__vhost_vsock_get(u32 guest_cid) 53 + /* Callers that dereference the return value must hold vhost_vsock_lock or the 54 + * RCU read lock. 55 + */ 56 + static struct vhost_vsock *vhost_vsock_get(u32 guest_cid) 55 57 { 56 58 struct vhost_vsock *vsock; 57 59 58 - list_for_each_entry(vsock, &vhost_vsock_list, list) { 60 + hash_for_each_possible_rcu(vhost_vsock_hash, vsock, hash, guest_cid) { 59 61 u32 other_cid = vsock->guest_cid; 60 62 61 63 /* Skip instances that have no CID yet */ ··· 71 67 } 72 68 73 69 return NULL; 74 - } 75 - 76 - static struct vhost_vsock *vhost_vsock_get(u32 guest_cid) 77 - { 78 - struct vhost_vsock *vsock; 79 - 80 - spin_lock_bh(&vhost_vsock_lock); 81 - vsock = __vhost_vsock_get(guest_cid); 82 - spin_unlock_bh(&vhost_vsock_lock); 83 - 84 - return vsock; 85 70 } 86 71 87 72 static void ··· 203 210 struct vhost_vsock *vsock; 204 211 int len = pkt->len; 205 212 213 + rcu_read_lock(); 214 + 206 215 /* Find the vhost_vsock according to guest context id */ 207 216 vsock = vhost_vsock_get(le64_to_cpu(pkt->hdr.dst_cid)); 208 217 if (!vsock) { 218 + rcu_read_unlock(); 209 219 virtio_transport_free_pkt(pkt); 210 220 return -ENODEV; 211 221 } ··· 221 225 spin_unlock_bh(&vsock->send_pkt_list_lock); 222 226 223 227 vhost_work_queue(&vsock->dev, &vsock->send_pkt_work); 228 + 229 + rcu_read_unlock(); 224 230 return len; 225 231 } 226 232 ··· 232 234 struct vhost_vsock *vsock; 233 235 struct virtio_vsock_pkt *pkt, *n; 234 236 int cnt = 0; 237 + int ret = -ENODEV; 235 238 LIST_HEAD(freeme); 239 + 240 + rcu_read_lock(); 236 241 237 242 /* Find the vhost_vsock according to guest context id */ 238 243 vsock = vhost_vsock_get(vsk->remote_addr.svm_cid); 239 244 if (!vsock) 240 - return -ENODEV; 245 + goto out; 241 246 242 247 spin_lock_bh(&vsock->send_pkt_list_lock); 243 248 list_for_each_entry_safe(pkt, n, &vsock->send_pkt_list, list) { ··· 266 265 vhost_poll_queue(&tx_vq->poll); 267 266 } 268 267 269 - return 0; 268 + ret = 0; 269 + out: 270 + rcu_read_unlock(); 271 + return ret; 270 272 } 271 273 272 274 static struct virtio_vsock_pkt * ··· 537 533 spin_lock_init(&vsock->send_pkt_list_lock); 538 534 INIT_LIST_HEAD(&vsock->send_pkt_list); 539 535 vhost_work_init(&vsock->send_pkt_work, vhost_transport_send_pkt_work); 540 - 541 - spin_lock_bh(&vhost_vsock_lock); 542 - list_add_tail(&vsock->list, &vhost_vsock_list); 543 - spin_unlock_bh(&vhost_vsock_lock); 544 536 return 0; 545 537 546 538 out: ··· 585 585 struct vhost_vsock *vsock = file->private_data; 586 586 587 587 spin_lock_bh(&vhost_vsock_lock); 588 - list_del(&vsock->list); 588 + if (vsock->guest_cid) 589 + hash_del_rcu(&vsock->hash); 589 590 spin_unlock_bh(&vhost_vsock_lock); 591 + 592 + /* Wait for other CPUs to finish using vsock */ 593 + synchronize_rcu(); 590 594 591 595 /* Iterating over all connections for all CIDs to find orphans is 592 596 * inefficient. Room for improvement here. */ ··· 632 628 633 629 /* Refuse if CID is already in use */ 634 630 spin_lock_bh(&vhost_vsock_lock); 635 - other = __vhost_vsock_get(guest_cid); 631 + other = vhost_vsock_get(guest_cid); 636 632 if (other && other != vsock) { 637 633 spin_unlock_bh(&vhost_vsock_lock); 638 634 return -EADDRINUSE; 639 635 } 636 + 637 + if (vsock->guest_cid) 638 + hash_del_rcu(&vsock->hash); 639 + 640 640 vsock->guest_cid = guest_cid; 641 + hash_add_rcu(vhost_vsock_hash, &vsock->hash, guest_cid); 641 642 spin_unlock_bh(&vhost_vsock_lock); 642 643 643 644 return 0;