// Copyright 2018 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include #include enum class Instruction { SGDT, SIDT, SLDT, STR, SMSW, NOOP, // Used to ensure harness does not always report failure MOV_NONCANON, // Used to ensure harness does not always report success }; namespace { bool is_umip_supported() { uint32_t eax, ebx, ecx, edx; if (__get_cpuid(7, &eax, &ebx, &ecx, &edx) != 1) { return false; } return ecx & (1u << 2); } // If this returns true, the instruction is expected to cause a #GP if it // is executed. bool isn_should_crash(Instruction isn) { switch (isn) { case Instruction::SGDT: case Instruction::SIDT: case Instruction::SLDT: case Instruction::STR: case Instruction::SMSW: // If UMIP is supported, the kernel should have turned it on. return is_umip_supported(); case Instruction::NOOP: return false; case Instruction::MOV_NONCANON: return true; } __builtin_trap(); } void isn_thread_func(uintptr_t raw_isn, uintptr_t unused) { Instruction isn = static_cast(raw_isn); alignas(16) static uint8_t scratch_buf[16]; switch (isn) { case Instruction::SGDT: __asm__ volatile ("sgdt %0" : "=m"(*scratch_buf)); break; case Instruction::SIDT: __asm__ volatile ("sidt %0" : "=m"(*scratch_buf)); break; case Instruction::SLDT: __asm__ volatile ("sldt %0" : "=m"(*scratch_buf)); break; case Instruction::STR: __asm__ volatile ("str %0" : "=m"(*scratch_buf)); break; case Instruction::SMSW: uint64_t msw; __asm__ volatile ("smsw %0" : "=r"(msw)); break; case Instruction::NOOP: __asm__ volatile ("nop"); break; case Instruction::MOV_NONCANON: // We use a non-canonical address in order to produce a #GP, which we // specifically want to test (as opposed to other fault types such as // page faults). uint8_t* v = reinterpret_cast(1ULL << 63); __asm__ volatile ("movq $0, %0" : "=m"(*v)); break; } zx_thread_exit(); } bool test_instruction(Instruction isn) { BEGIN_HELPER; zx::thread thread; ASSERT_EQ(zx::thread::create(*zx::process::self(), "isn_probe", 9u, 0u, &thread), ZX_OK); alignas(16) static uint8_t thread_stack[128]; void* stack_top = thread_stack + sizeof(thread_stack); zx::port port; ASSERT_EQ(zx::port::create(0, &port), ZX_OK); ASSERT_EQ(thread.wait_async(port, 0, ZX_THREAD_TERMINATED, ZX_WAIT_ASYNC_ONCE), ZX_OK); ASSERT_EQ(zx_task_bind_exception_port(thread.get(), port.get(), 0, 0), ZX_OK); ASSERT_EQ(thread.start(&isn_thread_func, stack_top, static_cast(isn), 0), ZX_OK); // Wait for crash or thread completion. zx_port_packet_t packet; while (port.wait(zx::time::infinite(), &packet) == ZX_OK) { if (ZX_PKT_IS_EXCEPTION(packet.type)) { zx_exception_report_t report; ASSERT_EQ(thread.get_info(ZX_INFO_THREAD_EXCEPTION_REPORT, &report, sizeof(report), NULL, NULL), ZX_OK); ASSERT_EQ(thread.kill(), ZX_OK); ASSERT_TRUE(isn_should_crash(isn)); // These instructions should cause a GPF ASSERT_EQ(report.header.type, ZX_EXCP_GENERAL); break; } else if (ZX_PKT_IS_SIGNAL_ONE(packet.type)) { if (packet.signal.observed & ZX_THREAD_TERMINATED) { // Thread terminated normally so the instruction did not crash ASSERT_FALSE(isn_should_crash(isn)); break; } } } END_HELPER; } template bool umip_test() { BEGIN_TEST; test_instruction(isn); END_TEST; } } BEGIN_TEST_CASE(x86_umip_test) RUN_TEST(umip_test) RUN_TEST(umip_test) RUN_TEST(umip_test) RUN_TEST(umip_test) RUN_TEST(umip_test) RUN_TEST(umip_test) RUN_TEST(umip_test) END_TEST_CASE(x86_umip_test) int main(int argc, char** argv) { return unittest_run_all_tests(argc, argv) ? 0 : -1; }