SELinux: Add a network node caching mechanism similar to the sel_netif_*() functions
Paul Moore [Tue, 29 Jan 2008 13:38:13 +0000 (08:38 -0500)]
This patch adds a SELinux IP address/node SID caching mechanism similar to the
sel_netif_*() functions.  The node SID queries in the SELinux hooks files are
also modified to take advantage of this new functionality.  In addition, remove
the address length information from the sk_buff parsing routines as it is
redundant since we already have the address family.

Signed-off-by: Paul Moore <paul.moore@hp.com>
Signed-off-by: James Morris <jmorris@namei.org>

security/selinux/Makefile
security/selinux/hooks.c
security/selinux/include/netnode.h [new file with mode: 0644]
security/selinux/include/objsec.h
security/selinux/netnode.c [new file with mode: 0644]

index dc3502e..00afd85 100644 (file)
@@ -4,7 +4,14 @@
 
 obj-$(CONFIG_SECURITY_SELINUX) := selinux.o ss/
 
-selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o exports.o
+selinux-y := avc.o \
+            hooks.o \
+            selinuxfs.o \
+            netlink.o \
+            nlmsgtab.o \
+            netif.o \
+            netnode.o \
+            exports.o
 
 selinux-$(CONFIG_SECURITY_NETWORK_XFRM) += xfrm.o
 
index 1a1fa3f..4bca0af 100644 (file)
@@ -76,6 +76,7 @@
 #include "avc.h"
 #include "objsec.h"
 #include "netif.h"
+#include "netnode.h"
 #include "xfrm.h"
 #include "netlabel.h"
 
@@ -3395,7 +3396,7 @@ out:
 #endif /* IPV6 */
 
 static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad,
-                            char **addrp, int *len, int src, u8 *proto)
+                            char **addrp, int src, u8 *proto)
 {
        int ret = 0;
 
@@ -3404,7 +3405,6 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad,
                ret = selinux_parse_skb_ipv4(skb, ad, proto);
                if (ret || !addrp)
                        break;
-               *len = 4;
                *addrp = (char *)(src ? &ad->u.net.v4info.saddr :
                                        &ad->u.net.v4info.daddr);
                break;
@@ -3414,7 +3414,6 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad,
                ret = selinux_parse_skb_ipv6(skb, ad, proto);
                if (ret || !addrp)
                        break;
-               *len = 16;
                *addrp = (char *)(src ? &ad->u.net.v6info.saddr :
                                        &ad->u.net.v6info.daddr);
                break;
@@ -3614,7 +3613,7 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in
                        break;
                }
                
-               err = security_node_sid(family, addrp, addrlen, &sid);
+               err = sel_netnode_sid(addrp, family, &sid);
                if (err)
                        goto out;
                
@@ -3826,7 +3825,8 @@ static int selinux_socket_unix_may_send(struct socket *sock,
 }
 
 static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb,
-               struct avc_audit_data *ad, u16 family, char *addrp, int len)
+                                      struct avc_audit_data *ad,
+                                      u16 family, char *addrp)
 {
        int err = 0;
        u32 netif_perm, node_perm, node_sid, if_sid, recv_perm = 0;
@@ -3886,7 +3886,7 @@ static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb,
        if (err)
                goto out;
        
-       err = security_node_sid(family, addrp, len, &node_sid);
+       err = sel_netnode_sid(addrp, family, &node_sid);
        if (err)
                goto out;
        
@@ -3915,7 +3915,7 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
 {
        u16 family;
        char *addrp;
-       int len, err = 0;
+       int err = 0;
        struct avc_audit_data ad;
        struct sk_security_struct *sksec = sk->sk_security;
 
@@ -3931,13 +3931,12 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
        ad.u.net.netif = skb->iif;
        ad.u.net.family = family;
 
-       err = selinux_parse_skb(skb, &ad, &addrp, &len, 1, NULL);
+       err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL);
        if (err)
                goto out;
 
        if (selinux_compat_net)
-               err = selinux_sock_rcv_skb_compat(sk, skb, &ad, family,
-                                                 addrp, len);
+               err = selinux_sock_rcv_skb_compat(sk, skb, &ad, family, addrp);
        else
                err = avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET,
                                   PACKET__RECV, &ad);
@@ -4158,9 +4157,11 @@ out:
 
 #ifdef CONFIG_NETFILTER
 
