apparmor: validate DFA start states are in bounds in unpack_pdb

commit 9063d7e261 upstream.

Backport for conflicts caused by
  ad596ea74e ("apparmor: group dfa policydb unpacking")
  - rearrange and consolidated the unpack.

  b11e51dd70 ("apparmor: test: make static symbols visible during kunit testing")
  - rename function and make it visible to kunit tests

Start states are read from untrusted data and used as indexes into the
DFA state tables. The aa_dfa_next() function call in unpack_pdb() will
access dfa->tables[YYTD_ID_BASE][start], and if the start state exceeds
the number of states in the DFA, this results in an out-of-bound read.

==================================================================
 BUG: KASAN: slab-out-of-bounds in aa_dfa_next+0x2a1/0x360
 Read of size 4 at addr ffff88811956fb90 by task su/1097
 ...

Reject policies with out-of-bounds start states during unpacking
to prevent the issue.

Fixes: ad5ff3db53 ("AppArmor: Add ability to load extended policy")
Reported-by: Qualys Security Advisory <qsa@qualys.com>
Tested-by: Salvatore Bonaccorso <carnil@debian.org>
Reviewed-by: Georgia Garcia <georgia.garcia@canonical.com>
Reviewed-by: Cengiz Can <cengiz.can@canonical.com>
Signed-off-by: Massimiliano Pellizzer <massimiliano.pellizzer@canonical.com>
Signed-off-by: John Johansen <john.johansen@canonical.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Massimiliano Pellizzer
2026-04-12 23:46:26 -07:00
committed by Greg Kroah-Hartman
parent 6495204f82
commit f43eea8ae0
+19 -2
View File
@@ -841,9 +841,18 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
error = -EPROTO;
goto fail;
}
if (!unpack_u32(e, &profile->policy.start[0], "start"))
if (!unpack_u32(e, &profile->policy.start[0], "start")) {
/* default start state */
profile->policy.start[0] = DFA_START;
} else {
size_t state_count = profile->policy.dfa->tables[YYTD_ID_BASE]->td_lolen;
if (profile->policy.start[0] >= state_count) {
info = "invalid dfa start state";
goto fail;
}
}
/* setup class index */
for (i = AA_CLASS_FILE; i <= AA_CLASS_LAST; i++) {
profile->policy.start[i] =
@@ -864,9 +873,17 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
info = "failed to unpack profile file rules";
goto fail;
} else if (profile->file.dfa) {
if (!unpack_u32(e, &profile->file.start, "dfa_start"))
if (!unpack_u32(e, &profile->file.start, "dfa_start")) {
/* default start state */
profile->file.start = DFA_START;
} else {
size_t state_count = profile->file.dfa->tables[YYTD_ID_BASE]->td_lolen;
if (profile->file.start >= state_count) {
info = "invalid dfa start state";
goto fail;
}
}
} else if (profile->policy.dfa &&
profile->policy.start[AA_CLASS_FILE]) {
profile->file.dfa = aa_get_dfa(profile->policy.dfa);