Skip to content

Commit

Permalink
mmap: explicitly expose capability permissions
Browse files Browse the repository at this point in the history
Introduce three new PROT_ values PROT_READ_CAP, PROT_WRITE_CAP, and
PROT_NO_IMPLY_CAP.  They combine to allow capability permissions to be
implied in unmodified code using PROT_READ and PROT_WRITE allowing
capability read and write permissions to be set or unset explicity.

If any of PROT_READ_CAP, PROT_WRITE_CAP, or PROT_NO_IMPLY_CAP are set,
then the values of the PROT_READ_CAP and PROT_WRITE_CAP flag bits define
the page protections and capability permissions for a given mapping.

In the underlying implementation, PROT_READ_CAP and PROT_WRITE_CAP map
to VM_PROT_READ_CAP and VM_PROT_WRITE_CAP respectively and
PROT_NO_IMPLY_CAP maps to a new VM_PROT_NO_IMPLY_CAP.
VM_PROT_NO_IMPLY_CAP used transiently in fo_mmap implementations to
avoid accidently adding capability permission and is also added to
vm_entry's max_protection to allow superset tests to succeed when
reducing capability permissions on a mapping via mmap or mprotect.
  • Loading branch information
brooksdavis committed Aug 9, 2024
1 parent 0968f83 commit 9fa14ac
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 32 deletions.
85 changes: 85 additions & 0 deletions bin/cheribsdtest/cheribsdtest_vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,91 @@ CHERIBSDTEST(vm_tag_mmap_anon,
cheribsdtest_success();
}

CHERIBSDTEST(vm_tag_mmap_anon_cap,
"check tags are stored for MAP_ANON pages with explict permissions")
{
mmap_and_check_tag_stored(-1,
PROT_READ | PROT_WRITE | PROT_READ_CAP | PROT_WRITE_CAP, MAP_ANON);
cheribsdtest_success();
}

CHERIBSDTEST(vm_notag_mmap_no_cap,
"check tags are not stored we request not capablity permissions",
.ct_flags = CT_FLAG_SIGNAL | CT_FLAG_SI_CODE | CT_FLAG_SI_TRAPNO | CT_FLAG_SI_ADDR,

Check warning on line 128 in bin/cheribsdtest/cheribsdtest_vm.c

View workflow job for this annotation

GitHub Actions / Style Checker

line over 80 characters
.ct_signum = SIGSEGV,
.ct_si_code = SEGV_STORETAG,
.ct_si_trapno = TRAPNO_STORE_CAP_PF,
.ct_check_skip = skip_need_writable_tmp)
{
void * __capability volatile *cp;

Check failure on line 134 in bin/cheribsdtest/cheribsdtest_vm.c

View workflow job for this annotation

GitHub Actions / Style Checker

"foo * bar" should be "foo *bar"
void * __capability cp_value;

Check failure on line 135 in bin/cheribsdtest/cheribsdtest_vm.c

View workflow job for this annotation

GitHub Actions / Style Checker

"foo * bar" should be "foo *bar"
int v;

cp = CHERIBSDTEST_CHECK_SYSCALL(mmap(NULL, getpagesize(),
PROT_READ | PROT_WRITE | PROT_NO_IMPLY_CAP, MAP_ANON, -1, 0));
cheribsdtest_set_expected_si_addr(NULL_DERIVED_VOIDP(cp));
cp_value = cheri_ptr(&v, sizeof(v));
*cp = cp_value;
cheribsdtest_failure_errx("tagged store succeeded");
}

CHERIBSDTEST(vm_notag_mprotect_no_cap,
"check tags are not stored we remove capability page permissions",
.ct_flags = CT_FLAG_SIGNAL | CT_FLAG_SI_CODE | CT_FLAG_SI_TRAPNO | CT_FLAG_SI_ADDR,

Check warning on line 148 in bin/cheribsdtest/cheribsdtest_vm.c

View workflow job for this annotation

GitHub Actions / Style Checker

line over 80 characters
.ct_signum = SIGSEGV,
.ct_si_code = SEGV_STORETAG,
.ct_si_trapno = TRAPNO_STORE_CAP_PF,
.ct_check_skip = skip_need_writable_tmp)
{
void * __capability volatile *cp;

Check failure on line 154 in bin/cheribsdtest/cheribsdtest_vm.c

View workflow job for this annotation

GitHub Actions / Style Checker

"foo * bar" should be "foo *bar"
void * __capability cp_value;

Check failure on line 155 in bin/cheribsdtest/cheribsdtest_vm.c

View workflow job for this annotation

GitHub Actions / Style Checker

"foo * bar" should be "foo *bar"
int v;

cp = CHERIBSDTEST_CHECK_SYSCALL(mmap(NULL, getpagesize(),
PROT_READ | PROT_WRITE, MAP_ANON, -1, 0));
CHERIBSDTEST_CHECK_SYSCALL(mprotect(__DEVOLATILE(void *, cp),
getpagesize(), PROT_READ | PROT_WRITE | PROT_NO_IMPLY_CAP));
cheribsdtest_set_expected_si_addr(NULL_DERIVED_VOIDP(cp));
cp_value = cheri_ptr(&v, sizeof(v));
*cp = cp_value;
cheribsdtest_failure_errx("tagged store succeeded");
}

static void
mmap_check_bad_protections(int prot, int expected_errno)
{
CHERIBSDTEST_CHECK_CALL_ERROR((int)(intptr_t)mmap(NULL, getpagesize(),
prot, MAP_ANON, -1, 0), expected_errno);
}

CHERIBSDTEST(vm_mmap_diallowed_prot,
"check that disallowed protection combinations are rejected")
{
/* Max protections not a superset */
mmap_check_bad_protections(PROT_READ | PROT_WRITE | PROT_MAX(PROT_READ),
ENOTSUP);

/* Mixing implied and explict protections */
mmap_check_bad_protections(PROT_READ | PROT_READ_CAP |
PROT_MAX(PROT_READ), ENOTSUP);

/* Disallowed explicit capability protection combinations */
mmap_check_bad_protections(PROT_READ_CAP, ENOTSUP);
mmap_check_bad_protections(PROT_WRITE_CAP, ENOTSUP);
mmap_check_bad_protections(PROT_MAX(PROT_READ_CAP), ENOTSUP);
mmap_check_bad_protections(PROT_MAX(PROT_WRITE_CAP), ENOTSUP);
mmap_check_bad_protections(PROT_READ | PROT_WRITE | PROT_READ_CAP,
ENOTSUP);
mmap_check_bad_protections(PROT_READ | PROT_WRITE | PROT_WRITE_CAP,
ENOTSUP);
mmap_check_bad_protections(PROT_MAX(PROT_READ | PROT_WRITE |
PROT_READ_CAP), ENOTSUP);
mmap_check_bad_protections(PROT_MAX(PROT_READ | PROT_WRITE |
PROT_WRITE_CAP),
ENOTSUP);
cheribsdtest_success();
}

