RELRO - A (not so well known) Memory Corruption Mitigation Technique
After a discussion with Sebastian Krahmer about RELRO I did a little writeup on this memory corruption mitigation technique for my own purpose. I also decided to post it here just in case someone else might find this information valuable.RELRO is a generic mitigation technique to harden the data sections of an ELF binary/process. There are two different "operation modes" of RELRO:
Partial RELRO
- compiler command line: gcc -Wl,-z,relro
- the ELF sections are reordered so that the ELF internal data sections (.got, .dtors, etc.) precede the program's data sections (.data and .bss)
- non-PLT GOT is read-only
- GOT is still writeable
- compiler command line: gcc -Wl,-z,relro,-z,now
- supports all the features of partial RELRO
- bonus: the entire GOT is also (re)mapped as read-only
Interim conclusion: In case of a bss or data overflow bug partial and full RELRO protect the ELF internal data sections from being overwritten (as the ELF sections are reordered). Only full RELRO mitigates the well known technique of modifying a GOT entry to get control over the program execution flow.
Testcases
The checkrelro.sh script can be used to test if an ELF binary or a process supports RELRO.Recent Linux distris have partial RELRO enabled by default (e.g. Ubuntu 8.10 and openSUSE 11.1). There is therefore no difference between "gcc testcase.c" and "gcc -Wl,-z,relro testcase.c" on these platforms.
Testcase 1 (Ubuntu 8.10): Partial RELRO
File: testcase.c
#include <stdio.h>
int
main (int argc, char *argv[])
{
size_t *p = (size_t *)strtol (argv[1], NULL, 16);
p[0] = 0x41414141;
printf ("RELRO: %p\n", p);
return 0;
}This test program tries to write the value 0x41414141 at a given memory address.
Compiling "testcase" with partial RELRO:
$ gcc -g -o testcase testcase.c
Test binary:
$ ./checkrelro.sh --file testcase testcase - partial RELRO
Get GOT entry of printf(3):
$ readelf -r ./testcase | grep printf 0804a00c 00000407 R_386_JUMP_SLOT 00000000 printf
Try to modify the GOT address:
$ gdb -q ./testcase (gdb) r 0x0804a00c Starting program: /home/tk/Desktop/testcase 0x0804a00c Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? ()If only partial RELRO is used, it is still possible to modify arbitrary GOT entries to gain control of the execution flow of a process.
Testcase 2 (Ubuntu 8.10): Full RELRO
Compiling "testcase" with full RELRO:
$ gcc -g -Wl,-z,relro,-z,now -o testcase testcase.c
Test binary:
$ ./checkrelro.sh --file testcase testcase - full RELRO
Get GOT entry of printf(3):
$ readelf -r ./testcase | grep printf 08049ff8 00000407 R_386_JUMP_SLOT 00000000 printf
Try to modify the GOT address:
$ gdb -q ./testcase (gdb) r 0x08049ff8 Starting program: /home/tk/Desktop/testcase 0x08049ff8 Program received signal SIGSEGV, Segmentation fault. 0x0804842b in main (argc=Cannot access memory at address 0x0 ) at testcase.c:8 8 p[0] = 0x41414141;
If full RELRO is enabled, the attempt to overwrite a GOT address leads to an error as the GOT section is mapped read-only.
How is RELRO used in recent Linux distris?
The checkrelro.sh script is also able to enumerate all processes running on a system and test each one of them if they have RELRO enabled.Testcase 3 (Ubuntu 8.10): Inspect all running processes
$ sudo ./checkrelro.sh --proc-all init (1) - full RELRO vmware-guestd (15296) - no RELRO gedit (15771) - partial RELRO sshd (16181) - partial RELRO sshd (16193) - partial RELRO bash (16196) - no RELRO notification-da (16626) - partial RELRO udevd (2542) - partial RELRO getty (4382) - partial RELRO getty (4383) - partial RELRO getty (4390) - partial RELRO getty (4393) - partial RELRO getty (4395) - partial RELRO acpid (4563) - partial RELRO syslogd (4677) - partial RELRO dd (4729) - partial RELRO klogd (4731) - partial RELRO dbus-daemon (4754) - partial RELRO avahi-daemon (4776) - partial RELRO avahi-daemon (4777) - partial RELRO sshd (4806) - partial RELRO cupsd (4849) - partial RELRO hald (4913) - partial RELRO console-kit-dae (4916) - partial RELRO hald-runner (4979) - partial RELRO hald-addon-inpu (4999) - partial RELRO hald-addon-stor (5004) - partial RELRO hald-addon-acpi (5009) - partial RELRO bluetoothd (5055) - partial RELRO NetworkManager (5110) - partial RELRO wpa_supplicant (5115) - partial RELRO nm-system-setti (5118) - partial RELRO gdm (5148) - partial RELRO gdm (5151) - partial RELRO Xorg (5155) - partial RELRO system-tools-ba (5171) - partial RELRO atd (5206) - partial RELRO cron (5234) - partial RELRO dhclient (5236) - partial RELRO getty (5333) - partial RELRO gnome-keyring-d (5425) - partial RELRO x-session-manag (5436) - partial RELRO dbus-launch (5554) - partial RELRO dbus-daemon (5555) - partial RELRO pulseaudio (5558) - partial RELRO gconf-helper (5561) - partial RELRO gconfd-2 (5563) - partial RELRO seahorse-agent (5569) - partial RELRO gnome-keyring-d (5574) - partial RELRO gnome-settings- (5575) - partial RELRO metacity (5577) - partial RELRO gvfsd (5599) - partial RELRO gnome-panel (5600) - partial RELRO nautilus (5603) - partial RELRO bonobo-activati (5606) - partial RELRO gvfs-fuse-daemo (5610) - partial RELRO gnome-screensav (5630) - partial RELRO gvfs-hal-volume (5634) - partial RELRO gvfs-gphoto2-vo (5636) - partial RELRO trashapplet (5640) - partial RELRO gvfsd-trash (5643) - partial RELRO gvfsd-burn (5646) - partial RELRO fast-user-switc (5650) - partial RELRO mixer_applet2 (5653) - partial RELRO nm-applet (5655) - partial RELRO evolution-alarm (5658) - partial RELRO trackerd (5659) - partial RELRO update-notifier (5662) - partial RELRO tracker-applet (5663) - partial RELRO bluetooth-apple (5664) - partial RELRO python (5666) - partial RELRO gnome-power-man (5669) - partial RELRO gnome-terminal (5733) - partial RELRO gnome-pty-helpe (5735) - partial RELRO # of Processes: 74 No RELRO : 2 Partial RELRO : 71 Full RELRO : 1
Testcase 4 (openSUSE 11.1): Inspect all running processes
tk@linux-0skt:~> sudo ./checkrelro.sh --proc-all init (1) - partial RELRO acpid (1599) - partial RELRO klogd (1621) - partial RELRO syslog-ng (1635) - partial RELRO dbus-daemon (1645) - partial RELRO hald (1734) - partial RELRO console-kit-dae (1746) - partial RELRO hald-runner (1818) - partial RELRO hald-addon-inpu (1880) - partial RELRO hald-addon-stor (1929) - partial RELRO rpcbind (1930) - partial RELRO hald-addon-stor (1931) - partial RELRO hald-addon-acpi (1933) - partial RELRO kdm (1966) - partial RELRO X (1982) - partial RELRO auditd (2173) - partial RELRO audispd (2175) - partial RELRO avahi-daemon (2194) - partial RELRO kdm (2227) - partial RELRO nscd (2286) - full RELRO NetworkManager (2290) - partial RELRO cupsd (2291) - partial RELRO modem-manager (2293) - partial RELRO dbus-launch (2294) - partial RELRO dbus-daemon (2297) - partial RELRO wpa_supplicant (2299) - partial RELRO nm-system-setti (2302) - partial RELRO master (2377) - partial RELRO pickup (2391) - partial RELRO qmgr (2392) - partial RELRO cron (2404) - partial RELRO dhclient (2475) - partial RELRO vmware-guestd (2484) - partial RELRO sshd (2485) - partial RELRO startpar (2488) - partial RELRO mingetty (2607) - partial RELRO mingetty (2609) - partial RELRO mingetty (2611) - partial RELRO mingetty (2613) - partial RELRO mingetty (2615) - partial RELRO mingetty (2622) - partial RELRO startkde (2658) - partial RELRO dbus-launch (3025) - partial RELRO dbus-daemon (3029) - partial RELRO kdeinit4 (3048) - partial RELRO klauncher (3052) - partial RELRO kded4 (3085) - partial RELRO kwrapper4 (3289) - partial RELRO ksmserver (3290) - partial RELRO kwin (3292) - partial RELRO plasma (3299) - partial RELRO knotify4 (3300) - partial RELRO krunner (3305) - partial RELRO nepomukserver (3307) - partial RELRO nepomukservices (3309) - partial RELRO nepomukservices (3310) - partial RELRO nepomukservices (3311) - partial RELRO kaccess (3315) - partial RELRO kmix (3325) - partial RELRO amarok (3327) - partial RELRO policykit-kde (3331) - partial RELRO pulseaudio (3334) - partial RELRO klipper (3336) - partial RELRO kupdateapplet (3340) - partial RELRO knetworkmanager (3341) - partial RELRO kdeinit (3346) - partial RELRO dcopserver (3349) - partial RELRO klauncher (3351) - partial RELRO kded (3354) - partial RELRO kio_file (3379) - partial RELRO konsole (3421) - partial RELRO bash (3423) - partial RELRO sshd (3436) - partial RELRO sshd (3439) - partial RELRO bash (3440) - partial RELRO krunner_lock (4565) - partial RELRO kblankscrn.kss (4567) - partial RELRO udevd (529) - partial RELRO # of Processes: 78 No RELRO : 0 Partial RELRO : 77 Full RELRO : 1
Conclusion
In case of a bss or data overflow bug both partial and full RELRO protect the ELF internal data sections from being overwritten.With full RELRO a working mitigation technique to successfully prevent the modification of GOT entries is available. But as the above testcases have shown, this mitigation technique is not used as default on current Linux distris. The only argument why full RELRO isn't widely used is that the startup of processes is slowed down as the linker has to perform all relocations at startup time.
In consequence the good old GOT overwrite technique can still be used today to get reliable control of the execution flow of a process while exploiting "write n bytes anywhere in memory" bugs like the one in FFmpeg. To gain reliable code execution from that point if ASLR and NX are also enabled is another story :)
There is another interesting writeup available that describes a generic way to implement a similar mitigation technique for ELF objects even if the platform doesn't support RELRO.