]> git.hungrycats.org Git - linux/commitdiff
iommu/io-pgtable-arm: Fix stage-2 map/unmap for concatenated tables
authorMostafa Saleh <smostafa@google.com>
Thu, 24 Oct 2024 16:25:15 +0000 (16:25 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 9 Dec 2024 09:33:05 +0000 (10:33 +0100)
commit d71fa842d33c48ac2809ae11d2379b5a788792cb upstream.

ARM_LPAE_LVL_IDX() takes into account concatenated PGDs and can return
an index spanning multiple page-table pages given a sufficiently large
input address. However, when the resulting index is used to calculate
the number of remaining entries in the page, the possibility of
concatenation is ignored and we end up computing a negative upper bound:

max_entries = ARM_LPAE_PTES_PER_TABLE(data) - map_idx_start;

On the map path, this results in a negative 'mapped' value being
returned but on the unmap path we can leak child tables if they are
skipped in __arm_lpae_free_pgtable().

Introduce an arm_lpae_max_entries() helper to convert a table index into
the remaining number of entries within a single page-table page.

Cc: <stable@vger.kernel.org>
Signed-off-by: Mostafa Saleh <smostafa@google.com>
Link: https://lore.kernel.org/r/20241024162516.2005652-2-smostafa@google.com
[will: Tweaked comment and commit message]
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/iommu/io-pgtable-arm.c

index 934dc97f5df9ed05cc5d08176f6b050df47a82a3..bc758ab70f4940a48b7e186b2b898635bf23a0e4 100644 (file)
@@ -180,6 +180,18 @@ static phys_addr_t iopte_to_paddr(arm_lpae_iopte pte,
        return (paddr | (paddr << (48 - 12))) & (ARM_LPAE_PTE_ADDR_MASK << 4);
 }
 
+/*
+ * Convert an index returned by ARM_LPAE_PGD_IDX(), which can point into
+ * a concatenated PGD, into the maximum number of entries that can be
+ * mapped in the same table page.
+ */
+static inline int arm_lpae_max_entries(int i, struct arm_lpae_io_pgtable *data)
+{
+       int ptes_per_table = ARM_LPAE_PTES_PER_TABLE(data);
+
+       return ptes_per_table - (i & (ptes_per_table - 1));
+}
+
 static bool selftest_running = false;
 
 static dma_addr_t __arm_lpae_dma_addr(void *pages)
@@ -357,7 +369,7 @@ static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova,
 
        /* If we can install a leaf entry at this level, then do so */
        if (size == block_size) {
-               max_entries = ARM_LPAE_PTES_PER_TABLE(data) - map_idx_start;
+               max_entries = arm_lpae_max_entries(map_idx_start, data);
                num_entries = min_t(int, pgcount, max_entries);
                ret = arm_lpae_init_pte(data, iova, paddr, prot, lvl, num_entries, ptep);
                if (!ret)
@@ -557,7 +569,7 @@ static size_t arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data,
 
        if (size == split_sz) {
                unmap_idx_start = ARM_LPAE_LVL_IDX(iova, lvl, data);
-               max_entries = ptes_per_table - unmap_idx_start;
+               max_entries = arm_lpae_max_entries(unmap_idx_start, data);
                num_entries = min_t(int, pgcount, max_entries);
        }
 
@@ -615,7 +627,7 @@ static size_t __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,
 
        /* If the size matches this level, we're in the right place */
        if (size == ARM_LPAE_BLOCK_SIZE(lvl, data)) {
-               max_entries = ARM_LPAE_PTES_PER_TABLE(data) - unmap_idx_start;
+               max_entries = arm_lpae_max_entries(unmap_idx_start, data);
                num_entries = min_t(int, pgcount, max_entries);
 
                while (i < num_entries) {