summaryrefslogtreecommitdiffstats
path: root/drivers/vhost
diff options
context:
space:
mode:
authorJason Wang <jasowang@redhat.com>2019-05-17 00:29:50 -0400
committerMichael S. Tsirkin <mst@redhat.com>2019-05-27 11:08:22 -0400
commite2412c07f8f3040593dfb88207865a3cd58680c0 (patch)
tree66bff001940b6cf438f615ae7bccb1a979f6b0bf /drivers/vhost
parente82b9b0727ff6d665fff2d326162b460dded554d (diff)
vhost_net: fix possible infinite loop
When the rx buffer is too small for a packet, we will discard the vq descriptor and retry it for the next packet: while ((sock_len = vhost_net_rx_peek_head_len(net, sock->sk, &busyloop_intr))) { ... /* On overrun, truncate and discard */ if (unlikely(headcount > UIO_MAXIOV)) { iov_iter_init(&msg.msg_iter, READ, vq->iov, 1, 1); err = sock->ops->recvmsg(sock, &msg, 1, MSG_DONTWAIT | MSG_TRUNC); pr_debug("Discarded rx packet: len %zd\n", sock_len); continue; } ... } This makes it possible to trigger a infinite while..continue loop through the co-opreation of two VMs like: 1) Malicious VM1 allocate 1 byte rx buffer and try to slow down the vhost process as much as possible e.g using indirect descriptors or other. 2) Malicious VM2 generate packets to VM1 as fast as possible Fixing this by checking against weight at the end of RX and TX loop. This also eliminate other similar cases when: - userspace is consuming the packets in the meanwhile - theoretical TOCTOU attack if guest moving avail index back and forth to hit the continue after vhost find guest just add new buffers This addresses CVE-2019-3900. Fixes: d8316f3991d20 ("vhost: fix total length when packets are too short") Fixes: 3a4d5c94e9593 ("vhost_net: a kernel-level virtio server") Signed-off-by: Jason Wang <jasowang@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Diffstat (limited to 'drivers/vhost')
-rw-r--r--drivers/vhost/net.c29
1 files changed, 13 insertions, 16 deletions
diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
index 061a06dc12a3..2d9df786a9d3 100644
--- a/drivers/vhost/net.c
+++ b/drivers/vhost/net.c
@@ -773,7 +773,7 @@ static void handle_tx_copy(struct vhost_net *net, struct socket *sock)
773 int sent_pkts = 0; 773 int sent_pkts = 0;
774 bool sock_can_batch = (sock->sk->sk_sndbuf == INT_MAX); 774 bool sock_can_batch = (sock->sk->sk_sndbuf == INT_MAX);
775 775
776 for (;;) { 776 do {
777 bool busyloop_intr = false; 777 bool busyloop_intr = false;
778 778
779 if (nvq->done_idx == VHOST_NET_BATCH) 779 if (nvq->done_idx == VHOST_NET_BATCH)
@@ -839,9 +839,7 @@ done:
839 vq->heads[nvq->done_idx].id = cpu_to_vhost32(vq, head); 839 vq->heads[nvq->done_idx].id = cpu_to_vhost32(vq, head);
840 vq->heads[nvq->done_idx].len = 0; 840 vq->heads[nvq->done_idx].len = 0;
841 ++nvq->done_idx; 841 ++nvq->done_idx;
842 if (vhost_exceeds_weight(vq, ++sent_pkts, total_len)) 842 } while (likely(!vhost_exceeds_weight(vq, ++sent_pkts, total_len)));
843 break;
844 }
845 843
846 vhost_tx_batch(net, nvq, sock, &msg); 844 vhost_tx_batch(net, nvq, sock, &msg);
847} 845}
@@ -866,7 +864,7 @@ static void handle_tx_zerocopy(struct vhost_net *net, struct socket *sock)
866 bool zcopy_used; 864 bool zcopy_used;
867 int sent_pkts = 0; 865 int sent_pkts = 0;
868 866
869 for (;;) { 867 do {
870 bool busyloop_intr; 868 bool busyloop_intr;
871 869
872 /* Release DMAs done buffers first */ 870 /* Release DMAs done buffers first */
@@ -943,10 +941,7 @@ static void handle_tx_zerocopy(struct vhost_net *net, struct socket *sock)
943 else 941 else
944 vhost_zerocopy_signal_used(net, vq); 942 vhost_zerocopy_signal_used(net, vq);
945 vhost_net_tx_packet(net); 943 vhost_net_tx_packet(net);
946 if (unlikely(vhost_exceeds_weight(vq, ++sent_pkts, 944 } while (likely(!vhost_exceeds_weight(vq, ++sent_pkts, total_len)));
947 total_len)))
948 break;
949 }
950} 945}
951 946
952/* Expects to be always run from workqueue - which acts as 947/* Expects to be always run from workqueue - which acts as
@@ -1144,8 +1139,11 @@ static void handle_rx(struct vhost_net *net)
1144 vq->log : NULL; 1139 vq->log : NULL;
1145 mergeable = vhost_has_feature(vq, VIRTIO_NET_F_MRG_RXBUF); 1140 mergeable = vhost_has_feature(vq, VIRTIO_NET_F_MRG_RXBUF);
1146 1141
1147 while ((sock_len = vhost_net_rx_peek_head_len(net, sock->sk, 1142 do {
1148 &busyloop_intr))) { 1143 sock_len = vhost_net_rx_peek_head_len(net, sock->sk,
1144 &busyloop_intr);
1145 if (!sock_len)
1146 break;
1149 sock_len += sock_hlen; 1147 sock_len += sock_hlen;
1150 vhost_len = sock_len + vhost_hlen; 1148 vhost_len = sock_len + vhost_hlen;
1151 headcount = get_rx_bufs(vq, vq->heads + nvq->done_idx, 1149 headcount = get_rx_bufs(vq, vq->heads + nvq->done_idx,
@@ -1230,12 +1228,11 @@ static void handle_rx(struct vhost_net *net)
1230 vhost_log_write(vq, vq_log, log, vhost_len, 1228 vhost_log_write(vq, vq_log, log, vhost_len,
1231 vq->iov, in); 1229 vq->iov, in);
1232 total_len += vhost_len; 1230 total_len += vhost_len;
1233 if (unlikely(vhost_exceeds_weight(vq, ++recv_pkts, total_len))) 1231 } while (likely(!vhost_exceeds_weight(vq, ++recv_pkts, total_len)));
1234 goto out; 1232
1235 }
1236 if (unlikely(busyloop_intr)) 1233 if (unlikely(busyloop_intr))
1237 vhost_poll_queue(&vq->poll); 1234 vhost_poll_queue(&vq->poll);
1238 else 1235 else if (!sock_len)
1239 vhost_net_enable_vq(net, vq); 1236 vhost_net_enable_vq(net, vq);
1240out: 1237out:
1241 vhost_net_signal_used(nvq); 1238 vhost_net_signal_used(nvq);
@@ -1328,7 +1325,7 @@ static int vhost_net_open(struct inode *inode, struct file *f)
1328 } 1325 }
1329 vhost_dev_init(dev, vqs, VHOST_NET_VQ_MAX, 1326 vhost_dev_init(dev, vqs, VHOST_NET_VQ_MAX,
1330 UIO_MAXIOV + VHOST_NET_BATCH, 1327 UIO_MAXIOV + VHOST_NET_BATCH,
1331 VHOST_NET_WEIGHT, VHOST_NET_PKT_WEIGHT); 1328 VHOST_NET_PKT_WEIGHT, VHOST_NET_WEIGHT);
1332 1329
1333 vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev); 1330 vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev);
1334 vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev); 1331 vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev);