ACL semantics -- consistency with existing ACL systems?

Robert Watson robert at cyrus.watson.org
Sun Oct 24 01:53:57 GMT 1999


I am part way into implementing POSIX.1e ACLs in FreeBSD, and had a couple
of questions.  During my first pass, I plan to largely implement the same
ACL library functions as found in Solaris and Linux (the two reference
implementations I have access to) and put the POSIX.1e interface on top.
There are some inconsistencies between POSIX.1e behavior and these
behaviors.  Given that this is only a first pass, and that I haven't had a
chance to use either a strict POSIX.1e implementation or the Solaris/Linux
ones in any depth, it's possible that my concerns are due to
misunderstanding.  Either way, I'd love suggestions and comments :-).
Unfortunately, I don't have access to an IRIX box so couldn't look at the
implementation there.

Both platforms provide acl() and facl() syscalls that both get and set
ACLs by path or file descriptor, respectively.  The calls work like this
(Solaris):

     #include <sys/acl.h>

     int  acl(char  *pathp,  int  cmd,  int  nentries,   aclent_t
     *aclbufp)

     int  facl(int  fildes,  int  cmd,  int  nentries,   aclent_t
     *aclbufp)

This is different from POSIX.1e which defines the setting and retrieval of
ACLs for directories/etc as either the access ACL (ACL_TYPE_ACCESS) or
default ACL (ACL_TYPE_DEFAULT).  Both Linux and Solaris combine the
contents of the two ACLs by adding a flag to the type in each acl_entry_t
to indicate that a given entry is a default entry, and not the base entry.
For compatibility reasons, I would like to support both the POSIX.1e and
Solaris/Linux interfaces to the ACLs.

My plan was to wrap POSIX.1e around the simpler acl/facl interface, but it
raises some consistency problems because of the lack of seperation between
default and access ACLs.  Suppose, for example, a process reads in the
default ACL of a directory, and then wants to write it out again.  In
POSIX.1e this is described by the series of calls acl_get_file() and
acl_set_file() with ACL_TYPE_DEFAULT set on both calls.  If this is passed
down to acl/facl, then the entire ACL will be read and written in both
cases.  So we'd actually get:

acl_get_file()
	acl()		# read in both acls
process modifies acl
acl_set_file()
	acl()		# read in both acls
	acl()		# write out both acls with one changed

So in acl_set_file() on just one of the two ACLs, there is an opportunity
for a race condition--because acl_set_file() doesn't want to remove the
access portion of the ACL from the file, it must read in the existing ACL,
merge the changes (overwrite the existing defaul ACL) and then write the
whole thing out.  The result is that, given a second process doing a
similar thing at the same time, you can end up with an unfortunate
arrangement of having it be as though one process hadn't made the changes.
I.e., 

	p1				p2
	acl_get_file()
		acl()
					acl_get_file()
					acl()
	acl_set_file()
		acl()
					acl_set_file()
					acl()
		acl()
					acl()

I.e., if p1 was modifying the default acl, and p2 the access acl, p2 will
stomp on p1's changes even though in theory they were independent.  In the
event that they were both restricting access to the directory in question,
this could result in a user having greater rights on the directory than
was intended--clearly undesirable.

I suppose I could code the posix1e routines to use advisory locking during
multiple calls to acl() in the same library call, but that doesn't seem
like a great idea.  The correct answer is probably to make acl/facl aware
of different types of ACLs on the same object.  It seems unfortunately as
though reversing the arrangement to wrap Solaris/Linux around POSIX.1e
also suffers from a race, so the solution may end up being to add a new
argument to acl/facl, or to have four syscalls (which is getting a little
excessive).  I could also add a couple more cmd constants but none of
these solutions seems clean.

Another side effect of the storage of both ACLs in the same structure is
that routines such as aclcheck and aclsort have mildly different
semantics.

My other thought on the issue was that in my original first pass, I
required the user process to only submit ordered ACLs, allowing the
verification of the validity of the ACL to be performed in linear time in
the kernel, as opposed to the nlogn time if the kernel is responsible for
dealing with the ordering, etc, to check for unique uids/gids/etc.  This
is not compatible at a syscall level with Linux and Solaris as they
require the kernel to verify ACL validity on unordered ACLs.  Clearly
someone has to do the sorting for the verification sometime (or risk an
n^2 verification, or use hashes?) but I thought it better to dump the
burden on the user process (possibly via the library).  Has anyone had any
thoughts on this one?

Solaris assigns a maximum ACL length of 1024 entries--Linux places a bound
based on the block size.  Is this a limit people actually run into? 
Without user-defined groups in UNIX, I could see this as a possible
problem in some situations, but given the limits of groups under UNIX,
perhaps that isn't a solution either. 
	
This also raises an issue with the POSIX.1e interface: acl_get_file() 
provides an argument to determine whether the access acl or default acl
should be retrieved, if the path provided points to a directory. 
acl_get_fd() does not provide this option, under the (false) assumption
that it is not possible to have an fd open for a directory?  My temptation
is to modify the arguments of acl_get_fd to allow for this additional
argument in the style of acl_get_file().  Has anyone else tried to address
this?

  Robert N M Watson 

robert at fledge.watson.org              http://www.watson.org/~robert/
PGP key fingerprint: AF B5 5F FF A6 4A 79 37  ED 5F 55 E9 58 04 6A B1
TIS Labs at Network Associates, Safeport Network Services


To Unsubscribe: send mail to majordomo at cyrus.watson.org
with "unsubscribe posix1e" in the body of the message



More information about the posix1e mailing list