Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reset values of capability CSRs #43

Closed
sorear opened this issue Jan 24, 2024 · 21 comments · Fixed by #101
Closed

Reset values of capability CSRs #43

sorear opened this issue Jan 24, 2024 · 21 comments · Fixed by #101
Labels
bug Something isn't working

Comments

@sorear
Copy link
Contributor

sorear commented Jan 24, 2024

The base privileged architecture does not specify reset values for CSRs if M-mode code can ensure that they are not used until they have been written with a value. While defining the initial values of the capability system is important for lower modes, M-mode is likely to run before RAM initialization and erasing tags in RAM is unlikely to be performed in a way transparent to the earliest M-mode code, so M-mode code which runs immediately after reset must already be prepared to deal with garbage capabilities.

For consistency with the base ISA, remove reset values from capability CSRs that can be safely initialized by M-mode at the same time as RAM.

This applies to mscratchc, sscratchc, mepcc, sepcc, stvecc, jvtc, ddc, stdc, mtdc straightforwardly as they are not used prior to the execution of CSR instructions or of a MRET. It also applies to mtvecc, since M-mode code is responsible for not enabling interrupts or doing anything that could generate an exception until a trap handler is installed. (This may be controversial since somebody decided to file CVE-2021-1104. If there is overwhelming demand for a mtvecc reset value, it should be Null not Infinity to ensure that trap loops are harmless; pcc provides the initial Infinity capability and clearly must have a reset value.)

@tariqkurd-repo
Copy link
Collaborator

Interesting information - especially the link to the CVE.
On a purecap machine the reset values can be avoided, except for PCC, as you say, which needs to be the source of infinity.
On a legacy machine it must run existing RISC-V code, without the code ever knowing that it is on a CHERI machine. This only works if the reset value of MTVECC is infinity, otherwise when the boot code writes to MTVEC then it may not be able to successfully update the address field.

Any capabililty width CSRs which are non reset must reset the tag to zero, so (e.g.) mscratchc could only have the tag bit reset.

@sorear
Copy link
Contributor Author

sorear commented Jan 25, 2024

Even with Zcheri_legacy implemented, M-mode always starts in Capability mode (since MXL resets to the widest supported XLEN and xenvcfg CSRs only affect modes lower than x), so I don't think it's possible for cap-supporting hardware to run a cap-naive M-mode. I wouldn't personally oppose an effort to make cap-naive M-mode work, although past comments (riscv/riscv-isa-manual#820, riscv/riscv-isa-manual#817, possibly others) suggest this is considered a non-goal.

@andresag01
Copy link
Collaborator

Even with Zcheri_legacy implemented, M-mode always starts in Capability mode (since MXL resets to the widest supported XLEN and xenvcfg CSRs only affect modes lower than x), so I don't think it's possible for cap-supporting hardware to run a cap-naive M-mode.

The machine always starts in Legacy mode when Zcheri_mode is supported because the reset value of PCC in that case is M=0. As Tariq mentioned, the intention is to be compatible with RISC-V code that is not capability-aware in M-mode.

@sorear: I agree that the current spec does not need to require extended CSRs to have all their bits reset to a value, but they do need to have their tag bits reset at the very least.

@tariqkurd-repo tariqkurd-repo added question Further information is requested enhancement New feature or request labels Jan 25, 2024
@andresag01 andresag01 added bug Something isn't working and removed enhancement New feature or request question Further information is requested labels Feb 7, 2024
@andresag01
Copy link
Collaborator

Concluding this, the idea is to have reset values as follows:

CSR Reset
xtvecc Infinite
pcc Infinite + boot_addr
ddc Infinite
xscratchc Clear tag only
xepcc Clear tag only
dpcc Clear tag only
dscratch0c Clear tag only
dscratch1c Clear tag only
dddc Clear tag only
jvtc Clear tag only

This confines the resets for a lot of registers to only the tag bit. Any thoughts? @jrtc27 @arichardson @sorear

@jrtc27
Copy link
Collaborator

jrtc27 commented Feb 8, 2024

Surely xepcc, dpcc and dddc need to be infinite too

@andresag01
Copy link
Collaborator

andresag01 commented Feb 8, 2024

Surely xepcc, dpcc and dddc need to be infinite too

You are right, I think xepcc needs to be Infinite as well. Cap-unaware software will start in M-mode and Legacy, attempt to write (e.g.) mepcc's address only using the standard RISC-V addresses and jump to S/U mode; this operation will fail if those registers are not Infinite.

