mirror of
https://github.com/Bareflank/hypervisor
synced 2026-08-17 06:23:04 -04:00
This patch: - Fixes some bugs with Windows. It is working again - Fixes a long-time issue with Linux on some Intel CPUs that would cause any attempt to load the driver to crash due to the GDT being marked as read only, and the Linux APIs for this will triple fault your system if these APIs are not used very quickly (a bug that probably should be reported to Linux). - Fixes issues with how the fail logic worked. You can not fail inside the fail handler. New integration tests were added to ensure that this case works as expected. In general, the hypervisor has less assembly, and should be more robust as a result. - Fixes how TLB flushing is done, and adds TLB flushing APIs. A future PR will use this code to implement free_page and free_huge. - Continued work on removing the heap logic that is no longer supported.
263 lines
11 KiB
C++
263 lines
11 KiB
C++
/// @copyright
|
|
/// Copyright (C) 2020 Assured Information Security, Inc.
|
|
///
|
|
/// @copyright
|
|
/// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
/// of this software and associated documentation files (the "Software"), to deal
|
|
/// in the Software without restriction, including without limitation the rights
|
|
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
/// copies of the Software, and to permit persons to whom the Software is
|
|
/// furnished to do so, subject to the following conditions:
|
|
///
|
|
/// @copyright
|
|
/// The above copyright notice and this permission notice shall be included in
|
|
/// all copies or substantial portions of the Software.
|
|
///
|
|
/// @copyright
|
|
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
/// SOFTWARE.
|
|
|
|
#include <bf_control_ops.hpp>
|
|
#include <bf_syscall_t.hpp>
|
|
#include <dispatch_bootstrap.hpp>
|
|
#include <dispatch_fail.hpp>
|
|
#include <dispatch_vmexit.hpp>
|
|
#include <gs_initialize.hpp>
|
|
#include <gs_t.hpp>
|
|
#include <integration_utils.hpp>
|
|
#include <intrinsic_t.hpp>
|
|
#include <tls_t.hpp>
|
|
#include <vp_pool_t.hpp>
|
|
#include <vs_pool_t.hpp>
|
|
|
|
#include <bsl/convert.hpp>
|
|
#include <bsl/debug.hpp>
|
|
#include <bsl/errc_type.hpp>
|
|
#include <bsl/safe_integral.hpp>
|
|
#include <bsl/unlikely.hpp>
|
|
|
|
namespace syscall
|
|
{
|
|
/// NOTE:
|
|
/// - This is where we store all of our global and thread local variables.
|
|
/// All of the variables are marked as static to ensure they are not
|
|
/// visable to the rest of the code.
|
|
/// - All global and thread local variables must be passed around from
|
|
/// function to function as needed. This ensures that constexpr unit
|
|
/// tests work properly as the rest of the code never relies on global
|
|
/// variables. In addition, it dramatically simplifies unit testing, so
|
|
/// enforcing this coding style, although annoying for the function
|
|
/// signatures, makes working with the rest of the code a lot easier.
|
|
/// - We use constinit here, which works around a specific AUTOSAR rule
|
|
/// that does not allow global constructors/destructors. By using
|
|
/// constinit, we are sure that runtime global constructors are not used.
|
|
/// Bareflank does not attempt to run any init/fini sections of the
|
|
/// ELF binary, so if you use accidentally forget constinit, the code
|
|
/// will likely not execute and fail as a reminder. Instead, use the
|
|
/// initialization/release pattern that this example provides.
|
|
/// - From a unit testing point of view, each of these will have dummy
|
|
/// versions that are used for testing. When the code is compiled, each
|
|
/// source file and head file is compiled in isolation, meaning they are
|
|
/// not given include folder access to all of the code. This means that
|
|
/// each of these must be mocked, and the unit tests are given include
|
|
/// access to the MOCK. This prevents the need for templates, and
|
|
/// instead, all mock injection is done using the build system, greatly
|
|
/// simplifying both the code and branch analysis during unit tests as
|
|
/// the removal of templates also removes issues with branches being
|
|
/// counted for each instantiaion of a template type.
|
|
/// - Finally, some of these are not really needed for this simple example,
|
|
/// but we added them for completness so that it is easier to get
|
|
/// started with your own extension as more complicated code will likely
|
|
/// need most of these if not all.
|
|
///
|
|
|
|
/// @brief stores the bf_syscall_t that this code will use
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
constinit bf_syscall_t g_mut_sys{};
|
|
/// @brief stores the intrinsic_t that this code will use
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
constinit intrinsic_t g_mut_intrinsic{};
|
|
|
|
/// @brief stores the pool of VPs that we will use
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
constinit vp_pool_t g_mut_vp_pool{};
|
|
/// @brief stores the pool of VSs that we will use
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
constinit vs_pool_t g_mut_vs_pool{};
|
|
|
|
/// @brief stores the Global Storage for this extension
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
constinit gs_t g_mut_gs{};
|
|
/// @brief stores the Thread Local Storage for this extension on this PP
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
constinit thread_local tls_t g_mut_tls{};
|
|
|
|
/// <!-- description -->
|
|
/// @brief Implements the bootstrap entry function. This function is
|
|
/// called on each PP while the hypervisor is being bootstrapped.
|
|
///
|
|
/// <!-- inputs/outputs -->
|
|
/// @param ppid the physical process to bootstrap
|
|
///
|
|
extern "C" void
|
|
bootstrap_entry(bsl::safe_u16::value_type const ppid) noexcept
|
|
{
|
|
/// NOTE:
|
|
/// - Call into the bootstrap handler. This entry point serves as a
|
|
/// trampoline between C and C++. Specifically, the microkernel
|
|
/// cannot call a member function directly, and can only call
|
|
/// a C style function.
|
|
///
|
|
|
|
auto const ret{dispatch_bootstrap( // --
|
|
g_mut_gs, // --
|
|
g_mut_tls, // --
|
|
g_mut_sys, // --
|
|
g_mut_intrinsic, // --
|
|
g_mut_vp_pool, // --
|
|
g_mut_vs_pool, // --
|
|
bsl::to_u16(ppid))};
|
|
|
|
if (bsl::unlikely(!ret)) {
|
|
bsl::print<bsl::V>() << bsl::here();
|
|
return bf_control_op_exit();
|
|
}
|
|
|
|
/// NOTE:
|
|
/// - This code should never be reached. The bootstrap handler should
|
|
/// always call one of the "run" ABIs to return back to the
|
|
/// microkernel when a bootstrap is finished. If this is called, it
|
|
/// is because the bootstrap handler returned with an error.
|
|
///
|
|
|
|
return bf_control_op_exit();
|
|
}
|
|
|
|
/// <!-- description -->
|
|
/// @brief Implements the fast fail entry function. This is registered
|
|
/// by the main function to execute whenever a fast fail occurs.
|
|
///
|
|
/// <!-- inputs/outputs -->
|
|
/// @param errc the reason for the failure, which is CPU
|
|
/// specific. On x86, this is a combination of the exception
|
|
/// vector and error code.
|
|
/// @param addr contains a faulting address if the fail reason
|
|
/// is associated with an error that involves a faulting address (
|
|
/// for example like a page fault). Otherwise, the value of this
|
|
/// input is undefined.
|
|
///
|
|
extern "C" void
|
|
fail_entry(bsl::safe_u64::value_type const errc, bsl::safe_u64::value_type const addr) noexcept
|
|
{
|
|
/// NOTE:
|
|
/// - Call into the fast fail handler. This entry point serves as a
|
|
/// trampoline between C and C++. Specifically, the microkernel
|
|
/// cannot call a member function directly, and can only call
|
|
/// a C style function.
|
|
///
|
|
|
|
auto const ret{dispatch_fail( // --
|
|
g_mut_gs, // --
|
|
g_mut_tls, // --
|
|
g_mut_sys, // --
|
|
g_mut_intrinsic, // --
|
|
g_mut_vp_pool, // --
|
|
g_mut_vs_pool, // --
|
|
bsl::to_u64(errc), // --
|
|
bsl::to_u64(addr))};
|
|
|
|
if (bsl::unlikely(!ret)) {
|
|
bsl::print<bsl::V>() << bsl::here();
|
|
return bf_control_op_exit();
|
|
}
|
|
|
|
/// NOTE:
|
|
/// - This code should never be reached. The fast fail handler should
|
|
/// always call one of the "run" ABIs to return back to the
|
|
/// microkernel when a fast fail is finished. If this is called, it
|
|
/// is because the fast fail handler returned with an error.
|
|
///
|
|
|
|
return bf_control_op_exit();
|
|
}
|
|
|
|
/// <!-- description -->
|
|
/// @brief Implements the VMExit entry function. This is registered
|
|
/// by the main function to execute whenever a VMExit occurs.
|
|
///
|
|
/// <!-- inputs/outputs -->
|
|
/// @param vsid the ID of the VS that generated the VMExit
|
|
/// @param exit_reason the exit reason associated with the VMExit
|
|
///
|
|
extern "C" void
|
|
vmexit_entry(
|
|
bsl::safe_u16::value_type const vsid, bsl::safe_u64::value_type const exit_reason) noexcept
|
|
{
|
|
/// NOTE:
|
|
/// - Call into the vmexit handler. This entry point serves as a
|
|
/// trampoline between C and C++. Specifically, the microkernel
|
|
/// cannot call a member function directly, and can only call
|
|
/// a C style function.
|
|
///
|
|
|
|
auto const ret{dispatch_vmexit( // --
|
|
g_mut_gs, // --
|
|
g_mut_tls, // --
|
|
g_mut_sys, // --
|
|
g_mut_intrinsic, // --
|
|
g_mut_vp_pool, // --
|
|
g_mut_vs_pool, // --
|
|
bsl::to_u16(vsid), // --
|
|
bsl::to_u64(exit_reason))};
|
|
|
|
if (bsl::unlikely(!ret)) {
|
|
bsl::print<bsl::V>() << bsl::here();
|
|
return bf_control_op_exit();
|
|
}
|
|
|
|
/// NOTE:
|
|
/// - This code should never be reached. The VMExit handler should
|
|
/// always call one of the "run" ABIs to return back to the
|
|
/// microkernel when a VMExit is finished. If this is called, it
|
|
/// is because the VMExit handler returned with an error.
|
|
///
|
|
|
|
return bf_control_op_exit();
|
|
}
|
|
|
|
/// <!-- description -->
|
|
/// @brief Implements the main entry function for this example
|
|
///
|
|
/// <!-- inputs/outputs -->
|
|
/// @param version the version of the spec implemented by the
|
|
/// microkernel. This can be used to ensure the extension and the
|
|
/// microkernel speak the same ABI.
|
|
///
|
|
extern "C" void
|
|
ext_main_entry(bsl::uint32 const version) noexcept
|
|
{
|
|
bsl::safe_umx hndl{};
|
|
bf_status_t ret{};
|
|
|
|
if (bsl::unlikely(!bf_is_spec1_supported(bsl::to_u32(version)))) {
|
|
bsl::error() << "integration test not supported\n" << bsl::here();
|
|
return bf_control_op_exit();
|
|
}
|
|
|
|
ret = bf_handle_op_open_handle_impl(BF_SPEC_ID1_VAL.get(), hndl.data());
|
|
integration::require(ret == BF_STATUS_SUCCESS);
|
|
|
|
ret = bf_callback_op_register_vmexit_impl(hndl.get(), &vmexit_entry);
|
|
integration::require(ret == BF_STATUS_SUCCESS);
|
|
|
|
ret = bf_callback_op_register_fail_impl(hndl.get(), &fail_entry);
|
|
integration::require(ret == BF_STATUS_SUCCESS);
|
|
|
|
return bf_control_op_wait();
|
|
}
|
|
}
|