Saturday, February 21, 2009

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
Full RELRO
  • 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.