-
Windows 커널 드라이버에서 파일 시스템 콜백 내 로깅으로 인한 데드락 문제윈도우/커널 덤프분석 2025. 12. 18. 01:06반응형
개요
Windows 미니필터 드라이버 개발 중 파일 시스템 콜백 함수 내에서 파일 기반 로깅을 수행할 때 발생하는 데드락 문제와 해결 방법을 정리합니다.
증상
- 특정 파일 작업(암호화 프로그램 실행 등) 시 시스템 전체가 멈춤(hang)
- 작업 관리자, 탐색기 등 모든 프로그램이 응답 없음
- 커널 디버거 연결 시 브레이크포인트에 걸린 위치가 없음
원인 분석
콜스택 확인
커널 디버거에서 !process 0 7 <프로세스명>으로 스택을 확인한 결과:
THREAD ffff99077c54a080 Cid 1c44.0de0 WAIT: (WrResource) KernelMode Non-Alertable ffff8401e5090a70 SynchronizationEvent Child-SP RetAddr : Call Site ffff8401`e5090610 fffff801`1b2e3260 : nt!KiSwapContext+0x76 ffff8401`e5090750 fffff801`1b2e278f : nt!KiSwapThread+0x500 ffff8401`e5090800 fffff801`1b2e2033 : nt!KiCommitThreadWait+0x14f ffff8401`e50908a0 fffff801`1b2deacd : nt!KeWaitForSingleObject+0x233 ffff8401`e5090990 fffff801`1b2e84ae : nt!ExpWaitForResource+0x6d ffff8401`e5090a10 fffff801`1c61b15c : nt!ExAcquireResourceExclusiveLite+0x1fe ffff8401`e5090aa0 fffff801`1b9fdb1b : VerifierExt!ExAcquireResourceExclusiveLite_wrapper+0x15c ffff8401`e5090af0 fffff807`eee36e86 : nt!VerifierExAcquireResourceExclusiveLite+0x3b ffff8401`e5090b30 fffff807`eee36d2d : MyDrv!KLogWriteToFile+0x66 [MyDrv_Logger.c @ 509] ffff8401`e5091030 fffff807`eee36034 : MyDrv!KLogV+0xcd [MyDrv_Logger.c @ 341] ffff8401`e50914a0 fffff807`eee265da : MyDrv!KLog+0x34 [MyDrv_Logger.c @ 311] ffff8401`e50914e0 fffff807`eee35736 : MyDrv!RTBackupFile+0xb0a [MyDrv_Backup.c @ 723] ffff8401`e5091ad0 fffff801`1c37cf67 : MyDrv!MyDrv_PreAcquireForSectionSync+0x2e6 [MyDrv.c @ 2720] ffff8401`e5091fc0 fffff801`1c3264cc : FLTMGR!FltvPreOperation+0xe7 ... ffff8401`e5092610 fffff801`1b670a4b : nt!FsRtlAcquireToCreateMappedSection+0x5b ffff8401`e5092690 fffff801`1b6706dd : nt!MiCallCreateSectionFilters+0x37 ffff8401`e50926d0 fffff801`1b66ee94 : nt!MiCreateImageOrDataSection+0x13d ffff8401`e50927c0 fffff801`1b670cc7 : nt!MiCreateSection+0xf4 ffff8401`e5092940 fffff801`1b670eac : nt!MiCreateSectionCommon+0x207 ffff8401`e5092a20 fffff801`1b42d505 : nt!NtCreateSection+0x5c데드락 발생 메커니즘
NtCreateSection └─ MiCreateSection └─ FsRtlAcquireToCreateMappedSection ← 파일 매핑 리소스 획득 시도 └─ FLTMGR!FltpPreFsFilterOperation └─ MyDrv!PreAcquireForSectionSync ← 미니필터 콜백 진입 └─ MyDrv!RTBackupFile └─ KLOG_DEBUG(...) ← 로그 기록 시도 └─ KLogWriteToFile └─ ExAcquireResourceExclusiveLite ← 로그 파일 락 대기 └─ 🔒 데드락!문제의 핵심
- PreAcquireForSectionSync 콜백은 파일 섹션 매핑 전에 호출됨
- 이 콜백 내에서 파일 백업(RTBackupFile) 수행
- 백업 완료 후 KLOG_DEBUG로 로그 기록 시도
- KLog는 내부적으로 로그 파일에 쓰기 수행
- 로그 파일 쓰기를 위해 ExAcquireResourceExclusiveLite로 리소스 락 획득 시도
- 이미 파일 시스템 콜백 체인 내에 있으므로 리소스 획득 불가 → 무한 대기
문제 코드
// MyDrv_Backup.c - RTBackupFile 함수 내 NTSTATUS RTBackupFile( _In_ PCUNICODE_STRING FileName, _Out_opt_ PUNICODE_STRING OutBackupPath) { // ... 백업 로직 ... DbgPrint(RT_LOG_PREFIX "Backup completed for %wZ -> %wZ\n", FileName, &UniBackupPath); KLOG_DEBUG("Backup: %wZ", FileName); // ❌ 데드락 발생 지점! return STATUS_SUCCESS; }// MyDrv_Logger.c - KLogWriteToFile 함수 static NTSTATUS KLogWriteToFile(PCSTR Message, SIZE_T Length) { if (!g_KLogCtx.FileHandle) { return STATUS_INVALID_HANDLE; } // 파일 락 획득 시도 - 콜백 내에서 호출 시 데드락! KeEnterCriticalRegion(); ExAcquireResourceExclusiveLite(&g_KLogCtx.FileLock, TRUE); // ❌ 여기서 블로킹 // ... 파일 쓰기 ... ExReleaseResourceLite(&g_KLogCtx.FileLock); KeLeaveCriticalRegion(); return status; }해결 방법
1. 파일 콜백 내에서 파일 I/O 로깅 제거
// 수정 후 NTSTATUS RTBackupFile(...) { // ... 백업 로직 ... DbgPrint(RT_LOG_PREFIX "Backup completed for %wZ -> %wZ\n", FileName, &UniBackupPath); // KLOG_DEBUG 제거 - 파일 콜백 내에서 KLog(파일 쓰기)하면 데드락 발생 return STATUS_SUCCESS; }2. 비동기 로깅 큐 사용 (권장)
파일 콜백에서 직접 로그를 쓰지 않고, 로그 메시지를 큐에 넣고 별도 스레드에서 처리:
typedef struct _KLOG_ENTRY { LIST_ENTRY ListEntry; LARGE_INTEGER Timestamp; KLOG_LEVEL Level; CHAR Message[KLOG_MAX_MESSAGE_LEN]; } KLOG_ENTRY, *PKLOG_ENTRY; // 콜백 내에서 호출 가능한 비블로킹 로깅 VOID KLogAsync(KLOG_LEVEL Level, PCSTR Format, ...) { PKLOG_ENTRY entry = ExAllocatePoolWithTag(NonPagedPool, sizeof(KLOG_ENTRY), 'gLoK'); if (!entry) return; // 메시지 포맷팅 va_list args; va_start(args, Format); RtlStringCbVPrintfA(entry->Message, sizeof(entry->Message), Format, args); va_end(args); // 큐에 추가 (스핀락으로 보호, 블로킹 없음) KeAcquireSpinLock(&g_LogQueueLock, &oldIrql); InsertTailList(&g_LogQueue, &entry->ListEntry); KeReleaseSpinLock(&g_LogQueueLock, oldIrql); // 워커 스레드에 신호 KeSetEvent(&g_LogQueueEvent, IO_NO_INCREMENT, FALSE); }3. DbgPrint만 사용
파일 콜백 내에서는 DbgPrint만 사용하고, 파일 로깅은 콜백 외부에서만 수행:
// 콜백 내에서는 DbgPrint만 사용 #define KLOG_CALLBACK_SAFE(fmt, ...) DbgPrint("[MyDrv] " fmt "\n", ##__VA_ARGS__)주의사항
파일 시스템 미니필터 콜백에서 피해야 할 작업
작업위험도이유
파일 I/O (ZwCreateFile, ZwWriteFile 등) 🔴 높음 재진입으로 인한 데드락 ERESOURCE 획득 🔴 높음 리소스 경합 시 블로킹 동기 IRP 전송 🔴 높음 콜백 체인 재진입 대량 메모리 할당 🟡 중간 페이지 폴트 → 파일 I/O 긴 시간 소요 작업 🟡 중간 시스템 응답성 저하 안전한 로깅 방법
- DbgPrint: 항상 안전, 디버거 연결 시에만 출력
- ETW (Event Tracing for Windows): 커널 모드에서 권장되는 방식
- 비동기 큐 + 워커 스레드: 자체 구현 시 사용
- WPP (Windows software trace PreProcessor): MS 권장 방식
결론
커널 드라이버, 특히 파일 시스템 미니필터 개발 시 콜백 함수 내에서 파일 I/O를 수행하면 데드락이 발생할 수 있습니다. 로깅이 필요한 경우 DbgPrint를 사용하거나, 비동기 큐 방식으로 구현해야 합니다.
핵심: 파일 시스템 콜백 = 파일 I/O 금지 구역
참고 자료
반응형'윈도우 > 커널 덤프분석' 카테고리의 다른 글