vhost_net_set_features() is reachable through VHOST_SET_FEATURES and
VHOST_SET_FEATURES_ARRAY. A successful VHOST_NET_SET_BACKEND with a
socket backend invokes vhost_net_set_backend() and enables a virtqueue;
a later VHOST_SET_FEATURES can still change feature-dependent state.

When ACCESS_PLATFORM is first enabled, vhost_net_set_features() calls
vhost_init_device_iotlb(). vhost_get_vq_desc() calls translate_desc(),
which prefers dev->iotlb over dev->umem. Clearing ACCESS_PLATFORM
updates acked_features but does not clear dev->iotlb, so the old IOTLB
and its HVA entries remain in the descriptor path.

With IN_ORDER disabled, vhost_get_vq_desc() reads the next head from
the guest's avail ring but still advances vq->next_avail_head after a
successful consume. Enabling IN_ORDER later makes vhost use that
existing cursor without resetting it, so the next descriptor may not
be the one from the guest's avail ring.

Keep feature negotiation fixed after the first successful backend
activation, except for VHOST_F_LOG_ALL. Reject clearing
ACCESS_PLATFORM while a device IOTLB exists, and avoid rebuilding an
existing IOTLB for a log-only update. Use VHOST_RESET_OWNER before
starting a new feature negotiation.

Fixes: 6b1e6cc7855b ("vhost: new device IOTLB API")
Fixes: 45347e79b544 ("vhost_net: basic in_order support")
Signed-off-by: Jia Jia <[email protected]>
---
 drivers/vhost/net.c | 42 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 40 insertions(+), 2 deletions(-)

diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
index 6edac0c1ba9b..df357142461a 100644
--- a/drivers/vhost/net.c
+++ b/drivers/vhost/net.c
@@ -144,6 +144,8 @@ struct vhost_net {
        unsigned tx_zcopy_err;
        /* Flush in progress. Protected by tx vq lock. */
        bool tx_flush;
+       /* Feature negotiation is fixed after a backend has been started. */
+       bool features_locked;
        /* Private page frag cache */
        struct page_frag_cache pf_cache;
 };
@@ -318,6 +320,24 @@ static void vhost_net_vq_reset(struct vhost_net *n)
 
 }
 
+/* Caller must hold n->dev.mutex. */
+static bool vhost_net_features_changed(struct vhost_net *n, const u64 
*features)
+{
+       const u64 *acked = n->vqs[VHOST_NET_VQ_TX].vq.acked_features_array;
+       int i;
+
+       for (i = 0; i < VIRTIO_FEATURES_DWORDS; i++) {
+               u64 changed = features[i] ^ acked[i];
+
+               if (i == VIRTIO_DWORD(VHOST_F_LOG_ALL))
+                       changed &= ~VIRTIO_BIT(VHOST_F_LOG_ALL);
+               if (changed)
+                       return true;
+       }
+
+       return false;
+}
+
 static void vhost_net_tx_packet(struct vhost_net *net)
 {
        ++net->tx_packets;
@@ -1321,6 +1341,7 @@ static int vhost_net_open(struct inode *inode, struct 
file *f)
        n = kvmalloc(sizeof *n, GFP_KERNEL | __GFP_RETRY_MAYFAIL);
        if (!n)
                return -ENOMEM;
+       n->features_locked = false;
        vqs = kmalloc_array(VHOST_NET_VQ_MAX, sizeof(*vqs), GFP_KERNEL);
        if (!vqs) {
                kvfree(n);
@@ -1597,6 +1618,8 @@ static long vhost_net_set_backend(struct vhost_net *n, 
unsigned index, int fd)
                vhost_dev_flush(&n->dev);
                sockfd_put(oldsock);
        }
+       if (sock)
+               n->features_locked = true;
 
        mutex_unlock(&n->dev.mutex);
        return 0;
@@ -1637,6 +1660,7 @@ static long vhost_net_reset_owner(struct vhost_net *n)
        vhost_dev_stop(&n->dev);
        vhost_dev_reset_owner(&n->dev, umem);
        vhost_net_vq_reset(n);
+       n->features_locked = false;
 done:
        mutex_unlock(&n->dev.mutex);
        if (tx_sock)
@@ -1649,6 +1673,8 @@ static long vhost_net_reset_owner(struct vhost_net *n)
 static int vhost_net_set_features(struct vhost_net *n, const u64 *features)
 {
        size_t vhost_hlen, sock_hlen, hdr_len;
+       bool access_platform;
+       int r = -EFAULT;
        int i;
 
        hdr_len = virtio_features_test_bit(features, VIRTIO_NET_F_MRG_RXBUF) ||
@@ -1672,11 +1698,23 @@ static int vhost_net_set_features(struct vhost_net *n, 
const u64 *features)
                sock_hlen = hdr_len;
        }
        mutex_lock(&n->dev.mutex);
+       if (n->features_locked && vhost_net_features_changed(n, features)) {
+               r = -EBUSY;
+               goto out_unlock;
+       }
+
        if (virtio_features_test_bit(features, VHOST_F_LOG_ALL) &&
            !vhost_log_access_ok(&n->dev))
                goto out_unlock;
 
-       if (virtio_features_test_bit(features, VIRTIO_F_ACCESS_PLATFORM)) {
+       access_platform =
+               virtio_features_test_bit(features, VIRTIO_F_ACCESS_PLATFORM);
+       if (!access_platform && n->dev.iotlb) {
+               r = -EBUSY;
+               goto out_unlock;
+       }
+
+       if (access_platform && !n->dev.iotlb) {
                if (vhost_init_device_iotlb(&n->dev))
                        goto out_unlock;
        }
@@ -1694,7 +1732,7 @@ static int vhost_net_set_features(struct vhost_net *n, 
const u64 *features)
 
 out_unlock:
        mutex_unlock(&n->dev.mutex);
-       return -EFAULT;
+       return r;
 }
 
 static long vhost_net_set_owner(struct vhost_net *n)
-- 
2.34.1


Reply via email to