blob: a919a17b18c1fe54c97d2c99b7da64b50f9baa42 [file] [log] [blame]
#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