基于Minifilter的透明加密:FltWriteFileEx在PostCloseWhenSafe中死锁求助
Minifilter PostCloseWhenSafe写入死锁分析与解决
死锁根源分析
从日志和代码逻辑来看,死锁的核心是循环等待:
- 执行
PostCloseWhenSafe的线程先获取streamContext->Resource独占锁,随后调用同步版FltWriteFileEx(未指定异步回调,会阻塞等待I/O完成)。 - 此时有
PostWrite请求触发,该请求尝试获取同一个独占锁,但锁已被PostCloseWhenSafe线程持有,因此PostWrite线程进入等待状态。 PostCloseWhenSafe线程在等待FltWriteFileEx的I/O完成,而该I/O的完成可能依赖于PostWrite线程的处理(比如底层文件系统I/O完成需要上层例程收尾,或存在其他并发I/O依赖),最终形成循环等待:PostCloseWhenSafe线程:持有锁 → 等待I/O完成PostWrite线程:等待锁 → 无法推进I/O完成
你提到的文档说明FltWriteFileEx的请求不会发给当前实例及以上,但死锁并非源于该请求触发自身的PostWrite,而是同步I/O等待期间,并发的PostWrite请求(或依赖PostWrite处理的其他I/O)与锁的持有逻辑冲突。
解决方案
针对这个场景,有两种可靠的修正思路:
1. 缩小锁的持有范围,避免I/O期间持有锁
将锁的持有仅限定在访问streamContext的临界区,不在I/O操作期间持有锁:
// 第一步:获取锁,准备写入所需的上下文数据 CtxAcquireResourceExclusive(streamContext->Resource); // 保存写入参数(如果需要) LARGE_INTEGER savedOffset = ByteOffset; SIZE_T savedLength = WriteLength; PVOID savedBuffer = WriteBuffer; CtxReleaseResource(streamContext->Resource); // 第二步:执行同步写入,此时不持有锁 DbgPrint("before FltWriteFileEx.\n"); Status = FltWriteFileEx( Instance, FileObject, &savedOffset, savedLength, savedBuffer, FLTFL_IO_OPERATION_NON_CACHED | FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET, &BytesWritten, NULL, NULL, NULL, NULL); // 第三步:再次获取锁,更新上下文状态 CtxAcquireResourceExclusive(streamContext->Resource); if (NT_SUCCESS(Status) && BytesWritten != 0) { streamContext->something = xxx; DbgPrint("after FltWriteFileEx.\n"); } else { DbgPrint("tail FltWriteFile failed. status = %08x, bytesWritten = %ld.\n", Status, BytesWritten); } CtxReleaseResource(streamContext->Resource);
2. 将FltWriteFileEx改为异步调用
通过指定完成回调,避免在持有锁时阻塞等待I/O完成:
// 定义I/O完成回调函数 VOID WriteTailCompletionRoutine( _In_ PFLT_CALLBACK_DATA CallbackData, _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_opt_ PVOID CompletionContext, _In_ NTSTATUS Status, _In_ ULONG_PTR BytesWritten ) { PSTREAM_CONTEXT streamContext = (PSTREAM_CONTEXT)CompletionContext; CtxAcquireResourceExclusive(streamContext->Resource); if (NT_SUCCESS(Status) && BytesWritten != 0) { streamContext->something = xxx; DbgPrint("after FltWriteFileEx.\n"); } else { DbgPrint("tail FltWriteFile failed. status = %08x, bytesWritten = %ld.\n", Status, BytesWritten); } CtxReleaseResource(streamContext->Resource); // 若CompletionContext是动态分配的资源,需在此释放 } // PostCloseWhenSafe中的逻辑 CtxAcquireResourceExclusive(streamContext->Resource); DbgPrint("before FltWriteFileEx.\n"); Status = FltWriteFileEx( Instance, FileObject, &ByteOffset, WriteLength, WriteBuffer, FLTFL_IO_OPERATION_NON_CACHED | FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET, NULL, // 异步调用无需同步返回BytesWritten WriteTailCompletionRoutine, streamContext, // 传递上下文到回调 NULL, NULL); if (!NT_SUCCESS(Status)) { DbgPrint("FltWriteFileEx async failed. status = %08x.\n", Status); streamContext->something = xxx; } CtxReleaseResource(streamContext->Resource);
额外注意事项
- 异步调用时要确保
WriteBuffer等资源的生命周期覆盖整个I/O过程,避免回调时内存已被释放(可使用非分页池内存或引用计数管理)。 - 若
PostWrite中也有对streamContext的修改,要确保所有临界区的锁逻辑一致,避免其他潜在死锁场景。
内容的提问来源于stack exchange,提问作者Drake Wu
相关产品推荐
相关产品推荐