CHERIBSDTEST(vm_tag_shm_open_anon_shared,
"check tags are stored for SHM_ANON MAP_SHARED pages")
{
Expand Down
58 changes: 57 additions & 1 deletion lib/libsys/mmap.2
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ argument by
.Em or Ns 'ing
the following values:
.Pp
.Bl -tag -width PROT_WRITE -compact
.Bl -tag -width PROT_NO_IMPLY_CAP -compact
.It Dv PROT_NONE
Pages may not be accessed.
.It Dv PROT_READ
Expand All @@ -113,8 +113,47 @@ Pages may be read.
Pages may be written.
.It Dv PROT_EXEC
Pages may be executed.
.It Dv PROT_READ_CAP
CHERI capabilities may be read from pages.
.It Dv PROT_WRITE_CAP
CHERI capabilities may be written to pages.
.It Dv PROT_NO_IMPLY_CAP
Respect the absence of
.Dv PROT_READ_CAP/PROT_WRITE_CAP .
.El
.Pp
On CHERI platforms, compatability is retained with unmodified POSIX
programs by implying
.Dv PROT_READ_CAP
and
.Dv PROT_WRITE_CAP
based on the presence of
.Dv PROT_READ
and
.Dv PROT_WRITE
respectively, unless the undelying backing store can not safety support
capabilities (e.g., a
.Dv MAP_SHARED
mapping of a file).
If any of
.Dv PROT_READ_CAP ,
.Dv PROT_WRITE_CAP ,
or
.Dv PROT_NO_IMPLY_CAP
are set, then capability permissions will not be implied.
When
.Dv PROT_READ_CAP
or
.Dv PROT_WRITE_CAP
are passed
.Dv PROT_READ
and
.Dv PROT_WRITE
are required respectively.
On non-CHERI platforms the
.Dv PROT_*_CAP
bits have no effect.
.Pp
In addition to these protection flags,
.Fx
provides the ability to set the maximum protection of a region allocated by
Expand Down Expand Up @@ -614,6 +653,23 @@ The
.Fa prot
argument contains protections which are not a subset of the specified
maximum protections.
.It Bq Er ENOTSUP
.Dv PROT_READ_CAP
or
.Dv PROT_WRITE_CAP
was not pared with
.Dv PROT_READ
or
.Dv PROT_WRITE
respectively or
.Dv PROT_READ
and
.Dv PROT_WRITE
were specified without
both
.Dv PROT_READ_CAP
and
.Dv PROT_WRITE_CAP .
.El
.Sh SEE ALSO
.Xr madvise 2 ,
Expand Down
11 changes: 7 additions & 4 deletions sys/arm64/include/cherireg.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,16 @@
* vm_prot_t to capability permission bits
*/
#define CHERI_PERMS_PROT2PERM_READ \
(CHERI_PERM_LOAD | CHERI_PERM_LOAD_CAP | CHERI_PERM_MUTABLE_LOAD)
CHERI_PERM_LOAD
#define CHERI_PERMS_PROT2PERM_READ_CAP \
(CHERI_PERM_LOAD_CAP | CHERI_PERM_MUTABLE_LOAD)
#define CHERI_PERMS_PROT2PERM_WRITE \
(CHERI_PERM_STORE | CHERI_PERM_STORE_CAP | \
CHERI_PERM_STORE_LOCAL_CAP)
CHERI_PERM_STORE
#define CHERI_PERMS_PROT2PERM_WRITE_CAP \
(CHERI_PERM_STORE_CAP | CHERI_PERM_STORE_LOCAL_CAP)
#define CHERI_PERMS_PROT2PERM_EXEC \
(CHERI_PERM_EXECUTE | CHERI_PERM_EXECUTIVE | \
CHERI_PERMS_PROT2PERM_READ)
CHERI_PERMS_PROT2PERM_READ | CHERI_PERMS_PROT2PERM_READ_CAP)

/*
* Basic userspace permission mask; CHERI_PERM_EXECUTE will be added for
Expand Down
2 changes: 1 addition & 1 deletion sys/arm64/vmm/vmm.c
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ vm_mmap_getnext(struct vm *vm, vm_paddr_t *gpa, int *segid,
* Hide the bits implicitly added by vm_mmap_memseg().
* Userspace might not expect to see them returned here.
*/
*prot &= ~VM_PROT_CAP;
*prot &= ~(VM_PROT_CAP|VM_PROT_NO_IMPLY_CAP);

Check failure on line 852 in sys/arm64/vmm/vmm.c

View workflow job for this annotation

GitHub Actions / Style Checker

