Wrapper routine for write() with unistd.h included results in error

北城以北 提交于 2019-12-04 12:51:05
jschmier

An alternative to using the GNU liner --wrap symbol option as suggested by Matthew Slattery would be to use dlsym() to obtain the address of the execve() symbol at runtime in order to avoid the compile-time issues with including unistd.h.

I suggest reading Jay Conrod's blog post entitled Tutorial: Function Interposition in Linux for additional information on replacing calls to functions in dynamic libraries with calls to your own wrapper functions.

The following example provides a write() wrapper function that calls the original write() before calling execve() and does not include unistd.h. It is important to note that you cannot directly call the original write() from the wrapper because it will be interpreted as a recursive call to the wrapper itself.

Code:

#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>

size_t write(int fd, const void *buf, size_t count)
{
    static size_t (*write_func)(int, const void *, size_t) = NULL;    
    static int (*execve_func)(const char *, char *const[], char *const[]) = NULL;

    /* arguments for execve()  */
    char *path = "/bin/echo";
    char *argv[] = { path, "hello world", NULL };
    char *envp[] = { NULL };

    if (!write_func)
    {
        /* get reference to original (libc provided) write */
        write_func = (size_t(*)(int, const void *, size_t)) dlsym(RTLD_NEXT, "write");
    }

    if (!execve_func)
    {
        /* get reference to execve */
        execve_func = (int(*)(const char *, char *const[], char *const[])) dlsym(RTLD_NEXT, "execve");
    }

    /* call original write() */
    write_func(fd, buf, count);

    /* call execve() */
    return execve_func(path, argv, envp);
}

int main(int argc, char *argv[])
{
    int filedes = 1;
    char buf[] = "write() called\n";
    size_t nbyte = sizeof buf / sizeof buf[0];

    write(filedes, buf, nbyte);

    return 0;
}

Output:

$ gcc -Wall -Werror -ldl test.c -o test
$ ./test
write() called
hello world
$

Note: This code is provided as an example of what is possible. I would recommend following Jonathan Leffler's advice on code segregation in constructing the final implementation.

The GNU linker has a --wrap <symbol> option which allows you to do this sort of thing.

If you link with --wrap write, references to write will redirect to __wrap_write (which you implement), and references to __real_write will redirect to the original write (so you can call it from within your wrapper implementation).

Here's a sophisticated test application using write() - I'm doing the compilation and linking steps separately because I'll want to use hello.o again in a minute:

$ cat hello.c
#include <unistd.h>

int main(void)
{
    write(0, "Hello, world!\n", 14);
    return 0;
}
$ gcc -Wall -c hello.c
$ gcc -o test1 hello.o
$ ./test1
Hello, world!
$

Here's an implementation of __wrap_write(), which calls __real_write(). (Note that we want a prototype for __real_write to match the original. I've added a matching prototype explicitly, but another possible option is to #define write __real_write before #include <unistd.h>.)

$ cat wrapper.c
#include <unistd.h>

extern ssize_t __real_write(int fd, const void *buf, size_t n);

ssize_t __wrap_write(int fd, const void *buf, size_t n)
{
    __real_write(fd, "[wrapped] ", 10);
    return __real_write(fd, buf, n);
}
$ gcc -Wall -c wrapper.c
$

Now, link the hello.o we made earlier with wrapper.o, passing the appropriate flags to the linker. (We can pass arbitrary options through gcc to the linker using the slightly odd -Wl,option syntax.)

$ gcc -o test2 -Wl,--wrap -Wl,write hello.o wrapper.o
$ ./test2
[wrapped] Hello, world!
$

It is an utterly bad idea to try wrapping write() and use POSIX functions. If you chose to work in standard C, then you could wrap write() because it is not a name reserved to the standard. However, once you start using POSIX functions - and execve() is a POSIX function - then you are running into conflicts; POSIX reserves the name write().

If you want to try, you may get away with it if you segregate the code carefully. You have your write() wrapper in one source file which does not include <unistd.h> or use any functions not defined in the C standard for the headers you do include. You have your code that does the execve() in a second file that does include <unistd.h>. And you link those parts together with appropriate function calls.

If you are lucky, it will work as intended. If you aren't lucky, all hell will break loose. And note that your luck status might change on different machines depending on factors outside your control such as o/s updates (bug fixes) or upgrades. It is a very fragile design decision to wrap write().

Just making an illustration for Muggen's attention call (therefore community wiki):

You want to redefine write and call write from inside your redefinition. Something like

void write(int a) {
    /* code code code */
    write(42);                  /* ??? what `write`?
                                   ??? recursive `write`?
                                   ??? the other `write`? */
    /* code code code */
}

Better think better about it :)

jschmier

If you segregate your code appropriately as suggested by Jonathan Leffler, you should be able to avoid compile-time issues related to including unistd.h. The following code is provided as an example of such segregation.

Note that you cannot interpose internal library function calls, since these are resolved before runtime. For instance, if some function in libc calls write(), it will never call your wrapper function.

Code:
exec.c

#include <unistd.h>

inline int execve_func(const char *path, char *const argv[], char *const envp[])
{
    return execve(path, argv, envp);
}

test.c

#include <stdio.h>

extern int execve_func(const char *, char *const[], char *const[]);

size_t write(int fd, const void *buf, size_t count)
{    
    /* arguments for execve()  */
    char *path = "/bin/echo";
    char *argv[] = { path, "hello world", NULL };
    char *envp[] = { NULL };

    return execve_func(path, argv, envp);
}

int main(int argc, char *argv[])
{
    int filedes = 1;
    char buf[] = "dummy";
    size_t nbyte = sizeof buf / sizeof buf[0];

    write(filedes, buf, nbyte);

    return 0;
}

Output:

$ gcc -Wall -Werror test.c exec.c -o test
$ ./test
hello world
$

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!