2019-06-01 11:08:55 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2010-07-30 01:47:57 +04:00
/*
* AppArmor security module
*
* This file contains basic common functions used in AppArmor
*
* Copyright ( C ) 1998 - 2008 Novell / SUSE
* Copyright 2009 - 2010 Canonical Ltd .
*/
2017-01-16 11:42:23 +03:00
# include <linux/ctype.h>
2011-06-16 15:01:34 +04:00
# include <linux/mm.h>
2010-07-30 01:47:57 +04:00
# include <linux/slab.h>
# include <linux/string.h>
# include <linux/vmalloc.h>
# include "include/audit.h"
2011-08-29 05:15:25 +04:00
# include "include/apparmor.h"
2017-01-16 11:42:13 +03:00
# include "include/lib.h"
2017-05-26 11:57:09 +03:00
# include "include/perms.h"
2017-01-16 11:42:14 +03:00
# include "include/policy.h"
2010-07-30 01:47:57 +04:00
2017-05-29 22:19:39 +03:00
struct aa_perms nullperms ;
2017-05-29 22:16:04 +03:00
struct aa_perms allperms = { . allow = ALL_PERMS_MASK ,
. quiet = ALL_PERMS_MASK ,
. hide = ALL_PERMS_MASK } ;
2010-07-30 01:47:57 +04:00
/**
* aa_split_fqname - split a fqname into a profile and namespace name
* @ fqname : a full qualified name in namespace profile format ( NOT NULL )
* @ ns_name : pointer to portion of the string containing the ns name ( NOT NULL )
*
* Returns : profile name or NULL if one is not specified
*
* Split a namespace name from a profile name ( see policy . c for naming
* description ) . If a portion of the name is missing it returns NULL for
* that portion .
*
* NOTE : may modify the @ fqname string . The pointers returned point
* into the @ fqname string .
*/
char * aa_split_fqname ( char * fqname , char * * ns_name )
{
char * name = strim ( fqname ) ;
* ns_name = NULL ;
if ( name [ 0 ] = = ' : ' ) {
char * split = strchr ( & name [ 1 ] , ' : ' ) ;
2010-08-28 05:33:28 +04:00
* ns_name = skip_spaces ( & name [ 1 ] ) ;
2010-07-30 01:47:57 +04:00
if ( split ) {
/* overwrite ':' with \0 */
2013-02-27 15:45:05 +04:00
* split + + = 0 ;
if ( strncmp ( split , " // " , 2 ) = = 0 )
split + = 2 ;
name = skip_spaces ( split ) ;
2010-07-30 01:47:57 +04:00
} else
/* a ns name without a following profile is allowed */
name = NULL ;
}
if ( name & & * name = = 0 )
name = NULL ;
return name ;
}
2017-01-16 11:42:23 +03:00
/**
* skipn_spaces - Removes leading whitespace from @ str .
* @ str : The string to be stripped .
*
* Returns a pointer to the first non - whitespace character in @ str .
* if all whitespace will return NULL
*/
2017-05-22 12:47:22 +03:00
const char * skipn_spaces ( const char * str , size_t n )
2017-01-16 11:42:23 +03:00
{
for ( ; n & & isspace ( * str ) ; - - n )
+ + str ;
if ( n )
return ( char * ) str ;
return NULL ;
}
const char * aa_splitn_fqname ( const char * fqname , size_t n , const char * * ns_name ,
size_t * ns_len )
{
const char * end = fqname + n ;
const char * name = skipn_spaces ( fqname , n ) ;
* ns_name = NULL ;
* ns_len = 0 ;
2018-09-28 00:49:17 +03:00
if ( ! name )
return NULL ;
2017-01-16 11:42:23 +03:00
if ( name [ 0 ] = = ' : ' ) {
char * split = strnchr ( & name [ 1 ] , end - & name [ 1 ] , ' : ' ) ;
* ns_name = skipn_spaces ( & name [ 1 ] , end - & name [ 1 ] ) ;
if ( ! * ns_name )
return NULL ;
if ( split ) {
* ns_len = split - * ns_name ;
if ( * ns_len = = 0 )
* ns_name = NULL ;
split + + ;
if ( end - split > 1 & & strncmp ( split , " // " , 2 ) = = 0 )
split + = 2 ;
name = skipn_spaces ( split , end - split ) ;
} else {
/* a ns name without a following profile is allowed */
name = NULL ;
* ns_len = end - * ns_name ;
}
}
if ( name & & * name = = 0 )
name = NULL ;
return name ;
}
2010-07-30 01:47:57 +04:00
/**
* aa_info_message - log a none profile related status message
* @ str : message to log
*/
void aa_info_message ( const char * str )
{
if ( audit_enabled ) {
2017-01-16 11:43:02 +03:00
DEFINE_AUDIT_DATA ( sa , LSM_AUDIT_DATA_NONE , NULL ) ;
aad ( & sa ) - > info = str ;
2010-07-30 01:47:57 +04:00
aa_audit_msg ( AUDIT_APPARMOR_STATUS , & sa , NULL ) ;
}
printk ( KERN_INFO " AppArmor: %s \n " , str ) ;
}
2017-06-09 17:09:05 +03:00
__counted char * aa_str_alloc ( int size , gfp_t gfp )
{
struct counted_str * str ;
str = kmalloc ( sizeof ( struct counted_str ) + size , gfp ) ;
if ( ! str )
return NULL ;
kref_init ( & str - > count ) ;
return str - > name ;
}
void aa_str_kref ( struct kref * kref )
{
kfree ( container_of ( kref , struct counted_str , count ) ) ;
}
2017-05-27 01:07:22 +03:00
const char aa_file_perm_chrs [ ] = " xwracd km l " ;
const char * aa_file_perm_names [ ] = {
" exec " ,
" write " ,
" read " ,
" append " ,
" create " ,
" delete " ,
" open " ,
" rename " ,
" setattr " ,
" getattr " ,
" setcred " ,
" getcred " ,
" chmod " ,
" chown " ,
" chgrp " ,
" lock " ,
" mmap " ,
" mprot " ,
" link " ,
" snapshot " ,
" unknown " ,
" unknown " ,
" unknown " ,
" unknown " ,
" unknown " ,
" unknown " ,
" unknown " ,
" unknown " ,
" stack " ,
" change_onexec " ,
" change_profile " ,
" change_hat " ,
} ;
/**
* aa_perm_mask_to_str - convert a perm mask to its short string
* @ str : character buffer to store string in ( at least 10 characters )
2018-07-06 08:25:00 +03:00
* @ str_size : size of the @ str buffer
* @ chrs : NUL - terminated character buffer of permission characters
2017-05-27 01:07:22 +03:00
* @ mask : permission mask to convert
*/
2018-07-06 08:25:00 +03:00
void aa_perm_mask_to_str ( char * str , size_t str_size , const char * chrs , u32 mask )
2017-05-27 01:07:22 +03:00
{
unsigned int i , perm = 1 ;
2018-07-06 08:25:00 +03:00
size_t num_chrs = strlen ( chrs ) ;
for ( i = 0 ; i < num_chrs ; perm < < = 1 , i + + ) {
if ( mask & perm ) {
/* Ensure that one byte is left for NUL-termination */
if ( WARN_ON_ONCE ( str_size < = 1 ) )
break ;
2017-05-27 01:07:22 +03:00
* str + + = chrs [ i ] ;
2018-07-06 08:25:00 +03:00
str_size - - ;
}
2017-05-27 01:07:22 +03:00
}
* str = ' \0 ' ;
}
2017-07-19 09:18:33 +03:00
void aa_audit_perm_names ( struct audit_buffer * ab , const char * const * names ,
u32 mask )
2017-05-29 22:16:04 +03:00
{
const char * fmt = " %s " ;
unsigned int i , perm = 1 ;
bool prev = false ;
for ( i = 0 ; i < 32 ; perm < < = 1 , i + + ) {
if ( mask & perm ) {
audit_log_format ( ab , fmt , names [ i ] ) ;
if ( ! prev ) {
prev = true ;
fmt = " %s " ;
}
}
}
}
void aa_audit_perm_mask ( struct audit_buffer * ab , u32 mask , const char * chrs ,
2017-07-19 09:18:33 +03:00
u32 chrsmask , const char * const * names , u32 namesmask )
2017-05-29 22:16:04 +03:00
{
char str [ 33 ] ;
audit_log_format ( ab , " \" " ) ;
if ( ( mask & chrsmask ) & & chrs ) {
2018-07-06 08:25:00 +03:00
aa_perm_mask_to_str ( str , sizeof ( str ) , chrs , mask & chrsmask ) ;
2017-05-29 22:16:04 +03:00
mask & = ~ chrsmask ;
audit_log_format ( ab , " %s " , str ) ;
if ( mask & namesmask )
audit_log_format ( ab , " " ) ;
}
if ( ( mask & namesmask ) & & names )
aa_audit_perm_names ( ab , names , mask & namesmask ) ;
audit_log_format ( ab , " \" " ) ;
}
2017-06-09 18:14:28 +03:00
/**
* aa_audit_perms_cb - generic callback fn for auditing perms
* @ ab : audit buffer ( NOT NULL )
* @ va : audit struct to audit values of ( NOT NULL )
*/
static void aa_audit_perms_cb ( struct audit_buffer * ab , void * va )
{
struct common_audit_data * sa = va ;
if ( aad ( sa ) - > request ) {
audit_log_format ( ab , " requested_mask= " ) ;
aa_audit_perm_mask ( ab , aad ( sa ) - > request , aa_file_perm_chrs ,
PERMS_CHRS_MASK , aa_file_perm_names ,
PERMS_NAMES_MASK ) ;
}
if ( aad ( sa ) - > denied ) {
audit_log_format ( ab , " denied_mask= " ) ;
aa_audit_perm_mask ( ab , aad ( sa ) - > denied , aa_file_perm_chrs ,
PERMS_CHRS_MASK , aa_file_perm_names ,
PERMS_NAMES_MASK ) ;
}
audit_log_format ( ab , " peer= " ) ;
aa_label_xaudit ( ab , labels_ns ( aad ( sa ) - > label ) , aad ( sa ) - > peer ,
FLAGS_NONE , GFP_ATOMIC ) ;
}
2017-05-29 22:16:04 +03:00
/**
* aa_apply_modes_to_perms - apply namespace and profile flags to perms
* @ profile : that perms where computed from
* @ perms : perms to apply mode modifiers to
*
* TODO : split into profile and ns based flags for when accumulating perms
*/
void aa_apply_modes_to_perms ( struct aa_profile * profile , struct aa_perms * perms )
{
switch ( AUDIT_MODE ( profile ) ) {
case AUDIT_ALL :
perms - > audit = ALL_PERMS_MASK ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2017-05-29 22:16:04 +03:00
case AUDIT_NOQUIET :
perms - > quiet = 0 ;
break ;
case AUDIT_QUIET :
perms - > audit = 0 ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2017-05-29 22:16:04 +03:00
case AUDIT_QUIET_DENIED :
perms - > quiet = ALL_PERMS_MASK ;
break ;
}
if ( KILL_MODE ( profile ) )
perms - > kill = ALL_PERMS_MASK ;
else if ( COMPLAIN_MODE ( profile ) )
perms - > complain = ALL_PERMS_MASK ;
/*
* TODO :
* else if ( PROMPT_MODE ( profile ) )
* perms - > prompt = ALL_PERMS_MASK ;
*/
}
static u32 map_other ( u32 x )
{
return ( ( x & 0x3 ) < < 8 ) | /* SETATTR/GETATTR */
( ( x & 0x1c ) < < 18 ) | /* ACCEPT/BIND/LISTEN */
( ( x & 0x60 ) < < 19 ) ; /* SETOPT/GETOPT */
}
void aa_compute_perms ( struct aa_dfa * dfa , unsigned int state ,
struct aa_perms * perms )
{
2017-09-15 22:55:46 +03:00
* perms = ( struct aa_perms ) {
. allow = dfa_user_allow ( dfa , state ) ,
. audit = dfa_user_audit ( dfa , state ) ,
. quiet = dfa_user_quiet ( dfa , state ) ,
} ;
2017-05-29 22:16:04 +03:00
/* for v5 perm mapping in the policydb, the other set is used
* to extend the general perm set
*/
perms - > allow | = map_other ( dfa_other_allow ( dfa , state ) ) ;
perms - > audit | = map_other ( dfa_other_audit ( dfa , state ) ) ;
perms - > quiet | = map_other ( dfa_other_quiet ( dfa , state ) ) ;
// perms->xindex = dfa_user_xindex(dfa, state);
}
2017-06-09 18:14:28 +03:00
/**
* aa_perms_accum_raw - accumulate perms with out masking off overlapping perms
* @ accum - perms struct to accumulate into
* @ addend - perms struct to add to @ accum
*/
void aa_perms_accum_raw ( struct aa_perms * accum , struct aa_perms * addend )
{
accum - > deny | = addend - > deny ;
accum - > allow & = addend - > allow & ~ addend - > deny ;
accum - > audit | = addend - > audit & addend - > allow ;
accum - > quiet & = addend - > quiet & ~ addend - > allow ;
accum - > kill | = addend - > kill & ~ addend - > allow ;
accum - > stop | = addend - > stop & ~ addend - > allow ;
accum - > complain | = addend - > complain & ~ addend - > allow & ~ addend - > deny ;
accum - > cond | = addend - > cond & ~ addend - > allow & ~ addend - > deny ;
accum - > hide & = addend - > hide & ~ addend - > allow ;
accum - > prompt | = addend - > prompt & ~ addend - > allow & ~ addend - > deny ;
}
/**
* aa_perms_accum - accumulate perms , masking off overlapping perms
* @ accum - perms struct to accumulate into
* @ addend - perms struct to add to @ accum
*/
void aa_perms_accum ( struct aa_perms * accum , struct aa_perms * addend )
{
accum - > deny | = addend - > deny ;
accum - > allow & = addend - > allow & ~ accum - > deny ;
accum - > audit | = addend - > audit & accum - > allow ;
accum - > quiet & = addend - > quiet & ~ accum - > allow ;
accum - > kill | = addend - > kill & ~ accum - > allow ;
accum - > stop | = addend - > stop & ~ accum - > allow ;
accum - > complain | = addend - > complain & ~ accum - > allow & ~ accum - > deny ;
accum - > cond | = addend - > cond & ~ accum - > allow & ~ accum - > deny ;
accum - > hide & = addend - > hide & ~ accum - > allow ;
accum - > prompt | = addend - > prompt & ~ accum - > allow & ~ accum - > deny ;
}
void aa_profile_match_label ( struct aa_profile * profile , struct aa_label * label ,
int type , u32 request , struct aa_perms * perms )
{
/* TODO: doesn't yet handle extended types */
unsigned int state ;
state = aa_dfa_next ( profile - > policy . dfa ,
profile - > policy . start [ AA_CLASS_LABEL ] ,
type ) ;
aa_label_match ( profile , label , state , false , request , perms ) ;
}
/* currently unused */
int aa_profile_label_perm ( struct aa_profile * profile , struct aa_profile * target ,
u32 request , int type , u32 * deny ,
struct common_audit_data * sa )
{
struct aa_perms perms ;
aad ( sa ) - > label = & profile - > label ;
aad ( sa ) - > peer = & target - > label ;
aad ( sa ) - > request = request ;
aa_profile_match_label ( profile , & target - > label , type , request , & perms ) ;
aa_apply_modes_to_perms ( profile , & perms ) ;
* deny | = request & perms . deny ;
return aa_check_perms ( profile , & perms , request , sa , aa_audit_perms_cb ) ;
}
/**
* aa_check_perms - do audit mode selection based on perms set
* @ profile : profile being checked
* @ perms : perms computed for the request
* @ request : requested perms
* @ deny : Returns : explicit deny set
* @ sa : initialized audit structure ( MAY BE NULL if not auditing )
2018-04-12 13:34:31 +03:00
* @ cb : callback fn for type specific fields ( MAY BE NULL )
2017-06-09 18:14:28 +03:00
*
* Returns : 0 if permission else error code
*
* Note : profile audit modes need to be set before calling by setting the
* perm masks appropriately .
*
* If not auditing then complain mode is not enabled and the
* error code will indicate whether there was an explicit deny
* with a positive value .
*/
int aa_check_perms ( struct aa_profile * profile , struct aa_perms * perms ,
u32 request , struct common_audit_data * sa ,
void ( * cb ) ( struct audit_buffer * , void * ) )
{
int type , error ;
u32 denied = request & ( ~ perms - > allow | perms - > deny ) ;
if ( likely ( ! denied ) ) {
/* mask off perms that are not being force audited */
request & = perms - > audit ;
if ( ! request | | ! sa )
return 0 ;
type = AUDIT_APPARMOR_AUDIT ;
error = 0 ;
} else {
error = - EACCES ;
if ( denied & perms - > kill )
type = AUDIT_APPARMOR_KILL ;
else if ( denied = = ( denied & perms - > complain ) )
type = AUDIT_APPARMOR_ALLOWED ;
else
type = AUDIT_APPARMOR_DENIED ;
if ( denied = = ( denied & perms - > hide ) )
error = - ENOENT ;
denied & = ~ perms - > quiet ;
if ( ! sa | | ! denied )
return error ;
}
if ( sa ) {
aad ( sa ) - > label = & profile - > label ;
aad ( sa ) - > request = request ;
aad ( sa ) - > denied = denied ;
aad ( sa ) - > error = error ;
aa_audit_msg ( type , sa , cb ) ;
}
if ( type = = AUDIT_APPARMOR_ALLOWED )
error = 0 ;
return error ;
}
2017-01-16 11:42:14 +03:00
/**
* aa_policy_init - initialize a policy structure
* @ policy : policy to initialize ( NOT NULL )
* @ prefix : prefix name if any is required . ( MAYBE NULL )
* @ name : name of the policy , init will make a copy of it ( NOT NULL )
2017-06-09 17:09:05 +03:00
* @ gfp : allocation mode
2017-01-16 11:42:14 +03:00
*
* Note : this fn creates a copy of strings passed in
*
* Returns : true if policy init successful
*/
bool aa_policy_init ( struct aa_policy * policy , const char * prefix ,
2017-01-16 11:42:31 +03:00
const char * name , gfp_t gfp )
2017-01-16 11:42:14 +03:00
{
2017-06-09 17:09:05 +03:00
char * hname ;
2017-01-16 11:42:14 +03:00
/* freed by policy_free */
if ( prefix ) {
2017-06-09 17:09:05 +03:00
hname = aa_str_alloc ( strlen ( prefix ) + strlen ( name ) + 3 , gfp ) ;
if ( hname )
sprintf ( hname , " %s//%s " , prefix , name ) ;
} else {
hname = aa_str_alloc ( strlen ( name ) + 1 , gfp ) ;
if ( hname )
strcpy ( hname , name ) ;
}
if ( ! hname )
2017-04-06 16:55:19 +03:00
return false ;
2017-06-09 17:09:05 +03:00
policy - > hname = hname ;
2017-01-16 11:42:14 +03:00
/* base.name is a substring of fqname */
2017-01-16 11:42:31 +03:00
policy - > name = basename ( policy - > hname ) ;
2017-01-16 11:42:14 +03:00
INIT_LIST_HEAD ( & policy - > list ) ;
INIT_LIST_HEAD ( & policy - > profiles ) ;
2017-04-06 16:55:19 +03:00
return true ;
2017-01-16 11:42:14 +03:00
}
/**
* aa_policy_destroy - free the elements referenced by @ policy
* @ policy : policy that is to have its elements freed ( NOT NULL )
*/
void aa_policy_destroy ( struct aa_policy * policy )
{
2017-01-16 11:42:32 +03:00
AA_BUG ( on_list_rcu ( & policy - > profiles ) ) ;
AA_BUG ( on_list_rcu ( & policy - > list ) ) ;
2017-01-16 11:42:14 +03:00
/* don't free name as its a subset of hname */
2017-06-09 17:09:05 +03:00
aa_put_str ( policy - > hname ) ;
2017-01-16 11:42:14 +03:00
}