How to detect the launching of programs on Linux?

17,701

Solution 1

I don't know if there exists a better way, but you could periodically scan the /proc filesystem.

For example, /proc/<pid>/exe is a symlink to the process's executable.

On my systems (Ubuntu/RedHat), /proc/loadavg contains the number of running processes (the number after the forward slash) as well as the pid of the most recently started process. If your daemon polls the file, any change to either of the two numbers will tell it when it needs to re-scan /proc looking for new processes.

This is by no means bullet-proof, but is the most suitable mechanism I can think of.

Solution 2

For Linux, there appears to be an interface in the kernel. Whilst researching this problem I came across people using CONFIG_CONNECTOR and CONFIG_PROC_EVENTS kernel configuration to get events on process death.

Some more google and I found this:

http://netsplit.com/2011/02/09/the-proc-connector-and-socket-filters/

The Proc Connector and Socket Filters Posted on February 9, 2011 by scott

The proc connector is one of those interesting kernel features that most people rarely come across, and even more rarely find documentation on. Likewise the socket filter. This is a shame, because they’re both really quite useful interfaces that might serve a variety of purposes if they were better documented.

The proc connector allows you to receive notification of process events such fork and exec calls, as well as changes to a process’s uid, gid or sid (session id). These are provided through a socket-based interface by reading instances of struct proc_event defined in the kernel header....

The header of interest is:

#include <linux/cn_proc.h>

I found example code here:

http://bewareofgeek.livejournal.com/2945.html

/* This file is licensed under the GPL v2 (http://www.gnu.org/licenses/gpl2.txt) (some parts was originally borrowed from proc events example)

pmon.c

code highlighted with GNU source-highlight 3.1
*/

#define _XOPEN_SOURCE 700
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/connector.h>
#include <linux/cn_proc.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/*
* connect to netlink
* returns netlink socket, or -1 on error
*/
static int nl_connect()
{
int rc;
int nl_sock;
struct sockaddr_nl sa_nl;

nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
if (nl_sock == -1) {
    perror("socket");
    return -1;
}

sa_nl.nl_family = AF_NETLINK;
sa_nl.nl_groups = CN_IDX_PROC;
sa_nl.nl_pid = getpid();

rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
if (rc == -1) {
    perror("bind");
    close(nl_sock);
    return -1;
}

return nl_sock;
}

/*
* subscribe on proc events (process notifications)
*/
static int set_proc_ev_listen(int nl_sock, bool enable)
{
int rc;
struct __attribute__ ((aligned(NLMSG_ALIGNTO))) {
    struct nlmsghdr nl_hdr;
    struct __attribute__ ((__packed__)) {
    struct cn_msg cn_msg;
    enum proc_cn_mcast_op cn_mcast;
    };
} nlcn_msg;

memset(&nlcn_msg, 0, sizeof(nlcn_msg));
nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg);
nlcn_msg.nl_hdr.nlmsg_pid = getpid();
nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE;

nlcn_msg.cn_msg.id.idx = CN_IDX_PROC;
nlcn_msg.cn_msg.id.val = CN_VAL_PROC;
nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op);

nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE;

rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
if (rc == -1) {
    perror("netlink send");
    return -1;
}

return 0;
}

/*
* handle a single process event
*/
static volatile bool need_exit = false;
static int handle_proc_ev(int nl_sock)
{
int rc;
struct __attribute__ ((aligned(NLMSG_ALIGNTO))) {
    struct nlmsghdr nl_hdr;
    struct __attribute__ ((__packed__)) {
    struct cn_msg cn_msg;
    struct proc_event proc_ev;
    };
} nlcn_msg;

while (!need_exit) {
    rc = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
    if (rc == 0) {
    /* shutdown? */
    return 0;
    } else if (rc == -1) {
    if (errno == EINTR) continue;
    perror("netlink recv");
    return -1;
    }
    switch (nlcn_msg.proc_ev.what) {
    case PROC_EVENT_NONE:
        printf("set mcast listen ok\n");
        break;
    case PROC_EVENT_FORK:
        printf("fork: parent tid=%d pid=%d -> child tid=%d pid=%d\n",
            nlcn_msg.proc_ev.event_data.fork.parent_pid,
            nlcn_msg.proc_ev.event_data.fork.parent_tgid,
            nlcn_msg.proc_ev.event_data.fork.child_pid,
            nlcn_msg.proc_ev.event_data.fork.child_tgid);
        break;
    case PROC_EVENT_EXEC:
        printf("exec: tid=%d pid=%d\n",
            nlcn_msg.proc_ev.event_data.exec.process_pid,
            nlcn_msg.proc_ev.event_data.exec.process_tgid);
        break;
    case PROC_EVENT_UID:
        printf("uid change: tid=%d pid=%d from %d to %d\n",
            nlcn_msg.proc_ev.event_data.id.process_pid,
            nlcn_msg.proc_ev.event_data.id.process_tgid,
            nlcn_msg.proc_ev.event_data.id.r.ruid,
            nlcn_msg.proc_ev.event_data.id.e.euid);
        break;
    case PROC_EVENT_GID:
        printf("gid change: tid=%d pid=%d from %d to %d\n",
            nlcn_msg.proc_ev.event_data.id.process_pid,
            nlcn_msg.proc_ev.event_data.id.process_tgid,
            nlcn_msg.proc_ev.event_data.id.r.rgid,
            nlcn_msg.proc_ev.event_data.id.e.egid);
        break;
    case PROC_EVENT_EXIT:
        printf("exit: tid=%d pid=%d exit_code=%d\n",
            nlcn_msg.proc_ev.event_data.exit.process_pid,
            nlcn_msg.proc_ev.event_data.exit.process_tgid,
            nlcn_msg.proc_ev.event_data.exit.exit_code);
        break;
    default:
        printf("unhandled proc event\n");
        break;
    }
}

return 0;
}

