kvm_arm_set_nr_counters() updates MDCR_EL2.HPMN for every vCPU while holding kvm->arch.config_lock. However, KVM_SET_ONE_REG currently writes MDCR_EL2 through the generic sysreg path without taking the same lock. Concurrent PMU configuration and register restore can therefore race and lose updates to unrelated MDCR_EL2 bits. Add explicit userspace accessors for MDCR_EL2. Serialize them with config_lock so whole-register userspace writes cannot race with HPMN rewrites, reject HPMN values above the configured PMU counter count, and request a PMU reload when HPME changes to match guest trap behavior. Fixes: c8823e51b534 ("KVM: arm64: Fix MDCR_EL2.HPMN reset value") Closes: https://sashiko.dev/#/patchset/20260706-hybrid-v8-0-de459617b59d%40rsg.ci.i.u-tokyo.ac.jp?part=6 Assisted-by: Codex:gpt-5.5 Signed-off-by: Akihiko Odaki --- arch/arm64/kvm/sys_regs.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c index d217530359ba..2b2ea33159e9 100644 --- a/arch/arm64/kvm/sys_regs.c +++ b/arch/arm64/kvm/sys_regs.c @@ -2949,6 +2949,42 @@ static bool access_mdcr(struct kvm_vcpu *vcpu, return true; } +static int get_mdcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd, + u64 *val) +{ + struct kvm *kvm = vcpu->kvm; + + guard(mutex)(&kvm->arch.config_lock); + + *val = __vcpu_sys_reg(vcpu, MDCR_EL2); + + return 0; +} + +static int set_mdcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd, + u64 val) +{ + struct kvm *kvm = vcpu->kvm; + u64 old, hpmn = FIELD_GET(MDCR_EL2_HPMN, val); + + guard(mutex)(&kvm->arch.config_lock); + + if (hpmn > vcpu->kvm->arch.nr_pmu_counters) + return -EINVAL; + + old = __vcpu_sys_reg(vcpu, MDCR_EL2); + __vcpu_assign_sys_reg(vcpu, MDCR_EL2, val); + + /* + * Request a reload of the PMU to enable/disable the counters + * affected by HPME. + */ + if ((old ^ val) & MDCR_EL2_HPME) + kvm_make_request(KVM_REQ_RELOAD_PMU, vcpu); + + return 0; +} + static bool access_ras(struct kvm_vcpu *vcpu, struct sys_reg_params *p, const struct sys_reg_desc *r) @@ -3652,7 +3688,8 @@ static const struct sys_reg_desc sys_reg_descs[] = { EL2_REG_FILTERED(SCTLR2_EL2, access_vm_reg, reset_val, 0, sctlr2_el2_visibility), EL2_REG_VNCR(HCR_EL2, reset_hcr, 0), - EL2_REG(MDCR_EL2, access_mdcr, reset_mdcr, 0), + SYS_REG_USER_FILTER(MDCR_EL2, access_mdcr, reset_mdcr, 0, + get_mdcr, set_mdcr, el2_visibility), EL2_REG(CPTR_EL2, access_rw, reset_val, CPTR_NVHE_EL2_RES1), EL2_REG_VNCR(HSTR_EL2, reset_val, 0), EL2_REG_VNCR_FILT(HFGRTR_EL2, fgt_visibility), -- 2.55.0