mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 04:31:50 +00:00
apparmor: move exec domain mediation to using labels
Signed-off-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
parent
5379a33120
commit
93c98a484c
@ -87,42 +87,236 @@ out:
|
|||||||
return error;
|
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
|
* change_profile_perms - find permissions for change_profile
|
||||||
* @profile: the current profile (NOT NULL)
|
* @profile: the current profile (NOT NULL)
|
||||||
* @ns: the namespace being switched to (NOT NULL)
|
* @target: label to transition to (NOT NULL)
|
||||||
* @name: the name of the profile to change to (NOT NULL)
|
* @stack: whether this is a stacking request
|
||||||
* @request: requested perms
|
* @request: requested perms
|
||||||
* @start: state to start matching in
|
* @start: state to start matching in
|
||||||
*
|
*
|
||||||
|
*
|
||||||
* Returns: permission set
|
* 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,
|
static int change_profile_perms(struct aa_profile *profile,
|
||||||
struct aa_ns *ns,
|
struct aa_label *target, bool stack,
|
||||||
const char *name, u32 request,
|
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)
|
unsigned int start)
|
||||||
{
|
{
|
||||||
struct aa_perms perms;
|
struct aa_perms perms;
|
||||||
struct path_cond cond = { };
|
|
||||||
unsigned int state;
|
|
||||||
|
|
||||||
if (profile_unconfined(profile)) {
|
if (profile_unconfined(profile)) {
|
||||||
perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
|
perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
|
||||||
perms.audit = perms.quiet = perms.kill = 0;
|
perms.audit = perms.quiet = perms.kill = 0;
|
||||||
return perms;
|
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 */
|
if (change_profile_perms(profile, &target->label, false, request,
|
||||||
state = aa_dfa_match(profile->file.dfa, start, ns->base.name);
|
start, &perms))
|
||||||
state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
|
return nullperms;
|
||||||
aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
|
|
||||||
|
|
||||||
return perms;
|
return perms;
|
||||||
}
|
}
|
||||||
@ -173,10 +367,10 @@ static struct aa_profile *__attach_match(const char *name,
|
|||||||
* @list: list to search (NOT NULL)
|
* @list: list to search (NOT NULL)
|
||||||
* @name: the executable name to match against (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,
|
static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list,
|
||||||
struct list_head *list, const char *name)
|
const char *name)
|
||||||
{
|
{
|
||||||
struct aa_profile *profile;
|
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));
|
profile = aa_get_profile(__attach_match(name, list));
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
return profile;
|
return profile ? &profile->label : NULL;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *next_name(int xtype, const char *name)
|
static const char *next_name(int xtype, const char *name)
|
||||||
@ -238,99 +390,370 @@ static const char *next_name(int xtype, const char *name)
|
|||||||
* x_table_lookup - lookup an x transition name via transition table
|
* x_table_lookup - lookup an x transition name via transition table
|
||||||
* @profile: current profile (NOT NULL)
|
* @profile: current profile (NOT NULL)
|
||||||
* @xindex: index into x transition table
|
* @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_label *label = NULL;
|
||||||
struct aa_ns *ns = profile->ns;
|
|
||||||
u32 xtype = xindex & AA_X_TYPE_MASK;
|
u32 xtype = xindex & AA_X_TYPE_MASK;
|
||||||
int index = xindex & AA_X_INDEX_MASK;
|
int index = xindex & AA_X_INDEX_MASK;
|
||||||
const char *name;
|
|
||||||
|
AA_BUG(!name);
|
||||||
|
|
||||||
/* index is guaranteed to be in range, validated at load time */
|
/* index is guaranteed to be in range, validated at load time */
|
||||||
for (name = profile->file.trans.table[index]; !new_profile && name;
|
/* TODO: move lookup parsing to unpack time so this is a straight
|
||||||
name = next_name(xtype, name)) {
|
* index into the resultant label
|
||||||
struct aa_ns *new_ns;
|
*/
|
||||||
const char *xname = NULL;
|
for (*name = profile->file.trans.table[index]; !label && *name;
|
||||||
|
*name = next_name(xtype, *name)) {
|
||||||
new_ns = NULL;
|
|
||||||
if (xindex & AA_X_CHILD) {
|
if (xindex & AA_X_CHILD) {
|
||||||
|
struct aa_profile *new_profile;
|
||||||
/* release by caller */
|
/* 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;
|
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 */
|
label = aa_label_parse(&profile->label, *name, GFP_ATOMIC,
|
||||||
new_ns = aa_find_ns(ns, ns_name);
|
true, false);
|
||||||
if (!new_ns)
|
if (IS_ERR(label))
|
||||||
continue;
|
label = NULL;
|
||||||
} else if (*name == '@') {
|
|
||||||
/* TODO: variable support */
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
/* basic namespace lookup */
|
|
||||||
xname = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* released by caller */
|
/* released by caller */
|
||||||
new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname);
|
|
||||||
aa_put_ns(new_ns);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* released by caller */
|
return label;
|
||||||
return new_profile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
* @profile: current profile (NOT NULL)
|
||||||
* @name: name to lookup (NOT NULL)
|
* @name: name to lookup (NOT NULL)
|
||||||
* @xindex: index into x transition table
|
* @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,
|
static struct aa_label *x_to_label(struct aa_profile *profile,
|
||||||
const char *name, u32 xindex)
|
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;
|
struct aa_ns *ns = profile->ns;
|
||||||
u32 xtype = xindex & AA_X_TYPE_MASK;
|
u32 xtype = xindex & AA_X_TYPE_MASK;
|
||||||
|
const char *stack = NULL;
|
||||||
|
|
||||||
switch (xtype) {
|
switch (xtype) {
|
||||||
case AA_X_NONE:
|
case AA_X_NONE:
|
||||||
/* fail exec unless ix || ux fallback - handled by caller */
|
/* 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:
|
case AA_X_NAME:
|
||||||
if (xindex & AA_X_CHILD)
|
if (xindex & AA_X_CHILD)
|
||||||
/* released by caller */
|
/* released by caller */
|
||||||
new_profile = find_attach(ns, &profile->base.profiles,
|
new = find_attach(ns, &profile->base.profiles,
|
||||||
name);
|
name);
|
||||||
else
|
else
|
||||||
/* released by caller */
|
/* released by caller */
|
||||||
new_profile = find_attach(ns, &ns->base.profiles,
|
new = find_attach(ns, &ns->base.profiles,
|
||||||
name);
|
name);
|
||||||
break;
|
*lookupname = name;
|
||||||
case AA_X_TABLE:
|
|
||||||
/* released by caller */
|
|
||||||
new_profile = x_table_lookup(profile, xindex);
|
|
||||||
break;
|
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 */
|
/* released by caller */
|
||||||
return new_profile;
|
return new;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_label *new = NULL;
|
||||||
|
const char *info = NULL, *name = NULL, *target = NULL;
|
||||||
|
unsigned int state = profile->file.start;
|
||||||
|
struct aa_perms perms = {};
|
||||||
|
bool nonewprivs = false;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
AA_DEBUG("name lookup ix on error");
|
||||||
|
error = 0;
|
||||||
|
new = aa_get_newest_label(&profile->label);
|
||||||
|
}
|
||||||
|
name = bprm->filename;
|
||||||
|
goto audit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile_unconfined(profile)) {
|
||||||
|
new = find_attach(profile->ns, &profile->ns->base.profiles,
|
||||||
|
name);
|
||||||
|
if (new) {
|
||||||
|
AA_DEBUG("unconfined attached to new label");
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
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 = 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 transition not found";
|
||||||
|
/* remove MAY_EXEC to audit as failure */
|
||||||
|
perms.allow &= ~MAY_EXEC;
|
||||||
|
}
|
||||||
|
} else if (COMPLAIN_MODE(profile)) {
|
||||||
|
/* 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 {
|
||||||
|
error = -EACCES;
|
||||||
|
new = &new_profile->label;
|
||||||
|
}
|
||||||
|
perms.xindex |= AA_X_UNSAFE;
|
||||||
|
} else
|
||||||
|
/* fail exec */
|
||||||
|
error = -EACCES;
|
||||||
|
|
||||||
|
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) &&
|
||||||
|
!profile_unconfined(profile) &&
|
||||||
|
!aa_label_is_subset(new, &profile->label)) {
|
||||||
|
error = -EPERM;
|
||||||
|
info = "no new privs";
|
||||||
|
nonewprivs = true;
|
||||||
|
perms.allow &= ~MAY_EXEC;
|
||||||
|
goto audit;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -338,22 +761,22 @@ static struct aa_profile *x_to_profile(struct aa_profile *profile,
|
|||||||
* @bprm: binprm for the exec (NOT NULL)
|
* @bprm: binprm for the exec (NOT NULL)
|
||||||
*
|
*
|
||||||
* Returns: %0 or error on failure
|
* 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)
|
int apparmor_bprm_set_creds(struct linux_binprm *bprm)
|
||||||
{
|
{
|
||||||
struct aa_task_ctx *ctx;
|
struct aa_task_ctx *ctx;
|
||||||
struct aa_label *label;
|
struct aa_label *label, *new = NULL;
|
||||||
struct aa_profile *profile, *new_profile = NULL;
|
struct aa_profile *profile;
|
||||||
struct aa_ns *ns;
|
|
||||||
char *buffer = NULL;
|
char *buffer = NULL;
|
||||||
unsigned int state;
|
const char *info = NULL;
|
||||||
struct aa_perms perms = {};
|
int error = 0;
|
||||||
|
bool unsafe = false;
|
||||||
struct path_cond cond = {
|
struct path_cond cond = {
|
||||||
file_inode(bprm->file)->i_uid,
|
file_inode(bprm->file)->i_uid,
|
||||||
file_inode(bprm->file)->i_mode
|
file_inode(bprm->file)->i_mode
|
||||||
};
|
};
|
||||||
const char *name = NULL, *info = NULL;
|
|
||||||
int error = 0;
|
|
||||||
|
|
||||||
if (bprm->cred_prepared)
|
if (bprm->cred_prepared)
|
||||||
return 0;
|
return 0;
|
||||||
@ -362,171 +785,82 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
|
|||||||
AA_BUG(!ctx);
|
AA_BUG(!ctx);
|
||||||
|
|
||||||
label = aa_get_newest_label(ctx->label);
|
label = aa_get_newest_label(ctx->label);
|
||||||
profile = labels_profile(label);
|
|
||||||
|
|
||||||
/* buffer freed below, name is pointer into buffer */
|
/* buffer freed below, name is pointer into buffer */
|
||||||
get_buffers(buffer);
|
get_buffers(buffer);
|
||||||
/*
|
/* Test for onexec first as onexec override other x transitions. */
|
||||||
* get the namespace from the replacement profile as replacement
|
|
||||||
* can change the namespace
|
|
||||||
*/
|
|
||||||
ns = profile->ns;
|
|
||||||
state = profile->file.start;
|
|
||||||
|
|
||||||
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))
|
|
||||||
error = 0;
|
|
||||||
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)
|
if (ctx->onexec)
|
||||||
/* change_profile on exec already been granted */
|
new = handle_onexec(label, ctx->onexec, ctx->token,
|
||||||
new_profile = labels_profile(aa_get_label(ctx->onexec));
|
bprm, buffer, &cond, &unsafe);
|
||||||
else
|
else
|
||||||
new_profile = find_attach(ns, &ns->base.profiles, name);
|
new = fn_label_build(label, profile, GFP_ATOMIC,
|
||||||
if (!new_profile)
|
profile_transition(profile, bprm, buffer,
|
||||||
goto cleanup;
|
&cond, &unsafe));
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* find exec permissions for name */
|
AA_BUG(!new);
|
||||||
state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
|
if (IS_ERR(new)) {
|
||||||
if (ctx->onexec) {
|
error = PTR_ERR(new);
|
||||||
struct aa_perms cp;
|
goto done;
|
||||||
info = "change_profile onexec";
|
} else if (!new) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
error = -EACCES;
|
|
||||||
info = "profile 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,
|
|
||||||
GFP_ATOMIC);
|
|
||||||
if (!new_profile) {
|
|
||||||
error = -ENOMEM;
|
error = -ENOMEM;
|
||||||
info = "could not create null profile";
|
goto done;
|
||||||
} else
|
|
||||||
error = -EACCES;
|
|
||||||
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 (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) {
|
|
||||||
error = -EPERM;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!new_profile)
|
/* TODO: Add ns level no_new_privs subset test */
|
||||||
goto audit;
|
|
||||||
|
|
||||||
if (bprm->unsafe & LSM_UNSAFE_SHARE) {
|
if (bprm->unsafe & LSM_UNSAFE_SHARE) {
|
||||||
/* FIXME: currently don't mediate shared state */
|
/* FIXME: currently don't mediate shared state */
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bprm->unsafe & LSM_UNSAFE_PTRACE) {
|
if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) {
|
||||||
error = may_change_ptraced_domain(&new_profile->label, &info);
|
/* TODO: test needs to be profile of label to new */
|
||||||
|
error = may_change_ptraced_domain(new, &info);
|
||||||
if (error)
|
if (error)
|
||||||
goto audit;
|
goto audit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Determine if secure exec is needed.
|
if (unsafe) {
|
||||||
* Can be at this point for the following reasons:
|
if (DEBUG_ON) {
|
||||||
* 1. unconfined switching to confined
|
dbg_printk("scrubbing environment variables for %s "
|
||||||
* 2. confined switching to different confinement
|
"label=", bprm->filename);
|
||||||
* 3. confined switching to unconfined
|
aa_label_printk(new, GFP_ATOMIC);
|
||||||
*
|
dbg_printk("\n");
|
||||||
* 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);
|
|
||||||
bprm->unsafe |= AA_SECURE_X_NEEDED;
|
bprm->unsafe |= AA_SECURE_X_NEEDED;
|
||||||
}
|
}
|
||||||
apply:
|
|
||||||
/* when transitioning profiles clear unsafe personality bits */
|
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;
|
bprm->per_clear |= PER_CLEAR_ON_SETID;
|
||||||
|
}
|
||||||
x_clear:
|
|
||||||
aa_put_label(ctx->label);
|
aa_put_label(ctx->label);
|
||||||
/* transfer new profile reference will be released when ctx is freed */
|
/* transfer reference, released when ctx is freed */
|
||||||
ctx->label = &new_profile->label;
|
ctx->label = new;
|
||||||
new_profile = NULL;
|
|
||||||
|
|
||||||
/* clear out all temporary/transitional state from the context */
|
done:
|
||||||
|
/* clear out temporary/transitional state from the context */
|
||||||
aa_clear_task_ctx_trans(ctx);
|
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);
|
aa_put_label(label);
|
||||||
put_buffers(buffer);
|
put_buffers(buffer);
|
||||||
|
|
||||||
return error;
|
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,
|
perms = change_profile_perms_wrapper(profile, target, request,
|
||||||
request, profile->file.start);
|
profile->file.start);
|
||||||
if (!(perms.allow & request)) {
|
if (!(perms.allow & request)) {
|
||||||
error = -EACCES;
|
error = -EACCES;
|
||||||
goto audit;
|
goto audit;
|
||||||
|
@ -211,4 +211,89 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix,
|
|||||||
const char *name, gfp_t gfp);
|
const char *name, gfp_t gfp);
|
||||||
void aa_policy_destroy(struct aa_policy *policy);
|
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 */
|
||||||
|
Loading…
Reference in New Issue
Block a user