Discussion:
Want to turn permission propagation off in SetNamedSecurityInfo . . .
(too old to reply)
Toby Everett
2004-01-24 18:17:44 UTC
Permalink
I'm the developer of a recently released Perl module that provides an
OO interface for manipulating Win32 permissions (Win32::Security).
It's still being fleshed out, but the core is reasonably solid.

The module understands inherited permissions, and so I've been able to
write scripts that search for scenarios where the permissions marked
as inherited don't correlate with the inheritable permissions from the
parent object (i.e. where a user has moved files or objects from one
folder to another on the same volume). It has support for dumping
permissions in CSV (excluding properly inherited permissions, of
course:), fixing mixups like the above, etc.

Right now I'm working on code that will let one take a CSV file with
permissions, make changes to it, and then apply it back onto the file
system. The underlying module uses GetNamedSecurityInfo and
SetNamedSecurityInfo. It occurs to me that while the automatic
propagation of inherited permissions is useful, it would be very
useful to be able to turn it off at times.

For instance, say I want to set permissions on C:\, C:\WINDOWS, and
C:\WINDOWS\system32. That means that when I set the permissions on
C:\, SetNamedSecurityInfo will ripple through the entire file system
checking every single permission. That is all well and good. Then,
when I set the permissions on C:\WINDOWS, SetNamedSecurityInfo will
ripple through the entire WINDOWS tree checking permissions.
Similarly for C:\WINDOWS\system32. Now, this means that every file in
C:\WINDOWS\system32 will have its permissions checked three times.
This is rather inefficient. It would be nice if there was a way to
call SetNamedSecurityInfo and _not_ have it ripple permissions. Then
I would make the calls in reverse order (actually, it doesn't matter
what order I use so long as I make the call to C:\ last), and on the
call to C:\ tell it to ripple permissions. Voila! Efficient use of
computing resources.

Thoughts?

I've thought about using SetFileSecurity, but it's obsolete and I'd
have to hunt for its kindred functions when dealing with the registry,
AD, etc.

--Toby Ovod-Everett
***@ovod-everett.org
Dave McPherson [MS]
2004-01-30 22:38:20 UTC
Permalink
SetNamedSecurityInfo does not expose a no-propagate option because the
inheritance is driven from the administrator by specifying the "inherit from
parent permissions that apply to child objects...".



If you know your going to set the permission on sub directories you could
temporarily set the protected bit on there security descriptor. This would
avoid propagation by SetNamedSecurityInfo.
Toby Everett
2004-02-01 02:15:26 UTC
Permalink
Post by Dave McPherson [MS]
SetNamedSecurityInfo does not expose a no-propagate option because the
inheritance is driven from the administrator by specifying the "inherit from
parent permissions that apply to child objects...".
If you know your going to set the permission on sub directories you could
temporarily set the protected bit on there security descriptor. This would
avoid propagation by SetNamedSecurityInfo.
But there's no way to turn the bit back off in such a manner that you
reduce overlapping propagation. Turning the bit on for a given sub
directory automatically causes propagation on all child nodes of the
node in question. Turning the bit back off automatically causes
propagation on all child nodes yet again, thus causing double
propagation. Also, ordering becomes a little more tricky using this
approach, because you need to walk the entire tree leaves to root
appying the bits at all nodes that will later get permissions,
ensuring that no node is touched until all applicable child nodes have
been protected, then walk the tree in the reverse order applying the
permissions, and at the end of the day you've still double-propagated
everything. The "don't propagate this permission right now" approach
is much simpler (all calls except for root calls request no
propagation, the order within a rooted tree doesn't matter) and it
only results in one propagation exercise.

An ideal interface would look something like this:

* Open "Security Transaction" object
* Make security changes to the file system referencing the Security
Transaction object.
* Call either "Commit" or "Rollback" on the Security Transaction
object, at which point all of the changes would either be made or not
made. Ideally such a change would be consistent with all of the
standard database ACID properties (Atomic, Consistent, Isolated,
Durable), although that's probably asking for a little too much of
file system designers:).

This would ensure optimal propagation efficiency while also ensuring
that the file system and its permissions remained coherent at all
times. Of course, if the later were a design criteria, the current
file move behavior would never have been tolerated (although it is a
necessary artifact of the design decisions made by the FS developers).

In any event, I've got SetFileSecurity working, so while it would be
handy to have such capabilities for other scenarios (i.e. Active
Directory), I've got the file system handled for now.

--Toby Ovod-Everett
Dave McPherson [MSFT]
2004-02-03 00:15:28 UTC
Permalink
The low-level access control API can set the protected flag w/o causing
propagation. This limits your exposure vs. using low-level api to modify the
ACL (which is sounds like you may be doing.) Though generally using
low-level access control APIs is not recommended.
--
HTH
Dave McPherson [MSFT]

This posting is provided "AS IS" with no warranties, and confers no rights.
Use of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm
Toby Everett
2004-02-03 07:04:01 UTC
Permalink
Post by Dave McPherson [MSFT]
The low-level access control API can set the protected flag w/o causing
propagation. This limits your exposure vs. using low-level api to modify the
ACL (which is sounds like you may be doing.) Though generally using
low-level access control APIs is not recommended.
The ACL and ACEs were pretty easy to parse, especially since all the
structs are published. The object-specific ACEs are a bit weird and I
haven't written the code to parse them yet, but it's still easy to
parse the ACL since the size of the ACE is part of the struct. I
think it's infinitely easier to parse them and manipulate them in Perl
than it is to struggle with the C APIs.

Far more difficult, and it required a lot of reverse-engineering, was
determining the exact algorithms used to propagate the permissions. I
didn't do this in order to set permissions (no need -
SetNamedObjectSecurity is infinitely faster than my code would ever
be), but because it's the only way to detect when moved files have
resulted in mis-inherited permissions. Moving files doesn't cause
inherited permissions to be recalculated, but since the INHERITED_ACE
bit is set, if you can calculate the permissions the node should have
inherited from the parent, you can determine if the permissions are
properly inherited. If they're misset, a quick call to
SetNamedObjectSecurity will correct them.

In the end, I have a blazingly fast library precisely because it's
easy to code things efficiently in Perl. For instance, I cache all
calls to LookupAccountName (it can be a very expensive call). File
systems generally don't have millions of DACLs or ACEs - they have
hundreds or thousands - and so I cache every ACL and ACE I see, as
well as all of the parsed and computed information. I used mutable
flyweight objects to get the illusion of mutable objects while
maintaining a central cache indexed on the binary forms for the ACEs
and ACLs.

The end result is that on a P4 1.8 GHz, if I'm working from a cached
FS (i.e. the second time I scan the entire drive), I can scan around
500 files per second for permissions and dump out all explicit
permissions, all mis-inherited permissions, etc. It's a really easy
environment to work in.

As in:

my $namedobject = Win32::Security::NamedObject::SE_FILE_OBJECT->new($filename);
my $dacl = $namedobject->dacl();
$dacl->deleteAces(sub {lc($_->trustee()) eq lc($trustee) &&
!$_->aceFlags->{INHERITED_ACE});
$namedobject->dacl($dacl);

That opened $filename as a Win32::Security::NamedObject of
SE_FILE_OBJECT, read the dacl, then deleted any ACEs from the DACL
that had a trustee that matched $trustee and that didn't have the
INHERITED_ACE bit set (using an anonymous subroutine as a filter to
select which ACEs to delete), then wrote the DACL back to the object.

There's also a infrastructure designed for writing recursors (well,
queue faked recursors:) to scan trees of nodes.

--Toby Ovod-Everett

Loading...