Exercise heap overflows
This exercise demonstrates inter-object heap buffer overflows on baseline and CHERI-enabled architectures, and asks you to characterize and fix the bug detected by CHERI bounds enforcement.
-
Compile
buffer-overflow-heap.c
for the baseline architecture to the binarybuffer-overflow-heap-baseline
and for the CHERI-aware architecture tobuffer-overflow-heap-cheri
. -
Run both versions, passing
0x20
as the (sole) command line argument. Observe that the CHERI version crashes with "In-address space security exception". -
Run the CHERI version, again with
0x20
, undergdb
and examine the crash in more detail. Where must the bounds on the capability implementingb1
have come from? -
Run both programs again, but now with
0x1001
as the argument. Draw a picture of the portion of the heap containing (the end of)b1
and (the start of)b2
. There are, in some sense, two different ends ofb1
in the baseline program and three in the CHERI program! What are they and how do they arise? -
While this program does crash on CHERI, again of a bounds violation, this happens slightly later than might be expected looking at the program's source. In particular, this program actually commits two out of bounds stores using the
b1
capability. Examine the output carefully and describe, merely in terms of the mechanism, without venturing philosophical, why the first does not trigger a trap. -
Now consider the bigger picture. Since CHERI uses compressed capability bounds, what additional steps must be taken, and by whom, to ensure spatial safety of a C program?
Source Files
buffer-overflow-heap.c
/*
* SPDX-License-Identifier: BSD-2-Clause
* Copyright (c) 2022 Microsoft Corporation
*/
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int
main(int argc, char **argv)
{
char *b1, *b2;
assert(argc == 2);
char *p;
size_t sz = (size_t)strtoull(argv[1], &p, 0);
assert(sz > 0);
assert(sz <= 0x8000);
b1 = malloc(sz);
assert(b1 != NULL);
b2 = malloc(sz);
assert(b2 != NULL);
#ifdef __CHERI_PURE_CAPABILITY__
printf("sz=%zx, CRRL(sz)=%zx\n", sz,
__builtin_cheri_round_representable_length(sz));
printf("b1=%#p b2=%#p diff=%tx\n", b1, b2, b2 - b1);
#else
printf("b1=%p b2=%p diff=%tx\n", b1, b2, b2 - b1);
#endif
/*
* The default CheriBSD malloc uses "size classes" for allocations.
* Check that we've landed "nearby".
*/
assert((ptraddr_t)(b1 + sz) <= (ptraddr_t)b2);
assert((ptraddr_t)(b1 + sz + sz/2) > (ptraddr_t)b2);
memset(b2, 'B', sz);
printf("Overflowing by 1\n");
memset(b1, 'A', sz + 1);
printf("b2 begins: %.4s\n", b2);
/* And now let's definitely make trouble */
const size_t oversz = b2 - b1 + 2 - sz;
printf("Overflowing by %zx\n", oversz);
memset(b1 + sz, 'A', oversz);
printf("b2 begins: %.4s\n", b2);
}
Courseware
This exercise has presentation materials available.