Answers - Exercise an inter-stack-object buffer overflow
-
Expected output:
# run_qemu ./buffer-overflow-stack-baseline upper = 0000003FFFFFEFA0, lower = 0000003FFFFFEF90, diff = 10 upper[0] = a upper[0] = b # run_qemu ./buffer-overflow-stack-cheri upper = 00000000000000000000003FFFFFEF30, lower = 00000000000000000000003FFFFFEF20, diff = 10 upper[0] = a MON|ERROR: received message 0x00000006 badge: 0x0000000000000001 tcb cap: 0x8000000000000008 MON|ERROR: faulting PD: buffer-overflow-stack-cheri -
An example of the Monitor's output for buffer-overflow-stack-cheri` on CHERI-RISC-V:
MON|ERROR: received message 0x00000006 badge: 0x0000000000000001 tcb cap: 0x8000000000000008 MON|ERROR: faulting PD: buffer-overflow-stack-cheri Registers: ddc : 0x0 pcc : 0x200060 [rxCM1111,0x200000-0x204000] (capmode) cra : 0x2000ea [rxCM1111,0x200000-0x204000] (capmode) (sentry) csp : 0x3fffffeef0 [rwCM1111,0x3fffffe000-0x3ffffff000] cgp : 0x0 cs0 : 0x3fffffef20 [rwCM1111,0x3fffffef20-0x3fffffef30] cs1 : 0x202693 [rCM1111,0x202693-0x2026a2] cs2 : 0x0 cs3 : 0x203000 [rwCM1111,0x203000-0x203010] cs4 : 0x0 cs5 : 0x0 cs6 : 0x0 cs7 : 0x0 cs8 : 0x0 cs9 : 0x0 cs10 : 0x0 cs11 : 0x0 ca0 : 0x3fffffef30 [rwCM1111,0x3fffffef20-0x3fffffef30] ca1 : 0x62 ca2 : 0xd ca3 : 0xffffffffffffffff ca4 : 0x0 ca5 : 0x0 ca6 : 0x0 ca7 : 0xfffffffffffffff4 ct0 : 0x0 ct1 : 0x201d46 [rxCM1111,0x200000-0x202cf0] (capmode) ct2 : 0x0 ct3 : 0x0 ct4 : 0x0 ct5 : 0x0 ct6 : 0x0 ctp : 0x0 MON|ERROR: CHERI Security Violation: ip=0x0000000000200060 fault_addr=0x0000003fffffef30 fsr=0x0000000000000814 (data fault) MON|ERROR: description of fault: Bounds violation MON|ERROR: CHERI fault type: CHERI data fault due to load, store or AMO <<seL4(CPU 0) [receiveIPC/142 T0xffffffc0fffe6400 "rootserver" @8a000460]: Reply object already has unexecuted reply!>Using GDB or llvm-objdump to see what instructions at the faulting PC (ip=0x000200060), we see:
; buf[ix] = 'b'; 200060: 23 00 b5 00 sb a1, 0(ca0)If we investigate the content of
ca0(either using GDB'sinfo reg $ca0or from the register dump), we can see it does have something like:ca0 : 0x3fffffef30 [rwCM1111,0x3fffffef20-0x3fffffef30]The capability in
ca0, which is a pointer into thelowerbuffer, has been taken beyond the end of the allocation, as out of bounds store has been attempted (Bounds violation).But where did those bounds originate? Heading
upa stack frame anddisassembling, we see (eliding irrelevant instructions):(gdb) up #1 0x00000000002000ea in init () at buffer-overflow-stack.c:31 31 write_buf(lower, sizeof(lower)); (gdb) disass Dump of assembler code for function init: 0x0000000000200066 <+0>: c.addi16sp csp,-128 0x0000000000200074 <+14>: c.addi4spn a0,csp,48 0x0000000000200076 <+16>: scbndsi cs0,ca0,16 0x00000000002000dc <+118>: li a1,16 0x00000000002000de <+120>: mv ca0,cs0 0x00000000002000e2 <+124>: auipcc cra,0x0 0x00000000002000e6 <+128>: jalr -138(cra) => 0x00000000002000ea <+132>: lbu a0,64(csp)The compiler has arranged for
initto allocate 128 bytes on the stack by decrementing the capability stack pointer register (csp) by 128 bytes. Further, the compiler has placedlower48 bytes up into that allocation:ca0is made to point at its lowest address and then the pointer toloweris materialized incs0by bounding the capability inca0to be 16 (sizeof(lower)) bytes long. This capability is passed towrite_bufinca0. -
The code for
write_buffunction is only slightly changed. On RISC-V it compiles to20004c <write_buf>: 20004c: 2e 95 add a0, a0, a1 20004e: 93 05 20 06 li a1, 98 200052: 23 00 b5 00 sb a1, 0(a0) 200056: 82 80 retwhile on CHERI-RISC-V, it is
200058 <write_buf>: 200058: 33 05 b5 0c cadd ca0, ca0, a1 20005c: 93 05 20 06 li a1, 98 200060: 23 00 b5 00 sb a1, 0(ca0) 200064: 82 80 retIn both cases, it amounts to displacing the pointer passed in
a0(resp.ca0) by the offset passed ina1and then performing a store-byte instruction before returning. In the baseline case, the store-byte takes an integer address for its store, while in the CHERI case, the store-byte takes a capability authorizing the store. There are no conditional branches or overt bounds checks in the CHERI instruction stream; rather, thesbinstruction itself enforces the requirement for authority to write to memory, in the shape of a valid, in-bounds capability.We have already seen the CHERI program's call site to
write_bufininit, and the derivation of the capability to thelowerbuffer, above. In the baseline version, the corresponding instructions are shown asBreakpoint 1, init () at buffer-overflow-stack.c:23 23 upper, lower, (size_t)(upper - lower)); (gdb) disassemble init Dump of assembler code for function init: 0x0000000000200058 <+0>: addi sp,sp,-48 0x00000000002000c0 <+104>: mv a0,sp 0x00000000002000c2 <+106>: li a1,16 0x00000000002000c4 <+108>: auipc ra,0x0 0x00000000002000c8 <+112>: jalr -120(ra) # 0x20004c <write_buf>Here, the compiler has reserved only 48 bytes of stack space and has placed the
lowerbuffer at the lowest bytes of this reservation. Thus, to pass a pointer to thelowerbuffer towrite_buf, the program simply copies the stack pointer register (an integer register, holding an address) to the argument registera0. The subsequent address arithmetic derives an address out of bounds, clobbering a byte of theupperregister.