Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 - 두 번째 이야기

지난번 덤프 분석을,

windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례
; https://www.sysnet.pe.kr/2/0/12058

사전 준비도 되었으니,

windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례
; http://sysnet.pe.kr/2/0/12058

windbg/Visual Studio - HeapFree x86의 동작 분석
; http://sysnet.pe.kr/2/0/12059

windbg/Visual Studio - HeapFree x64의 동작 분석
; http://sysnet.pe.kr/2/0/12060

다시 한번 가보겠습니다. ^^ 우선, callstack의 Finalizer에서 예외가 발생했던(OraOps19.dll!00007ffcf0628a29로 시작했던) 덤프를 먼저 분석할 텐데 Visual Studio로 해당 덤프를 열어 "Debug with Mixed"로,

debug_mixed_in_vs_1.png

시작하면 곧바로 다음과 같이 crash 당시에 발생한 예외를 인식해 창을 띄워줍니다.

debug_mixed_in_vs_2.png

Unhandled exception at 0x00007FFD05D882D3 (ntdll.dll) in dump4580.dmp: 0xC0000374: A heap has been corrupted (parameters: 0x00007FFD05DDF6B0).


오호~~~ "windbg/Visual Studio - HeapFree x64의 동작 분석" 글에서 설명했던 바로 그 "parameters" 정보입니다. 따라서 해당 주소를 메모리 창(Ctrl + Alt + M, 1)에서 보면,

vs_heap_free_3.png

0x00007FFD05DDF6B0  000006e000000002 
0x00007FFD05DDF6B8  0000000000000004 
0x00007FFD05DDF6C0  0000014e8cba0000 // hHandle
0x00007FFD05DDF6C8  00000152e6608b40 // (pVoid - 0x10) 위치
0x00007FFD05DDF6D0  0000000000000000 
0x00007FFD05DDF6D8  0000000000000000 
0x00007FFD05DDF6E0  0000000000000000 
0x00007FFD05DDF6E8  00000152e66045e0 
0x00007FFD05DDF6F0  00000152e660c5f0 
0x00007FFD05DDF6F8  0000000000000000 
0x00007FFD05DDF700  0000000000000000 

감각적으로 ^^ HeapFree로 해제하려는 대상 Heap Handle과 메모리 위치를 알 수 있습니다. 사실 지난 글에서는 9번째 슬롯에 있는 값이 "pVoid - 0x10" 위치였는데 이번에는 4번째로 바뀌었습니다. (이런 건 OS 버전 또는 Patch의 차이일 수 있으니 센스 있게 낚으시면 됩니다. ^^)

참고로, (HeapCreate로 생성된) hHandle의 경우 운영체제에서 메모리 할당 시 Page 크기에 정렬되므로 마지막 4바이트가 "0000"으로 끝나는 것을 보고 그나마 쉽게 알아볼 수 있습니다. 또는, 더 확실하게 hHandle을 확정하고 싶다면 역시 지난 글에서 보여준 코드에 따라,

00007FFDF6EFFBCF 49 8B F8             mov         rdi,r8    // rdi == r8 == pVoid1
00007FFDF6EFFBD2 8B F2                mov         esi,edx   // esi == edx == 0
00007FFDF6EFFBD4 48 8B D9             mov         rbx,rcx   // rbx == rcx == hHandle
00007FFDF6EFFBD7 4D 85 C0             test        r8,r8  
00007FFDF6EFFBDA 74 52                je          RtlFreeHeap+6Eh (07FFDF6EFFC2Eh)  
00007FFDF6EFFBDC 48 85 C9             test        rcx,rcx  
00007FFDF6EFFBDF 0F 84 29 50 07 00    je          memset+11C4Eh (07FFDF6F74C0Eh)  
00007FFDF6EFFBE5 81 7B 10 EE DD EE DD cmp         dword ptr [rbx+10h],0DDEEDDEEh // dword([rbx + 10h]) == 0xffeeffee

[hHandle + 0x10] 위치의 (SegmentSignature라고 불리는) dword 값이 ffeeffee 또는 ddeeddee인 것을 보고 확인하면 됩니다.

0x0000014E8CBA0000  0000000000000000 
0x0000014E8CBA0008  0100eba53f7de08a 
0x0000014E8CBA0010  00000002ffeeffee // hHandle + 0x10 위치
0x0000014E8CBA0018  0000014e8d650018 
0x0000014E8CBA0020  0000014e8cba0120

또한, (pVoid - 0x10) 위치 값을 더 신뢰하고 싶다면 역시 지난 글에서 call stack의 프레임 문맥 중 레지스터에 저장된 정보가 있었으므로,

[ntdll.dll!RtlpLogHeapFailure()]
rbx == 0000014E8CBA0000 == hHandle
rsi == rdi == 00000152E6608B40 == (pVoid - 0x10)

