| // 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); |
| } |
| } |
| } |
| } |