Reputation: 13577
I have the following code:
DirectoryInfo directory = new DirectoryInfo(@"C:\Program Files\Company\Product");
if (!directory.Exists) { directory.Create(); }
DirectorySecurity directorySecurity = directory.GetAccessControl();
SecurityIdentifier securityIdentifier = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
directorySecurity.AddAccessRule(
new FileSystemAccessRule(
securityIdentifier,
FileSystemRights.Write,
InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
PropagationFlags.None,
AccessControlType.Allow));
directory.SetAccessControl(directorySecurity);
The call to AddAccessRule throws an InvalidOperationException with the following stack trace:
System.InvalidOperationException: This access control list is not in canonical form and therefore cannot be modified.
at System.Security.AccessControl.CommonAcl.ThrowIfNotCanonical()
at System.Security.AccessControl.CommonAcl.AddQualifiedAce(SecurityIdentifier sid, AceQualifier qualifier, Int32 accessMask, AceFlags flags, ObjectAceFlags objectFlags, Guid objectType, Guid inheritedObjectType)
at System.Security.AccessControl.DiscretionaryAcl.AddAccess(AccessControlType accessType, SecurityIdentifier sid, Int32 accessMask, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags)
at System.Security.AccessControl.CommonObjectSecurity.ModifyAccess(AccessControlModification modification, AccessRule rule, Boolean& modified)
at System.Security.AccessControl.CommonObjectSecurity.AddAccessRule(AccessRule rule)
at System.Security.AccessControl.FileSystemSecurity.AddAccessRule(FileSystemAccessRule rule)
This only happens on some systems (I've seen Windows XP and Windows 7). In the situations where the error occurs, viewing the security permissions for the directory using Windows Explorer usually causes a message box to be shown with the following text:
The permissions on are incorrectly ordered, which may cause some entries to be ineffective. Press OK to continue and sort the permissions correctly, or Cancel to reset the permissions.
Clicking OK at this point fixes the problem. What's going on here? How does a system get into this state, and is there any way to detect/fix it programmatically (i.e. without having the user manually use Explorer to fix this)?
I did a bit more research about ACL, what canonical form is, and why it's necessary. I'm still not sure how a file would normally get into this state, but I found that the Icacls tool can be used to create a directory with a non-canonical ACL by saving the permission list, altering it to be out-of-order, and restoring it. Now I just need a way to fix it without requiring user interaction.
Upvotes: 27
Views: 16633
Reputation: 2803
There are extention methods for 'RawAcl' which seem to canonalize wrong ACEs.
But it's kind of mysterious. The methods are just present and I haven't found any documentation. Looking at the sourcode of .net 4.8 DirectoryObjectSecurity the author complains: A better way would be to have an internal method that would canonicalize the ACL and call it once
This is the signature of the methods:
{
//
// Summary:
// Canonicalizes the specified Access Control List.
//
// Parameter:
// acl:
// The Access Control List.
public static void Canonicalize(this RawAcl acl);
//
// Summary:
// Sort ACEs according to canonical form for this System.Security.AccessControl.ObjectSecurity.
//
// Parameter:
// objectSecurity:
// The object security whose DiscretionaryAcl will be made canonical.
public static void CanonicalizeAccessRules(this ObjectSecurity objectSecurity);
}
But As we know, there are ACEs which cannot be canonalized without lost of informations. These extention methods have no return value
and seem not to throw any exception
for this case. Therefore it may come to loss of informations using them. And the great answer from Kevin Kibler ma be the better way doing this.
Upvotes: 0
Reputation: 13577
I found the solution to this in an MSDN blog post: Say wwhhhaaaat? - The access control list is not canonical. Basically, you need to construct a new DACL with the same permissions, but in the correct canonical order:
static void Main(string[] args)
{
// directory with known ACL problem (created using Icacls)
DirectoryInfo directoryInfo = new DirectoryInfo("acltest");
var directorySecurity = directoryInfo.GetAccessControl(AccessControlSections.Access);
CanonicalizeDacl(directorySecurity);
directoryInfo.SetAccessControl(directorySecurity);
}
static void CanonicalizeDacl(NativeObjectSecurity objectSecurity)
{
if (objectSecurity == null) { throw new ArgumentNullException("objectSecurity"); }
if (objectSecurity.AreAccessRulesCanonical) { return; }
// A canonical ACL must have ACES sorted according to the following order:
// 1. Access-denied on the object
// 2. Access-denied on a child or property
// 3. Access-allowed on the object
// 4. Access-allowed on a child or property
// 5. All inherited ACEs
RawSecurityDescriptor descriptor = new RawSecurityDescriptor(objectSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Access));
List<CommonAce> implicitDenyDacl = new List<CommonAce>();
List<CommonAce> implicitDenyObjectDacl = new List<CommonAce>();
List<CommonAce> inheritedDacl = new List<CommonAce>();
List<CommonAce> implicitAllowDacl = new List<CommonAce>();
List<CommonAce> implicitAllowObjectDacl = new List<CommonAce>();
foreach (CommonAce ace in descriptor.DiscretionaryAcl)
{
if ((ace.AceFlags & AceFlags.Inherited) == AceFlags.Inherited) { inheritedDacl.Add(ace); }
else
{
switch (ace.AceType)
{
case AceType.AccessAllowed:
implicitAllowDacl.Add(ace);
break;
case AceType.AccessDenied:
implicitDenyDacl.Add(ace);
break;
case AceType.AccessAllowedObject:
implicitAllowObjectDacl.Add(ace);
break;
case AceType.AccessDeniedObject:
implicitDenyObjectDacl.Add(ace);
break;
}
}
}
Int32 aceIndex = 0;
RawAcl newDacl = new RawAcl(descriptor.DiscretionaryAcl.Revision, descriptor.DiscretionaryAcl.Count);
implicitDenyDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
implicitDenyObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
implicitAllowDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
implicitAllowObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
inheritedDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
if (aceIndex != descriptor.DiscretionaryAcl.Count)
{
System.Diagnostics.Debug.Fail("The DACL cannot be canonicalized since it would potentially result in a loss of information");
return;
}
descriptor.DiscretionaryAcl = newDacl;
objectSecurity.SetSecurityDescriptorSddlForm(descriptor.GetSddlForm(AccessControlSections.Access), AccessControlSections.Access);
}
Upvotes: 53