Skip to contentSkip to conversation

xen/arm: guest_walk: refuse a block descriptor where the granule has none

PATCH In review Stefano Stabellini 4 participants 8 messages Nov 7 – Nov 10

lore
Share & tools
lore
#0 lore raw

PATCH 1/4 xen/arm: guest_walk: refuse a block descriptor where the granule has none

guest_walk_ld() accepted a block descriptor at any level. No granule has level 0 blocks, and the 16KB and 64KB granules have no level 1 blocks (ARM DDI 0487A.k D4.3.1): the translation table walk makes such an entry a Translation fault (AArch64.TranslationTableWalk(), J1.1.5). Refuse it.

Fixes 7d623b358a45 ("arm/mem_access: Add long-descriptor based gpt")
Cc Julien Grall <[email protected]>
Cc Bertrand Marquis <[email protected]>
Cc Michal Orzel <[email protected]>
Cc Volodymyr Babchuk <[email protected]>
---
 xen/arch/arm/guest_walk.c | 9 +++++++++
 1 file changed, 9 insertions(+)
xen/arch/arm/guest_walk.c +9 -0
index 09fe486598..e23457869b 100644
@@ -533,6 +533,15 @@ static bool guest_walk_ld(const struct vcpu *v,
     if ( !lpae_is_valid(pte) || !lpae_is_mapping(pte, level) )
         return false;
 
+    /*
+     * A block descriptor is only supported at level 1 and level 2 with the
+     * 4KB granule, and only at level 2 with the 16KB and 64KB granules (ARM
+     * DDI 0487A.k D4.3.1). The block encoding at any other level generates a
+     * Translation fault (AArch64.TranslationTableWalk(), J1.1.5).
+     */
+    if ( level == 0 || (level == 1 && gran != GRANULE_SIZE_INDEX_4K) )
+        return false;
+
     /* Make sure that the lower bits of the PTE's base address are zero. */
     mask = GENMASK_ULL(47, grainsizes[gran]);
     *ipa = (pfn_to_paddr(pte.walk.base) & mask) |
Signature · 1 line
2.43.0
#1 lore raw

PATCH 2/4 xen/arm: guest_walk: refuse an address outside both halves of the range

The bits of an input address from its top bit (bit 63, or bit 55 with the top byte ignored) down to the input address size must be all 0 for TTBR0 and all 1 for TTBR1, else the walk faults (ARM DDI 0487A.k J1.1.5). guest_walk_ld() chose the base from the top bit alone and translated such an address. Refuse it before reading any table.

Fixes 7d623b358a45 ("arm/mem_access: Add long-descriptor based gpt")
Cc Julien Grall <[email protected]>
Cc Bertrand Marquis <[email protected]>
Cc Michal Orzel <[email protected]>
Cc Volodymyr Babchuk <[email protected]>
---
 xen/arch/arm/guest_walk.c | 9 +++++++++
 1 file changed, 9 insertions(+)
xen/arch/arm/guest_walk.c +9 -0
index e23457869b..a8f8530683 100644
@@ -407,6 +407,15 @@ static bool guest_walk_ld(const struct vcpu *v,
         if ( (input_size > TCR_EL1_IPS_48_BIT_VAL) ||
              (input_size < TCR_EL1_IPS_MIN_VAL) )
             return false;
+
+        /*
+         * The bits of the GVA from the top bit down to the input size must
+         * all be 0 for TTBR0 and all 1 for TTBR1, or the translation faults
+         * (ARM DDI 0487A.k AArch64.TranslationTableWalk(), J1.1.5).
+         */
+        mask = GENMASK_ULL(topbit, input_size);
+        if ( (gva & mask) != ((gva & BIT(topbit, ULL)) ? mask : 0) )
+            return false;
     }
     else
     {
Signature · 1 line
2.43.0
#2 lore raw

Re: [PATCH 2/4] xen/arm: guest_walk: refuse an address outside both halves of the range

Hi Stefano,

Quoted 4 lines · Stefano Stabellini

The bits of an input address from its top bit (bit 63, or bit 55 with the top byte ignored) down to the input address size must be all 0 for TTBR0 and all 1 for TTBR1, else the walk faults (ARM DDI 0487A.k J1.1.5). guest_walk_ld() chose the base from the top bit alone and

Similar to your first patch, the specification is quite old. Please quote a more recent one (e.g. M.d).

Quoted 17 lines

translated such an address. Refuse it before reading any table.

Fixes 7d623b358a45 ("arm/mem_access: Add long-descriptor based gpt")
Cc Julien Grall <[email protected]>
Cc Bertrand Marquis <[email protected]>
Cc Michal Orzel <[email protected]>
Cc Volodymyr Babchuk <[email protected]>
---
  xen/arch/arm/guest_walk.c | 9 +++++++++
  1 file changed, 9 insertions(+)
xen/arch/arm/guest_walk.c +0 -0
index e23457869b..a8f8530683 100644

xen/arch/arm/guest_walk.c static bool guest_walk_ld(const struct vcpu *v, · line 415

Quoted 9 lines
xen/arch/arm/guest_walk.c +6 -0
@@ -407,6 +407,15 @@ static bool guest_walk_ld(const struct vcpu *v,
          if ( (input_size > TCR_EL1_IPS_48_BIT_VAL) ||
               (input_size < TCR_EL1_IPS_MIN_VAL) )
              return false;
+
+        /*
+         * The bits of the GVA from the top bit down to the input size must
+         * all be 0 for TTBR0 and all 1 for TTBR1, or the translation faults
+         * (ARM DDI 0487A.k AArch64.TranslationTableWalk(), J1.1.5).
+         */

Regardless what I wrote above, I checked the algorithm in A.k and I can't find the logic suggesting for a fault. Instead it seems to be:

if inputaddr<top> == '0' then
inputsize = 64 - UInt(TCR_EL1.T0SZ);
[...]
else
inputsize = 64 - UInt(TCR_EL1.T1SZ);
[...]

And this seems right to me because the be used for tagging. So they are not guarantee to be all 1s or all 0s. Can you point me to the part of the algorithm that says otherwise?

Cheers,

Signature · 1 line
Julien Grall
#3 lore raw

PATCH 3/4 xen/arm: traps: enter the SP_EL0 vector group for an exception at EL1t

In reply to Stefano Stabellini

An exception taken from the current level is delivered to the SP_EL0 vector group (offset 0x000) when the guest was at EL1t, and to the SP_ELx group (0x200) only at EL1h (ARM DDI 0487A.k D1.10.2, Table D1-7). exception_handler64() used 0x200 for both.

Cc Julien Grall <[email protected]>
Cc Bertrand Marquis <[email protected]>
Cc Michal Orzel <[email protected]>
Cc Volodymyr Babchuk <[email protected]>
---
 xen/arch/arm/traps.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
xen/arch/arm/traps.c +7 -1
index 6a5dcef2aa..f6abe25f74 100644
@@ -532,7 +532,13 @@ static vaddr_t exception_handler64(struct cpu_user_regs *regs, vaddr_t offset)
         base += VECTOR64_LOWER32_BASE;
     else if ( psr_mode(regs->cpsr,PSR_MODE_EL0t) )
         base += VECTOR64_LOWER64_BASE;
-    else /* Otherwise must be from kernel mode */
+    /*
+     * Otherwise must be from kernel mode. The current EL group is chosen by
+     * the stack pointer in use (ARM DDI 0487A.k D1.10.2, Table D1-7).
+     */
+    else if ( psr_mode(regs->cpsr,PSR_MODE_EL1t) )
+        base += VECTOR64_CURRENT_SP0_BASE;
+    else
         base += VECTOR64_CURRENT_SPx_BASE;
 
     return base + offset;
Signature · 1 line
2.43.0
Reviewed-by on v1 3/4
#4 lore raw

Re: [PATCH 3/4] xen/arm: traps: enter the SP_EL0 vector group for an exception at EL1t

Quoted 3 lines · Stefano Stabellini

An exception taken from the current level is delivered to the SP_EL0 vector group (offset 0x000) when the guest was at EL1t, and to the SP_ELx group (0x200) only at EL1h (ARM DDI 0487A.k D1.10.2, Table

Please cite the latest Arm ARM, if at all (here and in the comment).

Quoted 7 lines

D1-7). exception_handler64() used 0x200 for both.

Cc Julien Grall <[email protected]>
Cc Bertrand Marquis <[email protected]>
Cc Michal Orzel <[email protected]>
Cc Volodymyr Babchuk <[email protected]>

Please move the CC list after ---.

With that:

~Michal

#5 lore raw

PATCH 4/4 vpci/header: empty the BAR range sets when modify_bars() fails

In reply to Stefano Stabellini

On error modify_bars() returned with the ranges already collected still in the per-BAR sets. The next request on the device then maps or unmaps them along with its own. Purge every set when collecting the ranges fails.

Fixes 622bdd962822 ("vpci/header: handle p2m range sets per BAR")
Cc "Roger Pau Monné" <[email protected]>
Cc Stewart Hildebrand <[email protected]>
---
 xen/drivers/vpci/header.c | 25 +++++++++++++++++++------
 1 file changed, 19 insertions(+), 6 deletions(-)
xen/drivers/vpci/header.c +19 -6
index 7e5f4b8a00..0bdc4d6a46 100644
@@ -366,7 +366,8 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)
             gprintk(XENLOG_G_WARNING,
                     "%pp: can't map BAR%u - offset mismatch: %#lx vs %#lx\n",
                     &pdev->sbdf, i, bar->guest_addr, bar->addr);
-            return -EINVAL;
+            rc = -EINVAL;
+            goto fail;
         }
 
         rc = rangeset_add_range(bar->mem, start_guest, end_guest);
@@ -374,7 +375,7 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)
         {
             printk(XENLOG_G_WARNING "Failed to add [%lx, %lx]: %d\n",
                    start_guest, end_guest, rc);
-            return rc;
+            goto fail;
         }
 
         /* Check for overlap with the already setup BAR ranges. */
@@ -391,7 +392,7 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)
                 gprintk(XENLOG_WARNING,
                        "%pp: failed to remove overlapping range [%lx, %lx]: %d\n",
                         &pdev->sbdf, start_guest, end_guest, rc);
