Subject: [nfsv4] CIFS friendly ACL/mode algorithm

From: Sam Falkner <[email-masked]>
Date: Wed, 30 Aug 2006 14:14:52 -0600

Here is an outline of an algorithm that is an alternative to the one  
currently in Section A.3 of draft-ietf-nfsv4-minorversion1-06.

Both this and the Section A.3 algorithm conform to the requirements  
set in Section 11 (the normative ACL section).  The primary  
differences are that this one tries to produce ACLs that are friendly  
toward the Windows ACL GUI, and that it has different goals with  
respect to preserving the ACL upon chmod.

This isn't a formal document.  If it were deemed useful, it could be  
made more formal and become the new contents of Section A.3; or, it  
could go on to be a standalone I-D on an informational track.

Please note that this message, and the I-D that may come of it, is  
meant to be helpful, not a hard requirement.  The requirements are in  
Section 11 of the minorversion1 document, eventually to be in the  
RFC.  This is just one way to meet the requirements.  I hope it's  
useful.

* Producing an ACL based upon a mode

Before dealing with changing an existing ACL based upon a mode, we  
will look at generating an ACL from a mode.  On servers that always  
have an ACL present, this could be used to compute an initial ACL  
based upon a mode if no inheritance is happening.  On servers that  
have the concept of "this file has no ACL", this could be used to  
return an ACL when requested on a file that currently has no ACL.

If a server implementer decided to discard the ACL upon receiving a  
new mode, instead of changing the ACL, then this could be a way to  
compute the new ACL in a way that's more Windows-friendly than the  
canonical six-member ACL as seen in Section A.3 and other places.

** The Good

Start with three ACEs, call them A1, A2, and A3.  Lay them out thus:

A1: OWNER@:::ALLOW
A2: GROUP@:::ALLOW
A3: EVERYONE@:::ALLOW

For the owner permissions, i.e. the access_mask bits on A1, set the  
bits READ_DATA, WRITE_DATA, and EXECUTE according to the mode bits  
MODE4_RUSR, MODE4_WUSR, and MODE4_XUSR.  For A2 and A3, do the same  
thing with MODE4_*GRP and MODE4_*OTH.

Finally, on A1, set the access_mask bits WRITE_ATTRIBUTES,  
WRITE_NAMED_ATTRS, and WRITE_ACL.  On A3, set the access_mask bits  
READ_ATTRIBUTES, READ_NAMED_ATTRS, READ_ACL, and SYNCHRONIZE.  At  
implementer's discretion, this could be modified slightly, e.g. A1  
may receive WRITE_OWNER, if that is needed for normal "chgrp" or even  
"chown", given the policies of that server.

The good news is that this produces very easy-to-understand ACLs that  
are no problem for Windows ACL GUIs.  It covers the vast majority of  
permissions that are seen in the UNIX-like world.

** The Bad

The above doesn't work with modes such as 077 or 007.

Create two more ACEs, call them D1 and D2, and lay them out thus:

D1: OWNER@:::DENY
D2: GROUP@:::DENY

If (( ! MODE4_RUSR) && (MODE4_RGRP || MODE4_ROTH)), set the  
access_mask bit READ_DATA in D1.  If (( ! MODE4_RGRP) &&  
(MODE4_ROTH)), set READ_DATA in D2.  Set WRITE_DATA and EXECUTE in D1  
and D2, according to MODE4_W* and MODE4_X*.

Put it all together like: D1, D2, A1, A2, A3.

It is recommended that you omit D1 and D2 if they have no access_mask  
bits set.

The bad news is that this is now more complicated than the simple A1,  
A2, A3 ACL.  The good news is that it's still friendly to the Windows  
ACL GUI, and it should be very rare.

** The Ugly

The above doesn't work with modes such as 707.

Create an ACE, and call it A0, thus:

A0: OWNER@:::ALLOW

If ((MODE4_RUSR) && ( ! MODE4_RGRP) && (MODE4_ROTH)), set the  
access_mask bit READ_DATA in A0.  Do the same for WRITE_DATA and  
EXECUTE according to MODE4_W* and MODE4_X*.

Put it all together like: A0, D1, D2, A1, A2, A3.

It is strongly recommended that you omit A0 if it has no access_mask  
bits set.  Note that if A0 is present, then at least D2 is guaranteed  
to be present.

You might be tempted to just move A1 to the front of the ACL, instead  
of creating a separate A0.  That might be okay, but it's nicer to  
just create A0, and keep A1, A2, and A3 constant.

The bad news is that you have an ALLOW ACE appearing before a DENY  
ACE.  Windows ACL GUI responds: "Wicked, tricksy ACL!  We hates it!"  
and sorts it into D1, D2, A0, A1, A2, A3.  If it did do this, and the  
owner of the file was a member of the owning group, then the owner  
would be denied those permissions that "other" has but "group" is  
lacking.

The good news is that, once again, this should be rare.  It should be  
much rarer still that a file where an NFS client has set mode 705  
will have a CIFS client read the ACL, modify it without taking care  
to make it sane, and write it back to the server.  But worst case  
scenario, the owner gets denied a subset of read/write/execute  
permissions, and note that the owner can always just fix the ACL.

If this became a chronic problem somewhere, the end user could be  
encouraged to use ACLs rather than modes, to either whitelist the  
users who should be accessing a file, or blacklist a troublesome set  
of users.  As Nico pointed out in a previous thread, blacklisting a  
group SID with a DENY ACE is a bad idea; instead, if you want to  
blacklist, then list the users explicitly, one ACE per user.

** This algorithm compared with a "mask attribute"

If a new "mask attribute" were added to the NFSv4 protocol, and a  
server implemented such an attribute, it could stick with ACEs A1,  
A2, and A3, without ever needing to have A0, D1, or D2.  In such a  
scheme, mode 705 would look like (omitting bits beyond read, write,  
and execute):

OWNER@:READ_DATA/WRITE_DATA/EXECUTE::ALLOW
GROUP@:::ALLOW
EVERYONE@:READ_DATA/EXECUTE::ALLOW

Users in the owning group would be denied READ_DATA and EXECUTE due  
to the mask attribute.

That's all very nice, but the problem is that the ACL is a lie.  With  
the above ACL, members of the owning group should be permitted  
READ_DATA and EXECUTE, due to the EVERYONE@ ACE at the end.  If they  
are denied, this would be confusing and annoying to end users, and  
likely to be a service call generator.  Worse, if such an ACL were  
copied (through "cp -p" or a backup/restore utility), the owning  
group suddenly gets elevated permission.  So a server implementing  
this proposed mask attribute still has to solve the problem of  
representing mode 705 as an ACL.

* Applying a mode to an existing ACL

Okay, now what do we do if we are applying a mode to a file that  
already has an ACL?  Maybe it's an ACL on a pre-existing file, or  
maybe it's a newly created file that inherited an ACL.

First step, remove all ACEs with a "who" value of OWNER@, GROUP@, or  
EVERYONE@, a type of ALLOW or DENY, and having the INHERIT_ONLY_ACE  
flag clear.

Next, we throttle the ACL to the group permissions.  Examine all ACEs  
of type ALLOW with the INHERIT_ONLY_ACE flag clear.  For each ACE, if  
MODE4_RGRP is not set, we clear READ_DATA.  If MODE4_WGRP is not set,  
we clear WRITE_DATA and APPEND_DATA.  If MODE4_XGRP is not set, we  
clear EXECUTE.  Optionally, you may clear other frightful bits, such  
as WRITE_OWNER.  When this is done, we refer to this ACL as <modified- 
ACL>.

Finally, we lay the result out thus:

A0, D1, D2, <modified-ACL>, A1, A2, A3.

The good news is that if the original ACL was laid out in a manner  
that was friendly to the Windows ACL GUI, and A0 is not present, then  
the new ACL is also Windows ACL GUI friendly.  I believe that in the  
overwhelming majority of cases, you will end up with an ACL without  
any DENY ACEs.

** This algorithm compared with the current Section A.3 algorithm

The most significant difference is that this algorithm makes no  
attempt to make its group-permission-throttling temporary.  Recall  
the thread "ACLs and modes" from last week.  As Bruce put it, many  
existing mode-affects-ACL semantics attempt to have the sequence:

chmod 600
chmod 644

restore ACL permissions that the "chmod 600" denied.  Moreover, some  
implementations attempt to have

chmod 600
getacl
setacl
chmod 644

restore permissions in the ACL.  This algorithm attempts neither.

Mario asked whether there was a real business justification for  
restoring the ACL.  Good question.  When we set out to write the  
algorithm that is now in Section A.3, an important goal was, "don't  
throw away the ACL upon chmod".  And except for some very rare,  
annoying cases, it doesn't throw much of anything away.  But does  
anyone really do "chmod 644" to restore an ACL?

I don't.  I primarily use ACLs to give a small, targeted set of  
people enhanced access, usually read/write, to a file.  "chmod 600"  
disables this access, for good reason.  But to re-grant the  
permission, I don't do "chmod 664"; instead, I simply re-edit the ACL.

Besides potentially restoring an ACL on chmod, the Section A.3  
algorithm has the nice property of preserving information.  So if  
"chmod 600" removed my friends' access to my file, I could edit the  
ACL, and delete the DENY ACEs.  This was nice.  The algorithm  
described here could be modified to do this.

*** Modifying this algorithm to preserve information

If the implementer wishes to preserve information in the ACL  
following a chmod, it may take this variation.  This doesn't  
implement "chmod 644 restores permission", it simply preserves a  
shadow of the former ACL, which could be restored explicitly by a  
user at some future time.

Replace the paragraph above, beginning with "Next, we throttle the  
ACL...".

For every ACE in the ACL of type ALLOW and INHERIT_ONLY_ACE clear, we  
check to see if there is a previous DENY ACE with the same "who"  
field and the same flags field.  If there isn't one, we add one to  
the beginning of <modified-ACL>.  If MODE4_RGRP is not set, we set  
READ_DATA in the DENY ACE.  If MODE4_WGRP is not set, we set  
WRITE_DATA and APPEND_DATA in the DENY ACE.  If MODE4_XGRP is not  
set, we set EXECUTE in the DENY ACE.

If we set any bits in a DENY ACE where IDENTIFIER_GROUP was set, or  
for a special "who" (e.g. my personal favorite, "DIALUP@"), then we  
need to add the A0 ACE to the front of the ACL.  Instead of doing  
that, you could only use DENY ACEs for ordinary users, not groups or  
special "who" values.  For groups or special "who" values, just clear  
the access_mask bit in the ALLOW ACE.  Is this worth doing?  It's a  
balance between preserving information and being Windows ACL GUI  
friendly.

*** This algorithm doesn't preserve all "weird bits"

Another difference is that the Section A.3 algorithm permits one to  
have an ACE such as:

EVERYONE@:READ_ACL::DENY

persist beyond setting a new mode.  If this is really desirable, then  
change the first step in this algorithm.  Rather than delete OWNER@,  
GROUP@, and EVERYONE@ ACEs, simply clear the bits READ_DATA,  
WRITE_DATA, APPEND_DATA, and EXECUTE.  This is pretty much what the  
Section A.3 algorithm does.  But you would have to determine how to  
keep the ACL from growing by three or more ACEs every time this  
algorithm is invoked.  It's possible.  My opinion is that it's not  
worth the effort to do this.

* Examples

Create a file with mode 644.

OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:READ_DATA::ALLOW
EVERYONE@:READ_DATA/READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/ 
SYNCHRONIZE::ALLOW

chmod 044

OWNER@:READ_DATA::DENY
OWNER@:WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:READ_DATA::ALLOW
EVERYONE@:READ_DATA/READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/ 
SYNCHRONIZE::ALLOW

chmod 705

OWNER@:READ_DATA/WRITE_DATA/EXECUTE::ALLOW
GROUP@:READ_DATA/EXECUTE::DENY
OWNER@:READ_DATA/WRITE_DATA/EXECUTE/WRITE_ATTRIBUTES/ 
WRITE_NAMED_ATTRS/WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:::ALLOW
EVERYONE@:READ_DATA/EXECUTE/READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/ 
SYNCHRONIZE::ALLOW

chmod 644

OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:READ_DATA::ALLOW
EVERYONE@:READ_DATA/READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/ 
SYNCHRONIZE::ALLOW

modify the ACL, giving samf read/write permission; mode remains 644

[email-masked]:READ_DATA/WRITE_DATA::ALLOW
OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:READ_DATA::ALLOW
EVERYONE@:READ_DATA/READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/ 
SYNCHRONIZE::ALLOW

chmod 600

[email-masked]:::ALLOW
OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:::ALLOW
EVERYONE@:READ_ATTRIBUTES/READ_NAMED_ATTRS/SYNCHRONIZE::ALLOW

- or, if you decide to implement the "preserve information" option -

[email-masked]:READ_DATA/WRITE_DATA::DENY
[email-masked]:READ_DATA/WRITE_DATA::ALLOW
OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:::ALLOW
EVERYONE@:READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/SYNCHRONIZE::ALLOW

chmod 644

[email-masked]:::ALLOW
OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:READ_DATA::ALLOW
EVERYONE@:READ_DATA/READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/ 
SYNCHRONIZE::ALLOW

- or, if you decide to implement the "preserve information" option -

[email-masked]:READ_DATA/WRITE_DATA::DENY
[email-masked]:READ_DATA/WRITE_DATA::ALLOW
OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:READ_DATA::ALLOW
EVERYONE@:READ_DATA/READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/ 
SYNCHRONIZE::ALLOW

create file, mode 644, inheriting a "deny apache web server read/ 
write" ACE from the parent directory

[email-masked]:READ_DATA/WRITE_DATA::DENY
OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:READ_DATA::ALLOW
EVERYONE@:READ_DATA/READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/ 
SYNCHRONIZE::ALLOW

chmod 666

[email-masked]:READ_DATA/WRITE_DATA::DENY
OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:READ_DATA/WRITE_DATA::ALLOW
EVERYONE@:READ_DATA/WRITE_DATA/READ_ATTRIBUTES/READ_NAMED_ATTRS/ 
READ_ACL/SYNCHRONIZE::ALLOW

create a file, mode 600, inheriting an "allow evil guy read/write"  
ACE from the parent directory

[email-masked]:::ALLOW
OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:::ALLOW
EVERYONE@:READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/SYNCHRONIZE::ALLOW

- or, if you decide to implement the "preserve information" option -

[email-masked]:READ_DATA/WRITE_DATA::DENY
[email-masked]:READ_DATA/WRITE_DATA::ALLOW
OWNER@:READ_DATA/WRITE_DATA/WRITE_ATTRIBUTES/WRITE_NAMED_ATTRS/ 
WRITE_ACL/WRITE_OWNER::ALLOW
GROUP@:::ALLOW
EVERYONE@:READ_ATTRIBUTES/READ_NAMED_ATTRS/READ_ACL/SYNCHRONIZE::ALLOW

- Sam

_______________________________________________
nfsv4 mailing list
[email-masked]
https://www1.ietf.org/mailman/listinfo/nfsv4

---

From: "J. Bruce Fields" <[email-masked]>
Date: Wed, 30 Aug 2006 23:12:37 -0400

> currently in Section A.3 of draft-ietf-nfsv4-minorversion1-06.

Thanks, that sounds fine to me.

My only request is that we consider removing the stuff from the appendix
that recommends adding deny's to emulate the mask.  It's clever, but too
complicated, especially for a spec.

The rest looks OK.  Just one correction:

> A2, and A3, without ever needing to have A0, D1, or D2.

The mask attribute proposal *doesn't* change what's returned as the ACL
attribute.  The ACL still represents effective permissions.  (So,
probably something like what you describe.)

It gives an optional method on the side for returning the extra stuff
(unmasked acl and masks), for clients that want that stuff; thus this
sort of decision...

> Is this worth doing?  It's a balance between preserving information
> and being Windows ACL GUI  friendly.

...is left to the clients instead of being a decision that's made for
the clients on behalf of the server.

Anyone that wants a simple ACL that doesn't have lots of DENY's added
can just request the ACL attribute, and a client that really wants all
that information can also request the mask attribute and get the
(unmasked ACL, mask) pair in all its glory.

--b.