5

I want to know what program calls a particular executable, including when that executable is used as an interpreter via a shebang line.

This is not quite the same problem as knowing what program accesses a particular file. For example, auditctl -w /usr/bin/myprogram tells me that the program is being executed by… itself, since the audit event is generated after the successful execve call.

One option is to replace the executable by a wrapper program, like this…

#!/bin/sh
logger "$0: executed by uid=$(id -u) ruid=$(id -ur) cmd=$(ps -o args= -p $PPID)"
exec "$0.real" "$@"

But this requires moving the actual file, which is disruptive (the file can't be read-only, it clashes with modifications made by a package manager, etc.). And it doesn't work if the program is used as an interpreter for a script, because shebang doesn't nest. (In that case, auditctl -w /usr/bin/interpreter does give a useful result, but I want a solution that works for both cases.) It also doesn't work for setuid programs if /bin/sh is bash since bash drops privileges.

How can I monitor executions of a particular executable including uses of the executable as a shebang interpreter, and in particular log useful information about the calling process (not just the PPID but at least the process name or the parent executable path, ideally also the invoking user and arguments)? Preferably without replacing the file with a wrapper. A Linux-specific solution is fine.

5
  • 3
    Hmm, the audit subsystem could be used if you don't mind lots of logging; auditctl -a exit,always -F arch=b64 -S execve will log every exec call and the parameters (so a script will show with the binary as the shell and $0 as the script name), so you could build a history of PIDs and track back callers... but that's gonna impact performance! Aug 22, 2016 at 0:29
  • @StephenHarris auditctl -w /path/to/file -p x is enough if the program is called as an interpreter via a shebang — that gives me the path to the script, which is the relevant information in that case. But that doesn't give useful information if the executable is called explicitly: the PPID is logged but not other information about the parent command. Aug 22, 2016 at 0:32
  • Right, that's why you'd need to log all exec's (-S execve rather than -w /path/to/file) and keep track of PIDs. Messy :-( Aug 22, 2016 at 0:35
  • I haven't tried this yet, but it seems like this could be done using systemtap to do a syscall.execve probe where the process's execname is recorded and then if a syscall.execve.return probe for the same processid sees that the new executable is the one you're looking for, the previously recorded execname is logged. Aug 22, 2016 at 0:37
  • Not that messy. You can add filter conditions to the auditctl to give the executable a different key than the rest of the execve conditions and when you find an instance of the monitored executable you can do an ausearch -p for that PPID for a time period within a minute or so the time on the target execve's invocation. Since the PID is unlikely to get recycled quite that fast it should be pretty simple to track back from that provided you're starting from a reliably constructed and succinct list of execve's you actually care about.
    – Bratchley
    Aug 22, 2016 at 0:41

2 Answers 2

5

This would be hacky, but if it's a dynamically linked executable, you could set up a global preload in /etc/ld.so.preload which would only trigger a logging hook if it detected you were in the right executable.

Something like:

#define _XOPEN_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define TARGET "/some_executable"

__attribute__((constructor)) 
static void 
logger(int argc, char** argv){ 
    /*catch own argv right here and parent's later from /proc */

    static char buf[sizeof(TARGET)];

    readlink("/proc/self/exe", buf, sizeof(buf)-1);

    if ( 0==strcmp(TARGET, buf)){
        /* ... */
        syslog(/*...*/);
    }
}

The obvious disadvantage of this approach is it would slightly delay the execution of each dynamically linked executable on your system, but my measurements indicate the delay is quite small (<1ms where fork+exec costs about 2ms).

As for the dropped permission problem, you could have a small setuid-root binary that will unconditionally read and echo its grandparents proc files (the status file, most likely), possibly if and only if its parent is the executable whose parents you want to log. You could then spawn that setuid executable inside your logging hook to obtain the info on the executables parent (grandparent of the setuid helper).

5

You can use the fanotify API to listen for all opens made on files in a given filesystem. The information is provided as a stream of event structures that include a file descriptor that provides the filename being opened, and the process id of the requestor.

Your program can opt to allow or reject the open, which means it can lookup the pid in /proc to find the command and user doing the open, before allowing it to continue, with no possibility of a race condition.

The man page fanotify(7) provides a complete C program to get events and intercede on the open requests, so you just need to plug in some extra lines to get your wanted data from /proc. Note, you will get events for all files in an entire mount point, so test your code on a specially mounted small filesystem.

The fatrace command shows how you get some of this information in a simple test. For example, copy /bin/bash to /tmp/bash, then write a small script ~/test2 with

#!/tmp/bash
pwd

and run fatrace on the /tmp filesystem only (assuming it is a separate tmpfs mount) by using option -c to listen to the current directory filesystem, and -f O to listen for opens:

cd /tmp
sudo fatrace -c -f O

Now when you run

sh -c 'echo $$; ~/test2'
expect -c 'spawn /home/meuh/test2'
~/test2

you should see logged

sh(7360): RO /tmp/bash
expect(7414): RO /tmp/bash
bash(7590): RO /tmp/bash
1
  • It's a lot more obvious if you don't keep spawning new processes - change pwd to exec pwd in the script, and the sh command to sh -c 'echo $$; exec ~/test2' and you will see that the PID in the fatrace output matches the displayed PID from $$. Aug 22, 2016 at 17:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .