Named CPU models zero the ID registers QEMU knows about and then apply
SYSREG_ properties. However, the host and KVM might expose ID
registers that QEMU does not know about yet. In this scenario, named
CPU models might
leak host features to the guest.
Part of the problem can be addressed because the Arm Architecture
Reference Manual specifies that unused encodings in the space (op1==0,
CRn==0, CRm=={2-7}) may be treated as RES0. This range includes all
ID_AA64* registers.
Error out if KVM returns a non-zero value for a register in the above
space which QEMU does not know about yet.
get_sysreg_idx() now returns -1 for encodings that are not known ID
registers, and kvm_arm_read_sys_reg64() exposes a KVM GET_ONE_REG
read for this probe.
Signed-off-by: Khushit Shah <[email protected]>
---
target/arm/cpu-models.c | 74 ++++++++++++++++++++++++++++++++++++++++
target/arm/cpu-sysregs.h | 2 +-
target/arm/cpu64.c | 3 +-
target/arm/kvm-stub.c | 7 ++++
target/arm/kvm.c | 7 ++++
target/arm/kvm_arm.h | 12 +++++++
6 files changed, 103 insertions(+), 2 deletions(-)
diff --git a/target/arm/cpu-models.c b/target/arm/cpu-models.c
index 4e92909755..855eb5ee6b 100644
--- a/target/arm/cpu-models.c
+++ b/target/arm/cpu-models.c
@@ -362,6 +362,78 @@ static void reset_res1_fields(ARMCPU *cpu)
}
}
+/*
+ * From ARM ARM:
+ *
+ * For unused encodings in the part of the space that has op1==0 and
+ * CRm=={2-7}, direct reads that are not trapped behave as Reserved,
+ * RES0 to ensure correct behavior if the encodings are used for ID
+ * registers in the future.
+ *
+ * It includes every ID_AA64* register.
+ *
+ * For named CPU models, we can check if any of the unknown ID registers
+ * that fall into this space is non-zero on the host. If so, the named CPU
+ * model leaks those host features. Error out in such case.
+ *
+ * We cannot do the same for the remaining Feature ID space (op1==0 and
+ * CRm==0, or op1=={1,3}). Unused encodings there are UNDEFINED. The
+ * implemented registers in that space (MIDR_EL1, CTR_EL0, DCZID_EL0,
+ * CCSIDR_EL1, ...) are identification or cache geometry.
+ */
+static void arm_named_cpu_zero_unknown_feature_ids(ARMCPU *cpu, Error **errp)
+{
+ uint32_t init_features0 = 0;
+ static bool checked;
+ uint32_t target;
+ int fdarray[3];
+ int crm, op2;
+ int fd;
+
+ if (checked) {
+ return;
+ }
+
+ if (!kvm_arm_create_scratch_max_host_vcpu(fdarray, &init_features0,
+ &target, errp)) {
+ return;
+ }
+
+ fd = fdarray[2];
+ for (crm = 2; crm <= 7; crm++) {
+ for (op2 = 0; op2 <= 7; op2++) {
+ uint64_t val = 0;
+ int ret;
+
+ if (get_sysreg_idx(ENCODE_ID_REG(3, 0, 0, crm, op2)) != -1) {
+ continue;
+ }
+
+ ret = kvm_arm_read_sys_reg64(fd, &val, 3, 0, 0, crm, op2);
+ if (ret) {
+ continue;
+ }
+
+ /*
+ * TODO: if this encoding is writable, try to write 0?
+ */
+ if (val) {
+ error_setg(errp,
+ "unknown Feature ID register "
+ "(op0=3, op1=0, CRn=0, CRm=%d, op2=%d) "
+ "reads as 0x%" PRIx64 " on the host; named CPU "
+ "models cannot hide it",
+ crm, op2, val);
+ kvm_arm_destroy_scratch_host_vcpu(fdarray);
+ return;
+ }
+ }
+ }
+
+ kvm_arm_destroy_scratch_host_vcpu(fdarray);
+ checked = true;
+}
+
static void arm_named_cpu_initfn(Object *obj)
{
ARMCPU *cpu = ARM_CPU(obj);
@@ -401,6 +473,8 @@ static void arm_named_cpu_initfn(Object *obj)
/* 3. Realize the model chain. */
arm_realize_model_chain(obj, model, &error_abort);
+
+ arm_named_cpu_zero_unknown_feature_ids(cpu, &error_abort);
}
void arm_register_named_cpu_models(void)
diff --git a/target/arm/cpu-sysregs.h b/target/arm/cpu-sysregs.h
index 7877a3b06a..d48b44705f 100644
--- a/target/arm/cpu-sysregs.h
+++ b/target/arm/cpu-sysregs.h
@@ -37,6 +37,6 @@ typedef enum ARMSysRegs {
extern const uint32_t id_register_sysreg[NUM_ID_IDX];
-int get_sysreg_idx(ARMSysRegs sysreg);
+int get_sysreg_idx(ARMSysRegs sysreg); /* -1 if @sysreg is not a known ID
register */
#endif /* ARM_CPU_SYSREGS_H */
diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c
index a0114b0fa2..3cc698d65a 100644
--- a/target/arm/cpu64.c
+++ b/target/arm/cpu64.c
@@ -57,8 +57,9 @@ int get_sysreg_idx(ARMSysRegs sysreg)
{
switch (sysreg) {
#include "cpu-sysregs.h.inc"
+ default:
+ return -1;
}
- g_assert_not_reached();
}
#undef DEF
diff --git a/target/arm/kvm-stub.c b/target/arm/kvm-stub.c
index eae2e1ec2c..d263adc921 100644
--- a/target/arm/kvm-stub.c
+++ b/target/arm/kvm-stub.c
@@ -50,6 +50,13 @@ void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu)
g_assert_not_reached();
}
+int kvm_arm_read_sys_reg64(int fd, uint64_t *val,
+ uint8_t op0, uint8_t op1,
+ uint8_t crn, uint8_t crm, uint8_t op2)
+{
+ g_assert_not_reached();
+}
+
void kvm_arm_add_vcpu_properties(ARMCPU *cpu)
{
g_assert_not_reached();
diff --git a/target/arm/kvm.c b/target/arm/kvm.c
index 70d9a07580..6f704c0509 100644
--- a/target/arm/kvm.c
+++ b/target/arm/kvm.c
@@ -263,6 +263,13 @@ static int read_sys_reg64(int fd, uint64_t *pret, uint64_t
id)
return ioctl(fd, KVM_GET_ONE_REG, &idreg);
}
+int kvm_arm_read_sys_reg64(int fd, uint64_t *val,
+ uint8_t op0, uint8_t op1,
+ uint8_t crn, uint8_t crm, uint8_t op2)
+{
+ return read_sys_reg64(fd, val, ARM64_SYS_REG(op0, op1, crn, crm, op2));
+}
+
static uint64_t idregs_sysreg_to_kvm_reg(ARMSysRegs sysreg)
{
return ARM64_SYS_REG((sysreg & CP_REG_ARM64_SYSREG_OP0_MASK) >>
CP_REG_ARM64_SYSREG_OP0_SHIFT,
diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h
index 997120966f..4e563fab96 100644
--- a/target/arm/kvm_arm.h
+++ b/target/arm/kvm_arm.h
@@ -154,6 +154,18 @@ bool kvm_arm_create_scratch_max_host_vcpu(int *fdarray,
*/
void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu);
+/**
+ * kvm_arm_read_sys_reg64:
+ * @fd: vCPU fd
+ * @val: filled with the register value on success
+ * @op0/@op1/@crn/@crm/@op2: System ID register encoding
+ *
+ * Returns: 0 on success, -1 on failure.
+ */
+int kvm_arm_read_sys_reg64(int fd, uint64_t *val,
+ uint8_t op0, uint8_t op1,
+ uint8_t crn, uint8_t crm, uint8_t op2);
+
/**
* kvm_arm_add_vcpu_properties:
* @cpu: The CPU object to add the properties to
--
2.52.0