2013-06-23 9 views
19

Czy istnieje biblioteka, która ma jakąś funkcję, która pozwala monitorować zewnętrzny proces zdarzeń za pomocą jej pid_t? Mam na myśli monitorowanie, czy zewnętrzny proces zakończył się, czy utworzył jedno lub więcej procesów podrzędnych (z fork), czy stał się innym obrazem wykonywalnym (przez wywołanie funkcji rodziny exec lub posix_spawn) lub czy sygnał Unix był dostarczone do niego.Jak monitorować zewnętrzny proces zdarzeń za pomocą jego PID w C?

EDIT

muszę coś, co nie koliduje z realizacją programu, który jest monitorowany. Tak więc nie powinienem używać ptrace, ponieważ zatrzymuje on proces, który jest monitorowany, kiedy emituje jakiś sygnał i konieczne jest wznowienie procesu, kiedy to nastąpi.

+2

Masz na myśli coś takiego jak "strace" (http: //linux.die.net/man/1/strace)? Następnie sprawdź wywołanie systemowe ['ptrace'] (http://linux.die.net/man/2/ptrace). –

+0

@JoachimPileborg Tak, potrzebuję czegoś takiego jak 'strace'. Sprawdzę 'ptrace'. Dzięki. – LuisABOL

+0

@JoachimPileborg 'ptrace' i' waitpid' wydają się działać dobrze. Jednak przypadki 'fork' i' exec' wydają się niemożliwe, ponieważ nie ma żadnych odpowiadających im sygnałów dla tych funkcji, czy istnieje? Czy proces wysyła dowolny sygnał, gdy wywołuje 'fork' lub' exec'? – LuisABOL

Odpowiedz

7

Jeśli można uruchomić jako root, a następnie można użyć NETLINK wydarzenia interfejs proc:

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

Właśnie opracowano go równo na Fedora 17 x86_64 i daje mi to:

[[email protected] yotest]# ./proc 
set mcast listen ok 
fork: parent tid=2358 pid=2358 -> child tid=21007 pid=21007 
exec: tid=21007 pid=21007 
fork: parent tid=21007 pid=21007 -> child tid=21008 pid=21008 
fork: parent tid=21007 pid=21007 -> child tid=21009 pid=21009 
fork: parent tid=21007 pid=21007 -> child tid=21010 pid=21010 
fork: parent tid=21007 pid=21007 -> child tid=21011 pid=21011 
exec: tid=21010 pid=21010 
exec: tid=21008 pid=21008 
exec: tid=21011 pid=21011 
exec: tid=21009 pid=21009 
exit: tid=21008 pid=21008 exit_code=0 
fork: parent tid=21010 pid=21010 -> child tid=21012 pid=21012 
exit: tid=21009 pid=21009 exit_code=0 
exec: tid=21012 pid=21012 
exit: tid=21012 pid=21012 exit_code=0 
exit: tid=21010 pid=21010 exit_code=0 
exit: tid=21011 pid=21011 exit_code=0 
exit: tid=21007 pid=21007 exit_code=0 

Będziesz musiał odfiltrować określone typy pidów, które Cię interesują, ale możesz to łatwo zrobić w instrukcji switch na linii 107.

Dla celów ochrony:

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

(gcc -o proc proc.c)

I trochę informacji na netlink:

fragment: http://www.linuxjournal.com/article/7356

Netlink jest asynchroniczny, ponieważ tak jak w przypadku każdego innego API gniazda, zapewnia kolejkę gniazd dla wygładzenia serii wiadomości. System wzywający do wysłania komunikatu netlink umieszcza kolejkę wiadomości w kolejce netlink odbiornika, a następnie wywołuje procedurę odbioru odbiorcy. Odbiornik, w kontekście kontekstu obsługi odbioru, może zdecydować, czy natychmiast przetworzyć wiadomość, czy pozostawić wiadomość w kolejce i przetworzyć ją później w innym kontekście. W przeciwieństwie do netlink, wywołania systemowe wymagają synchronicznego przetwarzania. Dlatego jeśli użyjemy wywołania systemowego do przekazania wiadomości z przestrzeni użytkownika do jądra, może to wpłynąć na ziarnistość harmonogramu jądra, jeśli czas przetwarzania tego komunikatu jest długi.

Jest także to interesujące ogłoszenie, które ostatnio zostało opublikowane przez Nltrace. http://lists.infradead.org/pipermail/libnl/2013-April/000993.html

+0

Dziękuję bardzo za odpowiedź. Właściwie wcześniej próbowałem 'netlink'. Ale odkąd buduję rodzaj biblioteki, nie mogę założyć, że aplikacje będą działać jako root. A jeśli spróbujesz uruchomić powyższy kod jako zwykły użytkownik, funkcja 'bind' nie działa. Niestety nie mogę użyć 'netlink'. – LuisABOL

+0

Och, strzelaj, więc nie jestem pewien, czy istnieje uniwersalne rozwiązanie, ponieważ myślę, że musisz być albo procesem nadrzędnym, albo być w stanie zaprogramować procesy, aby cię powiadomić. Nie będąc rooterem, nie myślę nawet, że ładowanie bibliotek współdzielonych będzie działało, chociaż mógłbym się mylić, ale ta technika działałaby tylko dla programów, które i tak nie są statycznie skompilowane. – hoonto

+0

Możliwe, że będziesz w stanie instrumentować, również modyfikując istniejącą kompilację binarną, ale nie wiem, czy masz dostęp do pisania do tych, które są zainteresowane, a to jest ryzykowne, jeśli nie masz pełnej kontroli nad kierunek. – hoonto

16

Run cel binarny przy użyciu biblioteki napięcia wstępnego, że połowy fork().Dopóki wszystkie procesy potomne korzystają również z biblioteki wstępnego ładowania, wszystkie lokalne procesy podrzędne będą widoczne bez względu na sposób ich wykonania.

Oto przykładowa implementacja.

Najpierw plik nagłówkowy forkmonitor.h. Definiuje komunikaty przekazywane z biblioteki napięcia wstępnego, do procesu monitorowania:

#ifndef FORKMONITOR_H 
#define FORKMONITOR_H 

#define FORKMONITOR_ENVNAME "FORKMONITOR_SOCKET" 

#ifndef UNIX_PATH_MAX 
#define UNIX_PATH_MAX 108 
#endif 

#define TYPE_EXEC  1 /* When a binary is executed */ 
#define TYPE_DONE  2 /* exit() or return from main() */ 
#define TYPE_FORK  3 
#define TYPE_VFORK  4 
#define TYPE_EXIT  5 /* _exit() or _Exit() */ 
#define TYPE_ABORT  6 /* abort() */ 

struct message { 
    pid_t   pid;  /* Process ID */ 
    pid_t   ppid; /* Parent process ID */ 
    pid_t   sid;  /* Session ID */ 
    pid_t   pgid; /* Process group ID */ 
    uid_t   uid;  /* Real user ID */ 
    gid_t   gid;  /* Real group ID */ 
    uid_t   euid; /* Effective user ID */ 
    gid_t   egid; /* Effective group ID */ 
    unsigned short len;  /* Length of data[] */ 
    unsigned char type; /* One of the TYPE_ constants */ 
    char   data[0]; /* Optional payload, possibly longer */ 
}; 

#endif /* FORKMONITOR_H */ 

Zmienna FORKMONITOR_SOCKET środowisko (nazwany przez FORKMONITOR_ENVNAME makro powyżej) określa Unix datagramowy domeny gniazdo addess do procesu monitorowania. Jeśli nie jest zdefiniowany lub pusty, nie są wysyłane żadne wiadomości monitorowania.

Oto sama biblioteka, libforkmonitor.c. Należy zauważyć, że znacznie uprościłem kod, pomijając inicjalizację wielowątkową (ponieważ biblioteka rzadko wywołuje dowolne przechwycone funkcje, a jeszcze rzadziej robi to z wielu wątków). Lepiej byłoby użyć wbudowanych atomów (__sync_bool_compare_and_swap()), aby zaktualizować wskaźnik funkcji i getter atomowy (__sync_fetch_and_or (, 0)) w celu pobrania wskaźnika funkcji, aby uniknąć problemów z bibliotekami o nietypowych rozmiarach. (To jest zupełnie bezpieczna dla programów wielowątkowych, ponieważ wskaźniki zostaną zmienione tylko przed main() jest wykonywany.)

#define _POSIX_C_SOURCE 200809L 
#define _GNU_SOURCE 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/stat.h> 
#include <sys/un.h> 
#include <dlfcn.h> 
#include <limits.h> 
#include <string.h> 
#include <stdio.h> 
#include <errno.h> 
#include "forkmonitor.h" 

static pid_t (*actual_fork)(void) = NULL; 
static pid_t (*actual_vfork)(void) = NULL; 
static void (*actual_abort)(void) = NULL; 
static void (*actual__exit)(int) = NULL; 
static void (*actual__Exit)(int) = NULL; 
static int  commfd = -1; 

#define MINIMUM_COMMFD 31 

static void notify(const int type, struct message *const msg, const size_t extra) 
{ 
    const int saved_errno = errno; 

    msg->pid = getpid(); 
    msg->ppid = getppid(); 
    msg->sid = getsid(0); 
    msg->pgid = getpgrp(); 
    msg->uid = getuid(); 
    msg->gid = getgid(); 
    msg->euid = geteuid(); 
    msg->egid = getegid(); 
    msg->len = extra; 
    msg->type = type; 

    /* Since we don't have any method of dealing with send() errors 
    * or partial send()s, we just fire one off and hope for the best. */ 
    send(commfd, msg, sizeof (struct message) + extra, MSG_EOR | MSG_NOSIGNAL); 

    errno = saved_errno; 
} 

void libforkmonitor_init(void) __attribute__((constructor)); 
void libforkmonitor_init(void) 
{ 
    const int saved_errno = errno; 
    int  result; 

    /* Save the actual fork() call pointer. */ 
    if (!actual_fork) 
     *(void **)&actual_fork = dlsym(RTLD_NEXT, "fork"); 

    /* Save the actual vfork() call pointer. */ 
    if (!actual_vfork) 
     *(void **)&actual_vfork = dlsym(RTLD_NEXT, "vfork"); 

    /* Save the actual abort() call pointer. */ 
    if (!actual_abort) 
     *(void **)&actual_abort = dlsym(RTLD_NEXT, "abort"); 

    /* Save the actual _exit() call pointer. */ 
    if (!actual__exit) 
     *(void **)&actual__exit = dlsym(RTLD_NEXT, "_exit"); 
    if (!actual__exit) 
     *(void **)&actual__exit = dlsym(RTLD_NEXT, "_Exit"); 

    /* Save the actual abort() call pointer. */ 
    if (!actual__Exit) 
     *(void **)&actual__Exit = dlsym(RTLD_NEXT, "_Exit"); 
    if (!actual__Exit) 
     *(void **)&actual__Exit = dlsym(RTLD_NEXT, "_exit"); 

    /* Open an Unix domain datagram socket to the observer. */ 
    if (commfd == -1) { 
     const char *address; 

     /* Connect to where? */ 
     address = getenv(FORKMONITOR_ENVNAME); 
     if (address && *address) { 
      struct sockaddr_un addr; 

      memset(&addr, 0, sizeof addr); 
      addr.sun_family = AF_UNIX; 
      strncpy(addr.sun_path, address, sizeof addr.sun_path - 1); 

      /* Create and bind the socket. */ 
      commfd = socket(AF_UNIX, SOCK_DGRAM, 0); 
      if (commfd != -1) { 
       if (connect(commfd, (const struct sockaddr *)&addr, sizeof (addr)) == -1) { 
        /* Failed. Close the socket. */ 
        do { 
         result = close(commfd); 
        } while (result == -1 && errno == EINTR); 
        commfd = -1; 
       } 
      } 

      /* Move commfd to a high descriptor, to avoid complications. */ 
      if (commfd != -1 && commfd < MINIMUM_COMMFD) { 
       const int newfd = MINIMUM_COMMFD; 
       do { 
        result = dup2(commfd, newfd); 
       } while (result == -1 && errno == EINTR); 
       if (!result) { 
        do { 
         result = close(commfd); 
        } while (result == -1 && errno == EINTR); 
        commfd = newfd; 
       } 
      } 
     } 
    } 

    /* Send an init message, listing the executable path. */ 
    if (commfd != -1) { 
     size_t   len = 128; 
     struct message *msg = NULL; 

     while (1) { 
      ssize_t n; 

      free(msg); 
      msg = malloc(sizeof (struct message) + len); 
      if (!msg) { 
       len = 0; 
       break; 
      } 

      n = readlink("/proc/self/exe", msg->data, len); 
      if (n > (ssize_t)0 && (size_t)n < len) { 
       msg->data[n] = '\0'; 
       len = n + 1; 
       break; 
      } 

      len = (3 * len)/2; 
      if (len >= 65536U) { 
       free(msg); 
       msg = NULL; 
       len = 0; 
       break; 
      } 
     } 

     if (len > 0) { 
      /* INIT message with executable name */ 
      notify(TYPE_EXEC, msg, len); 
      free(msg); 
     } else { 
      /* INIT message without executable name */ 
      struct message msg2; 
      notify(TYPE_EXEC, &msg2, sizeof msg2); 
     } 
    } 

    /* Restore errno. */ 
    errno = saved_errno; 
} 

void libforkmonitor_done(void) __attribute__((destructor)); 
void libforkmonitor_done(void) 
{ 
    const int saved_errno = errno; 
    int  result; 

    /* Send an exit message, no data. */ 
    if (commfd != -1) { 
     struct message msg; 
     notify(TYPE_DONE, &msg, sizeof msg); 
    } 

    /* If commfd is open, close it. */ 
    if (commfd != -1) { 
     do { 
      result = close(commfd); 
     } while (result == -1 && errno == EINTR); 
    } 

    /* Restore errno. */ 
    errno = saved_errno; 
} 

/* 
* Hooked C library functions. 
*/ 

pid_t fork(void) 
{ 
    pid_t result; 

    if (!actual_fork) { 
     const int saved_errno = errno; 

     *(void **)&actual_fork = dlsym(RTLD_NEXT, "fork"); 
     if (!actual_fork) { 
      errno = EAGAIN; 
      return (pid_t)-1; 
     } 

     errno = saved_errno; 
    } 

    result = actual_fork(); 
    if (!result && commfd != -1) { 
     struct message msg; 
     notify(TYPE_FORK, &msg, sizeof msg); 
    } 

    return result; 
} 

pid_t vfork(void) 
{ 
    pid_t result; 

    if (!actual_vfork) { 
     const int saved_errno = errno; 

     *(void **)&actual_vfork = dlsym(RTLD_NEXT, "vfork"); 
     if (!actual_vfork) { 
      errno = EAGAIN; 
      return (pid_t)-1; 
     } 

     errno = saved_errno; 
    } 

    result = actual_vfork(); 
    if (!result && commfd != -1) { 
     struct message msg; 
     notify(TYPE_VFORK, &msg, sizeof msg); 
    } 

    return result; 
} 

void _exit(const int code) 
{ 
    if (!actual__exit) { 
     const int saved_errno = errno; 
     *(void **)&actual__exit = dlsym(RTLD_NEXT, "_exit"); 
     if (!actual__exit) 
      *(void **)&actual__exit = dlsym(RTLD_NEXT, "_Exit"); 
     errno = saved_errno; 
    } 

    if (commfd != -1) { 
     struct { 
      struct message msg; 
      int    extra; 
     } data; 

     memcpy(&data.msg.data[0], &code, sizeof code); 
     notify(TYPE_EXIT, &(data.msg), sizeof (struct message) + sizeof (int)); 
    } 

    if (actual__exit) 
     actual__exit(code); 

    exit(code); 
} 

void _Exit(const int code) 
{ 
    if (!actual__Exit) { 
     const int saved_errno = errno; 
     *(void **)&actual__Exit = dlsym(RTLD_NEXT, "_Exit"); 
     if (!actual__Exit) 
      *(void **)&actual__Exit = dlsym(RTLD_NEXT, "_exit"); 
     errno = saved_errno; 
    } 

    if (commfd != -1) { 
     struct { 
      struct message msg; 
      int    extra; 
     } data; 

     memcpy(&data.msg.data[0], &code, sizeof code); 
     notify(TYPE_EXIT, &(data.msg), sizeof (struct message) + sizeof (int)); 
    } 

    if (actual__Exit) 
     actual__Exit(code); 

    exit(code); 
} 

void abort(void) 
{ 
    if (!actual_abort) { 
     const int saved_errno = errno; 
     *(void **)&actual_abort = dlsym(RTLD_NEXT, "abort"); 
     errno = saved_errno; 
    } 

    if (commfd != -1) { 
     struct message msg; 
     notify(TYPE_ABORT, &msg, sizeof msg); 
    } 

    actual_abort(); 
    exit(127); 
} 

Funkcja libforkmonitor_init() jest wywoływana automatycznie przez łącznik wykonawczego przed procesem main() nazywa i libforkmonitor_done() jest wywoływane, gdy proces powraca z main() lub wywołuje exit().

Otwarcie gniazda datagramów domeny uniksowej do procesu monitorowania i przesłanie poświadczeń oraz ścieżki do bieżącego pliku wykonywalnego. Każdy proces potomny (tak długo, jak biblioteka wstępnego ładowania wciąż jest wczytywana), wykonuje to po załadowaniu, więc nie ma potrzeby, aby w ogóle korzystać z funkcji exec*() lub posix_spawn*() lub "popen()" itd.

Funkcje biblioteki C fork() i vfork() są przechwytywane. Te przechwycenia są potrzebne, aby uchwycić przypadki, w których oryginalny program prosi o tworzenie procesów niewolniczych bez wykonywania jakichkolwiek innych plików binarnych. (Przynajmniej GNU biblioteki C wykorzystuje fork() wewnątrz tak, aby te złapie popen(), posix_spawn(), itp.) Za

Dodatkowo, funkcje biblioteki C _exit(), _Exit() i abort() przechwytywane są za. Dodałem te, ponieważ niektóre pliki binarne, szczególnie Dash, lubią używać _exit(), i pomyślałem, że byłoby miło złapać wszystkie formy normalnych wyjść. (Śmierć z powodu sygnałów nie jest jednak wykrywana, a jeśli plik binarny wykonuje inny plik binarny, otrzymasz tylko nową wiadomość EXEC.) Zanotuj proces i identyfikator procesu nadrzędnego.)

