检查 IRP_MJ_CREATE 上的遍历特权

检查IRP_MJ_CREATE的主要问题之一是调用方是否具有遍历权限,即访问对象路径的权限。 也就是说,调用方可以访问文件对象(如 dirA/dirB/file),但无权访问该文件对象路径 (dirA 和 dirA/dirB) 的目录内容。

默认情况下,Windows 向所有用户授予遍历权限。 “用户权限”常量是 SeChangeNotifyPrivilege,它映射到 ACCESS_MASK 中的 FILE_TRAVERSE。 作为一项安全功能,系统管理员可以删除用户的遍历权限。

由于大多数调用方确实具有遍历特权,因此文件系统通常执行的第一项检查是在 IRP 安全上下文的 AccessState-Flags> 字段中检查此特权:

    BOOLEAN traverseCheck = 
        !(IrpContext->IrpSp->Parameters.Create.SecurityContext->AccessState->Flags
            & TOKEN_HAS_TRAVERSE_PRIVILEGE);

文件系统使用 Flags 跟踪在操作过程中授予了哪些访问权限。 然后,文件系统可以先快速检查访问状态位,如果已授予访问检查调用 (遍历 check = 0) ,则避免访问检查调用的开销。

如果以前未授予遍历特权,则文件系统必须在所打开文件的路径上对每个目录执行遍历检查。 在下面的部分代码片段中,遍历检查是使用泛型例程完成的,该例程通常用于大多数安全检查:


{
// accessParams is passed to the file system and is normally based
// on the fields of the same name from the IRP.

// Only one thread can be looking at this data structure in memory
// at a time (and potentially changing it), so acquire a lock on it.

    SeLockSubjectContext(
        &accessParams.AccessState->SubjectSecurityContext);

// Check whether the desired access can be granted.
// For this example, assume desiredAccess = FILE_TRAVERSE

    granted = SeAccessCheck( Fcb->SecurityDescriptor,
        &AccessParams.AccessState->SubjectSecurityContext,
        TRUE,
        AccessParams.desiredAccess,
        0,
        &Privileges,
        IoGetFileObjectGenericMapping(),
        AccessParams.AccessMode,
        &AccessParams.GrantedAccess,
        &AccessParams.status );

    // The file system uses AccessState to cache access privileges
    // that have been granted thus far along the operation's code
    // path. Update AccessState with the newly acquired Privileges.
    
    if (Privileges != NULL) {

        (void) SeAppendPrivileges(AccessParams.AccessState, Privileges );
        SeFreePrivileges( Privileges );
        Privileges = NULL;
    }

    if (granted) {
        //
        // The desired access was granted, so clear the
        // granted bits from desiredAccess. 
        //
        AccessParams.desiredAccess &= 
            ~(AccessParams.GrantedAccess | MAXIMUM_ALLOWED);
 
        if (!checkOnly) {
        //
        // The caller wants to modify the access state for this 
        // request
        //
            AccessParams.AccessState->PreviouslyGrantedAccess |= 
                AccessParams.GrantedAccess;
        }

        if (maxDesired) {

            maxDelete = 
                (BOOLEAN)(AccessParams.AccessState->PreviouslyGrantedAccess & 
                    DELETE);
            maxReadAttr = 
                (BOOLEAN)(AccessParams.AccessState->PreviouslyGrantedAccess & 
                    FILE_READ_ATTRIBUTES);
        }
        AccessParams.AccessState->RemainingDesiredAccess &= 
            ~(AccessParams.GrantedAccess | MAXIMUM_ALLOWED);
    }

    // Release the lock on the security context
    SeUnlockSubjectContext(&accessParams.AccessState->SubjectSecurityContext);  
}

此函数执行泛型安全检查。 此函数在执行此操作时必须处理以下问题:

  • 它必须指定要用于检查的正确安全描述符。

  • 它必须传递安全上下文, (这些是) 执行操作的实体的凭据。

  • 它必须基于安全检查的结果更新访问状态。

  • 它必须考虑MAXIMUM_ALLOWED选项, (请参阅 ntifs.h) 。 MAXIMUM_ALLOWED选项指定文件系统应将访问权限设置为文件系统 (读/写/删除允许的最大可能访问,例如) 。 很少有应用程序使用 MAXIMUM_ALLOWED 选项,因为 FASTFAT 文件系统不支持此选项。 由于 MAXIMUM_ALLOWED 选项位不是 FASTFAT 文件系统识别的访问位之一,因此它会拒绝对给定文件的访问请求。 如果应用程序尝试打开 FASTFAT 卷上设置了MAXIMUM_ALLOWED选项的文件,则会发现请求失败。 有关详细信息,请参阅 WDK 包含的 FASTFAT 示例代码的 Acchksup.c 源文件中的 FatCheckFileAccess 函数。

请注意,对于简单遍历检查,请求的访问权限将FILE_TRAVERSE,安全描述符为调用方尝试遍历的目录,而不是从原始IRP_MJ_CREATE IRP 请求的访问权限。