2008-04-14 09:09:53 +04:00
/*
* Network port table
*
* SELinux must keep a mapping of network ports to labels / SIDs . This
* mapping is maintained as part of the normal policy but a fast cache is
* needed to reduce the lookup overhead .
*
* 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 . , 2008
*
* 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 "netport.h"
# include "objsec.h"
# define SEL_NETPORT_HASH_SIZE 256
# define SEL_NETPORT_HASH_BKT_LIMIT 16
struct sel_netport_bkt {
int size ;
struct list_head list ;
} ;
struct sel_netport {
struct netport_security_struct psec ;
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_netport_list ) ;
static DEFINE_SPINLOCK ( sel_netport_lock ) ;
static struct sel_netport_bkt sel_netport_hash [ SEL_NETPORT_HASH_SIZE ] ;
/**
* sel_netport_free - Frees a port 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 port entry can be
* released safely .
*
*/
static void sel_netport_free ( struct rcu_head * p )
{
struct sel_netport * port = container_of ( p , struct sel_netport , rcu ) ;
kfree ( port ) ;
}
/**
* sel_netport_hashfn - Hashing function for the port table
* @ pnum : port number
*
* Description :
* This is the hashing function for the port table , it returns the bucket
* number for the given port .
*
*/
static unsigned int sel_netport_hashfn ( u16 pnum )
{
return ( pnum & ( SEL_NETPORT_HASH_SIZE - 1 ) ) ;
}
/**
* sel_netport_find - Search for a port record
* @ protocol : protocol
* @ port : pnum
*
* Description :
* Search the network port table and return the matching record . If an entry
* can not be found in the table return NULL .
*
*/
static struct sel_netport * sel_netport_find ( u8 protocol , u16 pnum )
{
unsigned int idx ;
struct sel_netport * port ;
idx = sel_netport_hashfn ( pnum ) ;
list_for_each_entry_rcu ( port , & sel_netport_hash [ idx ] . list , list )
2008-04-25 23:03:39 +04:00
if ( port - > psec . port = = pnum & & port - > psec . protocol = = protocol )
2008-04-14 09:09:53 +04:00
return port ;
return NULL ;
}
/**
* sel_netport_insert - Insert a new port into the table
* @ port : the new port record
*
* Description :
2008-04-25 23:03:39 +04:00
* Add a new port record to the network address hash table .
2008-04-14 09:09:53 +04:00
*
*/
2008-04-25 23:03:39 +04:00
static void sel_netport_insert ( struct sel_netport * port )
2008-04-14 09:09:53 +04:00
{
unsigned int 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 */
idx = sel_netport_hashfn ( port - > psec . port ) ;
list_add_rcu ( & port - > list , & sel_netport_hash [ idx ] . list ) ;
if ( sel_netport_hash [ idx ] . size = = SEL_NETPORT_HASH_BKT_LIMIT ) {
struct sel_netport * tail ;
2008-04-25 23:03:39 +04:00
tail = list_entry (
rcu_dereference ( sel_netport_hash [ idx ] . list . prev ) ,
struct sel_netport , list ) ;
list_del_rcu ( & tail - > list ) ;
2008-04-14 09:09:53 +04:00
call_rcu ( & tail - > rcu , sel_netport_free ) ;
} else
sel_netport_hash [ idx ] . size + + ;
}
/**
* sel_netport_sid_slow - Lookup the SID of a network address using the policy
* @ protocol : protocol
* @ pnum : port
* @ sid : port SID
*
* Description :
* This function determines the SID of a network port by quering the security
* policy . The result is added to the network port table to speedup future
* queries . Returns zero on success , negative values on failure .
*
*/
static int sel_netport_sid_slow ( u8 protocol , u16 pnum , u32 * sid )
{
2008-04-25 23:03:39 +04:00
int ret = - ENOMEM ;
2008-04-14 09:09:53 +04:00
struct sel_netport * port ;
struct sel_netport * new = NULL ;
spin_lock_bh ( & sel_netport_lock ) ;
port = sel_netport_find ( protocol , pnum ) ;
if ( port ! = NULL ) {
* sid = port - > psec . sid ;
2008-04-25 23:03:39 +04:00
spin_unlock_bh ( & sel_netport_lock ) ;
return 0 ;
2008-04-14 09:09:53 +04:00
}
new = kzalloc ( sizeof ( * new ) , GFP_ATOMIC ) ;
2008-04-25 23:03:39 +04:00
if ( new = = NULL )
2008-04-14 09:09:53 +04:00
goto out ;
2008-04-25 23:03:39 +04:00
ret = security_port_sid ( protocol , pnum , sid ) ;
2008-04-14 09:09:53 +04:00
if ( ret ! = 0 )
goto out ;
2008-04-25 23:03:39 +04:00
2008-04-14 09:09:53 +04:00
new - > psec . port = pnum ;
new - > psec . protocol = protocol ;
2008-04-25 23:03:39 +04:00
new - > psec . sid = * sid ;
sel_netport_insert ( new ) ;
2008-04-14 09:09:53 +04:00
out :
spin_unlock_bh ( & sel_netport_lock ) ;
if ( unlikely ( ret ) ) {
printk ( KERN_WARNING
" SELinux: failure in sel_netport_sid_slow(), "
" unable to determine network port label \n " ) ;
kfree ( new ) ;
}
return ret ;
}
/**
* sel_netport_sid - Lookup the SID of a network port
* @ protocol : protocol
* @ pnum : port
* @ sid : port SID
*
* Description :
* This function determines the SID of a network port using the fastest method
* possible . First the port 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_netport_sid ( u8 protocol , u16 pnum , u32 * sid )
{
struct sel_netport * port ;
rcu_read_lock ( ) ;
port = sel_netport_find ( protocol , pnum ) ;
if ( port ! = NULL ) {
* sid = port - > psec . sid ;
rcu_read_unlock ( ) ;
return 0 ;
}
rcu_read_unlock ( ) ;
return sel_netport_sid_slow ( protocol , pnum , sid ) ;
}
/**
* sel_netport_flush - Flush the entire network port table
*
* Description :
* Remove all entries from the network address table .
*
*/
static void sel_netport_flush ( void )
{
unsigned int idx ;
2008-04-25 23:03:39 +04:00
struct sel_netport * port , * port_tmp ;
2008-04-14 09:09:53 +04:00
spin_lock_bh ( & sel_netport_lock ) ;
for ( idx = 0 ; idx < SEL_NETPORT_HASH_SIZE ; idx + + ) {
2008-04-25 23:03:39 +04:00
list_for_each_entry_safe ( port , port_tmp ,
& sel_netport_hash [ idx ] . list , list ) {
2008-04-14 09:09:53 +04:00
list_del_rcu ( & port - > list ) ;
call_rcu ( & port - > rcu , sel_netport_free ) ;
}
sel_netport_hash [ idx ] . size = 0 ;
}
spin_unlock_bh ( & sel_netport_lock ) ;
}
static int sel_netport_avc_callback ( u32 event , u32 ssid , u32 tsid ,
u16 class , u32 perms , u32 * retained )
{
if ( event = = AVC_CALLBACK_RESET ) {
sel_netport_flush ( ) ;
synchronize_net ( ) ;
}
return 0 ;
}
static __init int sel_netport_init ( void )
{
int iter ;
int ret ;
if ( ! selinux_enabled )
return 0 ;
for ( iter = 0 ; iter < SEL_NETPORT_HASH_SIZE ; iter + + ) {
INIT_LIST_HEAD ( & sel_netport_hash [ iter ] . list ) ;
sel_netport_hash [ iter ] . size = 0 ;
}
ret = avc_add_callback ( sel_netport_avc_callback , AVC_CALLBACK_RESET ,
2008-05-14 19:27:45 +04:00
SECSID_NULL , SECSID_NULL , SECCLASS_NULL , 0 ) ;
2008-04-14 09:09:53 +04:00
if ( ret ! = 0 )
panic ( " avc_add_callback() failed, error %d \n " , ret ) ;
return ret ;
}
__initcall ( sel_netport_init ) ;