blob: c0bc046c2af8ffa0e4fd162a9e91ed55a45b75e8 [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cheriot/riscv_cheriot_instructions.h"
#include <array>
#include <cstdint>
#include <ios>
#include <limits>
#include <string>
#include <tuple>
#include <vector>
#include "absl/log/check.h"
#include "absl/random/random.h"
#include "absl/strings/str_format.h"
#include "absl/types/span.h"
#include "cheriot/cheriot_register.h"
#include "cheriot/cheriot_state.h"
#include "googlemock/include/gmock/gmock.h"
#include "mpact/sim/generic/immediate_operand.h"
#include "mpact/sim/generic/instruction.h"
#include "mpact/sim/generic/type_helpers.h"
#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
#include "riscv//riscv_state.h"
// This file contains the unit tests for the RiscV CHERIoT capability specific
// instructions.
namespace {
using ::mpact::sim::generic::operator*; // NOLINT: used below (clang error).
using ::mpact::sim::cheriot::CheriotRegister;
using PB = CheriotRegister::PermissionBits;
using OT = CheriotRegister::ObjectType;
using ::mpact::sim::cheriot::CheriotState;
using CH_EC = ::mpact::sim::cheriot::ExceptionCode;
using RV_EC = ::mpact::sim::riscv::ExceptionCode;
using ISA = ::mpact::sim::riscv::IsaExtension;
using ::mpact::sim::generic::ImmediateOperand;
using ::mpact::sim::generic::Instruction;
using ::mpact::sim::util::TaggedFlatDemandMemory;
// Instruction semantic functions.
using ::mpact::sim::cheriot::CheriotAuicap;
using ::mpact::sim::cheriot::CheriotCAndPerm;
using ::mpact::sim::cheriot::CheriotCClearTag;
using ::mpact::sim::cheriot::CheriotCGetAddr;
using ::mpact::sim::cheriot::CheriotCGetBase;
using ::mpact::sim::cheriot::CheriotCGetHigh;
using ::mpact::sim::cheriot::CheriotCGetLen;
using ::mpact::sim::cheriot::CheriotCGetPerm;
using ::mpact::sim::cheriot::CheriotCGetTag;
using ::mpact::sim::cheriot::CheriotCGetType;
using ::mpact::sim::cheriot::CheriotCIncAddr;
using ::mpact::sim::cheriot::CheriotCJal;
using ::mpact::sim::cheriot::CheriotCJalrCra;
using ::mpact::sim::cheriot::CheriotCLc;
using ::mpact::sim::cheriot::CheriotCLcChild;
using ::mpact::sim::cheriot::CheriotCMove;
using ::mpact::sim::cheriot::CheriotCRepresentableAlignmentMask;
using ::mpact::sim::cheriot::CheriotCRoundRepresentableLength;
using ::mpact::sim::cheriot::CheriotCSc;
using ::mpact::sim::cheriot::CheriotCSeal;
using ::mpact::sim::cheriot::CheriotCSetAddr;
using ::mpact::sim::cheriot::CheriotCSetBounds;
using ::mpact::sim::cheriot::CheriotCSetBoundsExact;
using ::mpact::sim::cheriot::CheriotCSetEqualExact;
using ::mpact::sim::cheriot::CheriotCSetHigh;
using ::mpact::sim::cheriot::CheriotCSpecialR;
using ::mpact::sim::cheriot::CheriotCSpecialRW;
using ::mpact::sim::cheriot::CheriotCSub;
using ::mpact::sim::cheriot::CheriotCTestSubset;
using ::mpact::sim::cheriot::CheriotCUnseal;
// Register name definitions.
constexpr char kCra[] = "c1";
constexpr char kC1[] = "c11";
constexpr char kC2[] = "c12";
constexpr char kC3[] = "c13";
constexpr char kC4[] = "c14";
// Register number definitions.
constexpr int kC1Num = 11;
constexpr int kPccNum = 0b1'00000;
constexpr uint32_t kInstAddress = 0x2468;
constexpr uint32_t kMemAddress = 0x1000;
constexpr int kDataSeal10 = 10;
constexpr int kInstSizeNormal = 4;
// Test fixture.
class RiscVCheriotInstructionsTest : public ::testing::Test {
protected:
static constexpr int kCapabilityGranule = 8;
RiscVCheriotInstructionsTest() {
memory_ = new TaggedFlatDemandMemory(kCapabilityGranule);
state_ = new CheriotState("test_state", memory_);
ResetInstruction(kInstSizeNormal);
for (auto &[reg_name, cap_reg_ptr] :
std::vector<std::tuple<std::string, CheriotRegister **>>{
{kC1, &c1_reg_},
{kC2, &c2_reg_},
{kC3, &c3_reg_},
{kC4, &c4_reg_},
{kCra, &cra_reg_}}) {
*cap_reg_ptr = state_->GetRegister<CheriotRegister>(reg_name).first;
}
state_->set_on_trap([this](bool is_interrupt, uint64_t trap_value,
uint64_t exception_code, uint64_t epc,
const Instruction *inst) {
return TrapHandler(is_interrupt, trap_value, exception_code, epc, inst);
});
}
~RiscVCheriotInstructionsTest() override {
inst_->DecRef();
delete state_;
delete memory_;
}
// Handler for any traps that are raised. It is used to capture the trap
// information for checks.
bool TrapHandler(bool is_interrupt, uint64_t trap_value,
uint64_t exception_code, uint64_t epc,
const Instruction *inst);
// Clear the captured trap values.
void ResetTrapHandler();
void AppendCapabilityOperands(Instruction *inst,
absl::Span<const std::string> sources,
absl::Span<const std::string> dests) {
for (auto &reg_name : sources) {
auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
inst->AppendSource(reg->CreateSourceOperand(reg_name));
}
for (auto &reg_name : dests) {
auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
inst->AppendDestination(reg->CreateDestinationOperand(0, reg_name));
}
}
template <typename T>
void AppendImmediateOperand(Instruction *inst, T value) {
auto *src = new ImmediateOperand<T>(value);
inst_->AppendSource(src);
}
template <typename T>
void AppendImmediateOperands(const std::vector<T> &values) {
for (auto &value : values) {
AppendImmediateOperand<T>(inst_, value);
}
}
// Takes a vector of tuples of register names and values. Fetches each
// named register and sets it to the corresponding value.
template <typename T>
void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) {
for (auto &[reg_name, value] : values) {
auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
reg->set_address(value);
}
}
void ResetInstruction(int size) {
if (inst_ != nullptr) inst_->DecRef();
inst_ = new Instruction(kInstAddress, state_);
inst_->set_size(size);
}
// Return true if the capability is null (except for address field).
bool IsNullCapability(CheriotRegister *cap) {
return cap->is_null() ||
(cap->top() == 0 && cap->base() == 0 && cap->permissions() == 0 &&
cap->tag() == 0 && cap->object_type() == 0 && cap->reserved() == 0);
}
void SetUpForLoadCapabilityTest(uint32_t address,
const CheriotRegister *cap) {
ResetInstruction(kInstSizeNormal);
inst()->set_semantic_function(&CheriotCLc);
// Add the child instruction.
auto *child = new Instruction(kInstAddress, state());
child->set_semantic_function(&CheriotCLcChild);
inst()->AppendChild(child);
child->DecRef();
// Store a capability to memory.
auto cap_db = state()->db_factory()->Allocate<uint32_t>(2);
cap_db->Set<uint32_t>(0, 0xdeadbeef);
cap_db->Set<uint32_t>(1, cap->Compress());
auto tag_db = state()->db_factory()->Allocate<uint8_t>(1);
tag_db->Set<uint8_t>(0, 1);
state()->StoreCapability(inst(), address, cap_db, tag_db);
cap_db->DecRef();
tag_db->DecRef();
}
// Accessors.
Instruction *inst() { return inst_; }
TaggedFlatDemandMemory *memory() { return memory_; }
CheriotState *state() { return state_; }
// Capability register pointers.
CheriotRegister *c1_reg() { return c1_reg_; }
CheriotRegister *c2_reg() { return c2_reg_; }
CheriotRegister *c3_reg() { return c3_reg_; }
CheriotRegister *c4_reg() { return c4_reg_; }
CheriotRegister *cra_reg() { return cra_reg_; }
absl::BitGen &bitgen() { return bitgen_; }
bool trap_taken() { return trap_taken_; }
bool trap_is_interrupt() { return trap_is_interrupt_; }
uint64_t trap_value() { return trap_value_; }
uint64_t trap_exception_code() { return trap_exception_code_; }
uint64_t trap_epc() { return trap_epc_; }
const Instruction *trap_inst() { return trap_inst_; }
private:
Instruction *inst_ = nullptr;
TaggedFlatDemandMemory *memory_;
CheriotState *state_;
CheriotRegister *c1_reg_;
CheriotRegister *c2_reg_;
CheriotRegister *c3_reg_;
CheriotRegister *c4_reg_;
CheriotRegister *cra_reg_;
absl::BitGen bitgen_;
bool trap_taken_ = false;
bool trap_is_interrupt_ = false;
uint64_t trap_value_ = 0;
uint64_t trap_exception_code_ = 0;
uint64_t trap_epc_ = 0;
const Instruction *trap_inst_ = nullptr;
};
bool RiscVCheriotInstructionsTest::TrapHandler(bool is_interrupt,
uint64_t trap_value,
uint64_t exception_code,
uint64_t epc,
const Instruction *inst) {
trap_taken_ = true;
trap_is_interrupt_ = is_interrupt;
trap_value_ = trap_value;
trap_exception_code_ = exception_code;
trap_epc_ = epc;
trap_inst_ = inst;
return true;
}
void RiscVCheriotInstructionsTest::ResetTrapHandler() {
trap_taken_ = false;
trap_is_interrupt_ = false;
trap_value_ = 0;
trap_exception_code_ = 0;
trap_epc_ = 0;
trap_inst_ = nullptr;
}
TEST_F(RiscVCheriotInstructionsTest, Auicap) {
inst()->set_semantic_function(&CheriotAuicap);
// Make the source register the memory root.
c1_reg()->ResetMemoryRoot();
// Try different offsets.
for (int32_t offset : {16, 1024, 0, -16 - 1024}) {
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(offset);
inst()->Execute(nullptr);
// Verify that the source didn't change, and that the value of the
// destination capability is as expected.
EXPECT_EQ(c1_reg()->address(), kMemAddress);
EXPECT_EQ(c3_reg()->address(), kMemAddress + offset);
EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
EXPECT_TRUE(c3_reg()->tag());
}
// Try with c1 being sealed.
CHECK_OK(c1_reg()->Seal(*state()->sealing_root(), kDataSeal10));
uint32_t offset = 0x1000;
c2_reg()->set_address(offset);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), kMemAddress + offset);
EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
EXPECT_FALSE(c3_reg()->tag());
// Now limit c1 to a smaller range and set the offset outside that.
c1_reg()->ResetMemoryRoot();
offset = 0x210;
c1_reg()->SetBounds(kMemAddress, offset - 16);
c1_reg()->set_address(kMemAddress);
EXPECT_TRUE(c1_reg()->tag());
// Set the offset to be just outside.
offset = (1 << (c1_reg()->exponent() + 9)) + 1;
c2_reg()->set_address(offset);
inst()->Execute(nullptr);
EXPECT_EQ(c1_reg()->address(), kMemAddress);
EXPECT_EQ(c3_reg()->address(), kMemAddress + offset);
EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
EXPECT_FALSE(c3_reg()->tag());
}
// Verify that permission removal using CAndPerm works.
TEST_F(RiscVCheriotInstructionsTest, CAndPerm) {
uint32_t mask = PB::kPermitGlobal | PB::kPermitLoadGlobal | PB::kPermitStore |
PB::kPermitLoadMutable | PB::kPermitStoreLocalCapability |
PB::kPermitLoad | PB::kPermitLoadStoreCapability;
inst()->set_semantic_function(&CheriotCAndPerm);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC1});
c1_reg()->ResetMemoryRoot(); // Full memory permissions.
uint32_t and_mask = mask;
uint32_t expected = PB::kPermitGlobal | PB::kPermitLoadGlobal |
PB::kPermitLoadMutable | PB::kPermitStoreLocalCapability |
PB::kPermitLoadStoreCapability | PB::kPermitStore |
PB::kPermitLoad;
EXPECT_EQ(c1_reg()->permissions(), expected);
for (uint32_t i :
{PB::kPermitGlobal, PB::kPermitLoadGlobal, PB::kPermitLoadMutable,
PB::kPermitStoreLocalCapability, PB::kPermitLoadStoreCapability,
PB::kPermitStore, PB::kPermitLoad}) {
and_mask = mask & ~i;
expected &= and_mask;
c2_reg()->set_address(and_mask);
inst()->Execute(nullptr);
EXPECT_EQ(c1_reg()->permissions(), expected) << absl::StrFormat(
"p: %08x expected: %08x\n", c1_reg()->permissions(), expected);
EXPECT_TRUE(c1_reg()->tag());
}
// A sealed capability should clear the tag.
c1_reg()->ResetMemoryRoot();
CHECK_OK(c1_reg()->Seal(*state()->sealing_root(), kDataSeal10));
expected = PB::kPermitGlobal | PB::kPermitLoadGlobal |
PB::kPermitLoadMutable | PB::kPermitStoreLocalCapability |
PB::kPermitLoadStoreCapability | PB::kPermitStore |
PB::kPermitLoad;
and_mask = mask & ~PB::kPermitGlobal;
expected &= and_mask;
c2_reg()->set_address(and_mask);
inst()->Execute(nullptr);
EXPECT_EQ(c1_reg()->permissions(), expected) << absl::StrFormat(
"p: %08x expected: %08x\n", c1_reg()->permissions(), expected);
EXPECT_FALSE(c1_reg()->tag());
}
// Verify that CClearTag clears the tag properly.
TEST_F(RiscVCheriotInstructionsTest, CClearTag) {
inst()->set_semantic_function(&CheriotCClearTag);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
c1_reg()->ResetMemoryRoot();
// Make c3 reg tag true.
c3_reg()->ResetMemoryRoot();
EXPECT_TRUE(c3_reg()->tag());
inst()->Execute(nullptr);
EXPECT_FALSE(c3_reg()->tag());
}
// Verify that the correct address is returned.
TEST_F(RiscVCheriotInstructionsTest, CGetAddr) {
inst()->set_semantic_function(&CheriotCGetAddr);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
c1_reg()->ResetMemoryRoot();
c3_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0);
c1_reg()->set_address(kMemAddress);
c3_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), kMemAddress);
EXPECT_TRUE(IsNullCapability(c3_reg()));
}
// Verify that the correct base is returned.
TEST_F(RiscVCheriotInstructionsTest, CGetBase) {
inst()->set_semantic_function(&CheriotCGetBase);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
c1_reg()->ResetMemoryRoot();
c3_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->base(), 0);
EXPECT_TRUE(c1_reg()->SetBounds(kMemAddress, 0x200));
c1_reg()->set_address(kMemAddress);
c3_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), kMemAddress);
EXPECT_TRUE(IsNullCapability(c3_reg()));
}
// Verify that the correct 'compressed' value is returned.
TEST_F(RiscVCheriotInstructionsTest, CGetHigh) {
inst()->set_semantic_function(&CheriotCGetHigh);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
c1_reg()->ResetMemoryRoot();
c3_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), c1_reg()->Compress());
EXPECT_TRUE(c1_reg()->SetBounds(kMemAddress, 0x200));
c3_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), c1_reg()->Compress());
EXPECT_TRUE(IsNullCapability(c3_reg()));
c1_reg()->ResetNull();
c3_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0);
EXPECT_TRUE(IsNullCapability(c3_reg()));
}
// Verify that the correct length is returned.
TEST_F(RiscVCheriotInstructionsTest, CGetLen) {
inst()->set_semantic_function(&CheriotCGetLen);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
c1_reg()->ResetMemoryRoot();
c3_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0xffff'ffff);
c1_reg()->SetBounds(kMemAddress, 0x200);
c3_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0x200);
EXPECT_TRUE(IsNullCapability(c3_reg()));
}
// Verify that the correct permission bits are returned.
TEST_F(RiscVCheriotInstructionsTest, CGetPerm) {
inst()->set_semantic_function(&CheriotCGetPerm);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
c1_reg()->ResetNull();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0);
EXPECT_TRUE(IsNullCapability(c3_reg()));
c1_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(),
PB::kPermitGlobal | PB::kPermitLoadGlobal | PB::kPermitStore |
PB::kPermitLoadMutable | PB::kPermitStoreLocalCapability |
PB::kPermitLoad | PB::kPermitLoadStoreCapability);
EXPECT_TRUE(IsNullCapability(c3_reg()));
c1_reg()->ResetExecuteRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(),
PB::kPermitGlobal | PB::kPermitExecute | PB::kPermitLoad |
PB::kPermitLoadStoreCapability | PB::kPermitLoadGlobal |
PB::kPermitLoadMutable | PB::kPermitAccessSystemRegisters);
EXPECT_TRUE(IsNullCapability(c3_reg()));
c1_reg()->ResetSealingRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), PB::kPermitGlobal | PB::kPermitSeal |
PB::kPermitUnseal | PB::kUserPerm0);
EXPECT_TRUE(IsNullCapability(c3_reg()));
}
// Verify that CGetTag gets the correct tag value.
TEST_F(RiscVCheriotInstructionsTest, CGetTag) {
inst()->set_semantic_function(&CheriotCGetTag);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
inst()->Execute(nullptr);
// Initial value of a capability register is null, so the tag should be false.
EXPECT_EQ(c3_reg()->address(), 0);
// Make c1 a valid capability, now the tag should be true.
c1_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 1);
EXPECT_TRUE(IsNullCapability(c3_reg()));
}
// Checking that CGetType gets the correct object type.
TEST_F(RiscVCheriotInstructionsTest, CGetType) {
inst()->set_semantic_function(&CheriotCGetType);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
inst()->Execute(nullptr);
c1_reg()->ResetExecuteRoot();
EXPECT_EQ(c3_reg()->address(), 0);
EXPECT_TRUE(IsNullCapability(c3_reg()));
for (int i = 0; i < 8; i++) {
c1_reg()->set_object_type(i);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), i & 0x7);
EXPECT_TRUE(IsNullCapability(c3_reg()));
}
c1_reg()->ResetMemoryRoot();
for (int i = 0; i < 8; i++) {
c1_reg()->set_object_type(i);
inst()->Execute(nullptr);
if (i == 0) {
EXPECT_EQ(c3_reg()->address(), 0);
} else {
EXPECT_EQ(c3_reg()->address(), 0x8 | i);
}
EXPECT_TRUE(IsNullCapability(c3_reg()));
}
}
TEST_F(RiscVCheriotInstructionsTest, CIncAddr) {
inst()->set_semantic_function(&CheriotCIncAddr);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
// Set up c1 as a valid capability, base kMemAddress, length 200.
c1_reg()->ResetMemoryRoot();
c1_reg()->SetBounds(kMemAddress, 0x80);
c1_reg()->set_address(kMemAddress);
// Set the value of c2 to 0x10.
c2_reg()->set_address(0x10);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), kMemAddress + 0x10);
EXPECT_TRUE(c3_reg()->tag());
EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
// Change increment to 0x200.
c2_reg()->set_address(0x20);
// Increment again.
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), kMemAddress + 0x20);
EXPECT_TRUE(c3_reg()->tag());
EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
// Change increment to 1 << (exponent + 9) + 1;
c2_reg()->set_address((0x1 << (c1_reg()->exponent() + 9)) + 1);
// Increment again. This time the tag will be cleared.
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(),
kMemAddress + (0x1 << (c1_reg()->exponent() + 9)) + 1);
EXPECT_FALSE(c3_reg()->tag())
<< absl::StrFormat("b: 0x%08x a: 0x%08x e:%d", c3_reg()->base(),
c3_reg()->address(), c3_reg()->exponent());
EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
// Change increment back to 0x100.
c2_reg()->set_address(0x100);
// Seal the source capability. That will make the tag false.
EXPECT_TRUE(c1_reg()->Seal(*state()->sealing_root(), kDataSeal10).ok());
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), kMemAddress + 0x100);
EXPECT_FALSE(c3_reg()->tag());
EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
}
// Jump and link - no traps.
TEST_F(RiscVCheriotInstructionsTest, CJal) {
inst()->set_semantic_function(&CheriotCJal);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
state()->pcc()->set_address(inst()->address());
c1_reg()->set_address(0x200);
// Set interrupt enable to true.
state()->mstatus()->set_mie(1);
state()->mstatus()->Submit();
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
EXPECT_TRUE(c3_reg()->tag());
EXPECT_TRUE(c3_reg()->IsSentry());
EXPECT_EQ(c3_reg()->object_type(), OT::kInterruptEnablingReturnSentry);
EXPECT_TRUE(state()->pcc()->tag());
// Set interrupt enable to false.
state()->mstatus()->set_mie(0);
state()->mstatus()->Submit();
state()->pcc()->set_address(inst()->address());
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
EXPECT_TRUE(c3_reg()->tag());
EXPECT_TRUE(c3_reg()->IsSentry());
EXPECT_EQ(c3_reg()->object_type(), OT::kInterruptDisablingReturnSentry);
EXPECT_TRUE(state()->pcc()->tag());
}
// Jump and link - out of bounds error.
TEST_F(RiscVCheriotInstructionsTest, CJalOutOfBounds) {
inst()->set_semantic_function(&CheriotCJal);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
state()->pcc()->set_address(inst()->address());
c1_reg()->set_address(0x200);
// Set interrupt enable to true.
state()->mstatus()->set_mie(1);
state()->mstatus()->Submit();
// Restrict the bounds of pcc.
EXPECT_TRUE(state()->pcc()->SetBounds(kInstAddress, 0x100));
inst()->Execute(nullptr);
}
// Jump and link - misaligned (jumping to 2 byte aligned address with no
// compact instructions).
TEST_F(RiscVCheriotInstructionsTest, CJalMisaligned) {
inst()->set_semantic_function(&CheriotCJal);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
state()->pcc()->set_address(inst()->address());
state()->misa()->Set(state()->misa()->AsUint64() & ~*ISA::kCompressed);
c1_reg()->set_address(0x202);
// Set interrupt enable to true.
state()->mstatus()->set_mie(1);
state()->mstatus()->Submit();
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), kInstAddress + 0x202);
EXPECT_EQ(trap_exception_code(), *RV_EC::kInstructionAddressMisaligned);
EXPECT_EQ(inst(), trap_inst());
}
// Jump and link register (capability) indirect - no traps, unsealed source.
TEST_F(RiscVCheriotInstructionsTest, CJalr) {
inst()->set_semantic_function(&CheriotCJalrCra);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kCra});
state()->pcc()->set_address(inst()->address());
// Set up the destination capability.
c1_reg()->ResetExecuteRoot();
c1_reg()->set_address(kInstAddress + 0x100);
c1_reg()->SetBounds(kInstAddress, 0x400);
// Set offset.
c2_reg()->set_address(0x100);
// Set interrupt enable to true.
state()->mstatus()->set_mie(1);
state()->mstatus()->Submit();
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
EXPECT_TRUE(cra_reg()->tag());
EXPECT_TRUE(cra_reg()->IsSentry());
EXPECT_EQ(cra_reg()->object_type(), OT::kInterruptEnablingReturnSentry);
EXPECT_TRUE(state()->pcc()->tag());
// Set interrupt enable to false.
state()->mstatus()->set_mie(0);
state()->mstatus()->Submit();
state()->pcc()->set_address(inst()->address());
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
EXPECT_TRUE(cra_reg()->tag());
EXPECT_TRUE(cra_reg()->IsSentry());
EXPECT_EQ(cra_reg()->object_type(), OT::kInterruptDisablingReturnSentry);
EXPECT_TRUE(state()->pcc()->tag());
}
// Jump and link register (capability) indirect - no traps, sentry.
TEST_F(RiscVCheriotInstructionsTest, CJalrSentry) {
inst()->set_semantic_function(&CheriotCJalrCra);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kCra});
state()->pcc()->set_address(inst()->address());
// Set up the destination capability.
c1_reg()->ResetExecuteRoot();
c1_reg()->set_address(kInstAddress + 0x200);
c1_reg()->SetBounds(kInstAddress, 0x400);
(void)c1_reg()->Seal(*(state()->sealing_root()),
OT::kInterruptEnablingSentry);
// Set offset to zero (because c1_reg is sealed).
c2_reg()->set_address(0);
// Set interrupt enable to false.
state()->mstatus()->set_mie(0);
state()->mstatus()->Submit();
inst()->Execute(nullptr);
EXPECT_TRUE(state()->mstatus()->mie());
EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
EXPECT_TRUE(cra_reg()->tag());
EXPECT_TRUE(cra_reg()->IsSentry());
EXPECT_EQ(cra_reg()->object_type(), OT::kInterruptDisablingReturnSentry);
EXPECT_TRUE(state()->pcc()->tag());
// Set up the destination capability.
c1_reg()->ResetExecuteRoot();
c1_reg()->SetBounds(kInstAddress, 0x400);
c1_reg()->set_address(kInstAddress + 0x200);
(void)c1_reg()->Seal(*(state()->sealing_root()),
OT::kInterruptDisablingSentry);
// Set interrupt enable to true.
state()->mstatus()->set_mie(1);
state()->mstatus()->Submit();
state()->pcc()->set_address(inst()->address());
inst()->Execute(nullptr);
EXPECT_FALSE(state()->mstatus()->mie());
EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
EXPECT_TRUE(cra_reg()->tag());
EXPECT_TRUE(cra_reg()->IsSentry());
EXPECT_EQ(cra_reg()->object_type(), OT::kInterruptEnablingReturnSentry);
EXPECT_TRUE(state()->pcc()->tag());
}
// Verify an unset tag generates a tag violation exception.
TEST_F(RiscVCheriotInstructionsTest, CJalrTagViolation) {
inst()->set_semantic_function(&CheriotCJalrCra);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kCra});
state()->pcc()->set_address(inst()->address());
// Set up the destination capability.
c1_reg()->ResetExecuteRoot();
c1_reg()->SetBounds(kInstAddress, 0x400);
c1_reg()->set_address(kInstAddress + 0x200);
// Clear c1_reg tag.
c1_reg()->Invalidate();
// Set offset to non-zero.
c2_reg()->set_address(0);
inst()->Execute(nullptr);
EXPECT_EQ(state()->pcc()->address(), inst()->address());
EXPECT_FALSE(c3_reg()->tag());
EXPECT_FALSE(c3_reg()->IsSentry());
EXPECT_TRUE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), kC1Num << 5 | *CH_EC::kCapExTagViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// For a jalr with a sentry, the immediate has to be zero or it will cause
// an exception. Make sure the exception happens.
TEST_F(RiscVCheriotInstructionsTest, CJalrSentryNonZeroImmediate) {
inst()->set_semantic_function(&CheriotCJalrCra);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kCra});
state()->pcc()->set_address(inst()->address());
// Set up the destination capability.
c1_reg()->ResetExecuteRoot();
c1_reg()->set_address(kInstAddress + 0x200);
c1_reg()->SetBounds(kInstAddress, 0x400);
(void)c1_reg()->Seal(*(state()->sealing_root()),
OT::kInterruptEnablingSentry);
// Set offset to non-zero - should cause an exception.
c2_reg()->set_address(0x100);
inst()->Execute(nullptr);
EXPECT_EQ(state()->pcc()->address(), inst()->address());
EXPECT_FALSE(c3_reg()->tag());
EXPECT_FALSE(c3_reg()->IsSentry());
EXPECT_TRUE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), kC1Num << 5 | *CH_EC::kCapExSealViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// If the source capability does not have execute permission, there should
// be an exception.
TEST_F(RiscVCheriotInstructionsTest, CJalrExecuteViolation) {
inst()->set_semantic_function(&CheriotCJalrCra);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kCra});
state()->pcc()->set_address(inst()->address());
// Set up the destination capability.
c1_reg()->ResetExecuteRoot();
c1_reg()->SetBounds(kInstAddress, 0x400);
c1_reg()->set_address(kInstAddress + 0x200);
c1_reg()->ClearPermissions(PB::kPermitExecute);
// Clear c1_reg tag.
c1_reg()->Invalidate();
// Set offset to non-zero.
c2_reg()->set_address(0);
inst()->Execute(nullptr);
EXPECT_EQ(state()->pcc()->address(), inst()->address());
EXPECT_FALSE(c3_reg()->tag());
EXPECT_FALSE(c3_reg()->IsSentry());
EXPECT_TRUE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), kC1Num << 5 | *CH_EC::kCapExTagViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// If the architecture does not have compact instructions, then misaligned
// access on two byte boundary should cause an exception.
TEST_F(RiscVCheriotInstructionsTest, CJalrAlignmentViolation) {
inst()->set_semantic_function(&CheriotCJalrCra);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
state()->pcc()->set_address(inst()->address());
// Set up the destination capability.
c1_reg()->ResetExecuteRoot();
c1_reg()->set_address(kInstAddress + 0x200);
// Set offset to non-zero.
c2_reg()->set_address(2);
// Clear the compressed isa bit.
state()->misa()->Set(state()->misa()->AsUint64() & ~*ISA::kCompressed);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
<< " value: " << trap_value();
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), kInstAddress + 0x202);
EXPECT_EQ(trap_exception_code(), *RV_EC::kInstructionAddressMisaligned);
EXPECT_EQ(inst(), trap_inst());
}
// Check load capability - no traps.
TEST_F(RiscVCheriotInstructionsTest, CLc) {
SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x200);
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken());
EXPECT_TRUE(*c3_reg() == *state()->memory_root());
}
// Load without global flag should clear global flag of loaded capability.
TEST_F(RiscVCheriotInstructionsTest, CLcNoLoadGlobal) {
c4_reg()->ResetMemoryRoot();
c4_reg()->ClearPermissions(PB::kPermitGlobal | PB::kPermitLoadGlobal);
SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->ClearPermissions(PB::kPermitLoadGlobal);
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x200);
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken());
EXPECT_FALSE(*c3_reg() == *state()->memory_root());
EXPECT_TRUE(*c3_reg() == *c4_reg());
}
// Load without mutable flag should clear mutable and store permissions of
// unsealed capability.
TEST_F(RiscVCheriotInstructionsTest, CLcNoLoadMutableUnsealed) {
c4_reg()->ResetMemoryRoot();
c4_reg()->ClearPermissions(PB::kPermitLoadMutable | PB::kPermitStore);
SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->ClearPermissions(PB::kPermitLoadMutable);
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x200);
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken());
EXPECT_FALSE(*c3_reg() == *state()->memory_root());
EXPECT_TRUE(*c3_reg() == *c4_reg());
}
// Load without mutable flag should not clear mutable and store permissions of
// sealed capability.
TEST_F(RiscVCheriotInstructionsTest, CLcNoLoadMutableSealed) {
c4_reg()->ResetMemoryRoot();
(void)c4_reg()->Seal(*(state()->sealing_root()), kDataSeal10);
SetUpForLoadCapabilityTest(kMemAddress + 0x200, c4_reg());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->ClearPermissions(PB::kPermitLoadMutable);
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x200);
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken());
EXPECT_TRUE(*c3_reg() == *c4_reg());
}
TEST_F(RiscVCheriotInstructionsTest, CLcNoLoadStoreCapability) {
SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->ClearPermissions(PB::kPermitLoadStoreCapability);
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x200);
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken());
// Result should be equal to memory root, but without the valid tag and some
// permissions removed.
c4_reg()->ResetMemoryRoot();
c4_reg()->set_address(0xdeadbeef);
c4_reg()->ClearPermissions(
PB::kPermitGlobal | PB::kPermitLoadGlobal | PB::kPermitLoadMutable |
PB::kPermitStoreLocalCapability | PB::kPermitStore);
c4_reg()->Invalidate();
EXPECT_TRUE(*c3_reg() == *c4_reg()) << "c3_reg(): " << c3_reg()->AsString()
<< "\nc4_reg(): " << c4_reg()->AsString();
}
// Check load capability with invalid capability.
TEST_F(RiscVCheriotInstructionsTest, CLcTagViolation) {
SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c1_reg()->Invalidate();
c2_reg()->set_address(0x200);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExTagViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// Check load capability with sealed capability.
TEST_F(RiscVCheriotInstructionsTest, CLcSealViolation) {
SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
(void)c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10);
c2_reg()->set_address(0x200);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExSealViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// Check load capability with no load permission.
TEST_F(RiscVCheriotInstructionsTest, CLcPermitLoadViolation) {
SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c1_reg()->ClearPermissions(PB::kPermitLoad);
c2_reg()->set_address(0x200);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExPermitLoadViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// Check load capability with bounds violation.
TEST_F(RiscVCheriotInstructionsTest, CLcPermitBoundsViolation) {
SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c1_reg()->SetBounds(0, kMemAddress + 0x100);
c2_reg()->set_address(0x200);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExBoundsViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// Check load capability with unaligned address.
TEST_F(RiscVCheriotInstructionsTest, CLcUnaligned) {
SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
AppendCapabilityOperands(inst(), {kC1, kC2}, {});
AppendCapabilityOperands(inst()->child(), {}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x201);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), kMemAddress + 0x201);
EXPECT_EQ(trap_exception_code(), *RV_EC::kLoadAddressMisaligned);
EXPECT_EQ(inst(), trap_inst());
}
// Verify that copy works.
TEST_F(RiscVCheriotInstructionsTest, CMove) {
inst()->set_semantic_function(&CheriotCMove);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
c1_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_TRUE(*c1_reg() == *c3_reg());
c1_reg()->ResetExecuteRoot();
inst()->Execute(nullptr);
EXPECT_TRUE(*c3_reg() == *c1_reg());
c1_reg()->ResetSealingRoot();
inst()->Execute(nullptr);
EXPECT_TRUE(*c3_reg() == *c1_reg());
c1_reg()->ResetNull();
inst()->Execute(nullptr);
EXPECT_TRUE(*c3_reg() == *c1_reg());
}
// Verify that CRepresentableAlignmentMask return the correct mask.
TEST_F(RiscVCheriotInstructionsTest, CRepresentableAlignmentMask) {
inst()->set_semantic_function(&CheriotCRepresentableAlignmentMask);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
std::array fixed_values{5039028};
for (int i = 0; i < 1000; i++) {
uint32_t len;
if (i < fixed_values.size()) {
len = fixed_values[i];
} else {
len = absl::Uniform(absl::IntervalClosed, bitgen(), 0ULL,
std::numeric_limits<uint32_t>::max());
}
c1_reg()->set_address(len);
inst()->Execute(nullptr);
uint32_t alignment;
if (len <= 511)
alignment = 1;
else if (len <= 1022)
alignment = 2;
else if (len <= 2044)
alignment = 4;
else if (len <= 4088)
alignment = 8;
else if (len <= 8176)
alignment = 16;
else if (len <= 16352)
alignment = 32;
else if (len <= 32704)
alignment = 64;
else if (len <= 65408)
alignment = 128;
else if (len <= 130816)
alignment = 256;
else if (len <= 261632)
alignment = 512;
else if (len <= 523264)
alignment = 1024;
else if (len <= 1046528)
alignment = 2048;
else if (len <= 2093056)
alignment = 4096;
else if (len <= 4186112)
alignment = 8192;
else if (len <= 8372224)
alignment = 16384;
else
alignment = 16777216;
uint32_t mask = ~(alignment - 1);
EXPECT_EQ(mask, c3_reg()->address())
<< "len: " << len << " exp alignment: " << alignment
<< " alignment: " << (~(c3_reg()->address()) + 1) << std::hex
<< " mask: " << mask << " c3_reg: " << c3_reg()->address();
}
}
// Verify that round to representable length works properly. The key here is
// that the result of this instruction should be the minimum length >= the
// given length that can be used for exact bounds assuming a suitably aligned
// base address.
TEST_F(RiscVCheriotInstructionsTest, CRoundRepresentableLength) {
inst()->set_semantic_function(&CheriotCRoundRepresentableLength);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
for (int i = 0; i < 1000; i++) {
uint32_t len = absl::Uniform(absl::IntervalClosed, bitgen(), 0ULL,
std::numeric_limits<uint32_t>::max());
c1_reg()->set_address(len);
inst()->Execute(nullptr);
uint32_t alignment;
if (len <= 511)
alignment = 1;
else if (len <= 1022)
alignment = 2;
else if (len <= 2044)
alignment = 4;
else if (len <= 4088)
alignment = 8;
else if (len <= 8176)
alignment = 16;
else if (len <= 16352)
alignment = 32;
else if (len <= 32704)
alignment = 64;
else if (len <= 65408)
alignment = 128;
else if (len <= 130816)
alignment = 256;
else if (len <= 261632)
alignment = 512;
else if (len <= 523264)
alignment = 1024;
else if (len <= 1046528)
alignment = 2048;
else if (len <= 2093056)
alignment = 4096;
else if (len <= 4186112)
alignment = 8192;
else if (len <= 8372224)
alignment = 16384;
else
alignment = 16777216;
uint32_t length = alignment * ((len + alignment - 1) / alignment);
EXPECT_EQ(length, c3_reg()->address());
}
}
// Check store capability - no traps.
TEST_F(RiscVCheriotInstructionsTest, CSc) {
inst()->set_semantic_function(&CheriotCSc);
AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x200);
c3_reg()->ResetSealingRoot();
c3_reg()->set_address(kDataSeal10);
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken());
auto *db = state()->db_factory()->Allocate<uint32_t>(2);
memory()->Load(kMemAddress + 0x200, db, nullptr, nullptr);
EXPECT_EQ(db->Get<uint32_t>(0), c3_reg()->address());
EXPECT_EQ(db->Get<uint32_t>(1), c3_reg()->Compress());
db->DecRef();
}
// Check store capability with invalid capability.
TEST_F(RiscVCheriotInstructionsTest, CScTagViolation) {
inst()->set_semantic_function(&CheriotCSc);
AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c1_reg()->Invalidate();
c2_reg()->set_address(0x200);
c3_reg()->ResetSealingRoot();
c3_reg()->set_address(kDataSeal10);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExTagViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// Check store capability with sealed capability.
TEST_F(RiscVCheriotInstructionsTest, CScSealViolation) {
inst()->set_semantic_function(&CheriotCSc);
AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
(void)c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10);
c2_reg()->set_address(0x200);
c3_reg()->ResetSealingRoot();
c3_reg()->set_address(kDataSeal10);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExSealViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// Check store capability with no load permission.
TEST_F(RiscVCheriotInstructionsTest, CScPermitStoreViolation) {
inst()->set_semantic_function(&CheriotCSc);
AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c1_reg()->ClearPermissions(PB::kPermitStore);
c2_reg()->set_address(0x200);
c3_reg()->ResetSealingRoot();
c3_reg()->set_address(kDataSeal10);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExPermitStoreViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// Check store capability with no load store capability permission.
TEST_F(RiscVCheriotInstructionsTest, CScPermitStoreCapViolation) {
inst()->set_semantic_function(&CheriotCSc);
AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c1_reg()->ClearPermissions(PB::kPermitLoadStoreCapability);
c2_reg()->set_address(0x200);
c3_reg()->ResetSealingRoot();
c3_reg()->set_address(kDataSeal10);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(),
(kC1Num << 5) | *CH_EC::kCapExPermitStoreCapabilityViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// Check for proper generation of store local cap violation.
TEST_F(RiscVCheriotInstructionsTest, CScStoreLocalCapViolation) {
inst()->set_semantic_function(&CheriotCSc);
AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c1_reg()->ClearPermissions(PB::kPermitStoreLocalCapability);
c2_reg()->set_address(0x200);
c3_reg()->ResetMemoryRoot();
c3_reg()->ClearPermissions(PB::kPermitGlobal);
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken());
auto *db = state()->db_factory()->Allocate<uint32_t>(2);
memory()->Load(kMemAddress + 0x200, db, nullptr, nullptr);
// Invalidate c3 - the stored tag should be the same as c3, but with the tag
// cleared.
c3_reg()->Invalidate();
EXPECT_EQ(db->Get<uint32_t>(0), c3_reg()->address());
EXPECT_EQ(db->Get<uint32_t>(1), c3_reg()->Compress());
db->DecRef();
}
// Check store capability with bounds violation.
TEST_F(RiscVCheriotInstructionsTest, CScPermitBoundsViolation) {
inst()->set_semantic_function(&CheriotCSc);
AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c1_reg()->SetBounds(0, kMemAddress + 0x100);
c2_reg()->set_address(0x200);
c3_reg()->ResetSealingRoot();
c3_reg()->set_address(kDataSeal10);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExBoundsViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
// Check store capability with unaligned address.
TEST_F(RiscVCheriotInstructionsTest, CScUnaligned) {
inst()->set_semantic_function(&CheriotCSc);
AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x201);
c3_reg()->ResetSealingRoot();
c3_reg()->set_address(kDataSeal10);
inst()->Execute(nullptr);
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(), kMemAddress + 0x201);
EXPECT_EQ(trap_exception_code(), *RV_EC::kStoreAddressMisaligned);
EXPECT_EQ(inst(), trap_inst());
}
TEST_F(RiscVCheriotInstructionsTest, CSeal) {
inst()->set_semantic_function(&CheriotCSeal);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c2_reg()->ResetSealingRoot();
// Memory sealing.
c1_reg()->ResetMemoryRoot();
for (uint32_t o_type = 0; o_type < 18; o_type++) {
c2_reg()->set_address(o_type);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->object_type(), o_type & 0b111);
// For executable or illegal object types the tag will be false.
if ((o_type <= 8) || (o_type > 15)) {
EXPECT_FALSE(c3_reg()->tag());
} else {
EXPECT_TRUE(c3_reg()->tag());
}
}
// Executable sealing.
c1_reg()->ResetExecuteRoot();
for (uint32_t o_type = 0; o_type < 18; o_type++) {
c2_reg()->set_address(o_type);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->object_type(), o_type & 0b111);
// For executable object types the tag should be true.
if ((o_type == OT::kSentry) || (o_type == OT::kInterruptDisablingSentry) ||
(o_type == OT::kInterruptEnablingSentry) ||
(o_type == OT::kSealedExecutable6) ||
(o_type == OT::kSealedExecutable7)) {
EXPECT_TRUE(c3_reg()->tag());
} else {
EXPECT_FALSE(c3_reg()->tag());
}
}
// Sealing type outside range.
EXPECT_TRUE(c2_reg()->SetBounds(0, 12));
c2_reg()->set_address(14);
c1_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->object_type(), 14 & 0b111);
EXPECT_FALSE(c3_reg()->tag());
// Attempt sealing using a sealed capability.
c2_reg()->ResetSealingRoot();
c2_reg()->set_address(kDataSeal10);
EXPECT_TRUE(c2_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->object_type(), 10 & 0b111);
EXPECT_FALSE(c3_reg()->tag());
}
TEST_F(RiscVCheriotInstructionsTest, CSetAddr) {
inst()->set_semantic_function(&CheriotCSetAddr);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c1_reg()->ResetMemoryRoot();
EXPECT_EQ(c1_reg()->address(), 0);
c2_reg()->set_address(kMemAddress);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), kMemAddress);
EXPECT_TRUE(c3_reg()->tag());
// If c1 is sealed, the tag is cleared.
EXPECT_TRUE(c1_reg()->Seal(*(state()->sealing_root()), 9).ok());
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), kMemAddress);
EXPECT_FALSE(c3_reg()->tag());
// If address is out of range, the tag is cleared.
c1_reg()->ResetMemoryRoot();
c1_reg()->SetBounds(0, 200);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), kMemAddress);
EXPECT_FALSE(c3_reg()->tag());
}
TEST_F(RiscVCheriotInstructionsTest, CSetBounds) {
inst()->set_semantic_function(&CheriotCSetBounds);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c1_reg()->ResetMemoryRoot();
// Set the requested new base.
c1_reg()->set_address(kMemAddress);
for (uint32_t len = 1; len < 0x8000'0000; len <<= 1) {
// Set the requested new length.
c2_reg()->set_address(len);
inst()->Execute(nullptr);
// The bounds will be no smaller than requested.
EXPECT_LE(c3_reg()->base(), kMemAddress);
EXPECT_GE(c3_reg()->length(), len);
EXPECT_TRUE(c3_reg()->tag());
}
// Request bounds outside the capability - first base below.
c1_reg()->SetBounds(kMemAddress, 0x200);
c1_reg()->set_address(0);
c2_reg()->set_address(0x100);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->base(), 0);
EXPECT_EQ(c3_reg()->length(), 0x100);
EXPECT_FALSE(c3_reg()->tag());
// Next, length too long.
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x300);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->base(), kMemAddress);
EXPECT_EQ(c3_reg()->length(), 0x300);
EXPECT_FALSE(c3_reg()->tag());
// Base too high.
c1_reg()->set_address(kMemAddress * 2);
c2_reg()->set_address(0x100);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->base(), kMemAddress * 2);
EXPECT_EQ(c3_reg()->length(), 0x100);
EXPECT_FALSE(c3_reg()->tag());
// Sealed capability.
c1_reg()->ResetMemoryRoot();
EXPECT_TRUE(c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x100);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->base(), kMemAddress);
EXPECT_EQ(c3_reg()->length(), 0x100);
EXPECT_FALSE(c3_reg()->tag());
}
TEST_F(RiscVCheriotInstructionsTest, CSetBoundsExact) {
inst()->set_semantic_function(&CheriotCSetBoundsExact);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c1_reg()->ResetMemoryRoot();
// Set the requested new base.
c1_reg()->set_address(kMemAddress);
for (uint32_t len = 1; len < 0x8000'0000; len <<= 1) {
// Set the requested new length.
c2_reg()->set_address(len);
inst()->Execute(nullptr);
// The bounds will be no smaller than requested.
EXPECT_LE(c3_reg()->base(), kMemAddress);
EXPECT_GE(c3_reg()->length(), len);
// If they are not exactly what were requested, the tag will be false.
if ((c3_reg()->length() != len) || (c3_reg()->base() != kMemAddress)) {
EXPECT_FALSE(c3_reg()->tag());
} else {
EXPECT_TRUE(c3_reg()->tag());
}
}
// Request bounds outside the capability - first base below.
c1_reg()->SetBounds(kMemAddress, 0x200);
c1_reg()->set_address(0);
c2_reg()->set_address(0x100);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->base(), 0);
EXPECT_EQ(c3_reg()->length(), 0x100);
EXPECT_FALSE(c3_reg()->tag());
// Next, length too long.
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x300);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->base(), kMemAddress);
EXPECT_EQ(c3_reg()->length(), 0x300);
EXPECT_FALSE(c3_reg()->tag());
// Base too high.
c1_reg()->set_address(kMemAddress * 2);
c2_reg()->set_address(0x100);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->base(), kMemAddress * 2);
EXPECT_EQ(c3_reg()->length(), 0x100);
EXPECT_FALSE(c3_reg()->tag());
// Sealed capability.
c1_reg()->ResetMemoryRoot();
EXPECT_TRUE(c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
c1_reg()->set_address(kMemAddress);
c2_reg()->set_address(0x100);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->base(), kMemAddress);
EXPECT_EQ(c3_reg()->length(), 0x100);
EXPECT_FALSE(c3_reg()->tag());
}
TEST_F(RiscVCheriotInstructionsTest, CSetEqualExact) {
inst()->set_semantic_function(&CheriotCSetEqualExact);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c1_reg()->ResetMemoryRoot();
c2_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 1);
// Change c1.
c1_reg()->ResetExecuteRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0);
// Change c2 too.
c2_reg()->ResetExecuteRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 1);
// Change c1 to sealing root.
c1_reg()->ResetSealingRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0);
// Change c2 too.
c2_reg()->ResetSealingRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 1);
}
TEST_F(RiscVCheriotInstructionsTest, CSetHigh) {
inst()->set_semantic_function(&CheriotCSetHigh);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c1_reg()->ResetMemoryRoot();
c1_reg()->set_address(kMemAddress + 10);
// Initialize another capability register.
c4_reg()->ResetMemoryRoot();
c4_reg()->SetBounds(kMemAddress, 200);
uint32_t high = c4_reg()->Compress();
c2_reg()->set_address(high);
inst()->Execute(nullptr);
// Tag should be cleared.
EXPECT_FALSE(c3_reg()->tag());
// Other fields should be the same.
EXPECT_EQ(c3_reg()->address(), c1_reg()->address());
EXPECT_EQ(c3_reg()->base(), c4_reg()->base());
EXPECT_EQ(c3_reg()->length(), c4_reg()->length());
EXPECT_EQ(c3_reg()->permissions(), c4_reg()->permissions());
EXPECT_EQ(c3_reg()->object_type(), c4_reg()->object_type());
}
TEST_F(RiscVCheriotInstructionsTest, CSpecialR) {
inst()->set_semantic_function(&CheriotCSpecialR);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
c1_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken());
EXPECT_TRUE(*c1_reg() == *c3_reg());
}
TEST_F(RiscVCheriotInstructionsTest, CSpecialRException) {
inst()->set_semantic_function(&CheriotCSpecialR);
AppendCapabilityOperands(inst(), {kC1}, {kC3});
c1_reg()->ResetMemoryRoot();
// Remove system registers access permission.
state()->pcc()->ClearPermissions(PB::kPermitAccessSystemRegisters);
inst()->Execute(nullptr);
// C3 should be null capability, just like c2 is.
EXPECT_TRUE(*c3_reg() == *c4_reg());
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(),
kPccNum << 5 | *CH_EC::kCapExPermitAccessSystemRegistersViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
TEST_F(RiscVCheriotInstructionsTest, CSpecialRW) {
inst()->set_semantic_function(&CheriotCSpecialRW);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c1_reg()->ResetMemoryRoot();
c2_reg()->ResetSealingRoot();
inst()->Execute(nullptr);
EXPECT_FALSE(trap_taken());
EXPECT_TRUE(*state()->sealing_root() == *c3_reg());
EXPECT_TRUE(*state()->memory_root() == *c2_reg());
}
TEST_F(RiscVCheriotInstructionsTest, CSpecialRWException) {
inst()->set_semantic_function(&CheriotCSpecialRW);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c1_reg()->ResetMemoryRoot();
c2_reg()->ResetSealingRoot();
// Remove system registers access permission.
state()->pcc()->ClearPermissions(PB::kPermitAccessSystemRegisters);
inst()->Execute(nullptr);
// C3 is unmodified.
EXPECT_TRUE(*c3_reg() == *c4_reg());
EXPECT_TRUE(trap_taken());
EXPECT_FALSE(trap_is_interrupt());
EXPECT_EQ(trap_epc(), kInstAddress);
EXPECT_EQ(trap_value(),
kPccNum << 5 | *CH_EC::kCapExPermitAccessSystemRegistersViolation);
EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
EXPECT_EQ(inst(), trap_inst());
}
TEST_F(RiscVCheriotInstructionsTest, CSub) {
inst()->set_semantic_function(&CheriotCSub);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
for (int i = 0; i < 100; i++) {
c3_reg()->ResetMemoryRoot();
// Generate random address and compressed capability.
uint32_t val0 = absl::Uniform(absl::IntervalClosed, bitgen(), 0ULL,
std::numeric_limits<uint32_t>::max());
uint32_t val1 = absl::Uniform(absl::IntervalClosed, bitgen(), 0ULL,
std::numeric_limits<uint32_t>::max());
c1_reg()->set_address(val0);
c2_reg()->set_address(val1);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), val0 - val1);
EXPECT_FALSE(c3_reg()->tag());
}
}
// Tests if cs2 is a subset of cs1.
TEST_F(RiscVCheriotInstructionsTest, CTestSubset) {
inst()->set_semantic_function(&CheriotCTestSubset);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c1_reg()->ResetMemoryRoot();
c2_reg()->ResetMemoryRoot();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 1);
// Narrow c1 bounds, now the result should be 0.
c1_reg()->set_address(kMemAddress);
c1_reg()->SetBounds(kMemAddress, 0x400);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0);
// Make c2 bounds narrower than c1, result should be 1.
c2_reg()->set_address(kMemAddress + 0x100);
c2_reg()->SetBounds(kMemAddress + 0x100, 0x200);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 1);
// Remove a permission bit from c1, result should be 0.
c1_reg()->ClearPermissions(PB::kPermitGlobal);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0);
// Remove that and another bit from c2 result should be 1.
c2_reg()->ClearPermissions(PB::kPermitGlobal | PB::kPermitLoadGlobal);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 1);
// If c1 is invalidated, then the result is 0.
c1_reg()->Invalidate();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 0);
// If c2 is also invalidated, the result is 1.
c2_reg()->Invalidate();
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->address(), 1);
}
TEST_F(RiscVCheriotInstructionsTest, CUnseal) {
inst()->set_semantic_function(&CheriotCUnseal);
AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
c2_reg()->ResetSealingRoot();
// Set unsealing cap address to 10.
c2_reg()->set_address(kDataSeal10);
// If c1 is unsealed, it fails.
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
EXPECT_FALSE(c3_reg()->tag());
EXPECT_EQ(c3_reg()->permissions(), c1_reg()->permissions());
// Seal c1.
c1_reg()->ResetMemoryRoot();
// Seal c1 with otype 10.
EXPECT_TRUE(c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
EXPECT_TRUE(c3_reg()->tag());
EXPECT_EQ(c3_reg()->permissions(), c1_reg()->permissions());
// Remove global permission from c2. The resulting capability will have it
// removed too.
c2_reg()->ClearPermissions(PB::kPermitGlobal);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
EXPECT_TRUE(c3_reg()->tag());
EXPECT_NE(c3_reg()->permissions(), c1_reg()->permissions());
EXPECT_EQ(c3_reg()->permissions() | PB::kPermitGlobal,
c1_reg()->permissions());
// Set the wrong unsealing value.
c2_reg()->ResetSealingRoot();
c2_reg()->set_address(11);
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
EXPECT_FALSE(c3_reg()->tag());
EXPECT_EQ(c3_reg()->permissions(), c1_reg()->permissions());
// If c2 is sealed it fails.
c2_reg()->set_address(kDataSeal10);
EXPECT_TRUE(c2_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
inst()->Execute(nullptr);
EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
EXPECT_FALSE(c3_reg()->tag());
EXPECT_EQ(c3_reg()->permissions(), c1_reg()->permissions());
}
} // namespace