-static int selinux_ip_postroute_last_compat(struct sock *sk, struct net_device *dev,
+static int selinux_ip_postroute_last_compat(struct sock *sk,
+                                           struct net_device *dev,
                                            struct avc_audit_data *ad,
-                                           u16 family, char *addrp, int len)
+                                           u16 family,
+                                           char *addrp)
 {
        int err = 0;
        u32 netif_perm, node_perm, node_sid, if_sid, send_perm = 0;
@@ -4211,7 +4212,7 @@ static int selinux_ip_postroute_last_compat(struct sock *sk, struct net_device *
        if (err)
                goto out;
                
-       err = security_node_sid(family, addrp, len, &node_sid);
+       err = sel_netnode_sid(addrp, family, &node_sid);
        if (err)
                goto out;
        
@@ -4245,7 +4246,7 @@ static unsigned int selinux_ip_postroute_last(unsigned int hooknum,
                                               u16 family)
 {
        char *addrp;
-       int len, err = 0;
+       int err = 0;
        struct sock *sk;
        struct avc_audit_data ad;
        struct net_device *dev = (struct net_device *)out;
@@ -4262,13 +4263,13 @@ static unsigned int selinux_ip_postroute_last(unsigned int hooknum,
        ad.u.net.netif = dev->ifindex;
        ad.u.net.family = family;
 
-       err = selinux_parse_skb(skb, &ad, &addrp, &len, 0, &proto);
+       err = selinux_parse_skb(skb, &ad, &addrp, 0, &proto);
        if (err)
                goto out;
 
        if (selinux_compat_net)
                err = selinux_ip_postroute_last_compat(sk, dev, &ad,
-                                                      family, addrp, len);
+                                                      family, addrp);
        else
                err = avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET,
                                   PACKET__SEND, &ad);
diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h
new file mode 100644 (file)
index 0000000..1b94450
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Network node table
+ *
+ * SELinux must keep a mapping of network nodes to labels/SIDs.  This
+ * mapping is maintained as part of the normal policy but a fast cache is
+ * needed to reduce the lookup overhead since most of these queries happen on
+ * a per-packet basis.
+ *
+ * Author: Paul Moore <paul.moore@hp.com>
+ *
+ */
+
+/*
+ * (c) Copyright Hewlett-Packard Development Company, L.P., 2007
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#ifndef _SELINUX_NETNODE_H
+#define _SELINUX_NETNODE_H
+
+int sel_netnode_sid(void *addr, u16 family, u32 *sid);
+
+#endif
index 2d0a92e..95fb5ec 100644 (file)
@@ -100,6 +100,15 @@ struct netif_security_struct {
        u32 sid;                        /* SID for this interface */
 };
 
+struct netnode_security_struct {
+       union {
+               __be32 ipv4;            /* IPv4 node address */
+               struct in6_addr ipv6;   /* IPv6 node address */
+       } addr;
+       u32 sid;                        /* SID for this node */
+       u16 family;                     /* address family */
+};
+
 struct sk_security_struct {
        struct sock *sk;                /* back pointer to sk object */
        u32 sid;                        /* SID of this object */
diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c
new file mode 100644 (file)
index 0000000..49c5277
--- /dev/null
@@ -0,0 +1,350 @@
+/*
+ * Network node table
+ *
+ * SELinux must keep a mapping of network nodes to labels/SIDs.  This
+ * mapping is maintained as part of the normal policy but a fast cache is
+ * needed to reduce the lookup overhead since most of these queries happen on
+ * a per-packet basis.
+ *
+ * Author: Paul Moore <paul.moore@hp.com>
+ *
+ * This code is heavily based on the "netif" concept originally developed by
+ * James Morris <jmorris@redhat.com>
+ *   (see security/selinux/netif.c for more information)
+ *
+ */
+
+/*
+ * (c) Copyright Hewlett-Packard Development Company, L.P., 2007
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/rcupdate.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <asm/bug.h>
+
+#include "objsec.h"
+
+#define SEL_NETNODE_HASH_SIZE       256
+#define SEL_NETNODE_HASH_BKT_LIMIT   16
+
+struct sel_netnode {
+       struct netnode_security_struct nsec;
+
+       struct list_head list;
+       struct rcu_head rcu;
+};
+
+/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason
+ * for this is that I suspect most users will not make heavy use of both
+ * address families at the same time so one table will usually end up wasted,
+ * if this becomes a problem we can always add a hash table for each address
+ * family later */
+
+static LIST_HEAD(sel_netnode_list);
+static DEFINE_SPINLOCK(sel_netnode_lock);
+static struct list_head sel_netnode_hash[SEL_NETNODE_HASH_SIZE];
+
+/**
+ * sel_netnode_free - Frees a node entry
+ * @p: the entry's RCU field
+ *
+ * Description:
+ * This function is designed to be used as a callback to the call_rcu()
+ * function so that memory allocated to a hash table node entry can be
+ * released safely.
+ *
+ */
+static void sel_netnode_free(struct rcu_head *p)
+{
+       struct sel_netnode *node = container_of(p, struct sel_netnode, rcu);
+       kfree(node);
+}
+
+/**
+ * sel_netnode_hashfn_ipv4 - IPv4 hashing function for the node table
+ * @addr: IPv4 address
+ *
+ * Description:
+ * This is the IPv4 hashing function for the node interface table, it returns
+ * the bucket number for the given IP address.
+ *
+ */
+static u32 sel_netnode_hashfn_ipv4(__be32 addr)
+{
+       /* at some point we should determine if the mismatch in byte order
+        * affects the hash function dramatically */
+       return (addr & (SEL_NETNODE_HASH_SIZE - 1));
+}
+
+/**
+ * sel_netnode_hashfn_ipv6 - IPv6 hashing function for the node table
+ * @addr: IPv6 address
+ *
+ * Description:
+ * This is the IPv6 hashing function for the node interface table, it returns
+ * the bucket number for the given IP address.
+ *
+ */
+static u32 sel_netnode_hashfn_ipv6(const struct in6_addr *addr)
+{
+       /* just hash the least significant 32 bits to keep things fast (they
+        * are the most likely to be different anyway), we can revisit this
+        * later if needed */
+       return (addr->s6_addr32[3] & (SEL_NETNODE_HASH_SIZE - 1));
+}
+
+/**
+ * sel_netnode_find - Search for a node record
+ * @addr: IP address
+ * @family: address family
+ *
+ * Description:
+ * Search the network node table and return the record matching @addr.  If an
+ * entry can not be found in the table return NULL.
+ *
+ */
+static struct sel_netnode *sel_netnode_find(const void *addr, u16 family)
+{
+       u32 idx;
+       struct sel_netnode *node;
+
+       switch (family) {
+       case PF_INET:
+               idx = sel_netnode_hashfn_ipv4(*(__be32 *)addr);
+               break;
+       case PF_INET6:
+               idx = sel_netnode_hashfn_ipv6(addr);
+               break;
+       default:
+               BUG();
+       }
+
+       list_for_each_entry_rcu(node, &sel_netnode_hash[idx], list)
+               if (node->nsec.family == family)
+                       switch (family) {
+                       case PF_INET:
+                               if (node->nsec.addr.ipv4 == *(__be32 *)addr)
+                                       return node;
+                               break;
+                       case PF_INET6:
+                               if (ipv6_addr_equal(&node->nsec.addr.ipv6,
+                                                   addr))
+                                       return node;
+                               break;
+                       }
+
+       return NULL;
+}
+
+/**
+ * sel_netnode_insert - Insert a new node into the table
+ * @node: the new node record
+ *
+ * Description:
+ * Add a new node record to the network address hash table.  Returns zero on
+ * success, negative values on failure.
+ *
+ */
+static int sel_netnode_insert(struct sel_netnode *node)
+{
+       u32 idx;
+       u32 count = 0;
+       struct sel_netnode *iter;
+
+       switch (node->nsec.family) {
+       case PF_INET:
+               idx = sel_netnode_hashfn_ipv4(node->nsec.addr.ipv4);
+               break;
+       case PF_INET6:
+               idx = sel_netnode_hashfn_ipv6(&node->nsec.addr.ipv6);
+               break;
+       default:
+               BUG();
+       }
+       list_add_rcu(&node->list, &sel_netnode_hash[idx]);
+
+       /* we need to impose a limit on the growth of the hash table so check
+        * this bucket to make sure it is within the specified bounds */
+       list_for_each_entry(iter, &sel_netnode_hash[idx], list)
+               if (++count > SEL_NETNODE_HASH_BKT_LIMIT) {
+                       list_del_rcu(&iter->list);
+                       call_rcu(&iter->rcu, sel_netnode_free);
+                       break;
+               }
+
+       return 0;
+}
+
+/**
+ * sel_netnode_destroy - Remove a node record from the table
+ * @node: the existing node record
+ *
+ * Description:
+ * Remove an existing node record from the network address table.
+ *
+ */
+static void sel_netnode_destroy(struct sel_netnode *node)
+{
+       list_del_rcu(&node->list);
+       call_rcu(&node->rcu, sel_netnode_free);
+}
+
+/**
+ * sel_netnode_sid_slow - Lookup the SID of a network address using the policy
+ * @addr: the IP address
+ * @family: the address family
+ * @sid: node SID
+ *
+ * Description:
+ * This function determines the SID of a network address by quering the
+ * security policy.  The result is added to the network address table to
+ * speedup future queries.  Returns zero on success, negative values on
+ * failure.
+ *
+ */
+static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid)
+{
+       int ret;
+       struct sel_netnode *node;
+       struct sel_netnode *new = NULL;
+
+       spin_lock_bh(&sel_netnode_lock);
+       node = sel_netnode_find(addr, family);
+       if (node != NULL) {
+               *sid = node->nsec.sid;
+               ret = 0;
+               goto out;
+       }
+       new = kzalloc(sizeof(*new), GFP_ATOMIC);
+       if (new == NULL) {
+               ret = -ENOMEM;
+               goto out;
+       }
+       switch (family) {
+       case PF_INET:
+               ret = security_node_sid(PF_INET,
+                                       addr, sizeof(struct in_addr),
+                                       &new->nsec.sid);
+               new->nsec.addr.ipv4 = *(__be32 *)addr;
+               break;
+       case PF_INET6:
+               ret = security_node_sid(PF_INET6,
+                                       addr, sizeof(struct in6_addr),
+                                       &new->nsec.sid);
+               ipv6_addr_copy(&new->nsec.addr.ipv6, addr);
+               break;
+       default:
+               BUG();
+       }
+       if (ret != 0)
+               goto out;
+       new->nsec.family = family;
+       ret = sel_netnode_insert(new);
+       if (ret != 0)
+               goto out;
+       *sid = new->nsec.sid;
+
+out:
+       spin_unlock_bh(&sel_netnode_lock);
+       if (ret != 0)
+               kfree(new);
+       return ret;
+}
+
+/**
+ * sel_netnode_sid - Lookup the SID of a network address
+ * @addr: the IP address
+ * @family: the address family
+ * @sid: node SID
+ *
+ * Description:
+ * This function determines the SID of a network address using the fastest
+ * method possible.  First the address table is queried, but if an entry
+ * can't be found then the policy is queried and the result is added to the
+ * table to speedup future queries.  Returns zero on success, negative values
+ * on failure.
+ *
+ */
+int sel_netnode_sid(void *addr, u16 family, u32 *sid)
+{
+       struct sel_netnode *node;
+
+       rcu_read_lock();
+       node = sel_netnode_find(addr, family);
+       if (node != NULL) {
+               *sid = node->nsec.sid;
+               rcu_read_unlock();
+               return 0;
+       }
+       rcu_read_unlock();
+
+       return sel_netnode_sid_slow(addr, family, sid);
+}
+
+/**
+ * sel_netnode_flush - Flush the entire network address table
+ *
+ * Description:
+ * Remove all entries from the network address table.
+ *
+ */
+static void sel_netnode_flush(void)
+{
+       u32 idx;
+       struct sel_netnode *node;
+
+       spin_lock_bh(&sel_netnode_lock);
+       for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++)
+               list_for_each_entry(node, &sel_netnode_hash[idx], list)
+                       sel_netnode_destroy(node);
+       spin_unlock_bh(&sel_netnode_lock);
+}
+
+static int sel_netnode_avc_callback(u32 event, u32 ssid, u32 tsid,
+                                   u16 class, u32 perms, u32 *retained)
+{
+       if (event == AVC_CALLBACK_RESET) {
+               sel_netnode_flush();
+               synchronize_net();
+       }
+       return 0;
+}
+
+static __init int sel_netnode_init(void)
+{
+       int iter;
+       int ret;
+
+       if (!selinux_enabled)
+               return 0;
+
+       for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++)
+               INIT_LIST_HEAD(&sel_netnode_hash[iter]);
+
+       ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET,
+                              SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0);
+       if (ret != 0)
+               panic("avc_add_callback() failed, error %d\n", ret);
+
+       return ret;
+}
+
+__initcall(sel_netnode_init);