CVE-2026-89761: apparmor: fix out-of-bounds write when null terminating a label vec
In the Linux kernel, the following vulnerability has been resolved:
apparmor: fix out-of-bounds write when null terminating a label vec
aavecunique() null terminates at vec[n - dups] when VECFLAGTERMINATE is passed. If the components are all distinct no duplicates are dropped, dups is 0 and the terminator goes to vec[n], so the caller has to provide room for n + 1 entries.
aalabelstrnparse() sets up its vector with vecsetup(profile, vec, len, gfp) and then calls aavecunique(vec, len, VECFLAGTERMINATE), but vecsetup() does not reserve the terminator entry. Up to LOCALVECENTRIES it uses the local array of LOCALVECENTRIES pointers, above that it allocates exactly len pointers. The terminator therefore lands one entry past the end of the local array when len is LOCALVECENTRIES, and one entry past the end of the allocation when len is larger.
len comes from the number of "//&" separated components in the label name and labelcountstrnentries() does not bound it. An unprivileged task reaches the parse by writing to /proc/self/attr/apparmor/current or through lsmsetselfattr(2), both of which go through dosetattr(), and the name is parsed before the changeprofile permission is checked. The querylabel() path behind the securityfs .access file, which is mode 0666, performs no permission check at all. Every component has to resolve to a loaded profile, so a system with policy loaded is required.
The other two VECFLAGTERMINATE users work on a label vec that aalabelalloc() has already sized with "+ 1 for null terminator entry on vec". Reserve the same entry in vecsetup() and DEFINEVEC(). Passing len + 1 from the caller instead would move len == LOCALVECENTRIES out of the local array and into kzalloc().
Affected Software
Event History
Frequently Asked Questions
Who can trigger the vulnerable parsing path?
An unprivileged task can reach it by writing a crafted label name to /proc/self/attr/apparmor/current or by using lsm_set_self_attr(2). The label is parsed before the profile change operation.
What input is needed to trigger the out-of-bounds write?
The label name must contain components separated by "//&". When all parsed components are distinct, no duplicates are removed and the null terminator is written one pointer beyond the vector; the component count is not bounded.
When does the vector lack space for the terminator?
The local vector overflows when the component count equals LOCAL_VEC_ENTRIES. For counts above LOCAL_VEC_ENTRIES, the dynamically allocated vector has exactly one pointer per component and likewise has no additional terminator slot.