XiangXiangRen
总版主
总版主
  • 注册日期2003-02-22
  • 最后登录2015-09-01
  • 粉丝13
  • 关注0
  • 积分1042分
  • 威望472点
  • 贡献值1点
  • 好评度145点
  • 原创分13分
  • 专家分1分
阅读:1790回复:1

微过滤器驱动开发指南 之四

楼主#
更多 发布于:2005-06-20 22:55
10.与用户态的通信

10.1 过滤器通信端口对象
为了实现安全的和支持多种不同类型通信方法,一个新的对象被引入:微过滤器通信端口(以下简称通信端口或者端口)。专门设计用来给核心态-用户态通信或者反过来。核心-核心通信现在不在支持。一个端口是一个有名字的NT对象。而且有一个安全的描述符号。

过滤管理器生成了一个新的对象类型,FilterConnectionPort来实现这个。过滤管理器在它的DriverEntry中生成这个新的对象类型,赶在了任何微过滤器加载之前。

只有核心模式的驱动才能生成一个通信端口,使用以下的调用:
NTSTATUS
FltCreateCommunicationPort(
    IN PFLT_FILTER Filter,
    OUT PHANDLE PortHandle,
    IN POBJECT_ATTRIBUTES ObjectAttributes,
    IN PVOID ServerPortCookie OPTIONAL,
    IN PFLT_CONNECT_NOTIFY ConnectNotifyCallback,
    IN PFLT_DISCONNECT_NOTIFY DisconnectNotifyCallback,
    IN PFLT_MESSAGE_NOTIFY MessageNotifyCallback,
    IN ULONG MaxConnections
);
Filter是微过滤器的过滤器句柄。成功生成之后,端口的句柄在PortHandle参数中返回。
和其他的NT对象一样,ObjectAttributes参数定义了OBJECT_ATTRIBUTES结构来初始化要生成的端口对象的名字,对象属性和安全描述符等。

请注意属性中OBJ_KERNEL_HANDLE标记必须设置。因为通信端口只能是核心对象。

ServerPortCookie是一个上下文。微过滤器可以通过这个和端口联系在一起。这个上下文对过滤管理器是不透明的。所有的连接,中断通知,都会同过这个上下文才能传递给微过滤器。有些过滤器可能要生成一组通信端口,又想功用一个同志例程。那么可以通过这个上下文中保存的数据进行区分。

调用者还可以注册一些回调函数:

ConnectNotifyCallback():       当一个用户态进程尝试打开一个端口的时候,这个例程被调用。过滤器可以选择把这个请求失败掉。通知例程回手到一个关于此连接的句柄。每一个连接有唯一的一个句柄。ServerPortCookie 也会传入。微过可以填写ConnectionCookie为一个上下文。这个上下文会传到所有的用户态传来的消息以及连接中断例程中。

DisconnectNotifyCallback():       当一个端口被用户态关闭的时候会调用这个回调。(也就是打开计数到0的时候)。

MessageNotifyCallback():       任何时候手到一个消息都会调用这个。

MaxConnections指出了这个通信端口上允许的最大向外连接数。这没有默认值,必须设置得大于0。

并不能保证所有的对象名会生成在根名字空间。有可能过滤管理器把它们映射在\FileSystem\Filters目录下。不过即使如此,对微过滤器和用户态应用程序来说,这是透明的。当引用了一个通信断口的名字,那么所有的足见都应该使用同样的名字。例子Sacnner Minifiter中展示了这是怎么做的。

对应于新的对象类型,新的访问方式也被引入了。有新的访问方式如下:
FLT_PORT_CONNECT
FLT_PORT_ALL_ACCESS
这是一些访问类型。调用者可以设置这些来给使用者权限。用于构造安全描述符的时候,使用InitializeObjectAttributes()。

设置了FLT_PORT_CONNECT,那么我们的应用程序足可以连接这个端口并发送和接受消息。

微过滤器生成一个端口之后,端口就会开始侦听可能的连接。直到你使用ZwClose()将它关闭为止。

