Skip to content

Commit

Permalink
Proposed relaxing of cjalr sealing
Browse files Browse the repository at this point in the history
Attempt at capturing #85

Co-authored-by: Robert Norton <robert.norton@microsoft.com>
  • Loading branch information
nwf and ronorton committed Dec 2, 2024
1 parent bc66dbb commit 2c456c7
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 14 deletions.
6 changes: 6 additions & 0 deletions archdoc/chap-changes.tex
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ \chapter{Version history}
Make it explicitly 16-byte aligned and point out the unaligned write spanning \mshwmb{} corner case, which we do not require hardware to handle.
\item[\ghpr{54}] Create backward sentries for function returns and add more checks in \rvcheriasminsnref{CJAL}
Because CHERIoT allows manipulating the status of the interrupt through a function call (and function return) by encoding the interrupt type in the otype, the following attack can occur: A caller calling an interrupt-disabling callee can set the return sentry of the callee to the same callee. This means, the callee will call itself on return all the while operating with interrupts disabled. This will lead to infinite repeated calls to the callee with interrupts disabled, violating availability. This attack can be prevented in CHERIoT by adding two new ``backwards-edge'' sentries and adding more checks on \rvcheriasminsnref{CJALR}.
\begin{description}
\item[\ghissue{85}, \ghpr{86}] Relaxes the original change to better support code outlining,
by allowing \rvcheriasminsnref{CJALR} to create unsealed return addresses
when its (output) link register is not \asm{\$cra}.
See the discussion in \cref{sec:sealing}.
\end{description}
\item[\ghissue{71}, \ghpr{87}] \rvcheriasminsnref{CUnseal} now no longer requires exact equality between sealed input otype and authority address.
Instead, it merely requires that the otype of the sealed input is within bounds to yield a tagged result.
The address of a sealing-root capability is now meaningful only to \rvcheriasminsnref{CSeal}.
Expand Down
59 changes: 49 additions & 10 deletions archdoc/chap-cheri-riscv.tex
Original file line number Diff line number Diff line change
Expand Up @@ -453,16 +453,16 @@ \subsection{Sealed capabilities}
The capability is unsealed before jumping to it, creating a form of call gate.
Three kinds of sentry are defined that affect \asm{mstatus.MIE} in different ways: either leaving it unchanged, enabling interrupts or disabling interrupts.
Jumping to an interrupt enabling or disabling sentry will set or clear \asm{mstatus.MIE} accordingly.
Additionally, the link register stored by \insnriscvref{CJAL} and \insnriscvref{CJALR} is sealed as a sentry with the current interrupt status: if MIE is set it will produce an interrupt enabling sentry and if it is cleared it will produce an interrupt disabling sentry.
Additionally, if the link register is \asm{\$cra}, the capability stored by \insnriscvref{CJAL} and \insnriscvref{CJALR} is sealed as a sentry with the current interrupt status: if MIE is set it will produce an interrupt enabling sentry and if it is cleared it will produce an interrupt disabling sentry.
\end{description}
The \cotype{} field uses the following values:
\begin{description}
\item[0] unsealed
\item[1] sealed as sentry
\item[2] sealed as interrupt disabling sentry
\item[3] sealed as interrupt enabling sentry
\item[4] sealed as backward interrupt disabling sentry
\item[5] sealed as backward interrupt enabling sentry
\item[1] sealed as interrupt inheriting forward sentry
\item[2] sealed as interrupt disabling forward sentry
\item[3] sealed as interrupt enabling forward sentry
\item[4] sealed as interrupt disabling backward sentry
\item[5] sealed as interrupt enabling backward sentry
\item[6-7] executable capability sealed with given \cotype{}
\item[8] reserved (due to encoding)
\item[9-15] non-executable capability sealed with given \cotype{}
Expand All @@ -477,16 +477,55 @@ \subsection{Sealed capabilities}
\footnotesize
\begin{tabular}{|c|c|c|c|}
\hline
\asm{cs1} & \asm{cd} & Used for & Valid \cotype{}s \\
\asm{cs1} & \asm{cd} & Used for & Valid \asm{cs1} \cotype{}s \\
\hline
\asm{\$cra} & \asm{\$cnull} & Function return & Return sentries $(4, 5)$\\
$\ne$ \asm{\$cra} & \asm{\$cnull} & Tail call & Unsealed or interrupt inheriting forward sentry $(0, 1)$\\
any & $\not\in \{ \text{\asm{\$cnull}}, \text{\asm{\$cra}} \}$ & Function call & Unsealed or interrupt inheriting forward sentry $(0, 1)$\\
\asm{\$cra} & \asm{\$cnull} & Function return & Backward sentries $(4, 5)$\\
$\ne$ \asm{\$cra} & \asm{\$cnull} & Tail call & Unsealed or IRQ inheriting forward sentry $(0, 1)$\\
any & $\not\in \{ \text{\asm{\$cnull}}, \text{\asm{\$cra}} \}$ & Code outlining & Unsealed or IRQ inheriting forward sentry $(0, 1)$\\
any & \asm{\$cra} & Function call & Unsealed or forward sentries $(0, 1, 2, 3)$\\
\hline
\end{tabular}
\end{center}

