Как обнаружить запуск программ в Linux?

Я написал простой демон. Этот демон должен отвечать, когда я запускаю любую программу. Как это сделать? В большом цикле демона:

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

Какие функции вызывать в Linux, когда я запускаю новую программу (создать новый процесс)?

8 ответов

Решение

Я не знаю, существует ли лучший способ, но вы могли бы периодически сканировать /proc файловая система.

Например, /proc/<pid>/exe символическая ссылка на исполняемый файл процесса

В моих системах (Ubuntu/RedHat), /proc/loadavg содержит количество запущенных процессов (число после косой черты), а также pid самого последнего запущенного процесса. Если ваш демон опрашивает файл, любое изменение любого из этих двух чисел сообщит ему, когда ему необходимо выполнить повторное сканирование. /proc ищу новые процессы.

Это ни в коем случае не пуленепробиваемый, но самый подходящий механизм, который я могу придумать.

Для Linux в ядре есть интерфейс. Во время исследования этой проблемы я сталкивался с людьми, использующими конфигурацию ядра CONFIG_CONNECTOR и CONFIG_PROC_EVENTS для получения событий о смерти процесса.

Еще немного Google, и я нашел это:

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

Разъем Proc и Socket Filters Опубликовано 9 февраля 2011 по Скотт

Разъем proc - одна из тех интересных функций ядра, с которыми редко сталкиваются большинство людей, и еще реже находят документацию. Аналогично сокетный фильтр. Это позор, потому что они оба действительно весьма полезные интерфейсы, которые могли бы служить различным целям, если бы они были лучше задокументированы.

Соединитель proc позволяет получать уведомления о событиях процесса, таких как вызовы fork и exec, а также об изменениях uid, gid или sid процесса (идентификатор сеанса). Они предоставляются через интерфейс на основе сокетов, читая экземпляры struct proc_event, определенные в заголовке ядра....

Интересующий заголовок:

#include <linux/cn_proc.h>

Я нашел пример кода здесь:

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);
}

Я обнаружил, что этот код должен запускаться от имени root для получения уведомлений.

Я был заинтересован в попытке выяснить, как сделать это без голосования. Похоже, что inotify() не работает с /proc, так что идея отсутствует.

Однако любая программа, которая динамически связана, при запуске будет получать доступ к определенным файлам, таким как динамический компоновщик. Это было бы бесполезно в целях безопасности, так как оно не будет запускаться в статически связанной программе, но все же может представлять интерес:

#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;
}

События, которые это выводит на печать, не содержат никакой интересной информации - pid процесса запуска, кажется, не предоставлен inotify. Однако его можно использовать для пробуждения и запуска повторного сканирования / proc

Также имейте в виду, что недолговечные программы могут снова исчезнуть до того, как эта штука проснется и закончит сканирование / процесс - возможно, вы узнаете, что они существуют, но не сможете узнать, чем они были. И, конечно, кто угодно может просто открывать и закрывать fd для dyanmic линкера, чтобы утопить вас в шуме.

Использование forkstat это самый полный клиент для событий proc:

sudo forkstat -e exec,comm,core

Упаковано в Ubuntu, Debian и AUR.


До этого был cn_proc:

 bzr branch lp:~kees/+junk/cn_proc

Makefile нуждается в небольшом изменении (LDLIBS вместо LDFLAGS).

cn_proc и exec-notify.c (которые опубликовал Арно) имеют общего предка; cn_proc обрабатывает еще несколько событий и имеет более чистый вывод, но не устойчив, когда процессы быстро завершаются.


Ой, нашел еще одну развилку exec- notify, extrace. Этот отступ делает дочерний процесс ниже своего родителя (используя эвристику pid_depth).

Посмотрите на эту маленькую программу Себастьяна Крахмера, она делает именно то, что вы просите, эффективным способом и довольно простым кодом.

Требуется, чтобы в вашем ядре была включена функция CONFIG_PROC_EVENTS, чего нет, например, в последнем образе Amazon Linux (2012.09).

ОБНОВЛЕНИЕ: После запроса к Amazon ядра Amazon Linux Image теперь поддерживают PROC_EVENTS

Ключевое слово для поисковой машины по вашему выбору - "соединитель событий процесса".

Я нашел два инструмента, которые их используют, exec- notify и cn_proc.

Мне больше нравится позже, но оба хорошо выполняют свою работу.

CONFIG_FTRACE а также CONFIG_KPROBES через brendangregg/perf-tools

git clone https://github.com/brendangregg/perf-tools.git
cd perf-tools
git checkout 98d42a2a1493d2d1c651a5c396e015d4f082eb20
sudo ./execsnoop

На другой оболочке:

while true; do sleep 1; date; done

Первая оболочка показывает данные формата:

Tracing exec()s. Ctrl-C to end.                                                        
Instrumenting sys_execve                                                               
   PID   PPID ARGS 
 20109   4336 date                                                                                       
 20110   4336 sleep 1                                                                                    
 20111   4336 date                                                                                                                                                                                                 
 20112   4336 sleep 1                                                                                    
 20113   4336 date                                                                                       
 20114   4336 sleep 1                                                                                    
 20115   4336 date                                                                                       
 20116   4336 sleep 1

Вы можете либо сканировать операционную систему на наличие программ, соответствующих вашему критерию, либо подождать, пока программа сообщит о себе своему демону. Какой метод вы выберете, будет сильно зависеть от того, насколько вы контролируете свои программы, не являющиеся демонами.

Сканирование может быть выполнено с помощью системного вызова ядра или путем чтения сведений о ядре, объявленных в пространстве пользователя (как в файловой системе /proc). Обратите внимание, что сканирование не является гарантией того, что вы найдете какую-либо конкретную программу, так как, если программа удастся запустить и завершить между циклами сканирования, она вообще не будет обнаружена.

Возможны более сложные методы обнаружения процессов, но они также требуют более сложных реализаций. Важно знать, что действительно необходимо, прежде чем вы начнете искать экзотические решения (вставка драйверов ядра и т. Д.), Поскольку все, что вы делаете, не зависит от системы, которую вы отслеживаете; вы фактически изменяете окружающую среду, наблюдая за ней, и некоторые методы наблюдения за окружающей средой могут изменить ее ненадлежащим образом.

Другие вопросы по тегам