mirror of
https://github.com/torvalds/linux.git
synced 2026-09-14 16:10:02 +02:00
nvdimm: virtio_pmem: refcount requests for token lifetime
KASAN reports slab-use-after-free in __wake_up_common():
BUG: KASAN: slab-use-after-free in __wake_up_common+0x114/0x160
Read of size 8 at addr ffff88810fdcb710 by task swapper/0/0
CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted
6.19.0-next-20260220-00006-g1eae5f204ec3 #4 PREEMPT(full)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux
1.17.0-2-2 04/01/2014
Call Trace:
<IRQ>
dump_stack_lvl+0x6d/0xb0
print_report+0x170/0x4e2
? __pfx__raw_spin_lock_irqsave+0x10/0x10
? __virt_addr_valid+0x1dc/0x380
kasan_report+0xbc/0xf0
? __wake_up_common+0x114/0x160
? __wake_up_common+0x114/0x160
__wake_up_common+0x114/0x160
? __pfx__raw_spin_lock_irqsave+0x10/0x10
__wake_up+0x36/0x60
virtio_pmem_host_ack+0x11d/0x3b0
? sched_balance_domains+0x29f/0xb00
? __pfx_virtio_pmem_host_ack+0x10/0x10
? _raw_spin_lock_irqsave+0x98/0x100
? __pfx__raw_spin_lock_irqsave+0x10/0x10
vring_interrupt+0x1c9/0x5e0
? __pfx_vp_interrupt+0x10/0x10
vp_vring_interrupt+0x87/0x100
? __pfx_vp_interrupt+0x10/0x10
__handle_irq_event_percpu+0x17f/0x550
? __pfx__raw_spin_lock+0x10/0x10
handle_irq_event+0xab/0x1c0
handle_fasteoi_irq+0x276/0xae0
__common_interrupt+0x65/0x130
common_interrupt+0x78/0xa0
</IRQ>
virtio_pmem_host_ack() wakes a request that has already been freed by the
submitter.
This happens when the request token is still reachable via the virtqueue,
but virtio_pmem_flush() returns and frees it.
Fix the token lifetime by refcounting struct virtio_pmem_request.
virtio_pmem_flush() holds a submitter reference, and the virtqueue holds an
extra reference once the request is queued. The completion path drops the
virtqueue reference, and the submitter drops its reference before
returning.
Fixes: 6e84200c0a ("virtio-pmem: Add virtio pmem driver")
Cc: stable@vger.kernel.org
Signed-off-by: Li Chen <me@linux.beauty>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Message-ID: <20260630092338.2094628-9-me@linux.beauty>
This commit is contained in:
parent
08e72a5ba1
commit
e57140944b
|
|
@ -15,6 +15,14 @@ struct virtio_pmem_flush_work {
|
|||
struct bio *bio;
|
||||
};
|
||||
|
||||
static void virtio_pmem_req_release(struct kref *kref)
|
||||
{
|
||||
struct virtio_pmem_request *req;
|
||||
|
||||
req = container_of(kref, struct virtio_pmem_request, kref);
|
||||
kfree(req);
|
||||
}
|
||||
|
||||
static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem)
|
||||
{
|
||||
struct virtio_pmem_request *req_buf;
|
||||
|
|
@ -42,6 +50,7 @@ void virtio_pmem_host_ack(struct virtqueue *vq)
|
|||
virtio_pmem_wake_one_waiter(vpmem);
|
||||
WRITE_ONCE(req_data->done, true);
|
||||
wake_up(&req_data->host_acked);
|
||||
kref_put(&req_data->kref, virtio_pmem_req_release);
|
||||
}
|
||||
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
|
||||
}
|
||||
|
|
@ -72,6 +81,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
|
|||
if (!req_data)
|
||||
return -ENOMEM;
|
||||
|
||||
kref_init(&req_data->kref);
|
||||
WRITE_ONCE(req_data->done, false);
|
||||
init_waitqueue_head(&req_data->host_acked);
|
||||
init_waitqueue_head(&req_data->wq_buf);
|
||||
|
|
@ -89,10 +99,23 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
|
|||
* to req_list and wait for host_ack to wake us up when free
|
||||
* slots are available.
|
||||
*/
|
||||
while ((err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data,
|
||||
GFP_ATOMIC)) == -ENOSPC) {
|
||||
for (;;) {
|
||||
err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data,
|
||||
GFP_ATOMIC);
|
||||
if (!err) {
|
||||
/*
|
||||
* Take the virtqueue reference while @pmem_lock is
|
||||
* held so completion cannot run concurrently.
|
||||
*/
|
||||
kref_get(&req_data->kref);
|
||||
break;
|
||||
}
|
||||
|
||||
dev_info(&vdev->dev, "failed to send command to virtio pmem device, no free slots in the virtqueue\n");
|
||||
if (err != -ENOSPC)
|
||||
break;
|
||||
|
||||
dev_info_ratelimited(&vdev->dev,
|
||||
"failed to send command to virtio pmem device, no free slots in the virtqueue\n");
|
||||
WRITE_ONCE(req_data->wq_buf_avail, false);
|
||||
list_add_tail(&req_data->list, &vpmem->req_list);
|
||||
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
|
||||
|
|
@ -101,6 +124,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
|
|||
wait_event(req_data->wq_buf, READ_ONCE(req_data->wq_buf_avail));
|
||||
spin_lock_irqsave(&vpmem->pmem_lock, flags);
|
||||
}
|
||||
|
||||
err1 = virtqueue_kick(vpmem->req_vq);
|
||||
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
|
||||
/*
|
||||
|
|
@ -116,7 +140,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
|
|||
err = le32_to_cpu(req_data->resp.ret);
|
||||
}
|
||||
|
||||
kfree(req_data);
|
||||
kref_put(&req_data->kref, virtio_pmem_req_release);
|
||||
return err;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@
|
|||
|
||||
#include <linux/module.h>
|
||||
#include <uapi/linux/virtio_pmem.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/libnvdimm.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
struct virtio_pmem_request {
|
||||
struct kref kref;
|
||||
struct virtio_pmem_req req;
|
||||
struct virtio_pmem_resp resp;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user