10.2 从用户态连接到通信端口

微过滤器的通信端口模型和旧模型的过滤器一样,是不对称的。核心态端生成,用户态端连接。有一个接口用来给用户态应用打开一个端口。当端口建立,ConnectNotify()例程被调用来通知微过滤器。

用户态下连接一个端口的编程接口原型如下:
HRESULT
FilterConnectCommunicationPort(
    IN LPWSTR lpPortName,
    IN DWORD dwOptions,
    IN LPVOID lpContext,
    IN WORD wSizeOfContext,
    IN LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    OUT HANDLE *hPort
);

lpPortName是一个宽字符格式的字符串,指出要连接的端口的名字。这个名字应该和微过滤器生成这个端口的时候一样。名字可以以”\”开头表示在根路径中。过滤管理器会在合适的路径下打开他们并管理很多微过滤器通信端口。

dwOptions现在没有使用。
LpContext是一个指针。指向一个不透明的参数块。这个参数会被传入到ConnectNotify()中。比如可以用来鉴别请求生成端口的应用程序的版本。wSizeOfContext指出这个上下文的字节数。

LpSecurtityAttributes指出给用户端的安全权限。如果这个句柄是继承得到的。

如果这个调用不成功,合适的HRESULT会返回。

返回得到的句柄可以被管理或者被复制。这要通过一些编程接口。它也可以和一个I/O完成端口绑定。

当这个接口被调用,一个新的核心态的无名端口对象就生成了(类型是FilterCommunicationPort).这个对象用来表示这个连接。微过滤器的ConnectNotify()例程被调用,从而得到通知。而且也得到连接端口的句柄,用来从核心态发送消息。

如果调用者没有访问这个服务端口的权限,或者已经达到了最大连接数,那么调用会失败。

10.3 中断与通信端口的连接

当用户态程序调用CloseHandle()或者核心态调用ZwClose()来关闭连接句柄的时候,连接会中断。

只有用户态调用CloseHandle()的时候,微端口的DisconnectNotify()例程才会调用。

理想情况下,微过滤器应该总是在连接结束的时候,在DisconnectNotify()中关闭连接。如果一个微过滤器在其他地方关闭句柄,那么它必须用一些同步方法确保不会在DisconnectNotify()中关闭再次关闭它。

当一个连接在核心态或者用户态被中断了,以下情况发生:

1.       所有用户态的等待(通过FilterGetMessage)被清理掉,而且以STATUS_FLT_PORT_DISCONNECTED结束掉(被解释为win32错误码ERROR_DISCONNECTED)。
2. 所有核心的被阻塞的发送例程会以STATUS_FLT_PORT_DISCONNECTED结束阻塞。
3. 因为端口变无效了,所以不可能有其他的等待或者阻塞的情况出现。

微过滤器总是可以对服务端口的句柄调用ZwClose()来关闭服务端口。这并不会使已经建立的连接中断,但是会阻止新连接的建立。

10.4 卸载

微过滤器总是必须在FltUnregisterFilter()调用之前,在FilterUnload例程或者更早关闭服务端口。否则系统可能在卸载例程中被挂起。

即使在有一些连接打开的情况下(比如用户态一放已经打开了一些连接句柄),微过滤器也应该允许被卸载。这种情况下,过滤管理器会尝试强行终止这些连接。过滤管理器会调用DisconnectNotify()例程。微过滤器应该在这里关闭这些连接句柄以避免句柄的泄漏。

11.文件名处理

通过查找操作的参数或者询问文件系统,过滤管理器能得到对象的名字。因而过滤管理器提供一组调用来方便获取对象名。为了更高的效率,过滤管理器也暂存一些对象名。当很多过滤器经常查一个名字的时候,暂存这个名字所付出的代价是很值得的。

查询对象名的时候,过滤管理器返回一个FLT_FILE_NAME_INFORAMTION结构来避免数据拷贝。这些结构被根据过滤器的请求次数计数。只有通过过滤管理器的编程接口才能改变这些结构中的数据。下面有这些结构的详细信息。

