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.
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);
}