Heap temporal memory safety
CheriBSD 23.11 incorporates support for userlevel heap temporal memory safety based on a load-barrier extension to Cornucopia, which is inspired by garbage-collection techniques.
This feature involves a collaboration between the kernel (which provides
asynchronous capability revocation with VM acceleration) and the userlevel
heap allocator (which quarantines freed memory until revocation of any
pointers to it) to ensure that memory cannot be reallocated until there are
no outstanding valid capabilities lasting from its previous allocation.
The userlevel memory allocator and the kernel revoker share an epoch counter
that counts the number of completed atomic revocation sweeps.
Memory added to quarantine in one epoch cannot be removed from quarantine
until at least one complete epoch has passed -- i.e., the epoch counter has
been increased by 2.
More information on temporal memory safety support can be found in the
mrs(3) man page:
man mrs
This activity involves source code in the temporal
subdirectory:
cd ~/tutorial/src/temporal
Checking whether temporal safety is globally enabled
Use the sysctl(8)
command to inspect the value of the
security.cheri.runtime_revocation_default
system MIB entry:
sysctl security.cheri.runtime_revocation_default
This sysctl sets the default policy for revocation used by processes on
startup.
We recommend setting this in /boot/loader.conf
, which is processed by the
boot loader before any user processes start.
Controlling revocation by binary or process
You can forcefully enable or disable revocations for a specific binary or process with elfctl(1) or proccontrol(1) and ignore the default policy:
elfctl -e <+cherirevoke or +nocherirevoke> <binary>
proccontrol -m cherirevoke -s <enable or disable> <program with arguments>
You can read more on these commands in the mrs(3) man page.
Exercising a use-after-free bug
Compile the program use-after-free.c
:
cc -Wall -g -o use-after-free use-after-free.c
Run the program to see what happens when a use-after-free bug is exercised:
./use-after-free
Why doesn't the program crash?
Synchronous revocation
Revocation normally occurs asynchronously, with a memory quarantine preventing
memory reuse until revocation of any pointers to that memory.
Uncomment this line in use-after-free.c
to trigger a synchronous revocation
before the use-after-free memory access:
/* malloc_revoke(). */
Recompile and re-run the program, this time under GDB, and observe its behavior.
- Which line in the program faults, and why?
Monitoring revocation in processes
Use the procstat cheri -v
command to inspect the CHERI memory-safety state
of a target process.
For example:
# procstat cheri -v 923 1012
PID COMM C QUAR RSTATE EPOCH
923 seatd P yes none 0
1012 Xorg P yes none 0xd2
Both processes in this example use a pure-capability process environment, have quarantining enabled, and are not currently revoking. seatd has never needed to perform a revocation pass, as it remains in epoch 0, whereas X.org has a non-zero epoch and has performed multiple passes.
Modify use-after-free.c
to await user input before and after the call to
cheri_revoke()
using the POSIX
gets_s(3)
API; run use-after-free
.
In a second login session, use the ps(1)
command to obtain the process ID,
and then use procstat cheri
to inspects its protection state on either side
of the revocation call.
- What is EPOCH before and after calling
cheri_revoke()
-- and why?