blob: 4001319f0fb3b7f2aac9e412bb953c4b9754c043 [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
#include "microvium-ffi.hh"
#include "secret.h"
#include <compartment.h>
#include <cstdint>
#include <cstdlib>
#include <debug.hh>
#include <riscvreg.h>
#include <type_traits>
#include <vector>
/// Expose debugging features unconditionally for this compartment.
using Debug = ConditionalDebug<true, "JavaScript compartment">;
using CHERI::Capability;
/// Thread entry point.
void __cheri_compartment("js") run()
{
// Set the secret value on startup.
set_secret();
while (true)
{
// Get a capability to the UART.
auto uart = MMIO_CAPABILITY(Uart, uart);
// Create a vector to hold the bytecode.
std::vector<uint8_t> bytecode;
// Helper that reads a character and errors if it isn't the expected
// one.
auto skip = [&](char byte) {
char c = uart->blocking_read();
Debug::Assert(byte == c, "Read '{}', expected '{}'", c, byte);
};
// Helper that reads from the UART until a specific character is seen
auto skipUntil = [&](char byte) {
while (uart->blocking_read() != byte) {}
};
// Helper that converts a hex character to an integer. Returns 0 on
// invalid values.
auto hexByteToNumber = [](char c) {
switch (c)
{
case '0' ... '9':
return c - '0';
case 'a' ... 'f':
return c - 'a' + 10;
case 'A' ... 'F':
return c - 'A' + 10;
}
return 0;
};
// Read values in the form generated by microvium's hex output: a
// series of hex bytes in braces.
skipUntil('{');
while (true)
{
skip('0');
skip('x');
uint8_t byte = hexByteToNumber(uart->blocking_read()) << 4;
byte += hexByteToNumber(uart->blocking_read());
bytecode.push_back(byte);
char c = uart->blocking_read();
if (c == '}')
{
break;
}
Debug::Assert(c == ',', "Expected comma or close brace, read {}", c);
}
Debug::log("Read {} bytes of bytecode", bytecode.size());
Debug::log("{} bytes of heap available",
heap_quota_remaining(MALLOC_CAPABILITY));
////////////////////////////////////////////////////////////////////////
// We've now read the bytecode into a buffer. Spin up the JavaScript
// VM to execute it.
////////////////////////////////////////////////////////////////////////
// Allocate the space for the VM capability registers on the stack and
// record its location.
// **Note**: This must be on the stack and in same compartment as the
// JavaScript interpreter, so that the callbacks can re-derive it from
// csp.
AttackerRegisterState state;
attackerRegisterStateAddress = Capability{&state}.address();
mvm_TeError err;
std::unique_ptr<mvm_VM, MVMDeleter> vm;
// Create a Microvium VM from the bytecode.
{
mvm_VM *rawVm;
err = mvm_restore(
&rawVm, /* Out pointer to the VM */
bytecode.data(), /* Bytecode data */
bytecode.size(), /* Bytecode length */
MALLOC_CAPABILITY, /* Capability used to allocate memory */
::resolve_import); /* Callback used to resolve FFI imports */
// If this is not valid bytecode, give up.
Debug::Assert(
err == MVM_E_SUCCESS, "Failed to parse bytecode: {}", err);
vm.reset(rawVm);
}
// Get a handle to the JavaScript `run` function.
mvm_Value run;
err = mvm_resolveExports(vm.get(), &ExportRun, &run, 1);
if (err != MVM_E_SUCCESS)
{
Debug::log("Failed to get run function: {}", err);
}
else
{
// Call the function:
err = mvm_call(vm.get(), run, nullptr, nullptr, 0);
if (err != MVM_E_SUCCESS)
{
Debug::log("Failed to call run function: {}", err);
}
}
}
}