diff options
author | JP Abgrall <jpa@google.com> | 2011-12-13 01:12:13 +0800 |
---|---|---|
committer | Andy Green <andy.green@linaro.org> | 2011-12-26 15:43:59 +0800 |
commit | 833de6232fac80ea0c87d72c224a973d926902e7 (patch) | |
tree | 169beef35026fec5584d5695029918c151218b9f /net | |
parent | abfb6b0e0ac52fd60da3eceaa6c1a503c18833a7 (diff) |
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 <jpa@google.com>
Diffstat (limited to 'net')
-rw-r--r-- | net/netfilter/xt_qtaguid.c | 173 |
1 files changed, 128 insertions, 45 deletions
diff --git a/net/netfilter/xt_qtaguid.c b/net/netfilter/xt_qtaguid.c index 22bf687ec6b..fbaaf5bd5a5 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 <linux/file.h> #include <linux/inetdevice.h> @@ -25,6 +26,49 @@ #include <linux/netfilter/xt_socket.h> +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 <linux/android_aid.h> +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: @@ -425,15 +469,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", @@ -651,6 +695,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 : ""); @@ -678,6 +725,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 : ""); @@ -790,8 +840,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\n", - par->hooknum, skb, par->in, par->out); + + 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; @@ -897,6 +949,11 @@ static int qtaguid_ctrl_proc_read(char *page, char **start, off_t off, 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); @@ -930,6 +987,20 @@ static int qtaguid_ctrl_proc_read(char *page, char **start, off_t off, return out - 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) @@ -960,11 +1031,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); @@ -1059,14 +1133,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 { @@ -1078,8 +1156,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); @@ -1131,8 +1207,8 @@ static int ctrl_cmd_untag(const char *input) } /* HERE: The lock is held if there was a matching sock tag entry */ - /* 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); @@ -1181,6 +1257,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; @@ -1196,18 +1275,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 " @@ -1221,7 +1307,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), @@ -1238,6 +1324,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; } @@ -1258,7 +1345,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, @@ -1303,9 +1393,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; - (*(int *)num_items_returned)++; + if (len) { + outp += len; + char_count -= len; + (*num_items_returned)++; + } } spin_unlock_irqrestore(&iface_entry->tag_stat_list_lock, flags2); @@ -1317,11 +1409,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; @@ -1332,7 +1419,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 " @@ -1343,7 +1430,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 " @@ -1351,10 +1438,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 |