Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 388b3f14 authored by Jason Wang's avatar Jason Wang Committed by Greg Kroah-Hartman
Browse files

virtio-net: fix the race between refill work and close



[ Upstream commit 5a159128faff151b7fe5f4eb0f310b1e0a2d56bf ]

We try using cancel_delayed_work_sync() to prevent the work from
enabling NAPI. This is insufficient since we don't disable the source
of the refill work scheduling. This means an NAPI poll callback after
cancel_delayed_work_sync() can schedule the refill work then can
re-enable the NAPI that leads to use-after-free [1].

Since the work can enable NAPI, we can't simply disable NAPI before
calling cancel_delayed_work_sync(). So fix this by introducing a
dedicated boolean to control whether or not the work could be
scheduled from NAPI.

[1]
==================================================================
BUG: KASAN: use-after-free in refill_work+0x43/0xd4
Read of size 2 at addr ffff88810562c92e by task kworker/2:1/42

CPU: 2 PID: 42 Comm: kworker/2:1 Not tainted 5.19.0-rc1+ #480
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org 04/01/2014
Workqueue: events refill_work
Call Trace:
 <TASK>
 dump_stack_lvl+0x34/0x44
 print_report.cold+0xbb/0x6ac
 ? _printk+0xad/0xde
 ? refill_work+0x43/0xd4
 kasan_report+0xa8/0x130
 ? refill_work+0x43/0xd4
 refill_work+0x43/0xd4
 process_one_work+0x43d/0x780
 worker_thread+0x2a0/0x6f0
 ? process_one_work+0x780/0x780
 kthread+0x167/0x1a0
 ? kthread_exit+0x50/0x50
 ret_from_fork+0x22/0x30
 </TASK>
...

Fixes: b2baed69 ("virtio_net: set/cancel work on ndo_open/ndo_stop")
Signed-off-by: default avatarJason Wang <jasowang@redhat.com>
Acked-by: default avatarMichael S. Tsirkin <mst@redhat.com>
Reviewed-by: default avatarXuan Zhuo <xuanzhuo@linux.alibaba.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent 52be29e8
Loading
Loading
Loading
Loading
+34 −3
Original line number Original line Diff line number Diff line
@@ -213,9 +213,15 @@ struct virtnet_info {
	/* Packet virtio header size */
	/* Packet virtio header size */
	u8 hdr_len;
	u8 hdr_len;


	/* Work struct for refilling if we run low on memory. */
	/* Work struct for delayed refilling if we run low on memory. */
	struct delayed_work refill;
	struct delayed_work refill;


	/* Is delayed refill enabled? */
	bool refill_enabled;

	/* The lock to synchronize the access to refill_enabled */
	spinlock_t refill_lock;

	/* Work struct for config space updates */
	/* Work struct for config space updates */
	struct work_struct config_work;
	struct work_struct config_work;


@@ -319,6 +325,20 @@ static struct page *get_a_page(struct receive_queue *rq, gfp_t gfp_mask)
	return p;
	return p;
}
}


static void enable_delayed_refill(struct virtnet_info *vi)
{
	spin_lock_bh(&vi->refill_lock);
	vi->refill_enabled = true;
	spin_unlock_bh(&vi->refill_lock);
}

static void disable_delayed_refill(struct virtnet_info *vi)
{
	spin_lock_bh(&vi->refill_lock);
	vi->refill_enabled = false;
	spin_unlock_bh(&vi->refill_lock);
}

static void virtqueue_napi_schedule(struct napi_struct *napi,
static void virtqueue_napi_schedule(struct napi_struct *napi,
				    struct virtqueue *vq)
				    struct virtqueue *vq)
{
{
@@ -1388,8 +1408,12 @@ static int virtnet_receive(struct receive_queue *rq, int budget,
	}
	}


	if (rq->vq->num_free > min((unsigned int)budget, virtqueue_get_vring_size(rq->vq)) / 2) {
	if (rq->vq->num_free > min((unsigned int)budget, virtqueue_get_vring_size(rq->vq)) / 2) {
		if (!try_fill_recv(vi, rq, GFP_ATOMIC))
		if (!try_fill_recv(vi, rq, GFP_ATOMIC)) {
			spin_lock(&vi->refill_lock);
			if (vi->refill_enabled)
				schedule_delayed_work(&vi->refill, 0);
				schedule_delayed_work(&vi->refill, 0);
			spin_unlock(&vi->refill_lock);
		}
	}
	}


	u64_stats_update_begin(&rq->stats.syncp);
	u64_stats_update_begin(&rq->stats.syncp);
@@ -1508,6 +1532,8 @@ static int virtnet_open(struct net_device *dev)
	struct virtnet_info *vi = netdev_priv(dev);
	struct virtnet_info *vi = netdev_priv(dev);
	int i, err;
	int i, err;


	enable_delayed_refill(vi);

	for (i = 0; i < vi->max_queue_pairs; i++) {
	for (i = 0; i < vi->max_queue_pairs; i++) {
		if (i < vi->curr_queue_pairs)
		if (i < vi->curr_queue_pairs)
			/* Make sure we have some buffers: if oom use wq. */
			/* Make sure we have some buffers: if oom use wq. */
@@ -1878,6 +1904,8 @@ static int virtnet_close(struct net_device *dev)
	struct virtnet_info *vi = netdev_priv(dev);
	struct virtnet_info *vi = netdev_priv(dev);
	int i;
	int i;


	/* Make sure NAPI doesn't schedule refill work */
	disable_delayed_refill(vi);
	/* Make sure refill_work doesn't re-enable napi! */
	/* Make sure refill_work doesn't re-enable napi! */
	cancel_delayed_work_sync(&vi->refill);
	cancel_delayed_work_sync(&vi->refill);


@@ -2417,6 +2445,8 @@ static int virtnet_restore_up(struct virtio_device *vdev)


	virtio_device_ready(vdev);
	virtio_device_ready(vdev);


	enable_delayed_refill(vi);

	if (netif_running(vi->dev)) {
	if (netif_running(vi->dev)) {
		err = virtnet_open(vi->dev);
		err = virtnet_open(vi->dev);
		if (err)
		if (err)
@@ -3140,6 +3170,7 @@ static int virtnet_probe(struct virtio_device *vdev)
	vdev->priv = vi;
	vdev->priv = vi;


	INIT_WORK(&vi->config_work, virtnet_config_changed_work);
	INIT_WORK(&vi->config_work, virtnet_config_changed_work);
	spin_lock_init(&vi->refill_lock);


	/* If we can receive ANY GSO packets, we must allocate large ones. */
	/* If we can receive ANY GSO packets, we must allocate large ones. */
	if (virtio_has_feature(vdev, VIRTIO_NET_F_GUEST_TSO4) ||
	if (virtio_has_feature(vdev, VIRTIO_NET_F_GUEST_TSO4) ||