windows驱动文件流上下文区别和选用二
概述
在第一篇文章(如何创建上下文一)中,以流上下文(FLT_STREAM_CONTEXT)这种类型举例说明了创建流上下文的步骤和需要使用的API函数,但是在windows上下文类型中,与流上下文相似的还有流句柄上下文(FLT_STREAMHANDLE_CONTEXT)、文件上下文(FLT_FILE_CONTEXT),其他上下文和这三种类型的上下文有比较明确的区别,所以本文将通过在一个示例中同时创建以上三种上下文类型,并通过不同进程来操作同一个文件时的效果来理解一下上下文的使用过程和生命周期。
下文提到的上下文,没有特指某种上下文的情况下都是指这三种类型的上下文,暂不关注其他类型上下文的使用,其他上下文的使用会在其他篇幅中做说明。
验证思路
相信和会有和我一样的同学会在初次接触上下文的时候,不知道在实际场景中需要使用哪种上下文,也许参阅了其他人的代码,发现开源的代码中大部分应用场景都使用的流上下文类型;甚至,可能也不太清楚,自己是否需要使用上下文,以及使用上下文能够给自己的程序带来哪些好处。不管最终想要通过上下文实现的功能如何,比如不管你是否想要通过判断上下文知道文件是否是特定类型的还是加密的,在用到上下文的时候多是要解决不想反复判断,想为某个或某类特定文件的文件信息获取一次之后在特定应用场景下就不需要再次获取,从而提升驱动程序效率的办法。
鉴于此,上下文主要解决了在程序生命周期中部分关键信息不想多次获取或需要在多个操作中传递信息的问题,前者可以通过多次获取关键信息而不用上下文就可以解决问题,后者有时候不得不使用上下文来传递信息,前者使用上下文的情况,主要应对关键信息获取比较繁琐,使用上下文来减少在不同操作中相同逻辑处理的次数。
了解了上下文的一些基础场景后,接下来就是看选用哪种类型的上下文了,最终目标就是创建了上下文能不能在下次操作文件的时候操作上下文,获取上下文的信息,上下文的信息何时销毁。本文的验证逻辑比较简单,在IRP_MJ_CREATE中先获取上下文如果不存在则创建上下文,如果存在则查看上下文中的信息,并将信息打印出来,在上下文清理函数中同样也打印相关信息,观察上下文清理时机。
源码分析
获取或创建上下文接口
为避免文章代码过多,这里示例为流上下文获取或创建接口;流句柄上下文或文件上下文在示例中使用相同的上下文结构PFILE_CONTEXT_STRUC,针对这个函数另外两种上下文只需要修改获取上下文函数接口和申请上下文函数的接口类型以及对应的设置上下文函数即可。
PFILE_CONTEXT_STRUC 结构如下定义:
// 文件流上下文结构体
typedef struct _PFILE_CONTEXT_STRUCT
{
ULONG ulReferenceTimes; // 引用计数
UNICODE_STRING usName; // 文件名称
} FILE_CONTEXT_STRUCT, *PFILE_CONTEXT_STRUCT;
NTSTATUS
FctCreateFileStreamContext(
__in PFLT_INSTANCE pfiInstance,
__in PFILE_OBJECT pfoFileObject,
__in PFLT_CALLBACK_DATA pfcdCBD,
__in PFLT_FILE_NAME_INFORMATION pfniFileNameInformation,
__inout PFILE_CONTEXT_STRUCT * dpscFileStreamContext
)
{
UNREFERENCED_PARAMETER(pfcdCBD);
UNREFERENCED_PARAMETER(pfniFileNameInformation);
// 返回值
NTSTATUS status;
// 申请到的流上下文
PFILE_CONTEXT_STRUCT pscFileStreamContext = NULL;
// 保存旧的上下文
PFILE_CONTEXT_STRUCT pscOldStreamContext = NULL;
// 先把返回值置NULL
*dpscFileStreamContext = NULL;
// 必须 IRQL <= APC Level
PAGED_CODE();
// 先尝试获取流上下文
status = FltGetStreamContext(
pfiInstance,
pfoFileObject,
(PFLT_CONTEXT *)pscFileStreamContext );
if (!NT_SUCCESS(status)) {
return status;
}
if (NT_SUCCESS(status) && (status != STATUS_NOT_FOUND)) { // 已经有了, 此处增加了引用计数.
pscFileStreamContext->ulReferenceTimes++;
*dpscFileStreamContext = pscFileStreamContext;
return STATUS_FLT_CONTEXT_ALREADY_DEFINED;
}
// 申请上下文
status = FltAllocateContext(gFilterHandle,
FLT_STREAM_CONTEXT,
FILE_CONTEXT_SIZE,
NonPagedPool,
(PFLT_CONTEXT *)&pscFileStreamContext);
if (!NT_SUCCESS(status)) {
return status;
}
// 初始化上下文
RtlZeroMemory(pscFileStreamContext, FILE_CONTEXT_SIZE);
// 设置上下文
status = FltSetStreamContext(
pfiInstance,
pfoFileObject,
FLT_SET_CONTEXT_KEEP_IF_EXISTS,
pscFileStreamContext,
(PFLT_CONTEXT *)&pscOldStreamContext);
if (status != STATUS_FLT_CONTEXT_ALREADY_DEFINED
&& status != STATUS_FLT_CONTEXT_ALREADY_LINKED) {
if (!NT_SUCCESS(status)) {
FltReleaseContext(pscFileStreamContext);
return status;
}
}
if (pscOldStreamContext != NULL) {
FltReleaseContext(pscOldStreamContext);
}
// 保存返回值
pscFileStreamContext->ulReferenceTimes = 1;
*dpscFileStreamContext = pscFileStreamContext;
return STATUS_SUCCESS;
}
在PostCreate中调用接口测试上下文
在PostCreate中分别获取或创建三种上下文类型,这里简单粗暴直接三个都来执行一遍
FLT_POSTOP_CALLBACK_STATUS
MyPostCreate(
__inout PFLT_CALLBACK_DATA pfcdCBD,
__in PCFLT_RELATED_OBJECTS pFltObjects,
__in_opt PVOID lpCompletionContext,
__in FLT_POST_OPERATION_FLAGS Flags
)
{
PAGED_CODE();
UNREFERENCED_PARAMETER(Flags);
UNREFERENCED_PARAMETER(lpCompletionContext);
PFILE_OBJECT pfoFileObject = pFltObjects->FileObject;
PFLT_INSTANCE pfiInstance = pFltObjects->Instance;
// 获取到的文件对象
PFILE_CONTEXT_STRUCT pscFileStreamContext = NULL;
PFILE_CONTEXT_STRUCT pscFileStreamHandleContext = NULL;
PFILE_CONTEXT_STRUCT pscFileContext = NULL;
// 返回值
NTSTATUS status;
// 文件名称信息
PFLT_FILE_NAME_INFORMATION pfniFileNameInfo = 0;
do {
if (!NT_SUCCESS(pfcdCBD->IoStatus.Status)) {
// 如果打开失败了, 那就不管了
MYDBG("File failed to open, pass");
break;
}
// 获取文件名称信息
status = FltGetFileNameInformation(pfcdCBD,
FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,
&pfniFileNameInfo
);
if (!NT_SUCCESS(status)) { // 没拿到文件信息, 也不用管了
MYDBG("Cannot get file information, pass");
pfniFileNameInfo = NULL;
break;
}
// 如果文件名长度为0也不用管了
if (!pfniFileNameInfo->Name.Length)
{
MYDBG("Zero name length, pass");
break;
}
// 过滤一下我不关注的文件
if (InExcludeFile(&pfniFileNameInfo->Name))
{
break;
}
do { // 创建或检查文件流上下文, 如果已经存在, 引用计数将自动加 1.
status = FctCreateFileStreamContext(
pfiInstance,
pfoFileObject,
pfcdCBD,
pfniFileNameInfo,
&pscFileStreamContext);
if (status != STATUS_FLT_CONTEXT_ALREADY_DEFINED) {
MYDBG("StreamContext: 0x%x Post create file %ws, No Stream context info", pfcdCBD->Iopb->Parameters.Create.Options, pfniFileNameInfo->Name.Buffer);
if (status == STATUS_NOT_SUPPORTED) {
MYDBG("StreamContext: Error: File does not supported. %ws.", pfniFileNameInfo->Name.Buffer);
pscFileStreamContext = NULL;
break;
}
// 如果没有上下文, 那么新初始化上下文.初始化上下文的函数下文提供
status = FctInitializeContext(
pscFileStreamContext,
pfcdCBD,
pfniFileNameInfo);
if (!NT_SUCCESS(status)) {
MYDBG("StreamContext: Error: Cannot initialize fct context. %ws.", pfniFileNameInfo->Name.Buffer);
break;
}
MYDBG("StreamContext: Post create file %ws, create context info success", pfniFileNameInfo->Name.Buffer);
break;
}
}
else // 已有上下文的情况,直接检查上下文就可以了
{
MYDBG("StreamContext: Post create file %ws, has context info", pfniFileNameInfo->Name.Buffer);
}
} while (0);
do { // 创建或检查流句柄上下文, 如果已经存在, 引用计数将自动加 1.
status = FctCreateFileStreamHandleContext(
pfiInstance,
pfoFileObject,
pfcdCBD,
pfniFileNameInfo,
&pscFileStreamHandleContext);
if (status != STATUS_FLT_CONTEXT_ALREADY_DEFINED) {
MYDBG("StreamHanleContext: 0x%x Post create file %ws, No context info", pfcdCBD->Iopb->Parameters.Create.Options, pfniFileNameInfo->Name.Buffer);
if (status == STATUS_NOT_SUPPORTED) {
MYDBG("StreamHanleContext: Error: File does not supported. %ws.", pfniFileNameInfo->Name.Buffer);
pscFileStreamHandleContext = NULL;
break;
}
MYDBG("StreamHanleContext: Post create file %ws, create context info success", pfniFileNameInfo->Name.Buffer);
// 如果没有上下文, 那么新初始化上下文.
status = FctInitializeCustFileStreamHandleContext(
pscFileStreamHandleContext,
pfcdCBD,
pfniFileNameInfo);
if (!NT_SUCCESS(status)) {
MYDBG("StreamHanleContext: Error: Cannot initialize fct context. %ws.", pfniFileNameInfo->Name.Buffer);
break;
}
}
else // 已有上下文的情况,直接检查上下文就可以了
{
MYDBG("StreamHanleContext: Post create file %ws, has context info", pfniFileNameInfo->Name.Buffer);
}
} while (0);
do { // 创建或检查文件上下文, 如果已经存在, 引用计数将自动加 1.
status = FctCreateFileContext(
pfiInstance,
pfoFileObject,
pfcdCBD,
pfniFileNameInfo,
&pscFileContext);
if (status != STATUS_FLT_CONTEXT_ALREADY_DEFINED) {
MYDBG("FileContext: 0x%x Post create file %ws, No context info", pfcdCBD->Iopb->Parameters.Create.Options, pfniFileNameInfo->Name.Buffer);
if (status == STATUS_INSUFFICIENT_RESOURCES) {
MYDBG("FileContext: Error: pscFileStreamContext->prResource %ws, STATUS_INSUFFICIENT_RESOURCES.", pfniFileNameInfo->Name.Buffer);
pscFileContext = NULL;
break;
}
if (status == STATUS_NOT_SUPPORTED) {
MYDBG("FileContext: Error: File does not supported. %ws.", pfniFileNameInfo->Name.Buffer);
pscFileContext = NULL;
break;
}
MYDBG("FileContext: Post create file %ws, create context info success", pfniFileNameInfo->Name.Buffer);
// 如果没有上下文, 那么新初始化上下文.因为上下文结构体共用一套,初始化和上下文类型没关系,可以通用
status = FctInitializeContext(
pscFileContext,
pfcdCBD,
pfniFileNameInfo);
if (!NT_SUCCESS(status)) {
MYDBG("FileContext: Error: Cannot initialize fct context. %ws.", pfniFileNameInfo->Name.Buffer);
break;
}
}
else // 已有上下文的情况,直接检查上下文就可以了
{
MYDBG("FileContext: Post create file %ws, has context info", pfniFileNameInfo->Name.Buffer);
}
} while (0);
} while (0);
// 善后工作
if (NULL != pscFileStreamContext) {
FctReleaseCustFileStreamContext(pscFileStreamContext);
}
if (NULL != pscFileStreamHandleContext) {
FctReleaseCustFileStreamContext(pscFileStreamHandleContext);
}
if (NULL != pscFileContext) {
FctReleaseCustFileStreamContext(pscFileContext);
}
if (pfniFileNameInfo) {
FltReleaseFileNameInformation(pfniFileNameInfo);
}
return FLT_POSTOP_FINISHED_PROCESSING;
}
在没有上下文的情况下,新创建的上下文需要进行初始化, 初始化需要将文件名称写进去,下面简单示例该函数的实现
NTSTATUS
FctInitializeContext(
__inout PFILE_CONTEXT_STRUCT pscFileStreamContext,
__in PFLT_CALLBACK_DATA pfcdCBD,
__in PFLT_FILE_NAME_INFORMATION pfniFileNameInformation
)
{
// 返回值
NTSTATUS status;
// 更新文件流上下文中的文件名称
// 如果已经有名字了, 先释放掉.
if (pscFileStreamContext->usName.Buffer != NULL) {
ExFreePoolWithTag(pscFileStreamContext->usName.Buffer, MEM_TAG_CONTEXT);
pscFileStreamContext->usName.Length = 0;
pscFileStreamContext->usName.MaximumLength = 0;
pscFileStreamContext->usName.Buffer = NULL;
}
// 申请并拷贝新名称
pscFileStreamContext->usName.MaximumLength = pusName->Length;
pscFileStreamContext->usName.Buffer = (PWCH)ExAllocatePoolWithTag(
NonPagedPool,
pscFileStreamContext->usName.MaximumLength,
MEM_TAG_CONTEXT);
if (pscFileStreamContext->usName.Buffer == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyUnicodeString(&pscFileStreamContext->usName, pusName);
pscFileStreamContext->ulReferenceTimes = 1; // 引用计数设置为1
return STATUS_SUCCESS;
}
修改销毁上下文函数的调用逻辑
在销毁函数中,对上下文结构中申请的内存进行释放,并打印信息,标明是那个释放函数被调用了
VOID
CleanupContext(
__in PFLT_CONTEXT pcContext,
__in FLT_CONTEXT_TYPE pctContextType
)
{
PFILE_CONTEXT_STRUCT pscFileStreamContext = NULL;
PAGED_CODE();
switch (pctContextType) {
case FLT_STREAM_CONTEXT:
{
pscFileStreamContext = (PFILE_CONTEXT_STRUCT)pcContext;
MYDBG("FLT_STREAM_CONTEXT CleanupContext IN %ws", pscFileStreamContext->usName.Buffer);
// 释放上下文,这里需要自己实现一下将申请到的内存释放掉即可
FctFreeMyContext(pscFileStreamContext);
}
break;
case FLT_STREAMHANDLE_CONTEXT:
{
pscFileStreamContext = (PFILE_CONTEXT_STRUCT)pcContext;
MYDBG("FLT_STREAMHANDLE_CONTEXT CleanupContext IN %ws", pscFileStreamContext->usName.Buffer);
// 释放上下文,这里需要自己实现一下将申请到的内存释放掉即可
FctFreeMyContext(pscFileStreamContext);
}
break;
case FLT_FILE_CONTEXT:
{
pscFileStreamContext = (PFILE_CONTEXT_STRUCT)pcContext;
MYDBG("FLT_FILE_CONTEXT CleanupContext IN %ws", pscFileStreamContext->usName.Buffer);
// 释放上下文,这里需要自己实现一下将申请到的内存释放掉即可
FctFreeMyContext(pscFileStreamContext);
}
break;
}
}
实验结果
步骤一:使用记事本打开文件1212.txt
PostCreate.448:StreamContext: 0x1000060 Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, No Stream context info
PostCreate.469:StreamContext: Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, create context info success
PostCreate.490:StreamHanleContext: 0x1000060 Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, No context info
PostCreate.502:StreamHanleContext: Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, create context info success
PostCreate.532:FileContext: 0x1000060 Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, No context info
PostCreate.544:FileContext: Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, create context info success
CleanupContext.266:FLT_STREAMHANDLE_CONTEXT CleanupContext IN \Device\HarddiskVolume4\测试\驱动\1212.txt
可以看到此时都没有上下文信息,且都成功的创建了上下文信息,并且流句柄套接字(FLT_STREAMHANDLE_CONTEXT) 立马也被清理了,下面是用procmon看到的操作情况,看以看到用记事本将文件打开之后,获取到文件内容后,很快执行了CloseFile调用,这里也是流句柄上下文很快被释放的原因。
步骤二:关闭文件再次使用记事本打开1212.txt
PostCreate.473:StreamContext: Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, has context info
PostCreate.490:StreamHanleContext: 0x1000060 Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, No context info
PostCreate.502:StreamHanleContext: Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, create context info success
PostCreate.557:FileContext: Post create file \Device\HarddiskVolume4\测试\驱动\1212.txt, has context info
CleanupContext.266:FLT_STREAMHANDLE_CONTEXT CleanupContext IN \Device\HarddiskVolume4\测试\驱动\1212.txt
根据日志结果可以发现,StreamContext FileContext两种上下文类型已创建了上下文,可以获取到上下文信息,只有StreamHanleContext没有上下文信息,且新建上下文成功
步骤三:卸载驱动
CleanupContext.258:FLT_STREAM_CONTEXT CleanupContext IN \Device\HarddiskVolume4\测试\驱动\1212.txt
CleanupContext.274:FLT_FILE_CONTEXT CleanupContext IN \Device\HarddiskVolume4\测试\驱动\1212.txt
可以看出在卸载驱动时,这两中类型的文件上下文也被清理了;删除文件也可以得到上面的两个日志
根据以上测试结果,可以发现在简单场景下,FLT_STREAM_CONTEXT FLT_FILE_CONTEXT 都可以跟随驱动或者文件的生命周期内存在,StreamHanleContext只有在文件的打开过程中有效,msdn上有信息表明FLT_FILE_CONTEXT在vista之后支持。如果要记录文件的一些特殊信息可以在FLT_STREAM_CONTEXT 上进行,当然也需要在关注的文件信息发生变动时更新上下文信息。