libmemguard: The Ultimate Tool for Tracking Down Memory Illegal-Access Bugs

Introduction

First, the gayhub link: libmemguard. The idea, in short, is: if some variable / some memory region in the program gets modified for no apparent reason, we can use mprotect to mark that region read-only. Then, when it gets written to, an exception signal will be triggered. The faulting memory access address will be passed as a parameter to the signal handler, which helps with debugging.

Code

Talk is cheap, show the code first.

Note that this code is only a proof-of-concept demo. In practice, GNU already provides a POSIX cross-platform library libsigsegv to do this.

:::C++
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <stdint.h>
#include <time.h>
#include <execinfo.h>
#include <limits.h>
#include <ucontext.h>
#include <sys/types.h>
#include <sys/mman.h>

#define arrsizeof(x) (sizeof(x)/sizeof(x[0]))
#define PAGE_ALIGN(addr)  (void *)((uint64_t)addr & ~(4096 - 1))

static char buffer[4096];
static void *buf_page_addr = NULL;

static void handler(int sig, siginfo_t *siginfo, void *ctx)
{
  ucontext_t *context = (ucontext_t *)ctx;
  int ret = mprotect(buf_page_addr, 4096, PROT_READ | PROT_WRITE);
  assert(ret == 0);
    printf ("Violate addr = %p, RIP = 0x%08x\n",
          siginfo->si_addr, context->uc_mcontext.gregs[REG_RIP]);
  void *bt[128];
  int bt_size;
  bt_size = backtrace(bt, arrsizeof (bt));
  backtrace_symbols_fd(bt, bt_size, fileno(stderr));
  exit(0);
}

void test() {
  buffer[0] = 1;
  buf_page_addr = PAGE_ALIGN(buffer);
  printf("now protect addr %p.\n", buffer);
  int ret = mprotect(buf_page_addr, 4096, PROT_READ);
  assert(ret == 0);
  assert(buffer[0] == 1);
  buffer[0] = 2;
  assert(buffer[0] == 2);
}

int main (int argc, char *argv[])
{
  printf("main addr = %p\n", main);
    struct sigaction act;
    memset (&act, '\0', sizeof(act));

    /* Use the sa_sigaction field because the handles has two additional parameters */
    act.sa_sigaction = &handler;

    /* The SA_SIGINFO flag tells sigaction() to use the sa_sigaction field, not sa_handler. */
    act.sa_flags = SA_SIGINFO;

    if (sigaction(SIGSEGV, &act, NULL) < 0 ||
      sigaction(SIGBUS, &act, NULL) < 0 ||
      sigaction(SIGTRAP, &act, NULL) < 0) {
        perror ("sigaction");
        return 1;
    }
  puts("Test ready.");

  test();

    return 0;
}

Compile:

 g++ -g -rdynamic test.cpp -o test

Output:

$ ./test
main addr = 0x400da8
Test ready.
now protect addr 0x601480.
Violate addr = 0x601480, RIP = 0x00400d7b
./test[0x400cb5]
/opt/compiler/gcc-4.8.2/lib/libc.so.6(+0x35470)[0x7f0a08a6b470]
./test(_Z4testv+0x94)[0x400d7b]
./test(main+0xde)[0x400e86]
/opt/compiler/gcc-4.8.2/lib/libc.so.6(__libc_start_main+0xf5)[0x7f0a08a57bd5]
./test[0x400af9]

Code Notes

The code above is only a demo and does not implement the address-checking mechanism. There is one small detail to pay attention to: when we enter the signal handler, if we want the program to continue executing correctly, we need to clear the exception. For example, if this is a memory access fault, we must remove protection for that memory region; otherwise, EIP will return to the instruction that previously faulted and re-execute it, similar to a page fault—except that our handler did not fix the problem, so it will fall into an infinite loop.

Note that, according to POSIX, we actually cannot call a non-async-signal-safe function like mprotect in a signal-handling context, because it can disrupt the user-space state. However, considering that Linux (and all modern Unix systems) implement it as a system call, in practice we can safely enter kernel mode here without causing surprises. Of course, for portability, one should still strictly follow the POSIX spec—apparently the only portable behavior is to exit...

Limitations and Improvements

One issue is that mprotect clearly cannot protect only the small portion we want to inspect. Because x86 memory permission bits are tied to page tables, we must change permissions for at least an entire page. In that case, we can check in the handler whether the address is within the memory region we care about; if it is, print the stack trace or log it; if not, we can consider triggering a core dump directly.

But how do we focus only on the small region we want to protect (smaller than pagesize), and if some other address in the same page (that we do not care about) is written, we just let it pass and allow the application to continue executing that memory operation? One feasible idea is to use the third parameter ucontext (Linux Only) to modify the return address to skip the instruction that triggered the signal. But this approach is obviously not platform-independent at all. libsigsegv also does not implement such functionality; instead, in this case it directly unregisters the signal handler. Perhaps it really is tricky to implement. gdb does implement something similar, but it uses hardware breakpoints via debugging registers, and requires a parent/child process model to call ptrace to set up the debugging logic. This mechanism is specifically for implementing debuggers, somewhat complex, and the number of hardware breakpoints you can set depends on the number of debug registers.

The Trap Approach

What we actually need is this: for the memory read/write instruction that we want to “allow”, we can first remove page protection for the corresponding address with mprotect, execute that instruction, then re-enable page protection with mprotect, and finally skip over that instruction to execute the next one. So the question becomes: how do we execute only this single memory-access instruction at runtime and then put the protection back? An intuitive idea is: can we JIT a function, copy that memory-access instruction into it, and insert the required system calls before and after it? The problem is that we cannot guarantee the instruction’s context will be exactly the same as the original; register values may very well not match expectations.

So instead, in the signal-handler callback, we remove page protection, then directly modify the first byte of the next instruction to int 3, and use the corresponding callback to restore page protection and revert that next instruction back. As for how to determine an instruction’s length at runtime in order to locate the next instruction, a simple approach is to use some disassembly tool libraries such as libudis86, but in fact we have a better option: in reverse engineering there is a concept called a length disassembler, meaning it is only used to disassemble instruction lengths. Such an implementation is of course much more efficient.

comments powered by Disqus
Published:
2017-02-09
Category:
Tag: