The access control list is not canonical

 

Developing a program that does not require administrative privileges requires quite a bit of work up-front during the install. You need to make sure you do things like write registry keys to HKLM, create file system entries, and most importantly ensure that your ACLs (Access Control List) are correct to ensure smooth operation of your program while running under that non-admin account. However, sometimes this last step fails with a rather cryptic error, mentioning something along the lines of "The Access Control List is not canonical". After searching the web for solutions to this error, I couldn't find a definitive answer. One thing I did notice is if you open the 'security' dialog of the object that exhibits this issue, the Windows UI will actually recognize that there is an issue and ask if you would like to correct it. So obviously there is a way to fix this problem, but for my scenario requiring user intervention was just not good enough .. so I returned to searching. I finally came across an algorithm, buried in the depths of MSDN, which described how to fix the ordering of a DACL (Discretionary Access Control List). There was only one problem: it was written in VB. So, after a lot of hard work converting the code (just kidding, it was quite simple to convert), I produced the following helper method to attempt fixing the order of a DACL.

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

I hope this code saves someone out there a lot of trouble and time. Until next time …

Comments

  • Anonymous
    February 09, 2009
    PingBack from http://www.clickandsolve.com/?p=5306

  • Anonymous
    February 09, 2009
    I always have trouble with this. Thanks for the clear explanation!

  • Anonymous
    July 06, 2009
    Thanks for your efforts, it saved me quite some time. I was wrestling with this on Active Directory objects. It seems like the raw acls are not applicable to AD security so I needed to rewrite the method for that purpose...        internal static void CanonicalizeObjectAces(DirectoryEntry oDe)        {            if (oDe == null)            {                throw new ArgumentNullException("oDe");            }            var objectSecurity = oDe.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            //var descriptor = objectSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Access);            var sd = (SecurityDescriptor)oDe.Properties["ntSecurityDescriptor"].Value;            var dacl = (AccessControlList)sd.DiscretionaryAcl;            List<AccessControlEntry> implicitDenyDacl = new List<AccessControlEntry>();            List<AccessControlEntry> implicitDenyObjectDacl = new List<AccessControlEntry>();            List<AccessControlEntry> inheritedDacl = new List<AccessControlEntry>();            List<AccessControlEntry> implicitAllowDacl = new List<AccessControlEntry>();            List<AccessControlEntry> implicitAllowObjectDacl = new List<AccessControlEntry>();            foreach (AccessControlEntry ace in dacl)            {                if ((ace.AceFlags & (int)AceFlags.Inherited) == (int)AceFlags.Inherited)                {                    inheritedDacl.Add(ace);                }                else                {                    switch (ace.AceType)                    {                        case (int)AceType.AccessAllowed:                            implicitAllowDacl.Add(ace);                            break;                        case (int)AceType.AccessDenied:                            implicitDenyDacl.Add(ace);                            break;                        case (int)AceType.AccessAllowedObject:                            implicitAllowObjectDacl.Add(ace);                            break;                        case (int)AceType.AccessDeniedObject:                            implicitDenyObjectDacl.Add(ace);                            break;                    }                }            }            //Int32 aceIndex = 0;            var newDacl = new AccessControlList();            newDacl.AclRevision = dacl.AclRevision;            implicitDenyDacl.ForEach(x => newDacl.AddAce(x));            implicitDenyObjectDacl.ForEach(x => newDacl.AddAce(x));            implicitAllowDacl.ForEach(x => newDacl.AddAce(x));            implicitAllowObjectDacl.ForEach(x => newDacl.AddAce(x));            inheritedDacl.ForEach(x => newDacl.AddAce(x));            sd.DiscretionaryAcl = newDacl;            oDe.Properties["ntSecurityDescriptor"].Value = sd;            if (dacl.AceCount==newDacl.AceCount)                oDe.CommitChanges();            else            {                Debug.Fail("The DACL cannot be canonicalized since it would potentially result in a loss of information");                return;            }            return;        }

  • Anonymous
    June 30, 2013
    _Mike - many thanks for making you AD version available - this really saved the day!