25

Besides the LD_PRELOAD trick , and Linux Kernel Modules that replace a certain syscall with one provided by you , is there any possibility to intercept a syscall ( open for example ) , so that it first goes through your function , before it reaches the actual open ?

Matheus Santana
  • 581
  • 1
  • 6
  • 22
Vhaerun
  • 12,806
  • 16
  • 39
  • 38
  • 1
    The question needs to be clarified - it's much too vague. Why isn't LD_PRELOAD sufficient? – Arafangion May 17 '10 at 14:12
  • 11
    @Arafangion - LD_PRELOAD lets you intercept library calls. But kernel calls are something different. – PP. Jul 08 '10 at 13:31

10 Answers10

19

Why can't you / don't want to use the LD_PRELOAD trick?

Example code here:

/*
 * File: soft_atimes.c
 * Author: D.J. Capelis
 *
 * Compile:
 * gcc -fPIC -c -o soft_atimes.o soft_atimes.c
 * gcc -shared -o soft_atimes.so soft_atimes.o -ldl
 *
 * Use:
 * LD_PRELOAD="./soft_atimes.so" command
 *
 * Copyright 2007 Regents of the University of California
 */

#define _GNU_SOURCE
#include <dlfcn.h>
#define _FCNTL_H
#include <sys/types.h>
#include <bits/fcntl.h>
#include <stddef.h>

extern int errorno;

int __thread (*_open)(const char * pathname, int flags, ...) = NULL;
int __thread (*_open64)(const char * pathname, int flags, ...) = NULL;

int open(const char * pathname, int flags, mode_t mode)
{
    if (NULL == _open) {
        _open = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open");
    }
    if(flags & O_CREAT)
        return _open(pathname, flags | O_NOATIME, mode);
    else
        return _open(pathname, flags | O_NOATIME, 0);
}

int open64(const char * pathname, int flags, mode_t mode)
{
    if (NULL == _open64) {
        _open64 = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open64");
    }
    if(flags & O_CREAT)
        return _open64(pathname, flags | O_NOATIME, mode);
    else
        return _open64(pathname, flags | O_NOATIME, 0);
}

From what I understand... it is pretty much the LD_PRELOAD trick or a kernel module. There's not a whole lot of middle ground unless you want to run it under an emulator which can trap out to your function or do code re-writing on the actual binary to trap out to your function.