spaces required around that '|' (ctx:VxV)
}
if (flags)
*flags = mmnext->flags;
Expand Down
2 changes: 2 additions & 0 deletions sys/cheri/cherireg.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@
* Definition for mapping vm_prot_t to capability permission
*/
#define CHERI_PROT2PERM_READ_PERMS CHERI_PERMS_PROT2PERM_READ
#define CHERI_PROT2PERM_READ_CAP_PERMS CHERI_PERMS_PROT2PERM_READ_CAP
#define CHERI_PROT2PERM_WRITE_PERMS CHERI_PERMS_PROT2PERM_WRITE
#define CHERI_PROT2PERM_WRITE_CAP_PERMS CHERI_PERMS_PROT2PERM_WRITE_CAP
#define CHERI_PROT2PERM_EXEC_PERMS CHERI_PERMS_PROT2PERM_EXEC
#define CHERI_PROT2PERM_MASK \
(CHERI_PROT2PERM_READ_PERMS | CHERI_PROT2PERM_WRITE_PERMS | \
Expand Down
12 changes: 8 additions & 4 deletions sys/riscv/include/cherireg.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,16 @@
* vm_prot_t to capability permission bits
*/
#define CHERI_PERMS_PROT2PERM_READ \
(CHERI_PERM_LOAD | CHERI_PERM_LOAD_CAP)
CHERI_PERM_LOAD
#define CHERI_PERMS_PROT2PERM_READ_CAP \
CHERI_PERM_LOAD_CAP
#define CHERI_PERMS_PROT2PERM_WRITE \
(CHERI_PERM_STORE | CHERI_PERM_STORE_CAP | \
CHERI_PERM_STORE_LOCAL_CAP)
CHERI_PERM_STORE
#define CHERI_PERMS_PROT2PERM_WRITE_CAP \
(CHERI_PERM_STORE_CAP | CHERI_PERM_STORE_LOCAL_CAP)
#define CHERI_PERMS_PROT2PERM_EXEC \
(CHERI_PERM_EXECUTE | CHERI_PERMS_PROT2PERM_READ)
(CHERI_PERM_EXECUTE | CHERI_PERMS_PROT2PERM_READ | \
CHERI_PERMS_PROT2PERM_READ_CAP)

/*
* Hardware defines a kind of tripartite taxonomy: memory, type, and CID.
Expand Down
6 changes: 5 additions & 1 deletion sys/sys/mman.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@
#define PROT_READ 0x01 /* pages can be read */
#define PROT_WRITE 0x02 /* pages can be written */
#define PROT_EXEC 0x04 /* pages can be executed */
#define PROT_READ_CAP 0x08 /* capabilities can be read from pages */
#define PROT_WRITE_CAP 0x10 /* capabilities can be written to pages */
#define PROT_NO_IMPLY_CAP 0x20 /* don't imply PROT_{READ,WRITE}_CAP */
#if __BSD_VISIBLE
#define _PROT_ALL (PROT_READ | PROT_WRITE | PROT_EXEC)
#define _PROT_CAP (PROT_READ_CAP | PROT_WRITE_CAP | PROT_NO_IMPLY_CAP)
#define _PROT_ALL (PROT_READ | PROT_WRITE | PROT_EXEC | _PROT_CAP)
#define PROT_EXTRACT(prot) ((prot) & _PROT_ALL)

#define _PROT_MAX_SHIFT 16
Expand Down
15 changes: 9 additions & 6 deletions sys/vm/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ typedef u_char vm_prot_t; /* protection codes */
#define VM_PROT_EXECUTE ((vm_prot_t) 0x04)
#define VM_PROT_READ_CAP ((vm_prot_t) 0x08)
#define VM_PROT_WRITE_CAP ((vm_prot_t) 0x10)
#define VM_PROT_COPY ((vm_prot_t) 0x20) /* copy-on-read */
#define VM_PROT_PRIV_FLAG ((vm_prot_t) 0x40)
#define VM_PROT_NO_IMPLY_CAP ((vm_prot_t) 0x20)
#define VM_PROT_COPY ((vm_prot_t) 0x40) /* copy-on-read */
#define VM_PROT_PRIV_FLAG ((vm_prot_t) 0x80)
#define VM_PROT_FAULT_LOOKUP VM_PROT_PRIV_FLAG
#define VM_PROT_QUICK_NOFAULT VM_PROT_PRIV_FLAG /* same to save bits */

Expand All @@ -92,10 +93,12 @@ typedef u_char vm_prot_t; /* protection codes */
vm_prot_t cp, p; \
\
cp = p = (prot); \
if ((p & VM_PROT_READ) != 0) \
cp |= VM_PROT_READ_CAP; \
if ((p & VM_PROT_WRITE) != 0) \
cp |= VM_PROT_WRITE_CAP; \
if ((p & (VM_PROT_CAP|VM_PROT_NO_IMPLY_CAP)) == 0) { \

Check failure on line 96 in sys/vm/vm.h

View workflow job for this annotation

GitHub Actions / Style Checker

spaces required around that '|' (ctx:VxV)
if ((p & VM_PROT_READ) != 0) \
cp |= VM_PROT_READ_CAP; \
if ((p & VM_PROT_WRITE) != 0) \
cp |= VM_PROT_WRITE_CAP; \
} \
cp; \
})

