Stack Overflow Prevention

Stack Overflow

Local variables are stored on the stack. The stack is a contiguous block of memory that grows and shrinks as functions are called and return. When dealing with user input, it is possible to overflow the stack by providing too much input. This can be used to overwrite other data on the stack. This is a common method of exploiting buffer overflows.

For example, consider the following program:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef __morello__
#define PTR_FORMAT "%#p"
#else
#define PTR_FORMAT "%p"
#endif

int my_strcpy(char * buf, const char * src){
    int i = 0;
    if (!src) return i;

    char * cur = buf;
    while(src[i] != '\0'){
        printf("write to cur: " PTR_FORMAT "\n", cur);
        *cur = src[i];
        cur++;
        i++;
    }
    printf("write to cur: " PTR_FORMAT "\n", cur);
    *cur = '\0';
    return i;
}

int main(int argc, char **argv){
    int i = 1;
    char buffer[4];

    printf("buffer: " PTR_FORMAT "\n", buffer);
    printf ("i = %d (" PTR_FORMAT ")\n", i, &i);
    printf("Change i's value from 1 -> 500.\n");
    my_strcpy(buffer, argv[1]);

    if (i == 65) {
        printf("GOOD\n");
        system("/bin/sh");
    }

    printf("buffer: [%s] (%zu)\n", buffer, strlen(buffer));
    printf ("i = %d (" PTR_FORMAT ")\n", i, &i);
    return 0;
}

When compiling it with the following command on a non-CHERI system,

$ clang-morello -O0 -g stack_overflow.c -o stack_overflow_unprotected

and executing it with AAA as shown below, it works as expected:

$ ./stack_overflow_unprotected AAA
buffer: 0x809fa948
i = 1 (0x809fa94c)
Change i\'s value from 1 -> 500.
write to cur: 0x809fa948
write to cur: 0x809fa949
write to cur: 0x809fa94a
write to cur: 0x809fa94b
buffer: [AAA] (3)
i = 1 (0x809fa94c)

However, when executing it with AAAAA, it overwrites the value of i:

$ ./stack_overflow_unprotected AAAAA
buffer: 0x81036528
i = 1 (0x8103652c)
Change i\'s value from 1 -> 500.
write to cur: 0x81036528
write to cur: 0x81036529
write to cur: 0x8103652a
write to cur: 0x8103652b
write to cur: 0x8103652c
write to cur: 0x8103652d
GOOD
$ # We are now in a shell spawned by the program
$ ^D
buffer: [AAAAA] (5)
i = 65 (0x8103652c)

The program has a buffer overflow vulnerability. The program copies the first argument using a custom my_strcpy function to display the address it is modifying. The function copies the string character by character until it reaches the end.

Normally, it’s the programmer’s responsibility to ensure that the destination buffer is large enough to hold the source string, or using a safe function like strncpy. However, in this case, the programmer has made a mistake and the buffer is too small to hold the string. This means that the program will write past the end of the buffer, overwriting the value of i.

Vulnerability Mitigration with Morello

The Morello architecture tags the allocated memory and restrict any illegal access outside the designated bound. So the program will crash when it tries to write to an invalid address. Let’s compile the program with the Morello compiler:

# compile natively on a Morello system
$ clang-morello -O0 -g -D__morello__ \
    -march=morello -mabi=purecap \
    -Xclang -morello-vararg=new \
    stack_overflow.c -o stack_overflow

# cross-compile with LLVM toolchain and musl libc
# LLVM toolchain: https://git.morello-project.org/morello/llvm-project-releases
# musl libc: https://git.morello-project.org/morello/musl-libc
$ clang -O0 -g -D__morello__ \
    -march=morello --target=aarch64-linux-musl_purecap \
    --sysroot=/root/musl-sysroot-purecap \
    stack_overflow.c -o stack_overflow -static

and execute it with AAAAA:

# run on a Morello system natively
$ ./stack_overflow AAAAA

# OR
# run on a Morello system with Morello Instruction Emulator, morelloie
# morelloie: https://developer.arm.com/downloads/-/morello-instruction-emulator
$ morelloie -- ./stack_overflow AAAAA

buffer: 0xfffffff7ff58 [rwRW,0xfffffff7ff58-0xfffffff7ff5c]
i = 1 (0xfffffff7ff5c [rwRW,0xfffffff7ff5c-0xfffffff7ff60])
Change i\'s value from 1 -> 500.
write to cur: 0xfffffff7ff58 [rwRW,0xfffffff7ff58-0xfffffff7ff5c]
write to cur: 0xfffffff7ff59 [rwRW,0xfffffff7ff58-0xfffffff7ff5c]
write to cur: 0xfffffff7ff5a [rwRW,0xfffffff7ff58-0xfffffff7ff5c]
write to cur: 0xfffffff7ff5b [rwRW,0xfffffff7ff58-0xfffffff7ff5c]
write to cur: 0xfffffff7ff5c [rwRW,0xfffffff7ff58-0xfffffff7ff5c]
In-address space security exception (core dumped)

The program crashes with an in-address space security exception. This is because the program is trying to write to an address that is not in the address space of the buffer pointer. Although the value of cur was changed to 0xfffffff7ff5c, the original pointer is restricted in the address space 0xfffffff7ff58-0xfffffff7ff5c (not including the last byte). This means that, any pointer derived from the original buffer pointer will also be limited to that address space, and the program will crash when it tries to write outside that address space.