linux/security/selinux/netnode.c
<<
>>
Prefs
   1/*
   2 * Network node table
   3 *
   4 * SELinux must keep a mapping of network nodes to labels/SIDs.  This
   5 * mapping is maintained as part of the normal policy but a fast cache is
   6 * needed to reduce the lookup overhead since most of these queries happen on
   7 * a per-packet basis.
   8 *
   9 * Author: Paul Moore <paul@paul-moore.com>
  10 *
  11 * This code is heavily based on the "netif" concept originally developed by
  12 * James Morris <jmorris@redhat.com>
  13 *   (see security/selinux/netif.c for more information)
  14 *
  15 */
  16
  17/*
  18 * (c) Copyright Hewlett-Packard Development Company, L.P., 2007
  19 *
  20 * This program is free software: you can redistribute it and/or modify
  21 * it under the terms of version 2 of the GNU General Public License as
  22 * published by the Free Software Foundation.
  23 *
  24 * This program is distributed in the hope that it will be useful,
  25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  27 * GNU General Public License for more details.
  28 *
  29 */
  30
  31#include <linux/types.h>
  32#include <linux/rcupdate.h>
  33#include <linux/list.h>
  34#include <linux/slab.h>
  35#include <linux/spinlock.h>
  36#include <linux/in.h>
  37#include <linux/in6.h>
  38#include <linux/ip.h>
  39#include <linux/ipv6.h>
  40#include <net/ip.h>
  41#include <net/ipv6.h>
  42
  43#include "netnode.h"
  44#include "objsec.h"
  45
  46#define SEL_NETNODE_HASH_SIZE       256
  47#define SEL_NETNODE_HASH_BKT_LIMIT   16
  48
  49struct sel_netnode_bkt {
  50        unsigned int size;
  51        struct list_head list;
  52};
  53
  54struct sel_netnode {
  55        struct netnode_security_struct nsec;
  56
  57        struct list_head list;
  58        struct rcu_head rcu;
  59};
  60
  61/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason
  62 * for this is that I suspect most users will not make heavy use of both
  63 * address families at the same time so one table will usually end up wasted,
  64 * if this becomes a problem we can always add a hash table for each address
  65 * family later */
  66
  67static LIST_HEAD(sel_netnode_list);
  68static DEFINE_SPINLOCK(sel_netnode_lock);
  69static struct sel_netnode_bkt sel_netnode_hash[SEL_NETNODE_HASH_SIZE];
  70
  71/**
  72 * sel_netnode_hashfn_ipv4 - IPv4 hashing function for the node table
  73 * @addr: IPv4 address
  74 *
  75 * Description:
  76 * This is the IPv4 hashing function for the node interface table, it returns
  77 * the bucket number for the given IP address.
  78 *
  79 */
  80static unsigned int sel_netnode_hashfn_ipv4(__be32 addr)
  81{
  82        /* at some point we should determine if the mismatch in byte order
  83         * affects the hash function dramatically */
  84        return (addr & (SEL_NETNODE_HASH_SIZE - 1));
  85}
  86
  87/**
  88 * sel_netnode_hashfn_ipv6 - IPv6 hashing function for the node table
  89 * @addr: IPv6 address
  90 *
  91 * Description:
  92 * This is the IPv6 hashing function for the node interface table, it returns
  93 * the bucket number for the given IP address.
  94 *
  95 */
  96static unsigned int sel_netnode_hashfn_ipv6(const struct in6_addr *addr)
  97{
  98        /* just hash the least significant 32 bits to keep things fast (they
  99         * are the most likely to be different anyway), we can revisit this
 100         * later if needed */
 101        return (addr->s6_addr32[3] & (SEL_NETNODE_HASH_SIZE - 1));
 102}
 103
 104/**
 105 * sel_netnode_find - Search for a node record
 106 * @addr: IP address
 107 * @family: address family
 108 *
 109 * Description:
 110 * Search the network node table and return the record matching @addr.  If an
 111 * entry can not be found in the table return NULL.
 112 *
 113 */
 114static struct sel_netnode *sel_netnode_find(const void *addr, u16 family)
 115{
 116        unsigned int idx;
 117        struct sel_netnode *node;
 118
 119        switch (family) {
 120        case PF_INET:
 121                idx = sel_netnode_hashfn_ipv4(*(__be32 *)addr);
 122                break;
 123        case PF_INET6:
 124                idx = sel_netnode_hashfn_ipv6(addr);
 125                break;
 126        default:
 127                BUG();
 128                return NULL;
 129        }
 130
 131        list_for_each_entry_rcu(node, &sel_netnode_hash[idx].list, list)
 132                if (node->nsec.family == family)
 133                        switch (family) {
 134                        case PF_INET:
 135                                if (node->nsec.addr.ipv4 == *(__be32 *)addr)
 136                                        return node;
 137                                break;
 138                        case PF_INET6:
 139                                if (ipv6_addr_equal(&node->nsec.addr.ipv6,
 140                                                    addr))
 141                                        return node;
 142                                break;
 143                        }
 144
 145        return NULL;
 146}
 147
 148/**
 149 * sel_netnode_insert - Insert a new node into the table
 150 * @node: the new node record
 151 *
 152 * Description:
 153 * Add a new node record to the network address hash table.
 154 *
 155 */
 156static void sel_netnode_insert(struct sel_netnode *node)
 157{
 158        unsigned int idx;
 159
 160        switch (node->nsec.family) {
 161        case PF_INET:
 162                idx = sel_netnode_hashfn_ipv4(node->nsec.addr.ipv4);
 163                break;
 164        case PF_INET6:
 165                idx = sel_netnode_hashfn_ipv6(&node->nsec.addr.ipv6);
 166                break;
 167        default:
 168                BUG();
 169        }
 170
 171        /* we need to impose a limit on the growth of the hash table so check
 172         * this bucket to make sure it is within the specified bounds */
 173        list_add_rcu(&node->list, &sel_netnode_hash[idx].list);
 174        if (sel_netnode_hash[idx].size == SEL_NETNODE_HASH_BKT_LIMIT) {
 175                struct sel_netnode *tail;
 176                tail = list_entry(
 177                        rcu_dereference_protected(sel_netnode_hash[idx].list.prev,
 178                                                  lockdep_is_held(&sel_netnode_lock)),
 179                        struct sel_netnode, list);
 180                list_del_rcu(&tail->list);
 181                kfree_rcu(tail, rcu);
 182        } else
 183                sel_netnode_hash[idx].size++;
 184}
 185
 186/**
 187 * sel_netnode_sid_slow - Lookup the SID of a network address using the policy
 188 * @addr: the IP address
 189 * @family: the address family
 190 * @sid: node SID
 191 *
 192 * Description:
 193 * This function determines the SID of a network address by quering the
 194 * security policy.  The result is added to the network address table to
 195 * speedup future queries.  Returns zero on success, negative values on
 196 * failure.
 197 *
 198 */
 199static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid)
 200{
 201        int ret = -ENOMEM;
 202        struct sel_netnode *node;
 203        struct sel_netnode *new = NULL;
 204
 205        spin_lock_bh(&sel_netnode_lock);
 206        node = sel_netnode_find(addr, family);
 207        if (node != NULL) {
 208                *sid = node->nsec.sid;
 209                spin_unlock_bh(&sel_netnode_lock);
 210                return 0;
 211        }
 212        new = kzalloc(sizeof(*new), GFP_ATOMIC);
 213        if (new == NULL)
 214                goto out;
 215        switch (family) {
 216        case PF_INET:
 217                ret = security_node_sid(PF_INET,
 218                                        addr, sizeof(struct in_addr), sid);
 219                new->nsec.addr.ipv4 = *(__be32 *)addr;
 220                break;
 221        case PF_INET6:
 222                ret = security_node_sid(PF_INET6,
 223                                        addr, sizeof(struct in6_addr), sid);
 224                new->nsec.addr.ipv6 = *(struct in6_addr *)addr;
 225                break;
 226        default:
 227                BUG();
 228        }
 229        if (ret != 0)
 230                goto out;
 231
 232        new->nsec.family = family;
 233        new->nsec.sid = *sid;
 234        sel_netnode_insert(new);
 235
 236out:
 237        spin_unlock_bh(&sel_netnode_lock);
 238        if (unlikely(ret)) {
 239                printk(KERN_WARNING
 240                       "SELinux: failure in sel_netnode_sid_slow(),"
 241                       " unable to determine network node label\n");
 242                kfree(new);
 243        }
 244        return ret;
 245}
 246
 247/**
 248 * sel_netnode_sid - Lookup the SID of a network address
 249 * @addr: the IP address
 250 * @family: the address family
 251 * @sid: node SID
 252 *
 253 * Description:
 254 * This function determines the SID of a network address using the fastest
 255 * method possible.  First the address table is queried, but if an entry
 256 * can't be found then the policy is queried and the result is added to the
 257 * table to speedup future queries.  Returns zero on success, negative values
 258 * on failure.
 259 *
 260 */
 261int sel_netnode_sid(void *addr, u16 family, u32 *sid)
 262{
 263        struct sel_netnode *node;
 264
 265        rcu_read_lock();
 266        node = sel_netnode_find(addr, family);
 267        if (node != NULL) {
 268                *sid = node->nsec.sid;
 269                rcu_read_unlock();
 270                return 0;
 271        }
 272        rcu_read_unlock();
 273
 274        return sel_netnode_sid_slow(addr, family, sid);
 275}
 276
 277/**
 278 * sel_netnode_flush - Flush the entire network address table
 279 *
 280 * Description:
 281 * Remove all entries from the network address table.
 282 *
 283 */
 284static void sel_netnode_flush(void)
 285{
 286        unsigned int idx;
 287        struct sel_netnode *node, *node_tmp;
 288
 289        spin_lock_bh(&sel_netnode_lock);
 290        for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++) {
 291                list_for_each_entry_safe(node, node_tmp,
 292                                         &sel_netnode_hash[idx].list, list) {
 293                                list_del_rcu(&node->list);
 294                                kfree_rcu(node, rcu);
 295                }
 296                sel_netnode_hash[idx].size = 0;
 297        }
 298        spin_unlock_bh(&sel_netnode_lock);
 299}
 300
 301static int sel_netnode_avc_callback(u32 event)
 302{
 303        if (event == AVC_CALLBACK_RESET) {
 304                sel_netnode_flush();
 305                synchronize_net();
 306        }
 307        return 0;
 308}
 309
 310static __init int sel_netnode_init(void)
 311{
 312        int iter;
 313        int ret;
 314
 315        if (!selinux_enabled)
 316                return 0;
 317
 318        for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) {
 319                INIT_LIST_HEAD(&sel_netnode_hash[iter].list);
 320                sel_netnode_hash[iter].size = 0;
 321        }
 322
 323        ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET);
 324        if (ret != 0)
 325                panic("avc_add_callback() failed, error %d\n", ret);
 326
 327        return ret;
 328}
 329
 330__initcall(sel_netnode_init);
 331
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.