Skip to content

Commit

Permalink
apparmor: improve overlapping domain attachment resolution
Browse files Browse the repository at this point in the history
Overlapping domain attachments using the current longest left exact
match fail in some simple cases, and with the fix to ensure consistent
behavior by failing unresolvable attachments it becomes important to
do a better job.

eg. under the current match the following are unresolvable where
the alternation is clearly a better match under the most specific
left match rule.
  /**
  /{bin/,}usr/

Use a counting match that detects when a loop in the state machine is
enter, and return the match count to provide a better specific left
match resolution.

Signed-off-by: John Johansen <john.johansen@canonical.com>
  • Loading branch information
John Johansen committed Feb 9, 2018
1 parent 73f488c commit 21f6066
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 14 deletions.
1 change: 1 addition & 0 deletions security/apparmor/apparmorfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -2159,6 +2159,7 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = {
AA_SFS_FILE_BOOLEAN("stack", 1),
AA_SFS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1),
AA_SFS_FILE_BOOLEAN("post_nnp_subset", 1),
AA_SFS_FILE_BOOLEAN("computed_longest_left", 1),
AA_SFS_DIR("attach_conditions", aa_sfs_entry_attach),
AA_SFS_FILE_STRING("version", "1.2"),
{ }
Expand Down
30 changes: 17 additions & 13 deletions security/apparmor/domain.c
Original file line number Diff line number Diff line change
Expand Up @@ -385,10 +385,13 @@ static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
struct list_head *head,
const char **info)
{
int len = 0, xattrs = 0;
int candidate_len = 0, candidate_xattrs = 0;
bool conflict = false;
struct aa_profile *profile, *candidate = NULL;

AA_BUG(!name);
AA_BUG(!head);

list_for_each_entry_rcu(profile, head, base.list) {
if (profile->label.flags & FLAG_NULL &&
&profile->label == ns_unconfined(profile->ns))
Expand All @@ -406,19 +409,20 @@ static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
* match.
*/
if (profile->xmatch) {
unsigned int state;
unsigned int state, count;
u32 perm;

if (profile->xmatch_len < len)
continue;

state = aa_dfa_match(profile->xmatch,
DFA_START, name);
state = aa_dfa_leftmatch(profile->xmatch, DFA_START,
name, &count);
perm = dfa_user_allow(profile->xmatch, state);
/* any accepting state means a valid match. */
if (perm & MAY_EXEC) {
int ret = aa_xattrs_match(bprm, profile, state);
int ret;

if (count < candidate_len)
continue;

ret = aa_xattrs_match(bprm, profile, state);
/* Fail matching if the xattrs don't match */
if (ret < 0)
continue;
Expand All @@ -429,10 +433,10 @@ static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
* The new match isn't more specific
* than the current best match
*/
if (profile->xmatch_len == len &&
ret <= xattrs) {
if (count == candidate_len &&
ret <= candidate_xattrs) {
/* Match is equivalent, so conflict */
if (ret == xattrs)
if (ret == candidate_xattrs)
conflict = true;
continue;
}
Expand All @@ -441,8 +445,8 @@ static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
* xattrs, or a longer match
*/
candidate = profile;
len = profile->xmatch_len;
xattrs = ret;
candidate_len = profile->xmatch_len;
candidate_xattrs = ret;
conflict = false;
}
} else if (!strcmp(profile->base.name, name))
Expand Down
19 changes: 19 additions & 0 deletions security/apparmor/include/match.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,25 @@ unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,

void aa_dfa_free_kref(struct kref *kref);

#define WB_HISTORY_SIZE 8
struct match_workbuf {
unsigned int count;
unsigned int pos;
unsigned int len;
unsigned int size; /* power of 2, same as history size */
unsigned int history[WB_HISTORY_SIZE];
};
#define DEFINE_MATCH_WB(N) \
struct match_workbuf N = { \
.count = 0, \
.pos = 0, \
.len = 0, \
.size = WB_HISTORY_SIZE, \
}

unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start,
const char *str, unsigned int *count);

/**
* aa_get_dfa - increment refcount on dfa @p
* @dfa: dfa (MAYBE NULL)
Expand Down
122 changes: 121 additions & 1 deletion security/apparmor/match.c
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,6 @@ unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start,
return state;
}


/**
* aa_dfa_matchn_until - traverse @dfa until accept or @n bytes consumed
* @dfa: the dfa to match @str against (NOT NULL)
Expand Down Expand Up @@ -618,3 +617,124 @@ unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,
*retpos = str;
return state;
}

#define inc_wb_pos(wb) \
do { \
wb->pos = (wb->pos + 1) & (wb->size - 1); \
wb->len = (wb->len + 1) & (wb->size - 1); \
} while (0)

/* For DFAs that don't support extended tagging of states */
static bool is_loop(struct match_workbuf *wb, unsigned int state,
unsigned int *adjust)
{
unsigned int pos = wb->pos;
unsigned int i;

if (wb->history[pos] < state)
return false;

for (i = 0; i <= wb->len; i++) {
if (wb->history[pos] == state) {
*adjust = i;
return true;
}
if (pos == 0)
pos = wb->size;
pos--;
}

*adjust = i;
return true;
}

static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start,
const char *str, struct match_workbuf *wb,
unsigned int *count)
{
u16 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u16 *next = NEXT_TABLE(dfa);
u16 *check = CHECK_TABLE(dfa);
unsigned int state = start, pos;

AA_BUG(!dfa);
AA_BUG(!str);
AA_BUG(!wb);
AA_BUG(!count);

*count = 0;
if (state == 0)
return 0;

/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
/* Equivalence class table defined */
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
while (*str) {
unsigned int adjust;

wb->history[wb->pos] = state;
pos = base_idx(base[state]) + equiv[(u8) *str++];
if (check[pos] == state)
state = next[pos];
else
state = def[state];
if (is_loop(wb, state, &adjust)) {
state = aa_dfa_match(dfa, state, str);
*count -= adjust;
goto out;
}
inc_wb_pos(wb);
(*count)++;
}
} else {
/* default is direct to next state */
while (*str) {
unsigned int adjust;

wb->history[wb->pos] = state;
pos = base_idx(base[state]) + (u8) *str++;
if (check[pos] == state)
state = next[pos];
else
state = def[state];
if (is_loop(wb, state, &adjust)) {
state = aa_dfa_match(dfa, state, str);
*count -= adjust;
goto out;
}
inc_wb_pos(wb);
(*count)++;
}
}

out:
if (!state)
*count = 0;
return state;
}

/**
* aa_dfa_leftmatch - traverse @dfa to find state @str stops at
* @dfa: the dfa to match @str against (NOT NULL)
* @start: the state of the dfa to start matching in
* @str: the null terminated string of bytes to match against the dfa (NOT NULL)
* @count: current count of longest left.
*
* aa_dfa_match will match @str against the dfa and return the state it
* finished matching in. The final state can be used to look up the accepting
* label, or as the start state of a continuing match.
*
* Returns: final state reached after input is consumed
*/
unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start,
const char *str, unsigned int *count)
{
DEFINE_MATCH_WB(wb);

/* TODO: match for extended state dfas */

return leftmatch_fb(dfa, start, str, &wb, count);
}

0 comments on commit 21f6066

Please sign in to comment.