Wrapping Linux shared library functions

These tricks are widely documented, so these are mostly notes for me.

For an example of replacing, rather than wrapping, a shared library function, one can consider the simple code given on the apt-get and LD_PRELOAD page, or the more complex example on the 64 bit inodes page.

LD_PRELOAD can be used to preload a shared library, and its definitions will take precidence over other shared libraries. However, this won't work for setuid/setgid programs. It something needs to run in this fashion, it must be wrapped in something which sets the real and effective uids/gids equal before the exec. The LD_PRELOAD appears to be removed from the environment on execution of a setuid/setgid program. See the man page of ld.so for more details.

The dlsym function can be used to return a pointer to the next function of a given name. However, the RTLD_NEXT constant will not be defined unless GNU_SOURCE is defined, or maybe _GNU_SOURCE, or perhaps __USE_GNU, ...

So, here is the obligatory example. For a slightly different example, there is a page on wrapping to enable TCP keep-alives (useful for making ffmpeg time out when IP cameras disappear).

#include <stdio.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>

#include <errno.h>

#define __USE_GNU
#include <dlfcn.h>

int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen){
  static int (*connect_real)(int, const struct sockaddr*, socklen_t)=NULL;
  unsigned char *c;
  int port,ok=1;

  if (!connect_real) connect_real=dlsym(RTLD_NEXT,"connect");

  if (serv_addr->sa_family==AF_INET6) return EACCES;

  if (serv_addr->sa_family==AF_INET){
    c=serv_addr->sa_data;
    port=256*c[0]+c[1];
    c+=2;
    ok=0;

    // Allow all contacts with localhost
    if ((*c==127)&&(*(c+1)==0)&&(*(c+2)==0)&&(*(c+3)==1)) ok=1;

    // Allow contact to any WWW cache on 8080
    if (port==8080) ok=1;
  }

  if (ok) return connect_real(sockfd,serv_addr,addrlen);

  if (getenv("WRAP_TCP_DEBUG"))
      fprintf(stderr,"connect() denied to address %d.%d.%d.%d port %d\n",
              (int)(*c),(int)(*(c+1)),(int)(*(c+2)),(int)(*(c+3)),port);

  return EACCES;
}

To use, compile with

$ gcc -o demo.so -shared wrap_demo.c -ldl
and use with
LD_PRELOAD=`pwd`/demo.so firefox

The code wraps the connect() call, which is used when establishing an outgoing TCP connection. It allows all connections to localhost (127.0.0.1), and to port 8080 anywhere. Anything else, including all IPv6, is denied. If the environment variable WRAP_TCP_DEBUG is set, it will write a line to stderr when denying a connection.

The call to dlsym() is quite slow, so here it is called just once, and the resulting pointer kept in a static variable. In this case it does not matter: connect() is much slower than dlym(). However, in other cases it might matter, so this example shows the faster method.

The above is, of course, an example, not a security feature. Statically-linked programs are immune, as are programs which call the kernel functions directly. And if one was thinking of a software firewall, one would need to consider UDP and incoming TCP.