diff --git a/drivers/connector/Kconfig b/drivers/connector/Kconfig index 100bfd4..0d410f9 100644 --- a/drivers/connector/Kconfig +++ b/drivers/connector/Kconfig @@ -19,4 +19,12 @@ config PROC_EVENTS Provide a connector that reports process events to userspace. Send events such as fork, exec, id change (uid, gid, suid, etc), and exit. +config NET_EVENTS + boolean "Report network events to userspace" + depends on CONNECTOR=y && SECURITY_NETWORK + default y + ---help--- + Provide a connector that reports networking's events to userspace. + Send events such as DCCP/TCP listen/close and UDP bind/close. + endif # CONNECTOR diff --git a/drivers/connector/Makefile b/drivers/connector/Makefile index 1f255e4..436bb5d 100644 --- a/drivers/connector/Makefile +++ b/drivers/connector/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_CONNECTOR) += cn.o obj-$(CONFIG_PROC_EVENTS) += cn_proc.o +obj-$(CONFIG_NET_EVENTS) += cn_net.o cn-y += cn_queue.o connector.o diff --git a/drivers/connector/cn_net.c b/drivers/connector/cn_net.c new file mode 100644 index 0000000..4fde17f --- /dev/null +++ b/drivers/connector/cn_net.c @@ -0,0 +1,1118 @@ +/* + * drivers/connector/cn_net.c + * + * Network events connector + * Samir Bellabes + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* when waiting for a verdict, you get added to this */ +DECLARE_WAIT_QUEUE_HEAD(cn_net_wq); + +static atomic_t net_event_num_listeners = ATOMIC_INIT(0); +static struct cb_id cn_net_event_id = { CN_IDX_NET, CN_VAL_NET }; +static char cn_net_event_name[] = "cn_net_event"; +static int secondary = 0; + +static struct rb_root event_tree = RB_ROOT; +static rwlock_t event_lock = RW_LOCK_UNLOCKED; +static struct rb_root verdict_tree = RB_ROOT; +static rwlock_t verdict_lock = RW_LOCK_UNLOCKED; + +/* should we print out debug messages */ +static int debug = 0; +static int delay = 5; + +module_param(debug, bool, 0600); +MODULE_PARM_DESC(debug, "Debug enabled or not"); +module_param(delay, uint, 0600); +MODULE_PARM_DESC(delay, "Set the default pending delay for events"); + +#if defined(CONFIG_NET_EVENTS) +#define MY_NAME THIS_MODULE->name +#else +#define MY_NAME "cn_net" +#endif + +#define cn_net_dbg(fmt, arg...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "%s: %s: " fmt , \ + MY_NAME , __FUNCTION__ , \ + ## arg); \ + } while (0) + +static const char *cn_net_syscall_name(const u8 syscall) +{ + static const char *syscall_name[] = { + [CN_NET_SOCKET_LISTEN] = "LISTEN", + [CN_NET_SOCKET_BIND] = "BIND", + [CN_NET_SOCKET_CONNECT] = "CONNECT", + [CN_NET_SOCKET_SHUTDOWN] = "SHUTDOWN", + [CN_NET_SK_FREE_SECURITY] = "SK_FREE", + [CN_NET_SOCKET_ACCEPT] = "ACCEPT", + [CN_NET_SOCKET_POST_ACCEPT] = "POST_ACCEPT", + }; + return syscall_name[syscall]; +}; + +static const char *cn_net_msg_type_name(const u8 msg_type) +{ + static const char *msg_type_name[] = { + [CN_NET_NONE] = "CN_NET_NONE", + [CN_NET_ACK] = "CN_NET_ACK", + [CN_NET_DATA] = "CN_NET_DATA", + [CN_NET_VERDICT] = "CN_NET_VERDICT", + [CN_NET_CONFIG] = "CN_NET_CONFIG", + [CN_NET_LISTEN] = "CN_NET_LISTEN", + [CN_NET_IGNORE] = "CN_NET_IGNORE", + [CN_NET_DUMP] = "CN_NET_DUMP", + }; + return msg_type_name[msg_type]; +}; + +static const char *cn_net_config_name(const u8 config) +{ + static const char *config_name[] = { + [CN_NET_CONFIG_ADD] = "CN_NET_CONFIG_ADD", + [CN_NET_CONFIG_DEL] = "CN_NET_CONFIG_DEL", + [CN_NET_CONFIG_FLUSH] = "CN_NET_CONFIG_FLUSH", + }; + return config_name[config]; +} + +static const char *cn_net_verdict_name(const u8 verdict) +{ + static const char *verdict_name[] = { + [CN_NET_VERDICT_ACCEPT] = "CN_NET_VERDICT_ACCEPT", + [CN_NET_VERDICT_DENY] = "CN_NET_VERDICT_DENY", + [CN_NET_VERDICT_PENDING] = "CN_NET_VERDICT_PENDING", + [CN_NET_VERDICT_POLICY] = "CN_NET_VERDICT_POLICY", + }; + return verdict_name[verdict]; +} + +/** + * is_same_event() + * check if two events are the same or not + */ +static unsigned int is_same_event(struct event one, struct event two) +{ + return ((one.syscall_num == two.syscall_num) && + (one.protocol == two.protocol)); +} + +/** + * lookup_event() + * look for a match in the rbtree + * returns address of the wanted element if it is in the rbtree, or NULL + */ +static struct event_node *lookup_event(struct event ev) +{ + struct rb_node *rb_node = event_tree.rb_node; + + read_lock(&event_lock); + + while (rb_node) { + struct event_node *cur = rb_entry(rb_node, struct event_node, ev_node); + + if (is_same_event(cur->ev, ev)) { + read_unlock(&event_lock); + return cur; + } + + if (ev.syscall_num < cur->ev.syscall_num) + rb_node = rb_node->rb_left; + else if (ev.syscall_num > cur->ev.syscall_num) + rb_node = rb_node->rb_right; + else if (ev.protocol < cur->ev.protocol) + rb_node = rb_node->rb_left; + else if (ev.protocol > cur->ev.protocol) + rb_node = rb_node->rb_right; + } + + read_unlock(&event_lock); + return NULL; +} + +/** + * check_wanted_event() + * we don't send unwanted informations to userspace, according to the + * dynamic configuration in the rbtree + */ +static int check_wanted_event(struct sock *sk, enum cn_net_socket syscall_num) +{ + int err = -EINVAL; + struct event ev; + + if (!sk) + return err; + + ev.syscall_num = syscall_num; + ev.protocol = sk->sk_protocol; + + /* check if the event is registered */ + if (lookup_event(ev)) + err = 0; + + return err; +} + +/** + * dump_event() + * dump the entire rbtree in log + */ +static void dump_event(void) +{ + struct rb_node *p = NULL ; + struct event_node *tmp = NULL; + struct verdict_node *vtmp = NULL; + + read_lock(&event_lock); + p = rb_first(&event_tree); + while (p) { + tmp = rb_entry(p, struct event_node, ev_node); + printk(KERN_INFO "%d:%s\n", tmp->ev.protocol, + cn_net_syscall_name(tmp->ev.syscall_num)); + p = rb_next(p); + } + read_unlock(&event_lock); + + /* FIXME delete this code */ + read_lock(&verdict_lock); + p = rb_first(&verdict_tree); + while (p) { + vtmp = rb_entry(p, struct verdict_node, v_node); + printk(KERN_INFO "0x%x:%s\n", vtmp->verdict.id, + cn_net_verdict_name(vtmp->verdict.v)); + p = rb_next(p); + } + read_unlock(&verdict_lock); +} + +/** + * remove_all_events() + * delete all events registered in the rbtree + */ +static enum ack_err remove_all_events(void) +{ + struct rb_node *p = NULL; + struct event_node *cur = NULL; + + write_lock(&event_lock); + while ((p = rb_first(&event_tree)) != NULL) { + cur = rb_entry(p, struct event_node, ev_node); + rb_erase(p, &event_tree); + kfree(cur); + } + write_unlock(&event_lock); + + /* rbtree is now empty */ + return CN_NET_ACK_SUCCES; +} + +/** + * alloc_event() + * alloc memory for a event_node, and return pointer to it + */ +static struct event_node *alloc_event(void) +{ + struct event_node *evn = NULL; + evn = kzalloc(sizeof(struct event_node), GFP_KERNEL); + return evn; +} + +/** + * insert_event() + * insert a event in the rbtree + * we are checking if we are trying to register a same event twice + */ +static enum ack_err insert_event(struct event ev) +{ + struct rb_node **rb_link, *rb_parent; + struct event_node *new_evn; + enum ack_err ret = CN_NET_ACK_SUCCES; + + rb_link = &event_tree.rb_node; + rb_parent = NULL; + + if ((new_evn = alloc_event()) != NULL) { + new_evn->ev.syscall_num = ev.syscall_num; + new_evn->ev.protocol = ev.protocol; + } else { + /* can't allocate memory, exiting */ + ret = CN_NET_ACK_ENOMEM; + goto out; + } + + write_lock(&event_lock); + + while(*rb_link) { + struct event_node *tmp; + + rb_parent = *rb_link; + tmp = rb_entry(rb_parent, struct event_node, ev_node); + + if (ev.syscall_num < tmp->ev.syscall_num) + rb_link = &rb_parent->rb_left; + else if (ev.syscall_num > tmp->ev.syscall_num) + rb_link = &rb_parent->rb_right; + else if (ev.protocol < tmp->ev.protocol) + rb_link = &rb_parent->rb_left; + else if (ev.protocol > tmp->ev.protocol) + rb_link = &rb_parent->rb_right; + else { + /* event is already registered */ + write_unlock(&event_lock); + ret = CN_NET_ACK_EINCONFIG; + goto out_free; + } + } + + /* no match: event is added to the tree */ + rb_link_node(&new_evn->ev_node, rb_parent, rb_link); + rb_insert_color(&new_evn->ev_node, &event_tree); + write_unlock(&event_lock); + return ret; + +out_free: + kfree(new_evn); +out: + return ret; +} + +/** + * remove_event() + * delete a entry from the rbtree + * we are checking if we are trying to delete a unregistered event + */ +static enum ack_err remove_event(struct event ev) +{ + struct event_node *cur = NULL; + enum ack_err ret = CN_NET_ACK_EINCONFIG; + struct rb_node *rb_node = event_tree.rb_node; + + write_lock(&event_lock); + + while (rb_node) { + cur = rb_entry(rb_node, struct event_node, ev_node); + + if (is_same_event(cur->ev, ev)) + break; + + if (ev.syscall_num < cur->ev.syscall_num) + rb_node = rb_node->rb_left; + else if (ev.syscall_num > cur->ev.syscall_num) + rb_node = rb_node->rb_right; + else if (ev.protocol < cur->ev.protocol) + rb_node = rb_node->rb_left; + else if (ev.protocol > cur->ev.protocol) + rb_node = rb_node->rb_right; + } + + if (rb_node) { + rb_erase(&cur->ev_node, &event_tree); + kfree(cur); + ret = CN_NET_ACK_SUCCES; + } + + write_unlock(&event_lock); + return ret; +} + +/* routine for verdict_tree */ + +/** + * __lookup_verdict() + * look for a match in the rbtree + * this function is not lock-safe, remember to lock verdict_lock when using + * returns address of the verdict_node if it is in the rbtree, or NULL + */ +static struct verdict_node *__lookup_verdict(__u32 id) +{ + struct rb_node *rb_node = verdict_tree.rb_node; + + while (rb_node) { + struct verdict_node *cur = rb_entry(rb_node, struct verdict_node, v_node); + + if (cur->verdict.id == id) { + return cur; + } + + if (id < cur->verdict.id) + rb_node = rb_node->rb_left; + else if (id > cur->verdict.id) + rb_node = rb_node->rb_right; + } + + return NULL; +} + +/** + * apply_policy() + * when timer expires for a verdict, we apply the policy by default + */ +static void apply_policy(unsigned long arg) +{ + struct verdict_node *vnode; + __u32 id = (__u32) arg; + + write_lock(&verdict_lock); + vnode = __lookup_verdict(id); + if (vnode) { + vnode->verdict.v = DEFAULT_POLICY; + cn_net_dbg("apply_policy: id=0x%x verdict %d %s\n", /* FIXME debug to delete */ + vnode->verdict.id, + (int)vnode->verdict.v, + cn_net_verdict_name((int)vnode->verdict.v)); + } + write_unlock(&verdict_lock); + wake_up(&cn_net_wq); +} + +/** + * alloc_verdict() + * alloc memory for a verdict_node, and return pointer to it + */ +static struct verdict_node *alloc_verdict(void) +{ + struct verdict_node *vnode = NULL; + vnode = kzalloc(sizeof(struct verdict_node), GFP_KERNEL); + return vnode; +} + +/** + * insert_verdict() + * insert a verdict in the rbtree + * we are checking if we are trying to register a same verdict twice + * Returns 0 if insertion is ok, else -1 + */ +static int insert_verdict(__u32 id) +{ + struct rb_node **rb_link, *rb_parent; + struct verdict_node *new_verdict; + enum ack_err ret = CN_NET_ACK_SUCCES; + + rb_link = &verdict_tree.rb_node; + rb_parent = NULL; + + if ((new_verdict = alloc_verdict()) != NULL) { + new_verdict->verdict.id = id; + new_verdict->verdict.v = CN_NET_VERDICT_PENDING; + } else { + /* can't allocate memory, exiting */ + ret = CN_NET_ACK_ENOMEM; + goto out; + } + + write_lock(&verdict_lock); + + while(*rb_link) { + struct verdict_node *tmp; + + rb_parent = *rb_link; + tmp = rb_entry(rb_parent, struct verdict_node, v_node); + + if (id < tmp->verdict.id) + rb_link = &rb_parent->rb_left; + else if (id > tmp->verdict.id) + rb_link = &rb_parent->rb_right; + else { + /* verdict is already registered */ + write_unlock(&verdict_lock); + ret = CN_NET_ACK_EINCONFIG; + goto out_free; + } + } + + /* setting timer to set defaut policy at expiration */ + init_timer(&new_verdict->timer); + new_verdict->timer.expires = jiffies + delay*HZ; + new_verdict->timer.data = (unsigned long)id; + new_verdict->timer.function = apply_policy; + /* no match: verdict is added to the tree */ + rb_link_node(&new_verdict->v_node, rb_parent, rb_link); + rb_insert_color(&new_verdict->v_node, &verdict_tree); + write_unlock(&verdict_lock); + return 0; + +out_free: + kfree(new_verdict); +out: + if (ret != CN_NET_ACK_SUCCES) /* error occured, inform userspace with CN_NET_ACK */ + cn_net_ack(ret, id, 0, CN_NET_ACK); + return -1; +} + +/* routine for verdict_tree */ + + +/** + * do_register() + * check if userpace protocol version is same as kernel protocol version + */ +static enum ack_err do_register(__u32 version) +{ + enum ack_err err = CN_NET_ACK_SUCCES; + + cn_net_dbg("do_register: %d %d\n", + version, CN_NET_VERSION); + + if (version == CN_NET_VERSION) + atomic_inc(&net_event_num_listeners); + else + err = CN_NET_ACK_EBADPROTO; + return err; +} + +/** + * do_config() + * execute config asked by userspace + * return enum ack_err + */ +static enum ack_err do_config(struct config_msg *cfg) +{ + enum ack_err err = CN_NET_ACK_SUCCES; + + cn_net_dbg("do_config: %s %s %d\n", + cn_net_config_name(cfg->config_cmd), + cn_net_syscall_name(cfg->ev.syscall_num), + cfg->ev.protocol); + + switch (cfg->config_cmd) { + case CN_NET_CONFIG_ADD: + err = insert_event(cfg->ev); + break; + case CN_NET_CONFIG_DEL: + err= remove_event(cfg->ev); + break; + case CN_NET_CONFIG_FLUSH: + err = remove_all_events(); + break; + default: + err = CN_NET_ACK_EINTYPE; + break; + } + + return err; +} + +/** + * do_verdict() + * execute verdict for a CN_NET_DATA message + * return enum ack_err + */ +static enum ack_err do_verdict(struct verdict_msg *vdt) +{ + enum ack_err err = CN_NET_ACK_SUCCES; + struct verdict_node *vnode = NULL; + + cn_net_dbg("do_verdict: 0x%x %d\n", + vdt->id, vdt->v); + + switch (vdt->v) { + case CN_NET_VERDICT_ACCEPT: + case CN_NET_VERDICT_DENY: + case CN_NET_VERDICT_POLICY: + write_lock(&verdict_lock); + vnode = __lookup_verdict(vdt->id); + if (vnode) { + vnode->verdict.v = vdt->v; + write_unlock(&verdict_lock); + wake_up(&cn_net_wq); + } else { + write_unlock(&verdict_lock); + err = CN_NET_ACK_EINCONFIG; + } + break; + default: + err = CN_NET_ACK_EINTYPE; + break; + } + + return err; +} + +/** + * cn_net_ack + * Send an acknowledgement message to userspace + * + * Use 0 for SUCCESS, EFOO otherwise. (see enum ack_err) + */ +static void cn_net_ack(enum ack_err err, unsigned int rcvd_seq, + unsigned int rcvd_ack, enum msg_type msg_type) +{ + struct cn_msg *m; + struct net_event *net_ev; + struct timespec ts; + __u8 buffer[CN_NET_MSG_SIZE]; + + cn_net_dbg("cn_net_ack: listen=%d\n", + atomic_read(&net_event_num_listeners)); + + /* no listener, no response, excepted for notifying that + protocol version is not the same as in kernel */ + if (atomic_read(&net_event_num_listeners) < 1 && + err != CN_NET_ACK_EBADPROTO) + return; + + m = (struct cn_msg *) buffer; + net_ev = (struct net_event *) m->data; + + net_ev->msg_type = msg_type; +/* ktime_get_real_ts(&net_ev->timestamp); */ + ktime_get_real_ts(&ts); + put_unaligned(timespec_to_ns(&ts), (__u64 *)&net_ev->timestamp_ns); + net_ev->net_event_data.ack = err; + memcpy(&m->id, &cn_net_event_id, sizeof(m->id)); + m->seq = rcvd_seq; + m->ack = rcvd_ack + 1; + m->len = sizeof(struct net_event); + cn_netlink_send(m, CN_IDX_NET, gfp_any()); +} + +/** + * cn_net_ctl + * connector callback + * @data: message receive from userspace via the connector + */ +void cn_net_ctl(void *data) +{ + struct cn_msg *m = data; + struct net_event *net_ev = NULL; + enum msg_type msg_type; + enum ack_err err = CN_NET_ACK_SUCCES; + + if (m->len != sizeof(struct net_event)) { + cn_net_dbg("cn_net_ctl : message with " + "bad size, discard\n"); + return; + } + + net_ev = (struct net_event *) m->data; + + if (net_ev->msg_type != CN_NET_LISTEN && + atomic_read(&net_event_num_listeners) < 1) { + cn_net_dbg("cn_net_ctl : register first\n"); + return; + } + msg_type = CN_NET_ACK; /* default response message type */ + + switch (net_ev->msg_type) { + case CN_NET_NONE: /* want to play ping pong ? */ + msg_type = net_ev->msg_type; + break; + case CN_NET_ACK: /* userspace is ack'ing - check that */ + /* FIXME: we don't send an ACK to an ACK */ + /* we just check which message is ack */ + goto out; + break; + case CN_NET_DATA: /* CN_NET_DATA can't be used by userspace */ + err = CN_NET_ACK_EINTYPE; + break; + case CN_NET_VERDICT: /* userspace give verdict for the CN_NET_DATA msg*/ + err = do_verdict(&(net_ev->net_event_data.verdict)); + break; + case CN_NET_CONFIG: /* configuring kernel's behaviour */ + err = do_config(&(net_ev->net_event_data.config)); + break; + case CN_NET_LISTEN: /* userspace is registering */ + err = do_register(net_ev->net_event_data.version); + break; + case CN_NET_IGNORE: /* userspace is unregistering */ + atomic_dec(&net_event_num_listeners); + break; + case CN_NET_DUMP: /* dumping the rbtree -- debug purpose */ + dump_event(); + break; + default: + err = CN_NET_ACK_EINTYPE; + break; + } + cn_net_ack(err, m->seq, m->ack, msg_type); +out: + return; +} + +/** + * cn_net_send_event + * send data to userspace + * @sock: sock which sock->sk->sk_state change to the state identified by @event + */ +static __u32 cn_net_send_event(struct sock *sk, struct sockaddr *address, + enum cn_net_socket syscall_num) +{ + struct net_event *net_ev; + struct cn_msg *m; + struct inet_sock *inet = inet_sk(sk); + struct sockaddr_in *addr4 = NULL; + struct sockaddr_in6 *addr6 = NULL; + struct task_struct *me = current; + struct verdict_node *vnode = NULL; + struct timespec ts; + int insertion = -1; + + __u8 buffer[CN_NET_MSG_SIZE]; + + cn_net_dbg("cn_net_ack: listen=%d\n", + atomic_read(&net_event_num_listeners)); + + if (atomic_read(&net_event_num_listeners) < 1) + goto out; + + m = (struct cn_msg *) buffer; + net_ev = (struct net_event *) m->data; + + switch (syscall_num) { + case CN_NET_SOCKET_LISTEN: + case CN_NET_SOCKET_SHUTDOWN: + case CN_NET_SK_FREE_SECURITY: + switch (sk->sk_family) { + case AF_INET: + net_ev->net_event_data.data.saddr.ipv4 = inet->saddr; + net_ev->net_event_data.data.daddr.ipv4 = inet->daddr; + break; + case AF_INET6: + memcpy(&(net_ev->net_event_data.data.saddr.ipv6), + &(inet->pinet6->saddr), sizeof(struct in6_addr)); + memcpy(&(net_ev->net_event_data.data.daddr.ipv6), + &(inet->pinet6->daddr), sizeof(struct in6_addr)); + break; + default: + /* other protocol, sending nothing */ + goto out; + break; + }; + net_ev->net_event_data.data.sport = ntohs(inet->sport); + net_ev->net_event_data.data.dport = ntohs(inet->dport); + break; + case CN_NET_SOCKET_BIND: + switch (sk->sk_family) { + case AF_INET: + addr4 = (struct sockaddr_in *) address; + net_ev->net_event_data.data.saddr.ipv4 = addr4->sin_addr.s_addr; + net_ev->net_event_data.data.daddr.ipv4 = inet->daddr; + net_ev->net_event_data.data.sport = ntohs(addr4->sin_port); + net_ev->net_event_data.data.dport = ntohs(inet->dport); + break; + case AF_INET6: + addr6 = (struct sockaddr_in6 *) address; + memcpy(&(net_ev->net_event_data.data.saddr.ipv6), + &(addr6->sin6_addr), sizeof(struct in6_addr)); + memcpy(&(net_ev->net_event_data.data.daddr.ipv6), + &(inet->pinet6->daddr), sizeof(struct in6_addr)); + net_ev->net_event_data.data.sport = ntohs(addr6->sin6_port); + net_ev->net_event_data.data.dport = ntohs(inet->dport); + break; + default: + /* other protocol, sending nothing */ + goto out; + break; + }; + break; + case CN_NET_SOCKET_CONNECT: + switch (sk->sk_family) { + case AF_INET: + addr4 = (struct sockaddr_in *) address; + net_ev->net_event_data.data.saddr.ipv4 = inet->saddr; + net_ev->net_event_data.data.daddr.ipv4 = addr4->sin_addr.s_addr; + net_ev->net_event_data.data.sport = ntohs(inet->sport); + net_ev->net_event_data.data.dport = ntohs(addr4->sin_port); + break; + case AF_INET6: + addr6 = (struct sockaddr_in6 *) address; + memcpy(&(net_ev->net_event_data.data.saddr.ipv6), + &(inet->pinet6->saddr), sizeof(struct in6_addr)); + memcpy(&(net_ev->net_event_data.data.daddr.ipv6), + &(addr6->sin6_addr), sizeof(struct in6_addr)); + net_ev->net_event_data.data.sport = ntohs(inet->sport); + net_ev->net_event_data.data.dport = ntohs(addr6->sin6_port); + break; + default: + /* other protocol, sending nothing */ + goto out; + break; + }; + break; + default: + /* Bad syscall_num */ + break; + }; + net_ev->msg_type = CN_NET_DATA; +/* ktime_get_real_ts(&net_ev->timestamp); */ + ktime_get_real_ts(&ts); + put_unaligned(timespec_to_ns(&ts), (__u64 *)&net_ev->timestamp_ns); + net_ev->net_event_data.data.uid = me->uid; +/* get_task_comm(net_ev->net_event_data.data.taskname, me); */ + net_ev->net_event_data.data.pid = me->pid; + net_ev->net_event_data.data.ev.protocol = sk->sk_protocol; + net_ev->net_event_data.data.ev.syscall_num = syscall_num; + net_ev->net_event_data.data.family = sk->sk_family; + memcpy(&m->id, &cn_net_event_id, sizeof(m->id)); + m->seq = get_random_int(); + m->ack = 0; + m->len = sizeof(struct net_event); + + /* adding node entry to verdict_tree */ + insertion = insert_verdict(m->seq); + + if (insertion == 0) { /* sending CN_NET_DATA */ + cn_netlink_send(m, CN_IDX_NET, gfp_any()); + /* starting timer for default policy */ + write_lock(&verdict_lock); + vnode = __lookup_verdict(m->seq); + if (vnode) + add_timer(&vnode->timer); + write_unlock(&verdict_lock); + } +out: + return m->seq; +} + +static __u32 get_secure_verdict(__u32 id) +{ + struct verdict_node *vnode = NULL; + __u32 v; + + read_lock(&verdict_lock); + vnode = __lookup_verdict(id); + if (vnode) + v = vnode->verdict.v; + else + v = DEFAULT_POLICY; + read_unlock(&verdict_lock); + return v; +} + +static int cn_net_socket_listen(struct socket *sock, int backlog) +{ + struct verdict_node *vnode = NULL; + enum verdict v; + __u32 id = 0; + + DECLARE_WAITQUEUE(myself, current); + + cn_net_dbg("cn_net_socket_listen\n"); + if (check_wanted_event(sock->sk, CN_NET_SOCKET_LISTEN) == 0) + id = cn_net_send_event(sock->sk, NULL, CN_NET_SOCKET_LISTEN); + +/* if (!vnode) { */ +/* cn_net_dbg("cn_net_socket_listen: apply default_policy vnode == NULL\n"); */ +/* v = DEFAULT_POLICY; */ +/* } else { */ + add_wait_queue(&cn_net_wq, &myself); + for(;;) { + set_current_state(TASK_INTERRUPTIBLE); + if ((v = get_secure_verdict(id)) != CN_NET_VERDICT_PENDING) + break; + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&cn_net_wq, &myself); + /* remove verdict_node for rbtree */ + write_lock(&verdict_lock); + vnode = __lookup_verdict(id); + if (vnode) { + rb_erase(&vnode->v_node, &verdict_tree); + del_timer_sync(&vnode->timer); + } + write_unlock(&verdict_lock); + kfree(vnode); +/* } */ + cn_net_dbg("cn_net_socket_listen: verdict %d %s\n", (int) v, + cn_net_verdict_name((int)v)); + return v == CN_NET_VERDICT_POLICY ? DEFAULT_POLICY : (int)v; +} + +static int cn_net_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct verdict_node *vnode = NULL; + enum verdict v; + __u32 id = 0; + + DECLARE_WAITQUEUE(myself, current); + + cn_net_dbg("cn_net_socket_bind\n"); + if (check_wanted_event(sock->sk, CN_NET_SOCKET_BIND) == 0) + id = cn_net_send_event(sock->sk, address, CN_NET_SOCKET_BIND); + +/* if (!vnode) { */ +/* cn_net_dbg("cn_net_socket_bind: apply default_policy vnode == NULL\n"); */ +/* v = DEFAULT_POLICY; */ +/* } else { */ + add_wait_queue(&cn_net_wq, &myself); + for(;;) { + set_current_state(TASK_INTERRUPTIBLE); + if ((v = get_secure_verdict(id)) != CN_NET_VERDICT_PENDING) + break; + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&cn_net_wq, &myself); + /* remove verdict_node for rbtree */ + write_lock(&verdict_lock); + vnode = __lookup_verdict(id); + if (vnode) { + rb_erase(&vnode->v_node, &verdict_tree); + del_timer_sync(&vnode->timer); + } + write_unlock(&verdict_lock); + kfree(vnode); +/* } */ + cn_net_dbg("cn_net_socket_bind: verdict %d %s\n", (int) v, + cn_net_verdict_name((int)v)); + return v == CN_NET_VERDICT_POLICY ? DEFAULT_POLICY : (int)v; + +} + +static int cn_net_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct verdict_node *vnode = NULL; + enum verdict v; + __u32 id = 0; + + DECLARE_WAITQUEUE(myself, current); + + cn_net_dbg("cn_net_socket_connect\n"); + if (check_wanted_event(sock->sk, CN_NET_SOCKET_CONNECT) == 0) + id = cn_net_send_event(sock->sk, address, CN_NET_SOCKET_CONNECT); + +/* if (!vnode) { */ +/* cn_net_dbg("cn_net_socket_connect: apply default_policy vnode == NULL\n"); */ +/* v = DEFAULT_POLICY; */ +/* } else { */ + add_wait_queue(&cn_net_wq, &myself); + for(;;) { + set_current_state(TASK_INTERRUPTIBLE); + if ((v = get_secure_verdict(id)) != CN_NET_VERDICT_PENDING) + break; + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&cn_net_wq, &myself); + /* remove verdict_node for rbtree */ + write_lock(&verdict_lock); + vnode = __lookup_verdict(id); + if (vnode) { + rb_erase(&vnode->v_node, &verdict_tree); + del_timer_sync(&vnode->timer); + } + write_unlock(&verdict_lock); + kfree(vnode); +/* } */ + cn_net_dbg("cn_net_socket_connect: verdict %d %s\n", (int) v, + cn_net_verdict_name((int)v)); + return v == CN_NET_VERDICT_POLICY ? DEFAULT_POLICY : (int)v; +} + +static int cn_net_socket_shutdown(struct socket *sock, int how) +{ + struct verdict_node *vnode = NULL; + enum verdict v; + __u32 id = 0; + + DECLARE_WAITQUEUE(myself, current); + + cn_net_dbg("cn_net_socket_shutdown\n"); + if (check_wanted_event(sock->sk, CN_NET_SOCKET_SHUTDOWN) == 0) + id = cn_net_send_event(sock->sk, NULL, CN_NET_SOCKET_SHUTDOWN); + +/* if (!vnode) { */ +/* cn_net_dbg("cn_net_socket_shutdown: apply default_policy vnode == NULL\n"); */ +/* v = DEFAULT_POLICY; */ +/* } else { */ + add_wait_queue(&cn_net_wq, &myself); + for(;;) { + set_current_state(TASK_INTERRUPTIBLE); + if ((v = get_secure_verdict(id)) != CN_NET_VERDICT_PENDING) + break; + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&cn_net_wq, &myself); + /* remove verdict_node for rbtree */ + write_lock(&verdict_lock); + vnode = __lookup_verdict(id); + if (vnode) { + rb_erase(&vnode->v_node, &verdict_tree); + del_timer_sync(&vnode->timer); + } + write_unlock(&verdict_lock); + kfree(vnode); +/* } */ + cn_net_dbg("cn_net_socket_shutdown: verdict %d %s\n", (int) v, + cn_net_verdict_name((int)v)); + return v == CN_NET_VERDICT_POLICY ? DEFAULT_POLICY : (int)v; +} + +static void cn_net_sk_free_security(struct sock *sk) +{ + struct verdict_node *vnode = NULL; + enum verdict v; + __u32 id = 0; + + DECLARE_WAITQUEUE(myself, current); + + cn_net_dbg("cn_net_sk_free_security\n"); + if (check_wanted_event(sk, CN_NET_SK_FREE_SECURITY) == 0) + id = cn_net_send_event(sk, NULL, CN_NET_SK_FREE_SECURITY); + +/* if (!vnode) { */ +/* cn_net_dbg("cn_net_socket_free_security: apply default_policy vnode == NULL\n"); */ +/* v = DEFAULT_POLICY; */ +/* } else { */ + add_wait_queue(&cn_net_wq, &myself); + for(;;) { + set_current_state(TASK_INTERRUPTIBLE); + if ((v = get_secure_verdict(id)) != CN_NET_VERDICT_PENDING) + break; + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&cn_net_wq, &myself); + /* remove verdict_node for rbtree */ + write_lock(&verdict_lock); + vnode = __lookup_verdict(id); + if (vnode) { + rb_erase(&vnode->v_node, &verdict_tree); + del_timer_sync(&vnode->timer); + } + write_unlock(&verdict_lock); + kfree(vnode); +/* } */ + cn_net_dbg("cn_net_socket_free_security: verdict %d %s\n", (int) v, + cn_net_verdict_name((int)v)); + return; +} + +static void cn_net_socket_post_accept(struct socket *sock, struct socket *newsock) +{ + struct inet_sock *inet; + struct inet_sock *newinet; + + inet = inet_sk(sock->sk); + newinet = inet_sk(newsock->sk); + + cn_net_dbg("cn_net_socket_post_accept\n"); +/* if (check_wanted_event(sk, CN_NET_SOCKET_ACCEPT) == 0) */ +/* cn_net_socket_accept(sk, NULL, CN_NET_SOCKET_ACCEPT); */ + printk(KERN_INFO "post_accept: type: %d:%d sock=("NIPQUAD_FMT":%u -> "NIPQUAD_FMT":%u) " + "newsock=("NIPQUAD_FMT":%u -> "NIPQUAD_FMT":%u)\n", + sock->type, newsock->type, + NIPQUAD(inet->saddr), ntohs(inet->sport), + NIPQUAD(inet->daddr), ntohs(inet->dport), + NIPQUAD(newinet->saddr), ntohs(newinet->sport), + NIPQUAD(newinet->daddr), ntohs(newinet->dport)); + + return; +} + +static int cn_net_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct inet_sock *inet; + + inet = inet_sk(sock->sk); + + cn_net_dbg("cn_net_socket_accept\n"); +/* if (check_wanted_event(sk, CN_NET_SOCKET_ACCEPT) == 0) */ +/* cn_net_socket_accept(sk, NULL, CN_NET_SOCKET_ACCEPT); */ + printk(KERN_INFO "accept: %d: sock=("NIPQUAD_FMT":%u -> "NIPQUAD_FMT":%u)\n", + sock->type, + NIPQUAD(inet->saddr), ntohs(inet->sport), + NIPQUAD(inet->daddr), ntohs(inet->dport)); + + return 0; +} + +static struct security_operations cn_net_security_ops = { + .socket_listen = cn_net_socket_listen, + .socket_bind = cn_net_socket_bind, + .socket_connect = cn_net_socket_connect, + .socket_shutdown = cn_net_socket_shutdown, + .sk_free_security = cn_net_sk_free_security, + .socket_accept = cn_net_socket_accept, + .socket_post_accept = cn_net_socket_post_accept, +}; + +static int __init init(void) +{ + int err = 0; + + err = cn_add_callback(&cn_net_event_id, cn_net_event_name, &cn_net_ctl); + if (err) { + printk(KERN_WARNING "cn_net: failure add connector callback\n"); + goto out_callback; + } + + if (register_security(&cn_net_security_ops)) { + printk(KERN_INFO "cn_net: failure registering with kernel\n"); + if (mod_reg_security(MY_NAME, &cn_net_security_ops)) { + printk(KERN_WARNING + "cn_net: failure registering with " + "primary security module\n"); + err = -EINVAL; + goto out_security; + } + secondary = 1; + } + + printk(KERN_INFO "cn_net: network events module loaded\n"); + return 0; + +out_security: + cn_del_callback(&cn_net_event_id); + +out_callback: + return err; +} + +static void __exit fini(void) +{ + if (secondary) { + if (mod_unreg_security(MY_NAME, &cn_net_security_ops)) + printk(KERN_INFO "cn_net: failure unregistering with " + "primary security module\n"); + } else { + if (unregister_security(&cn_net_security_ops)) + printk(KERN_INFO "cn_net: failure unregistering with " + "kernel\n"); + } + + cn_del_callback(&cn_net_event_id); + + /* clean memory */ + remove_all_events(); + + printk(KERN_INFO "cn_net: network events module unloaded\n"); +} + +module_init(init); +module_exit(fini); + +MODULE_DESCRIPTION("Network events module"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Samir Bellabes "); diff --git a/include/linux/cn_net.h b/include/linux/cn_net.h new file mode 100644 index 0000000..8c8ba56 --- /dev/null +++ b/include/linux/cn_net.h @@ -0,0 +1,146 @@ +/* + * include/linux/cn_net.h + * + * Network events connector + * Samir Bellabes + * + * 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. + */ + +#ifndef CN_NET_H +#define CN_NET_H + +#include +#include + +#define CN_NET_VERSION 0x1 +#define DEFAULT_POLICY CN_NET_VERDICT_ACCEPT + +#define CN_NET_MSG_SIZE (sizeof(struct cn_msg) + sizeof(struct net_event)) + +/** + * identify which syscall has been called. + */ +enum cn_net_socket { + CN_NET_SOCKET_LISTEN = 0, + CN_NET_SOCKET_BIND, + CN_NET_SOCKET_CONNECT, + CN_NET_SOCKET_SHUTDOWN, + CN_NET_SK_FREE_SECURITY, + CN_NET_SOCKET_ACCEPT, + CN_NET_SOCKET_POST_ACCEPT, + CN_NET_SOCKET_MAX, +}; + +/** + * Protocol message type + */ +enum msg_type { + CN_NET_NONE = 0, + CN_NET_ACK, + CN_NET_DATA, + CN_NET_VERDICT, + CN_NET_CONFIG, + CN_NET_LISTEN, + CN_NET_IGNORE, + CN_NET_DUMP, +}; + +/** + * values for CN_NET_ACK messages + */ +enum ack_err { + CN_NET_ACK_SUCCES = 0, + CN_NET_ACK_ENOMEM, + CN_NET_ACK_EINCONFIG, + CN_NET_ACK_EINTYPE, + CN_NET_ACK_EBADPROTO, +}; + +/** + * values for CN_NET_VERDICT messages + */ +enum verdict { + CN_NET_VERDICT_ACCEPT = 0, + CN_NET_VERDICT_DENY, + CN_NET_VERDICT_PENDING, + CN_NET_VERDICT_POLICY, +}; + +/** + * values for CN_NET_CONFIG messages + */ +enum config_cmd { + CN_NET_CONFIG_ADD = 0, + CN_NET_CONFIG_DEL, + CN_NET_CONFIG_FLUSH, +}; + +struct event { + enum cn_net_socket syscall_num; + __u8 protocol; +}; + +struct event_node { + struct rb_node ev_node; + struct event ev; +}; + +struct config_msg { + enum config_cmd config_cmd; + struct event ev; +}; + +struct verdict_msg { + __u32 id; + enum verdict v; +}; + +struct verdict_node { + struct rb_node v_node; + struct timer_list timer; + struct verdict_msg verdict; +}; + +struct net_event { + enum msg_type msg_type; + __u64 __attribute__((aligned(8))) timestamp_ns; + union { + /* protocol version number */ + __u32 version; + + /* generic ack for both userspace and kernel */ + enum ack_err ack; + + /* send data to userspace */ + struct { + struct event ev; + uid_t uid; + pid_t pid; + unsigned int family; + union { + struct in6_addr ipv6; + __u32 ipv4; + } saddr; + union { + struct in6_addr ipv6; + __u32 ipv4; + } daddr; + unsigned int sport; + unsigned int dport; + } data; + + /* send config to kernel */ + struct config_msg config; + /* send verdict to kernel */ + struct verdict_msg verdict; + } net_event_data; +}; + +static void cn_net_ack(enum ack_err err, unsigned int rcvd_seq, + unsigned int rcvd_ack, enum msg_type msg_type); + +#endif /* CN_NET_H */ diff --git a/include/linux/connector.h b/include/linux/connector.h index 10eb56b..3042360 100644 --- a/include/linux/connector.h +++ b/include/linux/connector.h @@ -36,9 +36,10 @@ #define CN_IDX_CIFS 0x2 #define CN_VAL_CIFS 0x1 #define CN_W1_IDX 0x3 /* w1 communication */ #define CN_W1_VAL 0x1 +#define CN_IDX_NET 0x4 +#define CN_VAL_NET 0x1 - -#define CN_NETLINK_USERS 4 +#define CN_NETLINK_USERS 5 /* * Maximum connector's message size.