使用C ++对目录上的ACL进行递归扫描

时间:2013-02-27 19:37:43

标签: c++ winapi permissions acl

我对某事感到好奇。我正在用C ++编写一段代码,需要扫描特定文件夹子文件夹的ACL权限,并找到与第一个文件夹的ACL权限不同的代码。为此,我使用FindFirstFileFindNextFile递归检查文件夹中的所有文件夹,然后通过在找到的每个子文件夹上调用GetNamedSecurityInfo来检查ACL权限。

此方法有效,但工作速度非常慢,尤其是在扫描网络共享时。我知道名为accesschk的工具可以做同样的事情,但是当我在同一个文件夹(使用-dsqvli开关)上递归运行时,它返回的结果比我上面描述的程序更快。< / p>

所以我想知道如何加快此ACL权限查找过程?

我的第一个想法是在ACE上使用继承,我只是不确定如何实现它......

编辑2:感谢@arx的建议。这个ACL / ACE的内容很难记录。他在下面发布的代码为我工作。请注意,我检查ACL继承的原始代码没有产生可靠的结果,因为他的帖子中列出了@arx的原因。

1 个答案:

答案 0 :(得分:4)

评论中的一些进一步信息:

应用程序确定正在检查其访问权限的用户的SID。

在每个目录上调用GetNamedSecurityInfo后,应用程序使用用户的SID调用GetEffectiveRightsFromAcl。大部分时间都是后者的召唤。

GetEffectiveRightsFromAcl根据用户的SID和用户所属的任何组的SID检查ACL。它可能很慢,因为确定用户的组需要往返于域控制器。

有两种可能的修复和死胡同:

模拟GetEffectiveRightsFromAcl

在循环外部,确定用户的SID和用户组的SID。 (TODO:检查是否自动处理嵌套组,或者是否必须递归解析它们。)

确定ACL的有效权利:

  1. 使用ACCESS_MASK(实际上是DWORD)来表示权限掩码。将其初始化为零。
  2. 以相反的顺序处理ACL中的ACE。这可确保早期的ACE优先。
  3. 如果ACE引用任何先前确定的SID,那么对于允许访问的ACE或ACE的掩码与您的权限掩码以及访问被拒绝的ACE将屏蔽ACE的掩码与您的权限掩码。
  4. 一旦您处理了所有ACE,您的版权掩码就会得到答案。

    跳过继承的ACL

    在许多目录层次结构中,大多数或所有文件和目录都将从其父级继承其权限。但是,这没有用。父级的继承ACL可能不活动,因此子级的有效权限与父级的有效权限不匹配。因此,即使继承了ACL,仍然必须进行检查。

    缓存GetEffectiveRightsFromAcl的结果

    只需创建从ACL到有效权限掩码的映射。为此,您需要一种比较ACL的方法。您不能仅使用memcmp比较整个ACL,因为ACL.AclSize包含额外填充的大小。相反,比较ACE的数量,如果它们相同,则使用memcmp比较各个ACE。

    我在Program Files目录上尝试了这个。扫描整个目录结构需要6次调用GetEffectiveRightsFromAcl。其余的2,708个目录是从缓存中解析出来的,所以速度要快得多。

    以下实现了GetEffectiveRightsFromAcl的缓存版本。请注意,缺少错误处理,它永远不会释放它放在地图中的PACL。

    // Compare two access-control lists.
    // Return <0 if acl1<acl2, 0 if acl1==acl2 and >0 if acl1>acl2.
    // The ordering is arbitrary but consistent.
    int aclcmp(PACL acl1, PACL acl2)
    {
        // First compare by number of ACEs
        int c = acl1->AceCount - acl2->AceCount;
        if (c)
            return c;
    
        // We have the same number of ACEs, so compare each ACE
        int aceCount = acl1->AceCount;
        for (int aceIndex = 0; aceIndex != aceCount; ++aceIndex)
        {
            // Get the ACEs
            PACE_HEADER ace1;
            PACE_HEADER ace2;
            GetAce(acl1, aceIndex, (LPVOID*)&ace1);
            GetAce(acl2, aceIndex, (LPVOID*)&ace2);
            // Compare the ACE sizes
            c = ace1->AceSize - ace2->AceSize;
            if (c)
                return c;
    
            // Compare the ACE content
            c = memcmp(ace1, ace2, ace1->AceSize);
            if (c)
                return c;
        }
    
        return 0;
    }
    
    // Less-than operator for pointers to ACLs
    class ComparePAcl
    {
    public:
        bool operator()(const PACL& acl1, const PACL& acl2) const
        {
            return aclcmp(acl1, acl2) < 0;
        }
    };
    
    // Map from pointers-to-ACLs to access masks
    typedef std::map<PACL, ACCESS_MASK, ComparePAcl> AclToAccessMask;
    AclToAccessMask aclToAccessMask;
    
    // Just to check how the cache performs
    DWORD foundCount = 0;
    DWORD notFoundCount = 0;
    
    // Same as GetEffectiveRightsFromAcl but caches results.
    // Note that this must be called with the same trustee to get meaningful results.
    DWORD CachedGetEffectiveRightsFromAcl(PACL pacl, PTRUSTEE pTrustee, PACCESS_MASK pAccessRights)
    {
        AclToAccessMask::const_iterator it = aclToAccessMask.find(pacl);
        if (it != aclToAccessMask.end())
        {
            // The ACL is in the cache
            ++foundCount;
            *pAccessRights = it->second;
        }
        else
        {
            // The ACL is not in the cache
            ++notFoundCount;
            DWORD rc = GetEffectiveRightsFromAcl(pacl, pTrustee, pAccessRights);
            if (rc != ERROR_SUCCESS)
                return rc;
            // TODO: Clean up copies of ACLs afterwards
            PACL aclcopy = (PACL)malloc(pacl->AclSize);
            memcpy(aclcopy, pacl, pacl->AclSize);
            aclToAccessMask.insert(AclToAccessMask::value_type(aclcopy, *pAccessRights));
        }
    
        return ERROR_SUCCESS;
    }