2008-01-29 08:38:13 -05:00
/*
* 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>
2008-04-25 15:03:34 -04:00
# include "netnode.h"
2008-01-29 08:38:13 -05:00
# include "objsec.h"
# define SEL_NETNODE_HASH_SIZE 256
# define SEL_NETNODE_HASH_BKT_LIMIT 16
2008-04-25 15:03:34 -04:00
struct sel_netnode_bkt {
unsigned int size ;
struct list_head list ;
} ;
2008-01-29 08:38:13 -05:00
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 ) ;
2008-04-25 15:03:34 -04:00
static struct sel_netnode_bkt sel_netnode_hash [ SEL_NETNODE_HASH_SIZE ] ;
2008-01-29 08:38:13 -05:00
/**
* 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 .
*
*/
2008-04-25 15:03:34 -04:00
static unsigned int sel_netnode_hashfn_ipv4 ( __be32 addr )
2008-01-29 08:38:13 -05:00
{
/* 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 .
*
*/
2008-04-25 15:03:34 -04:00
static unsigned int sel_netnode_hashfn_ipv6 ( const struct in6_addr * addr )
2008-01-29 08:38:13 -05:00
{
/* 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 )
{
2008-04-25 15:03:34 -04:00
unsigned int idx ;
2008-01-29 08:38:13 -05:00
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 ( ) ;
}
2008-04-25 15:03:34 -04:00
list_for_each_entry_rcu ( node , & sel_netnode_hash [ idx ] . list , list )
2008-01-29 08:38:13 -05:00
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 :
2008-04-25 15:03:34 -04:00
* Add a new node record to the network address hash table .
2008-01-29 08:38:13 -05:00
*
*/
2008-04-25 15:03:34 -04:00
static void sel_netnode_insert ( struct sel_netnode * node )
2008-01-29 08:38:13 -05:00
{
2008-04-25 15:03:34 -04:00
unsigned int idx ;
2008-01-29 08:38:13 -05:00
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 ( ) ;
}
2008-04-25 15:03:34 -04:00
INIT_RCU_HEAD ( & node - > rcu ) ;
2008-01-29 08:38:13 -05:00
/* 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 */
2008-04-25 15:03:34 -04:00
list_add_rcu ( & node - > list , & sel_netnode_hash [ idx ] . list ) ;
if ( sel_netnode_hash [ idx ] . size = = SEL_NETNODE_HASH_BKT_LIMIT ) {
struct sel_netnode * tail ;
tail = list_entry (
rcu_dereference ( sel_netnode_hash [ idx ] . list . prev ) ,
struct sel_netnode , list ) ;
list_del_rcu ( & tail - > list ) ;
call_rcu ( & tail - > rcu , sel_netnode_free ) ;
} else
sel_netnode_hash [ idx ] . size + + ;
2008-01-29 08:38:13 -05:00
}
/**
* 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 )
{
2008-04-25 15:03:34 -04:00
int ret = - ENOMEM ;
2008-01-29 08:38:13 -05:00
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 ;
2008-04-25 15:03:34 -04:00
spin_unlock_bh ( & sel_netnode_lock ) ;
return 0 ;
2008-01-29 08:38:13 -05:00
}
new = kzalloc ( sizeof ( * new ) , GFP_ATOMIC ) ;
2008-04-25 15:03:34 -04:00
if ( new = = NULL )
2008-01-29 08:38:13 -05:00
goto out ;
switch ( family ) {
case PF_INET :
ret = security_node_sid ( PF_INET ,
2008-04-25 15:03:34 -04:00
addr , sizeof ( struct in_addr ) , sid ) ;
2008-01-29 08:38:13 -05:00
new - > nsec . addr . ipv4 = * ( __be32 * ) addr ;
break ;
case PF_INET6 :
ret = security_node_sid ( PF_INET6 ,
2008-04-25 15:03:34 -04:00
addr , sizeof ( struct in6_addr ) , sid ) ;
2008-01-29 08:38:13 -05:00
ipv6_addr_copy ( & new - > nsec . addr . ipv6 , addr ) ;
break ;
default :
BUG ( ) ;
}
if ( ret ! = 0 )
goto out ;
2008-04-25 15:03:34 -04:00
2008-01-29 08:38:13 -05:00
new - > nsec . family = family ;
2008-04-25 15:03:34 -04:00
new - > nsec . sid = * sid ;
sel_netnode_insert ( new ) ;
2008-01-29 08:38:13 -05:00
out :
spin_unlock_bh ( & sel_netnode_lock ) ;
2008-01-29 08:51:16 -05:00
if ( unlikely ( ret ) ) {
printk ( KERN_WARNING
" SELinux: failure in sel_netnode_sid_slow(), "
" unable to determine network node label \n " ) ;
2008-01-29 08:38:13 -05:00
kfree ( new ) ;
2008-01-29 08:51:16 -05:00
}
2008-01-29 08:38:13 -05:00
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 )
{
2008-04-25 15:03:34 -04:00
unsigned int idx ;
struct sel_netnode * node , * node_tmp ;
2008-01-29 08:38:13 -05:00
spin_lock_bh ( & sel_netnode_lock ) ;
2008-04-25 15:03:34 -04:00
for ( idx = 0 ; idx < SEL_NETNODE_HASH_SIZE ; idx + + ) {
list_for_each_entry_safe ( node , node_tmp ,
& sel_netnode_hash [ idx ] . list , list ) {
list_del_rcu ( & node - > list ) ;
call_rcu ( & node - > rcu , sel_netnode_free ) ;
}
sel_netnode_hash [ idx ] . size = 0 ;
}
2008-01-29 08:38:13 -05:00
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 ;
2008-04-25 15:03:34 -04:00
for ( iter = 0 ; iter < SEL_NETNODE_HASH_SIZE ; iter + + ) {
INIT_LIST_HEAD ( & sel_netnode_hash [ iter ] . list ) ;
sel_netnode_hash [ iter ] . size = 0 ;
}
2008-01-29 08:38:13 -05:00
ret = avc_add_callback ( sel_netnode_avc_callback , AVC_CALLBACK_RESET ,
2008-04-18 17:38:25 -04:00
SECSID_NULL , SECSID_NULL , SECCLASS_NULL , 0 ) ;
2008-01-29 08:38:13 -05:00
if ( ret ! = 0 )
panic ( " avc_add_callback() failed, error %d \n " , ret ) ;
return ret ;
}
__initcall ( sel_netnode_init ) ;