Assuming you can't modify the program and can't (or don't want to) modify the kernel, the LD_PRELOAD approach is the best one, assuming your application is fairly standard and isn't actually one that's maliciously trying to get past your interception. (In which case you will need one of the other techniques.)

Tns
  • 390
  • 1
  • 7
DJ Capelis
  • 951
  • 5
  • 8
  • 16
    It's entirely optional for a program to acknowledge LD_PRELOAD. Not every program links with libc. – vipw Dec 12 '12 at 20:33
  • 1
    @vipw can you elaborate? How is it that a program can bypass LD_PRELOAD? Every program not linking with libc has nothing to do with the fact that the linker will load a given library before the others when loading an executable, if specified with LD_PRELOAD. If that library happens to have a function called by the executable, the program looks first at the LD_PRELOAD loaded library. It doesn't matter that subsequent libraries have implement the function as well. – acib708 Mar 11 '15 at 19:29
  • 11
    @acib708 What I mean is that a program can make system calls without using libc. Then the library being loaded doesn't actually matter since no symbols from it are called. Instead, a small piece of assembly to setup the registers and create an interrupt can make the call. – vipw Apr 02 '15 at 09:36
  • @vipw Oh ok, yeah, agree. – acib708 Apr 17 '15 at 20:27
  • 100% agree: I've got that exact issue with Golang: my stub library get loaded but nothing of it gets called beyond the constructor... indeed golang has decided not to use the libc because... reasons. – Eric Aug 18 '21 at 06:47
  • 2
    Gotta love SO where the question is "Besides X..." and the top answer is "Use X". – Timmmm Nov 04 '21 at 23:16
  • @Eric: Go doesn't use glibc because it makes cross compilation, static linking and backwards compatibility a complete nightmare. – Timmmm Nov 04 '21 at 23:17
  • @Timmmm yeah but that's not the point here. Just that there are programs out there that will not play ball with `LD_PRELOAD`. – Eric Nov 25 '21 at 21:24
  • 1
    Yeah which is why the question said `besides LD_PRELOAD` and why it's silly that this answer is `use LD_PRELOAD`. – Timmmm Nov 25 '21 at 22:32
9

First lets eliminate some non-answers that other people have given:

  • Use LD_PRELOAD. Yeah you said "Besides LD_PRELOAD..." in the question but apparently that isn't enough for some people. This isn't a good option because it only works if the program uses libc which isn't necessarily the case.
  • Use Systemtap. Yeah you said "Besides ... Linux Kernel Modules" in the question but apparently that isn't enough for some people. This isn't a good option because you have to load a custom kernal module which is a major pain in the arse and also requires root.
  • Valgrind. This does sort of work but it works be simulating the CPU so it's really slow and really complicated. Fine if you're just doing this for one-off debugging. Not really an option if you're doing something production-worthy.
  • Various syscall auditing things. I don't think logging syscalls counts as "intercepting" them. We clearly want to modify the syscall parameters / return values or redirect the program through some other code.

However there are other possibilities not mentioned here yet. Note I'm new to all this stuff and haven't tried any of it yet so I may be wrong about some things.

Rewrite the code

In theory you could use some kind of custom loader that rewrites the syscall instructions to jump to a custom handler instead. But I think that would be an absolute nightmare to implement.

kprobes

kprobes are some kind of kernel instrumentation system. They only have read-only access to anything so you can't use them to intercept syscalls, only log them.

ptrace

ptrace is the API that debuggers like GDB use to do their debugging. There is a PTRACE_SYSCALL option which will pause execution just before/after syscalls. From there you can do pretty much whatever you like in the same way that GDB can. Here's an article about how to modify syscall paramters using ptrace. However it apparently has high overhead.

Seccomp

Seccomp is a system that is design to allow you to filter syscalls. You can't modify the arguments, but you can block them or return custom errors. Seccomp filters are BPF programs. If you're not familiar, they are basically arbitrary programs that users can run in a kernel-space VM. This avoids the user/kernel context switch which makes them faster than ptrace.

While you can't modify arguments directly from your BPF program you can return SECCOMP_RET_TRACE which will trigger a ptraceing parent to break. So it's basically the same as PTRACE_SYSCALL except you get to run a program in kernel space to decide whether you want to actually intercept a syscall based on its arguments. So it should be faster if you only want to intercept some syscalls (e.g. open() with specific paths).

I think this is probably the best option. Here's an article about it from the same author as the one above. Note they use classic BPF instead of eBPF but I guess you can use eBPF too.

Edit: Actually you can only use classic BPF, not eBPF. There's a LWN article about it.

Here are some related questions. The first one is definitely worth reading.

There's also a good article about manipulating syscalls via ptrace here.

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • Yeah I ended up doing a proof of concept using seccomp and it does work. Quite complicated though - especially if you want to access process memory e.g. to get string syscall parameters. For just filesystem syscalls there is also Landlock in new kernels. Seems like they might expand it to other areas. – Timmmm Mar 10 '22 at 14:44
  • 1
    What about [Syscall User Dispatch](https://docs.kernel.org/admin-guide/syscall-user-dispatch.html) added in Linux kernel 5.11? – Richard Hansen Apr 11 '23 at 20:20
  • In the "rewrite the code" category, [Dr. Syscall](https://drmemory.org/page_drsyscall.html) looks intriguing. – Richard Hansen Apr 13 '23 at 18:56
7

Valgrind can be used to intercept any function call. If you need to intercept a system call in your finished product then this will be no use. However, if you are try to intercept during development then it can be very useful. I have frequently used this technique to intercept hashing functions so that I can control the returned hash for testing purposes.

In case you are not aware, Valgrind is mainly used for finding memory leaks and other memory related errors. But the underlying technology is basically an x86 emulator. It emulates your program and intercepts calls to malloc/free etc. The good thing is, you do not need to recompile to use it.

Valgrind has a feature that they term Function Wrapping, which is used to control the interception of functions. See section 3.2 of the Valgrind manual for details. You can setup function wrapping for any function you like. Once the call is intercepted the alternative function that you provide is then invoked.

  • Valgrind is a full CPU simulator so it isn't so much intercepting syscalls, as providing a hook when a syscall happens on its simulated CPU, before it passes the syscall through to the kernel. – Timmmm Nov 04 '21 at 23:25
  • So it's an option for debugging purposes, but not for production use. – Timmmm Nov 04 '21 at 23:25
5

Some applications can trick strace/ptrace not to run, so the only real option I've had is using systemtap

Systemtap can intercept a bunch of system calls if need be due to its wild card matching. Systemtap is not C, but a separate language. In basic mode, the systemtap should prevent you from doing stupid things, but it also can run in "expert mode" that falls back to allowing a developer to use C if that is required.

It does not require you to patch your kernel (Or at least shouldn't), and once a module has been compiled, you can copy it from a test/development box and insert it (via insmod) on a production system.

I have yet to find a linux application that has found a way to work around/avoid getting caught by systemtap.

2

I don't have the syntax to do this gracefully with an LKM offhand, but this article provides a good overview of what you'd need to do: http://www.linuxjournal.com/article/4378

You could also just patch the sys_open function. It starts on line 1084 of file/open.c as of linux-2.6.26.

You might also see if you can't use inotify, systemtap or SELinux to do all this logging for you without you having to build a new system.

DJ Capelis
  • 951
  • 5
  • 8
2

If you just want to watch what's opened, you want to look at the ptrace() function, or the source code of the commandline strace utility. If you actually want to intercept the call, to maybe make it do something else, I think the options you listed - LD_PRELOAD or a kernel module - are your only options.

pjz
  • 41,842
  • 6
  • 48
  • 60
  • What are the differences between _watching_ and _intercepting_ here? I've used ptrace for intercepting (stopping, changing stuff and going on) syscalls. – Matheus Santana Apr 03 '18 at 09:52
2

If you just want to do it for debugging purposes look into strace, which is built in top of the ptrace(2) system call which allows you to hook up code when a system call is done. See the PTRACE_SYSCALL part of the man page.

Johan Dahlin
  • 25,300
  • 6
  • 40
  • 55
1

Sounds like you need auditd.

Auditd allows global tracking of all syscalls or accesses to files, with logging. You can set keys for specific events that you are interested in.

Troy Rose
  • 19
  • 1
1

Using SystemTap may be an option.

For Ubuntu, install it as indicated in https://wiki.ubuntu.com/Kernel/Systemtap.

Then just execute the following and you will be listening on all openat syscalls:

# stap -e 'probe syscall.openat { printf("%s(%s)\n", name, argstr) }'
openat(AT_FDCWD, "/dev/fb0", O_RDWR)
openat(AT_FDCWD, "/sys/devices/virtual/tty/tty0/active", O_RDONLY)
openat(AT_FDCWD, "/sys/devices/virtual/tty/tty0/active", O_RDONLY)
openat(AT_FDCWD, "/dev/tty1", O_RDONLY)
Jaime Hablutzel
  • 6,117
  • 5
  • 40
  • 57
1

if you really need a solution you might be interested in the DR rootkit that accomplishes just this, http://www.immunityinc.com/downloads/linux_rootkit_source.tbz2 the article about it is here http://www.theregister.co.uk/2008/09/04/linux_rootkit_released/

sztanpet
  • 731
  • 1
  • 12
  • 18
  • 4
    Why suggest an obscure method when other, far more conventional alternatives exist? LD_PRELOAD being the most common. – Arafangion May 17 '10 at 14:10
  • because he wasnt looking for the more conventional ones, or at least thats what I gathered from his original question – sztanpet May 28 '10 at 13:21
  • @Arafangion `LD_PRELOAD` doesn't work in many cases. And, more specifically, he was asking to intercept linux `syscalls`, not libc calls. – Antoine Viallon Jan 12 '23 at 09:55
  • The question back in 2008 may have been different, and nearly a decade ago this wasn’t especially obscure, but it is different today. – Arafangion Jan 14 '23 at 23:53