Oto prosty program monitorujący, forkmonitor.c :

#define _POSIX_C_SOURCE 200809L 
#include <unistd.h> 
#include <stdlib.h> 
#include <sys/socket.h> 
#include <sys/un.h> 
#include <signal.h> 
#include <pwd.h> 
#include <grp.h> 
#include <string.h> 
#include <stdio.h> 
#include <errno.h> 
#include "forkmonitor.h" 

static volatile sig_atomic_t done = 0; 

static void done_handler(const int signum) 
{ 
    if (!done) 
     done = signum; 
} 

static int catch_done(const int signum) 
{ 
    struct sigaction act; 

    sigemptyset(&act.sa_mask); 
    act.sa_handler = done_handler; 
    act.sa_flags = 0; 

    if (sigaction(signum, &act, NULL) == -1) 
     return errno; 

    return 0; 
} 

static const char *username(const uid_t uid) 
{ 
    static char buffer[128]; 
    struct passwd *pw; 

    pw = getpwuid(uid); 
    if (!pw) 
     return NULL; 

    strncpy(buffer, pw->pw_name, sizeof buffer - 1); 
    buffer[sizeof buffer - 1] = '\0'; 

    return (const char *)buffer; 
} 

static const char *groupname(const gid_t gid) 
{ 
    static char buffer[128]; 
    struct group *gr; 

    gr = getgrgid(gid); 
    if (!gr) 
     return NULL; 

    strncpy(buffer, gr->gr_name, sizeof buffer - 1); 
    buffer[sizeof buffer - 1] = '\0'; 

    return (const char *)buffer; 
} 

