Tracking down Write/Execute mmap() calls with LD_PRELOAD

One of the access controls in SE Linux is for execmem – which is used to stop processes from creating memory regions that are writable and executable (as they make it easier to compromise programs and get them to execute supplied code). When the SE Linux audit log tells you that a program is attempting such access it’s sometimes difficult to discover where in the code such an access occurs, for example if you have a large code base and mmap() is called in many places it can be difficult to determine which one is the culprit. Especially if you have a source package that contains multiple binaries that use a common shared library and you don’t know which bits of library code are called by each executable.

To solve this problem in the case of freshclam to provide extra information for Debian bug report #588599 [1] I wrote the following little shared object which can be compiled with “gcc -shared -g -fPIC mmap.c -o” and used with “LD_PRELOAD=./ whatever“. Then when the program in question (or any non-SUID program it executes) calls mmap() with both PROT_EXEC and PROT_WRITE set the program will abort. If you run this through gdb then the program will break and you will get a back-trace of the function calls that led to the undesired mmap().

One thing to note is that this method only catches direct calls to a library function outside libc. When the libc code calls the library function (EG all the fwrite() etc code that calls mmap()) the LD_PRELOAD hack won’t catch it. Thanks to Keith Owens for pointing this out.

#include <dlfcn.h>
#include <stdio.h>
#include <sys/mman.h>
#include <stdlib.h>
#undef NDEBUG
#include <assert.h>

void *libc6 = NULL;

void *(*real_mmap)(void *, size_t, int, int, int, off_t);

void do_init()
  libc6 = dlopen("", RTLD_LAZY | RTLD_GLOBAL);
  real_mmap = (void * (*)(void *, size_t, int, int, int, off_t))dlsym(libc6, "mmap");

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
  assert(!(prot & PROT_EXEC) || !(prot & PROT_WRITE));
  return real_mmap(addr, length, prot, flags, fd, offset);

2 comments to Tracking down Write/Execute mmap() calls with LD_PRELOAD