How do you programmatically fix a non-canonical ACL?
Asked Answered
A

2

26

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)?

Update

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.

Arak answered 14/11, 2011 at 19:16 Comment(1)
You know you're in trouble when you start finding references to Raymond Chen.Sharpfreeze
A
51

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);
}
Arak answered 15/11, 2011 at 19:50 Comment(2)
Fixed some registry permissions code I had as well. It worked fine before Windows 8, but Windows 8 started throwing this exception. Just wanted everyone to know it works for RegistrySecurity as well, since it inherits NativeObjectSecurity.Cornhusk
Without this answer and that blog post from 2009, this would be one of those moments where ... xkcd.com/979 Thank you for posting this answer or else I would be very much in the dark still.Neolamarckism
M
0

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.

Macswan answered 14/11, 2020 at 11:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.