blob: 9235cf6e4514b734ebdbe828909ad24ec0a98eaa [file] [log] [blame]
// 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;
}