Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
linux
Commits
93c98a48
Commit
93c98a48
authored
Jun 09, 2017
by
John Johansen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
apparmor: move exec domain mediation to using labels
Signed-off-by:
John Johansen
<
john.johansen@canonical.com
>
parent
5379a331
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
678 additions
and
259 deletions
+678
-259
security/apparmor/domain.c
security/apparmor/domain.c
+592
-258
security/apparmor/include/lib.h
security/apparmor/include/lib.h
+86
-1
No files found.
security/apparmor/domain.c
View file @
93c98a48
...
...
@@ -87,42 +87,236 @@ static int may_change_ptraced_domain(struct aa_label *to_label,
return
error
;
}
/**** TODO: dedup to aa_label_match - needs perm and dfa, merging
* specifically this is an exact copy of aa_label_match except
* aa_compute_perms is replaced with aa_compute_fperms
* and policy.dfa with file.dfa
****/
/* match a profile and its associated ns component if needed
* Assumes visibility test has already been done.
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
static
inline
unsigned
int
match_component
(
struct
aa_profile
*
profile
,
struct
aa_profile
*
tp
,
bool
stack
,
unsigned
int
state
)
{
const
char
*
ns_name
;
if
(
stack
)
state
=
aa_dfa_match
(
profile
->
file
.
dfa
,
state
,
"&"
);
if
(
profile
->
ns
==
tp
->
ns
)
return
aa_dfa_match
(
profile
->
file
.
dfa
,
state
,
tp
->
base
.
hname
);
/* try matching with namespace name and then profile */
ns_name
=
aa_ns_name
(
profile
->
ns
,
tp
->
ns
,
true
);
state
=
aa_dfa_match_len
(
profile
->
file
.
dfa
,
state
,
":"
,
1
);
state
=
aa_dfa_match
(
profile
->
file
.
dfa
,
state
,
ns_name
);
state
=
aa_dfa_match_len
(
profile
->
file
.
dfa
,
state
,
":"
,
1
);
return
aa_dfa_match
(
profile
->
file
.
dfa
,
state
,
tp
->
base
.
hname
);
}
/**
* label_compound_match - find perms for full compound label
* @profile: profile to find perms for
* @label: label to check access permissions for
* @stack: whether this is a stacking request
* @start: state to start match in
* @subns: whether to do permission checks on components in a subns
* @request: permissions to request
* @perms: perms struct to set
*
* Returns: 0 on success else ERROR
*
* For the label A//&B//&C this does the perm match for A//&B//&C
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
static
int
label_compound_match
(
struct
aa_profile
*
profile
,
struct
aa_label
*
label
,
bool
stack
,
unsigned
int
state
,
bool
subns
,
u32
request
,
struct
aa_perms
*
perms
)
{
struct
aa_profile
*
tp
;
struct
label_it
i
;
struct
path_cond
cond
=
{
};
/* find first subcomponent that is visible */
label_for_each
(
i
,
label
,
tp
)
{
if
(
!
aa_ns_visible
(
profile
->
ns
,
tp
->
ns
,
subns
))
continue
;
state
=
match_component
(
profile
,
tp
,
stack
,
state
);
if
(
!
state
)
goto
fail
;
goto
next
;
}
/* no component visible */
*
perms
=
allperms
;
return
0
;
next:
label_for_each_cont
(
i
,
label
,
tp
)
{
if
(
!
aa_ns_visible
(
profile
->
ns
,
tp
->
ns
,
subns
))
continue
;
state
=
aa_dfa_match
(
profile
->
file
.
dfa
,
state
,
"//&"
);
state
=
match_component
(
profile
,
tp
,
false
,
state
);
if
(
!
state
)
goto
fail
;
}
*
perms
=
aa_compute_fperms
(
profile
->
file
.
dfa
,
state
,
&
cond
);
aa_apply_modes_to_perms
(
profile
,
perms
);
if
((
perms
->
allow
&
request
)
!=
request
)
return
-
EACCES
;
return
0
;
fail:
*
perms
=
nullperms
;
return
-
EACCES
;
}
/**
* label_components_match - find perms for all subcomponents of a label
* @profile: profile to find perms for
* @label: label to check access permissions for
* @stack: whether this is a stacking request
* @start: state to start match in
* @subns: whether to do permission checks on components in a subns
* @request: permissions to request
* @perms: an initialized perms struct to add accumulation to
*
* Returns: 0 on success else ERROR
*
* For the label A//&B//&C this does the perm match for each of A and B and C
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
static
int
label_components_match
(
struct
aa_profile
*
profile
,
struct
aa_label
*
label
,
bool
stack
,
unsigned
int
start
,
bool
subns
,
u32
request
,
struct
aa_perms
*
perms
)
{
struct
aa_profile
*
tp
;
struct
label_it
i
;
struct
aa_perms
tmp
;
struct
path_cond
cond
=
{
};
unsigned
int
state
=
0
;
/* find first subcomponent to test */
label_for_each
(
i
,
label
,
tp
)
{
if
(
!
aa_ns_visible
(
profile
->
ns
,
tp
->
ns
,
subns
))
continue
;
state
=
match_component
(
profile
,
tp
,
stack
,
start
);
if
(
!
state
)
goto
fail
;
goto
next
;
}
/* no subcomponents visible - no change in perms */
return
0
;
next:
tmp
=
aa_compute_fperms
(
profile
->
file
.
dfa
,
state
,
&
cond
);
aa_apply_modes_to_perms
(
profile
,
&
tmp
);
aa_perms_accum
(
perms
,
&
tmp
);
label_for_each_cont
(
i
,
label
,
tp
)
{
if
(
!
aa_ns_visible
(
profile
->
ns
,
tp
->
ns
,
subns
))
continue
;
state
=
match_component
(
profile
,
tp
,
stack
,
start
);
if
(
!
state
)
goto
fail
;
tmp
=
aa_compute_fperms
(
profile
->
file
.
dfa
,
state
,
&
cond
);
aa_apply_modes_to_perms
(
profile
,
&
tmp
);
aa_perms_accum
(
perms
,
&
tmp
);
}
if
((
perms
->
allow
&
request
)
!=
request
)
return
-
EACCES
;
return
0
;
fail:
*
perms
=
nullperms
;
return
-
EACCES
;
}
/**
* label_match - do a multi-component label match
* @profile: profile to match against (NOT NULL)
* @label: label to match (NOT NULL)
* @stack: whether this is a stacking request
* @state: state to start in
* @subns: whether to match subns components
* @request: permission request
* @perms: Returns computed perms (NOT NULL)
*
* Returns: the state the match finished in, may be the none matching state
*/
static
int
label_match
(
struct
aa_profile
*
profile
,
struct
aa_label
*
label
,
bool
stack
,
unsigned
int
state
,
bool
subns
,
u32
request
,
struct
aa_perms
*
perms
)
{
int
error
;
*
perms
=
nullperms
;
error
=
label_compound_match
(
profile
,
label
,
stack
,
state
,
subns
,
request
,
perms
);
if
(
!
error
)
return
error
;
*
perms
=
allperms
;
return
label_components_match
(
profile
,
label
,
stack
,
state
,
subns
,
request
,
perms
);
}
/******* end TODO: dedup *****/
/**
* change_profile_perms - find permissions for change_profile
* @profile: the current profile (NOT NULL)
* @
ns: the namespace being switched to
(NOT NULL)
* @
name: the name of the profile to change to (NOT NULL)
* @
target: label to transition to
(NOT NULL)
* @
stack: whether this is a stacking request
* @request: requested perms
* @start: state to start matching in
*
*
* Returns: permission set
*
* currently only matches full label A//&B//&C or individual components A, B, C
* not arbitrary combinations. Eg. A//&B, C
*/
static
struct
aa_perms
change_profile_perms
(
struct
aa_profile
*
profile
,
struct
aa_ns
*
ns
,
const
char
*
name
,
u32
request
,
static
int
change_profile_perms
(
struct
aa_profile
*
profile
,
struct
aa_label
*
target
,
bool
stack
,
u32
request
,
unsigned
int
start
,
struct
aa_perms
*
perms
)
{
if
(
profile_unconfined
(
profile
))
{
perms
->
allow
=
AA_MAY_CHANGE_PROFILE
|
AA_MAY_ONEXEC
;
perms
->
audit
=
perms
->
quiet
=
perms
->
kill
=
0
;
return
0
;
}
/* TODO: add profile in ns screening */
return
label_match
(
profile
,
target
,
stack
,
start
,
true
,
request
,
perms
);
}
static
struct
aa_perms
change_profile_perms_wrapper
(
struct
aa_profile
*
profile
,
struct
aa_profile
*
target
,
u32
request
,
unsigned
int
start
)
{
struct
aa_perms
perms
;
struct
path_cond
cond
=
{
};
unsigned
int
state
;
if
(
profile_unconfined
(
profile
))
{
perms
.
allow
=
AA_MAY_CHANGE_PROFILE
|
AA_MAY_ONEXEC
;
perms
.
audit
=
perms
.
quiet
=
perms
.
kill
=
0
;
return
perms
;
}
else
if
(
!
profile
->
file
.
dfa
)
{
return
nullperms
;
}
else
if
((
ns
==
profile
->
ns
))
{
/* try matching against rules with out namespace prepended */
aa_str_perms
(
profile
->
file
.
dfa
,
start
,
name
,
&
cond
,
&
perms
);
if
(
COMBINED_PERM_MASK
(
perms
)
&
request
)
return
perms
;
}
/* try matching with namespace name and then profile */
state
=
aa_dfa_match
(
profile
->
file
.
dfa
,
start
,
ns
->
base
.
name
);
state
=
aa_dfa_match_len
(
profile
->
file
.
dfa
,
state
,
":"
,
1
);
aa_str_perms
(
profile
->
file
.
dfa
,
state
,
name
,
&
cond
,
&
perms
);
if
(
change_profile_perms
(
profile
,
&
target
->
label
,
false
,
request
,
start
,
&
perms
))
return
nullperms
;
return
perms
;
}
...
...
@@ -173,10 +367,10 @@ static struct aa_profile *__attach_match(const char *name,
* @list: list to search (NOT NULL)
* @name: the executable name to match against (NOT NULL)
*
* Returns:
profile
or NULL if no match found
* Returns:
label
or NULL if no match found
*/
static
struct
aa_
profile
*
find_attach
(
struct
aa_ns
*
ns
,
struct
list_head
*
list
,
const
char
*
name
)
static
struct
aa_
label
*
find_attach
(
struct
aa_ns
*
ns
,
struct
list_head
*
list
,
const
char
*
name
)
{
struct
aa_profile
*
profile
;
...
...
@@ -184,49 +378,7 @@ static struct aa_profile *find_attach(struct aa_ns *ns,
profile
=
aa_get_profile
(
__attach_match
(
name
,
list
));
rcu_read_unlock
();
return
profile
;
}
/**
* separate_fqname - separate the namespace and profile names
* @fqname: the fqname name to split (NOT NULL)
* @ns_name: the namespace name if it exists (NOT NULL)
*
* This is the xtable equivalent routine of aa_split_fqname. It finds the
* split in an xtable fqname which contains an embedded \0 instead of a :
* if a namespace is specified. This is done so the xtable is constant and
* isn't re-split on every lookup.
*
* Either the profile or namespace name may be optional but if the namespace
* is specified the profile name termination must be present. This results
* in the following possible encodings:
* profile_name\0
* :ns_name\0profile_name\0
* :ns_name\0\0
*
* NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table
*
* Returns: profile name if it is specified else NULL
*/
static
const
char
*
separate_fqname
(
const
char
*
fqname
,
const
char
**
ns_name
)
{
const
char
*
name
;
if
(
fqname
[
0
]
==
':'
)
{
/* In this case there is guaranteed to be two \0 terminators
* in the string. They are verified at load time by
* by unpack_trans_table
*/
*
ns_name
=
fqname
+
1
;
/* skip : */
name
=
*
ns_name
+
strlen
(
*
ns_name
)
+
1
;
if
(
!*
name
)
name
=
NULL
;
}
else
{
*
ns_name
=
NULL
;
name
=
fqname
;
}
return
name
;
return
profile
?
&
profile
->
label
:
NULL
;
}
static
const
char
*
next_name
(
int
xtype
,
const
char
*
name
)
...
...
@@ -238,295 +390,477 @@ static const char *next_name(int xtype, const char *name)
* x_table_lookup - lookup an x transition name via transition table
* @profile: current profile (NOT NULL)
* @xindex: index into x transition table
* @name: returns: name tested to find label (NOT NULL)
*
* Returns: refcounted
profile
, or NULL on failure (MAYBE NULL)
* Returns: refcounted
label
, or NULL on failure (MAYBE NULL)
*/
static
struct
aa_profile
*
x_table_lookup
(
struct
aa_profile
*
profile
,
u32
xindex
)
static
struct
aa_label
*
x_table_lookup
(
struct
aa_profile
*
profile
,
u32
xindex
,
const
char
**
name
)
{
struct
aa_profile
*
new_profile
=
NULL
;
struct
aa_ns
*
ns
=
profile
->
ns
;
struct
aa_label
*
label
=
NULL
;
u32
xtype
=
xindex
&
AA_X_TYPE_MASK
;
int
index
=
xindex
&
AA_X_INDEX_MASK
;
const
char
*
name
;
/* index is guaranteed to be in range, validated at load time */
for
(
name
=
profile
->
file
.
trans
.
table
[
index
];
!
new_profile
&&
name
;
name
=
next_name
(
xtype
,
name
))
{
struct
aa_ns
*
new_ns
;
const
char
*
xname
=
NULL
;
AA_BUG
(
!
name
);
new_ns
=
NULL
;
/* index is guaranteed to be in range, validated at load time */
/* TODO: move lookup parsing to unpack time so this is a straight
* index into the resultant label
*/
for
(
*
name
=
profile
->
file
.
trans
.
table
[
index
];
!
label
&&
*
name
;
*
name
=
next_name
(
xtype
,
*
name
))
{
if
(
xindex
&
AA_X_CHILD
)
{
struct
aa_profile
*
new_profile
;
/* release by caller */
new_profile
=
aa_find_child
(
profile
,
name
);
new_profile
=
aa_find_child
(
profile
,
*
name
);
if
(
new_profile
)
label
=
&
new_profile
->
label
;
continue
;
}
else
if
(
*
name
==
':'
)
{
/* switching namespace */
const
char
*
ns_name
;
xname
=
name
=
separate_fqname
(
name
,
&
ns_name
);
if
(
!
xname
)
/* no name so use profile name */
xname
=
profile
->
base
.
hname
;
if
(
*
ns_name
==
'@'
)
{
/* TODO: variable support */
;
}
/* released below */
new_ns
=
aa_find_ns
(
ns
,
ns_name
);
if
(
!
new_ns
)
continue
;
}
else
if
(
*
name
==
'@'
)
{
/* TODO: variable support */
continue
;
}
else
{
/* basic namespace lookup */
xname
=
name
;
label
=
aa_label_parse
(
&
profile
->
label
,
*
name
,
GFP_ATOMIC
,
true
,
false
);
if
(
IS_ERR
(
label
))
label
=
NULL
;
}
/* released by caller */
new_profile
=
aa_lookup_profile
(
new_ns
?
new_ns
:
ns
,
xname
);
aa_put_ns
(
new_ns
);
}
/* released by caller */
return
new_profile
;
return
label
;
}
/**
* x_to_
profile - get target profile
for a given xindex
* x_to_
label - get target label
for a given xindex
* @profile: current profile (NOT NULL)
* @name: name to lookup (NOT NULL)
* @xindex: index into x transition table
* @lookupname: returns: name used in lookup if one was specified (NOT NULL)
*
* find
profile
for a transition index
* find
label
for a transition index
*
* Returns: refcounted
profile
or NULL if not found available
* Returns: refcounted
label
or NULL if not found available
*/
static
struct
aa_profile
*
x_to_profile
(
struct
aa_profile
*
profile
,
const
char
*
name
,
u32
xindex
)
static
struct
aa_label
*
x_to_label
(
struct
aa_profile
*
profile
,
const
char
*
name
,
u32
xindex
,
const
char
**
lookupname
,
const
char
**
info
)
{
struct
aa_
profile
*
new_profile
=
NULL
;
struct
aa_
label
*
new
=
NULL
;
struct
aa_ns
*
ns
=
profile
->
ns
;
u32
xtype
=
xindex
&
AA_X_TYPE_MASK
;
const
char
*
stack
=
NULL
;
switch
(
xtype
)
{
case
AA_X_NONE
:
/* fail exec unless ix || ux fallback - handled by caller */
return
NULL
;
*
lookupname
=
NULL
;
break
;
case
AA_X_TABLE
:
/* TODO: fix when perm mapping done at unload */
stack
=
profile
->
file
.
trans
.
table
[
xindex
&
AA_X_INDEX_MASK
];
if
(
*
stack
!=
'&'
)
{
/* released by caller */
new
=
x_table_lookup
(
profile
,
xindex
,
lookupname
);
stack
=
NULL
;
break
;
}
/* fall through to X_NAME */
case
AA_X_NAME
:
if
(
xindex
&
AA_X_CHILD
)
/* released by caller */
new
_profile
=
find_attach
(
ns
,
&
profile
->
base
.
profiles
,
new
=
find_attach
(
ns
,
&
profile
->
base
.
profiles
,
name
);
else
/* released by caller */
new
_profile
=
find_attach
(
ns
,
&
ns
->
base
.
profiles
,
new
=
find_attach
(
ns
,
&
ns
->
base
.
profiles
,
name
);
*
lookupname
=
name
;
break
;
case
AA_X_TABLE
:
/* released by caller */
new_profile
=
x_table_lookup
(
profile
,
xindex
);
break
;
}
if
(
!
new
)
{
if
(
xindex
&
AA_X_INHERIT
)
{
/* (p|c|n)ix - don't change profile but do
* use the newest version
*/
*
info
=
"ix fallback"
;
/* no profile && no error */
new
=
aa_get_newest_label
(
&
profile
->
label
);
}
else
if
(
xindex
&
AA_X_UNCONFINED
)
{
new
=
aa_get_newest_label
(
ns_unconfined
(
profile
->
ns
));
*
info
=
"ux fallback"
;
}
}
if
(
new
&&
stack
)
{
/* base the stack on post domain transition */
struct
aa_label
*
base
=
new
;
new
=
aa_label_parse
(
base
,
stack
,
GFP_ATOMIC
,
true
,
false
);
if
(
IS_ERR
(
new
))
new
=
NULL
;
aa_put_label
(
base
);
}
/* released by caller */
return
new
_profile
;
return
new
;
}
/**
* apparmor_bprm_set_creds - set the new creds on the bprm struct
* @bprm: binprm for the exec (NOT NULL)
*
* Returns: %0 or error on failure
*/
int
apparmor_bprm_set_creds
(
struct
linux_binprm
*
bprm
)
static
struct
aa_label
*
profile_transition
(
struct
aa_profile
*
profile
,
const
struct
linux_binprm
*
bprm
,
char
*
buffer
,
struct
path_cond
*
cond
,
bool
*
secure_exec
)
{
struct
aa_task_ctx
*
ctx
;
struct
aa_label
*
label
;
struct
aa_profile
*
profile
,
*
new_profile
=
NULL
;
struct
aa_ns
*
ns
;
char
*
buffer
=
NULL
;
unsigned
int
state
;
struct
aa_label
*
new
=
NULL
;
const
char
*
info
=
NULL
,
*
name
=
NULL
,
*
target
=
NULL
;
unsigned
int
state
=
profile
->
file
.
start
;
struct
aa_perms
perms
=
{};
struct
path_cond
cond
=
{
file_inode
(
bprm
->
file
)
->
i_uid
,
file_inode
(
bprm
->
file
)
->
i_mode
};
const
char
*
name
=
NULL
,
*
info
=
NULL
;
bool
nonewprivs
=
false
;
int
error
=
0
;
if
(
bprm
->
cred_prepared
)
return
0
;
ctx
=
cred_ctx
(
bprm
->
cred
);
AA_BUG
(
!
ctx
);
label
=
aa_get_newest_label
(
ctx
->
label
);
profile
=
labels_profile
(
label
);
/* buffer freed below, name is pointer into buffer */
get_buffers
(
buffer
);
/*
* get the namespace from the replacement profile as replacement
* can change the namespace
*/
ns
=
profile
->
ns
;
state
=
profile
->
file
.
start
;
AA_BUG
(
!
profile
);
AA_BUG
(
!
bprm
);
AA_BUG
(
!
buffer
);
error
=
aa_path_name
(
&
bprm
->
file
->
f_path
,
profile
->
path_flags
,
buffer
,
&
name
,
&
info
,
profile
->
disconnected
);
if
(
error
)
{
if
(
profile_unconfined
(
profile
)
||
(
profile
->
label
.
flags
&
FLAG_IX_ON_NAME_ERROR
))
(
profile
->
label
.
flags
&
FLAG_IX_ON_NAME_ERROR
))
{
AA_DEBUG
(
"name lookup ix on error"
);
error
=
0
;
new
=
aa_get_newest_label
(
&
profile
->
label
);
}
name
=
bprm
->
filename
;
goto
audit
;
}
/* Test for onexec first as onexec directives override other
* x transitions.
*/
if
(
profile_unconfined
(
profile
))
{
/* unconfined task */
if
(
ctx
->
onexec
)
/* change_profile on exec already been granted */
new_profile
=
labels_profile
(
aa_get_label
(
ctx
->
onexec
));
else
new_profile
=
find_attach
(
ns
,
&
ns
->
base
.
profiles
,
name
);
if
(
!
new_profile
)
goto
cleanup
;
/*
* NOTE: Domain transitions from unconfined are allowed
* even when no_new_privs is set because this aways results
* in a further reduction of permissions.
*/
goto
apply
;
new
=
find_attach
(
profile
->
ns
,
&
profile
->
ns
->
base
.
profiles
,
name
);
if
(
new
)
{
AA_DEBUG
(
"unconfined attached to new label"
);
return
new
;
}
/* find exec permissions for name */
state
=
aa_str_perms
(
profile
->
file
.
dfa
,
state
,
name
,
&
cond
,
&
perms
);
if
(
ctx
->
onexec
)
{
struct
aa_perms
cp
;
info
=
"change_profile onexec"
;
new_profile
=
labels_profile
(
aa_get_newest_label
(
ctx
->
onexec
));
if
(
!
(
perms
.
allow
&
AA_MAY_ONEXEC
))
goto
audit
;
/* test if this exec can be paired with change_profile onexec.
* onexec permission is linked to exec with a standard pairing
* exec\0change_profile
*/
state
=
aa_dfa_null_transition
(
profile
->
file
.
dfa
,
state
);
cp
=
change_profile_perms
(
profile
,
labels_ns
(
ctx
->
onexec
),
labels_profile
(
ctx
->
onexec
)
->
base
.
name
,
AA_MAY_ONEXEC
,
state
);
if
(
!
(
cp
.
allow
&
AA_MAY_ONEXEC
))
goto
audit
;
goto
apply
;
AA_DEBUG
(
"unconfined exec no attachment"
);
return
aa_get_newest_label
(
&
profile
->
label
);
}
/* find exec permissions for name */
state
=
aa_str_perms
(
profile
->
file
.
dfa
,
state
,
name
,
cond
,
&
perms
);
if
(
perms
.
allow
&
MAY_EXEC
)
{
/* exec permission determine how to transition */
new_profile
=
x_to_profile
(
profile
,
name
,
perms
.
xindex
);
if
(
!
new_profile
)
{
if
(
perms
.
xindex
&
AA_X_INHERIT
)
{
/* (p|c|n)ix - don't change profile but do
* use the newest version, which was picked
* up above when getting profile
*/
info
=
"ix fallback"
;
new_profile
=
aa_get_profile
(
profile
);
goto
x_clear
;
}
else
if
(
perms
.
xindex
&
AA_X_UNCONFINED
)
{
new_profile
=
aa_get_newest_profile
(
ns
->
unconfined
);
info
=
"ux fallback"
;
}
else
{
new
=
x_to_label
(
profile
,
name
,
perms
.
xindex
,
&
target
,
&
info
);
if
(
new
&&
new
->
proxy
==
profile
->
label
.
proxy
&&
info
)
{
/* hack ix fallback - improve how this is detected */
goto
audit
;
}
else
if
(
!
new
)
{
error
=
-
EACCES
;
info
=
"profile
not found"
;
info
=
"profile transition
not found"
;
/* remove MAY_EXEC to audit as failure */
perms
.
allow
&=
~
MAY_EXEC
;
}
}
}
else
if
(
COMPLAIN_MODE
(
profile
))
{
/* no exec permission - are we in learning mode */
new_profile
=
aa_new_null_profile
(
profile
,
false
,
name
,
/* no exec permission - learning mode */
struct
aa_profile
*
new_profile
=
aa_new_null_profile
(
profile
,
false
,
name
,
GFP_ATOMIC
);
if
(
!
new_profile
)
{
error
=
-
ENOMEM
;
info
=
"could not create null profile"
;
}
else
}
else
{
error
=
-
EACCES
;
new
=
&
new_profile
->
label
;
}
perms
.
xindex
|=
AA_X_UNSAFE
;
}
else
/* fail exec */
error
=
-
EACCES
;
/*
* Policy has specified a domain transition, if no_new_privs then
* fail the exec.
if
(
!
new
)
goto
audit
;
/* Policy has specified a domain transitions. if no_new_privs and
* confined and not transitioning to the current domain fail.
*
* NOTE: Domain transitions from unconfined and to stritly stacked
* subsets are allowed even when no_new_privs is set because this
* aways results in a further reduction of permissions.
*/
if
(
bprm
->
unsafe
&
LSM_UNSAFE_NO_NEW_PRIVS
)
{
if
((
bprm
->
unsafe
&
LSM_UNSAFE_NO_NEW_PRIVS
)
&&
!
profile_unconfined
(
profile
)
&&
!
aa_label_is_subset
(
new
,
&
profile
->
label
))
{
error
=
-
EPERM
;
goto
cleanup
;
info
=
"no new privs"
;
nonewprivs
=
true
;
perms
.
allow
&=
~
MAY_EXEC
;
goto
audit
;
}
if
(
!
new_profile
)
if
(
!
(
perms
.
xindex
&
AA_X_UNSAFE
))
{
if
(
DEBUG_ON
)
{
dbg_printk
(
"apparmor: scrubbing environment variables"
" for %s profile="
,
name
);
aa_label_printk
(
new
,
GFP_ATOMIC
);
dbg_printk
(
"
\n
"
);
}
*
secure_exec
=
true
;
}
audit:
aa_audit_file
(
profile
,
&
perms
,
OP_EXEC
,
MAY_EXEC
,
name
,
target
,
new
,
cond
->
uid
,
info
,
error
);
if
(
!
new
||
nonewprivs
)
{
aa_put_label
(
new
);
return
ERR_PTR
(
error
);
}
return
new
;
}
static
int
profile_onexec
(
struct
aa_profile
*
profile
,
struct
aa_label
*
onexec
,
bool
stack
,
const
struct
linux_binprm
*
bprm
,
char
*
buffer
,
struct
path_cond
*
cond
,
bool
*
secure_exec
)
{
unsigned
int
state
=
profile
->
file
.
start
;
struct
aa_perms
perms
=
{};
const
char
*
xname
=
NULL
,
*
info
=
"change_profile onexec"
;
int
error
=
-
EACCES
;
AA_BUG
(
!
profile
);
AA_BUG
(
!
onexec
);
AA_BUG
(
!
bprm
);
AA_BUG
(
!
buffer
);
if
(
profile_unconfined
(
profile
))
{
/* change_profile on exec already granted */
/*
* NOTE: Domain transitions from unconfined are allowed
* even when no_new_privs is set because this aways results
* in a further reduction of permissions.
*/
return
0
;
}
error
=
aa_path_name
(
&
bprm
->
file
->
f_path
,
profile
->
path_flags
,
buffer
,
&
xname
,
&
info
,
profile
->
disconnected
);
if
(
error
)
{
if
(
profile_unconfined
(
profile
)
||
(
profile
->
label
.
flags
&
FLAG_IX_ON_NAME_ERROR
))
{
AA_DEBUG
(
"name lookup ix on error"
);
error
=
0
;
}
xname
=
bprm
->
filename
;
goto
audit
;
}
/* find exec permissions for name */
state
=
aa_str_perms
(
profile
->
file
.
dfa
,
state
,
xname
,
cond
,
&
perms
);
if
(
!
(
perms
.
allow
&
AA_MAY_ONEXEC
))
{
info
=
"no change_onexec valid for executable"
;
goto
audit
;
}
/* test if this exec can be paired with change_profile onexec.
* onexec permission is linked to exec with a standard pairing
* exec\0change_profile
*/
state
=
aa_dfa_null_transition
(
profile
->
file
.
dfa
,
state
);
error
=
change_profile_perms
(
profile
,
onexec
,
stack
,
AA_MAY_ONEXEC
,
state
,
&
perms
);
if
(
error
)
{
perms
.
allow
&=
~
AA_MAY_ONEXEC
;
goto
audit
;
}
/* Policy has specified a domain transitions. if no_new_privs and
* confined and not transitioning to the current domain fail.
*
* NOTE: Domain transitions from unconfined and to stritly stacked
* subsets are allowed even when no_new_privs is set because this
* aways results in a further reduction of permissions.
*/
if
((
bprm
->
unsafe
&
LSM_UNSAFE_NO_NEW_PRIVS
)
&&
!
profile_unconfined
(
profile
)
&&
!
aa_label_is_subset
(
onexec
,
&
profile
->
label
))
{
error
=
-
EPERM
;
info
=
"no new privs"
;
perms
.
allow
&=
~
AA_MAY_ONEXEC
;
goto
audit
;
}
if
(
!
(
perms
.
xindex
&
AA_X_UNSAFE
))
{
if
(
DEBUG_ON
)
{
dbg_printk
(
"apparmor: scrubbing environment "
"variables for %s label="
,
xname
);
aa_label_printk
(
onexec
,
GFP_ATOMIC
);
dbg_printk
(
"
\n
"
);
}
*
secure_exec
=
true
;
}
audit:
return
aa_audit_file
(
profile
,
&
perms
,
OP_EXEC
,
AA_MAY_ONEXEC
,
xname
,
NULL
,
onexec
,
cond
->
uid
,
info
,
error
);
}
/* ensure none ns domain transitions are correctly applied with onexec */
static
struct
aa_label
*
handle_onexec
(
struct
aa_label
*
label
,
struct
aa_label
*
onexec
,
bool
stack
,
const
struct
linux_binprm
*
bprm
,
char
*
buffer
,
struct
path_cond
*
cond
,
bool
*
unsafe
)
{
struct
aa_profile
*
profile
;
struct
aa_label
*
new
;
int
error
;
AA_BUG
(
!
label
);
AA_BUG
(
!
onexec
);
AA_BUG
(
!
bprm
);
AA_BUG
(
!
buffer
);
if
(
!
stack
)
{
error
=
fn_for_each_in_ns
(
label
,
profile
,
profile_onexec
(
profile
,
onexec
,
stack
,
bprm
,
buffer
,
cond
,
unsafe
));
if
(
error
)
return
ERR_PTR
(
error
);
new
=
fn_label_build_in_ns
(
label
,
profile
,
GFP_ATOMIC
,
aa_get_newest_label
(
onexec
),
profile_transition
(
profile
,
bprm
,
buffer
,
cond
,
unsafe
));
}
else
{
/* TODO: determine how much we want to losen this */
error
=
fn_for_each_in_ns
(
label
,
profile
,
profile_onexec
(
profile
,
onexec
,
stack
,
bprm
,
buffer
,
cond
,
unsafe
));
if
(
error
)
return
ERR_PTR
(
error
);
new
=
fn_label_build_in_ns
(
label
,
profile
,
GFP_ATOMIC
,
aa_label_merge
(
&
profile
->
label
,
onexec
,
GFP_ATOMIC
),
profile_transition
(
profile
,
bprm
,
buffer
,
cond
,
unsafe
));
}
if
(
new
)
return
new
;
/* TODO: get rid of GLOBAL_ROOT_UID */
error
=
fn_for_each_in_ns
(
label
,
profile
,
aa_audit_file
(
profile
,
&
nullperms
,
OP_CHANGE_ONEXEC
,
AA_MAY_ONEXEC
,
bprm
->
filename
,
NULL
,
onexec
,
GLOBAL_ROOT_UID
,
"failed to build target label"
,
-
ENOMEM
));
return
ERR_PTR
(
error
);
}
/**
* apparmor_bprm_set_creds - set the new creds on the bprm struct
* @bprm: binprm for the exec (NOT NULL)
*
* Returns: %0 or error on failure
*
* TODO: once the other paths are done see if we can't refactor into a fn
*/
int
apparmor_bprm_set_creds
(
struct
linux_binprm
*
bprm
)
{
struct
aa_task_ctx
*
ctx
;
struct
aa_label
*
label
,
*
new
=
NULL
;
struct
aa_profile
*
profile
;
char
*
buffer
=
NULL
;
const
char
*
info
=
NULL
;
int
error
=
0
;
bool
unsafe
=
false
;
struct
path_cond
cond
=
{
file_inode
(
bprm
->
file
)
->
i_uid
,
file_inode
(
bprm
->
file
)
->
i_mode
};
if
(
bprm
->
cred_prepared
)
return
0
;
ctx
=
cred_ctx
(
bprm
->
cred
);
AA_BUG
(
!
ctx
);
label
=
aa_get_newest_label
(
ctx
->
label
);
/* buffer freed below, name is pointer into buffer */
get_buffers
(
buffer
);
/* Test for onexec first as onexec override other x transitions. */
if
(
ctx
->
onexec
)
new
=
handle_onexec
(
label
,
ctx
->
onexec
,
ctx
->
token
,
bprm
,
buffer
,
&
cond
,
&
unsafe
);
else
new
=
fn_label_build
(
label
,
profile
,
GFP_ATOMIC
,
profile_transition
(
profile
,
bprm
,
buffer
,
&
cond
,
&
unsafe
));
AA_BUG
(
!
new
);
if
(
IS_ERR
(
new
))
{
error
=
PTR_ERR
(
new
);
goto
done
;
}
else
if
(
!
new
)
{
error
=
-
ENOMEM
;
goto
done
;
}
/* TODO: Add ns level no_new_privs subset test */
if
(
bprm
->
unsafe
&
LSM_UNSAFE_SHARE
)
{
/* FIXME: currently don't mediate shared state */
;
}
if
(
bprm
->
unsafe
&
LSM_UNSAFE_PTRACE
)
{
error
=
may_change_ptraced_domain
(
&
new_profile
->
label
,
&
info
);
if
(
bprm
->
unsafe
&
(
LSM_UNSAFE_PTRACE
))
{
/* TODO: test needs to be profile of label to new */
error
=
may_change_ptraced_domain
(
new
,
&
info
);
if
(
error
)
goto
audit
;
}
/* Determine if secure exec is needed.
* Can be at this point for the following reasons:
* 1. unconfined switching to confined
* 2. confined switching to different confinement
* 3. confined switching to unconfined
*
* Cases 2 and 3 are marked as requiring secure exec
* (unless policy specified "unsafe exec")
*
* bprm->unsafe is used to cache the AA_X_UNSAFE permission
* to avoid having to recompute in secureexec
*/
if
(
!
(
perms
.
xindex
&
AA_X_UNSAFE
))
{
AA_DEBUG
(
"scrubbing environment variables for %s profile=%s
\n
"
,
name
,
new_profile
->
base
.
hname
);
if
(
unsafe
)
{
if
(
DEBUG_ON
)
{
dbg_printk
(
"scrubbing environment variables for %s "
"label="
,
bprm
->
filename
);
aa_label_printk
(
new
,
GFP_ATOMIC
);
dbg_printk
(
"
\n
"
);
}
bprm
->
unsafe
|=
AA_SECURE_X_NEEDED
;
}
apply:
/* when transitioning profiles clear unsafe personality bits */
bprm
->
per_clear
|=
PER_CLEAR_ON_SETID
;
x_clear:
if
(
label
->
proxy
!=
new
->
proxy
)
{
/* when transitioning clear unsafe personality bits */
if
(
DEBUG_ON
)
{
dbg_printk
(
"apparmor: clearing unsafe personality "
"bits. %s label="
,
bprm
->
filename
);
aa_label_printk
(
new
,
GFP_ATOMIC
);
dbg_printk
(
"
\n
"
);
}
bprm
->
per_clear
|=
PER_CLEAR_ON_SETID
;
}
aa_put_label
(
ctx
->
label
);
/* transfer new profile reference will be released when ctx is freed */
ctx
->
label
=
&
new_profile
->
label
;
new_profile
=
NULL
;
/* transfer reference, released when ctx is freed */
ctx
->
label
=
new
;
/* clear out all temporary/transitional state from the context */
done:
/* clear out temporary/transitional state from the context */
aa_clear_task_ctx_trans
(
ctx
);
audit:
error
=
aa_audit_file
(
profile
,
&
perms
,
OP_EXEC
,
MAY_EXEC
,
name
,
new_profile
?
new_profile
->
base
.
hname
:
NULL
,
new_profile
?
&
new_profile
->
label
:
NULL
,
cond
.
uid
,
info
,
error
);
cleanup:
aa_put_profile
(
new_profile
);
aa_put_label
(
label
);
put_buffers
(
buffer
);
return
error
;
audit:
error
=
fn_for_each
(
label
,
profile
,
aa_audit_file
(
profile
,
&
nullperms
,
OP_EXEC
,
MAY_EXEC
,
bprm
->
filename
,
NULL
,
new
,
file_inode
(
bprm
->
file
)
->
i_uid
,
info
,
error
));
aa_put_label
(
new
);
goto
done
;
}
/**
...
...
@@ -778,8 +1112,8 @@ int aa_change_profile(const char *fqname, int flags)
}
}
perms
=
change_profile_perms
(
profile
,
target
->
ns
,
target
->
base
.
hname
,
request
,
profile
->
file
.
start
);
perms
=
change_profile_perms
_wrapper
(
profile
,
target
,
request
,
profile
->
file
.
start
);
if
(
!
(
perms
.
allow
&
request
))
{
error
=
-
EACCES
;
goto
audit
;
...
...
security/apparmor/include/lib.h
View file @
93c98a48
...
...
@@ -211,4 +211,89 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix,
const
char
*
name
,
gfp_t
gfp
);
void
aa_policy_destroy
(
struct
aa_policy
*
policy
);
#endif
/* AA_LIB_H */
/*
* fn_label_build - abstract out the build of a label transition
* @L: label the transition is being computed for
* @P: profile parameter derived from L by this macro, can be passed to FN
* @GFP: memory allocation type to use
* @FN: fn to call for each profile transition. @P is set to the profile
*
* Returns: new label on success
* ERR_PTR if build @FN fails
* NULL if label_build fails due to low memory conditions
*
* @FN must return a label or ERR_PTR on failure. NULL is not allowed
*/
#define fn_label_build(L, P, GFP, FN) \
({ \
__label__ __cleanup, __done; \
struct aa_label *__new_; \
\
if ((L)->size > 1) { \
/* TODO: add cache of transitions already done */
\
struct label_it __i; \
int __j, __k, __count; \
DEFINE_VEC(label, __lvec); \
DEFINE_VEC(profile, __pvec); \
if (vec_setup(label, __lvec, (L)->size, (GFP))) { \
__new_ = NULL; \
goto __done; \
} \
__j = 0; \
label_for_each(__i, (L), (P)) { \
__new_ = (FN); \
AA_BUG(!__new_); \
if (IS_ERR(__new_)) \
goto __cleanup; \
__lvec[__j++] = __new_; \
} \
for (__j = __count = 0; __j < (L)->size; __j++) \
__count += __lvec[__j]->size; \
if (!vec_setup(profile, __pvec, __count, (GFP))) { \
for (__j = __k = 0; __j < (L)->size; __j++) { \
label_for_each(__i, __lvec[__j], (P)) \
__pvec[__k++] = aa_get_profile(P); \
} \
__count -= aa_vec_unique(__pvec, __count, 0); \
if (__count > 1) { \
__new_ = aa_vec_find_or_create_label(__pvec,\
__count, (GFP)); \
/* only fails if out of Mem */
\
if (!__new_) \
__new_ = NULL; \
} else \
__new_ = aa_get_label(&__pvec[0]->label); \
vec_cleanup(profile, __pvec, __count); \
} else \
__new_ = NULL; \
__cleanup: \
vec_cleanup(label, __lvec, (L)->size); \
} else { \
(P) = labels_profile(L); \
__new_ = (FN); \
} \
__done: \
if (!__new_) \
AA_DEBUG("label build failed\n"); \
(__new_); \
})
#define __fn_build_in_ns(NS, P, NS_FN, OTHER_FN) \
({ \
struct aa_label *__new; \
if ((P)->ns != (NS)) \
__new = (OTHER_FN); \
else \
__new = (NS_FN); \
(__new); \
})
#define fn_label_build_in_ns(L, P, GFP, NS_FN, OTHER_FN) \
({ \
fn_label_build((L), (P), (GFP), \
__fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \
})
#endif
/* __AA_LIB_H */
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment