[PATCH] Process Events Connector
Matt Helsley [Mon, 7 Nov 2005 08:59:16 +0000 (00:59 -0800)]
This patch adds a connector that reports fork, exec, id change, and exit
events for all processes to userspace.  It replaces the fork_advisor patch
that ELSA is currently using.  Applications that may find these events
useful include accounting/auditing (e.g.  ELSA), system activity monitoring
(e.g.  top), security, and resource management (e.g.  CKRM).

Signed-off-by: Matt Helsley <matthltc@us.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>

drivers/connector/Kconfig
drivers/connector/Makefile
drivers/connector/cn_proc.c [new file with mode: 0644]
fs/exec.c
include/linux/cn_proc.h [new file with mode: 0644]
include/linux/connector.h
kernel/exit.c
kernel/fork.c
kernel/sys.c

index 0bc2059..e0bdc0d 100644 (file)
@@ -10,4 +10,12 @@ config CONNECTOR
          Connector support can also be built as a module.  If so, the module
          will be called cn.ko.
 
+config PROC_EVENTS
+       boolean "Report process events to userspace"
+       depends on CONNECTOR=y
+       default y
+       ---help---
+         Provide a connector that reports process events to userspace. Send
+         events such as fork, exec, id change (uid, gid, suid, etc), and exit.
+
 endmenu
index 12ca79e..1f255e4 100644 (file)
@@ -1,3 +1,4 @@
 obj-$(CONFIG_CONNECTOR)                += cn.o
+obj-$(CONFIG_PROC_EVENTS)      += cn_proc.o
 
 cn-y                           += cn_queue.o connector.o
diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
new file mode 100644 (file)
index 0000000..fcdf0ff
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * cn_proc.c - process events connector
+ *
+ * Copyright (C) Matt Helsley, IBM Corp. 2005
+ * Based on cn_fork.c by Guillaume Thouvenin <guillaume.thouvenin@bull.net>
+ * Original copyright notice follows:
+ * Copyright (C) 2005 BULL SA.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/atomic.h>
+
+#include <linux/cn_proc.h>
+
+#define CN_PROC_MSG_SIZE (sizeof(struct cn_msg) + sizeof(struct proc_event))
+
+static atomic_t proc_event_num_listeners = ATOMIC_INIT(0);
+static struct cb_id cn_proc_event_id = { CN_IDX_PROC, CN_VAL_PROC };
+
+/* proc_counts is used as the sequence number of the netlink message */
+static DEFINE_PER_CPU(__u32, proc_event_counts) = { 0 };
+
+static inline void get_seq(__u32 *ts, int *cpu)
+{
+       *ts = get_cpu_var(proc_event_counts)++;
+       *cpu = smp_processor_id();
+       put_cpu_var(proc_counts);
+}
+
+void proc_fork_connector(struct task_struct *task)
+{
+       struct cn_msg *msg;
+       struct proc_event *ev;
+       __u8 buffer[CN_PROC_MSG_SIZE];
+
+       if (atomic_read(&proc_event_num_listeners) < 1)
+               return;
+
+       msg = (struct cn_msg*)buffer;
+       ev = (struct proc_event*)msg->data;
+       get_seq(&msg->seq, &ev->cpu);
+       ev->what = PROC_EVENT_FORK;
+       ev->event_data.fork.parent_pid = task->real_parent->pid;
+       ev->event_data.fork.parent_tgid = task->real_parent->tgid;
+       ev->event_data.fork.child_pid = task->pid;
+       ev->event_data.fork.child_tgid = task->tgid;
+
+       memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id));
+       msg->ack = 0; /* not used */
+       msg->len = sizeof(*ev);
+       /*  If cn_netlink_send() failed, the data is not sent */
+       cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+}
+
+void proc_exec_connector(struct task_struct *task)
+{
+       struct cn_msg *msg;
+       struct proc_event *ev;
+       __u8 buffer[CN_PROC_MSG_SIZE];
+
+       if (atomic_read(&proc_event_num_listeners) < 1)
+               return;
+
+       msg = (struct cn_msg*)buffer;
+       ev = (struct proc_event*)msg->data;
+       get_seq(&msg->seq, &ev->cpu);
+       ev->what = PROC_EVENT_EXEC;
+       ev->event_data.exec.process_pid = task->pid;
+       ev->event_data.exec.process_tgid = task->tgid;
+
+       memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id));
+       msg->ack = 0; /* not used */
+       msg->len = sizeof(*ev);
+       cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+}
+
+void proc_id_connector(struct task_struct *task, int which_id)
+{
+       struct cn_msg *msg;
+       struct proc_event *ev;
+       __u8 buffer[CN_PROC_MSG_SIZE];
+
+       if (atomic_read(&proc_event_num_listeners) < 1)
+               return;
+
+       msg = (struct cn_msg*)buffer;
+       ev = (struct proc_event*)msg->data;
+       ev->what = which_id;
+       ev->event_data.id.process_pid = task->pid;
+       ev->event_data.id.process_tgid = task->tgid;
+       if (which_id == PROC_EVENT_UID) {
+               ev->event_data.id.r.ruid = task->uid;
+               ev->event_data.id.e.euid = task->euid;
+       } else if (which_id == PROC_EVENT_GID) {
+               ev->event_data.id.r.rgid = task->gid;
+               ev->event_data.id.e.egid = task->egid;
+       } else
+               return;
+       get_seq(&msg->seq, &ev->cpu);
+
+       memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id));
+       msg->ack = 0; /* not used */
+       msg->len = sizeof(*ev);
+       cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+}
+
+void proc_exit_connector(struct task_struct *task)
+{
+       struct cn_msg *msg;
+       struct proc_event *ev;
+       __u8 buffer[CN_PROC_MSG_SIZE];
+
+       if (atomic_read(&proc_event_num_listeners) < 1)
+               return;
+
+       msg = (struct cn_msg*)buffer;
+       ev = (struct proc_event*)msg->data;
+       get_seq(&msg->seq, &ev->cpu);
+       ev->what = PROC_EVENT_EXIT;
+       ev->event_data.exit.process_pid = task->pid;
+       ev->event_data.exit.process_tgid = task->tgid;
+       ev->event_data.exit.exit_code = task->exit_code;
+       ev->event_data.exit.exit_signal = task->exit_signal;
+
+       memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id));
+       msg->ack = 0; /* not used */
+       msg->len = sizeof(*ev);
+       cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+}
+
+/*
+ * Send an acknowledgement message to userspace
+ *
+ * Use 0 for success, EFOO otherwise.
+ * Note: this is the negative of conventional kernel error
+ * values because it's not being returned via syscall return
+ * mechanisms.
+ */
+static void cn_proc_ack(int err, int rcvd_seq, int rcvd_ack)
+{
+       struct cn_msg *msg;
+       struct proc_event *ev;
+       __u8 buffer[CN_PROC_MSG_SIZE];
+
+       if (atomic_read(&proc_event_num_listeners) < 1)
+               return;
+
+       msg = (struct cn_msg*)buffer;
+       ev = (struct proc_event*)msg->data;
+       msg->seq = rcvd_seq;
+       ev->cpu = -1;
+       ev->what = PROC_EVENT_NONE;
+       ev->event_data.ack.err = err;
+       memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id));
+       msg->ack = rcvd_ack + 1;
+       msg->len = sizeof(*ev);
+       cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+}
+
+/**
+ * cn_proc_mcast_ctl
+ * @data: message sent from userspace via the connector
+ */
+static void cn_proc_mcast_ctl(void *data)
+{
+       struct cn_msg *msg = data;
+       enum proc_cn_mcast_op *mc_op = NULL;
+       int err = 0;
+
+       if (msg->len != sizeof(*mc_op))
+               return;
+
+       mc_op = (enum proc_cn_mcast_op*)msg->data;
+       switch (*mc_op) {
+       case PROC_CN_MCAST_LISTEN:
+               atomic_inc(&proc_event_num_listeners);
+               break;
+       case PROC_CN_MCAST_IGNORE:
+               atomic_dec(&proc_event_num_listeners);
+               break;
+       default:
+               err = EINVAL;
+               break;
+       }
+       cn_proc_ack(err, msg->seq, msg->ack);
+}
+
+/*
+ * cn_proc_init - initialization entry point
+ *
+ * Adds the connector callback to the connector driver.
+ */
+static int __init cn_proc_init(void)
+{
+       int err;
+
+       if ((err = cn_add_callback(&cn_proc_event_id, "cn_proc",
+                                  &cn_proc_mcast_ctl))) {
+               printk(KERN_WARNING "cn_proc failed to register\n");
+               return err;
+       }
+       return 0;
+}
+
+module_init(cn_proc_init);
index 10d493f..ce76b33 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -48,6 +48,7 @@
 #include <linux/syscalls.h>
 #include <linux/rmap.h>
 #include <linux/acct.h>
+#include <linux/cn_proc.h>
 
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
@@ -1096,6 +1097,7 @@ int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
                                        fput(bprm->file);
                                bprm->file = NULL;
                                current->did_exec = 1;
+                               proc_exec_connector(current);
                                return retval;
                        }
                        read_lock(&binfmt_lock);
diff --git a/include/linux/cn_proc.h b/include/linux/cn_proc.h
new file mode 100644 (file)
index 0000000..70ab563
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * cn_proc.h - process events connector
+ *
+ * Copyright (C) Matt Helsley, IBM Corp. 2005
+ * Based on cn_fork.h by Nguyen Anh Quynh and Guillaume Thouvenin
+ * Original copyright notice follows:
+ * Copyright (C) 2005 Nguyen Anh Quynh <aquynh@gmail.com>
+ * Copyright (C) 2005 Guillaume Thouvenin <guillaume.thouvenin@bull.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef CN_PROC_H
+#define CN_PROC_H
+
+#include <linux/types.h>
+#include <linux/connector.h>
+
+/*
+ * Userspace sends this enum to register with the kernel that it is listening
+ * for events on the connector.
+ */
+enum proc_cn_mcast_op {
+       PROC_CN_MCAST_LISTEN = 1,
+       PROC_CN_MCAST_IGNORE = 2
+};
+
+/*
+ * From the user's point of view, the process
+ * ID is the thread group ID and thread ID is the internal
+ * kernel "pid". So, fields are assigned as follow:
+ *
+ *  In user space     -  In  kernel space
+ *
+ * parent process ID  =  parent->tgid
+ * parent thread  ID  =  parent->pid
+ * child  process ID  =  child->tgid
+ * child  thread  ID  =  child->pid
+ */
+
+struct proc_event {
+       enum what {
+               /* Use successive bits so the enums can be used to record
+                * sets of events as well
+                */
+               PROC_EVENT_NONE = 0x00000000,
+               PROC_EVENT_FORK = 0x00000001,
+               PROC_EVENT_EXEC = 0x00000002,
+               PROC_EVENT_UID  = 0x00000004,
+               PROC_EVENT_GID  = 0x00000040,
+               /* "next" should be 0x00000400 */
+               /* "last" is the last process event: exit */
+               PROC_EVENT_EXIT = 0x80000000
+       } what;
+       __u32 cpu;
+       union { /* must be last field of proc_event struct */
+               struct {
+                       __u32 err;
+               } ack;
+
+               struct fork_proc_event {
+                       pid_t parent_pid;
+                       pid_t parent_tgid;
+                       pid_t child_pid;
+                       pid_t child_tgid;
+               } fork;
+
+               struct exec_proc_event {
+                       pid_t process_pid;
+                       pid_t process_tgid;
+               } exec;
+
+               struct id_proc_event {
+                       pid_t process_pid;
+                       pid_t process_tgid;
+                       union {
+                               uid_t ruid; /* current->uid */
+                               gid_t rgid; /* current->gid */
+                       } r;
+                       union {
+                               uid_t euid;
+                               gid_t egid;
+                       } e;
+               } id;
+
+               struct exit_proc_event {
+                       pid_t process_pid;
+                       pid_t process_tgid;
+                       __u32 exit_code, exit_signal;
+               } exit;
+       } event_data;
+};
+
+#ifdef __KERNEL__
+#ifdef CONFIG_PROC_EVENTS
+void proc_fork_connector(struct task_struct *task);
+void proc_exec_connector(struct task_struct *task);
+void proc_id_connector(struct task_struct *task, int which_id);
+void proc_exit_connector(struct task_struct *task);
+#else
+static inline void proc_fork_connector(struct task_struct *task)
+{}
+
+static inline void proc_exec_connector(struct task_struct *task)
+{}
+
+static inline void proc_id_connector(struct task_struct *task,
+                                    int which_id)
+{}
+
+static inline void proc_exit_connector(struct task_struct *task)
+{}
+#endif /* CONFIG_PROC_EVENTS */
+#endif /* __KERNEL__ */
+#endif /* CN_PROC_H */
index 95952cc..c5769c6 100644 (file)
 #define CN_IDX_CONNECTOR               0xffffffff
 #define CN_VAL_CONNECTOR               0xffffffff
 
