Skip to content

Commit

Permalink
mmap: explicitly expose capability permissions
Browse files Browse the repository at this point in the history
Introduce two new PROT_ values PROT_CAP and PROT_NO_CAP.  They combine
to allow capability permissions to be implied in unmodified code using
PROT_READ and PROT_WRITE which allowing capability permissions to be set
or unset explicity.

If either of PROT_CAP or PROT_NO_CAP are set, then the value of
the PROT_CAP flag bit defines the page protections and capability
permissions for a given mapping.

In the underlying implementation, PROT_CAP maps to VM_PROT_READ_CAP and
VM_PROT_WRITE_CAP depending on the values of PROT_READ and PROT_WRITE.
PROT_NO_CAP maps to a new VM_PROT_NO_IMPLY_CAP.  VM_PROT_NO_IMPLY_CAP
is 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 Sep 4, 2024
1 parent 72196e6 commit 674ece8
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 59 deletions.
75 changes: 75 additions & 0 deletions bin/cheribsdtest/cheribsdtest_vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,81 @@ CHERIBSDTEST(vm_tag_mmap_anon,
cheribsdtest_success();
}

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

CHERIBSDTEST(vm_notag_mmap_no_cap,
"check tags are not stored it we request no 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_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 if 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_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_CAP | PROT_MAX(PROT_READ),
ENOTSUP);

/* Disallowed explicit capability protection combinations */
mmap_check_bad_protections(PROT_CAP, ENOTSUP);
mmap_check_bad_protections(PROT_MAX(PROT_CAP), ENOTSUP);

cheribsdtest_success();
}

CHERIBSDTEST(vm_tag_shm_open_anon_shared,
"check tags are stored for SHM_ANON MAP_SHARED pages")
{
Expand Down
51 changes: 50 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_CAP -compact
.It Dv PROT_NONE
Pages may not be accessed.
.It Dv PROT_READ
Expand All @@ -113,8 +113,43 @@ Pages may be read.
Pages may be written.
.It Dv PROT_EXEC
Pages may be executed.
.It Dv PROT_CAP
CHERI capabilities may be read or written as dictated by
.Dv PROT_READ
and
.Dv PROT_WRITE .
.It Dv PROT_NO_CAP
Respect the absence of
.Dv PROT_CAP .
.El
.Pp
On CHERI platforms, compatability is retained with unmodified POSIX
programs by implying
.Dv PROT_CAP
if either of
.Dv PROT_READ
and
.Dv PROT_WRITE
is set unless the underlying backing store can not safety support
capabilities (e.g., a
.Dv MAP_SHARED
mapping of a file).
If either of
.Dv PROT_NO_CAP
are set, then capability permissions will not be implied.
When
.Dv PROT_CAP
is passed, at least one of
.Dv PROT_READ
and
.Dv PROT_WRITE
is required.
On non-CHERI platforms the
.Dv PROT_CAP
and
.Dv PROT_NO_CAP
flags 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 All @@ -130,6 +165,14 @@ values wrapped in the
macro into the
.Fa prot
argument.
The
.Dv PROT_MAX()
flags must be a superset of the unwrapped flags.
If one set of flags contains
.Dv PROT_CAP
or
.Dv PROT_NO_CAP
then both must.
.Pp
The
.Fa flags
Expand Down Expand Up @@ -614,6 +657,12 @@ The
.Fa prot
argument contains protections which are not a subset of the specified
maximum protections.
.It Bq Er ENOTSUP
.Dv PROT_CAP
without
.Dv PROT_READ
or
.Dv PROT_WRITE .
.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);
}
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
4 changes: 2 additions & 2 deletions sys/compat/linuxkpi/common/include/linux/page.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ typedef unsigned long pgprot_t;

#define page vm_page

#define LINUXKPI_PROT_VALID (1 << 5)
#define LINUXKPI_CACHE_MODE_SHIFT 6
#define LINUXKPI_PROT_VALID (1 << 6)
#define LINUXKPI_CACHE_MODE_SHIFT 7

CTASSERT((VM_PROT_ALL & -LINUXKPI_PROT_VALID) == 0);

Expand Down
4 changes: 2 additions & 2 deletions sys/dev/drm/drmkpi/include/linux/page.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ typedef unsigned long pgprot_t;

#define page vm_page

#define DRMCOMPAT_PROT_VALID (1 << 5)
#define DRMCOMPAT_CACHE_MODE_SHIFT 6
#define DRMCOMPAT_PROT_VALID (1 << 6)
#define DRMCOMPAT_CACHE_MODE_SHIFT 7

CTASSERT((VM_PROT_ALL & -DRMCOMPAT_PROT_VALID) == 0);

Expand Down
2 changes: 2 additions & 0 deletions sys/fs/devfs/devfs_vnops.c
Original file line number Diff line number Diff line change
Expand Up @@ -2014,6 +2014,8 @@ devfs_mmap_f(struct file *fp, vm_map_t map, vm_pointer_t *addr,
else if ((prot & VM_PROT_WRITE) != 0)
return (EACCES);
}
if ((prot & (VM_PROT_CAP | VM_PROT_NO_IMPLY_CAP)) != 0)
maxprot = VM_PROT_ADD_CAP(maxprot);
maxprot &= cap_maxprot;

fpop = td->td_fpop;
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
5 changes: 4 additions & 1 deletion sys/sys/mman.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@
#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_CAP 0x08 /* capabilities can be read/written */
#define PROT_NO_CAP 0x10 /* honor PROT_CAP absense */
#if __BSD_VISIBLE
#define _PROT_ALL (PROT_READ | PROT_WRITE | PROT_EXEC)
#define _PROT_CAP (PROT_CAP | PROT_NO_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
19 changes: 11 additions & 8 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 @@ -86,17 +87,19 @@ typedef u_char vm_prot_t; /* protection codes */
#define VM_PROT_DEFAULT VM_PROT_RWX
#define VM_PROT_CAP (VM_PROT_READ_CAP|VM_PROT_WRITE_CAP)
#define VM_PROT_RW_CAP (VM_PROT_RW|VM_PROT_CAP)
#define VM_PROT_ALL (VM_PROT_RWX|VM_PROT_CAP)
#define VM_PROT_ALL (VM_PROT_RWX|VM_PROT_CAP|VM_PROT_NO_IMPLY_CAP)

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

View workflow job for this annotation

GitHub Actions / Style Checker

spaces required around that '|' (ctx:VxV)

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

View workflow job for this annotation

GitHub Actions / Style Checker

spaces required around that '|' (ctx:VxV)

#define VM_PROT_ADD_CAP(prot) __extension__ ({ \
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; \
cp; \
if ((p & (VM_PROT_CAP | VM_PROT_NO_IMPLY_CAP)) == 0) { \
if ((p & VM_PROT_READ) != 0) \
cp |= VM_PROT_READ_CAP; \
if ((p & VM_PROT_WRITE) != 0) \
cp |= VM_PROT_WRITE_CAP; \
} \
cp |= VM_PROT_NO_IMPLY_CAP; \
})

#define VM_PROT_EXTRACT(prot) ((prot) & VM_PROT_ALL)
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 674ece8

Please sign in to comment.