I am less certain about dpcc/dddc because these are just buffers for pcc/ddc when entering Debug mode. So my thinking is that you would, at least conceptually, come out of reset with pcc/ddc set to Infinite and, if needed, immediately go to Debug mode at which point Infinite is written into dpcc/dddc. Cap-unaware software would work in this case, but it depends on how this sentence from the RISC-V debug spec is interpreted:

When a hart comes out of reset and haltreq or resethaltreq are set, the hart will immediately enter Debug Mode
(halted state).

@tariqkurd-repo
Copy link
Collaborator

tariqkurd-repo commented Feb 9, 2024

jvtc also needs to be infinity on a legacy core for the same reason - legacy code can only update the address field.

so jvtc xepcc need to be infinity in the list above.

dddc and dpcc have the full cap updated from ddc and pcc on debug mode entry, so there's no need to reset more than the tag (plus they are debug mode only).

@andresag01
Copy link
Collaborator

andresag01 commented Feb 9, 2024

@tariqkurd-repo : Good spot! jvtc was missing too. Here is the updated table:

CSR Reset
xtvecc Infinite
pcc Infinite + boot_addr
ddc Infinite
xepcc Infinite
jvtc Infinite
xscratchc Clear tag only
xtdc Clear tag only
dpcc Clear tag only
dscratch0c Clear tag only
dscratch1c Clear tag only
dddc Clear tag only

I will create a PR to fix this.

@tariqkurd-repo
Copy link
Collaborator

In fact the rule is simple - anything which currently resets to NULL_CAP can be a tag clear, anything which resets to INFINITY needs to be kept.

@jrtc27
Copy link
Collaborator

jrtc27 commented Feb 9, 2024

I would expect use of JVTC in legacy code to be constrained by DDC, not be a capability. Not including it in my list was a deliberate choice.

@andresag01
Copy link
Collaborator

I would expect use of JVTC in legacy code to be constrained by DDC, not be a capability. Not including it in my list was a deliberate choice.

So you are suggesting that in Legacy mode the address to access is JVTC.address and the authorising capability for this access is DDC? Something like this:

table_access_ptr = setCapAddr(DDC, jvtc.address+index*XLEN/8)

@jrtc27
Copy link
Collaborator

jrtc27 commented Feb 9, 2024

Yes; treat it as just another pointer. Otherwise it's a strange special case from every other load that's authorised by DDC.

@jrtc27
Copy link
Collaborator

jrtc27 commented Feb 9, 2024

(especially since legacy code will access it as an integer not a capability)

@andresag01
Copy link
Collaborator

andresag01 commented Feb 9, 2024

Yes; treat it as just another pointer. Otherwise it's a strange special case from every other load that's authorised by DDC.

@jrtc27 : Then would you expect DDC to grant X permission even though it is supposed to be a data capability? @sorear explained here that the X permission is required to access JVTC because:

There are two intended implementation strategies for Zcmt in the base ISA. One, cm.jt can perform an XLEN-bit load in the execution pipeline to the address (jvt + index * XLEN/8), and subsequently branch to the fetched address. Two, cm.jt can branch to (jvt + index * XLEN/8) in a special execution mode where the next fetched XLEN bytes are considered to be an absolute jump instruction. Strategy one represents fewer steps overall but requires indirect jump handling to be scheduled after data cache access, which will typically not be the case in single-issue in-order cores. Strategy two minimizes pipeline impacts.

To make implementing strategy two in simple cores as straightforward as possible, cm.jt in the base ISA is defined to have the semantics of instruction fetches. It requires X-permission, not R-permission, in the authorizing PTE or PMP, and is affected by incoherent instruction caches (requring fence.i after any software update to the jump table).

@jrtc27
Copy link
Collaborator

jrtc27 commented Feb 9, 2024

No. The X requirement for the PTEs is a wart on the Zcmt spec that I do not want to see grow further and infect CHERI.

@arichardson
Copy link
Collaborator

Yes; treat it as just another pointer. Otherwise it's a strange special case from every other load that's authorised by DDC.

@jrtc27 : Then would you expect DDC to grant X permission even though it is supposed to be a data capability? @sorear explained here that the X permission is required to access JVTC because:

There are two intended implementation strategies for Zcmt in the base ISA. One, cm.jt can perform an XLEN-bit load in the execution pipeline to the address (jvt + index * XLEN/8), and subsequently branch to the fetched address. Two, cm.jt can branch to (jvt + index * XLEN/8) in a special execution mode where the next fetched XLEN bytes are considered to be an absolute jump instruction. Strategy one represents fewer steps overall but requires indirect jump handling to be scheduled after data cache access, which will typically not be the case in single-issue in-order cores. Strategy two minimizes pipeline impacts.
To make implementing strategy two in simple cores as straightforward as possible, cm.jt in the base ISA is defined to have the semantics of instruction fetches. It requires X-permission, not R-permission, in the authorizing PTE or PMP, and is affected by incoherent instruction caches (requring fence.i after any software update to the jump table).

