Memory Safety Mitigations
How do we protect against memory safety vulnerabilities?
Memory Safe Languages #
Memory safe languages check bounds and prevent undefined memory accesses, and therefore are immune to memory safety vulnerabilities.
Examples: Java, Python, C#…
However, memory safe languages come at the cost of performance: malloc takes amortized constant time, but having memory checking and garbage collection can add additional overhead. These days, this is fairly insignificant and languages like Go, Rust have comparable performance.
Writing Memory Safe Code #
- Defensive Programming (add checks, even when you think they aren’t necessary)
- Use safe libraries that check bounds (fgets, strncpy, snprintf…)
- Structure User Input (only allow particular types of inputs, such as digits)
Analyze Insecure Code #
- Add runtime checks with automatic bounds checking
- Monitor code for illegal calling sequences, instructions are run that never appear in the code itself, etc.
- Run components in VMs to isolate programs from affecting other areas of memory (privilege separation)
- Test for memory safety vulnerabilities
- Fuzz testing: random inputs
- Use valgrind for detecting memory leaks
Add mitigations for common vulnerabilities #
Turn exploits into crashes.
Make steps in creating an attack (find vulnerability, write shellcode at known memory address, overwrite RIP with address of shellcode, return from function, execute malicious code) difficult or impossible.
Non-Executable Pages #
Most programs don’t need memory that can be both written to and executed.
Idea: Make memory either executable OR writable, but not both
- Example: code segment executable; stack, heap, static writable
- Page table entries can have a writable/executable bit for this behavior
- Prevents many buffer overflow attacks by making it difficult to execute shellcode
- Will not prevent overwriting data (authentication bits etc), reading too far, using existing code in the system
return to libc: overwrite RIP to jump to standard C library function, or OS function (example:
systemfunction runs command)
return-oriented programming (ROP): create custom shellcode using existing code (gadgets)
Strategy: create a chain of return addresses. Recall that
retjust pops the next address on the stack, so attackers can write a list of exact positions to put together a desired program.There are ROP compilers that can automatically do this

Stack Canaries #
Idea: add a sacrificial value onto the stack, and check if it has been changed
- Randomized value
- Placed before RIP/SFP
- Uses null byte as first byte to mitigate string attacks

- Prevents common overflow attacks that write increasing, consecutive addresses (gets, fgets, fread…)
Subverting Canaries #
- Leak stack memory, and overwrite canary with itself
- Heap overflows don’t write onto stack
- Canaries can be guessed (especially on 32-bit systems)
Pointer Authentication #
Another method of preventing modification of RIP/SFP.
Idea: store canary values inside the pointer itself
- Values are generated by a CPU master secret, unused bits in 64-bit addresses replaced with pointer authentication code (PAC)
- Before pointers can be used, PAC is checked, and if invalid the program crashes
Subverting pointer authentication #
- Learn master secret by exploiting vulnerabilities in OS
- Brute force
- Reuse PAC from another pointer
Address Space Layout Randomization (ASLR) #
In practice, memory is mostly empty and there is a lot of unused space between the stack, heap, data, and code.
Idea: put each memory segment in a different location each time.
- Locations will be randomized for each machine, and for each run of the program
- Basically no overhead, since address are computed during linking and need to be relocated anyways
Subverting ASLR #
- String format vulnerabilities can still overwrite/read unintended locations since it is relative to a pointer
- Leaking the address of a pointer will undo the randomization (either stack pointer or RIP)
- Can use
%dwith printf
- Can use
- Brute force attacks