Как обнаружить запуск программ в Linux? - PullRequest
24 голосов
/ 20 мая 2011

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

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

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

Ответы [ 8 ]

63 голосов
/ 24 ноября 2011

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

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

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

Фильтры разъема и разъема Proc Опубликовано 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 для получения уведомлений.

19 голосов
/ 20 мая 2011

Мне было интересно выяснить, как это сделать без опроса. Похоже, 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 линкера, чтобы утопить вас в шуме.

10 голосов
/ 12 сентября 2012

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

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

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

9 голосов
/ 19 декабря 2014

Используйте 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).

3 голосов
/ 04 января 2015

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

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

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

1 голос
/ 20 мая 2011

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

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

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

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

0 голосов

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
0 голосов
/ 20 мая 2011

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

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

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

...