5.内存地址重建
当应用程序需要调用驱动程序实现的某些功能时,通常需要向驱动程序传递某些必要的信息。这时,可以通过共享内存向驱动程序传递参数,例如利用共享堆或者内存映像文件等。 在大多数情况下,驱动程序的功能都可以通过API调用来加以访问。
这可能带来两个问题:首先,API参数位于用户内存进程空间中,而驱动程序则位于内核内存空间(对于内核模式驱动程序)或者另一个用户进程(对于用户模式驱动程序而言)中。其次,调用方必须具备足够的权限才能访问被传递的缓冲区。因此,在驱动程序开发期间,您必须检查对要传递的缓冲区的访问权,并为驱动程序提供访问调用方的缓冲区数据所需权限。下图给出了内存地址重建示例。
图1 Abc.exe内存地址重建示例
在这个例子中,Abc.exe将通过两个参数来调用一个Driver.dll函数。第一个参数是指针参数,第二个是带有嵌入指针的结构体。如果Driver.dll函数是从调用方的线程中调用的,那么可以在调用期间直接从Driver.dll函数中访问Abc.exe的内存空间。同步调用时,直接访问需要检查访问权限,不过无需进行内存地址重建。
如果需要异步访问缓冲区的话,有两种选择:第一种是将缓冲区拷贝至驱动程序内存,第二种方法是把要传递的缓冲区设为其物理内存的别名。
内存地址重建的类型有多种,如下:
·直接访问。
调用进程的缓冲区在调用期间可以直接访问。
这仅适用于内核模式驱动程序的同步访问。
·复制。
将需要传递的缓冲区复制到驱动程序的工作缓冲区。
一个驱动程序使用一份副本。 如果需要,还可以反向复制。
·使用别名。
在驱动程序内创建一个新缓冲区,而该驱动程序与需要传递的缓冲区恰好位于同一物理内存区。
缓冲区的所有改变在调用进程中都是可访问的。
内核程序能确定最合适的内存地址重建方法,它会根据对缓冲区的同步或者异步访问,来确定出内存地址重建所需的API。
对于同步访问,内核程序会自动转换指针参数,所以开发人员必须通过调用CeOpenCallerBufer()来手动映射嵌入指针,验证访问权限,并执行内存地址重建,最后调用CeCloseCallerBuffer()。
对于异步访问,转换过程较之于同步访问还需为调用CeAllocAsynchronousBuffer()函数进行异步访问准备好所有的指针,并且最后要调用CeFreeAsynchronousBuffer()函数。
用户模式驱动程序的内存地址重建有以下限制:对于异步访问来说,指针参数必须以只读方式访问,而不可以写方式访问。尽管可以手工方式完成内部指针的内存地址重建,但是从内核程序调用一个驱动程序时,有可能收到无法从用户模式驱动程序访问的指针。
因此,更有效的方式是使用一个平滑的缓冲区存放用户模式驱动程序的所有数据,并且不异步访问。
下面介绍检查调用方缓冲区的访问权限和内存地址重建所需的系统API。对于同步访问,指针参数不需要额外的API调用;嵌入指针可以使用CeOpenCallerBufer()和CeCloseCallerBuffer () 。对于异步访问,当指针参数使用CeAllocAsynchronousBuffer ()时,嵌入指针可以使用CeOpenCallerBufer()和CeAllocAsynchronousBuffer();当指针参数使用CeFreeAsynchronousBuffer()时,嵌入指针可以使用CeFreeAsynchronousBuffer() 和CeCloseCallerBuffer() 。
6.安全复制
向驱动程序传递数据会导致额外的风险——可能改变已经过驱动程序验证的、API执行期间的指针及或它们指向的数据。为了防止这些类型的问题,可以使用安全副本方法,它会给驱动程序的数据创建一个单独的副本。创建安全副本时,被传递的缓冲区数据会拷贝至驱动程序的内部缓冲区中。这种方式适合于下列情形:
·用于所有嵌入指针。
·用于所有使用之前需验证的参数。
注意,使用安全副本在一定程度上会影响效率。创建安全副本时,可以使用下列方法:
·手动方式。
·利用CeOpenCallerBuffer()函数,并将嵌入指针的参数ForceDuplicate设为TRUE。
·为指针参数调用CeAllocDuplicateBuffer()。