| 1 | |
| 2 | #include "test_util.h"
|
| 3 | #include "kvm_util.h"
|
| 4 | #include "processor.h"
|
| 5 | #include "vmx.h"
|
| 6 | #include "svm_util.h"
|
| 7 |
|
| 8 | |
| 9 | * Arbitrary, never shoved into KVM/hardware, just need to avoid conflict with
|
| 10 | * the "real" exceptions used, #SS/#GP/#DF (12/13/8).
|
| 11 |
|
| 12 | #define FAKE_TRIPLE_FAULT_VECTOR 0xaa
|
| 13 |
|
| 14 |
|
| 15 | #define SS_ERROR_CODE 0xdeadbeef
|
| 16 |
|
| 17 | |
| 18 | * Bit '0' is set on Intel if the exception occurs while delivering a previous
|
| 19 | * event/exception. AMD's wording is ambiguous, but presumably the bit is set
|
| 20 | * if the exception occurs while delivering an external event, e.g. NMI or INTR,
|
| 21 | * but not for exceptions that occur when delivering other exceptions or
|
| 22 | * software interrupts.
|
| 23 | *
|
| 24 | * Note, Intel's name for it, "External event", is misleading and much more
|
| 25 | * aligned with AMD's behavior, but the SDM is quite clear on its behavior.
|
| 26 |
|
| 27 | #define ERROR_CODE_EXT_FLAG BIT(0)
|
| 28 |
|
| 29 | |
| 30 | * Bit '1' is set if the fault occurred when looking up a descriptor in the
|
| 31 | * IDT, which is the case here as the IDT is empty/NULL.
|
| 32 |
|
| 33 | #define ERROR_CODE_IDT_FLAG BIT(1)
|
| 34 |
|
| 35 | |
| 36 | * The #GP that occurs when vectoring #SS should show the index into the IDT
|
| 37 | * for #SS, plus have the "IDT flag" set.
|
| 38 |
|
| 39 | #define GP_ERROR_CODE_AMD ((SS_VECTOR * 8) | ERROR_CODE_IDT_FLAG)
|
| 40 | #define GP_ERROR_CODE_INTEL ((SS_VECTOR * 8) | ERROR_CODE_IDT_FLAG | ERROR_CODE_EXT_FLAG)
|
| 41 |
|
| 42 | |
| 43 | * Intel and AMD both shove '0' into the error code on #DF, regardless of what
|
| 44 | * led to the double fault.
|
| 45 |
|
| 46 | #define DF_ERROR_CODE 0
|
| 47 |
|
| 48 | #define INTERCEPT_SS (BIT_ULL(SS_VECTOR))
|
| 49 | #define INTERCEPT_SS_DF (INTERCEPT_SS | BIT_ULL(DF_VECTOR))
|
| 50 | #define INTERCEPT_SS_GP_DF (INTERCEPT_SS_DF | BIT_ULL(GP_VECTOR))
|
| 51 |
|
| 52 | static void l2_ss_pending_test(void)
|
| 53 | {
|
| 54 | GUEST_SYNC(SS_VECTOR);
|
| 55 | }
|
| 56 |
|
| 57 | static void l2_ss_injected_gp_test(void)
|
| 58 | {
|
| 59 | GUEST_SYNC(GP_VECTOR);
|
| 60 | }
|
| 61 |
|
| 62 | static void l2_ss_injected_df_test(void)
|
| 63 | {
|
| 64 | GUEST_SYNC(DF_VECTOR);
|
| 65 | }
|
| 66 |
|
| 67 | static void l2_ss_injected_tf_test(void)
|
| 68 | {
|
| 69 | GUEST_SYNC(FAKE_TRIPLE_FAULT_VECTOR);
|
| 70 | }
|
| 71 |
|
| 72 | static void svm_run_l2(struct svm_test_data *svm, void *l2_code, int vector,
|
| 73 | u32 error_code)
|
| 74 | {
|
| 75 | struct vmcb *vmcb = svm->vmcb;
|
| 76 | struct vmcb_control_area *ctrl = &vmcb->control;
|
| 77 |
|
| 78 | vmcb->save.rip = (u64)l2_code;
|
| 79 | run_guest(vmcb, svm->vmcb_gpa);
|
| 80 |
|
| 81 | if (vector == FAKE_TRIPLE_FAULT_VECTOR)
|
| 82 | return;
|
| 83 |
|
| 84 | GUEST_ASSERT_EQ(ctrl->exit_code, (SVM_EXIT_EXCP_BASE + vector));
|
| 85 | GUEST_ASSERT_EQ(ctrl->exit_info_1, error_code);
|
| 86 | GUEST_ASSERT(!ctrl->int_state);
|
| 87 | }
|
| 88 |
|
| 89 | static void l1_svm_code(struct svm_test_data *svm)
|
| 90 | {
|
| 91 | struct vmcb_control_area *ctrl = &svm->vmcb->control;
|
| 92 |
|
| 93 | generic_svm_setup(svm, NULL);
|
| 94 | svm->vmcb->save.idtr.limit = 0;
|
| 95 | ctrl->intercept |= BIT_ULL(INTERCEPT_SHUTDOWN);
|
| 96 |
|
| 97 | ctrl->intercept_exceptions = INTERCEPT_SS_GP_DF;
|
| 98 | svm_run_l2(svm, l2_ss_pending_test, SS_VECTOR, SS_ERROR_CODE);
|
| 99 | svm_run_l2(svm, l2_ss_injected_gp_test, GP_VECTOR, GP_ERROR_CODE_AMD);
|
| 100 |
|
| 101 | ctrl->intercept_exceptions = INTERCEPT_SS_DF;
|
| 102 | svm_run_l2(svm, l2_ss_injected_df_test, DF_VECTOR, DF_ERROR_CODE);
|
| 103 |
|
| 104 | ctrl->intercept_exceptions = INTERCEPT_SS;
|
| 105 | svm_run_l2(svm, l2_ss_injected_tf_test, FAKE_TRIPLE_FAULT_VECTOR, 0);
|
| 106 | GUEST_ASSERT_EQ(ctrl->exit_code, SVM_EXIT_SHUTDOWN);
|
| 107 |
|
| 108 | GUEST_DONE();
|
| 109 | }
|
| 110 |
|
| 111 | static void vmx_run_l2(void *l2_code, int vector, u32 error_code)
|
| 112 | {
|
| 113 | GUEST_ASSERT(!vmwrite(GUEST_RIP, (u64)l2_code));
|
| 114 |
|
| 115 | GUEST_ASSERT_EQ(vector == SS_VECTOR ? vmlaunch() : vmresume(), 0);
|
| 116 |
|
| 117 | if (vector == FAKE_TRIPLE_FAULT_VECTOR)
|
| 118 | return;
|
| 119 |
|
| 120 | GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_EXCEPTION_NMI);
|
| 121 | GUEST_ASSERT_EQ((vmreadz(VM_EXIT_INTR_INFO) & 0xff), vector);
|
| 122 | GUEST_ASSERT_EQ(vmreadz(VM_EXIT_INTR_ERROR_CODE), error_code);
|
| 123 | GUEST_ASSERT(!vmreadz(GUEST_INTERRUPTIBILITY_INFO));
|
| 124 | }
|
| 125 |
|
| 126 | static void l1_vmx_code(struct vmx_pages *vmx)
|
| 127 | {
|
| 128 | GUEST_ASSERT_EQ(prepare_for_vmx_operation(vmx), true);
|
| 129 |
|
| 130 | GUEST_ASSERT_EQ(load_vmcs(vmx), true);
|
| 131 |
|
| 132 | prepare_vmcs(vmx, NULL);
|
| 133 | GUEST_ASSERT_EQ(vmwrite(GUEST_IDTR_LIMIT, 0), 0);
|
| 134 |
|
| 135 | |
| 136 | * VMX disallows injecting an exception with error_code[31:16] != 0,
|
| 137 | * and hardware will never generate a VM-Exit with bits 31:16 set.
|
| 138 | * KVM should likewise truncate the "bad" userspace value.
|
| 139 |
|
| 140 | GUEST_ASSERT_EQ(vmwrite(EXCEPTION_BITMAP, INTERCEPT_SS_GP_DF), 0);
|
| 141 | vmx_run_l2(l2_ss_pending_test, SS_VECTOR, (u16)SS_ERROR_CODE);
|
| 142 | vmx_run_l2(l2_ss_injected_gp_test, GP_VECTOR, GP_ERROR_CODE_INTEL);
|
| 143 |
|
| 144 | GUEST_ASSERT_EQ(vmwrite(EXCEPTION_BITMAP, INTERCEPT_SS_DF), 0);
|
| 145 | vmx_run_l2(l2_ss_injected_df_test, DF_VECTOR, DF_ERROR_CODE);
|
| 146 |
|
| 147 | GUEST_ASSERT_EQ(vmwrite(EXCEPTION_BITMAP, INTERCEPT_SS), 0);
|
| 148 | vmx_run_l2(l2_ss_injected_tf_test, FAKE_TRIPLE_FAULT_VECTOR, 0);
|
| 149 | GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_TRIPLE_FAULT);
|
| 150 |
|
| 151 | GUEST_DONE();
|
| 152 | }
|
| 153 |
|
| 154 | static void __attribute__((__flatten__)) l1_guest_code(void *test_data)
|
| 155 | {
|
| 156 | if (this_cpu_has(X86_FEATURE_SVM))
|
| 157 | l1_svm_code(test_data);
|
| 158 | else
|
| 159 | l1_vmx_code(test_data);
|
| 160 | }
|
| 161 |
|
| 162 | static void assert_ucall_vector(struct kvm_vcpu *vcpu, int vector)
|
| 163 | {
|
| 164 | struct ucall uc;
|
| 165 |
|
| 166 | TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
|
| 167 |
|
| 168 | switch (get_ucall(vcpu, &uc)) {
|
| 169 | case UCALL_SYNC:
|
| 170 | TEST_ASSERT(vector == uc.args[1],
|
| 171 | "Expected L2 to ask for %d, got %ld", vector, uc.args[1]);
|
| 172 | break;
|
| 173 | case UCALL_DONE:
|
| 174 | TEST_ASSERT(vector == -1,
|
| 175 | "Expected L2 to ask for %d, L2 says it's done", vector);
|
| 176 | break;
|
| 177 | case UCALL_ABORT:
|
| 178 | REPORT_GUEST_ASSERT(uc);
|
| 179 | break;
|
| 180 | default:
|
| 181 | TEST_FAIL("Expected L2 to ask for %d, got unexpected ucall %lu", vector, uc.cmd);
|
| 182 | }
|
| 183 | }
|
| 184 |
|
| 185 | static void queue_ss_exception(struct kvm_vcpu *vcpu, bool inject)
|
| 186 | {
|
| 187 | struct kvm_vcpu_events events;
|
| 188 |
|
| 189 | vcpu_events_get(vcpu, &events);
|
| 190 |
|
| 191 | TEST_ASSERT(!events.exception.pending,
|
| 192 | "Vector %d unexpectedlt pending", events.exception.nr);
|
| 193 | TEST_ASSERT(!events.exception.injected,
|
| 194 | "Vector %d unexpectedly injected", events.exception.nr);
|
| 195 |
|
| 196 | events.flags = KVM_VCPUEVENT_VALID_PAYLOAD;
|
| 197 | events.exception.pending = !inject;
|
| 198 | events.exception.injected = inject;
|
| 199 | events.exception.nr = SS_VECTOR;
|
| 200 | events.exception.has_error_code = true;
|
| 201 | events.exception.error_code = SS_ERROR_CODE;
|
| 202 | vcpu_events_set(vcpu, &events);
|
| 203 | }
|
| 204 |
|
| 205 | |
| 206 | * Verify KVM_{G,S}ET_EVENTS play nice with pending vs. injected exceptions
|
| 207 | * when an exception is being queued for L2. Specifically, verify that KVM
|
| 208 | * honors L1 exception intercept controls when a #SS is pending/injected,
|
| 209 | * triggers a #GP on vectoring the #SS, morphs to #DF if #GP isn't intercepted
|
| 210 | * by L1, and finally causes (nested) SHUTDOWN if #DF isn't intercepted by L1.
|
| 211 |
|
| 212 | int main(int argc, char *argv[])
|
| 213 | {
|
| 214 | gva_t nested_test_data_gva;
|
| 215 | struct kvm_vcpu_events events;
|
| 216 | struct kvm_vcpu *vcpu;
|
| 217 | struct kvm_vm *vm;
|
| 218 |
|
| 219 | TEST_REQUIRE(kvm_has_cap(KVM_CAP_EXCEPTION_PAYLOAD));
|
| 220 | TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX));
|
| 221 |
|
| 222 | vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
|
| 223 | vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, -2ul);
|
| 224 |
|
| 225 | if (kvm_cpu_has(X86_FEATURE_SVM))
|
| 226 | vcpu_alloc_svm(vm, &nested_test_data_gva);
|
| 227 | else
|
| 228 | vcpu_alloc_vmx(vm, &nested_test_data_gva);
|
| 229 |
|
| 230 | vcpu_args_set(vcpu, 1, nested_test_data_gva);
|
| 231 |
|
| 232 |
|
| 233 | vcpu_run(vcpu);
|
| 234 | assert_ucall_vector(vcpu, SS_VECTOR);
|
| 235 |
|
| 236 |
|
| 237 | queue_ss_exception(vcpu, false);
|
| 238 | vcpu->run->immediate_exit = true;
|
| 239 | vcpu_run_complete_io(vcpu);
|
| 240 |
|
| 241 |
|
| 242 | vcpu_events_get(vcpu, &events);
|
| 243 | TEST_ASSERT_EQ(events.flags & KVM_VCPUEVENT_VALID_PAYLOAD,
|
| 244 | KVM_VCPUEVENT_VALID_PAYLOAD);
|
| 245 | TEST_ASSERT_EQ(events.exception.pending, true);
|
| 246 | TEST_ASSERT_EQ(events.exception.nr, SS_VECTOR);
|
| 247 | TEST_ASSERT_EQ(events.exception.has_error_code, true);
|
| 248 | TEST_ASSERT_EQ(events.exception.error_code, SS_ERROR_CODE);
|
| 249 |
|
| 250 | |
| 251 | * Run for real with the pending #SS, L1 should get a VM-Exit due to
|
| 252 | * #SS interception and re-enter L2 to request #GP (via injected #SS).
|
| 253 |
|
| 254 | vcpu->run->immediate_exit = false;
|
| 255 | vcpu_run(vcpu);
|
| 256 | assert_ucall_vector(vcpu, GP_VECTOR);
|
| 257 |
|
| 258 | |
| 259 | * Inject #SS, the #SS should bypass interception and cause #GP, which
|
| 260 | * L1 should intercept before KVM morphs it to #DF. L1 should then
|
| 261 | * disable #GP interception and run L2 to request #DF (via #SS => #GP).
|
| 262 |
|
| 263 | queue_ss_exception(vcpu, true);
|
| 264 | vcpu_run(vcpu);
|
| 265 | assert_ucall_vector(vcpu, DF_VECTOR);
|
| 266 |
|
| 267 | |
| 268 | * Inject #SS, the #SS should bypass interception and cause #GP, which
|
| 269 | * L1 is no longer interception, and so should see a #DF VM-Exit. L1
|
| 270 | * should then signal that is done.
|
| 271 |
|
| 272 | queue_ss_exception(vcpu, true);
|
| 273 | vcpu_run(vcpu);
|
| 274 | assert_ucall_vector(vcpu, FAKE_TRIPLE_FAULT_VECTOR);
|
| 275 |
|
| 276 | |
| 277 | * Inject #SS yet again. L1 is not intercepting #GP or #DF, and so
|
| 278 | * should see nested TRIPLE_FAULT / SHUTDOWN.
|
| 279 |
|
| 280 | queue_ss_exception(vcpu, true);
|
| 281 | vcpu_run(vcpu);
|
| 282 | assert_ucall_vector(vcpu, -1);
|
| 283 |
|
| 284 | kvm_vm_free(vm);
|
| 285 | }
|