Devices that are DMA non-coherent and require a remap were skipping
dma_set_decrypted(), leaving DMA buffers encrypted even when the device
requires unencrypted access. Move the call after the if (remap) branch
so that both the direct and remapped allocation paths correctly mark the
allocation as decrypted (or fail cleanly) before use.

Fix dma_direct_alloc() and dma_direct_free() to apply set_memory_*() to
the linear-map alias of the backing pages instead of the remapped CPU
address. Also disallow highmem pages for DMA_ATTR_CC_SHARED, because
highmem buffers do not provide a usable linear-map address.

Fixes: f3c962226dbe ("dma-direct: clean up the remapping checks in 
dma_direct_alloc")
Signed-off-by: Aneesh Kumar K.V (Arm) <[email protected]>
---
 kernel/dma/direct.c | 56 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 45 insertions(+), 11 deletions(-)

diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index 5aaa813c5509..f5da6e992d83 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -204,6 +204,7 @@ void *dma_direct_alloc(struct device *dev, size_t size,
 {
        bool remap = false, set_uncached = false;
        bool mark_mem_decrypt = false;
+       bool allow_highmem = true;
        struct page *page;
        void *ret;
 
@@ -222,6 +223,15 @@ void *dma_direct_alloc(struct device *dev, size_t size,
                mark_mem_decrypt = true;
        }
 
+       if (attrs & DMA_ATTR_CC_SHARED)
+               /*
+                * Unencrypted/shared DMA requires a linear-mapped buffer
+                * address to look up the PFN and set architecture-required PFN
+                * attributes. This is not possible with HighMem. Avoid HighMem
+                * allocation.
+                */
+               allow_highmem = false;
+
        size = PAGE_ALIGN(size);
        if (attrs & DMA_ATTR_NO_WARN)
                gfp |= __GFP_NOWARN;
@@ -280,7 +290,7 @@ void *dma_direct_alloc(struct device *dev, size_t size,
        }
 
        /* we always manually zero the memory once we are done */
-       page = __dma_direct_alloc_pages(dev, size, gfp & ~__GFP_ZERO, true);
+       page = __dma_direct_alloc_pages(dev, size, gfp & ~__GFP_ZERO, 
allow_highmem);
        if (!page)
                return NULL;
 
@@ -295,6 +305,14 @@ void *dma_direct_alloc(struct device *dev, size_t size,
                set_uncached = false;
        }
 
+       if (mark_mem_decrypt) {
+               void *lm_addr;
+
+               lm_addr = page_address(page);
+               if (set_memory_decrypted((unsigned long)lm_addr, PFN_UP(size)))
+                       goto out_leak_pages;
+       }
+
        if (remap) {
                pgprot_t prot = dma_pgprot(dev, PAGE_KERNEL, attrs);
 
@@ -305,31 +323,39 @@ void *dma_direct_alloc(struct device *dev, size_t size,
                ret = dma_common_contiguous_remap(page, size, prot,
                                __builtin_return_address(0));
                if (!ret)
-                       goto out_free_pages;
+                       goto out_encrypt_pages;
        } else {
                ret = page_address(page);
-               if (mark_mem_decrypt && dma_set_decrypted(dev, ret, size))
-                       goto out_leak_pages;
        }
 
        memset(ret, 0, size);
 
        if (set_uncached) {
+               void *uncached_cpu_addr;
+
                arch_dma_prep_coherent(page, size);
-               ret = arch_dma_set_uncached(ret, size);
-               if (IS_ERR(ret))
-                       goto out_encrypt_pages;
+               uncached_cpu_addr = arch_dma_set_uncached(ret, size);
+               if (IS_ERR(uncached_cpu_addr))
+                       goto out_free_remap_pages;
+               ret = uncached_cpu_addr;
        }
 
        *dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
        return ret;
 
+
+out_free_remap_pages:
+       if (remap)
+               dma_common_free_remap(ret, size);
+
 out_encrypt_pages:
-       if (mark_mem_decrypt && dma_set_encrypted(dev, page_address(page), 
size))
-               return NULL;
-out_free_pages:
+       if (mark_mem_decrypt &&
+           dma_set_encrypted(dev, page_address(page), size))
+               goto out_leak_pages;
+
        __dma_direct_free_pages(dev, page, size);
        return NULL;
+
 out_leak_pages:
        return NULL;
 }
@@ -384,8 +410,16 @@ void dma_direct_free(struct device *dev, size_t size,
        } else {
                if (IS_ENABLED(CONFIG_ARCH_HAS_DMA_CLEAR_UNCACHED))
                        arch_dma_clear_uncached(cpu_addr, size);
-               if (mark_mem_encrypted && dma_set_encrypted(dev, cpu_addr, 
size))
+       }
+
+       if (mark_mem_encrypted) {
+               void *lm_addr;
+
+               lm_addr = phys_to_virt(dma_to_phys(dev, dma_addr));
+               if (set_memory_encrypted((unsigned long)lm_addr, PFN_UP(size))) 
{
+                       pr_warn_ratelimited("leaking DMA memory that can't be 
re-encrypted\n");
                        return;
+               }
        }
 
        __dma_direct_free_pages(dev, dma_direct_to_page(dev, dma_addr), size);
-- 
2.43.0


Reply via email to