Vista와 Windows 7 커널에 존재하는 버그고 확인해 보니 Windows 8 커널에서는 수정되었다.
어떤 볼륨이 NTFS로 포맷되어 있는 경우 마운트되면서 커널이 볼륨 디바이스의 레퍼런스 카운트를 하나 올리고 내리지 않아서 발생하는 문제다. 레퍼런스 카운트가 남아 있으므로 드라이버가 내려가지 않는다. -_-;;;
NTFS 파일시스템 관련 초기화를 하는 중에 TransactionManager를 생성하는데 여기에 버그가 존재한다.
(Vista부터 Transactional NTFS(Txf)가 도입되었는데 이와 관련된 초기화 작업으로 보인다.)
문제의 원인이 되는 콜스택은 아래와 같다.
마운트가 완료된 직후 볼륨을 초기화하는 과정이다.
; 호출하고 dereference하지 않는 버그가 있다
nt!TmpIsClusteredTransactionManager 함수의 내용을 들여다 보면 IoGetAttachedDeviceReference를 호출하고 나서 ObDereferenceObject를 호출하지 않는 것을 확인할 수 있다.
kd> uf nt!TmpIsClusteredTransactionManager
nt!TmpIsClusteredTransactionManager:
82d9e5d3 8bff mov edi,edi
82d9e5d5 55 push ebp
82d9e5d6 8bec mov ebp,esp
82d9e5d8 83ec20 sub esp,20h
...
82d9e5f1 8b4508 mov eax,dword ptr [ebp+8]
82d9e5f4 ff7004 push dword ptr [eax+4]
82d9e5f7 e8bac9f1ff call nt!IoGetAttachedDeviceReference (82cbafb6)
...
nt!TmpIsClusteredTransactionManager+0x5d:
82d9e630 8bd0 mov edx,eax
82d9e632 8bce mov ecx,esi
82d9e634 e81fdee9ff call nt!IofCallDriver (82c3c458)
...
nt!TmpIsClusteredTransactionManager+0xbd:
82d9e690 5e pop esi
82d9e691 5b pop ebx
82d9e692 c9 leave
82d9e693 c20800 ret 8
함수가 끝나도 ObDereferenceObject를 호출하는 부분은 없다.
이 함수가 어떻게 볼륨 디바이스에 영향을 미치는지 이해하기 위해 볼륨 디바이스 스택을 확인해 본다.
일반적인 볼륨 디바이스 스택은 아래와 같다.
레퍼런스 카운트가 올라간 디바이스 오브젝트는 HarddiskVolume1의 854d400일까?
아니다.
HarddiskVolume1에 대한 마운트가 일어나면서 이 녀석에 대해 Ntfs!TxfInitializeVolume가 발생하지만 nt!TmpIsClusteredTransactionManager가 IoGetAttachedDeviceReference를 호출하기 때문에 최상위 스택의 852c6020의 레퍼런스 카운트가 올라간다.
따라서 HarddiskVolume1 디바이스 오브젝트는 잘 사라지고 \Driver\volmgr도 잘 언로드 될 수 있다.
하지만 디바이스 오브젝트 852c6020은 레퍼런스 카운트가 1 남기 때문에 Delete해도 DELETE_PENDING으로 남아 있고 \Driver\volsnap도 영원히 내려가지 않는다.
OS에서 volsnap은 원래 내리지 않기 때문에 별다른 문제없이 잘 사용하고 있었나 보다.
혹시 자기만의 볼륨 디바이스를 만들다가 드라이버가 내려가지 않는 현상이 발생한다면...
nt!TmpIsClusteredTransactionManager에 의한 레퍼런스 카운트 문제가 아닌지 확인해 봐야 한다.