1

I am working on a project where a dynamic library (.so) is injected in some target program at runtime (dynamic instrumentation). The library handles its own memory using mmap/munmap. For security reasons, it is required that some mapped region in the library only be writable through the exposed APIs from the library.

What we've done is toggle the write flag of the memory region using mprotect and PROT_WRITE at the prologue/epilogue of the library functions, e.g.:

void foo(void) {
  mprotect(addr, PAGE_SIZE, PROT_READ | PROT_WRITE);
  ...
  ...
  mprotect(addr, PAGE_SIZE, PROT_READ);
}

This works fine for single threaded applications. For multi-threaded applications, other tasks in the same process might be able to write to the mapped library region if a context switch (to a different task in the same process) takes place after the PROT_WRITE was set (so the memory is writable) and before it was cleared.

So, the question is: Is it possible to disable other tasks in the process until foo returns? If not, how do you suggest working around this?

MEE
  • 2,114
  • 17
  • 21
  • Look into locks, semaphores, condition variables :) – The Brofessor Aug 04 '15 at 04:29
  • 1
    @TheBrofessor Those only work when you can specifically identify the code you need to exclude. Won't work when that's "everything except this code". –  Aug 04 '15 at 04:38
  • Depending how you define the critical section, you can achieve either, right? More info on preemption here: https://en.wikipedia.org/wiki/Preemption_(computing) – The Brofessor Aug 04 '15 at 04:51
  • 2
    You want your library to put the protected stuff into a separate process. That's the only way this is going to make any sense. – David Schwartz Aug 04 '15 at 05:06
  • Thanks @DavidSchwartz. That may really be the only workable solution. We were hoping that there could be a way to avoid using a tracer process, since that adds a lot of overhead, e.g., due to context switches. – MEE Aug 06 '15 at 18:23

1 Answers1

1

Your security model is not valid. If your library code can mprotect these regions writable, so can any other part of the program. You cannot prevent this introspectively. Such security properties can only be achieved with some sort of outside supervision, either directly by the kernel or by a tracing process (e.g. using traditional ptrace or seccomp tracing).

If you don't actually need security properties, but just want reliable trapping of writes by already-trusted code, you could achieve something like this by keeping track of all your threads, signaling them all with pthread_kill, and having the signal handler block until the signaling thread allows them to continue.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Our threat model assumes that code execution isn't possible (handled at other layers of the defense), but data corruption is possible. We understand that is might be possible to change the protection flags if the attacker could control a pointer to the library region that happens to be used in some `mprotect` call. – MEE Aug 06 '15 at 18:18
  • A follow up question though: some compilers attempt to "harden" binaries by placing sensitive structures (e.g., virtual tables) in a read-only region. Wouldn't such solutions be susceptible to the same threat, i.e., hijacking a pointer that is passed to some `mprotect`? – MEE Aug 06 '15 at 18:21
  • @John: Such hardening does not protect against malicious code (e.g. `dlopen`ing an untrusted library). It just reduces the risk that exploitable bugs will convert into arbitrary code execution opportunities, where you usually won't have a chance to effect a `mprotect` until you've already achieved code execution. – R.. GitHub STOP HELPING ICE Aug 07 '15 at 01:39