The free queue read pointer is owned by hardware and can retain a
position from the previous ring when the queue is rebuilt at a smaller
order. Using an unmasked pointer to index the replacement page table can
then access it out of bounds. A replacement ring which does not contain
the raw retained pointer can also let hardware address past the new
allocation.
Mask software page indices to the active queue order. Keep the
replacement ring large enough for the retained hardware pointer and
initialize the software-owned write pointer from it before filling the
ring. This represents an empty queue without trying to write the
read-only pointer.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 003108270d06..7ebd62f8a3a4 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -887,6 +887,7 @@ static unsigned int geth_fill_freeq(struct gemini_ethernet *geth, bool refill)
rw.bits32 = readl(geth->base + GLOBAL_SWFQ_RWPTR_REG);
pn = (refill ? rw.bits.wptr : rw.bits.rptr) >> fpp_order;
+ pn &= m_pn;
epn = (rw.bits.rptr >> fpp_order) - 1;
epn &= m_pn;
@@ -936,6 +937,7 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
union dma_skb_size skbsz;
unsigned int filled;
unsigned int pn;
+ u16 rptr;
geth->freeq_ring = dma_alloc_coherent(geth->dev,
sizeof(*geth->freeq_ring) << geth->freeq_order,
@@ -958,6 +960,10 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
if (!geth_freeq_alloc_map_page(geth, pn))
goto err_freeq_alloc;
+ /* Match the software pointer to the retained hardware pointer. */
+ rptr = GET_RPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG);
+ SET_WPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG, rptr & (len - 1));
+
filled = geth_fill_freeq(geth, false);
if (!filled)
goto err_freeq_alloc;
@@ -1068,6 +1074,7 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
struct net_device *other_netdev;
unsigned int new_size = 0;
unsigned int new_order;
+ u16 rptr;
int ret;
ASSERT_RTNL();
@@ -1099,6 +1106,10 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
}
new_order = min(15, ilog2(new_size - 1) + 1);
+ rptr = GET_RPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG);
+ if (rptr >= BIT(15))
+ return -EIO;
+ new_order = max_t(unsigned int, new_order, fls(rptr));
dev_dbg(geth->dev, "set shared queue to size %d order %d\n",
new_size, new_order);
if (geth->freeq_ring && geth->freeq_order == new_order)
--
2.56.0