+/*
+ * Process Events connector unique ids -- used for message routing
+ */
+#define CN_IDX_PROC                    0x1
+#define CN_VAL_PROC                    0x1
+
 #define CN_NETLINK_USERS               1
 
 /*
index 537394b..452a1d1 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/cpuset.h>
 #include <linux/syscalls.h>
 #include <linux/signal.h>
+#include <linux/cn_proc.h>
 
 #include <asm/uaccess.h>
 #include <asm/unistd.h>
@@ -863,6 +864,7 @@ fastcall NORET_TYPE void do_exit(long code)
                module_put(tsk->binfmt->module);
 
        tsk->exit_code = code;
+       proc_exit_connector(tsk);
        exit_notify(tsk);
 #ifdef CONFIG_NUMA
        mpol_free(tsk->mempolicy);
index 8a06961..efac2c5 100644 (file)
@@ -42,6 +42,7 @@
 #include <linux/profile.h>
 #include <linux/rmap.h>
 #include <linux/acct.h>
+#include <linux/cn_proc.h>
 
 #include <asm/pgtable.h>
 #include <asm/pgalloc.h>
@@ -1143,6 +1144,7 @@ static task_t *copy_process(unsigned long clone_flags,
                        __get_cpu_var(process_counts)++;
        }
 
+       proc_fork_connector(p);
        if (!current->signal->tty && p->signal->tty)
                p->signal->tty = NULL;
 
index 2fa1ed1..1e1f41b 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/suspend.h>
 #include <linux/tty.h>
 #include <linux/signal.h>
+#include <linux/cn_proc.h>
 
 #include <linux/compat.h>
 #include <linux/syscalls.h>
@@ -623,6 +624,7 @@ asmlinkage long sys_setregid(gid_t rgid, gid_t egid)
        current->egid = new_egid;
        current->gid = new_rgid;
        key_fsgid_changed(current);
+       proc_id_connector(current, PROC_EVENT_GID);
        return 0;
 }
 
@@ -662,6 +664,7 @@ asmlinkage long sys_setgid(gid_t gid)
                return -EPERM;
 
        key_fsgid_changed(current);
+       proc_id_connector(current, PROC_EVENT_GID);
        return 0;
 }
   
@@ -751,6 +754,7 @@ asmlinkage long sys_setreuid(uid_t ruid, uid_t euid)
        current->fsuid = current->euid;
 
        key_fsuid_changed(current);
+       proc_id_connector(current, PROC_EVENT_UID);
 
        return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_RE);
 }
@@ -798,6 +802,7 @@ asmlinkage long sys_setuid(uid_t uid)
        current->suid = new_suid;
 
        key_fsuid_changed(current);
+       proc_id_connector(current, PROC_EVENT_UID);
 
        return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_ID);
 }
@@ -846,6 +851,7 @@ asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
                current->suid = suid;
 
        key_fsuid_changed(current);
+       proc_id_connector(current, PROC_EVENT_UID);
 
        return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_RES);
 }
@@ -898,6 +904,7 @@ asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
                current->sgid = sgid;
 
        key_fsgid_changed(current);
+       proc_id_connector(current, PROC_EVENT_GID);
        return 0;
 }
 
@@ -940,6 +947,7 @@ asmlinkage long sys_setfsuid(uid_t uid)
        }
 
        key_fsuid_changed(current);
+       proc_id_connector(current, PROC_EVENT_UID);
 
        security_task_post_setuid(old_fsuid, (uid_t)-1, (uid_t)-1, LSM_SETID_FS);
 
@@ -968,6 +976,7 @@ asmlinkage long sys_setfsgid(gid_t gid)
                }
                current->fsgid = gid;
                key_fsgid_changed(current);
+               proc_id_connector(current, PROC_EVENT_GID);
        }
        return old_fsgid;
 }