2016-06-27 22:02:46 +03:00
/*
* CALIPSO - Common Architecture Label IPv6 Security Option
*
* This is an implementation of the CALIPSO protocol as specified in
* RFC 5570.
*
* Authors : Paul Moore < paul . moore @ hp . com >
* Huw Davies < huw @ codeweavers . com >
*
*/
/* (c) Copyright Hewlett-Packard Development Company, L.P., 2006, 2008
* ( c ) Copyright Huw Davies < huw @ codeweavers . com > , 2015
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , see < http : //www.gnu.org/licenses/>.
*
*/
# include <linux/init.h>
# include <linux/types.h>
# include <linux/rcupdate.h>
# include <linux/list.h>
# include <linux/spinlock.h>
# include <linux/string.h>
# include <linux/jhash.h>
# include <linux/audit.h>
# include <linux/slab.h>
# include <net/ip.h>
# include <net/icmp.h>
# include <net/tcp.h>
# include <net/netlabel.h>
# include <net/calipso.h>
# include <linux/atomic.h>
# include <linux/bug.h>
# include <asm/unaligned.h>
2016-06-27 22:02:51 +03:00
# include <linux/crc-ccitt.h>
/* Maximium size of the calipso option including
* the two - byte TLV header .
*/
# define CALIPSO_OPT_LEN_MAX (2 + 252)
/* Size of the minimum calipso option including
* the two - byte TLV header .
*/
# define CALIPSO_HDR_LEN (2 + 8)
/* Maximium size of the calipso option including
* the two - byte TLV header and upto 3 bytes of
* leading pad and 7 bytes of trailing pad .
*/
# define CALIPSO_OPT_LEN_MAX_WITH_PAD (3 + CALIPSO_OPT_LEN_MAX + 7)
2016-06-27 22:06:15 +03:00
/* Maximium size of u32 aligned buffer required to hold calipso
* option . Max of 3 initial pad bytes starting from buffer + 3.
* i . e . the worst case is when the previous tlv finishes on 4 n + 3.
*/
# define CALIPSO_MAX_BUFFER (6 + CALIPSO_OPT_LEN_MAX)
2016-06-27 22:02:46 +03:00
/* List of available DOI definitions */
static DEFINE_SPINLOCK ( calipso_doi_list_lock ) ;
static LIST_HEAD ( calipso_doi_list ) ;
/* DOI List Functions
*/
/**
* calipso_doi_search - Searches for a DOI definition
* @ doi : the DOI to search for
*
* Description :
* Search the DOI definition list for a DOI definition with a DOI value that
* matches @ doi . The caller is responsible for calling rcu_read_ [ un ] lock ( ) .
* Returns a pointer to the DOI definition on success and NULL on failure .
*/
static struct calipso_doi * calipso_doi_search ( u32 doi )
{
struct calipso_doi * iter ;
list_for_each_entry_rcu ( iter , & calipso_doi_list , list )
if ( iter - > doi = = doi & & atomic_read ( & iter - > refcount ) )
return iter ;
return NULL ;
}
/**
* calipso_doi_add - Add a new DOI to the CALIPSO protocol engine
* @ doi_def : the DOI structure
* @ audit_info : NetLabel audit information
*
* Description :
* The caller defines a new DOI for use by the CALIPSO engine and calls this
* function to add it to the list of acceptable domains . The caller must
* ensure that the mapping table specified in @ doi_def - > map meets all of the
* requirements of the mapping type ( see calipso . h for details ) . Returns
* zero on success and non - zero on failure .
*
*/
static int calipso_doi_add ( struct calipso_doi * doi_def ,
struct netlbl_audit * audit_info )
{
int ret_val = - EINVAL ;
u32 doi ;
u32 doi_type ;
struct audit_buffer * audit_buf ;
doi = doi_def - > doi ;
doi_type = doi_def - > type ;
if ( doi_def - > doi = = CALIPSO_DOI_UNKNOWN )
goto doi_add_return ;
atomic_set ( & doi_def - > refcount , 1 ) ;
spin_lock ( & calipso_doi_list_lock ) ;
if ( calipso_doi_search ( doi_def - > doi ) ) {
spin_unlock ( & calipso_doi_list_lock ) ;
ret_val = - EEXIST ;
goto doi_add_return ;
}
list_add_tail_rcu ( & doi_def - > list , & calipso_doi_list ) ;
spin_unlock ( & calipso_doi_list_lock ) ;
ret_val = 0 ;
doi_add_return :
audit_buf = netlbl_audit_start ( AUDIT_MAC_CALIPSO_ADD , audit_info ) ;
if ( audit_buf ) {
const char * type_str ;
switch ( doi_type ) {
case CALIPSO_MAP_PASS :
type_str = " pass " ;
break ;
default :
type_str = " (unknown) " ;
}
audit_log_format ( audit_buf ,
" calipso_doi=%u calipso_type=%s res=%u " ,
doi , type_str , ret_val = = 0 ? 1 : 0 ) ;
audit_log_end ( audit_buf ) ;
}
return ret_val ;
}
/**
* calipso_doi_free - Frees a DOI definition
* @ doi_def : the DOI definition
*
* Description :
* This function frees all of the memory associated with a DOI definition .
*
*/
static void calipso_doi_free ( struct calipso_doi * doi_def )
{
kfree ( doi_def ) ;
}
2016-06-27 22:02:47 +03:00
/**
* calipso_doi_free_rcu - Frees a DOI definition via the RCU pointer
* @ entry : the entry ' s RCU field
*
* Description :
* This function is designed to be used as a callback to the call_rcu ( )
* function so that the memory allocated to the DOI definition can be released
* safely .
*
*/
static void calipso_doi_free_rcu ( struct rcu_head * entry )
{
struct calipso_doi * doi_def ;
doi_def = container_of ( entry , struct calipso_doi , rcu ) ;
calipso_doi_free ( doi_def ) ;
}
2016-06-27 22:02:49 +03:00
/**
* calipso_doi_remove - Remove an existing DOI from the CALIPSO protocol engine
* @ doi : the DOI value
* @ audit_secid : the LSM secid to use in the audit message
*
* Description :
* Removes a DOI definition from the CALIPSO engine . The NetLabel routines will
* be called to release their own LSM domain mappings as well as our own
* domain list . Returns zero on success and negative values on failure .
*
*/
static int calipso_doi_remove ( u32 doi , struct netlbl_audit * audit_info )
{
int ret_val ;
struct calipso_doi * doi_def ;
struct audit_buffer * audit_buf ;
spin_lock ( & calipso_doi_list_lock ) ;
doi_def = calipso_doi_search ( doi ) ;
if ( ! doi_def ) {
spin_unlock ( & calipso_doi_list_lock ) ;
ret_val = - ENOENT ;
goto doi_remove_return ;
}
if ( ! atomic_dec_and_test ( & doi_def - > refcount ) ) {
spin_unlock ( & calipso_doi_list_lock ) ;
ret_val = - EBUSY ;
goto doi_remove_return ;
}
list_del_rcu ( & doi_def - > list ) ;
spin_unlock ( & calipso_doi_list_lock ) ;
call_rcu ( & doi_def - > rcu , calipso_doi_free_rcu ) ;
ret_val = 0 ;
doi_remove_return :
audit_buf = netlbl_audit_start ( AUDIT_MAC_CALIPSO_DEL , audit_info ) ;
if ( audit_buf ) {
audit_log_format ( audit_buf ,
" calipso_doi=%u res=%u " ,
doi , ret_val = = 0 ? 1 : 0 ) ;
audit_log_end ( audit_buf ) ;
}
return ret_val ;
}
2016-06-27 22:02:47 +03:00
/**
* calipso_doi_getdef - Returns a reference to a valid DOI definition
* @ doi : the DOI value
*
* Description :
* Searches for a valid DOI definition and if one is found it is returned to
* the caller . Otherwise NULL is returned . The caller must ensure that
* calipso_doi_putdef ( ) is called when the caller is done .
*
*/
static struct calipso_doi * calipso_doi_getdef ( u32 doi )
{
struct calipso_doi * doi_def ;
rcu_read_lock ( ) ;
doi_def = calipso_doi_search ( doi ) ;
if ( ! doi_def )
goto doi_getdef_return ;
if ( ! atomic_inc_not_zero ( & doi_def - > refcount ) )
doi_def = NULL ;
doi_getdef_return :
rcu_read_unlock ( ) ;
return doi_def ;
}
/**
* calipso_doi_putdef - Releases a reference for the given DOI definition
* @ doi_def : the DOI definition
*
* Description :
* Releases a DOI definition reference obtained from calipso_doi_getdef ( ) .
*
*/
static void calipso_doi_putdef ( struct calipso_doi * doi_def )
{
if ( ! doi_def )
return ;
if ( ! atomic_dec_and_test ( & doi_def - > refcount ) )
return ;
spin_lock ( & calipso_doi_list_lock ) ;
list_del_rcu ( & doi_def - > list ) ;
spin_unlock ( & calipso_doi_list_lock ) ;
call_rcu ( & doi_def - > rcu , calipso_doi_free_rcu ) ;
}
2016-06-27 22:02:48 +03:00
/**
* calipso_doi_walk - Iterate through the DOI definitions
* @ skip_cnt : skip past this number of DOI definitions , updated
* @ callback : callback for each DOI definition
* @ cb_arg : argument for the callback function
*
* Description :
* Iterate over the DOI definition list , skipping the first @ skip_cnt entries .
* For each entry call @ callback , if @ callback returns a negative value stop
* ' walking ' through the list and return . Updates the value in @ skip_cnt upon
* return . Returns zero on success , negative values on failure .
*
*/
static int calipso_doi_walk ( u32 * skip_cnt ,
int ( * callback ) ( struct calipso_doi * doi_def ,
void * arg ) ,
void * cb_arg )
{
int ret_val = - ENOENT ;
u32 doi_cnt = 0 ;
struct calipso_doi * iter_doi ;
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( iter_doi , & calipso_doi_list , list )
if ( atomic_read ( & iter_doi - > refcount ) > 0 ) {
if ( doi_cnt + + < * skip_cnt )
continue ;
ret_val = callback ( iter_doi , cb_arg ) ;
if ( ret_val < 0 ) {
doi_cnt - - ;
goto doi_walk_return ;
}
}
doi_walk_return :
rcu_read_unlock ( ) ;
* skip_cnt = doi_cnt ;
return ret_val ;
}
2016-06-27 22:02:51 +03:00
/**
* calipso_map_cat_hton - Perform a category mapping from host to network
* @ doi_def : the DOI definition
* @ secattr : the security attributes
* @ net_cat : the zero ' d out category bitmap in network / CALIPSO format
* @ net_cat_len : the length of the CALIPSO bitmap in bytes
*
* Description :
* Perform a label mapping to translate a local MLS category bitmap to the
* correct CALIPSO bitmap using the given DOI definition . Returns the minimum
* size in bytes of the network bitmap on success , negative values otherwise .
*
*/
static int calipso_map_cat_hton ( const struct calipso_doi * doi_def ,
const struct netlbl_lsm_secattr * secattr ,
unsigned char * net_cat ,
u32 net_cat_len )
{
int spot = - 1 ;
u32 net_spot_max = 0 ;
u32 net_clen_bits = net_cat_len * 8 ;
for ( ; ; ) {
spot = netlbl_catmap_walk ( secattr - > attr . mls . cat ,
spot + 1 ) ;
if ( spot < 0 )
break ;
if ( spot > = net_clen_bits )
return - ENOSPC ;
netlbl_bitmap_setbit ( net_cat , spot , 1 ) ;
if ( spot > net_spot_max )
net_spot_max = spot ;
}
return ( net_spot_max / 32 + 1 ) * 4 ;
}
/**
* calipso_map_cat_ntoh - Perform a category mapping from network to host
* @ doi_def : the DOI definition
* @ net_cat : the category bitmap in network / CALIPSO format
* @ net_cat_len : the length of the CALIPSO bitmap in bytes
* @ secattr : the security attributes
*
* Description :
* Perform a label mapping to translate a CALIPSO bitmap to the correct local
* MLS category bitmap using the given DOI definition . Returns zero on
* success , negative values on failure .
*
*/
static int calipso_map_cat_ntoh ( const struct calipso_doi * doi_def ,
const unsigned char * net_cat ,
u32 net_cat_len ,
struct netlbl_lsm_secattr * secattr )
{
int ret_val ;
int spot = - 1 ;
u32 net_clen_bits = net_cat_len * 8 ;
for ( ; ; ) {
spot = netlbl_bitmap_walk ( net_cat ,
net_clen_bits ,
spot + 1 ,
1 ) ;
if ( spot < 0 ) {
if ( spot = = - 2 )
return - EFAULT ;
return 0 ;
}
ret_val = netlbl_catmap_setbit ( & secattr - > attr . mls . cat ,
spot ,
GFP_ATOMIC ) ;
if ( ret_val ! = 0 )
return ret_val ;
}
return - EINVAL ;
}
/**
* calipso_pad_write - Writes pad bytes in TLV format
* @ buf : the buffer
* @ offset : offset from start of buffer to write padding
* @ count : number of pad bytes to write
*
* Description :
* Write @ count bytes of TLV padding into @ buffer starting at offset @ offset .
* @ count should be less than 8 - see RFC 4942.
*
*/
static int calipso_pad_write ( unsigned char * buf , unsigned int offset ,
unsigned int count )
{
if ( WARN_ON_ONCE ( count > = 8 ) )
return - EINVAL ;
switch ( count ) {
case 0 :
break ;
case 1 :
buf [ offset ] = IPV6_TLV_PAD1 ;
break ;
default :
buf [ offset ] = IPV6_TLV_PADN ;
buf [ offset + 1 ] = count - 2 ;
if ( count > 2 )
memset ( buf + offset + 2 , 0 , count - 2 ) ;
break ;
}
return 0 ;
}
/**
* calipso_genopt - Generate a CALIPSO option
* @ buf : the option buffer
* @ start : offset from which to write
* @ buf_len : the size of opt_buf
* @ doi_def : the CALIPSO DOI to use
* @ secattr : the security attributes
*
* Description :
* Generate a CALIPSO option using the DOI definition and security attributes
* passed to the function . This also generates upto three bytes of leading
* padding that ensures that the option is 4 n + 2 aligned . It returns the
* number of bytes written ( including any initial padding ) .
*/
static int calipso_genopt ( unsigned char * buf , u32 start , u32 buf_len ,
const struct calipso_doi * doi_def ,
const struct netlbl_lsm_secattr * secattr )
{
int ret_val ;
u32 len , pad ;
u16 crc ;
static const unsigned char padding [ 4 ] = { 2 , 1 , 0 , 3 } ;
unsigned char * calipso ;
/* CALIPSO has 4n + 2 alignment */
pad = padding [ start & 3 ] ;
if ( buf_len < = start + pad + CALIPSO_HDR_LEN )
return - ENOSPC ;
if ( ( secattr - > flags & NETLBL_SECATTR_MLS_LVL ) = = 0 )
return - EPERM ;
len = CALIPSO_HDR_LEN ;
if ( secattr - > flags & NETLBL_SECATTR_MLS_CAT ) {
ret_val = calipso_map_cat_hton ( doi_def ,
secattr ,
buf + start + pad + len ,
buf_len - start - pad - len ) ;
if ( ret_val < 0 )
return ret_val ;
len + = ret_val ;
}
calipso_pad_write ( buf , start , pad ) ;
calipso = buf + start + pad ;
calipso [ 0 ] = IPV6_TLV_CALIPSO ;
calipso [ 1 ] = len - 2 ;
* ( __be32 * ) ( calipso + 2 ) = htonl ( doi_def - > doi ) ;
calipso [ 6 ] = ( len - CALIPSO_HDR_LEN ) / 4 ;
calipso [ 7 ] = secattr - > attr . mls . lvl ,
crc = ~ crc_ccitt ( 0xffff , calipso , len ) ;
calipso [ 8 ] = crc & 0xff ;
calipso [ 9 ] = ( crc > > 8 ) & 0xff ;
return pad + len ;
}
/* Hop-by-hop hdr helper functions
*/
/**
* calipso_opt_update - Replaces socket ' s hop options with a new set
* @ sk : the socket
* @ hop : new hop options
*
* Description :
* Replaces @ sk ' s hop options with @ hop . @ hop may be NULL to leave
* the socket with no hop options .
*
*/
static int calipso_opt_update ( struct sock * sk , struct ipv6_opt_hdr * hop )
{
struct ipv6_txoptions * old = txopt_get ( inet6_sk ( sk ) ) , * txopts ;
txopts = ipv6_renew_options_kern ( sk , old , IPV6_HOPOPTS ,
hop , hop ? ipv6_optlen ( hop ) : 0 ) ;
txopt_put ( old ) ;
if ( IS_ERR ( txopts ) )
return PTR_ERR ( txopts ) ;
txopts = ipv6_update_options ( sk , txopts ) ;
if ( txopts ) {
atomic_sub ( txopts - > tot_len , & sk - > sk_omem_alloc ) ;
txopt_put ( txopts ) ;
}
return 0 ;
}
/**
* calipso_tlv_len - Returns the length of the TLV
* @ opt : the option header
* @ offset : offset of the TLV within the header
*
* Description :
* Returns the length of the TLV option at offset @ offset within
* the option header @ opt . Checks that the entire TLV fits inside
* the option header , returns a negative value if this is not the case .
*/
static int calipso_tlv_len ( struct ipv6_opt_hdr * opt , unsigned int offset )
{
unsigned char * tlv = ( unsigned char * ) opt ;
unsigned int opt_len = ipv6_optlen ( opt ) , tlv_len ;
if ( offset < sizeof ( * opt ) | | offset > = opt_len )
return - EINVAL ;
if ( tlv [ offset ] = = IPV6_TLV_PAD1 )
return 1 ;
if ( offset + 1 > = opt_len )
return - EINVAL ;
tlv_len = tlv [ offset + 1 ] + 2 ;
if ( offset + tlv_len > opt_len )
return - EINVAL ;
return tlv_len ;
}
/**
* calipso_opt_find - Finds the CALIPSO option in an IPv6 hop options header
* @ hop : the hop options header
* @ start : on return holds the offset of any leading padding
* @ end : on return holds the offset of the first non - pad TLV after CALIPSO
*
* Description :
* Finds the space occupied by a CALIPSO option ( including any leading and
* trailing padding ) .
*
* If a CALIPSO option exists set @ start and @ end to the
* offsets within @ hop of the start of padding before the first
* CALIPSO option and the end of padding after the first CALIPSO
* option . In this case the function returns 0.
*
* In the absence of a CALIPSO option , @ start and @ end will be
* set to the start and end of any trailing padding in the header .
* This is useful when appending a new option , as the caller may want
* to overwrite some of this padding . In this case the function will
* return - ENOENT .
*/
static int calipso_opt_find ( struct ipv6_opt_hdr * hop , unsigned int * start ,
unsigned int * end )
{
int ret_val = - ENOENT , tlv_len ;
unsigned int opt_len , offset , offset_s = 0 , offset_e = 0 ;
unsigned char * opt = ( unsigned char * ) hop ;
opt_len = ipv6_optlen ( hop ) ;
offset = sizeof ( * hop ) ;
while ( offset < opt_len ) {
tlv_len = calipso_tlv_len ( hop , offset ) ;
if ( tlv_len < 0 )
return tlv_len ;
switch ( opt [ offset ] ) {
case IPV6_TLV_PAD1 :
case IPV6_TLV_PADN :
if ( offset_e )
offset_e = offset ;
break ;
case IPV6_TLV_CALIPSO :
ret_val = 0 ;
offset_e = offset ;
break ;
default :
if ( offset_e = = 0 )
offset_s = offset ;
else
goto out ;
}
offset + = tlv_len ;
}
out :
if ( offset_s )
* start = offset_s + calipso_tlv_len ( hop , offset_s ) ;
else
* start = sizeof ( * hop ) ;
if ( offset_e )
* end = offset_e + calipso_tlv_len ( hop , offset_e ) ;
else
* end = opt_len ;
return ret_val ;
}
/**
* calipso_opt_insert - Inserts a CALIPSO option into an IPv6 hop opt hdr
* @ hop : the original hop options header
* @ doi_def : the CALIPSO DOI to use
* @ secattr : the specific security attributes of the socket
*
* Description :
* Creates a new hop options header based on @ hop with a
* CALIPSO option added to it . If @ hop already contains a CALIPSO
* option this is overwritten , otherwise the new option is appended
* after any existing options . If @ hop is NULL then the new header
* will contain just the CALIPSO option and any needed padding .
*
*/
static struct ipv6_opt_hdr *
calipso_opt_insert ( struct ipv6_opt_hdr * hop ,
const struct calipso_doi * doi_def ,
const struct netlbl_lsm_secattr * secattr )
{
unsigned int start , end , buf_len , pad , hop_len ;
struct ipv6_opt_hdr * new ;
int ret_val ;
if ( hop ) {
hop_len = ipv6_optlen ( hop ) ;
ret_val = calipso_opt_find ( hop , & start , & end ) ;
if ( ret_val & & ret_val ! = - ENOENT )
return ERR_PTR ( ret_val ) ;
} else {
hop_len = 0 ;
start = sizeof ( * hop ) ;
end = 0 ;
}
buf_len = hop_len + start - end + CALIPSO_OPT_LEN_MAX_WITH_PAD ;
new = kzalloc ( buf_len , GFP_ATOMIC ) ;
if ( ! new )
return ERR_PTR ( - ENOMEM ) ;
if ( start > sizeof ( * hop ) )
memcpy ( new , hop , start ) ;
ret_val = calipso_genopt ( ( unsigned char * ) new , start , buf_len , doi_def ,
secattr ) ;
if ( ret_val < 0 )
return ERR_PTR ( ret_val ) ;
buf_len = start + ret_val ;
/* At this point buf_len aligns to 4n, so (buf_len & 4) pads to 8n */
pad = ( ( buf_len & 4 ) + ( end & 7 ) ) & 7 ;
calipso_pad_write ( ( unsigned char * ) new , buf_len , pad ) ;
buf_len + = pad ;
if ( end ! = hop_len ) {
memcpy ( ( char * ) new + buf_len , ( char * ) hop + end , hop_len - end ) ;
buf_len + = hop_len - end ;
}
new - > nexthdr = 0 ;
new - > hdrlen = buf_len / 8 - 1 ;
return new ;
}
/**
* calipso_opt_del - Removes the CALIPSO option from an option header
* @ hop : the original header
* @ new : the new header
*
* Description :
* Creates a new header based on @ hop without any CALIPSO option . If @ hop
* doesn ' t contain a CALIPSO option it returns - ENOENT . If @ hop contains
* no other non - padding options , it returns zero with @ new set to NULL .
* Otherwise it returns zero , creates a new header without the CALIPSO
* option ( and removing as much padding as possible ) and returns with
* @ new set to that header .
*
*/
static int calipso_opt_del ( struct ipv6_opt_hdr * hop ,
struct ipv6_opt_hdr * * new )
{
int ret_val ;
unsigned int start , end , delta , pad , hop_len ;
ret_val = calipso_opt_find ( hop , & start , & end ) ;
if ( ret_val )
return ret_val ;
hop_len = ipv6_optlen ( hop ) ;
if ( start = = sizeof ( * hop ) & & end = = hop_len ) {
/* There's no other option in the header so return NULL */
* new = NULL ;
return 0 ;
}
delta = ( end - start ) & ~ 7 ;
* new = kzalloc ( hop_len - delta , GFP_ATOMIC ) ;
if ( ! * new )
return - ENOMEM ;
memcpy ( * new , hop , start ) ;
( * new ) - > hdrlen - = delta / 8 ;
pad = ( end - start ) & 7 ;
calipso_pad_write ( ( unsigned char * ) * new , start , pad ) ;
if ( end ! = hop_len )
memcpy ( ( char * ) * new + start + pad , ( char * ) hop + end ,
hop_len - end ) ;
return 0 ;
}
/**
* calipso_opt_getattr - Get the security attributes from a memory block
* @ calipso : the CALIPSO option
* @ secattr : the security attributes
*
* Description :
* Inspect @ calipso and return the security attributes in @ secattr .
* Returns zero on success and negative values on failure .
*
*/
static int calipso_opt_getattr ( const unsigned char * calipso ,
struct netlbl_lsm_secattr * secattr )
{
int ret_val = - ENOMSG ;
u32 doi , len = calipso [ 1 ] , cat_len = calipso [ 6 ] * 4 ;
struct calipso_doi * doi_def ;
if ( cat_len + 8 > len )
return - EINVAL ;
doi = get_unaligned_be32 ( calipso + 2 ) ;
rcu_read_lock ( ) ;
doi_def = calipso_doi_search ( doi ) ;
if ( ! doi_def )
goto getattr_return ;
secattr - > attr . mls . lvl = calipso [ 7 ] ;
secattr - > flags | = NETLBL_SECATTR_MLS_LVL ;
if ( cat_len ) {
ret_val = calipso_map_cat_ntoh ( doi_def ,
calipso + 10 ,
cat_len ,
secattr ) ;
if ( ret_val ! = 0 ) {
netlbl_catmap_free ( secattr - > attr . mls . cat ) ;
goto getattr_return ;
}
secattr - > flags | = NETLBL_SECATTR_MLS_CAT ;
}
secattr - > type = NETLBL_NLTYPE_CALIPSO ;
getattr_return :
rcu_read_unlock ( ) ;
return ret_val ;
}
/* sock functions.
*/
/**
* calipso_sock_getattr - Get the security attributes from a sock
* @ sk : the sock
* @ secattr : the security attributes
*
* Description :
* Query @ sk to see if there is a CALIPSO option attached to the sock and if
* there is return the CALIPSO security attributes in @ secattr . This function
* requires that @ sk be locked , or privately held , but it does not do any
* locking itself . Returns zero on success and negative values on failure .
*
*/
static int calipso_sock_getattr ( struct sock * sk ,
struct netlbl_lsm_secattr * secattr )
{
struct ipv6_opt_hdr * hop ;
int opt_len , len , ret_val = - ENOMSG , offset ;
unsigned char * opt ;
struct ipv6_txoptions * txopts = txopt_get ( inet6_sk ( sk ) ) ;
if ( ! txopts | | ! txopts - > hopopt )
goto done ;
hop = txopts - > hopopt ;
opt = ( unsigned char * ) hop ;
opt_len = ipv6_optlen ( hop ) ;
offset = sizeof ( * hop ) ;
while ( offset < opt_len ) {
len = calipso_tlv_len ( hop , offset ) ;
if ( len < 0 ) {
ret_val = len ;
goto done ;
}
switch ( opt [ offset ] ) {
case IPV6_TLV_CALIPSO :
if ( len < CALIPSO_HDR_LEN )
ret_val = - EINVAL ;
else
ret_val = calipso_opt_getattr ( & opt [ offset ] ,
secattr ) ;
goto done ;
default :
offset + = len ;
break ;
}
}
done :
txopt_put ( txopts ) ;
return ret_val ;
}
/**
* calipso_sock_setattr - Add a CALIPSO option to a socket
* @ sk : the socket
* @ doi_def : the CALIPSO DOI to use
* @ secattr : the specific security attributes of the socket
*
* Description :
* Set the CALIPSO option on the given socket using the DOI definition and
* security attributes passed to the function . This function requires
* exclusive access to @ sk , which means it either needs to be in the
* process of being created or locked . Returns zero on success and negative
* values on failure .
*
*/
static int calipso_sock_setattr ( struct sock * sk ,
const struct calipso_doi * doi_def ,
const struct netlbl_lsm_secattr * secattr )
{
int ret_val ;
struct ipv6_opt_hdr * old , * new ;
struct ipv6_txoptions * txopts = txopt_get ( inet6_sk ( sk ) ) ;
old = NULL ;
if ( txopts )
old = txopts - > hopopt ;
new = calipso_opt_insert ( old , doi_def , secattr ) ;
txopt_put ( txopts ) ;
if ( IS_ERR ( new ) )
return PTR_ERR ( new ) ;
ret_val = calipso_opt_update ( sk , new ) ;
kfree ( new ) ;
return ret_val ;
}
/**
* calipso_sock_delattr - Delete the CALIPSO option from a socket
* @ sk : the socket
*
* Description :
* Removes the CALIPSO option from a socket , if present .
*
*/
static void calipso_sock_delattr ( struct sock * sk )
{
struct ipv6_opt_hdr * new_hop ;
struct ipv6_txoptions * txopts = txopt_get ( inet6_sk ( sk ) ) ;
if ( ! txopts | | ! txopts - > hopopt )
goto done ;
if ( calipso_opt_del ( txopts - > hopopt , & new_hop ) )
goto done ;
calipso_opt_update ( sk , new_hop ) ;
kfree ( new_hop ) ;
done :
txopt_put ( txopts ) ;
}
2016-06-27 22:05:29 +03:00
/* request sock functions.
*/
/**
* calipso_req_setattr - Add a CALIPSO option to a connection request socket
* @ req : the connection request socket
* @ doi_def : the CALIPSO DOI to use
* @ secattr : the specific security attributes of the socket
*
* Description :
* Set the CALIPSO option on the given socket using the DOI definition and
* security attributes passed to the function . Returns zero on success and
* negative values on failure .
*
*/
static int calipso_req_setattr ( struct request_sock * req ,
const struct calipso_doi * doi_def ,
const struct netlbl_lsm_secattr * secattr )
{
struct ipv6_txoptions * txopts ;
struct inet_request_sock * req_inet = inet_rsk ( req ) ;
struct ipv6_opt_hdr * old , * new ;
struct sock * sk = sk_to_full_sk ( req_to_sk ( req ) ) ;
if ( req_inet - > ipv6_opt & & req_inet - > ipv6_opt - > hopopt )
old = req_inet - > ipv6_opt - > hopopt ;
else
old = NULL ;
new = calipso_opt_insert ( old , doi_def , secattr ) ;
if ( IS_ERR ( new ) )
return PTR_ERR ( new ) ;
txopts = ipv6_renew_options_kern ( sk , req_inet - > ipv6_opt , IPV6_HOPOPTS ,
new , new ? ipv6_optlen ( new ) : 0 ) ;
kfree ( new ) ;
if ( IS_ERR ( txopts ) )
return PTR_ERR ( txopts ) ;
txopts = xchg ( & req_inet - > ipv6_opt , txopts ) ;
if ( txopts ) {
atomic_sub ( txopts - > tot_len , & sk - > sk_omem_alloc ) ;
txopt_put ( txopts ) ;
}
return 0 ;
}
/**
* calipso_req_delattr - Delete the CALIPSO option from a request socket
* @ reg : the request socket
*
* Description :
* Removes the CALIPSO option from a request socket , if present .
*
*/
static void calipso_req_delattr ( struct request_sock * req )
{
struct inet_request_sock * req_inet = inet_rsk ( req ) ;
struct ipv6_opt_hdr * new ;
struct ipv6_txoptions * txopts ;
struct sock * sk = sk_to_full_sk ( req_to_sk ( req ) ) ;
if ( ! req_inet - > ipv6_opt | | ! req_inet - > ipv6_opt - > hopopt )
return ;
if ( calipso_opt_del ( req_inet - > ipv6_opt - > hopopt , & new ) )
return ; /* Nothing to do */
txopts = ipv6_renew_options_kern ( sk , req_inet - > ipv6_opt , IPV6_HOPOPTS ,
new , new ? ipv6_optlen ( new ) : 0 ) ;
if ( ! IS_ERR ( txopts ) ) {
txopts = xchg ( & req_inet - > ipv6_opt , txopts ) ;
if ( txopts ) {
atomic_sub ( txopts - > tot_len , & sk - > sk_omem_alloc ) ;
txopt_put ( txopts ) ;
}
}
kfree ( new ) ;
}
2016-06-27 22:06:15 +03:00
/* skbuff functions.
*/
/**
* calipso_skbuff_optptr - Find the CALIPSO option in the packet
* @ skb : the packet
*
* Description :
* Parse the packet ' s IP header looking for a CALIPSO option . Returns a pointer
* to the start of the CALIPSO option on success , NULL if one if not found .
*
*/
static unsigned char * calipso_skbuff_optptr ( const struct sk_buff * skb )
{
const struct ipv6hdr * ip6_hdr = ipv6_hdr ( skb ) ;
int offset ;
if ( ip6_hdr - > nexthdr ! = NEXTHDR_HOP )
return NULL ;
offset = ipv6_find_tlv ( skb , sizeof ( * ip6_hdr ) , IPV6_TLV_CALIPSO ) ;
if ( offset > = 0 )
return ( unsigned char * ) ip6_hdr + offset ;
return NULL ;
}
/**
* calipso_skbuff_setattr - Set the CALIPSO option on a packet
* @ skb : the packet
* @ doi_def : the CALIPSO DOI to use
* @ secattr : the security attributes
*
* Description :
* Set the CALIPSO option on the given packet based on the security attributes .
* Returns a pointer to the IP header on success and NULL on failure .
*
*/
static int calipso_skbuff_setattr ( struct sk_buff * skb ,
const struct calipso_doi * doi_def ,
const struct netlbl_lsm_secattr * secattr )
{
int ret_val ;
struct ipv6hdr * ip6_hdr ;
struct ipv6_opt_hdr * hop ;
unsigned char buf [ CALIPSO_MAX_BUFFER ] ;
int len_delta , new_end , pad ;
unsigned int start , end ;
ip6_hdr = ipv6_hdr ( skb ) ;
if ( ip6_hdr - > nexthdr = = NEXTHDR_HOP ) {
hop = ( struct ipv6_opt_hdr * ) ( ip6_hdr + 1 ) ;
ret_val = calipso_opt_find ( hop , & start , & end ) ;
if ( ret_val & & ret_val ! = - ENOENT )
return ret_val ;
} else {
start = 0 ;
end = 0 ;
}
memset ( buf , 0 , sizeof ( buf ) ) ;
ret_val = calipso_genopt ( buf , start & 3 , sizeof ( buf ) , doi_def , secattr ) ;
if ( ret_val < 0 )
return ret_val ;
new_end = start + ret_val ;
/* At this point new_end aligns to 4n, so (new_end & 4) pads to 8n */
pad = ( ( new_end & 4 ) + ( end & 7 ) ) & 7 ;
len_delta = new_end - ( int ) end + pad ;
ret_val = skb_cow ( skb , skb_headroom ( skb ) + len_delta ) ;
if ( ret_val < 0 )
return ret_val ;
if ( len_delta ) {
if ( len_delta > 0 )
skb_push ( skb , len_delta ) ;
else
skb_pull ( skb , - len_delta ) ;
memmove ( ( char * ) ip6_hdr - len_delta , ip6_hdr ,
sizeof ( * ip6_hdr ) + start ) ;
skb_reset_network_header ( skb ) ;
ip6_hdr = ipv6_hdr ( skb ) ;
}
hop = ( struct ipv6_opt_hdr * ) ( ip6_hdr + 1 ) ;
if ( start = = 0 ) {
struct ipv6_opt_hdr * new_hop = ( struct ipv6_opt_hdr * ) buf ;
new_hop - > nexthdr = ip6_hdr - > nexthdr ;
new_hop - > hdrlen = len_delta / 8 - 1 ;
ip6_hdr - > nexthdr = NEXTHDR_HOP ;
} else {
hop - > hdrlen + = len_delta / 8 ;
}
memcpy ( ( char * ) hop + start , buf + ( start & 3 ) , new_end - start ) ;
calipso_pad_write ( ( unsigned char * ) hop , new_end , pad ) ;
return 0 ;
}
/**
* calipso_skbuff_delattr - Delete any CALIPSO options from a packet
* @ skb : the packet
*
* Description :
* Removes any and all CALIPSO options from the given packet . Returns zero on
* success , negative values on failure .
*
*/
static int calipso_skbuff_delattr ( struct sk_buff * skb )
{
int ret_val ;
struct ipv6hdr * ip6_hdr ;
struct ipv6_opt_hdr * old_hop ;
u32 old_hop_len , start = 0 , end = 0 , delta , size , pad ;
if ( ! calipso_skbuff_optptr ( skb ) )
return 0 ;
/* since we are changing the packet we should make a copy */
ret_val = skb_cow ( skb , skb_headroom ( skb ) ) ;
if ( ret_val < 0 )
return ret_val ;
ip6_hdr = ipv6_hdr ( skb ) ;
old_hop = ( struct ipv6_opt_hdr * ) ( ip6_hdr + 1 ) ;
old_hop_len = ipv6_optlen ( old_hop ) ;
ret_val = calipso_opt_find ( old_hop , & start , & end ) ;
if ( ret_val )
return ret_val ;
if ( start = = sizeof ( * old_hop ) & & end = = old_hop_len ) {
/* There's no other option in the header so we delete
* the whole thing . */
delta = old_hop_len ;
size = sizeof ( * ip6_hdr ) ;
ip6_hdr - > nexthdr = old_hop - > nexthdr ;
} else {
delta = ( end - start ) & ~ 7 ;
if ( delta )
old_hop - > hdrlen - = delta / 8 ;
pad = ( end - start ) & 7 ;
size = sizeof ( * ip6_hdr ) + start + pad ;
calipso_pad_write ( ( unsigned char * ) old_hop , start , pad ) ;
}
if ( delta ) {
skb_pull ( skb , delta ) ;
memmove ( ( char * ) ip6_hdr + delta , ip6_hdr , size ) ;
skb_reset_network_header ( skb ) ;
}
return 0 ;
}
2016-06-27 22:02:46 +03:00
static const struct netlbl_calipso_ops ops = {
. doi_add = calipso_doi_add ,
. doi_free = calipso_doi_free ,
2016-06-27 22:02:49 +03:00
. doi_remove = calipso_doi_remove ,
2016-06-27 22:02:47 +03:00
. doi_getdef = calipso_doi_getdef ,
. doi_putdef = calipso_doi_putdef ,
2016-06-27 22:02:48 +03:00
. doi_walk = calipso_doi_walk ,
2016-06-27 22:02:51 +03:00
. sock_getattr = calipso_sock_getattr ,
. sock_setattr = calipso_sock_setattr ,
. sock_delattr = calipso_sock_delattr ,
2016-06-27 22:05:29 +03:00
. req_setattr = calipso_req_setattr ,
. req_delattr = calipso_req_delattr ,
2016-06-27 22:06:15 +03:00
. opt_getattr = calipso_opt_getattr ,
. skbuff_optptr = calipso_skbuff_optptr ,
. skbuff_setattr = calipso_skbuff_setattr ,
. skbuff_delattr = calipso_skbuff_delattr ,
2016-06-27 22:02:46 +03:00
} ;
/**
* calipso_init - Initialize the CALIPSO module
*
* Description :
* Initialize the CALIPSO module and prepare it for use . Returns zero on
* success and negative values on failure .
*
*/
int __init calipso_init ( void )
{
netlbl_calipso_ops_register ( & ops ) ;
return 0 ;
}
void calipso_exit ( void )
{
netlbl_calipso_ops_register ( NULL ) ;
}