11.1 从操作获得一个文件名

可以在当前操作的CallbackData->Iopb->TargetFileObject中得到文件名。这需要调用下面的例程:
NTSTATUS
FLTAPI
FltGetFileNameInformation (
    IN PFLT_CALLBACK_DATA CallbackData,
    IN FLT_FILE_NAME_FORMAT NameFormat,
    IN FLT_FILE_NAME_QUERY_METHOD QueryMethod,
    OUT PFLT_FILE_NAME_INFORMATION *FileNameInformation
);

CallbackData是这个操作的FLT_CALLBACK_DATA结构。现在假设过滤器想从这个操作中得到文件名。

名字的格式是以下三种:
FLT_FILE_NAME_NORMALIZED_FORMAT:请求全路径名字,包括卷名。所有的短名被扩展成长名。任何流名足见回去掉后边的“:$DATA”.如果这是一个目录名,且不是根目录,最后的“\”会被去掉。
FLT_FILE_NAME_OPENED_FORMAT:       包含全路径,包括卷名。但是这个名字和打开这个对象所用的名字相同。因此可能在路径中包含一些短名。
FLT_FILE_NAME_SHORT_FORMAT:       仅仅含有路径中最后一个元素的短名(Dos名),不会返回全路径。

QueryMethod应该是以下之一:
FLT_FILE_NAME_QUERY_DEFAULT:       搜索一个名字的时候,管理器会首先找暂存的名字。然后再询问文件系统。
FLT_FILE_NAME_QUERY_CACHE_ONLY:       仅仅在暂存中找。如果失败,返回STATUS_FLT_NAME_CACHE_MISS.
FLT_FILE_NAME_QUERY_FILE_SYSTEM_ONLY:仅仅询问文件系统,不会从暂存中去寻找这个名字。

名字在最后一个参数中返回,FileNameInformation.这个结构是一组共享缓冲的Unicode字符串。不同的字符串表明不名字中不同的部分.

typedef struct _FLT_FILE_NAME_INFORMATION {
    USHORT Size;
    FLT_FILE_NAME_FORMAT Format;
    FLT_FILE_NAME_PARSED_FLAGS NamesParsed;
    UNICODE_STRING Name;
    UNICODE_STRING Volume;
    UNICODE_STRING Share;
    UNICODE_STRING Extension;
    UNICODE_STRING Stream;
    UNICODE_STRING FinalComponent;
    UNICODE_STRING ParentDir;
} FLT_FILE_NAME_INFORMATION, *PFLT_FILE_NAME_INFORMATION;

当一个文件名信息结构从FltGetFileNameInforamtion()返回,name,Volume,Share(用于远程文件)会被解析出来。如果一个微过滤器需要其他的名字信息,它应该调用FltParseFileNameInformation().

运行在DPC或者更低级的中断级别的时候,微过滤器可以在IO过程中任何地方调用FltGetFileNameInformation().当它在一个可能导致死锁的情况下请求(比如处理分页交换的时候),如果在暂存中没有找到名字或者指定了必须从文件系统读取那么调用会失败。

即使没有回调数据(Callback Data)存在,仅仅知道文件对象(FileObject)的时候,微过滤器也可以调用FltGetFileNameInformationUnsafe()来获得一个文件名。不过过滤器必须知道当前向文件系统询问这个名字是安全的。这个调用不能像FltGetFileNameInformation()那样检查是否导致死活。微过滤器必须自己保证它。

当一个名字使用完毕,应该调用以下的例程来释放:

FLTAPI
FltReleaseFileNameInformation (
    IN PFLT_FILE_NAME_INFORMATION FileNameInformation
);

过滤管理器的名字暂存对微过滤查找对象的名字来说效率足够了。名字暂存机制也管理了由于重新命名而无效的名字。维护一个名字暂存空间的复杂逻辑对微过滤器们而言是透明的。但是微过滤器对于无效的名字并不是完全不需要关系。当一个重命名发生了,过滤管理器清理所有的受影响的暂存文件名。但是微过滤器可能引用过一个过时的文件名。当这些引用全部被释放的时候,过时的文件名信息结构才会被真的释放掉。如果一个微过滤器请求询问一个已经被重新命名的对象,将尽可能的返回新的名字。

11.2 文件名的附加支持

过滤管理器也提供了一个编程接口来帮助微过滤器获得一个改名或生成硬连接的操作的目的名:
NTSTATUS
FltGetDestinationFileNameInformation (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    IN HANDLE RootDirectory OPTIONAL,
    IN PWSTR FileName,
    IN ULONG FileNameLength,
    IN FLT_FILE_NAME_FORMAT NameFormat,
    IN FLT_FILE_NAME_QUERY_METHOD QueryMethod,
    OUT PFLT_FILE_NAME_INFORMATION *RetFileNameInformation
    );

这个接口只能在IRP_MJ_SET_INFORMATION的预操作回调中调用,包括FileSetNameInformation和FileSetLinkInformation。调用者从这个操作中获得参数,这个调用会返回一个FLT_FILE_NAME_INFORMTION结构,包含了目标文件名在其中。与使用FltGetFileNameInformation()相同,这个调用返回的名字使用完毕之后,应该调用FltReleaseFileNameInformation()来释放。

命名隧道(Name Tunneling)是过滤器的另一个困绕之处。过滤管理器提供了一个编程接口来获得一个命名管道需要的新名字:
NTSTATUS
FltGetTunneledName (
    IN PFLT_CALLBACK_DATA CallbackData,
    IN PFLT_FILE_NAME_INFORMATION FileNameInformation,
    OUT PFLT_FILE_NAME_INFORMATION *RetTunneledFileNameInformation
    );

命名隧道仅仅影响获取文件名采用通常格式的微过滤器。如果一个微过滤器在它的一个预操作需要一个通常格式的文件名,这个预操作回调的来源是CREATE,改名,或生成硬连接的话,它应该在后操作回调中调用FltGetTunneledName()来确认这个名字是否被操作系统命名隧道所改过。如果命名隧道确实出现了,那么RetTunneledFileNameInformation中回返回一个新的文件名字信息结构。微过滤器必须用这个新的名字,并且处理完毕后必须用FltReleaseFileName()来释放它。

(译者注:什么是命名隧道(Name Tunneling)

长文件名出现之后,旧的16位应用程序随时可能破坏掉长文件名。为此出现了所谓的命名隧道概念。现在假设一个16位的程序比如文字处理程序把当前版本的文挡维护在一个临时文件中。当用户修改这个文件,原始的文件就被删除了,临时文件被改为原来的文件名。

如果原始文件有一个长文件名,但是临时文件仅仅有短文件名,那么当旧的文件被删除,名字也跟着丢失了。因此当命名隧道起作用的时候,文件系统记住了每个被删除的文件名一段时间(比如15秒),如果一个短文件名的文件生成了,刚好和被记忆的长文件名配套,那么短文件名自动改名为长文件名。这就是命名隧道概念。

想自己尝试一下:首先,在一个空文件夹中生成一个文件 longfilename.然后删除它。在生成一个文件longfi~1,然后输入dir /x,这时你发现,longfilename又出现了!)

11.3 名字提供接口

如果一个过滤器打算提供一个途径来改变名字空间,他必须注册三个附加的回调给过滤管理器。这些回调允许过滤器作为名字的提供者。过滤器将有责任对上层发来的FltGetFilenameInformation或者FltGetDestinationFileName返回FLT_FILE_NAME_INFORMATION结构,并填写其中名字的内容。而且这样的微过滤器还可以告知过滤管理器,他们所返回的名字要不要被暂存。