-                return rc;
+                goto fail;
             }
         }
 
@@ -401,7 +402,7 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)
             gprintk(XENLOG_WARNING,
                     "%pp: failed to sanitize BAR#%u memory: %d\n",
                     &pdev->sbdf, i, rc);
-            return rc;
+            goto fail;
         }
     }
 
@@ -425,7 +426,7 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)
                 gprintk(XENLOG_WARNING,
                        "%pp: failed to remove MSIX table [%lx, %lx]: %d\n",
                         &pdev->sbdf, start, end, rc);
-                return rc;
+                goto fail;
             }
         }
     }
@@ -487,7 +488,7 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)
                         gprintk(XENLOG_WARNING,
                                 "%pp: failed to remove [%lx, %lx]: %d\n",
                                 &pdev->sbdf, start, end, rc);
-                        return rc;
+                        goto fail;
                     }
                 }
             }
@@ -516,6 +517,18 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)
     defer_map(pdev, cmd, rom_only);
 
     return 0;
+
+ fail:
+    /*
+     * Leave no range collected for the request that failed: the next request
+     * expects every BAR's set to start empty, and a range left behind would
+     * be mapped or unmapped with it.
+     */
+    for ( i = 0; i < ARRAY_SIZE(header->bars); i++ )
+        if ( header->bars[i].mem )
+            rangeset_purge(header->bars[i].mem);
+
+    return rc;
 }
 
 static void cf_check cmd_write(
Signature · 1 line
2.43.0
Reviewed-by on v1 4/4
#6 lore raw

Re: [PATCH 4/4] vpci/header: empty the BAR range sets when modify_bars() fails

Quoted 4 lines · Stefano Stabellini

On error modify_bars() returned with the ranges already collected still in the per-BAR sets. The next request on the device then maps or unmaps them along with its own. Purge every set when collecting the ranges fails.

IOW the rangeset_is_empty() assertion would then trigger? May be worth mentioning here.

Quoted 2 lines
Fixes 622bdd962822 ("vpci/header: handle p2m range sets per BAR")

Jan

#7 lore raw
In reply to Stefano Stabellini

Hi Stefano,

Quoted 3 lines · Stefano Stabellini

guest_walk_ld() accepted a block descriptor at any level. No granule has level 0 blocks, and the 16KB and 64KB granules have no level 1 blocks (ARM DDI 0487A.k D4.3.1): the translation table walk makes such an entry

It would be good to quote a specification that is a bit newer (A.k was release in 2016!). In recent spec (M.d), it seems you can have level 1 blocks for 16KiB and 64KiB when 52-bit is supported.

Looking at the logic in get_ipa_output_size() we are rejecting 52-bit at the moment. So the check you add is fine as-is. I think it would be made more clear in the commit message and ...

Quoted 17 lines

a Translation fault (AArch64.TranslationTableWalk(), J1.1.5). Refuse it.

Fixes 7d623b358a45 ("arm/mem_access: Add long-descriptor based gpt")
Cc Julien Grall <[email protected]>
Cc Bertrand Marquis <[email protected]>
Cc Michal Orzel <[email protected]>
Cc Volodymyr Babchuk <[email protected]>
---
  xen/arch/arm/guest_walk.c | 9 +++++++++
  1 file changed, 9 insertions(+)
xen/arch/arm/guest_walk.c +0 -0
index 09fe486598..e23457869b 100644

xen/arch/arm/guest_walk.c static bool guest_walk_ld(const struct vcpu *v, · line 540

Quoted 8 lines
xen/arch/arm/guest_walk.c +5 -0
@@ -533,6 +533,15 @@ static bool guest_walk_ld(const struct vcpu *v,
      if ( !lpae_is_valid(pte) || !lpae_is_mapping(pte, level) )
          return false;
 
+    /*
+     * A block descriptor is only supported at level 1 and level 2 with the
+     * 4KB granule, and only at level 2 with the 16KB and 64KB granules (ARM
+     * DDI 0487A.k D4.3.1). The block encoding at any other level generates a
+     * Translation fault (AArch64.TranslationTableWalk(), J1.1.5).

... the comment that this is only valid for 48-bit. I would also recommend to update the comment in get_ipa_output_size() to mention this check would need to be updated. Alternatively, you could check the ipa_sizes now.

xen/arch/arm/guest_walk.c static bool guest_walk_ld(const struct vcpu *v, · line 547

Quoted 7 lines
xen/arch/arm/guest_walk.c +4 -0
+     */
+    if ( level == 0 || (level == 1 && gran != GRANULE_SIZE_INDEX_4K) )
+        return false;
+
      /* Make sure that the lower bits of the PTE's base address are zero. */
      mask = GENMASK_ULL(47, grainsizes[gran]);
      *ipa = (pfn_to_paddr(pte.walk.base) & mask) |

Cheers,

Signature · 1 line
Julien Grall