ALL CHERI/MIPS Instructinos are declared as emulating functions in
target/mips/helper.h
, with macro DEF_HELPER_*
.
Implemented in
target/mips/op_helper_cheri.c
, using macro CHERI_HELPER_IMPL
.
For example, cseal
instruction is declared as DEF_HELPER_4(cseal, void, env, i32, i32, i32)
, and implemented as void CHERI_HELPER_IMPL(cseal)(CPUMIPSState *env, uint32_t cd, uint32_t cs, uint32_t ct)
.
DEF_HELPER_2(ccheck_pc, void, env, i64)
CHERI_HELPER_IMPL(ccheck_pc)(CPUMIPSState *env, uint64_t next_pc)
will always be checked on ld/st without explicit instructions. see check_cap.
Problem: could we do PCC/DDC and pointer checks on their types? assuming types are already available (even if they are not currently).
DEF_HELPER_3(ccheck_store, tl, env, tl, i32)
DEF_HELPER_3(ccheck_store_right, tl, env, tl, i32)
DEF_HELPER_3(ccheck_load, tl, env, tl, i32)
DEF_HELPER_3(ccheck_load_right, tl, env, tl, i32)
DEF_HELPER_1(ccheck_btarget, void, env)
DEF_HELPER_3(cchecktype, void, env, i32, i32)
DEF_HELPER_3(ccheckperm, void, env, i32, tl)
DEF_HELPER_4(candperm, void, env, i32, i32, tl)
DEF_HELPER_4(cseal, void, env, i32, i32, i32)
DEF_HELPER_4(ccseal, void, env, i32, i32, i32) // ??????
DEF_HELPER_4(cunseal, void, env, i32, i32, i32)
DEF_HELPER_4(ccopytype, void, env, i32, i32, i32)
DEF_HELPER_3(csealentry, void, env, i32, i32) // ??????
DEF_HELPER_3(ccall, void, env, i32, i32)
DEF_HELPER_3(ccall_notrap, tl, env, i32, i32)
DEF_HELPER_5(cinvalidate_tag, void, env, tl, i32, i32, tl)
DEF_HELPER_5(cinvalidate_tag_left_right, void, env, tl, i32, i32, tl)
DEF_HELPER_5(cinvalidate_tag32, void, env, tl, i32, i32, i32)
DEF_HELPER_3(ccleartag, void, env, i32, i32)
DEF_HELPER_3(cloadtags, tl, env, i32, i64)
DEF_HELPER_5(cload, tl, env, i32, tl, i32, i32)
DEF_HELPER_5(cstore, tl, env, i32, tl, i32, i32)
DEF_HELPER_3(cloadlinked, tl, env, i32, i32)
DEF_HELPER_3(cstorecond, tl, env, i32, i32)
DEF_HELPER_3(cscc_without_tcg, tl, env, i32, i32)
DEF_HELPER_5(csc_without_tcg, void, env, i32, i32, tl, i32)
DEF_HELPER_5(clc_without_tcg, void, env, i32, i32, tl, i32)
DEF_HELPER_3(cllc_without_tcg, void, env, i32, i32)
/* cannot access EPC directly since it is the offset of EPCC */
DEF_HELPER_1(mfc0_epc, tl, env)
DEF_HELPER_2(mtc0_epc, void, env, tl)
DEF_HELPER_1(mfc0_error_epc, tl, env)
DEF_HELPER_2(mtc0_error_epc, void, env, tl)
DEF_HELPER_2(cclearreg, void, env, i32)
DEF_HELPER_4(cfromptr, void, env, i32, i32, tl)
DEF_HELPER_2(cgetaddr, tl, env, i32)
DEF_HELPER_2(cgetbase, tl, env, i32)
DEF_HELPER_1(cgetcause, tl, env)
DEF_HELPER_2(cgetlen, tl, env, i32)
DEF_HELPER_2(cgetoffset, tl, env, i32)
DEF_HELPER_2(cgetpcc, void, env, i32)
DEF_HELPER_3(cgetpccsetoffset, void, env, i32, tl)
DEF_HELPER_2(cgetperm, tl, env, i32)
DEF_HELPER_2(cgetsealed, tl, env, i32)
DEF_HELPER_2(cgettag, tl, env, i32)
DEF_HELPER_2(cgettype, tl, env, i32)
DEF_HELPER_4(cincbase, void, env, i32, i32, tl)
DEF_HELPER_4(cincoffset, void, env, i32, i32, tl)
DEF_HELPER_3(cjalr, tl, env, i32, i32)
DEF_HELPER_2(cjr, tl, env, i32)
DEF_HELPER_1(creturn, void, env)
DEF_HELPER_4(csetbounds, void, env, i32, i32, tl)
DEF_HELPER_4(csetboundsexact, void, env, i32, i32, tl)
DEF_HELPER_2(crap, tl, env, tl) // ???????
DEF_HELPER_2(cram, tl, env, tl) // ???????
DEF_HELPER_3(csub, tl, env, i32, i32)
DEF_HELPER_2(csetcause, void, env, tl)
DEF_HELPER_4(csetlen, void, env, i32, i32, tl)
DEF_HELPER_4(csetoffset, void, env, i32, i32, tl)
DEF_HELPER_3(ctoptr, tl, env, i32, i32)
DEF_HELPER_4(cmovz, void, env, i32, i32, tl)
DEF_HELPER_4(cmovn, void, env, i32, i32, tl)
DEF_HELPER_3(ceq, tl, env, i32, i32)
DEF_HELPER_3(cne, tl, env, i32, i32)
DEF_HELPER_3(clt, tl, env, i32, i32)
DEF_HELPER_3(cle, tl, env, i32, i32)
DEF_HELPER_3(cltu, tl, env, i32, i32)
DEF_HELPER_3(cleu, tl, env, i32, i32)
DEF_HELPER_3(cexeq, tl, env, i32, i32)
DEF_HELPER_3(cnexeq, tl, env, i32, i32)
DEF_HELPER_4(csetaddr, void, env, i32, i32, tl)
DEF_HELPER_3(cgetandaddr, tl, env, i32, tl)
DEF_HELPER_4(candaddr, void, env, i32, i32, tl)
DEF_HELPER_3(ctestsubset, tl, env, i32, i32)
DEF_HELPER_2(rdhwr_statcounters_icount, tl, env, i32)
DEF_HELPER_1(rdhwr_statcounters_reset, tl, env)
DEF_HELPER_1(rdhwr_statcounters_itlb_miss, tl, env)
DEF_HELPER_1(rdhwr_statcounters_dtlb_miss, tl, env)
DEF_HELPER_2(rdhwr_statcounters_memory, tl, env, i32)
DEF_HELPER_2(rdhwr_statcounters_ignored, tl, env, i32)
check_cap()
is called by:
check_ddc(CPUMIPSState *env, uint32_t perm, uint64_t ddc_offset, uint32_t len, bool instavail, uintptr_t retpc)
// file
// target/mips/op_helper_cheri.c
static inline void check_cap(CPUMIPSState *env, const cap_register_t *cr,
uint32_t perm, uint64_t addr, uint16_t regnum, uint32_t len, bool instavail, uintptr_t pc)
{
uint16_t cause;
/*
* See section 5.6 in CHERI Architecture.
*
* Capability checks (in order of priority):
* (1) <ctag> must be set (CP2Ca_TAG Violation).
* (2) Seal bit must be unset (CP2Ca_SEAL Violation).
* (3) <perm> permission must be set (CP2Ca_PERM_EXE, CP2Ca_PERM_LD,
* or CP2Ca_PERM_ST Violation).
* (4) <addr> must be within bounds (CP2Ca_LENGTH Violation).
*/
if (!cr->cr_tag) {
cause = CP2Ca_TAG;
// fprintf(qemu_logfile, "CAP Tag VIOLATION: ");
goto do_exception;
}
if (is_cap_sealed(cr)) {
cause = CP2Ca_SEAL;
// fprintf(qemu_logfile, "CAP Seal VIOLATION: ");
goto do_exception;
}
if ((cr->cr_perms & perm) != perm) {
switch (perm) {
case CAP_PERM_EXECUTE:
cause = CP2Ca_PERM_EXE;
// fprintf(qemu_logfile, "CAP Exe VIOLATION: ");
goto do_exception;
case CAP_PERM_LOAD:
cause = CP2Ca_PERM_LD;
// fprintf(qemu_logfile, "CAP LD VIOLATION: ");
goto do_exception;
case CAP_PERM_STORE:
cause = CP2Ca_PERM_ST;
// fprintf(qemu_logfile, "CAP ST VIOLATION: ");
goto do_exception;
default:
break;
}
}
// fprintf(stderr, "addr=%zx, len=%zd, cr_base=%zx, cr_len=%zd\n",
// (size_t)addr, (size_t)len, (size_t)cr->cr_base, (size_t)cr->cr_length);
if (!cap_is_in_bounds(cr, addr, len)) {
cause = CP2Ca_LENGTH;
// fprintf(qemu_logfile, "CAP Len VIOLATION: ");
goto do_exception;
}
return;
do_exception:
env->CP0_BadVAddr = addr;
if (!instavail)
env->error_code |= EXCP_INST_NOTAVAIL;
do_raise_c2_exception_impl(env, cause, regnum, pc);
}
cap_is_in_bounds()
, called by functions in target/mips/op_helper_cheri.c
check_cap()
, ccall_common()
, cseal_common()
CHERI_HELPER_IMPL(cjalr)
,
(cjr)
, (cunseal)
,(cload)
, (cloadlinked)
, (cstorecond)
,(cstore)
,get_clc_addr()
, get_cllc_addr()
, get_csc_addr()
, get_cscc_addr()
// file
// target/mips/cheri_util.h
// Check if num_bytes bytes at addr can be read using capability c
static inline bool cap_is_in_bounds(const cap_register_t* c, uint64_t addr, uint64_t num_bytes) {
if (addr < cap_get_base(c)) {
return false;
}
// Use __builtin_add_overflow to avoid wrapping around the end of the addres space
uint64_t access_end_addr = 0;
if (__builtin_add_overflow(addr, num_bytes, &access_end_addr)) {
warn_report("Found capability access that wraps around: 0x%" PRIx64
" + %" PRId64 ". Authorizing cap: " PRINT_CAP_FMTSTR,
addr, num_bytes, PRINT_CAP_ARGS(c));
return false;
}
if (access_end_addr > cap_get_top(c)) {
return false;
}
return true;
}
TODO-1: enable _sbit_for_memory
for 128 bit hybrid CHERI_128
; including
_sbit_for_memory
only;Things might need to change:
target/mips/cpu.h
_sbit_for_memory
.target/mips/cheri-compressed-cap/cheri_compressed_cap.h
compress_128cap_without_xor(): ==> store the sealed bit of the cap struct to the 128bit rep.
// LLM: read seal bit from csp
bool S_BIT = csp->_sbit_for_memory > 0 ? true : false;
uint64_t pesbt =
CC128_ENCODE_FIELD(csp->cr_uperms, UPERMS) |
CC128_ENCODE_FIELD(csp->cr_perms, HWPERMS) |
CC128_ENCODE_FIELD(S_BIT, SEALED_BIT) |
CC128_ENCODE_FIELD(csp->cr_otype, OTYPE) |
CC128_ENCODE_FIELD(IE, INTERNAL_EXPONENT) |
CC128_ENCODE_FIELD(Te, TOP_ENCODED) |
CC128_ENCODE_FIELD(Be, BOTTOM_ENCODED);
decompress_128cap_already_xored(): ==> read sealed bit from 128 rep and stored in the cap struct.
target/mips/op_helper_cheri.c:
compress_128cap(csp) -> compress_128cap_without_xor() -> add seal_bit
load_cap_from_memory(); //
decompress_128cap(pesbt, cursor, &ncd) -> decompress_128cap_already_xored()
sealing update in target/mips/cheri_utils.h:
sealing test in target/mips/cheri_utils.h:
TODO-2: duing unsealing, do not reset the otype, keep it instead; things might need to change:
Must change:
Functions might be affected:
target/mips/op_helper_cheri.c: store_cap_to_memory(),
target/mips/cheri_utils.h: CAP_OTYPE_UNSEALED;
cap_make_sealed_entry();
// file
// target/mips/op_helper_cheri.c
target_ulong CHERI_HELPER_IMPL(ccall_notrap)(CPUMIPSState *env, uint32_t cs, uint32_t cb)
{
return ccall_common(env, cs, cb, CCALL_SELECTOR_1, GETPC());
}
static target_ulong ccall_common(CPUMIPSState *env, uint32_t cs, uint32_t cb, uint32_t selector, uintptr_t _host_return_address)
{
const cap_register_t *csp = get_readonly_capreg(&env->active_tc, cs);
const cap_register_t *cbp = get_readonly_capreg(&env->active_tc, cb);
/*
* CCall: Call into a new security domain
*/
if (!csp->cr_tag) {
do_raise_c2_exception(env, CP2Ca_TAG, cs);
} else if (!cbp->cr_tag) {
do_raise_c2_exception(env, CP2Ca_TAG, cb);
} else if (!cap_is_sealed_with_type(cspRuntime check)) {
do_raise_c2_exception(env, CP2Ca_SEAL, cs);
} else if (!cap_is_sealed_with_type(cbp)) {
do_raise_c2_exception(env, CP2Ca_SEAL, cb);
} else if (csp->cr_otype != cbp->cr_otype || csp->cr_otype > CAP_MAX_SEALED_OTYPE) {
do_raise_c2_exception(env, CP2Ca_TYPE, cs);
} else if (!(csp->cr_perms & CAP_PERM_EXECUTE)) {
do_raise_c2_exception(env, CP2Ca_PERM_EXE, cs);
} else if (cbp->cr_perms & CAP_PERM_EXECUTE) {
do_raise_c2_exception(env, CP2Ca_PERM_EXE, cb);
} else if (!cap_is_in_bounds(csp, cap_get_cursor(csp), 1)) {
// TODO: check for at least one instruction worth of data? Like cjr/cjalr?
do_raise_c2_exception(env, CP2Ca_LENGTH, cs);
} else {
if (selector == CCALL_SELECTOR_0) {
do_raise_c2_exception(env, CP2Ca_CALL, cs);
} else if (!(csp->cr_perms & CAP_PERM_CCALL)){
do_raise_c2_exception(env, CP2Ca_PERM_CCALL, cs);
} else if (!(cbp->cr_perms & CAP_PERM_CCALL)){
do_raise_c2_exception(env, CP2Ca_PERM_CCALL, cb);
} else {
cap_register_t idc = *cbp;
cap_set_unsealed(&idc);
update_capreg(&env->active_tc, CP2CAP_IDC, &idc);
// The capability register is loaded into PCC during delay slot
env->active_tc.CapBranchTarget = *csp;
// XXXAR: clearing these fields is not strictly needed since they
// aren't copied from the CapBranchTarget to $pcc but it does make
// the LOG_INSTR output less confusing.
cap_set_unsealed(&env->active_tc.CapBranchTarget);
// Return the branch target address
return cap_get_cursor(csp);
}
}
return (target_ulong)0;
}
// file
// target/mips/cheri_utils.h
static inline void cap_set_unsealed(cap_register_t* c) {
assert(c->cr_tag);
assert(cap_is_sealed_with_type(c));
assert(c->cr_otype <= CAP_MAX_SEALED_OTYPE && "should not use this to unsealed reserved types");
c->cr_otype = CAP_OTYPE_UNSEALED;
#ifndef CHERI_128
assert(c->_sbit_for_memory == true);
c->_sbit_for_memory = false;
#endif
}
There is no seal bit (for memory) in 256-bit cap
// file
// target/mips/cpu.h
/*
* Please note if this structure is changed then the TCG gen_branch() in
* translate.c may need to be changed as well.
*/
struct cap_register {
/* offset = cursor - base */
uint64_t cr_offset; /* Capability offset */
uint64_t cr_base; /* Capability base addr */
/* Length is actually 65 bits (TODO: should store top instead) */
unsigned __int128 _cr_top; /* Capability top */
uint32_t cr_perms; /* Permissions */
uint32_t cr_uperms; /* User Permissions */
#ifdef CHERI_128
uint64_t cr_pesbt_xored_for_mem; /* Perms, E, Sealed, Bot, & Top bits (128-bit) */
#endif
uint32_t cr_otype; /* Object Type, 24 bits */
uint8_t cr_tag; /* Tag */
#ifndef CHERI_128
bool _sbit_for_memory;
#endif
};
// file
// target/mips/cheri-compressed-cap/cheri_compressed_cap.h
// QEMU already provides cap_register_t but if used in other programs
// we want to define it here:
#ifndef HAVE_CAP_REGISTER_T
struct cap_register {
/* offset = cursor - base */
uint64_t cr_offset; /* Capability offset */
uint64_t cr_base; /* Capability base addr */
cc128_length_t _cr_top; /* Capability top */
uint32_t cr_perms; /* Permissions */
uint32_t cr_uperms; /* User Permissions */
uint64_t cr_pesbt_xored_for_mem; /* Perms, E, Sealed, Bot, & Top bits (128-bit) */
uint32_t cr_otype; /* Object Type, 24 bits */
uint8_t cr_tag; /* Tag */
uint8_t _sbit_for_memory; /* sealed flag */
#ifdef __cplusplus
inline uint64_t base() const {
return cr_base;
}
inline uint64_t address() const {
return cr_base + cr_offset;
}
inline cc128_length_t top() const {
return _cr_top;
}
inline uint64_t top64() const {
const cc128_length_t t = top();
return t > UINT64_MAX ? UINT64_MAX : (uint64_t)t;
}
inline cc128_length_t length() const {
return _cr_top - cr_base;
}
inline uint64_t length64() const {
const cc128_length_t l = length();
return l > UINT64_MAX ? UINT64_MAX : (uint64_t)l;
}
#endif
};
typedef struct cap_register cap_register_t;
#endif
Reference:
Q&A How it is accessing the tag table without access data memory? call cheri_get_many Reference 1 // in disas/mips.c {"cloadtags", "t,+b", 0x4800.07bf, 0xffe0,07ff, 0, 0, I1}, Instruction ISA definition in C: // target/mips/helper.h DEF_HELPER_3(cloadtags, tl, env, i32, cap_checked_ptr) // target/mips/os_helper_cheri.c target_ulong CHERI_HELPER_IMPL(cloadtags(CPUArchState *env, uint32_t cb, uint64_t cbcursor)) Helper function to access tag table in memory. // in target/cheri-common/cheri_tagmem.c int cheri_tag_get_many(CPUArchState *env, target_ulong vaddr, int reg, hwaddr *ret_paddr, uintptr_t pc)// target/mips/translate_cheri.
If you could revise
the fundmental principles of
computer system design
to improve security...
... what would you change?