The aim of this project was to add native NFSv4 ACLs implementation - user/admin tools, userland libraries and kernel support for both UFS and ZFS - to the FreeBSD operating system, along with regression tests and documentation. Code is already in stock FreeBSD; the Perforce branch used to develop it was "//depot/projects/soc2008/trasz_nfs4acl/...".

If you have any questions or suggestions regarding NFSv4 ACL support, feel free to contact me: EdwardTomaszNapierala

Current state

Libc and some of the kernel changes were released as part of FreeBSD 8.0. Userland utilities, regression tests, support for NFSv4 ACLs in ZFS and UFS were released in FreeBSD 8.1. In FreeBSD 9.0 the semantics was changed to match PSARC/2010/029 (http://arc.opensolaris.org/caselog/PSARC/2010/029/20100126_mark.shellenbaum ).

API

The API is an extension of existing POSIX.1e API. It's similar to Darwin (MacOS X) API, with the following differences:

1. ACL type is ACL_TYPE_NFS4, not ACL_TYPE_EXTENDED. This is on purpose - in Darwin ACL_TYPE_EXTENDED means "additional" entries, i.e. it doesn't include the "canonical six" entries derived from the usual UNIX file mode. For example, with newly created file without any inherited or explicitly added ACL entries, acl_get_file(path, ACL_TYPE_EXTENDED) will return NULL. In FreeBSD, in the same situation, ACL_TYPE_NFS4 returns six entries: three "deny" and three "allow". This is the same "canonical six" you can see in SunOS. See getfacl example below.

2. Darwin uses UUIDs instead of the usual id_t (e.g. uid_t) for ACL entry qualifier. FreeBSD uses the usual IDs.

3. Because of the UUIDs, under Darwin there is no need for acl_tag_t to distinguish between owner, owning group, user, group etc. Under FreeBSD, acl_tag_t is used for this purpose, just like with POSIX.1e ACLs.

4. Because of #3, Darwin developers reused acl_{get,set}_tag_type to set ACL entry type, i.e. "allow" or "deny" etc. In FreeBSD, there is another set of routines, acl_{get,set}_entry_type_np (e.g. acl_set_entry_type_np(aclp, ACL_ENTRY_TYPE_ALLOW)).

One curious detail of this implementation - fortunately not visible to the API users - is the "branding": libc needs to keep track of what "brand" ACL is, whether it's NFSv4, POSIX.1e or unknown. It works automatically - for example, during acl_get_file(3) ACL gets branded according to the "type" argument; during acl_set_permset ACL, if its brand is unknown, it gets branded as NFSv4 if any of the NFSv4 permissions that are not valid for POSIX.1e ACL are set etc. Branding information is used for printing out the ACL (acl_to_text(3)), veryfying acl_set_whatever arguments (checking against setting bits that are valid only for NFSv4 in ACL branded as POSIX.1e) etc. Application may check the brand of the ACL given using acl_get_brand_np(3).

Tools

For POSIX.1 ACLs there are two standard tools - getfacl(1) and setfacl(1). NFSv4 standard does not define any command line interface. Darwin and SunOS, instead of reusing getfacl(1) and setfacl(1), use an extended chmod(1)/ls(1). They have different syntax, though. I decided to take another route: to extend getfacl(1)/setfacl(1), adding functionality where needed, for example to add ACL entry at a specified position (with NFSv4 ACLs, differently from POSIX.1e ACLs, position of the entry matters) or to remove entry at a specified position. Usual "-x" and "-m" options still work, although with NFSv4 ACLs, their behaviour might be slightly misleading.

[trasz@traszkan:/tmp]$ touch blah
[trasz@traszkan:/tmp]$ getfacl blah
# file: blah
# owner: trasz
# group: wheel
            owner@:--x-----------:------:deny
            owner@:rw-p---A-W-Co-:------:allow
            group@:rwxp----------:------:deny
            group@:--------------:------:allow
         everyone@:rwxp---A-W-Co-:------:deny
         everyone@:------a-R-c--s:------:allow
[trasz@traszkan:/tmp]$ getfacl -v blah
# file: blah
# owner: trasz
# group: wheel
            owner@:execute::deny
            owner@:read_data/write_data/append_data/write_attributes/write_xattr/write_acl/write_owner::allow
            group@:read_data/write_data/execute/append_data::deny
            group@:::allow
         everyone@:read_data/write_data/execute/append_data/write_attributes/write_xattr/write_acl/write_owner::deny
         everyone@:read_attributes/read_xattr/read_acl/synchronize::allow
[trasz@traszkan:/tmp]$ setfacl -m u:trasz:rwxcosW:allow blah
[trasz@traszkan:/tmp]$ getfacl blah
# file: blah
# owner: trasz
# group: wheel
        user:trasz:rwx------Wc-os:------:allow
            owner@:--x-----------:------:deny
            owner@:rw-p---A-W-Co-:------:allow
            group@:rwxp----------:------:deny
            group@:--------------:------:allow
         everyone@:rwxp---A-W-Co-:------:deny
         everyone@:------a-R-c--s:------:allow
[trasz@traszkan:/tmp]$ setfacl -x owner@::deny,owner@::allow blah
[trasz@traszkan:/tmp]$ getfacl blah
# file: blah
# owner: trasz
# group: wheel
        user:trasz:rwx------Wc-os:------:allow
            group@:rwxp----------:------:deny
            group@:--------------:------:allow
         everyone@:rwxp---A-W-Co-:------:deny
         everyone@:------a-R-c--s:------:allow
[trasz@traszkan:/tmp]$ setfacl -b blah
[trasz@traszkan:/tmp]$ getfacl blah
# file: blah
# owner: trasz
# group: wheel
            owner@:rwxp----------:------:deny
            owner@:-------A-W-Co-:------:allow
            group@:rwxp----------:------:deny
            group@:--------------:------:allow
         everyone@:rwxp---A-W-Co-:------:deny
         everyone@:------a-R-c--s:------:allow
[trasz@traszkan:/tmp]$ setfacl -x everyone@:rwxp---A-W-Co-:------:deny blah
[trasz@traszkan:/tmp]$ getfacl blah
# file: blah
# owner: trasz
# group: wheel
            owner@:rwxp----------:------:deny
            owner@:-------A-W-Co-:------:allow
            group@:rwxp----------:------:deny
            group@:--------------:------:allow
         everyone@:------a-R-c--s:------:allow
[trasz@traszkan:/tmp]$ chmod 644 blah
[trasz@traszkan:/tmp]$ getfacl blah
# file: blah
# owner: trasz
# group: wheel
            owner@:--------------:------:deny
            owner@:-------A-W-Co-:------:allow
            group@:--------------:------:deny
            group@:--------------:------:allow
         everyone@:------a-R-c--s:------:allow
            owner@:--x-----------:------:deny
            owner@:rw-p---A-W-Co-:------:allow
            group@:-wxp----------:------:deny
            group@:r-------------:------:allow
         everyone@:-wxp---A-W-Co-:------:deny
         everyone@:r-----a-R-c--s:------:allow
[trasz@traszkan:/tmp]$

Semantics

This implementation closely follows SunOS implementation found in ZFS, mostly described in http://tools.ietf.org/id/draft-ietf-nfsv4-minorversion1-03.txt. It's different from the Darwin implementation, for several reasons, such as the lack of documentation on Darwin behaviour. The only known differences from SunOS are direct consequences of semantical differences between SunOS and BSD, e.g. requirement of write access to the directory being moved from one parent directory to another, which is not enforced in non-BSD systems. Semantics of NFSv4 ACLS in UFS and FreeBSD port of ZFS are the same. This implementation is compliant with rfc3530. The only unimplemented parts are:

1. APPEND_DATA on regular files is ignored; WRITE_DATA permission is checked instead. Same as in SunOS.

2. WRITE_NAMED_ATTR and READ_NAMED_ATTR are ignored.

3. SYNCHRONIZE is ignored. Same as in SunOS.

4. ALARM and AUDIT entry types are not supported. Same as in SunOS.

Solaris, as mentioned in http://www.ietf.org/mail-archive/web/nfsv4/current/msg03290.html, seems to implement this: http://tools.ietf.org/id/draft-ietf-nfsv4-minorversion1-03.txt. There is also http://www.nfsv4.org/nfsv4-wg-archive-feb-03-feb-05/att-1278/01-S10FCS_SolarisACLBehavior.txt, referenced from http://www.nfsv4.org/nfsv4-wg-archive-feb-03-feb-05/1278.html, which describes the subset of NFSv4 ACLs accepted when being backed by POSIX-draft ACLs in filesystem.

Note that in the FreeBSD implementation, there is no support for translation between POSIX.1e and NFSv4 ACLs. Adding one is not planned either.

Testing

Two sets of tests were implemented - one is an extension of fstest by PawelJakubDawidek, intended to test actual access control. It can be found at tools/regression/fstest/tests/granular/. Second one, tools/regression/acltools/, is an "utility-level" test, intended to verify correct operation of user utilities, ACL part of libc, and some kernel routines, for example the ones that implement ACL inheritance, recomputation of ACL on mode change or recomputation of mode on ACL change. Basic tests for POSIX.1e ACLs were added as well.

Compatibility

Instead of adding support for the new API to Samba and possibly other software, I decided to write a wrapper that implements SunOS-compatible API (acl(3)/facl(3)) using FreeBSD system calls. It is available in the FreeBSD Ports Collection, as sysutils/libsunacl. Code can be found at http://sourceforge.net/projects/libsunacl/. Porting should be simple - in application you want to make work, change "<sys/acl.h>" into "<sunacl.h>"; then link with libsunacl.

FAQ

Q: Inheritance doesn't work the way I expect; access is denied while it shouldn't be.

A: Set "aclmode=passthrough" and "aclinherit=passthrough" ZFS properties. For UFS, you're out of luck, I'm afraid; there is no way to change the behaviour there.

NFSv4_ACLs (last edited 2021-03-30T08:27:06+0000 by KubilayKocak)