Having Trouble Wrapping Functions in The Linux Kernel

Having trouble wrapping functions in the linux kernel

1.It seems, the beginning of security_bprm_check() is not restored completely before the function is called. The oops happens at security_bprm_check+0x6, i.e. right after the jump you placed there, so it seems, some part of the jump is still there at that moment. I cannot say now why this can happen.

Take a look at the implementation of Kernel Probes (KProbes) on x86, it may give you some hints. See also the description of KProbes for details. KProbes need to patch and restore almost arbitrary pieces of kernel code in a safe way to do their work.

2.Now to the other approach that you mentioned, concerning copying of the function. The following is a bit of a hack and would be frowned upon by the kernel developers but if there is no other way, this might help.

You can allocate memory to copy the functions to from the same area where the memory for the code of the kernel modules is allocated. That area should be executable by default. Again, KProbes use this trick to allocate their detour buffers.

Memory is allocated by module_alloc() function and freed by module_free(). These functions are of course not exported but you can find their addresses in the same way as you do for security_file_mmap(), etc. Just of curiosity, you are using kallsyms_on_each_symbol(), right?

If you allocate memory this way, this could also help avoid another not so obvious problem. On x86-64, the memory address areas available for kmalloc and for the modules' code are located quite far away from each other (see Documentation/x86/x86_64/mm.txt), beyond the reach of any relative jump. If the memory is mapped to the modules' address area, you can use near relative jumps and calls to call the copied functions. A similar problem with RIP-relative addressing is also avoided this way.

EDIT: Note that on x86, if you copy some piece of code to a different memory area and you would like it to run there, some changes in that code may be necessary. At least you need to fixup the relative calls and jumps that transfer control outside of the copied code (e.g. the calls to another function, etc.) as well as the instructions with RIP-relative addressing.

Apart from that, there may be other structures in the code that need to be fixed up. For example, the compiler might have optimized some or even all switch statements to a jump via a table. That is, the addresses of the code blocks for each case are kept in a table in the memory and the switch variable is the index into that table. This way, instead of many comparisons, your module will execute something like jmp <table_start>(%reg, N) (N is the size of a pointer, in bytes). That is, just a jump to an address that is in the appropriate element of the table. Because such tables are created for the code before you copy it, fixup may be necessary otherwise such jumps will take the execution back to the original piece of code rather than the copied one.

Why wrap functions?

The example you gave is not a very good example, because it has nothing to do with the Linux kernel. This is just basic software engineering.

When you have two functions that need to have very close functionality, there are several paths you can take.

  1. You can implement the function twice. We don't like to do that, as it creates code duplication. It also means that if you need to change something in the common area of the code, you need to remember to change it at two places.

  2. You can split the common code into its own function, and call that function from each of the functions. That is the best solution if it is possible. The problem is that it is not always possible. It might not be possible because the common code needs too much context, or because it needs to be spread out across the function. Which brings us right to:

  3. Create an internal "common" function, with an argument telling which functionality to provide. Just write the code, and put an if where the two functions need to do something different. That is the path the kernel took in your example.

With that said, there is another case, specific to the Linux kernel, where two functions really do seem to be almost identical. On the i386 platform, the stat system call is implemented not twice, but three times:

  • oldstat syscall number 18
  • stat syscall number 106
  • stat64 syscall number 195

The reason for that is that the Linux kernel promises complete backwards compatibility over its user space kernel interface. When a function has to be superseded for some reason, as happened to stat not once, but twice (three times if you count fstatat), the old system call entry needs to be kept around and remain operational.

If you look at the actual implementation, however, you will notice that there is very little difference between them, and they all end up calling, pretty much, the same function.

Timekeeping in Linux kernel 2.6

What you are implementing here is essentially msleep_interruptible() (linux/kernel/timer.c)

/**
* msleep_interruptible - sleep waiting for signals
* @msecs: Time in milliseconds to sleep for
*/
unsigned long msleep_interruptible(unsigned int msecs)

This function has the advantage that the specification is in milliseconds and hides the details of jiffies wrapping internally. Be sure to check the return values as this call returns the number of jiffies remaining. Zero means the call slept the specified number of milliseconds while a non-zero value indicates the call was interrupted this many jiffies early.

With regard to wrapping, see section 6.2.1.2 for a description of jiffies and wrapping. Also, this post tries to describe wrapping in the abstract.

Linker error with intercepting function calls in GCC

It is likely you forgot to add -Wl,--wrap=connect and -Wl,--wrap=stat to your link line.

This works for me:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>

int __wrap_connect (int s, const struct sockaddr *addr, socklen_t len)
{
puts(__func__);
return __real_connect(s, addr, len);
}

int __wrap_stat (const char *path, struct stat *buf)
{
puts(__func__);
return __real_stat(path, buf);
}

int main(void) {
connect(0, NULL, 0);
stat("/", 0);
return 0;
}

When compiled on my system.

$ uname -s -r
Linux 2.6.32-696.16.1.el6.x86_64
$ gcc --version | grep gcc
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-18)
$ gcc c.c -Wl,--wrap=connect -Wl,--wrap=stat
$

However, when leaving off -Wl,--wrap=stat, for example, I get:

$ gcc c.c -Wl,--wrap=connect
/tmp/cchVzvsE.o: In function `__wrap_stat':
c.c:(.text+0x65): undefined reference to `__real_stat'
collect2: ld returned 1 exit status
$


Related Topics



Leave a reply



Submit