mirror of
https://abf.rosa.ru/djam/kernel-6.6.git
synced 2025-02-25 20:02:47 +00:00
135 lines
4.3 KiB
Diff
135 lines
4.3 KiB
Diff
From foo@baz Thu Feb 23 21:13:05 CET 2017
|
|
From: "Michael S. Tsirkin" <mst@redhat.com>
|
|
Date: Sun, 19 Feb 2017 07:17:17 +0200
|
|
Subject: ptr_ring: fix race conditions when resizing
|
|
|
|
From: "Michael S. Tsirkin" <mst@redhat.com>
|
|
|
|
|
|
[ Upstream commit e71695307114335be1ed912f4a347396c2ed0e69 ]
|
|
|
|
Resizing currently drops consumer lock. This can cause entries to be
|
|
reordered, which isn't good in itself. More importantly, consumer can
|
|
detect a false ring empty condition and block forever.
|
|
|
|
Further, nesting of consumer within producer lock is problematic for
|
|
tun, since it produces entries in a BH, which causes a lock order
|
|
reversal:
|
|
|
|
CPU0 CPU1
|
|
---- ----
|
|
consume:
|
|
lock(&(&r->consumer_lock)->rlock);
|
|
resize:
|
|
local_irq_disable();
|
|
lock(&(&r->producer_lock)->rlock);
|
|
lock(&(&r->consumer_lock)->rlock);
|
|
<Interrupt>
|
|
produce:
|
|
lock(&(&r->producer_lock)->rlock);
|
|
|
|
To fix, nest producer lock within consumer lock during resize,
|
|
and keep consumer lock during the whole swap operation.
|
|
|
|
Reported-by: Dmitry Vyukov <dvyukov@google.com>
|
|
Cc: stable@vger.kernel.org
|
|
Cc: "David S. Miller" <davem@davemloft.net>
|
|
Acked-by: Jason Wang <jasowang@redhat.com>
|
|
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
|
|
Signed-off-by: David S. Miller <davem@davemloft.net>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
---
|
|
include/linux/ptr_ring.h | 36 +++++++++++++++++++++++++++++++-----
|
|
1 file changed, 31 insertions(+), 5 deletions(-)
|
|
|
|
--- a/include/linux/ptr_ring.h
|
|
+++ b/include/linux/ptr_ring.h
|
|
@@ -111,6 +111,11 @@ static inline int __ptr_ring_produce(str
|
|
return 0;
|
|
}
|
|
|
|
+/*
|
|
+ * Note: resize (below) nests producer lock within consumer lock, so if you
|
|
+ * consume in interrupt or BH context, you must disable interrupts/BH when
|
|
+ * calling this.
|
|
+ */
|
|
static inline int ptr_ring_produce(struct ptr_ring *r, void *ptr)
|
|
{
|
|
int ret;
|
|
@@ -242,6 +247,11 @@ static inline void *__ptr_ring_consume(s
|
|
return ptr;
|
|
}
|
|
|
|
+/*
|
|
+ * Note: resize (below) nests producer lock within consumer lock, so if you
|
|
+ * call this in interrupt or BH context, you must disable interrupts/BH when
|
|
+ * producing.
|
|
+ */
|
|
static inline void *ptr_ring_consume(struct ptr_ring *r)
|
|
{
|
|
void *ptr;
|
|
@@ -357,7 +367,7 @@ static inline void **__ptr_ring_swap_que
|
|
void **old;
|
|
void *ptr;
|
|
|
|
- while ((ptr = ptr_ring_consume(r)))
|
|
+ while ((ptr = __ptr_ring_consume(r)))
|
|
if (producer < size)
|
|
queue[producer++] = ptr;
|
|
else if (destroy)
|
|
@@ -372,6 +382,12 @@ static inline void **__ptr_ring_swap_que
|
|
return old;
|
|
}
|
|
|
|
+/*
|
|
+ * Note: producer lock is nested within consumer lock, so if you
|
|
+ * resize you must make sure all uses nest correctly.
|
|
+ * In particular if you consume ring in interrupt or BH context, you must
|
|
+ * disable interrupts/BH when doing so.
|
|
+ */
|
|
static inline int ptr_ring_resize(struct ptr_ring *r, int size, gfp_t gfp,
|
|
void (*destroy)(void *))
|
|
{
|
|
@@ -382,17 +398,25 @@ static inline int ptr_ring_resize(struct
|
|
if (!queue)
|
|
return -ENOMEM;
|
|
|
|
- spin_lock_irqsave(&(r)->producer_lock, flags);
|
|
+ spin_lock_irqsave(&(r)->consumer_lock, flags);
|
|
+ spin_lock(&(r)->producer_lock);
|
|
|
|
old = __ptr_ring_swap_queue(r, queue, size, gfp, destroy);
|
|
|
|
- spin_unlock_irqrestore(&(r)->producer_lock, flags);
|
|
+ spin_unlock(&(r)->producer_lock);
|
|
+ spin_unlock_irqrestore(&(r)->consumer_lock, flags);
|
|
|
|
kfree(old);
|
|
|
|
return 0;
|
|
}
|
|
|
|
+/*
|
|
+ * Note: producer lock is nested within consumer lock, so if you
|
|
+ * resize you must make sure all uses nest correctly.
|
|
+ * In particular if you consume ring in interrupt or BH context, you must
|
|
+ * disable interrupts/BH when doing so.
|
|
+ */
|
|
static inline int ptr_ring_resize_multiple(struct ptr_ring **rings, int nrings,
|
|
int size,
|
|
gfp_t gfp, void (*destroy)(void *))
|
|
@@ -412,10 +436,12 @@ static inline int ptr_ring_resize_multip
|
|
}
|
|
|
|
for (i = 0; i < nrings; ++i) {
|
|
- spin_lock_irqsave(&(rings[i])->producer_lock, flags);
|
|
+ spin_lock_irqsave(&(rings[i])->consumer_lock, flags);
|
|
+ spin_lock(&(rings[i])->producer_lock);
|
|
queues[i] = __ptr_ring_swap_queue(rings[i], queues[i],
|
|
size, gfp, destroy);
|
|
- spin_unlock_irqrestore(&(rings[i])->producer_lock, flags);
|
|
+ spin_unlock(&(rings[i])->producer_lock);
|
|
+ spin_unlock_irqrestore(&(rings[i])->consumer_lock, flags);
|
|
}
|
|
|
|
for (i = 0; i < nrings; ++i)
|