From f0e215b07c40e8fbf4ae842d8b33f4a285c0b3e1 Mon Sep 17 00:00:00 2001 From: JP Abgrall Date: Wed, 13 Jul 2011 16:02:31 -0700 Subject: netfilter: quota2: add support to log quota limit reached. This uses the NETLINK NETLINK_NFLOG family to log a single message when the quota limit is reached. It uses the same packet type as ipt_ULOG, but - never copies skb data, - uses 112 as the event number (ULOG's +1) It doesn't log if the module param "event_num" is 0. Change-Id: I6f31736b568bb31a4ff0b9ac2ee58380e6b675ca Signed-off-by: JP Abgrall --- net/netfilter/Kconfig | 12 +++++++ net/netfilter/xt_quota2.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index ddb7bb507bd..5bd5c612a9b 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -975,6 +975,18 @@ config NETFILTER_XT_MATCH_QUOTA2 If you want to compile it as a module, say M here and read . If unsure, say `N'. +config NETFILTER_XT_MATCH_QUOTA2_LOG + bool '"quota2" Netfilter LOG support' + depends on NETFILTER_XT_MATCH_QUOTA2 + depends on IP_NF_TARGET_ULOG=n # not yes, not module, just no + default n + help + This option allows `quota2' to log ONCE when a quota limit + is passed. It logs via NETLINK using the NETLINK_NFLOG family. + It logs similarly to how ipt_ULOG would without data. + + If unsure, say `N'. + config NETFILTER_XT_MATCH_RATEEST tristate '"rateest" match support' depends on NETFILTER_ADVANCED diff --git a/net/netfilter/xt_quota2.c b/net/netfilter/xt_quota2.c index 454ce10c8cd..3c72bea2dd6 100644 --- a/net/netfilter/xt_quota2.c +++ b/net/netfilter/xt_quota2.c @@ -19,6 +19,9 @@ #include #include +#ifdef CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG +#include +#endif /** * @lock: lock to protect quota writers from each other @@ -32,6 +35,16 @@ struct xt_quota_counter { struct proc_dir_entry *procfs_entry; }; +#ifdef CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG +/* Harald's favorite number +1 :D From ipt_ULOG.C */ +static int qlog_nl_event = 112; +module_param_named(event_num, qlog_nl_event, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(event_num, + "Event number for NETLINK_NFLOG message. 0 disables log." + "111 is what ipt_ULOG uses."); +static struct sock *nflognl; +#endif + static LIST_HEAD(counter_list); static DEFINE_SPINLOCK(counter_list_lock); @@ -43,6 +56,69 @@ module_param_named(perms, quota_list_perms, uint, S_IRUGO | S_IWUSR); module_param_named(uid, quota_list_uid, uint, S_IRUGO | S_IWUSR); module_param_named(gid, quota_list_gid, uint, S_IRUGO | S_IWUSR); + +#ifdef CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG +static void quota2_log(unsigned int hooknum, + const struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + const char *prefix) +{ + ulog_packet_msg_t *pm; + struct sk_buff *log_skb; + size_t size; + struct nlmsghdr *nlh; + + if (!qlog_nl_event) + return; + + size = NLMSG_SPACE(sizeof(*pm)); + size = max(size, (size_t)NLMSG_GOODSIZE); + log_skb = alloc_skb(size, GFP_ATOMIC); + if (!log_skb) { + pr_err("xt_quota2: cannot alloc skb for logging\n"); + return; + } + + /* NLMSG_PUT() uses "goto nlmsg_failure" */ + nlh = NLMSG_PUT(log_skb, /*pid*/0, /*seq*/0, qlog_nl_event, + sizeof(*pm)); + pm = NLMSG_DATA(nlh); + if (skb->tstamp.tv64 == 0) + __net_timestamp((struct sk_buff *)skb); + pm->data_len = 0; + pm->hook = hooknum; + if (prefix != NULL) + strlcpy(pm->prefix, prefix, sizeof(pm->prefix)); + else + *(pm->prefix) = '\0'; + if (in) + strlcpy(pm->indev_name, in->name, sizeof(pm->indev_name)); + else + pm->indev_name[0] = '\0'; + + if (out) + strlcpy(pm->outdev_name, out->name, sizeof(pm->outdev_name)); + else + pm->outdev_name[0] = '\0'; + + NETLINK_CB(log_skb).dst_group = 1; + pr_debug("throwing 1 packets to netlink group 1\n"); + netlink_broadcast(nflognl, log_skb, 0, 1, GFP_ATOMIC); + +nlmsg_failure: /* Used within NLMSG_PUT() */ + pr_debug("xt_quota2: error during NLMSG_PUT\n"); +} +#else +static void quota2_log(unsigned int hooknum, + const struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + const char *prefix) +{ +} +#endif /* if+else CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG */ + static int quota_proc_read(char *page, char **start, off_t offset, int count, int *eof, void *data) { @@ -226,6 +302,14 @@ quota_mt2(const struct sk_buff *skb, struct xt_action_param *par) e->quota -= (q->flags & XT_QUOTA_PACKET) ? 1 : skb->len; ret = !ret; } else { + /* We are transitioning, log that fact. */ + if (e->quota) { + quota2_log(par->hooknum, + skb, + par->in, + par->out, + q->name); + } /* we do not allow even small packets from now on */ e->quota = 0; } @@ -262,6 +346,14 @@ static int __init quota_mt2_init(void) int ret; pr_debug("xt_quota2: init()"); +#ifdef CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG + nflognl = netlink_kernel_create(&init_net, + NETLINK_NFLOG, 1, NULL, + NULL, THIS_MODULE); + if (!nflognl) + return -ENOMEM; +#endif + proc_xt_quota = proc_mkdir("xt_quota", init_net.proc_net); if (proc_xt_quota == NULL) return -EACCES; -- cgit v1.2.3 From c477e60b6689d36121f7cabaea449c4014705078 Mon Sep 17 00:00:00 2001 From: JP Abgrall Date: Fri, 15 Jul 2011 22:27:28 -0700 Subject: netfilter: qtaguid: add tag delete command, expand stats output. * Add a new ctrl command to delete stored data. d [] The uid will default to the running process's. The accounting tag can be 0, in which case all counters and socket tags associated with the uid will be cleared. * Simplify the ctrl command handling at the expense of duplicate code. This should make it easier to maintain. * /proc/net/xt_qtaguid/stats now returns more stats idx iface acct_tag_hex uid_tag_int {rx,tx}_{bytes,packets} {rx,tx}_{tcp,udp,other}_{bytes,packets} the {rx,tx}_{bytes,packets} are the totals. * re-tagging will now allow changing the uid. Change-Id: I9594621543cefeab557caa3d68a22a3eb320466d Signed-off-by: JP Abgrall --- net/netfilter/xt_qtaguid.c | 414 +++++++++++++++++++++++++++++++-------------- 1 file changed, 291 insertions(+), 123 deletions(-) diff --git a/net/netfilter/xt_qtaguid.c b/net/netfilter/xt_qtaguid.c index 49ed432d793..0aa33daabed 100644 --- a/net/netfilter/xt_qtaguid.c +++ b/net/netfilter/xt_qtaguid.c @@ -8,7 +8,8 @@ * published by the Free Software Foundation. */ -/* TODO: support ipv6 for iface_stat */ +/* TODO: support ipv6 for iface_stat. + * Currently if an iface is only v6 it will not have stats collected. */ #include #include @@ -96,9 +97,6 @@ struct tag_stat { struct proc_dir_entry *proc_ptr; }; -static LIST_HEAD(iface_stat_list); -static DEFINE_SPINLOCK(iface_stat_list_lock); - struct iface_stat { struct list_head list; char *ifname; @@ -113,9 +111,8 @@ struct iface_stat { spinlock_t tag_stat_list_lock; }; - -static struct rb_root sock_tag_tree = RB_ROOT; -static DEFINE_SPINLOCK(sock_tag_list_lock); +static LIST_HEAD(iface_stat_list); +static DEFINE_SPINLOCK(iface_stat_list_lock); /* * Track tag that this socket is transferring data for, and not necesseraly @@ -128,6 +125,9 @@ struct sock_tag { tag_t tag; }; +static struct rb_root sock_tag_tree = RB_ROOT; +static DEFINE_SPINLOCK(sock_tag_list_lock); + static bool qtaguid_mt(const struct sk_buff *skb, struct xt_action_param *par); /*----------------------------------------------*/ @@ -181,6 +181,14 @@ static inline uint64_t dc_sum_bytes(struct data_counters *counters, + counters->bpc[direction][IFS_PROTO_OTHER].bytes; } +static inline uint64_t dc_sum_packets(struct data_counters *counters, + enum ifs_tx_rx direction) +{ + return counters->bpc[direction][IFS_TCP].packets + + counters->bpc[direction][IFS_UDP].packets + + counters->bpc[direction][IFS_PROTO_OTHER].packets; +} + static struct tag_stat *tag_stat_tree_search(struct rb_root *root, tag_t tag) { struct rb_node *node = root->rb_node; @@ -397,12 +405,11 @@ void iface_stat_create(const struct net_device *net_dev) return; } - new_iface = kmalloc(sizeof(*new_iface), GFP_KERNEL); + new_iface = kzalloc(sizeof(*new_iface), GFP_KERNEL); if (new_iface == NULL) { pr_err("iface_stat: create(): failed to alloc iface_stat\n"); return; } - memset(new_iface, 0, sizeof(*new_iface)); new_iface->ifname = kstrdup(ifname, GFP_KERNEL); if (new_iface->ifname == NULL) { pr_err("iface_stat: create(): failed to alloc ifname\n"); @@ -531,12 +538,11 @@ static struct tag_stat *create_if_tag_stat(struct iface_stat *iface_entry, pr_debug("iface_stat: create_if_tag_stat(): ife=%p tag=0x%llx" " (uid=%d)\n", iface_entry, tag, get_uid_from_tag(tag)); - new_tag_stat_entry = kmalloc(sizeof(*new_tag_stat_entry), GFP_ATOMIC); + new_tag_stat_entry = kzalloc(sizeof(*new_tag_stat_entry), GFP_ATOMIC); if (!new_tag_stat_entry) { pr_err("iface_stat: failed to alloc new tag entry\n"); goto done; } - memset(new_tag_stat_entry, 0, sizeof(*new_tag_stat_entry)); new_tag_stat_entry->tag = tag; tag_stat_tree_insert(new_tag_stat_entry, &iface_entry->tag_stat_tree); done: @@ -852,7 +858,7 @@ static bool qtaguid_mt(const struct sk_buff *skb, struct xt_action_param *par) pr_debug("xt_qtaguid[%d]: leaving (sk?sk->sk_socket)=%p\n", par->hooknum, sk ? sk->sk_socket : NULL); - res = (info->match ^ info->invert) == 0; + res = (info->match ^ info->invert) == 0; goto put_sock_ret_res; } else if (info->match & info->invert & XT_QTAGUID_SOCKET) { res = false; @@ -922,7 +928,7 @@ static int qtaguid_ctrl_proc_read(char *page, char **num_items_returned, struct rb_node *node; int item_index = 0; - pr_debug("xt_qtaguid:proc ctrl page=%p off=%ld char_count=%d *eof=%d\n", + pr_debug("xt_qtaguid: proc ctrl page=%p off=%ld char_count=%d *eof=%d\n", page, items_to_skip, char_count, *eof); if (*eof) @@ -934,7 +940,7 @@ static int qtaguid_ctrl_proc_read(char *page, char **num_items_returned, node = rb_next(node)) { if (item_index++ < items_to_skip) continue; - sock_tag_entry = rb_entry(node, struct sock_tag, node); + sock_tag_entry = rb_entry(node, struct sock_tag, node); uid = get_uid_from_tag(sock_tag_entry->tag); pr_debug("xt_qtaguid: proc_read(): sk=%p tag=0x%llx (uid=%d)\n", sock_tag_entry->sk, @@ -957,7 +963,103 @@ static int qtaguid_ctrl_proc_read(char *page, char **num_items_returned, return outp - page; } -static int qtaguid_ctrl_parse(const char *input, int count) +/* Delete socket tags, and stat tags associated with a given + * accouting tag and uid. */ +static int ctrl_cmd_delete(const char *input) +{ + char cmd; + uid_t uid = 0; + uid_t entry_uid; + tag_t acct_tag = 0; + tag_t tag; + int res, argc; + unsigned long flags, flags2; + struct iface_stat *iface_entry; + struct rb_node *node; + struct sock_tag *st_entry; + struct tag_stat *ts_entry; + + pr_debug("xt_qtaguid: ctrl_delete(%s): entered\n", input); + argc = sscanf(input, "%c %llu %u", &cmd, &acct_tag, &uid); + pr_debug("xt_qtaguid: ctrl_delete(%s): argc=%d cmd=%c " + "acct_tag=0x%llx uid=%u\n", input, argc, cmd, + acct_tag, uid); + if (argc < 2) { + res = -EINVAL; + goto err; + } + if (!valid_atag(acct_tag)) { + pr_info("xt_qtaguid: ctrl_delete(%s): invalid tag\n", input); + res = -EINVAL; + goto err; + } + if (argc < 3) + uid = current_fsuid(); + + /* TODO: check that the uid == current_fsuid() + * except for special uid/gid. */ + + spin_lock_irqsave(&sock_tag_list_lock, flags); + node = rb_first(&sock_tag_tree); + while (node) { + st_entry = rb_entry(node, struct sock_tag, node); + entry_uid = get_uid_from_tag(st_entry->tag); + node = rb_next(node); + if (entry_uid != uid) + continue; + + if (!acct_tag || st_entry->tag == tag) { + pr_debug("xt_qtaguid: ctrl_delete(): " + "erase sk=%p tag=0x%llx (uid=%d)\n", + st_entry->sk, + st_entry->tag, + entry_uid); + rb_erase(&ts_entry->node, &sock_tag_tree); + kfree(st_entry); + } + } + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + + /* If acct_tag is 0, then all entries belonging to uid are + * erased. */ + tag = combine_atag_with_uid(acct_tag, uid); + spin_lock_irqsave(&iface_stat_list_lock, flags); + list_for_each_entry(iface_entry, &iface_stat_list, list) { + + spin_lock_irqsave(&iface_entry->tag_stat_list_lock, flags2); + node = rb_first(&iface_entry->tag_stat_tree); + while (node) { + ts_entry = rb_entry(node, struct tag_stat, node); + entry_uid = get_uid_from_tag(ts_entry->tag); + node = rb_next(node); + if (entry_uid != uid) + continue; + if (!acct_tag || ts_entry->tag == tag) { + pr_debug("xt_qtaguid: ctrl_delete(): erase " + "%s 0x%llx %u\n", + iface_entry->ifname, + get_atag_from_tag(ts_entry->tag), + entry_uid); + rb_erase(&ts_entry->node, + &iface_entry->tag_stat_tree); + kfree(ts_entry); + } + } + spin_unlock_irqrestore(&iface_entry->tag_stat_list_lock, + flags2); + + } + spin_unlock_irqrestore(&iface_stat_list_lock, flags); + + res = 0; + +err: + pr_debug("xt_qtaguid: ctrl_delete(%s) res=%d\n", input, res); + return res; +} + + +static int ctrl_cmd_tag(const char *input) { char cmd; int sock_fd = 0; @@ -968,117 +1070,139 @@ static int qtaguid_ctrl_parse(const char *input, int count) struct sock_tag *sock_tag_entry; unsigned long flags; - pr_debug("xt_qtaguid: ctrl(%s): entered\n", input); /* Unassigned args will get defaulted later. */ - /* TODO: get acct_tag_str, keep a list of available tags for the - * uid, use num as acct_tag. */ argc = sscanf(input, "%c %d %llu %u", &cmd, &sock_fd, &acct_tag, &uid); - pr_debug("xt_qtaguid: ctrl(%s): argc=%d cmd=%c sock_fd=%d " - "acct_tag=0x%llx uid=%u\n", input, argc, cmd, sock_fd, - acct_tag, uid); + pr_debug("xt_qtaguid: ctrl_tag(%s): argc=%d cmd=%c sock_fd=%d " + "acct_tag=0x%llx uid=%u\n", input, argc, cmd, sock_fd, + acct_tag, uid); + if (argc < 2) { + res = -EINVAL; + goto err; + } + el_socket = sockfd_lookup(sock_fd, &res); + if (!el_socket) { + pr_info("xt_qtaguid: ctrl_tag(%s): failed to lookup" + " sock_fd=%d err=%d\n", input, sock_fd, res); + goto err; + } + if (argc < 3) { + acct_tag = 0; + } else if (!valid_atag(acct_tag)) { + pr_info("xt_qtaguid: ctrl_tag(%s): invalid tag\n", input); + res = -EINVAL; + goto err; + } + if (argc < 4) + uid = current_fsuid(); - /* Collect params for commands */ - switch (cmd) { - case 't': - case 'u': - if (argc < 2) { - res = -EINVAL; - goto err; - } - el_socket = sockfd_lookup(sock_fd, &res); - if (!el_socket) { - pr_info("xt_qtaguid: ctrl(%s): failed to lookup" - " sock_fd=%d err=%d\n", input, sock_fd, res); + spin_lock_irqsave(&sock_tag_list_lock, flags); + sock_tag_entry = get_sock_stat_nl(el_socket->sk); + if (sock_tag_entry) { + /* TODO: check that the uid == current_fsuid() + * except for special uid/gid. */ + sock_tag_entry->tag = combine_atag_with_uid(acct_tag, + uid); + } else { + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + sock_tag_entry = kzalloc(sizeof(*sock_tag_entry), + GFP_KERNEL); + if (!sock_tag_entry) { + res = -ENOMEM; goto err; } + sock_tag_entry->sk = el_socket->sk; + /* TODO: check that uid==current_fsuid() except + * for special uid/gid. */ + sock_tag_entry->tag = combine_atag_with_uid(acct_tag, + uid); spin_lock_irqsave(&sock_tag_list_lock, flags); - /* TODO: optim: pass in the current_fsuid() to do lookups - * as look ups will always be initiated form the same uid. */ - sock_tag_entry = get_sock_stat_nl(el_socket->sk); - if (!sock_tag_entry) - spin_unlock_irqrestore(&sock_tag_list_lock, flags); - /* HERE: The lock is held if there was a matching sock tag entry */ - break; - default: + sock_tag_tree_insert(sock_tag_entry, &sock_tag_tree); + } + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + + pr_debug("xt_qtaguid: tag: sock_tag_entry->sk=%p " + "...->tag=0x%llx (uid=%u)\n", + sock_tag_entry->sk, sock_tag_entry->tag, + get_uid_from_tag(sock_tag_entry->tag)); + res = 0; + +err: + pr_debug("xt_qtaguid: ctrl_tag(%s) res=%d\n", input, res); + return res; +} + + +static int ctrl_cmd_untag(const char *input) +{ + char cmd; + int sock_fd = 0; + struct socket *el_socket; + int res, argc; + struct sock_tag *sock_tag_entry; + unsigned long flags; + + pr_debug("xt_qtaguid: ctrl_untag(%s): entered\n", input); + argc = sscanf(input, "%c %d", &cmd, &sock_fd); + pr_debug("xt_qtaguid: ctrl_untag(%s): argc=%d cmd=%c sock_fd=%d\n", + input, argc, cmd, sock_fd); + if (argc < 2) { res = -EINVAL; goto err; } - /* HERE: The lock is held if there was a matching sock tag entry */ + el_socket = sockfd_lookup(sock_fd, &res); + if (!el_socket) { + pr_info("xt_qtaguid: ctrl_untag(%s): failed to lookup" + " sock_fd=%d err=%d\n", input, sock_fd, res); + goto err; + } + spin_lock_irqsave(&sock_tag_list_lock, flags); + sock_tag_entry = get_sock_stat_nl(el_socket->sk); + if (!sock_tag_entry) { + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + res = -EINVAL; + goto err; + } + + /* TODO: check that the uid==current_fsuid() + * except for special uid/gid. */ + rb_erase(&sock_tag_entry->node, &sock_tag_tree); + spin_unlock_irqrestore(&sock_tag_list_lock, flags); + kfree(sock_tag_entry); + + res = 0; +err: + pr_debug("xt_qtaguid: ctrl_untag(%s): res=%d\n", input, res); + return res; +} - /* Process commands */ +static int qtaguid_ctrl_parse(const char *input, int count) +{ + char cmd; + int res; + + pr_debug("xt_qtaguid: ctrl(%s): entered\n", input); + cmd = input[0]; + /* Collect params for commands */ switch (cmd) { + case 'd': + res = ctrl_cmd_delete(input); + break; case 't': - if (argc < 2) { - res = -EINVAL; - /* HERE: The lock is held if there was a matching sock - * tag entry */ - goto err_unlock; - } - if (argc < 3) { - acct_tag = 0; - } else if (!valid_atag(acct_tag)) { - res = -EINVAL; - /* HERE: The lock is held if there was a matching sock - * tag entry */ - goto err_unlock; - } - if (argc < 4) - uid = current_fsuid(); - if (!sock_tag_entry) { - /* HERE: There is no lock held because there was no - * sock tag entry */ - sock_tag_entry = kmalloc(sizeof(*sock_tag_entry), - GFP_KERNEL); - if (!sock_tag_entry) { - res = -ENOMEM; - goto err; - } - memset(sock_tag_entry, 0, sizeof(*sock_tag_entry)); - sock_tag_entry->sk = el_socket->sk; - /* TODO: check that uid==current_fsuid() except - * for special uid/gid. */ - sock_tag_entry->tag = combine_atag_with_uid(acct_tag, - uid); - spin_lock_irqsave(&sock_tag_list_lock, flags); - sock_tag_tree_insert(sock_tag_entry, &sock_tag_tree); - } else { - /* HERE: The lock is held because there is a matching - * sock tag entry */ - /* Just update the acct_tag portion. */ - uid_t orig_uid = get_uid_from_tag(sock_tag_entry->tag); - sock_tag_entry->tag = combine_atag_with_uid(acct_tag, - orig_uid); - } - spin_unlock_irqrestore(&sock_tag_list_lock, flags); - pr_debug("xt_qtaguid: tag: sock_tag_entry->sk=%p " - "...->tag=0x%llx (uid=%u)\n", - sock_tag_entry->sk, sock_tag_entry->tag, - get_uid_from_tag(sock_tag_entry->tag)); + res = ctrl_cmd_tag(input); break; case 'u': - if (!sock_tag_entry) { - res = -EINVAL; - goto err; - } - /* TODO: check that the uid==current_fsuid() - * except for special uid/gid. */ - rb_erase(&sock_tag_entry->node, &sock_tag_tree); - spin_unlock_irqrestore(&sock_tag_list_lock, flags); - kfree(sock_tag_entry); + res = ctrl_cmd_untag(input); break; - } - - /* All of the input has been processed */ - res = count; - goto ok; -err_unlock: - if (sock_tag_entry) - spin_unlock_irqrestore(&sock_tag_list_lock, flags); + default: + res = -EINVAL; + goto err; + } + if (!res) + res = count; err: -ok: pr_debug("xt_qtaguid: ctrl(%s): res=%d\n", input, res); return res; } @@ -1099,6 +1223,57 @@ static int qtaguid_ctrl_proc_write(struct file *file, const char __user *buffer, return qtaguid_ctrl_parse(input_buf, count); } +static int print_stats_line(char *outp, int char_count, int item_index, + char *ifname, tag_t tag, + struct data_counters *counters) +{ + int len; + if (!item_index) + len = snprintf(outp, char_count, + "idx iface acct_tag_hex uid_tag_int " + "rx_bytes rx_packets " + "tx_bytes tx_packets " + "rx_tcp_packets rx_tcp_bytes " + "rx_udp_packets rx_udp_bytes " + "rx_other_packets rx_other_bytes " + "tx_tcp_packets tx_tcp_bytes " + "tx_udp_packets tx_udp_bytes " + "tx_other_packets tx_other_bytes\n"); + else + len = snprintf(outp, char_count, + "%d %s 0x%llx %u " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu " + "%llu %llu\n", + item_index, + ifname, + get_atag_from_tag(tag), + get_uid_from_tag(tag), + dc_sum_bytes(counters, IFS_RX), + dc_sum_packets(counters, IFS_RX), + dc_sum_bytes(counters, IFS_TX), + dc_sum_packets(counters, IFS_TX), + counters->bpc[IFS_RX][IFS_TCP].bytes, + counters->bpc[IFS_RX][IFS_TCP].packets, + counters->bpc[IFS_RX][IFS_UDP].bytes, + counters->bpc[IFS_RX][IFS_UDP].packets, + counters->bpc[IFS_RX][IFS_PROTO_OTHER].bytes, + counters->bpc[IFS_RX][IFS_PROTO_OTHER].packets, + counters->bpc[IFS_TX][IFS_TCP].bytes, + counters->bpc[IFS_TX][IFS_TCP].packets, + counters->bpc[IFS_TX][IFS_UDP].bytes, + counters->bpc[IFS_TX][IFS_UDP].packets, + counters->bpc[IFS_TX][IFS_PROTO_OTHER].bytes, + counters->bpc[IFS_TX][IFS_PROTO_OTHER].packets); + return len; +} + + /* * Procfs reader to get all tag stats using style "1)" as described in * fs/proc/generic.c @@ -1126,9 +1301,8 @@ static int qtaguid_stats_proc_read(char *page, char **num_items_returned, if (!items_to_skip) { /* The idx is there to help debug when things go belly up. */ - len = snprintf(outp, char_count, - "idx iface acct_tag_hex uid_tag_int rx_bytes " - "tx_bytes\n"); + len = print_stats_line(outp, char_count, /*index*/0, NULL, + make_tag_from_uid(0), NULL); /* Don't advance the outp unless the whole line was printed */ if (len >= char_count) { *outp = '\0'; @@ -1137,7 +1311,6 @@ static int qtaguid_stats_proc_read(char *page, char **num_items_returned, outp += len; char_count -= len; } - spin_lock_irqsave(&iface_stat_list_lock, flags); list_for_each_entry(iface_entry, &iface_stat_list, list) { struct rb_node *node; @@ -1145,26 +1318,21 @@ static int qtaguid_stats_proc_read(char *page, char **num_items_returned, for (node = rb_first(&iface_entry->tag_stat_tree); node; node = rb_next(node)) { - ts_entry = rb_entry(node, struct tag_stat, node); + ts_entry = rb_entry(node, struct tag_stat, node); if (item_index++ < items_to_skip) continue; - len = snprintf(outp, char_count, - "%d %s 0x%llx %u %llu %llu\n", - item_index, - iface_entry->ifname, - get_atag_from_tag(ts_entry->tag), - get_uid_from_tag(ts_entry->tag), - dc_sum_bytes(&ts_entry->counters, - IFS_RX), - dc_sum_bytes(&ts_entry->counters, - IFS_TX)); + len = print_stats_line(outp, char_count, + item_index, + iface_entry->ifname, + ts_entry->tag, + &ts_entry->counters); if (len >= char_count) { + *outp = '\0'; spin_unlock_irqrestore( &iface_entry->tag_stat_list_lock, flags2); spin_unlock_irqrestore( &iface_stat_list_lock, flags); - *outp = '\0'; return outp - page; } outp += len; -- cgit v1.2.3 From 0b893f0f37736c1e26655f04d51706dfba417171 Mon Sep 17 00:00:00 2001 From: JP Abgrall Date: Sun, 17 Jul 2011 16:07:23 -0700 Subject: netfilter: xt_qtaguid: add uid permission checks during ctrl/stats access * uid handling - Limit UID impersonation to processes with a gid in AID_NET_BW_ACCT. This affects socket tagging, and data removal. - Limit stats lookup to own uid or the process gid is in AID_NET_BW_STATS. This affects stats lookup. * allow pacifying the module Setting passive to Y/y will make the module return immediately on external stimulus. No more stats and silent success on ctrl writes. Mainly used when one suspects this module of misbehaving. Change-Id: I83990862d52a9b0922aca103a0f61375cddeb7c4 Signed-off-by: JP Abgrall --- include/linux/android_aid.h | 2 + net/netfilter/xt_qtaguid.c | 170 ++++++++++++++++++++++++++++++++------------ 2 files changed, 128 insertions(+), 44 deletions(-) diff --git a/include/linux/android_aid.h b/include/linux/android_aid.h index 7f16a14c0fe..0f904b3ba7f 100644 --- a/include/linux/android_aid.h +++ b/include/linux/android_aid.h @@ -22,5 +22,7 @@ #define AID_INET 3003 #define AID_NET_RAW 3004 #define AID_NET_ADMIN 3005 +#define AID_NET_BW_STATS 3006 /* read bandwidth statistics */ +#define AID_NET_BW_ACCT 3007 /* change bandwidth statistics accounting */ #endif diff --git a/net/netfilter/xt_qtaguid.c b/net/netfilter/xt_qtaguid.c index 0aa33daabed..3ef8753bc37 100644 --- a/net/netfilter/xt_qtaguid.c +++ b/net/netfilter/xt_qtaguid.c @@ -10,6 +10,7 @@ /* TODO: support ipv6 for iface_stat. * Currently if an iface is only v6 it will not have stats collected. */ +#define DEBUG #include #include @@ -29,6 +30,49 @@ ((1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_LOCAL_IN)) +static const char *module_procdirname = "xt_qtaguid"; +static struct proc_dir_entry *xt_qtaguid_procdir; + +static unsigned int proc_iface_perms = S_IRUGO; +module_param_named(iface_perms, proc_iface_perms, uint, S_IRUGO | S_IWUSR); + +static struct proc_dir_entry *xt_qtaguid_stats_file; +static unsigned int proc_stats_perms = S_IRUGO; +module_param_named(stats_perms, proc_stats_perms, uint, S_IRUGO | S_IWUSR); + +static struct proc_dir_entry *xt_qtaguid_ctrl_file; +#ifdef CONFIG_ANDROID_PARANOID_NETWORK +static unsigned int proc_ctrl_perms = S_IRUGO | S_IWUGO; +#else +static unsigned int proc_ctrl_perms = S_IRUGO | S_IWUSR; +#endif +module_param_named(ctrl_perms, proc_ctrl_perms, uint, S_IRUGO | S_IWUSR); + +#ifdef CONFIG_ANDROID_PARANOID_NETWORK +#include +static gid_t proc_stats_readall_gid = AID_NET_BW_STATS; +static gid_t proc_ctrl_write_gid = AID_NET_BW_ACCT; +#else +/* 0 means, don't limit anybody */ +static gid_t proc_stats_readall_gid; +static gid_t proc_ctrl_write_gid; +#endif +module_param_named(stats_readall_gid, proc_stats_readall_gid, uint, + S_IRUGO | S_IWUSR); +module_param_named(ctrl_write_gid, proc_ctrl_write_gid, uint, + S_IRUGO | S_IWUSR); + +/* After the kernel has initiallized this module, it is still possible + * to make it passive: + * - do not register it via iptables. + * the matching code will not be invoked. + * - set passive to 0 + * the iface stats handling will not be act on notifications. + * This is mostly usefull when a bug is suspected. + */ +static bool module_passive; +module_param_named(passive, module_passive, bool, S_IRUGO | S_IWUSR); + /*---------------------------------------------------------------------------*/ /* * Tags: @@ -429,15 +473,15 @@ void iface_stat_create(const struct net_device *net_dev) new_iface->proc_ptr = proc_entry; /* TODO: make root access only */ - create_proc_read_entry("tx_bytes", S_IRUGO, proc_entry, + create_proc_read_entry("tx_bytes", proc_iface_perms, proc_entry, read_proc_u64, &new_iface->tx_bytes); - create_proc_read_entry("rx_bytes", S_IRUGO, proc_entry, + create_proc_read_entry("rx_bytes", proc_iface_perms, proc_entry, read_proc_u64, &new_iface->rx_bytes); - create_proc_read_entry("tx_packets", S_IRUGO, proc_entry, + create_proc_read_entry("tx_packets", proc_iface_perms, proc_entry, read_proc_u64, &new_iface->tx_packets); - create_proc_read_entry("rx_packets", S_IRUGO, proc_entry, + create_proc_read_entry("rx_packets", proc_iface_perms, proc_entry, read_proc_u64, &new_iface->rx_packets); - create_proc_read_entry("active", S_IRUGO, proc_entry, + create_proc_read_entry("active", proc_iface_perms, proc_entry, read_proc_bool, &new_iface->active); pr_debug("iface_stat: create(): done entry=%p dev=%s ip=%pI4\n", @@ -655,6 +699,9 @@ static int iface_netdev_event_handler(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = ptr; + if (unlikely(module_passive)) + return NOTIFY_DONE; + pr_debug("iface_stat: netdev_event(): ev=0x%lx netdev=%p->name=%s\n", event, dev, dev ? dev->name : ""); @@ -682,6 +729,9 @@ static int iface_inetaddr_event_handler(struct notifier_block *nb, struct in_device *in_dev = ifa->ifa_dev; struct net_device *dev = in_dev->dev; + if (unlikely(module_passive)) + return NOTIFY_DONE; + pr_debug("iface_stat: inetaddr_event(): ev=0x%lx netdev=%p->name=%s\n", event, dev, dev ? dev->name : ""); @@ -817,8 +867,10 @@ static bool qtaguid_mt(const struct sk_buff *skb, struct xt_action_param *par) struct sock *sk; uid_t sock_uid; bool res; + pr_debug("xt_qtaguid[%d]: entered skb=%p par->in=%p/out=%p fam=%d\n", par->hooknum, skb, par->in, par->out, par->family); + if (skb == NULL) { res = (info->match ^ info->invert) == 0; goto ret_res; @@ -928,6 +980,11 @@ static int qtaguid_ctrl_proc_read(char *page, char **num_items_returned, struct rb_node *node; int item_index = 0; + if (unlikely(module_passive)) { + *eof = 1; + return 0; + } + pr_debug("xt_qtaguid: proc ctrl page=%p off=%ld char_count=%d *eof=%d\n", page, items_to_skip, char_count, *eof); @@ -963,6 +1020,20 @@ static int qtaguid_ctrl_proc_read(char *page, char **num_items_returned, return outp - page; } +int can_impersonate_uid(uid_t uid) +{ + return uid == current_fsuid() + || !proc_ctrl_write_gid + || in_egroup_p(proc_ctrl_write_gid); +} + +int can_read_other_uid_stats(uid_t uid) +{ + return uid == current_fsuid() + || !proc_ctrl_write_gid + || in_egroup_p(proc_stats_readall_gid); +} + /* Delete socket tags, and stat tags associated with a given * accouting tag and uid. */ static int ctrl_cmd_delete(const char *input) @@ -993,11 +1064,14 @@ static int ctrl_cmd_delete(const char *input) res = -EINVAL; goto err; } - if (argc < 3) + if (argc < 3) { uid = current_fsuid(); - - /* TODO: check that the uid == current_fsuid() - * except for special uid/gid. */ + } else if (!can_impersonate_uid(uid)) { + pr_info("xt_qtaguid: ctrl_delete(%s): insuficient priv\n", + input); + res = -EPERM; + goto err; + } spin_lock_irqsave(&sock_tag_list_lock, flags); node = rb_first(&sock_tag_tree); @@ -1092,14 +1166,18 @@ static int ctrl_cmd_tag(const char *input) res = -EINVAL; goto err; } - if (argc < 4) + if (argc < 4) { uid = current_fsuid(); + } else if (!can_impersonate_uid(uid)) { + pr_info("xt_qtaguid: ctrl_tag(%s): insuficient priv\n", + input); + res = -EPERM; + goto err; + } spin_lock_irqsave(&sock_tag_list_lock, flags); sock_tag_entry = get_sock_stat_nl(el_socket->sk); if (sock_tag_entry) { - /* TODO: check that the uid == current_fsuid() - * except for special uid/gid. */ sock_tag_entry->tag = combine_atag_with_uid(acct_tag, uid); } else { @@ -1111,8 +1189,6 @@ static int ctrl_cmd_tag(const char *input) goto err; } sock_tag_entry->sk = el_socket->sk; - /* TODO: check that uid==current_fsuid() except - * for special uid/gid. */ sock_tag_entry->tag = combine_atag_with_uid(acct_tag, uid); spin_lock_irqsave(&sock_tag_list_lock, flags); @@ -1162,9 +1238,8 @@ static int ctrl_cmd_untag(const char *input) res = -EINVAL; goto err; } - - /* TODO: check that the uid==current_fsuid() - * except for special uid/gid. */ + /* The socket already belongs to the current process + * so it can do whatever it wants to it. */ rb_erase(&sock_tag_entry->node, &sock_tag_tree); spin_unlock_irqrestore(&sock_tag_list_lock, flags); kfree(sock_tag_entry); @@ -1213,6 +1288,9 @@ static int qtaguid_ctrl_proc_write(struct file *file, const char __user *buffer, { char input_buf[MAX_QTAGUID_CTRL_INPUT_LEN]; + if (unlikely(module_passive)) + return count; + if (count >= MAX_QTAGUID_CTRL_INPUT_LEN) return -EINVAL; @@ -1228,18 +1306,25 @@ static int print_stats_line(char *outp, int char_count, int item_index, struct data_counters *counters) { int len; - if (!item_index) + if (!item_index) { len = snprintf(outp, char_count, - "idx iface acct_tag_hex uid_tag_int " - "rx_bytes rx_packets " - "tx_bytes tx_packets " - "rx_tcp_packets rx_tcp_bytes " - "rx_udp_packets rx_udp_bytes " - "rx_other_packets rx_other_bytes " - "tx_tcp_packets tx_tcp_bytes " - "tx_udp_packets tx_udp_bytes " - "tx_other_packets tx_other_bytes\n"); - else + "idx iface acct_tag_hex uid_tag_int " + "rx_bytes rx_packets " + "tx_bytes tx_packets " + "rx_tcp_packets rx_tcp_bytes " + "rx_udp_packets rx_udp_bytes " + "rx_other_packets rx_other_bytes " + "tx_tcp_packets tx_tcp_bytes " + "tx_udp_packets tx_udp_bytes " + "tx_other_packets tx_other_bytes\n"); + } else { + uid_t stat_uid = get_uid_from_tag(tag); + if (!can_read_other_uid_stats(stat_uid)) { + pr_debug("xt_qtaguid: insufficient priv for stat line:" + "%s 0x%llx %u\n", + ifname, get_atag_from_tag(tag), stat_uid); + return 0; + } len = snprintf(outp, char_count, "%d %s 0x%llx %u " "%llu %llu " @@ -1253,7 +1338,7 @@ static int print_stats_line(char *outp, int char_count, int item_index, item_index, ifname, get_atag_from_tag(tag), - get_uid_from_tag(tag), + stat_uid, dc_sum_bytes(counters, IFS_RX), dc_sum_packets(counters, IFS_RX), dc_sum_bytes(counters, IFS_TX), @@ -1270,6 +1355,7 @@ static int print_stats_line(char *outp, int char_count, int item_index, counters->bpc[IFS_TX][IFS_UDP].packets, counters->bpc[IFS_TX][IFS_PROTO_OTHER].bytes, counters->bpc[IFS_TX][IFS_PROTO_OTHER].packets); + } return len; } @@ -1290,7 +1376,10 @@ static int qtaguid_stats_proc_read(char *page, char **num_items_returned, struct tag_stat *ts_entry; int item_index = 0; - /* TODO: make root access only */ + if (unlikely(module_passive)) { + *eof = 1; + return 0; + } pr_debug("xt_qtaguid:proc stats page=%p *num_items_returned=%p off=%ld " "char_count=%d *eof=%d\n", page, *num_items_returned, @@ -1335,9 +1424,11 @@ static int qtaguid_stats_proc_read(char *page, char **num_items_returned, &iface_stat_list_lock, flags); return outp - page; } - outp += len; - char_count -= len; - (*num_items_returned)++; + if (len) { + outp += len; + char_count -= len; + (*num_items_returned)++; + } } spin_unlock_irqrestore(&iface_entry->tag_stat_list_lock, flags2); @@ -1349,11 +1440,6 @@ static int qtaguid_stats_proc_read(char *page, char **num_items_returned, } /*------------------------------------------*/ -static const char *module_procdirname = "xt_qtaguid"; -static struct proc_dir_entry *xt_qtaguid_procdir; -static struct proc_dir_entry *xt_qtaguid_ctrl_file; -static struct proc_dir_entry *xt_qtaguid_stats_file; - static int __init qtaguid_proc_register(struct proc_dir_entry **res_procdir) { int ret; @@ -1364,7 +1450,7 @@ static int __init qtaguid_proc_register(struct proc_dir_entry **res_procdir) goto no_dir; } - xt_qtaguid_ctrl_file = create_proc_entry("ctrl", 0666, + xt_qtaguid_ctrl_file = create_proc_entry("ctrl", proc_ctrl_perms, *res_procdir); if (!xt_qtaguid_ctrl_file) { pr_err("xt_qtaguid: failed to create xt_qtaguid/ctrl " @@ -1375,7 +1461,7 @@ static int __init qtaguid_proc_register(struct proc_dir_entry **res_procdir) xt_qtaguid_ctrl_file->read_proc = qtaguid_ctrl_proc_read; xt_qtaguid_ctrl_file->write_proc = qtaguid_ctrl_proc_write; - xt_qtaguid_stats_file = create_proc_entry("stats", 0666, + xt_qtaguid_stats_file = create_proc_entry("stats", proc_stats_perms, *res_procdir); if (!xt_qtaguid_stats_file) { pr_err("xt_qtaguid: failed to create xt_qtaguid/stats " @@ -1383,10 +1469,6 @@ static int __init qtaguid_proc_register(struct proc_dir_entry **res_procdir) ret = -ENOMEM; goto no_stats_entry; } - /* - * TODO: add extra read_proc for full stats with protocol - * breakout - */ xt_qtaguid_stats_file->read_proc = qtaguid_stats_proc_read; /* * TODO: add support counter hacking -- cgit v1.2.3 From 8c59c45bc55f052d7d767d93c88a6f1666dcdecf Mon Sep 17 00:00:00 2001 From: JP Abgrall Date: Fri, 22 Jul 2011 10:34:22 -0700 Subject: netfilter: qtaguid: disable #define DEBUG This would cause log spam to the point of slowing down the system. Change-Id: I5655f0207935004b0198f43ad0d3c9ea25466e4e Signed-off-by: JP Abgrall --- net/netfilter/xt_qtaguid.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/netfilter/xt_qtaguid.c b/net/netfilter/xt_qtaguid.c index 3ef8753bc37..22552c9b81c 100644 --- a/net/netfilter/xt_qtaguid.c +++ b/net/netfilter/xt_qtaguid.c @@ -10,7 +10,6 @@ /* TODO: support ipv6 for iface_stat. * Currently if an iface is only v6 it will not have stats collected. */ -#define DEBUG #include #include -- cgit v1.2.3