<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta />
    <article-meta>
      <title-group>
        <article-title>A bug in Linux ACL implementation</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Jaroslav Janáček</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>Comenius University in Bratislava, Faculty of Mathematics</institution>
          ,
          <addr-line>Physics and Informatics</addr-line>
          ,
          <institution>Department of Computer Science</institution>
          ,
          <addr-line>Mlynská dolina, 842 48, Bratislava</addr-line>
          ,
          <country country="SK">Slovakia</country>
        </aff>
      </contrib-group>
      <abstract>
        <p>We demonstrate and analyse a long-standing bug in the Linux kernel ACL permission checking code that, under specific circumstances, allows users and/or groups to access filesystem objects they should not be allowed to access and propose a fix.</p>
      </abstract>
      <kwd-group>
        <kwd>eol&gt;Linux</kwd>
        <kwd>ACL</kwd>
      </kwd-group>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>1. Introduction</title>
      <sec id="sec-1-1">
        <title>Each ACL consists of ACL entries. There are six types</title>
        <p>of ACL entries:
Access control, controlling access to filesystem objects, is
an important part of operating systems. In Linux based • ACL_USER_OBJ – specifies the permissions for
operating systems, the filesystem access control is done the object’s owner,
in the Linux kernel. The Linux kernel’s basic access • ACL_USER – specifies the permissions for a
specontrol model is based on UNIX access control model, cific user,
where the permissions to read, write, and execute a file • ACL_GROUP_OBJ – specifies the permissions for
can be assigned to the owner of the file, a single group, the object’s group,
and others. While this basic model is suficient in many • ACL_GROUP – specifies the permissions for a
simple cases, there are cases when we need to assign specific group,
diferent permissions to diferent users and/or groups. • ACL_MASK – specifies the upper limit of
perFor example, if a file is to be readable and writeable by missions granted by the entries of the type
one group of users, and readable only by another group of ACL_USER, ACL_GROUP, or ACL_GROUP_OBJ,
users, this cannot be achieved using this simple model in • ACL_OTHER – specifies the permissions for
otha simple way (in general). To enhance the capabilities of ers.
access control, the Linux kernel extends the basic model
with Access Control Lists (ACL). An ACL associated with a Each ACL must contain exactly one entry of each
ifle can be used to assign permissions to additional users of the types ACL_USER_OBJ, ACL_GROUP_OBJ, and
and/or groups. ACL_OTHER. It may contain zero or more entries of each</p>
        <p>When experimenting with ACLs in Linux we have ob- of the types ACL_USER and ACL_GROUP, and it may
served a strange behaviour in some corner cases. Because contain zero or one entry of the type ACL_MASK. If it
it can compromise security, we consider it to be a bug. contains an entry of the type ACL_USER or ACL_GROUP,
We present our analysis of the problem and propose a fix it must contain an entry of the type ACL_MASK. Each
in this paper. ACL entry of the type ACL_USER or ACL_GROUP
contains a user/group ID.</p>
        <p>A process runs on behalf of a user who is a member of
2. Linux ACLs a primary group, and who can be a member of a number
The Linux kernel allows an ACL to be associated with any of supplementary groups. The user is identified by the
iflesystem object residing in a filesystem that supports efective user ID and the primary group is identified by
ACL storage (e.g. ext4 and many other commonly used the efective group ID . To be correct, we must say that in
iflesystems for Linux). A directory can also have a default the Linux kernel the filesystem user ID and the filesystem
ACL associated with it. The default ACL is used as a group ID are actually used instead, but these are usually
template for initialization of the ACL of a new object equal to the efective user ID and the efective group ID,
when it is created in the directory. so we will not distinguish among them.</p>
        <p>Each filesystem object is assigned an owner, identified
by an user ID, and a group, identified by a group ID. It
ITAT’24: Workshop on Applied Security 2024, September 20–24, 2024, is also assigned a 12-bit vector of UNIX permissions – 3
Drienica, Slovakia bits (read, write, execute) for the owner, the group, and
$ jaroslav.janacek@uniba.sk (J. Janáček)</p>
        <p>© 2024 Copyright for this paper by its author. Use permitted under Creative Commons License others, and 3 special bits (set-user-ID, set-group-ID, and
CPWrEooUrckReshdoinpgs IhStpN:/c1e6u1r3-w-0s.o7r3g ACttEribUutRion W4.0oInrtekrnsahtioonpal (PCCroBYce4.0e).dings (CEUR-WS.org) sticky bit).</p>
      </sec>
      <sec id="sec-1-2">
        <title>When a process tries to open a filesystem object which has an ACL associated with it, the Linux kernel performs a permission check algorithm, which should work as follows[1]:</title>
        <p>As we have mentioned above, the Linux kernel does
