【问题标题】:How do you programmatically fix a non-canonical ACL?您如何以编程方式修复非规范 ACL?
【发布时间】:2011-11-14 19:16:23
【问题描述】:

我有以下代码:

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

AddAccessRule 的调用会引发 InvalidOperationException,并带有以下堆栈跟踪:

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)

这只发生在某些系统上(我见过 Windows XP 和 Windows 7)。在发生错误的情况下,使用 Windows 资源管理器查看目录的安全权限通常会导致显示一个消息框,其中包含以下文本:

权限排序不正确,可能 导致某些条目无效。按 OK 继续并排序 正确设置权限,或取消以重置权限。

此时单击“确定”即可解决问题。这里发生了什么?系统是如何进入这种状态的,有没有办法以编程方式检测/修复它(即无需用户手动使用资源管理器来修复它)?

更新

我对 ACL、什么是规范形式以及 why it's necessary 做了更多研究。我仍然不确定文件通常如何进入此状态,但我发现Icacls 工具可用于通过保存权限列表来创建具有非规范 ACL 的目录,将其更改为 out-秩序,并恢复它。现在我只需要一种不需要用户交互的方法来修复它。

【问题讨论】:

  • 当您开始寻找 Raymond Chen 的参考资料时,您就知道自己遇到了麻烦。

标签: c# exception acl


【解决方案1】:

我在 MSDN 博客文章中找到了解决方案:Say wwhhhaaaat? - The access control list is not canonical。基本上,您需要以正确的规范顺序构建具有相同权限的新 DACL:

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

【讨论】:

  • 修复了我也拥有的一些注册表权限代码。它在 Windows 8 之前运行良好,但 Windows 8 开始抛出此异常。只是想让每个人都知道它也适用于RegistrySecurity,因为它继承了NativeObjectSecurity
  • 如果没有这个答案和 2009 年的那篇博客文章,这将是……xkcd.com/979 感谢您发布这个答案,否则我仍然会很茫然。
【解决方案2】:

RawAcl”有扩展方法,它们似乎规范化了错误的 ACE。

但这有点神秘。这些方法只是存在,我还没有找到任何文档。看.net 4.8 DirectoryObjectSecurity的源代码作者吐槽:A better way would be to have an internal method that would canonicalize the ACL and call it once

这是方法的签名:

{
    //
    // 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);
}

但是我们知道,有些 ACE 在不丢失信息的情况下是无法规范化的。这些扩展方法没有return value,并且似乎不会在这种情况下抛出任何exception。因此,使用它们可能会丢失信息。 great answer from Kevin Kibler 可能是更好的方法。

【讨论】:

    猜你喜欢
    • 2010-09-12
    • 2019-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多