3

Is it expected that changes made to a program's address space would not be reverted during reverse debugging?

I have been debugging a program which segfaults when a pointer to strlen in the GOT becomes corrupted during the course of execution. Thanks to advice stemming from comments to this question, I made the GOT of this program read-only by linking with the -z relro option; however, this does not prevent the pointer in question from being overwritten. Namely, I can start the program in gdb, step to the first occurrence of strlen, verify that the pointer is valid (for example: x/g 0x5555555d10a8 ==> 0x5555555d10a8 <strlen@got.plt>: 0x00007ffff7e8d1e0), continue running, and wait for the pointer to become invalid (pointing to a nonsensical address outside the address space of the program; for example: x/g 0x5555555d10a8 ==> 0x5555555d10a8 <strlen@got.plt>: 0x0000000000002156), prompting a segv.

However, if I record full the entire execution (from the first line until the program segfaults), and then awatch the address with the pointer to strlen during reverse-continue, the watchpoint never triggers. And when the program has finally gotten back to instruction #0, the pointer is still pointing to the invalid address that it had when it segfaulted.

This leads to two questions. First, why is the GOT mutable despite the -z relro linker option? Second, is it expected for a location in memory (the pointer to strlen) that is altered during program execution to not be restored to its original value during reverse execution?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
user001
  • 1,850
  • 4
  • 27
  • 42

2 Answers2

2

First, why is the GOT mutable despite the -z relro linker option?

What you are applying with -Wl,-z,relro is only partial RELRO protection. This option has the main purpose of moving the GOT before the BSS to avoid corruption of entries due to overflows from buffers in the BSS, and also makes a small portion of the GOT read-only. What you want is full RELRO, which is obtained with -Wl,-z,relro,-z,now. The -z now linker flag makes the loader resolve all symbols at startup, and then remap the segment containing the GOT as read-only, before starting the program.

Second, is it expected for a location in memory (the pointer to strlen) that is altered during program execution to not be restored to its original value during reverse execution?

You probably forgot to set target record-full before starting debugging, but I'm not sure that has any effect since you already use record full. Reverse execution is not easy, the debugger might decide not to reverse the memory modifications of the GOT (or other sections) for performance reasons (since those should normally not be touched anyway). GDB's record feature also has some other limitations, like not supporting AVX instructions for example. I don't know much more to give a better answer. You can try your luck with rr.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • Thanks, does `-z now` do the same thing as `export LD_BIND_NOW=1`? I set that flag before execution because the recorder in gdb otherwise complained about some functions not being recordable (I believe avx ones). So `target record-full` must be run before `record full`? I typed `info record` and the output led me to the belief that recording was working properly. I was also able to `record save` the output for further study. – user001 Jan 13 '20 at 22:55
  • 1
    @user001 not quite, `LD_BIND_NOW` loads everything at start, but does not remap as read-only. As per `target record-full` I missed that part in your question, it's probably the same thing and you're already doing it correctly (trying one more time doesn't hurt though). – Marco Bonelli Jan 13 '20 at 23:00
  • Thanks again. So the combination of `-Wl,-z,relro` and `LD_BIND_NOW=1` is not the same as the combination of `-Wl,-z,relro -Wl,-z,now`? – user001 Jan 13 '20 at 23:07
  • 1
    @user001 correct. The difference is that linking with `-z now` will place the GOT in such a section that it is possible for the loader to re-map as read-only after resolving symbols at startup, whereas linking without it and using `LD_BIND_NOW` only resolves the symbols at startup, but does not remap the section since it cannot know if it's possible to (the GOT could be near other data that needs to be R/W, since the executable wasn't linked with `-z now`). – Marco Bonelli Jan 13 '20 at 23:10
1

The -z relro option only does partial read-only (In partial RELRO, the non-PLT part of the GOT section (.got from readelf output) is read only but .got.plt is still writeable. Whereas in complete RELRO, the entire GOT (.got and .got.plt both) is marked as read-only.

If you want to have full readonly use -z, now as well.

Hardening ELF binaries using Relocation

l'L'l
  • 44,951
  • 10
  • 95
  • 146