\noindent Both \insnriscvref{CJAL} and \insnriscvref{CJALR} with non-\asm{\$cnull} \asm{cd} operands
determine the \cotype{} of the generated capability as a function of the \asm{cd} target itself:

\begin{center}
\footnotesize
\begin{tabular}{|c|c|}
\hline
\asm{cd} & Generated \asm{cd} \cotype{} \\
\hline
$\not\in \{ \text{\asm{\$cnull}}, \text{\asm{\$cra}} \}$ & 0 (unsealed) \\
\asm{\$cra} & 4, 5 (backward sentries, caller's IRQ disposition) \\
\hline
\end{tabular}
\end{center}

Thus, ordinary function calls, for which the calling convention is to use \asm{\$cra} as the return register,
can call any unsealed (executable) capability or forward sentry,
and will always create backward sentries, suitable for the function return case.
Tail calls, which must preserve \asm{\$cra} for their callee and do not generate return capabilities of their own,
are also permitted.
Last, code \emph{outlining}, the dual of code \emph{inlining}, is supported;
outlined code may use a specialized calling convention, not using \asm{\$cra} to exit
(so return from such outlined code looks like a ``tail call'' in the above table),
especially in cases where the outlined code must preserve \asm{\$cra} for its caller.
In this case, the \insnriscvref{CJAL} or \insnriscvref{CJALR} used to enter the outlined code
will have a \asm{cd} that is neither \asm{\$cnull} nor \asm{\$cra},
and so will create \emph{unsealed} return capabilities,
so that all forward sentries in the system must have been built by the RTOS loader.
(Outlined code that \emph{does} use \asm{\$cra} as its return address will also function correctly,
as if it were an ordinary function call and return.)

Given the overloading of the single \insnriscvref{CJALR} instruction,
with register selector operands selecting between multiple semantics,
compilers \emph{must} be careful to use matching entry and exit \insnriscvref{CJALR}s.
For example, entering a function with a \asm{\$cra} link register,
transfering the contents of \asm{\$cra} to another register,
and then ``tail calling'' through that latter register will not work on CHERIoT,
even though analogous code would have worked on base RISC-V.

\subsection{Capability bounds}
\label{sec:bounds}

Expand Down
30 changes: 26 additions & 4 deletions src/cheri_insts.sail
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,16 @@ function clause execute(CJAL(imm, cd)) = {
let (success, linkCap) = setCapAddr(PCC, nextPC); /* Note that nextPC accounts for compressed instructions */
assert(success, "Link cap should always be representable.");
assert(not (isCapSealed(linkCap)), "Link cap should always be unsealed");
let sentry_type = if mstatus.MIE() == 0b1 then otype_sentry_bie else otype_sentry_bid;
C(cd) = sealCap(linkCap, to_bits(cap_otype_width, sentry_type));

if cd == ra then {
/* When writing to ra, generate a sealed return capability. */
let sentry_type = if mstatus.MIE() == 0b1 then otype_sentry_bie else otype_sentry_bid;
C(cd) = sealCap(linkCap, to_bits(cap_otype_width, sentry_type));
} else {
/* Generate an unsealed return capability. */
C(cd) = linkCap;
};

nextPC = newPC;
RETIRE_SUCCESS
}
Expand Down Expand Up @@ -176,8 +184,22 @@ function clause execute(CJALR(imm, cs1, cd)) = {
let (success, linkCap) = setCapAddr(PCC, nextPC); /* Note that nextPC accounts for compressed instructions */
assert(success, "Link cap should always be representable.");
assert(not (isCapSealed(linkCap)), "Link cap should always be unsealed");
let sentry_type = if mstatus.MIE() == 0b1 then otype_sentry_bie else otype_sentry_bid;
C(cd) = sealCap(linkCap, to_bits(cap_otype_width, sentry_type));

if cd == ra then {
/*
* When writing to ra, generate a sealed return capability. We can get
* here with cs1 unsealed or any forward sentry type.
*/
let sentry_type = if mstatus.MIE() == 0b1 then otype_sentry_bie else otype_sentry_bid;
C(cd) = sealCap(linkCap, to_bits(cap_otype_width, sentry_type));
} else {
/*
* Generate an unsealed return capability. From the conditions above, it
* must be the case that cs1 is unsealed or a forward inheriting sentry.
*/
C(cd) = linkCap;
};

nextPC = newPC;
nextPCC = unsealCap(cs1_val);
if unsigned(cs1_val.otype) == otype_sentry_id | unsigned(cs1_val.otype) == otype_sentry_bid then
Expand Down

0 comments on commit 2c456c7

Please sign in to comment.