static void on_sigint(int unused)
{
need_exit = true;
}

int main(int argc, const char *argv[])
{
int nl_sock;
int rc = EXIT_SUCCESS;

signal(SIGINT, &on_sigint);
siginterrupt(SIGINT, true);

nl_sock = nl_connect();
if (nl_sock == -1)
    exit(EXIT_FAILURE);

rc = set_proc_ev_listen(nl_sock, true);
if (rc == -1) {
    rc = EXIT_FAILURE;
    goto out;
}

rc = handle_proc_ev(nl_sock);
if (rc == -1) {
    rc = EXIT_FAILURE;
    goto out;
}

    set_proc_ev_listen(nl_sock, false);

out:
close(nl_sock);
exit(rc);
}

I found that this code needs to run as root to get the notifications.

Solution 3

I was interested in trying to figure out how to do this without polling. inotify() does not seem to work on /proc, so that idea is out.

However, any program which is dynamically linked is going to access certain files on startup, such as the dynamic linker. This would be useless for security purposes since it won't trigger on a statically linked program, but might still be of interest:

#include <stdio.h>
#include <sys/inotify.h>
#include <assert.h>
int main(int argc, char **argv) {
    char buf[256];
    struct inotify_event *event;
    int fd, wd;
    fd=inotify_init();
    assert(fd > -1);
    assert((wd=inotify_add_watch(fd, "/lib/ld-linux.so.2", IN_OPEN)) > 0);
    printf("Watching for events, wd is %x\n", wd);
    while (read(fd, buf, sizeof(buf))) {
      event = (void *) buf;
      printf("watch %d mask %x name(len %d)=\"%s\"\n",
         event->wd, event->mask, event->len, event->name);
    }
    inotify_rm_watch(fd, wd);
    return 0;
}

The events this prints out don't contain any interesting information - the pid of the triggering process doesn't seem to be provided by inotify. However it could be used to wake up and trigger a rescan of /proc

Also be aware that short-lived programs might vanish again before this thing wakes up and finishes scanning /proc - presumably you would learn that they had existed, but not be able to learn what they were. And of course anybody could just keep opening and closing an fd to the dyanmic linker to drown you in noise.

Solution 4

Use forkstat, it's the most complete client for proc events:

sudo forkstat -e exec,comm,core

Packaged in Ubuntu, Debian and the AUR.


Before that, there was cn_proc:

 bzr branch lp:~kees/+junk/cn_proc

The Makefile needs a small change (LDLIBS instead of LDFLAGS).

cn_proc and exec-notify.c (which Arnaud posted) share a common ancestor; cn_proc handles a few more events and has cleaner output, but isn't resilient when processes exit quickly.


Ooh, found another fork of exec-notify, extrace. This one indents child processes below their parent (using a pid_depth heuristic).

Solution 5

Have a look at this little program by Sebastian Krahmer it does exactly what you are asking in a resource efficient way and pretty simple code.

It does require that your kernel has CONFIG_PROC_EVENTS enabled, which is not the case on the latest Amazon Linux Image (2012.09) for example.

UPDATE: Following a request to Amazon the Amazon Linux Image kernels now support PROC_EVENTS

Share:
17,701

Related videos on Youtube

nub
Author by

nub

Updated on July 09, 2020

Comments

  • nub
    nub almost 4 years

    I wrote a simple daemon. This daemon should respond when I run any program. How to do this? In a big daemon loop:

    while(1)
    {
       /* function which catches new programm running */
    }
    

    What functions to call in linux, when i'm running a new program (create new process)?

    • Adam Batkin
      Adam Batkin almost 13 years
      I think we are going to need a little more information
    • Chris Stratton
      Chris Stratton almost 13 years
      @Adam Batkin - I disagree, the question is sufficiently stated, the poster wants to be informed upon process creation
    • Cristian Ciupitu
      Cristian Ciupitu almost 13 years
      The audit daemon can log the programs that are run by tracing the execve syscall. Maybe you can inspire from it.
  • FrankH.
    FrankH. over 10 years
    Splendid one - helps to create the solution for superuser.com/questions/354150/… ;-)
  • notbad.jpeg
    notbad.jpeg over 8 years
    This is beautiful, was starting to think I was going to have to dive into the kernel code...
  • h0r53
    h0r53 almost 6 years
    Note that you can use the pid of spawned processes to read additional process information from the /proc/<pid> directory, if desired. I used this to extract command line information to also display the name of a process on creation.
  • voxoid
    voxoid almost 4 years
    "this little program" link is broken. Update to this?: gist.github.com/iNarcissuss/4809c725eb04b070f439d9c6df28cdd7