-
Windows Minifilter 드라이버에서 발생한 BSOD 0x7F (Double Fault) 커널 스택 오버플로우 분석 및 해결윈도우/커널 덤프분석 2025. 12. 22. 00:00반응형
개요
Windows Minifilter 드라이버를 개발하던 중 특정 조건에서 BSOD(Blue Screen of Death)가 발생하는 심각한 버그를 발견했습니다. 이 글에서는 해당 문제의 원인 분석과 해결 과정을 공유합니다.
증상
시스템이 갑자기 블루스크린과 함께 크래시되었고, WinDbg를 통해 크래시 덤프를 분석한 결과 다음과 같은 정보를 확인할 수 있었습니다.
UNEXPECTED_KERNEL_MODE_TRAP (7f) This means a trap occurred in kernel mode, and it's a trap of a kind that the kernel isn't allowed to have/catch (bound trap) or that is always instant death (double fault). Arguments: Arg1: 0000000000000008, EXCEPTION_DOUBLE_FAULT Arg2: 0000000080050033 Arg3: 00000000000000ff Arg4: fffff80535ca3da1BSOD 0x7F는 커널 모드에서 예상치 못한 트랩이 발생했음을 의미하며,
Arg1: 0x08 (EXCEPTION_DOUBLE_FAULT)는 Double Fault, 즉 예외 처리 중 또 다른 예외가 발생했음을 나타냅니다.스택 오버플로우 확인
WinDbg 분석에서 스택 오버플로우가 발생했음이 명확히 드러났습니다:
STACK_OVERFLOW_ON_CALLBACK: nt!ExExpandEnvironmentStrings+0x16bb10 STACK_DEPTH_ENTRIES: 53스택 깊이가 53단계로 매우 깊었고, 콜백 중 스택 오버플로우가 발생했습니다.
Call Stack 분석
크래시 시점의 전체 콜백 스택을 분석해보았습니다:
fffffe8a`6b2fb060 fffff805`35ca3da1 : nt!ExExpandEnvironmentStrings+0x16bb10 fffffe8a`6b2fb090 fffff805`35af6aa8 : nt!KiDoubleFaultAbort+0xb1 fffffe8a`6b2fb130 fffff805`35afd129 : nt!KiPageFault+0x448 fffffe8a`6b2fb2c8 fffff805`4e0f0e8c : nt!RtlDecompressBufferEx+0xe9 fffffe8a`6b2fb320 fffff805`4e0e54d9 : FLTMGR!TreeUnlink+0x7c fffffe8a`6b2fb370 fffff805`4e0e6df6 : FLTMGR!FltpSetCancelRoutine+0x39 fffffe8a`6b2fb3a0 fffff805`4e0f8c9a : FLTMGR!FltpSendMessageWaiter+0x66 fffffe8a`6b2fb3f0 fffff805`4e0f8b48 : FLTMGR!FltpSendMessage+0x10e fffffe8a`6b2fb4a0 fffff805`5c3f6f57 : FLTMGR!FltSendMessage+0x38 fffffe8a`6b2fb4f0 fffff805`5c3f7101 : MyDrv!RTSendEventWithQueue+0x97 fffffe8a`6b2fb560 fffff805`5c3f1a14 : MyDrv!RTSendIoEvent+0x71 fffffe8a`6b2fb5d0 fffff805`5c3f1ef9 : MyDrv!PreWrite+0x554 fffffe8a`6b2fb9d0 fffff805`4e0e8ab1 : MyDrv!PreWriteCallback+0x69 fffffe8a`6b2fba20 fffff805`4e0e89a0 : FLTMGR!FltpPerformPreCallbacks+0x301 fffffe8a`6b2fbb30 fffff805`4e0da07c : FLTMGR!FltpPassThroughInternal+0xd0 fffffe8a`6b2fbb80 fffff805`35dbab95 : FLTMGR!FltpPassThrough+0x16c fffffe8a`6b2fbc00 fffff805`361086ae : nt!IoSynchronousPageWrite+0x1d5 fffffe8a`6b2fbcc0 fffff805`35d29d53 : nt!MiFlushSectionInternal+0xa0e fffffe8a`6b2fc1d0 fffff805`35d1ec1f : nt!MmFlushSection+0xc3 fffffe8a`6b2fc280 fffff805`35dcc49b : nt!CcFlushCachePriv+0x46f fffffe8a`6b2fc420 fffff805`5b91d60f : nt!CcFlushCache+0x3b fffffe8a`6b2fc490 fffff805`5b91ccab : Ntfs!NtfsFlushUserStream+0xbf fffffe8a`6b2fc520 fffff805`5b91cb22 : Ntfs!NtfsFlushVolume+0x8b fffffe8a`6b2fc560 fffff805`5b9dfa5b : Ntfs!NtfsCommonFlushBuffers+0x62 fffffe8a`6b2fc5a0 fffff805`5b89e6a9 : Ntfs!NtfsCheckpointVolume+0x1bb fffffe8a`6b2fc7e0 fffff805`5b89e538 : Ntfs!NtfsCheckpointVolumeUntilDone+0x79 fffffe8a`6b2fc860 fffff805`5b89e1ef : Ntfs!NtfsCheckpointForLogFileFull+0xb8 fffffe8a`6b2fc8e0 fffff805`5b89e0f2 : Ntfs!NtfsCheckForLargeLogFile+0xaf fffffe8a`6b2fc930 fffff805`5b7c9f1a : Ntfs!NtfsWriteLog+0xc2 fffffe8a`6b2fc9b0 fffff805`5b7ca52e : Ntfs!NtfsSetEndOfFileRecordInformation+0x36 fffffe8a`6b2fca10 fffff805`5b7c9bfe : Ntfs!NtfsSetEndOfFileRecord+0x4e fffffe8a`6b2fca60 fffff805`5b7a5d2d : Ntfs!NtfsAddAllocation+0x9a fffffe8a`6b2fcab0 fffff805`5b8c4dde : Ntfs!NtfsCommonWrite+0x167d fffffe8a`6b2fce60 fffff805`4e0e8ab1 : Ntfs!NtfsFsdWrite+0x18e fffffe8a`6b2fcf00 fffff805`4e0e89a0 : FLTMGR!FltpPerformPreCallbacks+0x301 fffffe8a`6b2fd010 fffff805`4e0dd91f : FLTMGR!FltpPassThroughInternal+0xd0 fffffe8a`6b2fd060 fffff805`35a44af5 : FLTMGR!FltpPassThrough+0x3ff fffffe8a`6b2fd0e0 fffff805`35a44697 : nt!IofCallDriver+0x55 fffffe8a`6b2fd120 fffff805`35a43c3b : nt!IopSynchronousServiceTail+0x1c7 fffffe8a`6b2fd1d0 fffff805`35a4344f : nt!IopWriteFile+0x9fb fffffe8a`6b2fd390 fffff805`35af4ae3 : nt!NtWriteFile+0x6f fffffe8a`6b2fd420 fffff805`35ae7fe0 : nt!KiSystemServiceCopyEnd+0x43 fffffe8a`6b2fd628 fffff805`5c405bf4 : nt!KiServiceLinkage fffffe8a`6b2fd630 fffff805`5c405988 : MyDrv!RTBackupFile+0x2f4 fffffe8a`6b2fdb60 fffff805`5c3f32b9 : MyDrv!RTBackupFile+0x88 fffffe8a`6b2fdc00 fffff805`5c3f4063 : MyDrv!PreCreate+0x449 fffffe8a`6b2fe0e0 fffff805`4e0e8ab1 : MyDrv!PreCreateCallback+0xf3 fffffe8a`6b2fe160 fffff805`4e0e89a0 : FLTMGR!FltpPerformPreCallbacks+0x301 fffffe8a`6b2fe270 fffff805`4e0dd91f : FLTMGR!FltpPassThroughInternal+0xd0 fffffe8a`6b2fe2c0 fffff805`4e0e63d6 : FLTMGR!FltpPassThrough+0x3ff fffffe8a`6b2fe340 fffff805`4e0e6110 : FLTMGR!FltpCreate+0x316 fffffe8a`6b2fe3f0 fffff805`35a44af5 : FLTMGR!FltpCreateInternal+0x40 fffffe8a`6b2fe440 fffff805`35fbf475 : nt!IofCallDriver+0x55 fffffe8a`6b2fe480 fffff805`35fb0dc7 : nt!IopParseDevice+0x8e5 fffffe8a`6b2fe660 fffff805`35faffdc : nt!ObpLookupObjectName+0xa87 fffffe8a`6b2fe850 fffff805`35ee3a0d : nt!ObOpenObjectByNameEx+0x1fc fffffe8a`6b2fe990 fffff805`35ee1b3e : nt!IopCreateFile+0x3fd fffffe8a`6b2fea40 fffff805`35af4ae3 : nt!NtCreateFile+0x6e fffffe8a`6b2fead0 fffff805`35ae7fe0 : nt!KiSystemServiceCopyEnd+0x43문제의 원인
Call Stack을 자세히 보면 다음과 같은 재진입(Reentrant) 패턴이 나타납니다:
사용자 요청: NtCreateFile (파일 열기) ↓ MyDrv!PreCreate (파일 접근 감지) ↓ MyDrv!RTBackupFile (파일 백업 시도) ↓ NtWriteFile (백업 파일 쓰기) ↓ NTFS!NtfsWriteLog → NtfsCheckpointVolume (NTFS 체크포인트 발생) ↓ MmFlushSection → IoSynchronousPageWrite (Paging I/O 발생!) ↓ FLTMGR → MyDrv!PreWrite (Paging I/O로 다시 콜백 진입) ↓ MyDrv!RTSendIoEvent → RTSendEventWithQueue ↓ FLTMGR!FltSendMessage (유저 모드 통신 시도) ↓ 💥 스택 오버플로우 → Double Fault → BSOD핵심 문제점
- 깊은 콜 스택: PreCreate에서 백업 작업을 수행하면서 이미 스택이 깊어진 상태
- NTFS 체크포인트: 백업 파일 쓰기 중 NTFS가 로그 파일 체크포인트를 트리거
- Paging I/O 재진입: 체크포인트 과정에서 Paging I/O가 발생하고, 이로 인해 PreWrite 콜백이 다시 호출됨
- FltSendMessage 호출: Paging I/O 컨텍스트에서 FltSendMessage를 호출하려 했으나, 이미 스택 한계에 도달
Paging I/O는 메모리 관리자가 직접 발생시키는 I/O로, 일반 I/O와 다른 특수한 컨텍스트에서 실행됩니다. 이 상황에서 블로킹 호출인
FltSendMessage를 호출하는 것은 위험합니다.해결 방법
1. Paging I/O에서 이벤트 전송 스킵
Paging I/O 컨텍스트에서는 유저 모드 통신을 수행하지 않도록 수정했습니다:
// PreWrite 콜백에서 if (!RTIsMonitoredExtension(&pNameInfo->Extension)) { DbgPrint(RT_LOG_PREFIX "PREWRITE_SKIP: NotMonitored pid=%lu ext=%wZ paging=%d\n", ulPid, &pNameInfo->Extension, bPagingIo); // Paging I/O가 아닐 때만 이벤트 전송 if (!bPagingIo) { RTSendIoEvent(pFilter, pStreamContext->pszFilePath, pStreamContext->ullFileId, ulPid, RT_FILE_OP_WRITE, pNameInfo); } goto Cleanup; } // 첫 수정 이벤트 전송 부분에서도 동일하게 적용 if (bFirstModify && !bPagingIo) { RTSendIoEvent(pFilter, pStreamContext->pszFilePath, pStreamContext->ullFileId, ulPid, RT_FILE_OP_WRITE, pNameInfo); }2. PreSetInfo에도 동일한 보호 적용
파일 정보 설정 콜백에서도 같은 패턴을 적용했습니다:
// PreSetInfo 콜백에서 if (!bPagingIo) { RTSendIoEvent(pFilter, wszFilePath, ullFileId, ulPid, RT_FILE_OP_SETINFO, pNameInfo); }3. IRQL 안전성 체크 추가
추가적인 안전장치로 IRQL 레벨 확인을 추가했습니다:
VOID RTSendEventWithQueue(_In_ PRT_IO_EVENT Event) { // IRQL이 APC_LEVEL보다 높으면 FltSendMessage 호출 불가 if (KeGetCurrentIrql() > APC_LEVEL) { DbgPrint(RT_LOG_PREFIX "RTSendEventWithQueue: IRQL too high (%d), queueing only\n", KeGetCurrentIrql()); (VOID)RtpEnqueueEvent(Event); return; } // 정상 처리 계속... }왜 이벤트를 버려도 되는가?
Paging I/O에서 발생하는 이벤트를 버리는 것이 괜찮은 이유:
- 이벤트는 UI 알림 용도: 이벤트는 사용자 인터페이스에 파일 활동을 표시하기 위한 것이며, 핵심 보호 로직과는 분리되어 있습니다.
- MutationCount는 별도 관리: 랜섬웨어 탐지에 사용되는 변경 카운트는 이벤트 전송과 별개로 증가합니다.
- 백업도 독립적: 파일 복원에 사용되는 백업 파일은 이벤트 전송 여부와 관계없이 정상적으로 생성됩니다.
- Paging I/O의 특성: Paging I/O는 시스템이 내부적으로 발생시키는 것으로, 일반적인 사용자 파일 작업과는 다릅니다.
교훈
- Minifilter 콜백에서 블로킹 호출 주의:
FltSendMessage와 같은 블로킹 함수는 콜백 컨텍스트에서 신중하게 사용해야 합니다. - Paging I/O 특별 처리: Paging I/O 컨텍스트에서는 가능한 최소한의 작업만 수행하고, 유저 모드 통신은 피해야 합니다.
- 재진입 시나리오 고려: 백업이나 로깅 같은 작업이 추가 I/O를 발생시킬 수 있고, 이로 인해 콜백이 재진입될 수 있음을 고려해야 합니다.
- 스택 깊이 모니터링: 커널 스택은 제한되어 있으므로(일반적으로 24KB), 깊은 콜 체인이 발생할 수 있는 상황을 인지해야 합니다.
- IRQL 확인: 커널 모드에서 함수를 호출하기 전에 현재 IRQL이 해당 함수의 요구 사항을 충족하는지 확인해야 합니다.
결론
Windows Minifilter 드라이버 개발은 다양한 엣지 케이스를 고려해야 하는 복잡한 작업입니다. 특히 Paging I/O, 재진입 콜백, 스택 깊이 등의 요소를 항상 염두에 두어야 합니다. 이번 문제는 테스트 환경에서 발견되어 다행이었지만, 프로덕션 환경에서 발생했다면 심각한 시스템 불안정을 초래할 수 있었습니다.
이 글이 Windows 드라이버 개발자분들께 도움이 되길 바랍니다.
반응형'윈도우 > 커널 덤프분석' 카테고리의 다른 글
Windows 커널 드라이버에서 파일 시스템 콜백 내 로깅으로 인한 데드락 문제 (0) 2025.12.18 Windows Minifilter 드라이버에서 PreAcquireForSectionSync와 FLT_PREOP_PENDING 충돌로 인한 BSOD 분석 (0) 2025.12.15 Windows Minifilter 드라이버에서 Fast I/O와 FLT_PREOP_PENDING 충돌 문제 해결 (0) 2025.12.14 윈도우 커널 드라이버를 서비스로 등록하고 실행/정지 시키는 방법 (0) 2025.05.15