int main(int argc, char *argv[]) 
{ 
    const size_t msglen = 65536; 
    struct message *msg; 
    int    socketfd, result; 
    const char  *user, *group; 

    if (catch_done(SIGINT) || catch_done(SIGQUIT) || catch_done(SIGHUP) || 
     catch_done(SIGTERM) || catch_done(SIGPIPE)) { 
     fprintf(stderr, "Cannot set signal handlers: %s.\n", strerror(errno)); 
     return 1; 
    } 

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { 
     fprintf(stderr, "\n"); 
     fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]); 
     fprintf(stderr, "  %s MONITOR-SOCKET-PATH\n", argv[0]); 
     fprintf(stderr, "\n"); 
     fprintf(stderr, "This program outputs events reported by libforkmonitor\n"); 
     fprintf(stderr, "to Unix domain datagram sockets at MONITOR-SOCKET-PATH.\n"); 
     fprintf(stderr, "\n"); 
     return 0; 
    } 

    msg = malloc(msglen); 
    if (!msg) { 
     fprintf(stderr, "Out of memory.\n"); 
     return 1; 
    } 

    socketfd = socket(AF_UNIX, SOCK_DGRAM, 0); 
    if (socketfd == -1) { 
     fprintf(stderr, "Cannot create an Unix domain datagram socket: %s.\n", strerror(errno)); 
     return 1; 
    } 

    { 
     struct sockaddr_un addr; 
     size_t    len; 

     if (argv[1]) 
      len = strlen(argv[1]); 
     else 
      len = 0; 
     if (len < 1 || len >= UNIX_PATH_MAX) { 
      fprintf(stderr, "%s: Path is too long (max. %d characters)\n", argv[1], UNIX_PATH_MAX - 1); 
      return 1; 
     } 

     memset(&addr, 0, sizeof addr); 
     addr.sun_family = AF_UNIX; 
     memcpy(addr.sun_path, argv[1], len + 1); /* Include '\0' at end */ 

     if (bind(socketfd, (struct sockaddr *)&addr, sizeof (addr)) == -1) { 
      fprintf(stderr, "Cannot bind to %s: %s.\n", argv[1], strerror(errno)); 
      return 1; 
     } 
    } 

    printf("Waiting for connections.\n"); 
    printf("\n"); 

    /* Infinite loop. */ 
    while (!done) { 
     ssize_t n; 

     n = recv(socketfd, msg, msglen, 0); 
     if (n == -1) { 
      const char *const errmsg = strerror(errno); 
      fprintf(stderr, "%s.\n", errmsg); 
      fflush(stderr); 
      break; 
     } 

     if (msglen < sizeof (struct message)) { 
      fprintf(stderr, "Received a partial message; discarded.\n"); 
      fflush(stderr); 
      continue; 
     } 

     switch (msg->type) { 
     case TYPE_EXEC: 
      printf("Received an EXEC message:\n"); 
      break; 
     case TYPE_DONE: 
      printf("Received a DONE message:\n"); 
      break; 
     case TYPE_FORK: 
      printf("Received a FORK message:\n"); 
      break; 
     case TYPE_VFORK: 
      printf("Received a VFORK message:\n"); 
      break; 
     case TYPE_EXIT: 
      printf("Received an EXIT message:\n"); 
      break; 
     case TYPE_ABORT: 
      printf("Received an ABORT message:\n"); 
      break; 
     default: 
      printf("Received an UNKNOWN message:\n"); 
      break; 
     } 

     if (msg->type == TYPE_EXEC && (size_t)n > sizeof (struct message)) { 
      if (*((char *)msg + n - 1) == '\0') 
       printf("\tExecutable:  '%s'\n", (char *)msg + sizeof (struct message)); 
     } 

     printf("\tProcess ID:   %d\n", (int)msg->pid); 
     printf("\tParent process ID: %d\n", (int)msg->ppid); 
     printf("\tSession ID:   %d\n", (int)msg->sid); 
     printf("\tProcess group ID: %d\n", (int)msg->pgid); 

     user = username(msg->uid); 
     if (user) 
      printf("\tReal user:   '%s' (%d)\n", user, (int)msg->uid); 
     else 
      printf("\tReal user:   %d\n", (int)msg->uid); 

     group = groupname(msg->gid); 
     if (group) 
      printf("\tReal group:  '%s' (%d)\n", group, (int)msg->gid); 
     else 
      printf("\tReal group:   %d\n", (int)msg->gid); 

     user = username(msg->euid); 
     if (user) 
      printf("\tEffective user: '%s' (%d)\n", user, (int)msg->euid); 
     else 
      printf("\tEffective user:  %d\n", (int)msg->euid); 

     group = groupname(msg->egid); 
     if (group) 
      printf("\tEffective group: '%s' (%d)\n", group, (int)msg->egid); 
     else 
      printf("\tEffective group: %d\n", (int)msg->egid); 

     printf("\n"); 
     fflush(stdout); 
    } 

    do { 
     result = close(socketfd); 
    } while (result == -1 && errno == EINTR); 

    unlink(argv[1]); 

    return 0; 
} 