not replace the standard UNIX permissions assigned to
1. If the efective user ID of the process matches the iflesystem objects with ACLs, but ACLs are an extension.
user ID of the object’s owner, the ACL_USER_OBJ If there is no ACL associated with a file, the standard
entry is used – if it contains the requested permis- UNIX permissions are used; if there is an ACL associated
sions, the access is granted, otherwise the access with the file, the ACL is used. There are, however, many
is denied. programs that do not know about ACLs (e.g. because they
2. Otherwise, if the efective user ID matches the had been written before ACLs were widely supported).
user ID of an entry of the type ACL_USER, this When dealing with security, it is very important not to
entry is used – if the entry and the ACL_MASK break things people rely on. This is why the interaction
entry both contain the requested permissions, the of standard UNIX system calls and utilities dealing with
access is granted, otherwise the access is denied. permissions and ACLs is important.
3. Otherwise, if the efective group ID or any sup- A minimal ACL consists of the three mandatory entries
plementary group ID match the group ID of the – ACL_USER_OBJ, ACL_GROUP_OBJ, and ACL_OTHER.
object or the group ID of an ACL entry of the The meaning of these entries, in this simple case, matches
type ACL_GROUP, then these matching ACL exactly the meaning of the standard permission bits for
entries (of the types ACL_GROUP_OBJ and/or the owner, for the object’s group, and for others. Indeed,
ACL_GROUP) are used – if the ACL entry of the if we change an ACL entry, the corresponding permission
type ACL_MASK and any of the matching ACL bits are changed as well, and vice versa. This ensures
entries both contain the requested permissions, compatibility with legacy tools in this simple case.
the access if granted, otherwise it is denied. If When an entry of another type is added to the ACL,
there is no entry of the type ACL_MASK, the only things get a little bit more complicated. The permission
matching ACL entry can be the one of the type bits for the owner and for others still correspond to the
ACL_GROUP_OBJ, and that is used alone. ACL_USER_OBJ and ACL_OTHER entries, but the
stan4. Otherwise, if the entry of the type ACL_OTHER dard permission bits for the object’s group now
correcontains the requested permissions, the access is spond to the permissions of the ACL_MASK entry. While
granted. this may sound a bit strange at first, there is a good reason
5. Otherwise, the access is denied. behind it. Imagine a legacy program that wants to make
sure that only the owner of a file can access it. Such
program would change the standard permission bits for the
object’s group and for others to zero, leaving only some
nonzero bits for the owner. If the object’s group
permission bits corresponded to the ACL_GROUP_OBJ entry
while there was an ACL_USER or ACL_GROUP type ACL
entry present in the ACL, the legacy program would not
remove the permissions granted by these ACL entries.</p>
        <p>But, because the standard object’s group permission bits
correspond to the ACL_MASK entry, if it is present,
zeroing the standard object’s group permission bits zeroes
the ACL_MASK entry, thereby efectively removing all
permissions granted by ACL_USER, ACL_GROUP, and
ACL_GROUP_OBJ entries. This way, a sort of
compatibility with legacy tools is ensured even in the more complex
cases.</p>
      </sec>
      <sec id="sec-1-3">
        <title>There is an exception, if the process has a special ef</title>
        <p>fective capability to override the access control (typically
when it runs on behalf of the root user – efective user
ID zero), the permission check algorithm is skipped.</p>
        <p>As we can see, in all cases the efective
permissions are determined either by a single ACL entry
(ACL_USER_OBJ, ACL_OTHER, or ACL_GROUP_OBJ
if there is no ACL_MASK entry), or by the logical
AND of a single ACL entry (ACL_USER, ACL_GROUP,
ACL_GROUP_OBJ) and the ACL_MASK entry. Also, if
the efective user ID of the process matches either the
owner’s user ID, or the user ID in any of the ACL_USER
entries, the group ACL entries and the ACL_OTHER
entry are not used. And if the efective group ID or any
supplementary group ID match the object’s group ID or
the group ID of any ACL_GROUP entry, the ACL_OTHER
entry is not used. In other words, the ACL_OTHER entry
can only be used for processes running on behalf of a
user who has no matching ACL entries – neither directly,
nor via a group membership.</p>
      </sec>
    </sec>
    <sec id="sec-2">
      <title>3. Problem demonstration</title>
      <sec id="sec-2-1">
        <title>We will now show that there are cases when the system</title>
        <p>does not behave according to the permission check
algorithm as described above. We will use the kernel version
6.1, however, the same behaviour can be found in many
previous versions as well (5.x, 4.x, 3.x at least).</p>
        <sec id="sec-2-1-1">
          <title>3.1. Problem with ACL_USER entries</title>
          <p>First, we create a file f and set it’s ACL so that only the
ifle’s owner ( jerry) has the read and write permissions,
and others have the read permission:
j e r r y : e c h o ’ Works ! ’ &gt; f
j e r r y : s e t f a c l −m ’ u : : rw , g : : − , o : : r ’ f
j e r r y : g e t f a c l f
# f i l e : f
# owner : j e r r y
# g r o u p : j e r r y
u s e r : : rw−
g r o u p : : − − −
o t h e r : : r −−</p>
        </sec>
      </sec>
      <sec id="sec-2-2">
        <title>The setfacl command is used to modify (with the -m</title>
        <p>lfag) the ACL, the getfacl command is used to show
the current ACL associated with the file.</p>
        <p>Clearly, at this stage, another user (tom), who is not a
member of the group jerry, should be able to read the file,
because there are no ACL entries matching tom or any of
his groups, and the ACL_OTHER entry grants the read
permission to others. We test it by trying to show the
contents of the file as tom, and also by trying to modify
the file:
tom : c a t f
Works !
tom : e c h o a a a &gt;&gt; f
b a s h : f : P e r m i s s i o n d e n i e d</p>
        <p>Now, the owner of the file ( jerry) modifies the ACL so
that the user tom is granted no permissions:
j e r r y : s e t f a c l −m ’ u : tom : − ’ f
j e r r y : g e t f a c l f
# f i l e : f
# owner : j e r r y
# g r o u p : j e r r y
u s e r : : rw−
u s e r : tom : − − −
g r o u p : : − − −
mask : : − − −
o t h e r : : r −−</p>
      </sec>
      <sec id="sec-2-3">
        <title>According to the permission check algorithm described</title>
        <p>in the previous section, the user tom should have no
access to the file. However, when we try it:
tom : c a t f
Works !
tom : e c h o a a a &gt;&gt; f
b a s h : f : P e r m i s s i o n d e n i e d
we can see, the user can read the file, and cannot write to
the file. This behaviour is obviously incorrect and may
be considered a security problem.</p>
        <p>It seems that the permissions for others are used for
the user tom in this case, although, according to the
permission check algorithm, they should not be used. We
can try to support this hypothesis by changing the
permissions for others and retesting the tom’s access:
j e r r y : s e t f a c l −m ’ o : : w’ f
j e r r y : g e t f a c l f
# f i l e : f
# owner : j e r r y
# g r o u p : j e r r y
u s e r : : rw−
u s e r : tom : − − −
g r o u p : : − − −
mask : : − − −
o t h e r : : − w−
tom : c a t f
c a t : f : P e r m i s s i o n d e n i e d
tom : e c h o a a a &gt;&gt; f
j e r r y : c a t f
Works !
a a a</p>
      </sec>
      <sec id="sec-2-4">
        <title>As we can see, after changing the permissions for oth</title>
        <p>ers to write only, the user tom cannot read the file, but
can successfully write to the file.</p>
        <p>We can now change the permissions of the ACL_MASK
entry in the ACL to a nonzero value, e.g. to read and write,
and retest the tom’s access:
j e r r y : s e t f a c l −m ’m : : rw ’ f
j e r r y : g e t f a c l f
# f i l e : f</p>
        <p>As we can see, two new ACL entries have been cre- # owner : j e r r y
ated - the ACL_USER entry for the user tom, and the # g r o u p : j e r r y
ACL_MASK entry (which must be in the ACL when u s e r : : rw−
there is any ACL_USER or ACL_GROUP entry). The per- u s e r : tom : − − −
missions of the (explicitly unspecified) ACL_MASK en- g r o u p : : − − −
try have been automatically calculated by the setfacl mask : : rw−
utility as the union (logical OR) of the permissions o t h e r : : − w−
of all afected ACL entries (ACL_USER, ACL_GROUP,
ACL_GROUP_OBJ types).
tom : c a t f
c a t : f : P e r m i s s i o n d e n i e d
tom : e c h o a a a &gt;&gt; f
b a s h : f : P e r m i s s i o n d e n i e d</p>
      </sec>
      <sec id="sec-2-5">
        <title>The system now behaves as expected – the user tom</title>
        <p>is denied both read and write access to the file.</p>
        <sec id="sec-2-5-1">
          <title>3.2. Problem with ACL_GROUP entries</title>
          <p>We can now repeat the tests with group permissions
instead of user permissions. First we restore the ACL to
the initial state:
j e r r y : s e t f a c l −b f
j e r r y : s e t f a c l −m ’ o : : r ’ f
j e r r y : g e t f a c l f
# f i l e : f
# owner : j e r r y
# g r o u p : j e r r y
u s e r : : rw−
g r o u p : : − − −
o t h e r : : r −−</p>
        </sec>
      </sec>
      <sec id="sec-2-6">
        <title>The setfacl -b command removes all ACL entries, and</title>
        <p>then we restore the read permissions for others.</p>
        <p>The user tom should have the read access now again
because there are no matching ACL entries for tom or
his groups:</p>
      </sec>
      <sec id="sec-2-7">
        <title>We can see that the system behaves incorrectly again –</title>
        <p>the user tom as a member of the group users should have
no access to the file, but the permissions for others seem
to be applied instead.</p>
        <p>When we change the permissions of the ACL_MASK
entry to a nonzero value, it works correctly again:
j e r r y : s e t f a c l −m ’m : : rw ’ f
j e r r y : g e t f a c l f
# f i l e : f
# owner : j e r r y
# g r o u p : j e r r y
u s e r : : rw−
g r o u p : : − − −
g r o u p : u s e r s : − − −
mask : : rw−
o t h e r : : r −−
tom : c a t f
c a t : f : P e r m i s s i o n d e n i e d
tom : e c h o bbb &gt;&gt; f
b a s h : f : P e r m i s s i o n d e n i e d</p>
      </sec>
    </sec>
    <sec id="sec-3">
      <title>4. Problem analysis</title>
      <p>tom : c a t f We have demonstrated the incorrect behaviour in the
Works ! previous section and we have stated a hypothesis that
a a a under some conditions the ACL_OTHER entry (or
pertom : e c h o bbb &gt;&gt; f haps the standard permission bits for others) are used
b a s h : f : P e r m i s s i o n d e n i e d instead of the correct ACL_USER or ACL_GROUP entry.</p>
      <p>As far as the conditions are concerned, it appears that this</p>
      <p>
        We add an entry for the group users with no permis- incorrect behaviour is manifested when the ACL_MASK
sions: entry contains empty permissions. In this section we will
identify the root cause of this behaviour and refine and
j e r r y : s e t f a c l −m ’ g : u s e r s : − ’ f confirm our hypothesis.
j e r r y : g e t f a c l f To start, we look for the code responsible for the ACL
# f i l e : f permission check algorithm. After looking at the
struc# owner : j e r r y ture of the Linux kernel source code [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ] we can easily
# g r o u p : j e r r y discover an interesting file fs/posix_acl.c, and more
specifu s e r : : rw− icaly the posix_acl_permission function. Quick analysis of
g r o u p : : − − − this function leads to the conclusion that it does indeed
g r o u p : u s e r s : − − − implement the permission check algorithm in accordance
mask : : − − − with the specification in the section 2.
o t h e r : : r −− Looking for function calls of posix_acl_permissions
      </p>
      <p>Again, two new entries have been created – the leads to the namei.c file where we discover a chain of
ACL_GROUP entry for the group users and the functions dealing with permission checking. A short
ACL_MASK entry. We can test the tom’s access: description of the functions and their relationship is in
the table 1.
tom : c a t f The function acl_permission_check, shown in the
listWorks ! ing 1, is of a particular interest. In the lines 7–13 the
a a a code checks if the efective user ID (or more precisely the
tom : e c h o bbb &gt;&gt; f iflesystem user ID) matches the owner’s user ID, and if it
b a s h : f : P e r m i s s i o n d e n i e d does, it uses the standard UNIX permission bits for the
owner to determine whether to grant or deny the access.</p>
      <p>In this case it entirely bypasses the ACL permission check and the code continues with checking only the standard
algorithm, but it should not be a problem unless the ACL UNIX permission bits. And because in our test cases the
and the standard UNIX permission bits are desynchro- process’s group ID did not match the object’s group ID,
nized (which should not happen under normal operating the standard UNIX permission bits for others were used
conditions). to determine the access.</p>
      <p>The lines 15–20 handle the case when an ACL is If the line 16 was changed to
present – we will look into it in more detail in a short
while. i f ( IS_POSIXACL ( i n o d e ) ) {</p>
      <p>The lines 22–34 handle the standard UNIX permissions everything would work just fine. The most probable
reafor the object’s group. We can see an optimization here son for the second condition is an optimization. If the
– if the relevant permission bits for the object’s group ACL_MASK (and therefore the standard object’s group
are equal to the relevant permission bits for others, the permission bits) contains no permissions, no
permiscode skips checking if the object’s group ID matches sions can be efectively granted using the ACL_USER
the process’s group IDs and continues with checking or ACL_GROUP entries, and therefore the code’s author
the permission bits for others. It can do so, because the has probably incorrectly concluded that is not necessary
access would be granted or denied identically for both to process the ACL. But this is wrong – if the ACL
conthe object’s group and others in this case. tains an entry of the type ACL_USER or ACL_GROUP, it</p>
      <p>Finally, the lines 36–37 check the permissions for oth- can still match and deny access.
ers.</p>
      <p>Let’s now analyse the lines 15–20 in more detail. The
line 16 is crucial here. It checks if the file has an ACL 5. Proposed fix
associated with it and if the standard permission bits
contain at least one nonzero bit for the object’s group Having found the cause of the problem, we can propose
permissions. If both conditions are true, the function two possible ways to fix it. One obvious fix is to change
check_acl is called to process the ACL, and the result is the line 16 of the function acl_permission_check in
fs/returned. If there is no ACL associated with the file, it namei.c as shown in the previous section. This would
makes no sense to call check_acl. But what is the purpose correct the problem but could slow down the evaluation
of the second condition? It clearly causes the observed of the function.
incorrect behaviour – if the ACL_MASK entry contains Another way, preserving the optimization in
nonno permissions, the standard UNIX permissions bits for problematic cases, is to change the line 16 as follows:
the object’s group are zero, the ACL processing is skipped,</p>
      <p>Listing 1: fs/namei.c:acl_permission_check
i f ( IS_POSIXACL ( i n o d e ) &amp;&amp; ( mode &amp; ( The final question to ask is whether this optimization
S_IRWXG | S_IRWXO ) ) { is actually worth it. The cases when both the ACL_MASK
and ACL_OTHER entries of an ACL contain no
permis</p>
      <p>This would skip the (expensive) ACL processing in sions may be not so rare. They include the cases when a
the case when there are no permissions in the standard legacy tool removes all permissions except those for the
UNIX permission bits for the object’s group and in the object’s owner.
standard UNIX permission bits for others. Assuming the
standard UNIX permission bits are synchronized with
the corresponding ACL entries, both the ACL_MASK 6. Conclusions
and ACL_OTHER entries contain no permissions in this
case, and therefore only the object’s owner can have any We have identified a long-standing bug in the Linux
kerpermissions (which is not relevant at this point of the nel permission checking code. We have demonstrated
code). the bug in several examples, identified the piece of kernel
code that causes it, and proposed two fixes with diferent
performance impact.</p>
      <p>At the time of writing this paper, we have rechecked
the code in the newest available Linux kernel source code
(6.10-rc7) and the relevant code is still unchanged.</p>
      <p>We have also discovered that there are other cases
when the ACL is not checked at all, but these should
not cause any problems unless the standard UNIX
permission bits and the corresponding ACL entries become
desynchronized.</p>
      <p>Unless we receive some negative feedback, we plan
to submit one of the proposed fixes to the Linux kernel
maintainers.</p>
    </sec>
    <sec id="sec-4">
      <title>Acknowledgments</title>
      <sec id="sec-4-1">
        <title>This publication is the result of support under the</title>
        <p>Operational Program Integrated Infrastructure for the
project: Advancing University Capacity and
Competence in Research, Development a Innovation (ACCORD,
ITMS2014+:313021X329), co-financed by the European
Regional Development Fund.</p>
      </sec>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>A.</given-names>
            <surname>Gruenbacher</surname>
          </string-name>
          , acl - Access
          <source>Control Lists, man 5 acl</source>
          ,
          <year>2002</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <given-names>L.</given-names>
            <surname>Torvalds</surname>
          </string-name>
          , et al.,
          <source>The Linux kernel source code</source>
          , https://kernel.org/, 1991-
          <fpage>2024</fpage>
          . Accessed 2024-
          <volume>07</volume>
          -10.
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>