2022-08-21 22:48:32 -07:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor functions for unpacking policy loaded
* from userspace .
*
* Copyright ( C ) 1998 - 2008 Novell / SUSE
* Copyright 2009 - 2022 Canonical Ltd .
*
* Code to provide backwards compatibility with older policy versions ,
* by converting / mapping older policy formats into the newer internal
* formats .
*/
# include <linux/ctype.h>
# include <linux/errno.h>
# include "include/lib.h"
# include "include/policy_unpack.h"
# include "include/policy_compat.h"
/* remap old accept table embedded permissions to separate permission table */
static u32 dfa_map_xindex ( u16 mask )
{
u16 old_index = ( mask > > 10 ) & 0xf ;
u32 index = 0 ;
if ( mask & 0x100 )
index | = AA_X_UNSAFE ;
if ( mask & 0x200 )
index | = AA_X_INHERIT ;
if ( mask & 0x80 )
index | = AA_X_UNCONFINED ;
if ( old_index = = 1 ) {
index | = AA_X_UNCONFINED ;
} else if ( old_index = = 2 ) {
index | = AA_X_NAME ;
} else if ( old_index = = 3 ) {
index | = AA_X_NAME | AA_X_CHILD ;
} else if ( old_index ) {
index | = AA_X_TABLE ;
index | = old_index - 4 ;
}
return index ;
}
/*
* map old dfa inline permissions to new format
*/
# define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
( ( ACCEPT_TABLE ( dfa ) [ state ] ) & 0x80000000 ) )
# define dfa_user_xbits(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 7) & 0x7f)
# define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
# define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
# define dfa_user_xindex(dfa, state) \
( dfa_map_xindex ( ACCEPT_TABLE ( dfa ) [ state ] & 0x3fff ) )
# define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
0x7f ) | \
( ( ACCEPT_TABLE ( dfa ) [ state ] ) & 0x80000000 ) )
# define dfa_other_xbits(dfa, state) \
( ( ( ( ACCEPT_TABLE ( dfa ) [ state ] ) > > 7 ) > > 14 ) & 0x7f )
# define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
# define dfa_other_quiet(dfa, state) \
( ( ( ( ACCEPT_TABLE2 ( dfa ) [ state ] ) > > 7 ) > > 14 ) & 0x7f )
# define dfa_other_xindex(dfa, state) \
dfa_map_xindex ( ( ACCEPT_TABLE ( dfa ) [ state ] > > 14 ) & 0x3fff )
/**
* map_old_perms - map old file perms layout to the new layout
* @ old : permission set in old mapping
*
* Returns : new permission mapping
*/
static u32 map_old_perms ( u32 old )
{
u32 new = old & 0xf ;
if ( old & MAY_READ )
new | = AA_MAY_GETATTR | AA_MAY_OPEN ;
if ( old & MAY_WRITE )
new | = AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN ;
if ( old & 0x10 )
new | = AA_MAY_LINK ;
/* the old mapping lock and link_subset flags where overlaid
* and use was determined by part of a pair that they were in
*/
if ( old & 0x20 )
new | = AA_MAY_LOCK | AA_LINK_SUBSET ;
if ( old & 0x40 ) /* AA_EXEC_MMAP */
new | = AA_EXEC_MMAP ;
return new ;
}
static void compute_fperms_allow ( struct aa_perms * perms , struct aa_dfa * dfa ,
aa_state_t state )
{
perms - > allow | = AA_MAY_GETATTR ;
/* change_profile wasn't determined by ownership in old mapping */
if ( ACCEPT_TABLE ( dfa ) [ state ] & 0x80000000 )
perms - > allow | = AA_MAY_CHANGE_PROFILE ;
if ( ACCEPT_TABLE ( dfa ) [ state ] & 0x40000000 )
perms - > allow | = AA_MAY_ONEXEC ;
}
static struct aa_perms compute_fperms_user ( struct aa_dfa * dfa ,
aa_state_t state )
{
struct aa_perms perms = { } ;
perms . allow = map_old_perms ( dfa_user_allow ( dfa , state ) ) ;
perms . audit = map_old_perms ( dfa_user_audit ( dfa , state ) ) ;
perms . quiet = map_old_perms ( dfa_user_quiet ( dfa , state ) ) ;
perms . xindex = dfa_user_xindex ( dfa , state ) ;
compute_fperms_allow ( & perms , dfa , state ) ;
return perms ;
}
static struct aa_perms compute_fperms_other ( struct aa_dfa * dfa ,
aa_state_t state )
{
struct aa_perms perms = { } ;
perms . allow = map_old_perms ( dfa_other_allow ( dfa , state ) ) ;
perms . audit = map_old_perms ( dfa_other_audit ( dfa , state ) ) ;
perms . quiet = map_old_perms ( dfa_other_quiet ( dfa , state ) ) ;
perms . xindex = dfa_other_xindex ( dfa , state ) ;
compute_fperms_allow ( & perms , dfa , state ) ;
return perms ;
}
/**
2022-09-24 22:25:25 -07:00
* compute_fperms - convert dfa compressed perms to internal perms and store
* them so they can be retrieved later .
2022-08-21 22:48:32 -07:00
* @ dfa : a dfa using fperms to remap to internal permissions
*
* Returns : remapped perm table
*/
2023-04-17 02:57:55 -07:00
static struct aa_perms * compute_fperms ( struct aa_dfa * dfa ,
u32 * size )
2022-08-21 22:48:32 -07:00
{
aa_state_t state ;
unsigned int state_count ;
struct aa_perms * table ;
AA_BUG ( ! dfa ) ;
state_count = dfa - > tables [ YYTD_ID_BASE ] - > td_lolen ;
/* DFAs are restricted from having a state_count of less than 2 */
table = kvcalloc ( state_count * 2 , sizeof ( struct aa_perms ) , GFP_KERNEL ) ;
if ( ! table )
return NULL ;
2023-04-17 02:57:55 -07:00
* size = state_count * 2 ;
2022-08-21 22:48:32 -07:00
2023-02-14 20:21:17 -08:00
for ( state = 0 ; state < state_count ; state + + ) {
2022-08-21 22:48:32 -07:00
table [ state * 2 ] = compute_fperms_user ( dfa , state ) ;
table [ state * 2 + 1 ] = compute_fperms_other ( dfa , state ) ;
}
return table ;
}
2023-04-17 02:57:55 -07:00
static struct aa_perms * compute_xmatch_perms ( struct aa_dfa * xmatch ,
u32 * size )
2022-08-21 22:48:32 -07:00
{
struct aa_perms * perms ;
int state ;
int state_count ;
AA_BUG ( ! xmatch ) ;
state_count = xmatch - > tables [ YYTD_ID_BASE ] - > td_lolen ;
/* DFAs are restricted from having a state_count of less than 2 */
perms = kvcalloc ( state_count , sizeof ( struct aa_perms ) , GFP_KERNEL ) ;
2023-04-14 00:24:47 -07:00
if ( ! perms )
return NULL ;
2023-04-17 02:57:55 -07:00
* size = state_count ;
2022-08-21 22:48:32 -07:00
/* zero init so skip the trap state (state == 0) */
for ( state = 1 ; state < state_count ; state + + )
perms [ state ] . allow = dfa_user_allow ( xmatch , state ) ;
return perms ;
}
static u32 map_other ( u32 x )
{
return ( ( x & 0x3 ) < < 8 ) | /* SETATTR/GETATTR */
( ( x & 0x1c ) < < 18 ) | /* ACCEPT/BIND/LISTEN */
( ( x & 0x60 ) < < 19 ) ; /* SETOPT/GETOPT */
}
static u32 map_xbits ( u32 x )
{
return ( ( x & 0x1 ) < < 7 ) |
( ( x & 0x7e ) < < 9 ) ;
}
static struct aa_perms compute_perms_entry ( struct aa_dfa * dfa ,
aa_state_t state ,
u32 version )
{
struct aa_perms perms = { } ;
perms . allow = dfa_user_allow ( dfa , state ) ;
perms . audit = dfa_user_audit ( dfa , state ) ;
perms . quiet = dfa_user_quiet ( dfa , state ) ;
/*
* This mapping is convulated due to history .
* v1 - v4 : only file perms , which are handled by compute_fperms
* v5 : added policydb which dropped user conditional to gain new
* perm bits , but had to map around the xbits because the
* userspace compiler was still munging them .
* v9 : adds using the xbits in policydb because the compiler now
* supports treating policydb permission bits different .
* Unfortunately there is no way to force auditing on the
* perms represented by the xbits
*/
perms . allow | = map_other ( dfa_other_allow ( dfa , state ) ) ;
if ( VERSION_LE ( version , v8 ) )
perms . allow | = AA_MAY_LOCK ;
else
perms . allow | = map_xbits ( dfa_user_xbits ( dfa , state ) ) ;
/*
* for v5 - v9 perm mapping in the policydb , the other set is used
* to extend the general perm set
*/
perms . audit | = map_other ( dfa_other_audit ( dfa , state ) ) ;
perms . quiet | = map_other ( dfa_other_quiet ( dfa , state ) ) ;
if ( VERSION_GT ( version , v8 ) )
perms . quiet | = map_xbits ( dfa_other_xbits ( dfa , state ) ) ;
return perms ;
}
2023-04-17 02:57:55 -07:00
static struct aa_perms * compute_perms ( struct aa_dfa * dfa , u32 version ,
u32 * size )
2022-08-21 22:48:32 -07:00
{
unsigned int state ;
unsigned int state_count ;
struct aa_perms * table ;
AA_BUG ( ! dfa ) ;
state_count = dfa - > tables [ YYTD_ID_BASE ] - > td_lolen ;
/* DFAs are restricted from having a state_count of less than 2 */
table = kvcalloc ( state_count , sizeof ( struct aa_perms ) , GFP_KERNEL ) ;
if ( ! table )
return NULL ;
2023-04-17 02:57:55 -07:00
* size = state_count ;
2022-08-21 22:48:32 -07:00
/* zero init so skip the trap state (state == 0) */
for ( state = 1 ; state < state_count ; state + + )
table [ state ] = compute_perms_entry ( dfa , state , version ) ;
return table ;
}
/**
* remap_dfa_accept - remap old dfa accept table to be an index
* @ dfa : dfa to do the remapping on
* @ factor : scaling factor for the index conversion .
*
* Used in conjunction with compute_Xperms , it converts old style perms
* that are encoded in the dfa accept tables to the new style where
* there is a permission table and the accept table is an index into
* the permission table .
*/
static void remap_dfa_accept ( struct aa_dfa * dfa , unsigned int factor )
{
unsigned int state ;
unsigned int state_count = dfa - > tables [ YYTD_ID_BASE ] - > td_lolen ;
AA_BUG ( ! dfa ) ;
for ( state = 0 ; state < state_count ; state + + )
ACCEPT_TABLE ( dfa ) [ state ] = state * factor ;
kvfree ( dfa - > tables [ YYTD_ID_ACCEPT2 ] ) ;
dfa - > tables [ YYTD_ID_ACCEPT2 ] = NULL ;
}
/* TODO: merge different dfa mappings into single map_policy fn */
int aa_compat_map_xmatch ( struct aa_policydb * policy )
{
2023-04-17 02:57:55 -07:00
policy - > perms = compute_xmatch_perms ( policy - > dfa , & policy - > size ) ;
2022-08-21 22:48:32 -07:00
if ( ! policy - > perms )
return - ENOMEM ;
remap_dfa_accept ( policy - > dfa , 1 ) ;
return 0 ;
}
int aa_compat_map_policy ( struct aa_policydb * policy , u32 version )
{
2023-04-17 02:57:55 -07:00
policy - > perms = compute_perms ( policy - > dfa , version , & policy - > size ) ;
2022-08-21 22:48:32 -07:00
if ( ! policy - > perms )
return - ENOMEM ;
remap_dfa_accept ( policy - > dfa , 1 ) ;
return 0 ;
}
int aa_compat_map_file ( struct aa_policydb * policy )
{
2023-04-17 02:57:55 -07:00
policy - > perms = compute_fperms ( policy - > dfa , & policy - > size ) ;
2022-08-21 22:48:32 -07:00
if ( ! policy - > perms )
return - ENOMEM ;
remap_dfa_accept ( policy - > dfa , 2 ) ;
return 0 ;
}