The encoding of permissions in the DFA/HFA has evolved over time.
AppArmor 2.1 AppArmor 2.3 - 2.7
Reasons for the major permission rework
There are four basic reasons a permission layout was changed after
The layout of AppArmor 2.1 - 2.7 permissions was inefficient wasting
space, even for the limited set of permissions covered by AppArmor
2.7. Even though the full set of bits per entry where in use, if a
state shared the same permission with another state the information
was duplicated, 64bits per state. With a DFA/HFA with a size of 10,000
states typically having between 20 and 30 unique accept permissions,
the result was less than efficient, and would be even less efficient
if the accept information was widened.
So assuming the accept block size was not expanded and encoded the same
64-bit permissions, and there where 30 unique permissions to encode.
2.7 format = 8*10,000 = 80,000 bytes 3.0 format = 2*10,000 + 30*8 = 20,240 bytes
And as the accept block is expanded the saving increase.
As documented in the AppArmor 2.7 layout all 64bits of the permission
structure where in use, an expansion was needed to be able to add new
abilities. The expansion could have just expanded the width of the
accept table, but that would not have been as efficient or flexible.
The older accept format was to ridged to be able to efficiently
represent, all the new abilities that where desired.
Reworking the layout provided for better cache efficiency, and no need
to unpack and transform the entries to determine the final permissions.
The permission rework also affected the the internals of the DFA/HFA
compilation engine. - rework to track information
lost at tree to dfa state
provide for full minimization - provide for per rule negation - provide for conditional tracking
AppArmor 2.1 used a single 32-bit accept table in DFA to store
AppArmor 2.3 - 2.7
File Permission Layout
AppArmor 2.3 introduced a second accept 32 bit accept table to the
DFA making each accept state a total of 64 bits wide, and reworked
the bit layout, breaking compatibility with AppArmor 2.1.
The permissions where packed into the accept states as follows
Table 1 - perms mask
Table 2 - audit mask
The permissions stored in the owner mask are used if the objects
uid == to the tasks fsuid, other wise the other mask is used.
Execute permissions is determined by x, with the transition type determined by a combination of pux, u, i, and x0-x3.
i - fall back to inherit current profile
u - unsafe exec, have glibc scrub environment variables
pux - unconfined flag used in combination with x0-x3 to create
pux. Was added after original layout to extend pux support
explaining duplication of unconfined flag
x0-x3 - index into transition table.
0 - not defined (use i, u to determine transition)
1 - unconfined (precludes px)
2 - use executable name
3 - use executable name, lookup child profile
4-15 - index into named transition table, to find transition
name. Note: named transition to children profiles where stored
using compound name (ie parent//child), so they could share
in a single global table entry.
cp - is the change_profile permission
onx - change_profile on exec permission
the link pair permission overlaid on the k permission on the second
ls - link subset permission, overlaps k permission, only happens in
second match of chain (see link below) where k is not possible.
The audit and quiet table (Table2). The permissions stored in table
2 are used to determine if a permission is forced audited, or if for
denied requests where the auditing is quieted. The upper 7 bits of
the Owner and Other sets store the quiet masks, which get shifted by
7 bits when applied to mask the x through m permissions.
Link rules are encoded requiring sequential matching and permissions in
two permission tables. First the link name is matched, and a permission
lookup for the l permission bit is done against the matched state. A
transition on the 0 byte from the current state is done and then the
target name is matched, and a second permission look up for the l
permission bit is done against the new match state. The link_subset
permission (overlaps k in permission table) can also be looked up
against the second match.
change profile rules use cp and onx permissions and are overlayed
over top of file rule dfa entries. They are encoded in a multiphase
lookup, with the change_profile permission bit being used to
determine permission. First a check is done against the profile
name for permission, if that doesn't succeed then a second lookup
is done against the namespace name followed by a colon followed by
a profile name.
AppArmor 2.6 saw flattening of permissions at state creation time
as part of internal optimizations to speed up the compiler.
AppArmor 3.0 extends and reworks the dfa and permissions structures
used in the AppArmor 2.x series. Unlike the AppArmor 2.x series a
single dfa can be shared by more than one profile, with each profile
having its own accept permissions for a give state. To accommodate
this the permission structure is split off from the dfa. The dfa
accept table now contains a single 16 or 32 bit index value (size
dependent on dfa16, or dfa32 format).
If the accept index value has its high bit clear (15 or 31) then it is
an index into a profiles permission table. If the accept index value
has its high bit set then it is an index into a profiles conditional
table. Each profile that references the dfa defines its own conditional
and permission tables allow each profile to have a different set of
permissions for any given state.
This split of the dfa and permissions was done to allow for dfa sharing
and to provide greater performance as when a dfa is shared multiple
profile matches can performed at once. It also allowed reducing the
amount of memory dedicated to storing permission data, by allowing
for duplicate elimination of permissions.
The split of the conditional information from the permissions structure
was done to allow for a much broader range of conditions that could be
easily extended in the future, and for better sharing of the extended
The permission structure for AppArmor 3.0 is extended to support a
much broader set of permissions, and information. Firstly as mention
with the dfa accept table value, it has been split into a permission
table and a conditional table. The conditional table contains entries
that encode conditional expressions to obtain a permission (eg. the
owner flag), the permission table contains the actual permissions
that the profile will grant.
The permission table has been expanded to support a broader set of
permissions, including file, network, ipc.
File Permission Entry
Network Permission Entry
Capability Permission Entry
rework of permission handling to find unique permissions during
DFA/HFA creation and to carry full permission information through
out the DFA/HFA creation pipe line. Carry the full information
through the entire pipeline reduced performance slightly but was
offset by improvements brought by reducing comparison time by
separating out the accept states into unique sets.
The AppArmor DFA/HFA uses multiple unique accept states to encode
permissions, and related control information for any given match.
Why multiple distinct accept states
could encode in stream but
have to make multiple matches one for each permission
still have to store extra information for control that happens for the matched state (xtrans ..)
Encoding Conditional permissions
AppArmor encodes conditional information both in the DFA/HFA and in
a separate conditional structure. The split is determined by how the
permission request is done. Permission queries done with information
external to the object (file path vs. inode) are done within the dfa
while, permissions conditional on object properties are done in the
Encoding within the DFA/HFA (parameter)
To encode conditionals within the DFA alternations and ordering