Search code examples
linuxubuntucentoshookld-preload

Difference in behavior when hooking a library function via LD_PRELOAD on Ubuntu and CentOS


There is a hook function socketHook.c that intercepts socket() calls:

#include <stdio.h>
int socket(int domain, int type, int protocol)
{
    printf("socket() has been intercepted!\n");
    return 0;
}
gcc -c -fPIC socketHook.c
gcc -shared -o socketHook.so socketHook.o

And a simple program getpwuid.c (1) that just invokes the getpwuid() function:

#include <pwd.h>

int main()
{
    getpwuid(0);
    return 0;
}
gcc getpwuid.c -o getpwuid

getpwuid() internally makes a socket() call. On CentOS:

$ strace -e trace=socket ./getpwuid
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(AF_UNIX, SOCK_STREAM, 0)         = 4

On Ubuntu:

$ strace -e trace=socket ./getpwuid
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5

When running (1), socket() is intercepted on CentOS, but not on Ubuntu.

CentOS. printf() from socketHook.c is present:

$ uname -a
Linux centos-stream 4.18.0-301.1.el8.x86_64 #1 SMP Tue Apr 13 16:24:22 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ LD_PRELOAD=$(pwd)/socketHook.so ./getpwuid
socket() has been intercepted!

Ubuntu(Xubuntu 20.04). printf() from socketHook.c is NOT present:

$ uname -a
Linux ibse-VirtualBox 5.8.0-50-generic #56~20.04.1-Ubuntu SMP Mon Apr 12 21:46:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ LD_PRELOAD=$(pwd)/socketHook.so ./getpwuid
$

So my question is:

  1. What does it depend on? I think this is affected by the fact that socket() is not called directly from the executable, but from getpwuid(), which in turn is called, if I understand correctly, from libc.so
  2. How to achieve the same behavior in CentOS as in Ubuntu? I don't want intercept indirect calls from libc

Solution

  • What does it depend on?

    There are two questions to ask:

    1. Which function actually calls the socket system call?
    2. How is that function getting called.

    You can see how the socket system call is invoked by running your program under GDB, and using catch syscall socket command. On Ubuntu:

    (gdb) catch syscall socket    
    Catchpoint 1 (syscall 'socket' [41])
    (gdb) run 
    Starting program: /tmp/a.out 
    
    Catchpoint 1 (call to syscall socket), 0x00007ffff7ed3477 in socket () at ../sysdeps/unix/syscall-template.S:120
    120     ../sysdeps/unix/syscall-template.S: No such file or directory.
    (gdb) bt
    #0  0x00007ffff7ed3477 in socket () at ../sysdeps/unix/syscall-template.S:120
    #1  0x00007ffff7f08010 in open_socket (type=type@entry=GETFDPW, key=key@entry=0x7ffff7f612ca "passwd", keylen=keylen@entry=7) at nscd_helper.c:171
    #2  0x00007ffff7f084fa in __nscd_get_mapping (type=type@entry=GETFDPW, key=key@entry=0x7ffff7f612ca "passwd", mappedp=mappedp@entry=0x7ffff7f980c8 <map_handle+8>) at nscd_helper.c:269
    #3  0x00007ffff7f0894f in __nscd_get_map_ref (type=type@entry=GETFDPW, name=name@entry=0x7ffff7f612ca "passwd", mapptr=mapptr@entry=0x7ffff7f980c0 <map_handle>, 
        gc_cyclep=gc_cyclep@entry=0x7fffffffda0c) at nscd_helper.c:419
    #4  0x00007ffff7f04fb7 in nscd_getpw_r (key=0x7fffffffdaa6 "0", keylen=2, type=type@entry=GETPWBYUID, resultbuf=resultbuf@entry=0x7ffff7f96520 <resbuf>, 
        buffer=buffer@entry=0x5555555592a0 "", buflen=buflen@entry=1024, result=0x7fffffffdb60) at nscd_getpw_r.c:93
    #5  0x00007ffff7f05412 in __nscd_getpwuid_r (uid=uid@entry=0, resultbuf=resultbuf@entry=0x7ffff7f96520 <resbuf>, buffer=buffer@entry=0x5555555592a0 "", buflen=buflen@entry=1024, 
        result=result@entry=0x7fffffffdb60) at nscd_getpw_r.c:62
    #6  0x00007ffff7e9e95d in __getpwuid_r (uid=uid@entry=0, resbuf=resbuf@entry=0x7ffff7f96520 <resbuf>, buffer=0x5555555592a0 "", buflen=buflen@entry=1024, 
        result=result@entry=0x7fffffffdb60) at ../nss/getXXbyYY_r.c:255
    #7  0x00007ffff7e9dfd3 in getpwuid (uid=0) at ../nss/getXXbyYY.c:134
    #8  0x0000555555555143 in main () at t.c:5
    
    (gdb) info sym $pc
    socket + 7 in section .text of /lib/x86_64-linux-gnu/libc.so.6
    (gdb) up
    #1  0x00007ffff7f08010 in open_socket (type=type@entry=GETFDPW, key=key@entry=0x7ffff7f612ca "passwd", keylen=keylen@entry=7) at nscd_helper.c:171
    171     nscd_helper.c: No such file or directory.
    (gdb) x/i $pc-5
       0x7ffff7f0800b <open_socket+59>:     callq  0x7ffff7ed3470 <socket>
    

    From this we can see that

    1. The function socket is called. Using nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep ' socket' we can confirm that that function is exported from libc.so.6, and thus should be interposable.
    2. The caller does not call socket@plt (i.e. does not use the procedure linkage table), and so LD_PRELOAD will have no effect.

    The call from open_socket() to socket() has been non-interposable since 2004, so it's likely that this call isn't intercepted on CentOS either, but some other call is. Probably the 3rd one in your strace output.

    Using above method you should be able to tell where that call comes from.


    I don't want intercept indirect calls from libc

    In that case, LD_PRELOAD may be the wrong mechanism to use.

    If you want to only intercept socket() calls from your own code, it's trivial to redirect them to e.g. mysocket() without any need for LD_PRELOAD.

    You can do that at source level by adding e.g.

    #define socket mysocket
    

    to all your files, or using -Dsocket=mysocket argument at compile time.

    Alternatively, using the linker --wrap=socket will do the redirection without recompiling.