Commit 3162318194 for qemu.org
commit 3162318194472ace95700db9265f476c2a8d32f8
Author: Peter Maydell <peter.maydell@linaro.org>
Date: Mon Jul 20 19:05:30 2026 +0100
hw/dma/soc_dma: Use physical_memory_map() for mem2mem transfers
The soc_dma code has a fastpath for when DMA transfers are from RAM
to RAM. The current implementation of this has the caller of
soc_dma_port_add_mem() pass the underlying host address of the RAM
block that the DMA port is connected to (obtained via
memory_region_get_ram_ptr()). Then the actual transfer function does
a simple memcpy(). This has several problems.
Most importantly, no bounds checking is done on the address and size
passed by the guest, so the memcpy source and destination might be
outside the backing host RAM entirely. Secondly, because the DMA
access is done via this back door, there is no updating of the dirty
region when memory is written this way (there is a TODO comment
in omap_dma.c noting this).
Fix both of these by making the memory to memory transfer function
use physical_memory_map() to get the host addresses for the memory
copy. That function will automatically give us the bounds check that
we want and return a short length if the transfer would run off the
end of the RAM MemoryRegion it starts in. Since the OMAP DMA
documentation states that it's a guest error to misprogram the
addresses so that they fall outside the range that is valid for the
particular DMA port being addressed and that this can result in guest
memory corruption , we don't need to loop for short transfers, but
can simply log them and continue.
Note that we don't need to update addresses or bytecount here in the
transfer function, because when soc_dma_ch_update() selects
transfer_mem2mem it also sets ch->update to 1, which tells the
omap_dma_transfer_setup() code that it is responsible for updating
all the guest visible fields to match "transfer completed".
(We use physical_memory_map() here to match the use of
physical_memory_read() and physical_memory_write() in omap_dma.c;
making the DMA controller use an explicit AddressSpace would be
a separate cleanup task.)
Together with the preceding commits that fixed some integer overflow
problems, this fixes the "guest can provoke a bad memcpy() operation"
reported in issue #3204.
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3204
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Message-id: 20260710105907.2570621-7-peter.maydell@linaro.org
diff --git a/hw/dma/omap_dma.c b/hw/dma/omap_dma.c
index 14b310bf40..1a3ae74726 100644
--- a/hw/dma/omap_dma.c
+++ b/hw/dma/omap_dma.c
@@ -559,11 +559,6 @@ static void omap_dma_transfer_setup(struct soc_dma_ch_s *dma)
if (!ch->sync && frames) {
ch->cpc = a->dest & 0xffff;
}
-
- /*
- * TODO: if the destination port is IMIF or EMIFF, set the dirty
- * bits on it.
- */
}
omap_dma_interrupts_update(s);
diff --git a/hw/dma/soc_dma.c b/hw/dma/soc_dma.c
index 1b93aaf565..6e325122d9 100644
--- a/hw/dma/soc_dma.c
+++ b/hw/dma/soc_dma.c
@@ -20,13 +20,58 @@
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qemu/timer.h"
+#include "qemu/log.h"
+#include "system/physmem.h"
#include "hw/arm/soc_dma.h"
static void transfer_mem2mem(struct soc_dma_ch_s *ch)
{
- memcpy(ch->paddr[0], ch->paddr[1], ch->bytes);
- ch->paddr[0] += ch->bytes;
- ch->paddr[1] += ch->bytes;
+ /*
+ * Memory-to-memory transfer: do the whole thing in one go. The
+ * hardware spec says that it is invalid to program the OMAP DMA
+ * controller with addresses that don't match the port (i.e. to
+ * ask for a transfer to/from a memory port with a physaddr that
+ * isn't within that port range) and that if you do then the
+ * transfer continues and memory can be corrupted. So we can map
+ * both source and destination, and treat short mappings and
+ * failed mappings as a guest error.
+ */
+ hwaddr srclen = ch->bytes;
+ hwaddr dstlen = ch->bytes;
+ hwaddr srcaddr = ch->vaddr[0];
+ hwaddr dstaddr = ch->vaddr[1];
+ void *srcmem, *dstmem;
+ hwaddr xferlen = 0;
+
+ srcmem = physical_memory_map(srcaddr, &srclen, false);
+ if (!srcmem) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "soc_dma mem2mem transfer: could not map source; "
+ "guest error programming source port/address\n");
+ return;
+ }
+
+ dstmem = physical_memory_map(dstaddr, &dstlen, true);
+ if (!dstmem) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "soc_dma mem2mem transfer: could not map destination; "
+ "guest error programming destination port/address\n");
+ goto unmap_src;
+ }
+
+ xferlen = MIN(srclen, dstlen);
+ if (xferlen < ch->bytes) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "soc_dma mem2mem transfer: could not transfer all data; "
+ "guest error programming src or destination addresses\n");
+ /* Continue to transfer whatever did fit in the port window */
+ }
+
+ memmove(dstmem, srcmem, xferlen);
+
+ physical_memory_unmap(dstmem, dstlen, true, xferlen);
+unmap_src:
+ physical_memory_unmap(srcmem, srclen, false, xferlen);
}
struct dma_s {