ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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  ← 로그 파일 락 대기
                                              └─ 🔒 데드락!

    문제의 핵심

    1. PreAcquireForSectionSync 콜백은 파일 섹션 매핑 전에 호출됨
    2. 이 콜백 내에서 파일 백업(RTBackupFile) 수행
    3. 백업 완료 후 KLOG_DEBUG로 로그 기록 시도
    4. KLog는 내부적으로 로그 파일에 쓰기 수행
    5. 로그 파일 쓰기를 위해 ExAcquireResourceExclusiveLite로 리소스 락 획득 시도
    6. 이미 파일 시스템 콜백 체인 내에 있으므로 리소스 획득 불가 → 무한 대기

    문제 코드

    // 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
    긴 시간 소요 작업 🟡 중간 시스템 응답성 저하

    안전한 로깅 방법

    1. DbgPrint: 항상 안전, 디버거 연결 시에만 출력
    2. ETW (Event Tracing for Windows): 커널 모드에서 권장되는 방식
    3. 비동기 큐 + 워커 스레드: 자체 구현 시 사용
    4. WPP (Windows software trace PreProcessor): MS 권장 방식

    결론

    커널 드라이버, 특히 파일 시스템 미니필터 개발 시 콜백 함수 내에서 파일 I/O를 수행하면 데드락이 발생할 수 있습니다. 로깅이 필요한 경우 DbgPrint를 사용하거나, 비동기 큐 방식으로 구현해야 합니다.

    핵심: 파일 시스템 콜백 = 파일 I/O 금지 구역


    참고 자료

    반응형

    댓글

Designed by Tistory.