Skip to content

mmap PROT_CAP and PROT_NO_CAP support - #23

Open
scosu wants to merge 3 commits into
CHERI-Alliance:codasip-cheri-riscv-7.0from
scosu:topic/mmap-cap-prot/v7.0
Open

mmap PROT_CAP and PROT_NO_CAP support#23
scosu wants to merge 3 commits into
CHERI-Alliance:codasip-cheri-riscv-7.0from
scosu:topic/mmap-cap-prot/v7.0

Conversation

@scosu

@scosu scosu commented May 21, 2026

Copy link
Copy Markdown

In most cases PROT_CAP is automatically applied. PROT_NO_CAP removes the ability to store pointers and will create a segfault with SEGV_STORETAG.

This Pull Request also adds a self test, testing the assumptions of how it should work.

See the individual commit messages for more details.

Closes: #10

scosu added 3 commits May 20, 2026 20:52
Support flags PROT_CAP and PROT_NO_CAP in mmap. PROT_CAP is implied by
PROT_READ and PROT_WRITE for most mmap calls. PROT_NO_CAP forbids the
use of capabilities in this mapping. Storing a tag in this mapping will
cause a fault.

Note that CHERI on riscv needs additional setting of
CHERI_PERMS_LOAD_CAP or CHERI_PERMS_STORE_CAP, otherwise PROT_NO_CAP
is not enforced.

Signed-off-by: Markus Schneider-Pargmann (The Capable Hub) <msp@baylibre.com>
Implement Zcheripte 0.9.3 tval2 encoding support for CHERI PTE faults.
Return SEGV_STORETAG when there is a PTE fault.

Signed-off-by: Markus Schneider-Pargmann (The Capable Hub) <msp@baylibre.com>
Add selftests for mmap PROT_CAP and PROT_NO_CAP behavior.

Signed-off-by: Markus Schneider-Pargmann (The Capable Hub) <msp@baylibre.com>
perms |= CHERI_PERMS_LOAD_CAP;
if (prot & PROT_WRITE)
perms |= CHERI_PERMS_STORE_CAP;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but this and the related text in the commit message is bogus. I somewhat see why you are doing this but we really shouldn't add additional permissions to the capability with an explicitly given PROT_NO_CAP. Yes, you will get a CHERI fault instead of a capability fault but I don't think we care. If this fails tests adjust the tests.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that it is even more bogus than that if the CW bit is not supported, either because the hardware does not have it or because for some reason the arch decided to not make use of it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your comment. I went back to the spec and did a lot of debugging. Without these perms, a tagged store to a PROT_NO_CAP region silently strips the tag without any fault. From src/cheri-pte-ext.adoc:

NOTE: The tag bit of the stored capability is checked _after_ it is potentially
cleared due to lack of permissions.

Keeping the perms is what can get us a real fault on a tagged store. I think that is more useful than silently dropping the tag.

In one of my tests it initially looked like the strip was not working at all which confused me a lot. But I realized now the compiler optimized the readback against the just-stored register. After forcing a real load, the behavior as documented by the spec was observable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussing this, we take the view that the capability returned should be consistent with PROT_MAX(), PROT_CAP, and PROT_NO_CAP. For example, if the caller passed PROT_READ | PROT_WRITE | PROT_NO_CAP, then the returned capability should have no capability permissions. The small additional benefit to debuggability is likely not worth the inconsistency of returning a capability with capability permissions when creating a PROT_NO_CAP mapping.

As a related note, anonymous MAP_SHARED mappings should not imply PROT_CAP, whereas anonymous MAP_PRIVATE mappings should.

Tagging @brooksdavis and @qwattash in case they have any further comments.

Comment thread mm/mmap.c
return -EINVAL;
/*
* PROT_CAP is not supported with file-backed MAP_SHARED mapping
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this? Isn't it the whole point to allow just that?

Comment thread arch/riscv/mm/fault.c
local_flush_tlb_page(addr);
}

static inline bool access_error(unsigned long cause, struct vm_area_struct *vma)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is quite right. First, you are introducing quite a large diff in a function with subtle logic. This is bound to introduce merge errors when the upstream code changes.
But more importantly, you are now saying that a cheri PTE fault on a VMA with VM_CAP_WRITE is ok. That will trigger standard fault handling which is then trying to fix an ordinary page fault and will fail to do so. So I think that all cheri PTE faults should be bad_area_nosemaphore. This also simpifies things because you can just handle those before even calling access_ok().

Comment thread arch/riscv/mm/fault.c
@@ -351,11 +366,12 @@ void handle_page_fault(struct pt_regs *regs)
if (!vma)
goto lock_mmap;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above: Handle cheri_pte_faults here and always do an access error.

perms |= CHERI_PERMS_LOAD_CAP;
if (prot & PROT_WRITE)
perms |= CHERI_PERMS_STORE_CAP;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that it is even more bogus than that if the CW bit is not supported, either because the hardware does not have it or because for some reason the arch decided to not make use of it.

#define SEGV_CAPACCESSERR 14 /* Capability access fault */
#define NSIGSEGV 14
#define SEGV_STORETAG 15 /* Capability tag store fault */
#define NSIGSEGV 15

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the CI run: This breaks build on other archs.

@chrehrhardt chrehrhardt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See inline comments. At the very least the arch_user_ptr_owning_perms_from_prot needs to go

@brooksdavis

Copy link
Copy Markdown

This implementation doesn't appear to square with the design in CheriBSD https://man.cheribsd.org/cgi-bin/man.cgi/mmap. The key thing is that PROT_NO_CAP means "don't imply PROT_CAP" not prot &= ~PROT_CAP. The reason for this construction is that we feel we need to imply PROT_CAP for MAP_ANON or too much will break so we need a way to disable that implication when we don't want capabilities. This leads to a slightly awkward construction.

PROT_CAP|PROT_NO_CAP is a reasonable set of permissions if you're constructing permissions through a series of decisions. (Inside CheriBSD VM permissions always end up that way as VM_PROT_NO_IMPLY_CAP is used to finalizes the constructed set of permissions.)

In the original implementation (see CTSRD-CHERI/cheribsd#2191), I had PROT_READ_CAP, PROT_WRITE_CAP, and PROT_NO_IMPLY_CAP. Because it became clear RVY wasn't going to have separate READ_CAP and WRITE_CAP permissions (and because they aren't obviously useful), we consolidated down to the two permissions today. It could certainly be argued that PROT_NO_CAP should still be PROT_NO_IMPLY_CAP for clarity (or some of these other options in CTSRD-CHERI/cheribsd#2191 (comment) (note that github's UI for resolved or outdated comments is terrible so after following this link you'll need to expand comments on mmap.2 until you find a highlighted one)).

It would be super helpful if @paul-metzger could update his cheribsdtest port and test this branch with it to see how things align.

@paul-metzger

paul-metzger commented Jun 4, 2026

Copy link
Copy Markdown

Thanks for working on this! As @brooksdavis asked me to, I ran our test suite and two out of three tests for PROT_CAP and PROT_NO_CAP fail:

  1. A capability can be stored in a mapping created with PROT_READ | PROT_WRITE | PROT_NO_CAP. Reproducer code:
m = mmap(NULL, getpagesize(),
		PROT_MAX(PROT_READ | PROT_WRITE | PROT_CAP) |
		PROT_READ | PROT_WRITE | PROT_NO_CAP, MAP_ANON | MAP_PRIVATE, -1, 0);
cap = &v;
*m = cap;
  1. mprotect() fails when trying to set PROT_NO_CAP on a mapping. Reproducer code:
m = mmap(NULL, getpagesize(),
	    PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
mprotect(m, getpagesize(), PROT_READ | PROT_WRITE | PROT_NO_CAP);

@scosu

scosu commented Jun 5, 2026

Copy link
Copy Markdown
Author

This implementation doesn't appear to square with the design in CheriBSD https://man.cheribsd.org/cgi-bin/man.cgi/mmap. The key thing is that PROT_NO_CAP means "don't imply PROT_CAP" not prot &= ~PROT_CAP. The reason for this construction is that we feel we need to imply PROT_CAP for MAP_ANON or too much will break so we need a way to disable that implication when we don't want capabilities. This leads to a slightly awkward construction.

Yes, you are right, I missed that this is just to revert the implication, thanks. From my perspective PROT_NO_IMPLY_CAP would indeed be preferable.

Thanks for working on this! As @brooksdavis asked me to, I ran our test suite and two out of three tests for PROT_CAP and PROT_NO_CAP fail:

1. A capability can be stored in a mapping created with `PROT_READ | PROT_WRITE | PROT_NO_CAP`. Reproducer code:
m = mmap(NULL, getpagesize(),
		PROT_MAX(PROT_READ | PROT_WRITE | PROT_CAP) |
		PROT_READ | PROT_WRITE | PROT_NO_CAP, MAP_ANON | MAP_PRIVATE, -1, 0);
cap = &v;
*m = cap;

I tried reproducing this here. But it works here. How are you testing, are you using the qemu setup with meta-cheri or something else?

2. `mprotect()` fails when trying to set `PROT_NO_CAP` on a mapping. Reproducer code:
m = mmap(NULL, getpagesize(),
	    PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
mprotect(m, getpagesize(), PROT_READ | PROT_WRITE | PROT_NO_CAP);

mprotect is currently not implemented for PROT_NO_CAP or PROT_CAP. I see BSD has it implemented but doesn't have it documented in the man page. I mainly looked at the man page before implementing.

@paul-metzger

Copy link
Copy Markdown

tried reproducing this here. But it works here. How are you testing, are you using the qemu setup with meta-cheri or something else?

I'm using Qemu with cheribuild. Just to make sure we are not talking at cross purposes, you get a SIGSEGV when you run this code? I might not have time to look into this next week, but the week after should be better. I also realised that we have more tests for PROT_CAP, which I will run when I get back to this.

@scosu

scosu commented Jun 5, 2026

Copy link
Copy Markdown
Author

Yes, it does SIGSEGV here with the current version.

Can you check if PTE.CW is supported?

[    0.028147] CHERI: Selected SATP mode: 0x9000000000000000 PTE.CW support: yes

Also do you have a link to other tests? I would love to see what is tested and maybe integrate it into the selftests as well.

ptr = mmap(NULL, MMAP_SIZE,
PROT_READ | PROT_WRITE | PROT_CAP | PROT_NO_CAP,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_EQ(MAP_FAILED, ptr);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can only be the case if CONFIG_CHERI_PURECAP_UABI
Same for

  • test_prot_cap_file_shared_einval
  • test_prot_cap_mprotect_einval
  • test_prot_no_cap_mprotect_einval

void *ptr;
int fd;

fd = mkstemp(tmppath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mkstemp() requires stdlib.h to be included.

@paul-metzger

paul-metzger commented Jun 16, 2026

Copy link
Copy Markdown

Yes, it does SIGSEGV here with the current version.
Can you check if PTE.CW is supported?

[    0.028147] CHERI: Selected SATP mode: 0x9000000000000000 PTE.CW support: yes

Thanks, PTE.CW wasn't supported on my setup and I also get a SIGSEGV now after fixing this. The issue seems to be related to recent changes in Qemu. @heshamelmatary is currently working on a fix.

Also do you have a link to other tests? I would love to see what is tested and maybe integrate it into the selftests as well.

The tests that I'm using are a portable adaptation of a cheribsdtest, which is a test suite that was built for CheriBSD. These will be published soon after some more polishing. The non-portable originals of the additional tests are here:
https://github.com/CTSRD-CHERI/cheribsd/blob/main/bin/cheribsdtest/cheribsdtest_cheriabi.c#L211-L492
You must comply with their license terms if you want to integrate them with other tests.
When executing their portable variants, the cheriabi_mmap_no_cap_perms tests fail because the load-mutable permission and the capability permission are set on mappings created with PROT_READ | PROT_NO_CAP, PROT_RW | PROT_NO_CAP or PROT_RWX | PROT_NO_CAP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants