检测Linux平台上的程序启动

我写了一个简单的守护进程。 当我运行任何程序时,这个守护进程应该会响应。 这个怎么做? 在一个大的守护进程中:

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

什么函数在Linux中调用,当我运行一个新的程序(创build新的进程)?

Solutions Collecting From Web of "检测Linux平台上的程序启动"

我不知道是否存在更好的方法,但可以定期扫描/proc文件系统。

例如, /proc/<pid>/exe是进程可执行文件的符号链接。

在我的系统(Ubuntu / RedHat)上, /proc/loadavg包含正在运行的进程数(正斜杠后面的数字)以及最近启动的进程的PID。 如果你的守护进程轮询文件,任何两个数字的变化都会告诉它何时需要重新扫描/proc寻找新的进程。

这绝非防弹,而是我能想到的最适合的机制。

对于Linux,在内核中似乎有一个接口。 在研究这个问题的同时,我遇到了使用CONFIG_CONNECTOR和CONFIG_PROC_EVENTS内核配置的人来获取进程死亡事件。

更多谷歌,我发现这一点:

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

Proc连接器和插座滤波器发表于2011年2月9日,由scott

proc连接器是大多数人很少遇到的那些有趣的内核特性之一,甚至更少发现文档。 同样的插座过滤器。 这是一个耻辱,因为它们都是非常有用的接口,如果它们被更好的记录,它们可以用于各种目的。

proc连接器允许您接收进程事件(如fork和exec调用)的通知,以及对进程的uid,gid或sid(会话id)的更改。 这些通过读取内核头文件中定义的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 */ #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的重新扫描

另外请注意,在这个东西醒来之前,短命的程序可能会再次消失,并且完成扫描/ proc – 大概你会知道它们已经存在,但是不能够知道它们是什么。 当然,任何人都可以继续打开和关闭一个fd到dyanmic连接器来淹没你的声音。

看一下Sebastian Krahmer的这个小程序 ,它就是以资源有效的方式和非常简单的代码来完成的。

它确实要求您的内核启用了CONFIG_PROC_EVENTS,而最新的Amazon Linux Image(2012.09)则不是这样。

更新:在向Amazon发出请求之后,Amazon Linux Image内核现在支持PROC_EVENTS

使用forkstat ,它是proc事件的最完整的客户端:

 sudo forkstat -e exec,comm,core 
  • GitWeb: http ://kernel.ubuntu.com/git?p=cking/forkstat.git
  • 公告: http : //smackerelofopinion.blogspot.com/2014/03/forkstat-new-tool-to-trace-process.html

打包在Ubuntu,Debian和AUR。


在此之前,有cn_proc :

  bzr branch lp:~kees/+junk/cn_proc 

生成文件需要一个小的改变( LDLIBS而不是LDFLAGS )。

cn_proc和exec-notify.c (Arnaud发布的)共享一个共同的祖先; cn_proc可以处理更多的事件,并且输出更清晰,但是当进程退出的时候不会有弹性。


哦,找到另一个执行通知分叉, extrace 。 这一个在他们父母的下面压缩子进程(使用pid_depth启发式)。

您选择的搜索机器的关键字是“进程事件连接器”。

我发现了两个利用它们的工具, exec-notify和cn_proc 。

我更喜欢后者,但都做得很好。

您既可以扫描操作系统以查找符合条件的程序,也可以等待程序向守护程序报告。 您选择哪种技术在很大程度上取决于您对非守护程序的控制程度。

扫描可以通过内核系统调用完成,也可以通过读取用户空间公布的内核详细信息来完成(与/ proc文件系统一样)。 请注意,扫描并不能保证您可以找到任何特定的程序,就像程序设法在扫描周期之间启动和终止一样,它也不会被检测到。

过程检测的更复杂的技术是可能的,但是它们也需要更复杂的实现。 因为你所做的每一件事情都不是独立于你正在监控的系统,所以在你开始寻求异域的解决方案(插入内核驱动程序等)之前,知道真正需要什么是非常重要的。 你实际上是通过观察环境来改变环境的,一些观察环境的方法可能会不恰当的改变环境。