作为一个名字提供者,过滤器必须可以返回一个指定的文件对象(file object)的开放格式的名字。如果确定了通常名字格式,过滤管理器将重新遍历这个名字的所有部分并调用名字提供者的徽调来展开这些部分为一个通常格式的文件名。在展开一个路径的所有部分的过程中,过滤器的名字通常化例程可能被调用不止一次。因此这个过程中允许过滤器传入一个上下文。所有的过程结束后,如果调用返回了,过滤器会被要求清理这个上下文。

当一个过滤器作为名字提供者必须提供一个名字的时候,它的PFLT_GENERATE_FILE_NAME例程会被调用。你能在参数中得到文件对象,过滤器的实例,同时回调数据描述发生的操作。此外还有名字请求选项。那么过滤器必须在这中间生成名字:

typedef NTSTATUS
(*PFLT_GENERATE_FILE_NAME) (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject,
    IN PFLT_CALLBACK_DATA CallbackData OPTIONAL,
    IN ULONG NameOptions,
    OUT PBOOLEAN CacheFileNameInformation,
    OUT PFLT_NAME_CONTROL FileName
    );

typedef NTSTATUS
(*PFLT_NORMALIZE_NAME_COMPONENT) (
    IN PFLT_INSTANCE Instance,
    IN CONST PUNICODE_STRING ParentDirectory,
    IN USHORT VolumeNameLength,
    IN CONST PUNICODE_STRING Component,
    IN OUT PFILE_NAMES_INFORMATION ExpandComponentName,
    IN ULONG ExpandComponentNameLength,
    IN OUT PVOID *NormalizationContext
    );

typedef VOID
(*PFLT_NORMALIZE_CONTEXT_CLEANUP) (
    IN PVOID *NormalizationContext
    );

如果一个过滤器希望让所有自己提供的名字的暂存失效,他可以调用以下的接口。在此之前它可能已经提供了一些FLT_FILE_NAME_INFORMATION结构,而且其他过滤器可能刚好还在使用。那么这些FLT_FILE_NAME_INFORMATION结构只有当引用数降到0(已经没有人使用了),才会自己释放掉。

NTSTATUS
FltPurgeFileNameInformationCache (
    IN PFLT_INSTANCE Instance,
    IN PFILE_OBJECT FileObject OPTIONAL
    );

为了确保这样一个过滤可以卸载,过滤管理器有责任管理这些过滤器提供的文件名的暂存和释放。

一些FltGetFileNameInformation()调用FltGetDestinationFileName()调用基于一些过滤器提供的名字。过滤管理器将负责初始化这些FLT_FILE_NAME_INFORMATION结构。

为了提高效率,过滤管理器往名字提供者的PFLT_GENERATE_FILE_NAME回调传入一个缓冲区。这个缓冲是一个FLT_NAME_CONTROL所包装的UNICODE_STRING.这个结构中包含一些公有的或者私有的信息。在一个过滤器试图填充这个缓冲之前,应该先检查缓冲是否足够的大:

NTSTATUS
FltCheckAndGrowNameControl (
    IN OUT PFLT_NAME_CONTROL NameCtrl,
    IN USHORT NewSize
    );
如果这个调用返回STATUS_SUCCESS,说明FLT_NAME_CONTROL结构足够容纳从名字提供者返回的名字了。




--------------------------------------------(待续)-----------------------------------------------------
本文翻译仅仅用做交流学习。我不打算保留任何版权或者承担任何责任。不要引用到赢利出版物中给
您带来版权官司。本文的翻译者是楚狂人,如果有任何问题,你可以通过邮箱
MFC_Tan_Wen@163.com,或者是QQ16191935,或者是MSN walled_river@hotmail.com与我交流。
harry_chen
驱动牛犊
驱动牛犊
  • 注册日期2002-07-08
  • 最后登录2007-05-25
  • 粉丝0
  • 关注0
  • 积分31分
  • 威望4点
  • 贡献值0点
  • 好评度2点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2005-06-21 08:58
兄弟,支持你!:)
学而不思则罔,思而不学则殆 学而思之,思而学之,岂非圣人乎?
游客

返回顶部