[ntdll.dll!RtlFreeHeap()]
r14 == 0000014E8CBA0000 == hHandle
rsi == 00000152E6608B40 == (pVoid - 0x10)
rdi == 00000152E6608B50 == pVoid

확률을 높이는데 참고하시면 됩니다. 끝입니다, 이제 다음과 같이 해당 주소로 메모리 창을 통해 살펴 보면 헤더가 "8b00000000000000"으로 깨져 있는 것이 보이고, 이후 공백 문자로 시작하는 SQL 쿼리가 보입니다. 이와 함께 문자열을 좀 더 쉽게 식별하기 위해 "Watch" 창에 "(char *)...address..."를 입력하면 "Text Visualizer"를 통해 완전한 문자열을 구할 수 있습니다. (보는 바와 같이 SQL 쿼리가 저장되어 있습니다.)

debug_mixed_in_vs_3.png

분석은 여기까지입니다. 도대체 누가 어디서 8b00000000000000 값을 CoTaskMemAlloc으로 할당받은 메모리의 헤더에 덮어썼는지는 (snapshot에 불과한 메모리 덤프로는) 알 수 없습니다.




그다음 Marshal.FreeCoTaskMem callstack을 가진 두 번째 덤프도 한 번 볼까요? ^^ 역시 Visual Studio로 열면 다음과 같은 예외 메시지의 창이 뜹니다.

Unhandled exception at 0x00007FFD05D882D3 (ntdll.dll) in dump496.dmp: 0xC0000374: A heap has been corrupted (parameters: 0x00007FFD05DDF6B0). occurred


이젠 뭐 익숙해졌으니 ^^ 0x00007FFD05DDF6B0 값을 보면,

0x00007FFD05DDF6B0  000006e000000002 
0x00007FFD05DDF6B8  0000000000000004 
0x00007FFD05DDF6C0  00000286f28f0000 // hHandle
0x00007FFD05DDF6C8  0000028b412d3be0 // pVoid - 0x10
0x00007FFD05DDF6D0  0000000000000000 
0x00007FFD05DDF6D8  0000000000000000 
0x00007FFD05DDF6E0  0000000000000000 
0x00007FFD05DDF6E8  0000028b412cc9d0 
0x00007FFD05DDF6F0  0000028b412d49e0 

0000028b412d3be0 주솟값을 구했고 이 위치의 메모리 내용을 살펴 보니,

0000028b412d3be0 0000000000000000
0000028b412d3be8 8b00000000000000 == header 깨짐
0000028b412d3bf0 202020202020200a == pVoid 위치
0000028b412d3bf8 6c65732020202020
0000028b412d3c00 44492e2020202020

오호... 놀랍군요. ^^ 헤더를 깨뜨린 바이트 패턴(8b00000000000000)도 동일하고, 심지어 해당 메모리에 담겼던 SQL 쿼리 문자열도 동일합니다.




이제 문제를 좀 더 특정하기 위해, Oracle.DataAccess.Client의 ExecuteReader 메서드를 보겠습니다. 다행히, FreeCoTaskMem 메서드가 호출되는 곳은 하나뿐이어서 쉽게 문제의 코드를 찾아낼 수 있습니다.

try
{
    bool flag3 = ptr != null && ptr->pNewCommandText != IntPtr.Zero;
    num4 = OpsSql.Prepare2(this.m_opsConCtx, ref this.m_opsErrCtx, ref this.m_opsSqlCtx, 
        ref this.m_opsDacCtx, ref this.m_pOpoSqlValCtx, flag3 ? null : this.m_pooledCmdText, ref zero2, ref ptr, num5);
}
catch (Exception ex)
{
        // ...[생략]...
}
finally
{
    this.m_executeScalar = false;
    if (zero2 != IntPtr.Zero)
    {
        try
        {
            Marshal.FreeCoTaskMem(zero2);
        }
        // ...[생략]...
    }
    // ...[생략]...
}

그러니까, 위의 코드를 정리해 보면 string 타입인 this.m_pooledCmdText 필드에 있는 SQL 쿼리 텍스트가 (Native 모듈인 OraOps11w.dll에서 export한 OpsSqlPrepare2 함수를 호출하는) OpsSql.Prepare2 내에서 CoTaskMemAlloc을 할당받고 네이티브 heap에 쿼리 문자열이 복사된 다음 그 메모리 위치를 zero2 변수에 반환해 주는 구조입니다.

일단, 여기까지의 내용을 보면... 분명히 Oracle 쪽의 ODAC에 문제가 있는 듯합니다. 왜냐하면, 할당받은 메모리가 저 사이에 동일한 패턴으로 다른 스레드에 의해 침범을 당한다거나... 하는 확률은 극히 낮다고 볼 수 있기 때문입니다.




