检查 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 请求的访问权限。