KVM: arm64: Trap guest MPAM accesses whenever MPAM is implemented
Fuad Tabba
reviewerPATCH v2 1/1 KVM: arm64: Trap guest MPAM accesses whenever MPAM is implemented
finalise_el2_state() clears the EL2 MPAM traps whenever the ID registers advertise MPAM, while KVM sets them only under ARM64_MPAM, which also requires MPAMEN. Without EL3 nothing sets that enable, so a guest reaches the MPAM registers while ID_AA64PFR0_EL1.MPAM reads 0 for it.
Gate the traps on the ID registers alone: MPAMEN isn't a term in any MPAM accessor, so they take effect without it. The cap tests the sanitised ID fields, which never exceed a CPU's own, so it's set only where finalise_el2_state already wrote MPAM2_EL2. MPAM3_EL3.TRAPLOWER is clear there, and arm64.nompam turns the cap off too.
---
Notes:
Changes since v1:
- Commit message only. The code is unchanged, so Yuan Yao's Reviewed-by is carried.
- Fixes: names the commit that cleared the traps, rather than the one that added KVM's trapping.
- Says why the sanitised ID fields are the safe gate: the cap is set only where finalise_el2_state already wrote MPAM2_EL2.
Not covered, and answered on the v1 thread: a machine whose CPUs differ on MPAM, raised by Ben and by sashiko.
v1: https://lore.kernel.org/all/[email protected]/
arch/arm64/include/asm/cpufeature.h | 5 +++++ arch/arm64/kernel/cpufeature.c | 13 +++++++++++++ arch/arm64/kvm/hyp/include/hyp/switch.h | 4 ++-- arch/arm64/tools/cpucaps | 1 + 4 files changed, 21 insertions(+), 2 deletions(-)
arch/arm64/include/asm/cpufeature.h +5 -0
@@ -873,6 +873,11 @@ static __always_inline bool system_supports_mpam_hcr(void)return alternative_has_cap_unlikely (ARM64_MPAM_HCR ); } +static __always_inline bool system_supports_mpam_sysregs(void) +{ +return alternative_has_cap_unlikely (ARM64_MPAM_SYSREGS ); +} +static inline bool system_supports_pmuv3 (void ) {return cpus_have_final_cap (ARM64_HAS_PMUV3 );
arch/arm64/kernel/cpufeature.c +13 -0
@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)return (read_sysreg_s (SYS_MPAM1_EL1 ) &MPAM1_EL1_MPAMEN ); } +static bool +test_has_mpam_sysregs (const struct arm64_cpu_capabilities *entry,int __unused) +{ +/* The registers exist whether or not firmware enabled MPAM. */ +return detect_ftr_has_mpam (); +} +static void cpu_enable_mpam (const struct arm64_cpu_capabilities *entry) {
@@ -3116,6 +3123,12 @@ static const struct arm64_cpu_capabilities arm64_features[] = { .matches = test_has_mpam, .cpu_enable = cpu_enable_mpam, }, + { + .desc = "Memory Partitioning And Monitoring system registers", + .type = ARM64_CPUCAP_SYSTEM_FEATURE, + .capability = ARM64_MPAM_SYSREGS, + .matches = test_has_mpam_sysregs, + }, { .desc = "Memory Partitioning And Monitoring Virtualisation", .type = ARM64_CPUCAP_SYSTEM_FEATURE,
arch/arm64/kvm/hyp/include/hyp/switch.h +2 -2
@@ -298,7 +298,7 @@ static inline void __activate_traps_mpam(struct kvm_vcpu *vcpu)u64 clr = MPAM2_EL2_EnMPAMSM;u64 set =MPAM2_EL2_TRAPMPAM0EL1 |MPAM2_EL2_TRAPMPAM1EL1 ; -if (!system_supports_mpam ()) +if (!system_supports_mpam_sysregs ())return ;/* trap guest access to MPAMIDR_EL1 */
@@ -317,7 +317,7 @@ static inline void __deactivate_traps_mpam(void)u64 clr =MPAM2_EL2_TRAPMPAM0EL1 |MPAM2_EL2_TRAPMPAM1EL1 |MPAM2_EL2_TIDR ;u64 set = MPAM2_EL2_EnMPAMSM; -if (!system_supports_mpam ()) +if (!system_supports_mpam_sysregs ())return ;sysreg_clear_set_s (SYS_MPAM2_EL2 , clr, set);
arch/arm64/tools/cpucaps +1 -0
@@ -78,6 +78,7 @@ KVM_PROTECTED_MODE MISMATCHED_CACHE_TYPE MPAM MPAM_HCR +MPAM_SYSREGS MTE MTE_ASYMM MTE_FAR
Signature · 1 line
2.39.5
[email protected]
AI reviewThank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Guests can bypass EL2 traps and directly access physical MPAM registers if the host has asymmetric CPUs or boots with arm64.nompam. --
commit e23ed2fd702c6c43a8b21feffe356931e0f7eb5a
Author: Fuad Tabba <[email protected]>
KVM: arm64: Trap guest MPAM accesses whenever MPAM is implemented
This commit modifies the EL2 MPAM trap activation to gate on the sanitized ID registers rather than the MPAMEN bit. This ensures that guest MPAM accesses are trapped whenever MPAM registers are implemented, even if firmware hasn't enabled MPAM.
Quoted 4 lines
arch/arm64/kvm/hyp/include/hyp/switch.h +0
-0
[ ... ]
arch/arm64/kvm/hyp/include/hyp/switch.h static inline void __activate_traps_mpam(struct kvm_vcpu *vcpu) · line 301
Quoted 6 lines
arch/arm64/kvm/hyp/include/hyp/switch.h +1
-1@@ -298,7 +298,7 @@ static inline void __activate_traps_mpam(struct kvm_vcpu *vcpu)u64 clr = MPAM2_EL2_EnMPAMSM;u64 set =MPAM2_EL2_TRAPMPAM0EL1 |MPAM2_EL2_TRAPMPAM1EL1 ; -if (!system_supports_mpam ()) +if (!system_supports_mpam_sysregs ())
[Severity: High]
This is a pre-existing issue, but does gating this early return on the sanitized system-wide ID registers allow the guest direct access to physical MPAM registers in asymmetric or overridden configurations?
During early boot, head.S (in finalise_el2_state) reads the physical CPU ID registers and clears the EL2 MPAM traps. If the system has asymmetric MPAM CPUs, or if the kernel is booted with arm64.nompam, system_supports_mpam_sysregs() evaluates to false.
When that happens, __activate_traps_mpam() takes this early return and skips setting TRAPMPAM0EL1 and TRAPMPAM1EL1. Since the hardware traps were already cleared by head.S (or left clear by firmware), the guest's MPAM register accesses execute natively without trapping to EL2. This could allow an untrusted guest to manipulate host cache and memory bandwidth partitioning.
This patch swaps one system-wide capability (ARM64_MPAM) for another (ARM64_MPAM_SYSREGS), which inherits the exact same bypass. Should KVM base its trap activation on the physical ID registers to safely isolate the guest instead?
Quoted 3 lines
arch/arm64/kvm/hyp/include/hyp/switch.h +0
-0return ;/* trap guest access to MPAMIDR_EL1 */
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Will Deacon
maintainerQuoted 72 lines · Fuad Tabba
finalise_el2_state()clears the EL2 MPAM traps whenever the ID registers advertise MPAM, while KVM sets them only underARM64_MPAM, which also requires MPAMEN. Without EL3 nothing sets that enable, so a guest reaches the MPAM registers while ID_AA64PFR0_EL1.MPAM reads 0 for it.Gate the traps on the ID registers alone: MPAMEN isn't a term in any MPAM accessor, so they take effect without it. The cap tests the sanitised ID fields, which never exceed a CPU's own, so it's set only where
finalise_el2_statealready wroteMPAM2_EL2. MPAM3_EL3.TRAPLOWER is clear there, and arm64.nompam turns the cap off too.Fixes 23b33d1e168c ("arm64: head.S: Initialise MPAM EL2 registers and disable traps")Reviewed-by Yuan Yao <[email protected]>Signed-off-by Fuad Tabba <[email protected]>---
Notes:
Changes since v1:
- Commit message only. The code is unchanged, so Yuan Yao's Reviewed-by is carried.
- Fixes: names the commit that cleared the traps, rather than the one that added KVM's trapping.
- Says why the sanitised ID fields are the safe gate: the cap is set only wherefinalise_el2_statealready wroteMPAM2_EL2.Not covered, and answered on the v1 thread: a machine whose CPUs differ on MPAM, raised by Ben and by sashiko.
v1: https://lore.kernel.org/all/[email protected]/
arch/arm64/include/asm/cpufeature.h | 5 +++++ arch/arm64/kernel/cpufeature.c | 13 +++++++++++++ arch/arm64/kvm/hyp/include/hyp/switch.h | 4 ++-- arch/arm64/tools/cpucaps | 1 + 4 files changed, 21 insertions(+), 2 deletions(-)arch/arm64/include/asm/cpufeature.h +5
-0@@ -873,6 +873,11 @@ static __always_inline bool system_supports_mpam_hcr(void)return alternative_has_cap_unlikely (ARM64_MPAM_HCR ); } +static __always_inline bool system_supports_mpam_sysregs(void) +{ +return alternative_has_cap_unlikely (ARM64_MPAM_SYSREGS ); +} +static inline bool system_supports_pmuv3 (void ) {return cpus_have_final_cap (ARM64_HAS_PMUV3 );arch/arm64/kernel/cpufeature.c +7
-0@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)return (read_sysreg_s (SYS_MPAM1_EL1 ) &MPAM1_EL1_MPAMEN ); } +static bool +test_has_mpam_sysregs (const struct arm64_cpu_capabilities *entry,int __unused) +{ +/* The registers exist whether or not firmware enabled MPAM. */ +return detect_ftr_has_mpam (); +} +static void cpu_enable_mpam (const struct arm64_cpu_capabilities *entry) {
arch/arm64/kernel/cpufeature.c static const struct arm64_cpu_capabilities arm64_features[] = { · line 3131
Quoted 9 lines
arch/arm64/kernel/cpufeature.c +6
-0@@ -3116,6 +3123,12 @@ static const struct arm64_cpu_capabilities arm64_features[] = { .matches = test_has_mpam, .cpu_enable = cpu_enable_mpam, }, + { + .desc = "Memory Partitioning And Monitoring system registers", + .type = ARM64_CPUCAP_SYSTEM_FEATURE, + .capability = ARM64_MPAM_SYSREGS, + .matches = test_has_mpam_sysregs, + },
We already have two system capabilities for MPAM so I'm not overly keen to add a third, especially as the MPAM code is largely confined to the resctrl driver. In fact, this feels a bit similar to things like TRBE and SPE in the sense that (a) we have to probe it per-cpu (b) it can be disabled by a higher EL and (c) most of the code is in a driver, but KVM needs to know how to switch it.
Neither TRBE nor SPE need entries in arm64_features[] at all, so something isn't right here...
Will
Fuad Tabba
reviewerOn Thu, 10 Sept 2026 at 11:40, Will Deacon <[email protected]> wrote:
...
Quoted 28 lines
Quoted 11 lines
arch/arm64/kernel/cpufeature.c +7
-0+static bool +test_has_mpam_sysregs (const struct arm64_cpu_capabilities *entry,int __unused) +{ +/* The registers exist whether or not firmware enabled MPAM. */ +return detect_ftr_has_mpam (); +} +static void cpu_enable_mpam (const struct arm64_cpu_capabilities *entry) {arch/arm64/kernel/cpufeature.c static const struct arm64_cpu_capabilities arm64_features[] = { · line 3131
Quoted 9 lines
arch/arm64/kernel/cpufeature.c +6
-0@@ -3116,6 +3123,12 @@ static const struct arm64_cpu_capabilities arm64_features[] = { .matches = test_has_mpam, .cpu_enable = cpu_enable_mpam, }, + { + .desc = "Memory Partitioning And Monitoring system registers", + .type = ARM64_CPUCAP_SYSTEM_FEATURE, + .capability = ARM64_MPAM_SYSREGS, + .matches = test_has_mpam_sysregs, + },We already have two system capabilities for MPAM so I'm not overly keen to add a third, especially as the MPAM code is largely confined to the resctrl driver. In fact, this feels a bit similar to things like TRBE and SPE in the sense that (a) we have to probe it per-cpu (b) it can be disabled by a higher EL and (c) most of the code is in a driver, but KVM needs to know how to switch it.
Quoted 2 lines
Neither TRBE nor SPE need entries in arm64_features[] at all, so something isn't right here...
I think I've overthunk it. The problem isn't in what the host is doing, but that KVM is basing its traps on whether MPAM support exists and whether that support is enabled.
V3 should be more in line with what you're suggesting...
Cheers,
/fuad
Quoted 2 lines
Will
Marc Zyngier
maintainerQuoted 43 lines · Fuad Tabba
finalise_el2_state()clears the EL2 MPAM traps whenever the ID registers advertise MPAM, while KVM sets them only underARM64_MPAM, which also requires MPAMEN. Without EL3 nothing sets that enable, so a guest reaches the MPAM registers while ID_AA64PFR0_EL1.MPAM reads 0 for it.Gate the traps on the ID registers alone: MPAMEN isn't a term in any MPAM accessor, so they take effect without it. The cap tests the sanitised ID fields, which never exceed a CPU's own, so it's set only where
finalise_el2_statealready wroteMPAM2_EL2. MPAM3_EL3.TRAPLOWER is clear there, and arm64.nompam turns the cap off too.Fixes 23b33d1e168c ("arm64: head.S: Initialise MPAM EL2 registers and disable traps")Reviewed-by Yuan Yao <[email protected]>Signed-off-by Fuad Tabba <[email protected]>---
Notes:
Changes since v1:
- Commit message only. The code is unchanged, so Yuan Yao's Reviewed-by is carried.
- Fixes: names the commit that cleared the traps, rather than the one that added KVM's trapping.
- Says why the sanitised ID fields are the safe gate: the cap is set only wherefinalise_el2_statealready wroteMPAM2_EL2.Not covered, and answered on the v1 thread: a machine whose CPUs differ on MPAM, raised by Ben and by sashiko.
v1: https://lore.kernel.org/all/[email protected]/
arch/arm64/include/asm/cpufeature.h | 5 +++++ arch/arm64/kernel/cpufeature.c | 13 +++++++++++++ arch/arm64/kvm/hyp/include/hyp/switch.h | 4 ++-- arch/arm64/tools/cpucaps | 1 + 4 files changed, 21 insertions(+), 2 deletions(-)arch/arm64/include/asm/cpufeature.h +0
-0
arch/arm64/include/asm/cpufeature.h static __always_inline bool system_supports_mpam_hcr(void) · line 880
Quoted 8 lines
arch/arm64/include/asm/cpufeature.h +5
-0@@ -873,6 +873,11 @@ static __always_inline bool system_supports_mpam_hcr(void)return alternative_has_cap_unlikely (ARM64_MPAM_HCR ); } +static __always_inline bool system_supports_mpam_sysregs(void) +{ +return alternative_has_cap_unlikely (ARM64_MPAM_SYSREGS ); +} +
What is the rationale for choosing alternative_has_cap_unlikely() instead of cpus_have_final_cap(), which is more wildly used?
M.
Signature · 1 line
Without deviation from the norm, progress is not possible.
Fuad Tabba
reviewerOn Thu, 10 Sept 2026 at 12:08, Marc Zyngier <[email protected]> wrote:
...
Quoted 6 lines
Quoted 6 lines
arch/arm64/include/asm/cpufeature.h +0
-0@@ -873,6 +873,11 @@ static __always_inline bool system_supports_mpam_hcr(void)return alternative_has_cap_unlikely (ARM64_MPAM_HCR ); }
Quoted 8 lines
arch/arm64/include/asm/cpufeature.h static __always_inline bool system_supports_mpam_hcr(void) · line 880
Quoted 5 lines
arch/arm64/include/asm/cpufeature.h +5
-0+static __always_inline bool system_supports_mpam_sysregs(void) +{ +return alternative_has_cap_unlikely (ARM64_MPAM_SYSREGS ); +} +What is the rationale for choosing
alternative_has_cap_unlikely()instead ofcpus_have_final_cap(), which is more wildly used?
Not a good one to be honest, and it's gone in V3.
Cheers,
/fuad
Quoted 5 lines
M.--
Without deviation from the norm, progress is not possible.