해당 고객사도 Oracle 쪽에 이슈를 제기한 상황이라고 하니, ^^ 혹시나 후기가 들려오면 업데이트하겠습니다.

(2019-12-20 업데이트: 이 문제의 원인은 windbg - Marshal.FreeHGlobal에서 발생한 덤프 분석 사례 글에서 설명합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]





[최초 등록일: ]
[최종 수정일: 12/20/2019 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 




1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12089정성태12/23/2019571디버깅 기술: 146. gflags와 _CrtIsMemoryBlock을 이용한 Heap 메모리 손상 여부 체크
12088정성태12/23/2019464Linux: 28. Linux - 윈도우의 "Run as different user" 기능을 shell에서 실행하는 방법
12087정성태12/21/2019591디버깅 기술: 145. windbg/sos - Dictionary의 entries 배열 내용을 모두 덤프하는 방법 (do_hashtable.py)
12086정성태12/20/2019719디버깅 기술: 144. windbg - Marshal.FreeHGlobal에서 발생한 덤프 분석 사례
12085정성태12/20/2019602오류 유형: 586. iisreset - The data is invalid. (2147942413, 8007000d) 오류 발생 - 두 번째 이야기 [1]
12084정성태12/21/2019688디버깅 기술: 143. windbg/sos - Hashtable의 buckets 배열 내용을 모두 덤프하는 방법 (do_hashtable.py)
12083정성태12/17/2019994Linux: 27. linux - lldb를 이용한 .NET Core 응용 프로그램의 메모리 덤프 분석 방법 [2]
12082정성태12/17/2019742오류 유형: 585. lsof: WARNING: can't stat() fuse.gvfsd-fuse file system
12081정성태12/16/2019849개발 환경 구성: 465. 로컬 PC에서 개발 중인 ASP.NET Core 웹 응용 프로그램을 다른 PC에서도 접근하는 방법 [2]
12080정성태12/16/20191042.NET Framework: 870. C# - 프로세스의 모든 핸들을 열람
12079정성태12/13/2019784오류 유형: 584. 원격 데스크탑(rdp) 환경에서 다중 또는 고용량 파일 복사 시 "Unspecified error" 오류 발생
12078정성태12/13/20192000Linux: 26. .NET Core 응용 프로그램을 위한 메모리 덤프 방법 [1]
12077정성태12/13/2019897Linux: 25. 자주 실행할 명령어 또는 초기 환경을 "~/.bashrc" 파일에 등록
12076정성태12/17/2019982디버깅 기술: 142. Linux - lldb 환경에서 sos 확장 명령어를 이용한 닷넷 프로세스 디버깅 - 배포 방법에 따른 차이
12075정성태12/18/20191065디버깅 기술: 141. Linux - lldb 환경에서 sos 확장 명령어를 이용한 닷넷 프로세스 디버깅
12074정성태12/11/2019694디버깅 기술: 140. windbg/Visual Studio - 값이 변경된 경우를 위한 정지점(BP) 설정(Data Breakpoint)
12073정성태12/10/20191275Linux: 24. Linux/C# - 실행 파일이 아닌 스크립트 형식의 명령어를 Process.Start로 실행하는 방법
12072정성태12/9/2019499오류 유형: 583. iisreset 수행 시 "No such interface supported" 오류
12071정성태12/9/2019831오류 유형: 582. 리눅스 디스크 공간 부족 및 safemode 부팅 방법
12070정성태12/9/20191544오류 유형: 581. resize2fs: Bad magic number in super-block while trying to open /dev/.../root
12069정성태12/19/2019954디버깅 기술: 139. windbg - x64 덤프 분석 시 메서드의 인자 또는 로컬 변수의 값을 확인하는 방법
12068정성태11/28/20191610디버깅 기술: 138. windbg와 Win32 API로 알아보는 Windows Heap 정보 분석 [2]파일 다운로드2
12067정성태11/27/2019937디버깅 기술: 137. 실제 사례를 통해 Debug Diagnostics 도구가 생성한 닷넷 웹 응용 프로그램의 성능 장애 보고서 설명 [1]파일 다운로드1
12066정성태11/27/2019707디버깅 기술: 136. windbg - C# PInvoke 호출 시 마샬링을 담당하는 함수 분석 - OracleCommand.ExecuteReader에서 OpsSql.Prepare2 PInvoke 호출 분석
12065정성태11/25/2019813디버깅 기술: 135. windbg - C# PInvoke 호출 시 마샬링을 담당하는 함수 분석파일 다운로드1
12064정성태11/25/20191216오류 유형: 580. HTTP Error 500.0/500.33 - ANCM In-Process Handler Load Failure
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...