posted 2012-08-13
POSIX Access Control Lists (ACLs) introduce fine-grained permissions to many common POSIX systems, such as Linux and BSD. If you're used to Windows filesystem permissions, POSIX ACLs work similarly: there are named users and groups, and you can grant them a certain level of access to files and directories independently of the standard RWX permission bits.
There's one novel aspect of POSIX ACLs, though—the “mask”. The mask permissions are an upper bound on the permissions that any other user or group can have. If the mask on a file is r-x, no user or group will be able to write to it, even if you grant them write access.
The ACL proposals were never accepted into POSIX, but they have been widely implemented nonetheless. For more on POSIX ACLs, see,
Much like with NTFS on Windows, you can set a default ACL for a directory, causing all newly-created files and directories within that directory to inherit a set of permissions (ACL entries).
This works for some simple operations, but fails with a few common utilities:
user $ mkdir acl
user $ cd acl
user $ setfacl -d -m user:apache:rwx .
All newly-created files in this directory should be rwx for the apache user.
user $ cp /etc/profile ./
user $ getfacl profile
# file: profile
# owner: mjo
# group: mjo user::rw-
user:apache:rwx #effective:r--
group::r-x #effective:r--
mask::r--
other::r--
From the user:apache:rwx #effective:r--
line, we
see that the apache user only has read access to the
file. This is because cp preserves the source file's group
bits.
Once an ACL exists, these group bits no longer specify the group permissions: they now represent the mask entry which is an upper bound on all permissions. Therefore, no named entry can write to the new file, regardless of the default ACL.
We host a bunch of Drupal sites. The Drupal code lies under a public folder, and the site-specific configuration lies under public/sites/default. Everyone in the developers group should have read/write access to all of this stuff.
We'd like to upgrade some modules, and update the main installation of Drupal for www.example.com.
user $ cd public/sites/default/modules/
user $ getfacl .
# file: .
# owner: root
# group: root
user::rwx
user:www.example.com:r-x
group::---
group:developers:rwx
mask::rwx
other::---
default:user::rwx
default:user:www.example.com:r-x
default:group::---
default:group:developers:rwx
default:mask::rwx
default:other::---
All of the permissions are currently how we want them. Notice the default ACL for the developers group. We'll download and replace the ctools module.
user $ rm -rf ctools/
user $ wget -q https://ftp.drupal.org/files/projects/ctools-7.x-1.1.tar.gz
user $ tar -xf ctools-7.x-1.1.tar.gz
What does happen:
user $ getfacl ctools
# file: ctools
...
group:developers:rwx #effective:r-x
mask::r-x
...
So, whoever upgrades the module now needs to dig through the ctools directory and fix the mask on every file/directory contained therein. What should have happened:
user $ getfacl ctools
# file: ctools
# owner: mjo
# group: mjo
user::rwx
user:www.example.com:r-x
group::---
group:developers:rwx
mask::rwx
other::---
default:user::rwx
default:user:www.example.com:r-x
default:group::---
default:group:developers:rwx
default:mask::rwx
default:other::--
Now we'll upgrade Drupal as well.
user $ cd /var/www/example.com/www/
user $ ls
total 16K
drwxrwx---+ 9 root root 4.0K 2012-06-19 13:16 public
drwxrwx---+ 2 root root 4.0K 2012-06-17 10:32 tmp
user $ getfacl .
# file: .
# owner: root
# group: root
user::rwx
user:www.example.com:--x
group::---
group:developers:rwx
mask::rwx
other::---
default:user::rwx
default:user:www.example.com:r-x
default:group::---
default:group:developers:rwx
default:mask::rwx
default:other::---
Again, the default ACLs specify that developers should be able to read/write/execute anything under the current directory, and the www.example.com user should be able to read only.
user $ cp -r ~/drupal-7.15/ ./
user $ mv public/sites/ drupal-7.15/
user $ rm -rf public/
user $ mv drupal-7.15/ public
This completes the upgrade, but the default ACLs aren't in effect because cp -r copied the source group permissions into our mask entry.
user $ getfacl public/
...
group:developers:rwx #effective:r-x
mask::r-x
...
Again, this is what it should look like:
user $ getfacl public/
# file: public/
# owner: mjo
# group: mjo
user::rwx
user:www.example.com:r-x
group::---
group:developers:rwx
mask::rwx
other::---
default:user::rwx
default:user:www.example.com:r-x
default:group::---
default:group:developers:rwx
default:mask::rwx
default:other::---
To require every developer to go through the entire public folder (which contains some other special ACLs) and fix all of the masks is unrealistic. The public/sites/default/files directory needs to be writable by the web user, so it's not as simple as just recreating the ACLs on everything under public.
Note: This algorithm is outdated. In Fixing POSIX ACLs in Common Utilities, I propose a new fix that uses apply-default-acl, and so the default ACL application happens according to the apply-default-acl algorithm.
The utilities in question should be patched to reapply the default ACL when creating new files. For backwards-compatibility, an environment variable—GNU_REAPPLY_DEFAULT_ACL—should be set to “true” to request the new behavior.
The reapplication algorithm is as follows:
Note: I've written an update to this article with a new fix: Fixing POSIX ACLs in Common Utilities.