ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Windows Minifilter 드라이버에서 PreAcquireForSectionSync와 FLT_PREOP_PENDING 충돌로 인한 BSOD 분석
    윈도우/커널 덤프분석 2025. 12. 15. 10:33
    반응형

    개요

    Windows 커널 모드 미니필터 드라이버 개발 중 RESOURCE_NOT_OWNED (0xE3) BSOD가 발생했습니다. 이 오류는 PreAcquireForSectionSync 콜백에서 FLT_PREOP_PENDING을 반환했을 때 발생하는 리소스 소유권 문제입니다.

    본 포스트에서는 크래시 덤프 분석을 통해 원인을 파악하고, 섹션 생성 콜백에서의 올바른 I/O 처리 방법을 설명합니다.


    문제 상황

    증상

    • 파일 시스템 필터 드라이버 로드 후 특정 파일 작업 시 시스템 크래시
    • Bugcheck Code: 0xE3 (RESOURCE_NOT_OWNED)
    • 발생 시점: 메모리 매핑(MMAP) 파일 접근 시

    크래시 덤프 분석

    BUGCHECK_CODE:  e3
    
    BUGCHECK_P1: ffffa68c645613c0   (Address of resource)
    BUGCHECK_P2: ffffa68c653f3080   (Address of thread)
    BUGCHECK_P3: 0000000000000000   (Address of owner table)
    BUGCHECK_P4: 0000000000000002
    
    PROCESS_NAME:  Dbgview.exe
    
    FAILURE_BUCKET_ID:  0xE3_nt!ExpReleaseResourceSharedForThreadLite

    콜 스택 분석

    STACK_TEXT:
    fffffe02`6bb385c8 fffff804`5b057e03 : nt!KeBugCheckEx
    fffffe02`6bb385d0 fffff804`5aecbbf3 : nt!ExpReleaseResourceSharedForThreadLite+0x18c133
    fffffe02`6bb38690 fffff804`5e2a3c7d : nt!ExReleaseResourceLite+0xf3
    fffffe02`6bb386f0 fffff804`5b255256 : Ntfs!NtfsReleaseForCreateSection+0x4d
    fffffe02`6bb38720 fffff804`5b2554fb : nt!FsRtlReleaseFile+0x156
    fffffe02`6bb389e0 fffff804`5b25474d : nt!MiShareExistingControlArea+0x7f
    fffffe02`6bb38a10 fffff804`5b252e94 : nt!MiCreateImageOrDataSection+0x1ad
    fffffe02`6bb38b00 fffff804`5b254cc7 : nt!MiCreateSection+0xf4
    fffffe02`6bb38c80 fffff804`5b254eac : nt!MiCreateSectionCommon+0x207
    fffffe02`6bb38d60 fffff804`5b011505 : nt!NtCreateSection+0x5c
    fffffe02`6bb38dd0 00007ff9`fa40dee4 : nt!KiSystemServiceCopyEnd+0x25

    핵심 포인트:

    • Ntfs!NtfsReleaseForCreateSection - NTFS가 섹션 생성을 위해 획득한 리소스를 해제하려 함
    • nt!MiCreateSection - 메모리 매핑 섹션 생성 중
    • 스레드가 소유하지 않은 리소스를 해제하려고 시도하여 BSOD 발생

    원인 분석

    문제의 코드

    PreAcquireForSectionSync 콜백에서 비동기 백업을 위해 FLT_PREOP_PENDING을 반환한 것이 문제였습니다:

    FLT_PREOP_CALLBACK_STATUS
    RTCoreD_PreAcquireForSectionSync(
        _Inout_ PFLT_CALLBACK_DATA Data,
        _In_ PCFLT_RELATED_OBJECTS FltObjects,
        _Flt_CompletionContext_Outptr_ PVOID *CompletionContext
        )
    {
        // ... 생략 ...
    
        // [문제 코드] 비동기 백업 큐에 추가하고 PENDING 반환
        NTSTATUS statusQ = RTQueueBackupRequest(
            Data,
            FltObjects,
            hEffectivePid,
            &pNameInfo->Name,
            NULL,
            RT_OP_MODIFY,
            FALSE
        );
    
        if (NT_SUCCESS(statusQ)) {
            FltReleaseContext(pStreamCtx);
            FltReleaseFileNameInformation(pNameInfo);
            return FLT_PREOP_PENDING;  // ★ 이것이 문제!
        }
    
        // ...
    }

    왜 RESOURCE_NOT_OWNED가 발생하는가?

    PreAcquireForSectionSync는 파일 시스템이 섹션 생성을 위해 파일 리소스 락(ERESOURCE)을 획득하기 직전에 호출됩니다.

    단계 정상 흐름 PENDING 반환 시
    1 PreAcquireForSectionSync 호출 PreAcquireForSectionSync 호출
    2 콜백에서 SUCCESS 반환 콜백에서 PENDING 반환
    3 NTFS가 리소스 락 획득 I/O가 보류됨 (락 획득 안 됨)
    4 섹션 생성 비동기 워커에서 I/O 완료 처리
    5 NTFS가 리소스 락 해제 NTFS가 리소스 락 해제 시도 → BSOD!

    문제의 핵심:

    • FLT_PREOP_PENDING 반환 시, Filter Manager는 I/O를 보류 상태로 만듦
    • 하지만 NTFS는 이미 리소스 락을 획득했다고 가정하고 NtfsReleaseForCreateSection에서 해제 시도
    • 소유하지 않은 리소스를 해제하려 하여 BSOD 발생

    PreAcquireForSectionSync의 특수성

    콜백 종류 FLT_PREOP_PENDING 지원 이유
    PreCreate O 일반적인 IRP 기반 I/O
    PreWrite O 일반적인 IRP 기반 I/O
    PreCleanup △ (제한적) 파일 닫힘 직전, 복잡함
    PreAcquireForSectionSync X 파일 시스템 리소스 락과 연동
    PreRead O 일반적인 IRP 기반 I/O

    PreAcquireForSectionSync는 메모리 매니저와 파일 시스템 간의 동기화를 위한 특수 콜백으로, PENDING을 지원하지 않습니다.


    해결 방법

    수정된 코드

    FLT_PREOP_PENDING 대신 Fire-and-Forget 방식의 RTQueueDeferredBackup을 사용합니다:

    FLT_PREOP_CALLBACK_STATUS
    RTCoreD_PreAcquireForSectionSync(
        _Inout_ PFLT_CALLBACK_DATA Data,
        _In_ PCFLT_RELATED_OBJECTS FltObjects,
        _Flt_CompletionContext_Outptr_ PVOID *CompletionContext
        )
    {
        // ... 생략 ...
    
        if (bFirstModify && RT_DEFAULT_BACKUP_ENABLE &&
            pStreamCtx->BackupDone == FALSE &&
            (gRollbackInProgress == 0)) {
            //
            // [수정] RTQueueDeferredBackup으로 비동기 백업 요청
            // PreAcquireForSectionSync에서는 PENDING 불가하므로 Deferred 방식 사용
            // 워커가 FltCreateFileEx로 파일을 다시 열어 백업 수행
            //
            NTSTATUS statusQ = RTQueueDeferredBackup(
                FltObjects->Filter,
                FltObjects->Instance,
                hEffectivePid,
                &pNameInfo->Name,
                NULL,
                RT_OP_MODIFY
            );
    
            if (NT_SUCCESS(statusQ)) {
                pStreamCtx->BackupPending = TRUE;
                DbgPrint(RT_LOG_PREFIX "MMAP: Deferred backup QUEUED pid=%lu file=%wZ\n",
                         (ULONG)(ULONG_PTR)hPid, &pNameInfo->Name);
            }
        }
    
        // ... 정상적으로 SUCCESS 반환 ...
        FltReleaseContext(pStreamCtx);
        FltReleaseFileNameInformation(pNameInfo);
        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    RTQueueBackupRequest vs RTQueueDeferredBackup 비교

    특성 RTQueueBackupRequest RTQueueDeferredBackup
    반환값 FLT_PREOP_PENDING 필요 즉시 반환 (Fire-and-Forget)
    I/O 보류 O (백업 완료까지 원본 I/O 대기) X (원본 I/O 즉시 진행)
    원본 파일 보존 보장됨 보장 안 됨 (이미 변경될 수 있음)
    사용 가능 콜백 PreCreate, PreWrite 등 모든 콜백
    PreAcquireForSectionSync 사용 불가 사용 가능

    설계 고려사항

    Deferred 백업의 한계

    RTQueueDeferredBackup은 I/O를 보류하지 않으므로, 백업 시점에 파일이 이미 변경되었을 수 있습니다:

    [타임라인]
    1. PreAcquireForSectionSync 호출
    2. RTQueueDeferredBackup으로 백업 요청 큐 추가
    3. FLT_PREOP_SUCCESS 반환 → I/O 진행
    4. MMAP 쓰기로 파일 변경됨 ★
    5. 백업 워커가 파일 열어서 백업 → 이미 변경된 파일 백업됨

    대안적 접근법

    MMAP 케이스에서 원본 파일을 보존하려면:

    1. PreWrite에서 처리: MMAP 쓰기는 결국 PreWrite를 통해 디스크에 기록됨
    2. PostCreate에서 선제적 백업: 파일이 쓰기 모드로 열릴 때 미리 백업
    3. Copy-on-Write 전략: 페이지 폴트 핸들러 수준에서 처리 (복잡함)

    정리

    콜백별 FLT_PREOP_PENDING 지원 여부

    콜백 PENDING 지원 권장 백업 방식
    PreCreate O RTQueueBackupRequest
    PreWrite O RTQueueBackupRequest
    PreSetInformation O RTQueueBackupRequest
    PreCleanup RTQueueDeferredBackup
    PreAcquireForSectionSync X RTQueueDeferredBackup
    PostCreate X (PostOp) RTQueueDeferredBackup

    핵심 교훈

    1. PreAcquireForSectionSync에서 FLT_PREOP_PENDING을 절대 사용하지 마세요
      • 파일 시스템 리소스 락과 연동되어 RESOURCE_NOT_OWNED BSOD 발생
    2. 콜백의 특성을 이해하세요
      • 모든 Pre 콜백이 PENDING을 지원하는 것은 아님
      • 특히 동기화 관련 콜백(AcquireForSection, AcquireForModWrite 등)은 주의
    3. 크래시 덤프의 콜 스택을 분석하세요
      • Ntfs!NtfsReleaseForCreateSection이 보이면 섹션 관련 리소스 문제
      • ExpReleaseResourceSharedForThreadLite는 ERESOURCE 소유권 문제

    참고 자료

    반응형

    댓글

Designed by Tistory.