Files – How to Redirect a File Descriptor Before Execution

file-descriptorsfilenamesfilesprocess

I know that I can change the file a program writes to by interrupting the process in gdb, closing using the file descriptor and then re-opening with the file name I want. Is there a way to do the same thing at run time?

For example I know that the file I want to change uses file descriptor 5 so I tried

./myexe 5>/dev/null

But all that does is change things so the file of interest is on fd=6.

Best Answer

When a program opens a file, that file ends up on a file descriptor that's free at the time. By opening a file before the program starts, you're only making one more file descriptor busy, so the file you're interested in might end up on a different descriptor. If you want the program to open a different file, you'll need to modify the open operation when it takes place, or intervene afterwards.

One way to modify the operation is to wedge some code between the program and the system library, by preloading a small piece of code. This assumes that the program is a dynamically linked binary, or a script executed by a dynamically linked binary (i.e. it isn't statically linked). Write the following code to a file override_fopen.c:

#include <dlfcn.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef FROM
#error "Define FROM to the path to override in double quotes, e.g. -DFROM='\"/bad\"'"
#endif
#ifndef TO
#error "Define TO to the path to use instead in double quotes, e.g. -DFROM='\"/good\"'"
#endif
FILE *fopen(const char *path, const char *mode) {
    void *(*original_fopen)(const char *, const char *) = dlsym(RTLD_NEXT, "fopen");
    if (!strcmp(path, FROM)) {
        path = TO;
    }
    return original_fopen(path, mode);
}
int open(const char *path, int oflag, ...) {
    int (*original_open)(const char *, int, ...) = dlsym(RTLD_NEXT, "open");
    int ret;
    va_list args;
    if (!strcmp(path, FROM)) {
        path = TO;
    }
    va_start(args, oflag);
    if (oflag & O_CREAT) {
        ret = original_open(path, oflag, (mode_t)va_arg(args, mode_t));
    } else {
        ret = original_open(path, oflag);
    }
    va_end(args);
    return ret;
}

Compile with the following command (that's for Linux, other Unix variants may require different options). Note the quotes around the path you want to override.

gcc -DFROM='"/some/path"' -DTO='"/dev/null"' -D_GNU_SOURCE -O -Wall -fPIC -shared -o override_fopen.so override_fopen.c -ldl

Run the program as follows (on OSX, use DYLD_PRELOAD instead of LD_PRELOAD):

LD_PRELOAD=./override_fopen.so ./myexe

This only works if the program is calling the fopen or open library function. If it calls some other function, you'll need to override that one. You can use ltrace to see what library calls the program makes.

Related Question