| // 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 <assert.h> |
| #include <cpuid.h> |
| #include <stdio.h> |
| #include <unittest/unittest.h> |
| #include <zircon/compiler.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/exception.h> |
| #include <zircon/syscalls/port.h> |
| #include <lib/zx/port.h> |
| #include <lib/zx/process.h> |
| #include <lib/zx/thread.h> |
| #include <lib/zx/time.h> |
| |
| 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<Instruction>(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<uint8_t*>(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<uintptr_t>(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 <Instruction isn> |
| bool umip_test() { |
| BEGIN_TEST; |
| |
| test_instruction(isn); |
| |
| END_TEST; |
| } |
| |
| } |
| |
| BEGIN_TEST_CASE(x86_umip_test) |
| RUN_TEST(umip_test<Instruction::SGDT>) |
| RUN_TEST(umip_test<Instruction::SIDT>) |
| RUN_TEST(umip_test<Instruction::SLDT>) |
| RUN_TEST(umip_test<Instruction::SMSW>) |
| RUN_TEST(umip_test<Instruction::STR>) |
| RUN_TEST(umip_test<Instruction::NOOP>) |
| RUN_TEST(umip_test<Instruction::MOV_NONCANON>) |
| END_TEST_CASE(x86_umip_test) |
| |
| int main(int argc, char** argv) { |
| return unittest_run_all_tests(argc, argv) ? 0 : -1; |
| } |