DMA-BUF mappings do not support copy-on-write.  Mapping a dma-buf with
MAP_PRIVATE and PROT_WRITE faults into do_wp_page() -> wp_page_copy()
and allocates private anonymous pages whose writes never reach the
underlying buffer.

Individual exporters like cma_heap_mmap(), system_heap_mmap(), and
mmap_udmabuf() already require (VM_SHARED | VM_MAYSHARE), and DRM
helpers like ttm_bo_mmap_obj() and drm_gem_shmem_object_mmap() reject
vma_is_cow_mapping(vma).  However, dma_buf_mmap_internal() and
dma_buf_mmap() do not check for this and pass MAP_PRIVATE mappings
straight through to dmabuf->ops->mmap().

Reject mappings with -EINVAL in both dma_buf_mmap_internal() and
dma_buf_mmap() when neither VM_SHARED nor VM_MAYSHARE is set.  Checking
both flags still allows read-only MAP_SHARED mappings, where do_mmap()
clears VM_SHARED and leaves VM_MAYSHARE set.

Suggested-by: T.J. Mercier <[email protected]>
Suggested-by: Christian König <[email protected]>
Signed-off-by: Chris Wiseman <[email protected]>
---
Note: For the prior discussion Christian mentioned, commit f91142c62161
("drm/ttm: nuke VM_MIXEDMAP on BO mappings v3") added the
vma_is_cow_mapping() check in TTM, and commit fa5239f2af98
("drm/amdgpu: workaround failed COW checks for Thunk VMAs") worked
around a legacy ROCm KFD Thunk PROT_NONE, MAP_PRIVATE mapping on native
amdgpu GEM nodes.

Tested in ARM64 QEMU (Linux 7.3.0+) across /dev/dma_heap/system:

  BASE:
    [CASE 1: MAP_SHARED O_RDWR]     ret=0   buf[0]=0x00000000cafebabe
    [CASE 2: MAP_SHARED O_RDONLY]   ret=0
    [CASE 3: MAP_PRIVATE PROT_RDWR] ret=0   underlying_buf[0]=0x00000000cafebabe
    [CASE 4: MAP_PRIVATE PROT_READ] ret=0
    [CASE 5: MAP_PRIVATE PROT_NONE] ret=0

  PATCHED:
    [CASE 1: MAP_SHARED O_RDWR]     ret=0   buf[0]=0x00000000cafebabe
    [CASE 2: MAP_SHARED O_RDONLY]   ret=0
    [CASE 3: MAP_PRIVATE PROT_RDWR] ret=-22 (-EINVAL)
    [CASE 4: MAP_PRIVATE PROT_READ] ret=-22 (-EINVAL)
    [CASE 5: MAP_PRIVATE PROT_NONE] ret=-22 (-EINVAL)

 drivers/dma-buf/dma-buf.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/dma-buf/dma-buf.c b/drivers/dma-buf/dma-buf.c
index 1ccaffabbdbe..51e9a4dc56be 100644
--- a/drivers/dma-buf/dma-buf.c
+++ b/drivers/dma-buf/dma-buf.c
@@ -235,6 +235,10 @@ static int dma_buf_mmap_internal(struct file *file, struct 
vm_area_struct *vma)
        if (!dmabuf->ops->mmap)
                return -EINVAL;
 
+       /* check for private mappings */
+       if (!(vma->vm_flags & (VM_SHARED | VM_MAYSHARE)))
+               return -EINVAL;
+
        /* check for overflowing the buffer's size */
        if (vma->vm_pgoff + vma_pages(vma) >
            dmabuf->size >> PAGE_SHIFT)
@@ -1610,6 +1614,10 @@ int dma_buf_mmap(struct dma_buf *dmabuf, struct 
vm_area_struct *vma,
        if (!dmabuf->ops->mmap)
                return -EINVAL;
 
+       /* check for private mappings */
+       if (!(vma->vm_flags & (VM_SHARED | VM_MAYSHARE)))
+               return -EINVAL;
+
        /* check for offset overflow */
        if (pgoff + vma_pages(vma) < pgoff)
                return -EOVERFLOW;
-- 
2.51.0

Reply via email to