6.1 初始化赋值问题
初始化其实是一个简单的问题,但是笔者发现很多开发者在定义内核变量时,不会主动去初始化这些变量,这是一个坏习惯。
上面的代码定义了一个strNameBuf变量,这个变量用于保存驱动对应注册表的信息,代码中使用memcpy函数,把RegistryPath里面的字符串复制到strNameBuf缓冲区中,由于strNameBuf缓冲区定义时没有被初始化0,且RegistryPath→Buffer所指向的字符串也没有以0结尾,所以在memcpy操作后,strNameBuf中的字符串并没有0结尾。若通过字符串相关API使用strNameBuf,会存在越界风险。
所以,较好的格式应该是:WCHAR strNameBuf[260] = { 0 };。
另外,在初始化、赋值问题上,请读者遵守一个规则:有效资源的变量值为非空,否则为NULL。什么意思呢?请看下面代码:
上面的代码申请了一块非分页内存,赋值到pData变量中,pData被使用完毕后,通过ExFreePoolWithTag函数释放,这里存在一个隐患,由于pData被释放后,所指向的内存已经“无效”,所以最好的做法是把pData赋值为NULL,表明这个指针没有指向任何内存。这样做的好处是万一后面的代码误使用了pData,会马上触发一个空指针异常,这种错误可以在调试过程中发现。上面的代码可以改成:
读者也许会疑惑,对于上面的例子,pData被释放后,即使没有把pData设置为NULL,但pData所指向的内存已经是无效了,后面的代码误访问pData所指向的内存同样会触发异常。事实上,在某些环境下,pData被释放后内存依然是有效的,访问这块内存并不会触发异常,考虑一个极端的场景:这块内存被释放后,刚好被其他驱动申请下来,如果代码误使用这块内存,如修改内存数据,会导致其他驱动数据异常!另外,对于系统的POOL管理来说,释放一块内存,可能只是标记这块内存为“空闲”,并非真正删除这块内存,所以操作一块被释放的内存,大概率不会马上发生异常。但异常一定会发生,发生的时间不确定,可能是系统运行一段时间后蓝屏,而蓝屏的堆栈,可能是其他驱动模块的函数堆栈,此类问题就是典型的“POOL数据破坏”,类似应用层的“堆破坏”。定位这类问题非常困难,所以最好的方式是,在代码层面中尽可能规避这类问题,或者让问题在调试中马上暴露出来。
相应地,对于其他资源的使用,如句柄、对象指针等,在释放后,都必须相应地把该变量设置为“空”。尤其是句柄,众所周知,句柄的值是复用的,如一个EVENT的句柄值是0x8000004C,这个句柄值被关闭后,其他代码通过API申请句柄时,会大概率复用这个句柄值,所以一旦句柄误操作,其后果也是灾难性的。
上面大篇幅叙述了资源误操作所带来的危害以及定位问题的成本。开发者只要遵守上面提到的规则,则可以在一定程度上避免这种低级错误带来的高成本问题。