本篇文章为个人笔记,仓库暂时为私有状态
我们先进入这个commit状态的代码进行测试
我们启动驱动,然后使用用户模式的程序连接他的miniport,此时会触发miniport的连接回调函数Comm_PortConnect,该函数会创建一个用于保存连接状态的context结构体,并通过PortCtx_CreateAndInsert函数插入到链表中
此时可以看到新的ctx节点被插入

我们在链表head写入写断点来检测节点删除操作
此时我们卸载驱动,可以看到首先是Unload例程调用PortCtx_Uninit来删除链表中的节点并释放对应内存
# Child-SP RetAddr Call Site
00 ffffdb80`aba76840 fffff802`308b215e UserModeHookHelper!RemoveHeadList+0x7a
01 ffffdb80`aba76880 fffff802`308b1f39 UserModeHookHelper!PortCtx_Uninit+0x3e
02 ffffdb80`aba768c0 fffff802`35e77aba UserModeHookHelper!MiniUnload+0x19
之后我们在PortCtx_RemoveAndFree函数下断点,放行
断点触发后调用栈如下
# Child-SP RetAddr Call Site
00 ffffdb80`aba76788 fffff802`308b174d UserModeHookHelper!PortCtx_RemoveAndFree
01 ffffdb80`aba76790 fffff802`35e77f19 UserModeHookHelper!Comm_PortDisconnect+0x4d
02 ffffdb80`aba767d0 fffff802`35e7a7ba FLTMGR!FltpTerminateActiveConnections+0x6d
03 ffffdb80`aba76800 fffff802`308b1f92 FLTMGR!FltUnregisterFilter+0x1aa
04 ffffdb80`aba768c0 fffff802`35e77aba UserModeHookHelper!MiniUnload+0x72
05 ffffdb80`aba76900 fffff802`35e77d66 FLTMGR!FltpDoUnloadFilter+0x19e
06 ffffdb80`aba76af0 fffff802`32849b1f FLTMGR!FltpMiniFilterDriverUnload+0x146
07 ffffdb80`aba76b30 fffff802`322b8515 nt!IopLoadUnloadDriver+0xdbd0f
08 ffffdb80`aba76b70 fffff802`32355855 nt!ExpWorkerThread+0x105
09 ffffdb80`aba76c10 fffff802`323fe808 nt!PspSystemThreadStartup+0x55
0a ffffdb80`aba76c60 00000000`00000000 nt!KiStartSystemThread+0x28
此时Unload函数已经将所有的ctx节点释放掉了,那么再在Comm_PortDisconnect函数中引用ctx必然导致use after free进而导致蓝屏,是一个很大的安全隐患
VOID
Comm_PortDisconnect(
__in PVOID ConnectionCookie
) {
PCOMM_CONTEXT pPortCtx = (PCOMM_CONTEXT)ConnectionCookie;
if (!pPortCtx) return;
Log(L"client process %d disconnected with port context 0x%p\n",
pPortCtx->m_UserProcessId, pPortCtx);
// Remove and free via PortCtx module
PortCtx_RemoveAndFree(pPortCtx);
}
referecnce count do the rescue
现在将代码设置到这个commit
在定义COMM_CONTEXT结构体之初,我就设置了一个m_RefCount字段,但是只有这一个字段是不够的,还需要再添加一个marker字段m_Removed
一开始我也不太理解为什么要同时设置这两个字段,后来copilot给我解释了一下我才明白
m_Removed字段用于告诉调用者当前的ctx已经从list中移除了,其内存马上就要被释放了,不要再使用这个ctx了
m_RefCount作为ctx的引用计数可以确保ctx只有在m_RefCount为0的时候才会被释放
考虑下面这种场景
- 调用者A得到一个ctx对象,那么他必须给引用计数+1,这样就能确保该对象不会在自己的使用过程中被释放掉
- 调用者B想要从链表中移除并释放该ctx的内存,那么他需要进行如下操作:
- 加锁,将m_Remove设置为TRUE
- 降低引用计数,由于A增加了引用计数,此时的引用计数必不为0,因此无法释放内存
- 而此时如果我们没有m_RefCount,只有m_Removed字段,那么就会直接释放A正在使用的ctx对象的内存
- 而m_Removed又可以组织我们对正在准备被删除的对象进行操作
还有一点需要注意的就是,针对我们的miniport断开连接回调,我们需要先检测存储在ConnectionCookie中的ctx是否已经被释放,因为即使是使用了引用计数,这个函数也有可能在ctx被使用之后再被调用,此时获取到的ctx将指向已经被释放的内存,针对该指针的任何引用操作都可能导致预期外的行为
因此我们增加了PortCtx_FindAndReferenceByCookie函数以检查其中的ctx是否已经不存在于ctx链表中
这样就可以避免悬垂指针的问题