Skip to content

Commit 21f6066

Browse files
committed
apparmor: improve overlapping domain attachment resolution
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 <[email protected]>
1 parent 73f488c commit 21f6066

File tree

4 files changed

+158
-14
lines changed

4 files changed

+158
-14
lines changed

security/apparmor/apparmorfs.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,6 +2159,7 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = {
21592159
AA_SFS_FILE_BOOLEAN("stack", 1),
21602160
AA_SFS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1),
21612161
AA_SFS_FILE_BOOLEAN("post_nnp_subset", 1),
2162+
AA_SFS_FILE_BOOLEAN("computed_longest_left", 1),
21622163
AA_SFS_DIR("attach_conditions", aa_sfs_entry_attach),
21632164
AA_SFS_FILE_STRING("version", "1.2"),
21642165
{ }

security/apparmor/domain.c

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -385,10 +385,13 @@ static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
385385
struct list_head *head,
386386
const char **info)
387387
{
388-
int len = 0, xattrs = 0;
388+
int candidate_len = 0, candidate_xattrs = 0;
389389
bool conflict = false;
390390
struct aa_profile *profile, *candidate = NULL;
391391

392+
AA_BUG(!name);
393+
AA_BUG(!head);
394+
392395
list_for_each_entry_rcu(profile, head, base.list) {
393396
if (profile->label.flags & FLAG_NULL &&
394397
&profile->label == ns_unconfined(profile->ns))
@@ -406,19 +409,20 @@ static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
406409
* match.
407410
*/
408411
if (profile->xmatch) {
409-
unsigned int state;
412+
unsigned int state, count;
410413
u32 perm;
411414

412-
if (profile->xmatch_len < len)
413-
continue;
414-
415-
state = aa_dfa_match(profile->xmatch,
416-
DFA_START, name);
415+
state = aa_dfa_leftmatch(profile->xmatch, DFA_START,
416+
name, &count);
417417
perm = dfa_user_allow(profile->xmatch, state);
418418
/* any accepting state means a valid match. */
419419
if (perm & MAY_EXEC) {
420-
int ret = aa_xattrs_match(bprm, profile, state);
420+
int ret;
421+
422+
if (count < candidate_len)
423+
continue;
421424

425+
ret = aa_xattrs_match(bprm, profile, state);
422426
/* Fail matching if the xattrs don't match */
423427
if (ret < 0)
424428
continue;
@@ -429,10 +433,10 @@ static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
429433
* The new match isn't more specific
430434
* than the current best match
431435
*/
432-
if (profile->xmatch_len == len &&
433-
ret <= xattrs) {
436+
if (count == candidate_len &&
437+
ret <= candidate_xattrs) {
434438
/* Match is equivalent, so conflict */
435-
if (ret == xattrs)
439+
if (ret == candidate_xattrs)
436440
conflict = true;
437441
continue;
438442
}
@@ -441,8 +445,8 @@ static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
441445
* xattrs, or a longer match
442446
*/
443447
candidate = profile;
444-
len = profile->xmatch_len;
445-
xattrs = ret;
448+
candidate_len = profile->xmatch_len;
449+
candidate_xattrs = ret;
446450
conflict = false;
447451
}
448452
} else if (!strcmp(profile->base.name, name))

security/apparmor/include/match.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,25 @@ unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,
138138

139139
void aa_dfa_free_kref(struct kref *kref);
140140

141+
#define WB_HISTORY_SIZE 8
142+
struct match_workbuf {
143+
unsigned int count;
144+
unsigned int pos;
145+
unsigned int len;
146+
unsigned int size; /* power of 2, same as history size */
147+
unsigned int history[WB_HISTORY_SIZE];
148+
};
149+
#define DEFINE_MATCH_WB(N) \
150+
struct match_workbuf N = { \
151+
.count = 0, \
152+
.pos = 0, \
153+
.len = 0, \
154+
.size = WB_HISTORY_SIZE, \
155+
}
156+
157+
unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start,
158+
const char *str, unsigned int *count);
159+
141160
/**
142161
* aa_get_dfa - increment refcount on dfa @p
143162
* @dfa: dfa (MAYBE NULL)

security/apparmor/match.c

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,6 @@ unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start,
556556
return state;
557557
}
558558

559-
560559
/**
561560
* aa_dfa_matchn_until - traverse @dfa until accept or @n bytes consumed
562561
* @dfa: the dfa to match @str against (NOT NULL)
@@ -618,3 +617,124 @@ unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,
618617
*retpos = str;
619618
return state;
620619
}
620+
621+
#define inc_wb_pos(wb) \
622+
do { \
623+
wb->pos = (wb->pos + 1) & (wb->size - 1); \
624+
wb->len = (wb->len + 1) & (wb->size - 1); \
625+
} while (0)
626+
627+
/* For DFAs that don't support extended tagging of states */
628+
static bool is_loop(struct match_workbuf *wb, unsigned int state,
629+
unsigned int *adjust)
630+
{
631+
unsigned int pos = wb->pos;
632+
unsigned int i;
633+
634+
if (wb->history[pos] < state)
635+
return false;
636+
637+
for (i = 0; i <= wb->len; i++) {
638+
if (wb->history[pos] == state) {
639+
*adjust = i;
640+
return true;
641+
}
642+
if (pos == 0)
643+
pos = wb->size;
644+
pos--;
645+
}
646+
647+
*adjust = i;
648+
return true;
649+
}
650+
651+
static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start,
652+
const char *str, struct match_workbuf *wb,
653+
unsigned int *count)
654+
{
655+
u16 *def = DEFAULT_TABLE(dfa);
656+
u32 *base = BASE_TABLE(dfa);
657+
u16 *next = NEXT_TABLE(dfa);
658+
u16 *check = CHECK_TABLE(dfa);
659+
unsigned int state = start, pos;
660+
661+
AA_BUG(!dfa);
662+
AA_BUG(!str);
663+
AA_BUG(!wb);
664+
AA_BUG(!count);
665+
666+
*count = 0;
667+
if (state == 0)
668+
return 0;
669+
670+
/* current state is <state>, matching character *str */
671+
if (dfa->tables[YYTD_ID_EC]) {
672+
/* Equivalence class table defined */
673+
u8 *equiv = EQUIV_TABLE(dfa);
674+
/* default is direct to next state */
675+
while (*str) {
676+
unsigned int adjust;
677+
678+
wb->history[wb->pos] = state;
679+
pos = base_idx(base[state]) + equiv[(u8) *str++];
680+
if (check[pos] == state)
681+
state = next[pos];
682+
else
683+
state = def[state];
684+
if (is_loop(wb, state, &adjust)) {
685+
state = aa_dfa_match(dfa, state, str);
686+
*count -= adjust;
687+
goto out;
688+
}
689+
inc_wb_pos(wb);
690+
(*count)++;
691+
}
692+
} else {
693+
/* default is direct to next state */
694+
while (*str) {
695+
unsigned int adjust;
696+
697+
wb->history[wb->pos] = state;
698+
pos = base_idx(base[state]) + (u8) *str++;
699+
if (check[pos] == state)
700+
state = next[pos];
701+
else
702+
state = def[state];
703+
if (is_loop(wb, state, &adjust)) {
704+
state = aa_dfa_match(dfa, state, str);
705+
*count -= adjust;
706+
goto out;
707+
}
708+
inc_wb_pos(wb);
709+
(*count)++;
710+
}
711+
}
712+
713+
out:
714+
if (!state)
715+
*count = 0;
716+
return state;
717+
}
718+
719+
/**
720+
* aa_dfa_leftmatch - traverse @dfa to find state @str stops at
721+
* @dfa: the dfa to match @str against (NOT NULL)
722+
* @start: the state of the dfa to start matching in
723+
* @str: the null terminated string of bytes to match against the dfa (NOT NULL)
724+
* @count: current count of longest left.
725+
*
726+
* aa_dfa_match will match @str against the dfa and return the state it
727+
* finished matching in. The final state can be used to look up the accepting
728+
* label, or as the start state of a continuing match.
729+
*
730+
* Returns: final state reached after input is consumed
731+
*/
732+
unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start,
733+
const char *str, unsigned int *count)
734+
{
735+
DEFINE_MATCH_WB(wb);
736+
737+
/* TODO: match for extended state dfas */
738+
739+
return leftmatch_fb(dfa, start, str, &wb, count);
740+
}

0 commit comments

Comments
 (0)