Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Segmentation fault from hook on open()

Tags:

c

linux

gcc

I am trying to create a hook on the system function open(). I've done this along the following lines.

I created a wrapper library with the following:

extern int mocked_open(const char* fn, int flags, va_list args);

int open(const char* fn, int flags, ...)
{
    int r = -1;
    va_list args;

    va_start(args, flags);
    r = mocked_open(fn, flags, args);
    va_end(args);

    return r;
}

I compile this into libwrapper.so, which I load using LD_PRELOAD.

The implementation of mocked_open() is as follows (I use the CPPUtest framework):

int mocked_open(const char* fn, int flags, va_list args)
{
    if (strncmp(fn, test_device_id, 11) == 0)
    {
        return mock().actualCall("open").returnValue().getIntValue();
    }
    else
    {
        int r = -1;
        int (*my_open)(const char*, int, ...);
        void* fptr = dlsym(RTLD_NEXT, "open");
        memcpy(&my_open, &fptr, sizeof(my_open));

        if (flags & O_CREAT)
        {
            r = my_open(fn, flags, va_arg(args, mode_t));
        }
        else
        {
            r = my_open(fn, flags);
        }

        return r;
    }
}

The test_device_id is a simple string ("test_device"), which I hope is not used elsewhere.

During running the tests the executable crashes with a segmentation fault. I've traced this down to the GCC profiling functionality, which wants to open/create a bunch of .gcda files and calls open() for this.

After some debugging with strace (per suggestion below), I found that the line r = my_open(fn, flags, va_arg(args, mode_t)); is indeed the culprit. It is being called recursively, or so it seems: I see a lot of calls to this line, without the function returning. Then a segfault. The file being opened is the corresponding .gcda file (for profiling). In fact, the segfault only occurs with profiling enabled.

like image 264
Ludo Avatar asked Feb 16 '15 13:02

Ludo


2 Answers

Try this

typedef int (*OpenFunction)(const char* fn, int flags, ...);

then

OpenFunction function;
void      **pointer;

pointer  = (void **)&function;
*pointer = dlsym(RTLD_NEXT, "open");

this is a complete working example

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <fcntl.h>

#include <errno.h>

typedef int (*OpenFunction)(const char* fn, int flags, ...);

int main(int argc, char **argv)
{
    OpenFunction function;
    void       *dl;
    int         fd;
    void      **pointer;

    if (argc < 2)
        return -1;
    pointer  = (void **)&function;
    *pointer = dlsym(RTLD_NEXT, "open");

    fd = function(argv[1], O_RDONLY);
    if (fd != -1)
    {
        printf("file opened succesfully\n");
        close(fd);
    }
    else
    {
        printf("%s: cannot open the file\n", strerror(errno));
    }
    return 0;
}
like image 145
Iharob Al Asimi Avatar answered Nov 04 '22 10:11

Iharob Al Asimi


When you compile with gcov profiling enabled, the compiler inserts extra code into your functions, to keep track of which code has been executed. In rough pseudocode, that inserted code will do (among other things):

if (!output_file_has_been_opened) {
    fd = open(output_filename, ...);
    check_ok(fd);
    output_file_has_been_opened = TRUE;
    track_coverage();
}

... so if the output file hasn't yet been successfully opened (as at the start of your program), it will attempt to open it. Unfortunately, in this case that will call your mocked open() function - which has the same inserted code; since the file still hasn't been successfully opened, and since the gcov code isn't aware that there's something unusual going on, it will attempt the open() call again - and this is what's causing the recursion (and eventual segfault, once the stack is exhausted).

like image 4
psmears Avatar answered Nov 04 '22 12:11

psmears