Expand Down
28 changes: 21 additions & 7 deletions sys/vm/vm_map.c
Original file line number Diff line number Diff line change
Expand Up @@ -2099,7 +2099,7 @@ vm_map_insert1(vm_map_t map, vm_object_t object, vm_ooffset_t offset,

new_entry->inheritance = inheritance;
new_entry->protection = prot;
new_entry->max_protection = max;
new_entry->max_protection = max | VM_PROT_NO_IMPLY_CAP;
new_entry->wired_count = 0;
new_entry->wiring_thread = NULL;
new_entry->read_ahead = VM_FAULT_READ_AHEAD_INIT;
Expand Down Expand Up @@ -3463,7 +3463,8 @@ vm_map_protect(vm_map_t map, vm_offset_t start, vm_offset_t end,
old_prot = entry->protection;

if ((flags & VM_MAP_PROTECT_SET_MAXPROT) != 0) {
entry->max_protection = new_maxprot;
entry->max_protection = new_maxprot |
VM_PROT_NO_IMPLY_CAP;
entry->protection = new_maxprot & old_prot;
}
if ((flags & VM_MAP_PROTECT_SET_PROT) != 0)
Expand Down Expand Up @@ -6228,10 +6229,23 @@ vm_map_prot2perms(vm_prot_t prot)
{
int perms = 0;

if (prot & (VM_PROT_READ | VM_PROT_COPY))
perms |= CHERI_PROT2PERM_READ_PERMS;
if (prot & VM_PROT_WRITE)
perms |= CHERI_PROT2PERM_WRITE_PERMS;
if (prot & (VM_PROT_CAP | VM_PROT_NO_IMPLY_CAP)) {
if (prot & (VM_PROT_READ | VM_PROT_COPY))
perms |= CHERI_PROT2PERM_READ_PERMS;
if (prot & VM_PROT_READ_CAP)
perms |= CHERI_PROT2PERM_READ_CAP_PERMS;
if (prot & VM_PROT_WRITE)
perms |= CHERI_PROT2PERM_WRITE_PERMS;
if (prot & VM_PROT_WRITE_CAP)
perms |= CHERI_PROT2PERM_WRITE_CAP_PERMS;
} else {
if (prot & (VM_PROT_READ | VM_PROT_COPY))
perms |= CHERI_PROT2PERM_READ_PERMS |
CHERI_PROT2PERM_READ_CAP_PERMS;
if (prot & VM_PROT_WRITE)
perms |= CHERI_PROT2PERM_WRITE_PERMS |
CHERI_PROT2PERM_WRITE_CAP_PERMS;
}
if (prot & VM_PROT_EXECUTE)
perms |= CHERI_PROT2PERM_EXEC_PERMS;

Expand Down Expand Up @@ -6276,7 +6290,7 @@ vm_map_reservation_insert(vm_map_t map, vm_offset_t addr, vm_size_t length,
new_entry->end = addr + length;
new_entry->reservation = reservation;
new_entry->next_read = addr;
new_entry->max_protection = max;
new_entry->max_protection = max | VM_PROT_NO_IMPLY_CAP;
vm_map_entry_link(map, new_entry);
vm_map_log("reserve", new_entry);

Expand Down
Loading

0 comments on commit 9fa14ac

Please sign in to comment.