[PATCH] dmaengine: xilinx: Fix wild pointer dereference on empty segment list

From: Li Ximo

Date: Fri Oct 23 2026 - 03:03:04 EST


From: Wang Haoran <haoranwangsec@xxxxxxxxx>

list_first_entry() does not return NULL when the list is empty; it
returns the result of container_of() on the list head, i.e. a wild
pointer that points into the enclosing xilinx_dma_tx_descriptor rather
than to a valid segment. Dereferencing it reads or writes unrelated
descriptor memory. This was found by code audit.

In xilinx_dma_prep_peripheral_dma_vec(), xilinx_dma_prep_slave_sg()
and xilinx_mcdma_prep_slave_sg(), the loop that populates
desc->segments never executes when the transfer length is zero
(nb == 0, sg_len == 0, or all vecs[i].len / sg_dma_len() entries are
zero). The dmaengine inline wrappers do not reject such zero-length
transfers, so they reach the driver unchanged. The unconditional
list_first_entry() then yields a wild pointer: the following
head->phys / segment->phys reads are out-of-bounds reads within the
descriptor slab, and on the DMA_MEM_TO_DEV path the hw.control
read-modify-write corrupts the descriptor. In
xilinx_dma_prep_peripheral_dma_vec() the empty case additionally
dereferences the never-initialized 'segment' pointer. Similarly, in
xilinx_dma_get_metadata_ptr(), a descriptor with an empty segment
list produces a wild 'seg' whose hw.app pointer is handed back to the
DMA client.

Guard each list_first_entry() call: in the three prep functions, bail
out through the existing error path (freeing the descriptor and
returning NULL, consistent with the current failure handling) when
desc->segments is still empty after the build loop; in
xilinx_dma_get_metadata_ptr(), use list_first_entry_or_null() and
return NULL when there is no segment.

Reported-by: Wang Haoran <haoranwangsec@xxxxxxxxx>
Reported-by: Li Ximo <lixicaicai@xxxxxxxxx>
Signed-off-by: Wang Haoran <haoranwangsec@xxxxxxxxx>
---
drivers/dma/xilinx/xilinx_dma.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index cffe7c6fa..07f52f841 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -650,8 +650,11 @@ static void *xilinx_dma_get_metadata_ptr(struct dma_async_tx_descriptor *tx,
struct xilinx_axidma_tx_segment *seg;

*max_len = *payload_len = sizeof(u32) * XILINX_DMA_NUM_APP_WORDS;
- seg = list_first_entry(&desc->segments,
- struct xilinx_axidma_tx_segment, node);
+ seg = list_first_entry_or_null(&desc->segments,
+ struct xilinx_axidma_tx_segment, node);
+ if (!seg)
+ return NULL;
+
return seg->hw.app;
}

@@ -2305,6 +2308,9 @@ static struct dma_async_tx_descriptor *xilinx_dma_prep_peripheral_dma_vec(
}
}

+ if (list_empty(&desc->segments))
+ goto error;
+
head = list_first_entry(&desc->segments, struct xilinx_axidma_tx_segment, node);
desc->async_tx.phys = head->phys;

@@ -2406,6 +2412,9 @@ static struct dma_async_tx_descriptor *xilinx_dma_prep_slave_sg(
}
}

+ if (list_empty(&desc->segments))
+ goto error;
+
segment = list_first_entry(&desc->segments,
struct xilinx_axidma_tx_segment, node);
desc->async_tx.phys = segment->phys;
@@ -2613,6 +2622,9 @@ xilinx_mcdma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
}
}

+ if (list_empty(&desc->segments))
+ goto error;
+
segment = list_first_entry(&desc->segments,
struct xilinx_aximcdma_tx_segment, node);
desc->async_tx.phys = segment->phys;
--
2.30.2