This seems odd to me, would a simple core that needs this kind of "jump into a special decoding mode" workaround have a MMU that needs to be consulted? Since it's already splitting the instruction wouldn't it be easier to inject a xlen-load+jr?
But that's a completely orthogonal issue.

Even if we use DDC metadata in legacy mode, we still need jvtc to be a capability in purecap, so can't we just initialize it to infinity and keep DDC and JVTC metadata separate so that it can be constrained separately by a higher privilege mode?

@jrtc27
Copy link
Collaborator

jrtc27 commented Feb 9, 2024

Yes; treat it as just another pointer. Otherwise it's a strange special case from every other load that's authorised by DDC.

@jrtc27 : Then would you expect DDC to grant X permission even though it is supposed to be a data capability? @sorear explained here that the X permission is required to access JVTC because:

There are two intended implementation strategies for Zcmt in the base ISA. One, cm.jt can perform an XLEN-bit load in the execution pipeline to the address (jvt + index * XLEN/8), and subsequently branch to the fetched address. Two, cm.jt can branch to (jvt + index * XLEN/8) in a special execution mode where the next fetched XLEN bytes are considered to be an absolute jump instruction. Strategy one represents fewer steps overall but requires indirect jump handling to be scheduled after data cache access, which will typically not be the case in single-issue in-order cores. Strategy two minimizes pipeline impacts.
To make implementing strategy two in simple cores as straightforward as possible, cm.jt in the base ISA is defined to have the semantics of instruction fetches. It requires X-permission, not R-permission, in the authorizing PTE or PMP, and is affected by incoherent instruction caches (requring fence.i after any software update to the jump table).

This seems odd to me, would a simple core that needs this kind of "jump into a special decoding mode" workaround have a MMU that needs to be consulted? Since it's already splitting the instruction wouldn't it be easier to inject a xlen-load+jr? But that's a completely orthogonal issue.

Even if we use DDC metadata in legacy mode, we still need jvtc to be a capability in purecap, so can't we just initialize it to infinity and keep DDC and JVTC metadata separate so that it can be constrained separately by a higher privilege mode?

You could, but why should it? GPRs are capability registers in purecap code but aren't initialised to infinity.

@arichardson
Copy link
Collaborator

My reasoning for it is that it seems a bit odd to me that the access permissions for the jump table are either the CLEN-extended CSR for purecap or DDC for legacy. Using the same CSR for authorization seems like the cleaner approach to me even if it comes at the cost of an additional CSR that needs explicit initialization logic? But I don't have a strong opinion here since both cases work just fine, so happy to go with whatever is simpler for the uarch.

@sorear
Copy link
Contributor Author

sorear commented Feb 10, 2024

We could shrink jvtc back to XLEN bits and leave it UNSPECIFIED whether jump table accesses use pcc metadata or ddc metadata for the fetch. (I'll only accept an attempt to use CHERI as a vehicle for relitigating the controversial decisions in already ratified specifications if I'm allowed to do the same thing with the AUIPC low bits.)

We could move away from "legacy writes to CLEN-bit CSRs preserve the metadata" and towards "legacy writes to CLEN-bit CSRs copy metadata from pcc". Then the jvtc metadata would be initialized automatically when legacy code initializes the jvtc address.

We could keep the current write rules and initialize jvtc to Infinity. This poses system security problems, because if jvtc is added to the hardware but not privileged software support, unprivileged software can use the uninitialized jvtc as a source of Infinity at runtime. It could be initialized to Null, which would have the benefit of being fail-secure in the absence of privileged software support.

@tariqkurd-repo
Copy link
Collaborator

tariqkurd-repo commented Feb 12, 2024

#101 still has JVTC as infinity but fixes everything else to do with reset values.

There's no point in going against ratified specs - the purpose of this exercise is to get CHERI ratified. All such things will need to be undone later. I'm very aware that a lot of things will change when this finally gets to ARC review (whenever that is) but I don't see any point in making decisions which we know with certainty will be rejected.

arichardson pushed a commit that referenced this issue Feb 12, 2024
All CSRs which used to reset to NULL_CAP now just have a tag clear with
unspecified data.
All CSRs which reset to infinity are unchanged.

Fixes #43
@arichardson
Copy link
Collaborator

Since we did not resolve all the comments in this issue I've file #110 for the JVTC open issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants