A bug in Linux ACL implementation Jaroslav Janáček Comenius University in Bratislava, Faculty of Mathematics, Physics and Informatics, Department of Computer Science, Mlynská dolina, 842 48, Bratislava, Slovakia Abstract 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. Keywords Linux, ACL 1. Introduction Each ACL consists of ACL entries. There are six types 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 spe- control 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 sufficient in many • ACL_GROUP – specifies the permissions for a simple cases, there are cases when we need to assign specific group, different permissions to different users and/or groups. • ACL_MASK – specifies the upper limit of per- For 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 oth- a 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 file 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 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 con- tains a user/group ID. 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 of supplementary groups. The user is identified by the The Linux kernel allows an ACL to be associated with any effective user ID and the primary group is identified by filesystem object residing in a filesystem that supports the effective group ID. To be correct, we must say that in ACL storage (e.g. ext4 and many other commonly used the Linux kernel the filesystem user ID and the filesystem filesystems for Linux). A directory can also have a default group ID are actually used instead, but these are usually ACL associated with it. The default ACL is used as a equal to the effective user ID and the effective group ID, template for initialization of the ACL of a new object so we will not distinguish among them. when it is created in the directory. 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) © 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 CEUR Workshop Attribution 4.0 International (CC BY 4.0). CEUR Workshop Proceedings (CEUR-WS.org) Proceedings http://ceur-ws.org ISSN 1613-0073 sticky bit). CEUR ceur-ws.org Workshop ISSN 1613-0073 Proceedings When a process tries to open a filesystem object which 2.1. Correspondence between ACL entries has an ACL associated with it, the Linux kernel performs and standard UNIX permissions a permission check algorithm, which should work as follows[1]: As we have mentioned above, the Linux kernel does not replace the standard UNIX permissions assigned to 1. If the effective user ID of the process matches the filesystem 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 effective 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 effective 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 stan- 4. Otherwise, if the entry of the type ACL_OTHER dard permission bits for the object’s group now corre- contains 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 pro- There is an exception, if the process has a special ef- gram would change the standard permission bits for the fective capability to override the access control (typically object’s group and for others to zero, leaving only some when it runs on behalf of the root user – effective user nonzero bits for the owner. If the object’s group permis- ID zero), the permission check algorithm is skipped. sion bits corresponded to the ACL_GROUP_OBJ entry As we can see, in all cases the effective permis- while there was an ACL_USER or ACL_GROUP type ACL sions are determined either by a single ACL entry entry present in the ACL, the legacy program would not (ACL_USER_OBJ, ACL_OTHER, or ACL_GROUP_OBJ remove the permissions granted by these ACL entries. if there is no ACL_MASK entry), or by the logical But, because the standard object’s group permission bits AND of a single ACL entry (ACL_USER, ACL_GROUP, correspond to the ACL_MASK entry, if it is present, ze- ACL_GROUP_OBJ) and the ACL_MASK entry. Also, if roing the standard object’s group permission bits zeroes the effective user ID of the process matches either the the ACL_MASK entry, thereby effectively removing all owner’s user ID, or the user ID in any of the ACL_USER permissions granted by ACL_USER, ACL_GROUP, and entries, the group ACL entries and the ACL_OTHER en- ACL_GROUP_OBJ entries. This way, a sort of compatibil- try are not used. And if the effective group ID or any ity with legacy tools is ensured even in the more complex supplementary group ID match the object’s group ID or cases. 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 3. Problem demonstration user who has no matching ACL entries – neither directly, nor via a group membership. We will now show that there are cases when the system does not behave according to the permission check algo- rithm 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). According to the permission check algorithm described in the previous section, the user tom should have no 3.1. Problem with ACL_USER entries access to the file. However, when we try it: First, we create a file f and set it’s ACL so that only the tom : c a t f file’s owner (jerry) has the read and write permissions, Works ! and others have the read permission: tom : echo a a a >> f bash : f : Permission denied j e r r y : echo ’ Works ! ’ > f j e r r y : s e t f a c l −m ’ u : : rw , g : : − , o : : r ’ f we can see, the user can read the file, and cannot write to jerry : getfacl f the file. This behaviour is obviously incorrect and may # file : f be considered a security problem. # owner : j e r r y It seems that the permissions for others are used for # group : j e r r y the user tom in this case, although, according to the per- u s e r : : rw− mission check algorithm, they should not be used. We group :: − − − can try to support this hypothesis by changing the per- o t h e r : : r −− missions for others and retesting the tom’s access: The setfacl command is used to modify (with the -m j e r r y : s e t f a c l −m ’ o : : w’ f flag) the ACL, the getfacl command is used to show jerry : getfacl f the current ACL associated with the file. # file : f Clearly, at this stage, another user (tom), who is not a # owner : j e r r y member of the group jerry, should be able to read the file, # group : j e r r y because there are no ACL entries matching tom or any of u s e r : : rw− his groups, and the ACL_OTHER entry grants the read u s e r : tom : − − − permission to others. We test it by trying to show the group :: − − − contents of the file as tom, and also by trying to modify mask :: − − − the file: o t h e r : : − w− tom : c a t f tom : c a t f Works ! cat : f : Permission denied tom : echo a a a >> f tom : echo a a a >> f bash : f : Permission denied jerry : cat f Now, the owner of the file (jerry) modifies the ACL so Works ! that the user tom is granted no permissions: aaa j e r r y : s e t f a c l −m ’ u : tom : − ’ f As we can see, after changing the permissions for oth- jerry : getfacl f ers to write only, the user tom cannot read the file, but # file : f can successfully write to the file. # owner : j e r r y We can now change the permissions of the ACL_MASK # group : j e r r y entry in the ACL to a nonzero value, e.g. to read and write, u s e r : : rw− and retest the tom’s access: u s e r : tom : − − − group :: − − − j e r r y : s e t f a c l −m ’m : : rw ’ f mask :: − − − jerry : getfacl f o t h e r : : r −− # file : f # owner : j e r r y As we can see, two new ACL entries have been cre- # group : j e r r y ated - the ACL_USER entry for the user tom, and the u s e r : : rw− ACL_MASK entry (which must be in the ACL when u s e r : tom : − − − there is any ACL_USER or ACL_GROUP entry). The per- group :: − − − missions of the (explicitly unspecified) ACL_MASK en- mask : : rw− try have been automatically calculated by the setfacl o t h e r : : − w− utility as the union (logical OR) of the permissions of all affected ACL entries (ACL_USER, ACL_GROUP, tom : c a t f ACL_GROUP_OBJ types). cat : f : Permission denied tom : echo a a a >> f We can see that the system behaves incorrectly again – bash : f : Permission denied the user tom as a member of the group users should have no access to the file, but the permissions for others seem The system now behaves as expected – the user tom to be applied instead. is denied both read and write access to the file. When we change the permissions of the ACL_MASK entry to a nonzero value, it works correctly again: 3.2. Problem with ACL_GROUP entries j e r r y : s e t f a c l −m ’m : : rw ’ f We can now repeat the tests with group permissions jerry : getfacl f instead of user permissions. First we restore the ACL to # file : f the initial state: # owner : j e r r y # group : j e r r y j e r r y : s e t f a c l −b f u s e r : : rw− j e r r y : s e t f a c l −m ’ o : : r ’ f group :: − − − jerry : getfacl f group : u s e r s : − − − # file : f mask : : rw− # owner : j e r r y o t h e r : : r −− # group : j e r r y u s e r : : rw− tom : c a t f group :: − − − cat : f : Permission denied o t h e r : : r −− tom : echo bbb >> f The setfacl -b command removes all ACL entries, and bash : f : Permission denied then we restore the read permissions for others. The user tom should have the read access now again because there are no matching ACL entries for tom or 4. Problem analysis his groups: We have demonstrated the incorrect behaviour in the tom : c a t f previous section and we have stated a hypothesis that Works ! under some conditions the ACL_OTHER entry (or per- aaa haps the standard permission bits for others) are used tom : echo bbb >> f instead of the correct ACL_USER or ACL_GROUP entry. bash : f : Permission denied As far as the conditions are concerned, it appears that this 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. jerry : getfacl f To start, we look for the code responsible for the ACL # file : f permission check algorithm. After looking at the struc- # owner : j e r r y ture of the Linux kernel source code [2] we can easily # group : j e r r y discover an interesting file fs/posix_acl.c, and more specif- u s e r : : rw− icaly the posix_acl_permission function. Quick analysis of group :: − − − this function leads to the conclusion that it does indeed group : 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 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 list- Works ! ing 1, is of a particular interest. In the lines 7–13 the aaa code checks if the effective user ID (or more precisely the tom : echo bbb >> f filesystem user ID) matches the owner’s user ID, and if it bash : f : Permission denied does, it uses the standard UNIX permission bits for the owner to determine whether to grant or deny the access. Table 1 The functions dealing with permission checking Function Called from Short description posix_acl_permission check_acl Parse the ACL and perform the ACL permission check algorithm. check_acl acl_permission_check Retrieve the ACL and call posix_acl_permission. acl_permission_check generic_permission Handle the standard UNIX permission bits and call check_acl in case an ACL is present. generic_permission do_inode_permission Call acl_permission_check, if it denies access, han- dle overrides (exceptions) based on capabilities. do_inode_permission inode_permission Call generic_permission or a special function to check permissions provided by the filesystem. inode_permission may_open (and others) Handle denying write access to immutable files, call do_inode_permission, call advanced security modules’ hooks for additional restrictions. may_open do_open, vfs_tmpfile Handle additional restrictions, call in- ode_permission. do_open Handles the last step of opening a file, including calling may_open to check if it is permitted. 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. 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 i f ( IS_POSIXACL ( i n o d e ) ) { while. The lines 22–34 handle the standard UNIX permissions everything would work just fine. The most probable rea- for 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 permis- code skips checking if the object’s group ID matches sions can be effectively 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 con- the object’s group and others in this case. tains an entry of the type ACL_USER or ACL_GROUP, it Finally, the lines 36–37 check the permissions for oth- can still match and deny access. ers. 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 non- no 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, Listing 1: fs/namei.c:acl_permission_check 1 s t a t i c i n t a c l _ p e r m i s s i o n _ c h e c k ( s t r u c t u s e r _ n a m e s p a c e ∗ mnt_userns , 2 s t r u c t i n o d e ∗ i n o d e , i n t mask ) 3 { 4 unsigned i n t mode = i n o d e −> i_mode ; 5 kuid_t i_uid ; 6 7 / ∗ Are we t h e owner ? I f so , ACL ’ s don ’ t m a t t e r ∗ / 8 i _ u i d = i _ u i d _ i n t o _ m n t ( mnt_userns , i n o d e ) ; 9 i f ( l i k e l y ( uid_eq ( c u r r e n t _ f s u i d ( ) , i_uid ) ) ) { 10 mask &= 7 ; 11 mode >>= 6 ; 12 return ( mask & ~ mode ) ? −EACCES : 0 ; 13 } 14 15 / ∗ Do we h a v e ACL ’ s ? ∗ / 16 i f ( IS_POSIXACL ( i n o d e ) && ( mode & S_IRWXG ) ) { 17 i n t e r r o r = c h e c k _ a c l ( mnt_userns , i n o d e , mask ) ; 18 i f ( e r r o r ! = −EAGAIN ) 19 return e r r o r ; 20 } 21 22 / ∗ Only RWX m a t t e r s f o r g r o u p / o t h e r mode b i t s ∗ / 23 mask &= 7 ; 24 25 /∗ 26 ∗ Are t h e g r o u p p e r m i s s i o n s d i f f e r e n t from 27 ∗ t h e o t h e r p e r m i s s i o n s i n t h e b i t s we c a r e 28 ∗ a b o u t ? Need t o c h e c k g r o u p o w n e r s h i p i f s o . 29 ∗/ 30 i f ( mask & ( mode ^ ( mode >> 3 ) ) ) { 31 k g i d _ t k g i d = i _ g i d _ i n t o _ m n t ( mnt_userns , i n o d e ) ; 32 i f ( in_group_p ( kgid ) ) 33 mode >>= 3 ; 34 } 35 36 / ∗ B i t s i n ’ mode ’ c l e a r t h a t we r e q u i r e ? ∗ / 37 return ( mask & ~ mode ) ? −EACCES : 0 ; 38 } i f ( IS_POSIXACL ( i n o d e ) && ( mode & ( 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- 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 ker- permissions (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 different performance impact. 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. 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 per- mission bits and the corresponding ACL entries become desynchronized. Unless we receive some negative feedback, we plan to submit one of the proposed fixes to the Linux kernel maintainers. Acknowledgments This publication is the result of support under the Operational Program Integrated Infrastructure for the project: Advancing University Capacity and Compe- tence in Research, Development a Innovation (ACCORD, ITMS2014+:313021X329), co-financed by the European Regional Development Fund. References [1] A. Gruenbacher, acl – Access Control Lists, man 5 acl, 2002. [2] L. Torvalds, et al., The Linux kernel source code, https://kernel.org/, 1991–2024. Accessed 2024-07-10.