xen/arm: guest_walk: refuse a block descriptor where the granule has none
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.
--- xen/arch/arm/guest_walk.c | 9 +++++++++ 1 file changed, 9 insertions(+)
xen/arch/arm/guest_walk.c +9 -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). +*/ +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
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.
--- xen/arch/arm/guest_walk.c | 9 +++++++++ 1 file changed, 9 insertions(+)
xen/arch/arm/guest_walk.c +9 -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). +*/ + mask =GENMASK_ULL (topbit, input_size); +if ( (gva & mask) != ((gva &BIT (topbit,ULL )) ? mask :0 ) ) +return false; }else {
Signature · 1 line
2.43.0
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")Signed-off-by Stefano Stabellini <[email protected]>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
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
PATCH 3/4 xen/arm: traps: enter the SP_EL0 vector group for an exception at EL1t
In reply to Stefano StabelliniAn 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.
--- xen/arch/arm/traps.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
xen/arch/arm/traps.c +7 -1
@@ -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
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_EL0vector group (offset0x000) when the guest was at EL1t, and to theSP_ELxgroup (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()used0x200for both.Signed-off-by Stefano Stabellini <[email protected]>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
PATCH 4/4 vpci/header: empty the BAR range sets when modify_bars() fails
In reply to Stefano StabelliniOn 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.
--- xen/drivers/vpci/header.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-)
xen/drivers/vpci/header.c +19 -6
@@ -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
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")Signed-off-by Stefano Stabellini <[email protected]>
Jan
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")Signed-off-by Stefano Stabellini <[email protected]>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
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