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 (4)

  1. ryan.connelly says:

    I always have trouble with this. Thanks for the clear explanation!

  2. mike_____ says:

    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;

           }

  3. John.Douglas says:

    _Mike – many thanks for making you AD version available – this really saved the day!