|  | #pragma once | 
|  |  | 
|  | #include "secret.h" | 
|  | #include <debug.hh> | 
|  | #include <functional> | 
|  | #include <magic_enum/magic_enum.hpp> | 
|  | #include <microvium/microvium.h> | 
|  | #include <tuple> | 
|  |  | 
|  | /** | 
|  | * Code related to the JavaScript interpreter. | 
|  | */ | 
|  | namespace | 
|  | { | 
|  | using CHERI::Capability; | 
|  |  | 
|  | /** | 
|  | * Constants for functions exposed to JavaScript->C++ FFI | 
|  | * | 
|  | * The values here must match the ones used in cheri.js. | 
|  | */ | 
|  | enum Exports : mvm_HostFunctionID | 
|  | { | 
|  | Print = 1, | 
|  | Move, | 
|  | LoadCapability, | 
|  | LoadInt, | 
|  | Store, | 
|  | GetAddress, | 
|  | SetAddress, | 
|  | GetBase, | 
|  | GetLength, | 
|  | GetPermissions, | 
|  | CheckSecret, | 
|  | }; | 
|  |  | 
|  | /// Constant for the say_hello function exposed to C++->JavaScript FFI | 
|  | static constexpr mvm_VMExportID ExportRun = 1234; | 
|  |  | 
|  | /** | 
|  | * Type used for the set of capabilities that JavaScript has complete | 
|  | * control over. | 
|  | */ | 
|  | using AttackerRegisterState = std::array<void *, 8>; | 
|  |  | 
|  | /** | 
|  | * Address of the `AttackerRegisterState` on the stack.  This structure is | 
|  | * on the stack so that it can hold local capabilities. | 
|  | */ | 
|  | ptraddr_t attackerRegisterStateAddress; | 
|  |  | 
|  | /** | 
|  | * Re-derive the pointer to the on-stack register state. | 
|  | */ | 
|  | AttackerRegisterState &state() | 
|  | { | 
|  | register AttackerRegisterState *cspRegister asm("csp"); | 
|  | asm("" : "=C"(cspRegister)); | 
|  | Capability<AttackerRegisterState> rs{cspRegister}; | 
|  | rs.address() = attackerRegisterStateAddress; | 
|  | rs.bounds()  = sizeof(AttackerRegisterState); | 
|  | return *rs; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Write a capability into one of the VM's register set. | 
|  | */ | 
|  | void register_write(int regno, void *value) | 
|  | { | 
|  | if ((regno >= 0) && (regno < 8)) | 
|  | { | 
|  | state()[regno] = value; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Read a register from the VM's register set.  This reads one of the 8 | 
|  | * that can be written or CSP (8), CGP (9), or PCC (10). | 
|  | */ | 
|  | void *register_read(int regno) | 
|  | { | 
|  | switch (regno) | 
|  | { | 
|  | default: | 
|  | return nullptr; | 
|  | case 0 ... 7: | 
|  | return state()[regno]; | 
|  | case 8: | 
|  | { | 
|  | register void *cspRegister asm("csp"); | 
|  | asm("" : "=C"(cspRegister)); | 
|  | return cspRegister; | 
|  | } | 
|  | case 9: | 
|  | { | 
|  | register void *cgpRegister asm("cgp"); | 
|  | asm("" : "=C"(cgpRegister)); | 
|  | return cgpRegister; | 
|  | } | 
|  | case 10: | 
|  | { | 
|  | void *pcc; | 
|  | asm("auipcc %0, 0\n" : "=C"(pcc)); | 
|  | return pcc; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Template that returns JavaScript argument specified in `arg` as a C++ | 
|  | * type T. | 
|  | */ | 
|  | template<typename T> | 
|  | T extract_argument(mvm_VM *vm, mvm_Value arg); | 
|  |  | 
|  | /** | 
|  | * Specialisation to return integers. | 
|  | */ | 
|  | template<> | 
|  | __always_inline int32_t extract_argument<int32_t>(mvm_VM *vm, mvm_Value arg) | 
|  | { | 
|  | return mvm_toInt32(vm, arg); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Specialisation to return booleans. | 
|  | */ | 
|  | template<> | 
|  | __always_inline bool extract_argument<bool>(mvm_VM *vm, mvm_Value arg) | 
|  | { | 
|  | return mvm_toBool(vm, arg); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Populate a tuple with arguments from an array of JavaScript values. | 
|  | * This uses `extract_argument` to coerce each JavaScript value to the | 
|  | * expected type. | 
|  | */ | 
|  | template<typename Tuple, int Idx = 0> | 
|  | __always_inline void | 
|  | args_to_tuple(Tuple &tuple, mvm_VM *vm, mvm_Value *args) | 
|  | { | 
|  | if constexpr (Idx < std::tuple_size_v<Tuple>) | 
|  | { | 
|  | std::get<Idx>(tuple) = extract_argument< | 
|  | std::remove_reference_t<decltype(std::get<Idx>(tuple))>>( | 
|  | vm, args[Idx]); | 
|  | args_to_tuple<Tuple, Idx + 1>(tuple, vm, args); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper template to extract the arguments from a function type. | 
|  | */ | 
|  | template<typename T> | 
|  | struct FunctionSignature; | 
|  |  | 
|  | /** | 
|  | * The concrete specialisation that decomposes the function type. | 
|  | */ | 
|  | template<typename R, typename... Args> | 
|  | struct FunctionSignature<R(Args...)> | 
|  | { | 
|  | /** | 
|  | * A tuple type containing all of the argument types of the function | 
|  | * whose type is being extracted. | 
|  | */ | 
|  | using ArgumentType = std::tuple<Args...>; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * The concrete specialisation that decomposes the function type for a cross | 
|  | * compartment call. | 
|  | */ | 
|  | template<typename R, typename... Args> | 
|  | struct FunctionSignature<R __attribute__((cheri_ccall)) (Args...)> | 
|  | { | 
|  | /** | 
|  | * A tuple type containing all of the argument types of the function | 
|  | * whose type is being extracted. | 
|  | */ | 
|  | using ArgumentType = std::tuple<Args...>; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Call `Fn` with arguments from the Microvium arguments array. | 
|  | * | 
|  | * This is a wrapper that allows automatic forwarding from a function | 
|  | * exported to JavaScript | 
|  | */ | 
|  | template<auto Fn> | 
|  | __always_inline mvm_TeError call_export(mvm_VM    *vm, | 
|  | mvm_Value *result, | 
|  | mvm_Value *args, | 
|  | uint8_t    argsCount) | 
|  | { | 
|  | using TupleType = typename FunctionSignature< | 
|  | std::remove_pointer_t<decltype(Fn)>>::ArgumentType; | 
|  | // Return an error if we have the wrong number of arguments. | 
|  | if (argsCount < std::tuple_size_v<TupleType>) | 
|  | { | 
|  | return MVM_E_UNEXPECTED; | 
|  | } | 
|  | // Get the arguments in a tuple. | 
|  | TupleType arguments; | 
|  | args_to_tuple(arguments, vm, args); | 
|  | // If this returns void, we don't need to do anything with the return. | 
|  | if constexpr (std::is_same_v<void, decltype(std::apply(Fn, arguments))>) | 
|  | { | 
|  | std::apply(Fn, arguments); | 
|  | } | 
|  | else | 
|  | { | 
|  | // Coerce the return type to a JavaScript object of the correct | 
|  | // type and return it. | 
|  | auto primitiveResult = std::apply(Fn, arguments); | 
|  | if constexpr (std::is_same_v<decltype(primitiveResult), bool>) | 
|  | { | 
|  | *result = mvm_newBoolean(primitiveResult); | 
|  | } | 
|  | if constexpr (std::is_same_v<decltype(primitiveResult), int32_t>) | 
|  | { | 
|  | *result = mvm_newInt32(vm, primitiveResult); | 
|  | } | 
|  | if constexpr (std::is_same_v<decltype(primitiveResult), | 
|  | std::string>) | 
|  | { | 
|  | *result = mvm_newString( | 
|  | vm, primitiveResult.data(), primitiveResult.size()); | 
|  | } | 
|  | } | 
|  | return MVM_E_SUCCESS; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper that maps from Exports | 
|  | */ | 
|  | template<Exports> | 
|  | constexpr static std::nullptr_t ExportedFn = nullptr; | 
|  |  | 
|  | /** | 
|  | * Move a value from the source register to the destination. | 
|  | */ | 
|  | void export_move(int32_t destination, int32_t source) | 
|  | { | 
|  | register_write(destination, register_read(source)); | 
|  | } | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<Move> = export_move; | 
|  |  | 
|  | /** | 
|  | * Load a capability into the destination register. | 
|  | */ | 
|  | void export_load(int32_t destination, int32_t source, int32_t offset) | 
|  | { | 
|  | Capability<void *> s{static_cast<void **>(register_read(source))}; | 
|  | s.address() += offset; | 
|  | register_write(destination, *s); | 
|  | } | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<LoadCapability> = export_load; | 
|  |  | 
|  | /** | 
|  | * Load and return an integer. | 
|  | */ | 
|  | int32_t export_load_int(int32_t source, int32_t offset) | 
|  | { | 
|  | Capability<int32_t> s{static_cast<int32_t *>(register_read(source))}; | 
|  | s.address() += offset; | 
|  | return *s; | 
|  | } | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<LoadInt> = export_load_int; | 
|  |  | 
|  | /** | 
|  | * Store a capability from a register at a specified location. | 
|  | */ | 
|  | void export_store(int32_t value, int32_t source, int32_t offset) | 
|  | { | 
|  | Capability<void *> s{static_cast<void **>(register_read(source))}; | 
|  | s.address() += offset; | 
|  | *s = register_read(value); | 
|  | } | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<Store> = export_store; | 
|  |  | 
|  | /** | 
|  | * Returns the address of the capability in the specified register. | 
|  | */ | 
|  | int32_t export_get_address(int32_t regno) | 
|  | { | 
|  | Capability<void> value{register_read(regno)}; | 
|  | return value.address(); | 
|  | } | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<GetAddress> = export_get_address; | 
|  |  | 
|  | /** | 
|  | * Set the address of the capability in the specified register. | 
|  | */ | 
|  | void export_set_address(int32_t regno, int32_t address) | 
|  | { | 
|  | Capability<void> value{register_read(regno)}; | 
|  | value.address() = address; | 
|  | register_write(regno, value); | 
|  | } | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<SetAddress> = export_set_address; | 
|  |  | 
|  | /** | 
|  | * Return the base address of the capability in the specified register. | 
|  | */ | 
|  | int32_t export_get_base(int32_t regno) | 
|  | { | 
|  | Capability<void> value{register_read(regno)}; | 
|  | return value.base(); | 
|  | } | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<GetBase> = export_get_base; | 
|  |  | 
|  | /** | 
|  | * Return the length of the capability in the specified register. | 
|  | */ | 
|  | int32_t export_get_length(int32_t regno) | 
|  | { | 
|  | Capability<void> value{register_read(regno)}; | 
|  | return value.length(); | 
|  | } | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<GetLength> = export_get_length; | 
|  |  | 
|  | /** | 
|  | * Return the permissions of the capability in the specified register. | 
|  | */ | 
|  | int32_t export_get_permissions(int32_t regno) | 
|  | { | 
|  | Capability<void> value{register_read(regno)}; | 
|  | return value.permissions().as_raw(); | 
|  | } | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<GetPermissions> = export_get_permissions; | 
|  |  | 
|  | template<> | 
|  | constexpr static auto ExportedFn<CheckSecret> = check_secret; | 
|  |  | 
|  | /** | 
|  | * Base template for exported functions.  Forwards to the function defined | 
|  | * with `ExportedFn<E>`. | 
|  | */ | 
|  | template<Exports E> | 
|  | mvm_TeError exported_function(mvm_VM *vm, | 
|  | mvm_HostFunctionID, | 
|  | mvm_Value *result, | 
|  | mvm_Value *args, | 
|  | uint8_t    argCount) | 
|  | { | 
|  | return call_export<ExportedFn<E>>(vm, result, args, argCount); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Print a string passed from JavaScript. | 
|  | */ | 
|  | template<> | 
|  | mvm_TeError exported_function<Print>(mvm_VM            *vm, | 
|  | mvm_HostFunctionID funcID, | 
|  | mvm_Value         *result, | 
|  | mvm_Value         *args, | 
|  | uint8_t            argCount) | 
|  | { | 
|  | auto *uart = MMIO_CAPABILITY(Uart, uart); | 
|  | // Iterate over the arguments. | 
|  | for (unsigned i = 0; i < argCount; i++) | 
|  | { | 
|  | // Coerce the argument to a string and get it as a C string | 
|  | const char *str = mvm_toStringUtf8(vm, args[i], nullptr); | 
|  | // Write each character to the UART | 
|  | for (; *str != '\0'; str++) | 
|  | { | 
|  | uart->blocking_write(*str); | 
|  | } | 
|  | } | 
|  | // Write a trailing newline | 
|  | uart->blocking_write('\n'); | 
|  | // Unconditionally return success | 
|  | return MVM_E_SUCCESS; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Callback from microvium that resolves imports. | 
|  | * | 
|  | * This resolves each function to the template instantiation of | 
|  | * `exported_function` with `funcID` as the template parameter. | 
|  | */ | 
|  | mvm_TeError | 
|  | resolve_import(mvm_HostFunctionID funcID, void *, mvm_TfHostFunction *out) | 
|  | { | 
|  | return magic_enum::enum_switch( | 
|  | [&](auto val) { | 
|  | constexpr Exports Export = val; | 
|  | *out                     = exported_function<Export>; | 
|  | return MVM_E_SUCCESS; | 
|  | }, | 
|  | Exports(funcID), | 
|  | MVM_E_UNRESOLVED_IMPORT); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper that deletes a Microvium VM when used with a C++ unique pointer. | 
|  | */ | 
|  | struct MVMDeleter | 
|  | { | 
|  | void operator()(mvm_VM *mvm) const | 
|  | { | 
|  | mvm_free(mvm); | 
|  | } | 
|  | }; | 
|  | } // namespace |