Zajmuje jeden parametr wiersza polecenia, adres gniazda domeny uniksowej. Powinna to być absolutna ścieżka do systemu plików.

można zatrzymać program monitorowania poprzez INT (Ctrl + C), HUP, QUIT i TERM sygnałów.

Kompilacja biblioteki korzystając

gcc -W -Wall -O3 -fpic -fPIC -c libforkmonitor.c 
gcc -shared -Wl,-soname,libforkmonitor.so libforkmonitor.o -ldl -o libforkmonitor.so 

oraz program monitora przy użyciu

gcc -W -Wall -O3 forkmonitor.c -o forkmonitor 

w jednym oknie terminala uruchomić forkmonitor pierwszy:

./forkmonitor "$PWD/commsocket" 

W innym oknie terminala, w w tym samym katalogu, uruchom monitorowane polecenie, automatycznie wstępnie ładując libforkmonitor.so biblioteka i określając gniazdo do monitora:

env "LD_PRELOAD=$PWD/libforkmonitor.so" "FORKMONITOR_SOCKET=$PWD/commsocket" command args... 

Zauważ, że ten wykorzystuje zmienne LD_PRELOAD i FORKMONITOR_SOCKET środowiskowe, procesy potomne są ignorowane, gdy ich rodzic modyfikuje środowisko (usuwanie dwie zmienne środowiskowe), a podczas wykonywania Pliki binarne: setuid lub setgid. Ograniczenia tego można uniknąć poprzez wyeliminowanie zmiennych środowiskowych i ich kodowanie.

Bieg czasu łącznik nie będzie wstępnego ładowania bibliotek dla setuid lub setgid plików binarnych, chyba że biblioteka jest w jednym ze standardowych katalogów bibliotecznych, a także oznakowane setgid.

Dodanie nazwy biblioteki do /etc/ld.so.preload będzie wczytywać bibliotekę dla wszystkich plików binarnych, ale prawdopodobnie należy dodać mechanizm do libforkmonitor_init() który ogranicza kontrolę do żądanych plików binarnych i/lub określonej prawdziwego użytkownika (jak zmienia skuteczny użytkownik podczas uruchamiania setuid dwójkowy).

Na przykład, gdy biegnę

env "LD_PRELOAD=$PWD/libforkmonitor.so" "FORKMONITOR_SOCKET=$PWD/commsocket" sh -c 'date ; ls -laF' 

wyjście monitoringu (anonimowego):

Received an EXEC message: 
Executable:  'bin/dash' 
Process ID:   11403 
Parent process ID: 9265 
Session ID:   9265 
Process group ID: 11403 
Real user:   'username' (1000) 
Real group:  'username' (1000) 
Effective user: 'username' (1000) 
Effective group: 'username' (1000) 

Received a FORK message: 
Process ID:   11404 
Parent process ID: 11403 
Session ID:   9265 
Process group ID: 11403 
Real user:   'username' (1000) 
Real group:  'username' (1000) 
Effective user: 'username' (1000) 
Effective group: 'username' (1000) 

Received an EXEC message: 
Executable:  'bin/date' 
Process ID:   11404 
Parent process ID: 11403 
Session ID:   9265 
Process group ID: 11403 
Real user:   'username' (1000) 
Real group:  'username' (1000) 
Effective user: 'username' (1000) 
Effective group: 'username' (1000) 

Received a DONE message: 
Process ID:   11404 
Parent process ID: 11403 
Session ID:   9265 
Process group ID: 11403 
Real user:   'username' (1000) 
Real group:  'username' (1000) 
Effective user: 'username' (1000) 
Effective group: 'username' (1000) 

Received a FORK message: 
Process ID:   11405 
Parent process ID: 11403 
Session ID:   9265 
Process group ID: 11403 
Real user:   'username' (1000) 
Real group:  'username' (1000) 
Effective user: 'username' (1000) 
Effective group: 'username' (1000) 

Received an EXEC message: 
Executable:  'bin/ls' 
Process ID:   11405 
Parent process ID: 11403 
Session ID:   9265 
Process group ID: 11403 
Real user:   'username' (1000) 
Real group:  'username' (1000) 
Effective user: 'username' (1000) 
Effective group: 'username' (1000) 

Received a DONE message: 
Process ID:   11405 
Parent process ID: 11403 
Session ID:   9265 
Process group ID: 11403 
Real user:   'username' (1000) 
Real group:  'username' (1000) 
Effective user: 'username' (1000) 
Effective group: 'username' (1000) 

Received an EXIT message: 
Process ID:   11403 
Parent process ID: 9265 
Session ID:   9265 
Process group ID: 11403 
Real user:   'username' (1000) 
Real group:  'username' (1000) 
Effective user: 'username' (1000) 
Effective group: 'username' (1000) 

Jest to bardzo lekkie rozwiązanie do monitorowania drzewo proces. Inne niż uruchamianie, zamykanie i wywoływanie jednej z przechwyconych funkcji (fork(), vfork(), , , _Exit(),) nie wpłynie w żaden sposób na wykonanie programu. Ponieważ biblioteka jest tak lekka, nawet osoby nią dotknięte będą dotknięte tylko bardzo, bardzo małą ilością; prawdopodobnie nie wystarczy, aby zmierzyć w sposób wiarygodny.

Oczywiście można przechwytywać inne funkcje i/lub korzystać z komunikacji dwukierunkowej, "wstrzymując" wykonywanie przechwyconej funkcji, dopóki aplikacja monitorująca nie zareaguje.

Istnieje kilka ogólnych pułapek, szczególnie związanych z procesami setuid/setgid i procesami, które generują nowe środowisko (pomijając zmienne środowiskowe LD_PRELOAD i FORKMONITOR_SOCKET), ale można je obejść, jeśli uprawnienia administratora są dostępne.

Mam nadzieję, że znajdziesz to pouczające. Pytania?

+0

Dzięki za odpowiedź. To właściwie całkiem dobra odpowiedź. Jednak to rozwiązanie nie pasuje do moich potrzeb. Potrzebuję sposobu, aby normalny proces użytkownika mógł monitorować każdy inny proces systemowy, nie tylko jego dzieci, dla wywołań 'fork',' exec', 'exit' lub dowolnego odbioru sygnału Unix. Byłoby dobrze, gdyby Linux dostarczył wywołanie systemowe, które śledzi wszystkie zdarzenia związane z procesem, takie jak FreeBSD 'kevent' syscall. Myślę więc, że nie da się osiągnąć tego, co chcę w Linuksie, chyba że stworzę własne wywołanie systemowe. Tak więc, w każdym razie, dziękuję bardzo za wszystko, co w mojej mocy, aby mi pomóc, ale mogę co najwyżej głosować na twoją odpowiedź :( – LuisABOL

+1

@ LuisAntonioBotelhoO.Leite: Po pierwsze, monitoruje każdy proces, który ładuje bibliotekę. czy są procesami podrzędnymi, czy nie, może monitorować odbiór sygnału, jeśli włączysz podsłuchy 'signal()' i 'sigaction()' itp, aby użyć własnego pre-handlera, który informuje monitor. (Mogę pokazać, jak to zrobić jeśli chcesz.) –

+2

@ LuisAntonioBotelhoO.Leite: Wynajmowanie * dowolnego * monitora procesu użytkownika innego procesu jest zagrożeniem bezpieczeństwa Tylko dlatego, że pozwala na to jakiś system operacyjny, nie jest to dobry pomysł, Linux wymaga uprawnień root'a, aby to zrobić. W twoim przypadku myślę, że najlepszą opcją byłby program setuid filtrujący netlink: zainstalowany jako root-set, każdy użytkownik mógłby go uruchomić, ale raportowałby tylko informacje o procesach należących do tego samego użytkownika lub grupy (w tym grupy użytkownika jest członkiem). –

0

Użyj komendy systemowej "pidof" z biblioteki procps. Bardzo prosty i łatwy w użyciu. Jeśli zwraca coś, proces jest uruchomiony lub odwrotnie.

Powiązane problemy