找回密码
 立即注册
首页 业界区 业界 记一次 .NET 某医联体管理系统 崩溃分析

记一次 .NET 某医联体管理系统 崩溃分析

慢秤 2025-11-12 18:40:02
一:背景

1. 讲故事

这段时间都在跑外卖,感觉好久都没写文章了,今天继续给大家带来一篇崩溃类的生产事故,这是微信上有位老朋友找到我的,让我帮忙看下为啥崩溃了,dump也在手,接下来就可以一顿分析。
二:崩溃分析

1. 为什么会崩溃

双击打开dump文件,会看到崩溃信息通览,参考如下:
  1. Executable search path is:
  2. Windows 10 Version 17763 MP (48 procs) Free x64
  3. Product: Server, suite: TerminalServer DataCenter SingleUserTS
  4. Edition build lab: 17763.1.amd64fre.rs5_release.180914-1434
  5. Debug session time: Fri Oct 31 17:38:42.000 2025 (UTC + 8:00)
  6. System Uptime: 14 days 2:42:29.643
  7. Process Uptime: 0 days 0:00:58.000
  8. ................................................................
  9. .......................................
  10. Loading unloaded module list
  11. .
  12. This dump file has an exception of interest stored in it.
  13. The stored exception information can be accessed via .ecxr.
  14. (5a74.6250): Unknown exception - code c0000374 (first/second chance not available)
  15. For analysis of this file, run !analyze -v
  16. ntdll!NtWaitForMultipleObjects+0x14:
  17. 00007ffe`57baf0e4 c3              ret
复制代码
从卦中看崩溃码是 c0000374,即 ntheap 损坏,哈哈,到这里一下子就把范围给缩小了。
2. 为什么ntheap 损坏

那为什么ntheap会损坏呢?可以使用 .ecxr 切到崩溃时的调用栈,观察崩溃行为。
  1. 0:032> .ecxr
  2. 0:032> k
  3.   *** Stack trace for last set context - .thread/.cxr resets it
  4. # Child-SP          RetAddr               Call Site
  5. 00 000000b4`8503ede0 00007ffe`57c0b313     ntdll!RtlReportFatalFailure+0x9
  6. 01 000000b4`8503ee30 00007ffe`57c13b9e     ntdll!RtlReportCriticalFailure+0x97
  7. 02 000000b4`8503ef20 00007ffe`57c13eaa     ntdll!RtlpHeapHandleError+0x12
  8. 03 000000b4`8503ef50 00007ffe`57bae109     ntdll!RtlpHpHeapHandleError+0x7a
  9. 04 000000b4`8503ef80 00007ffe`57bbbb0e     ntdll!RtlpLogHeapFailure+0x45
  10. 05 000000b4`8503efb0 00007ffe`17d17b3f     ntdll!RtlFreeHeap+0x9d3ce
  11. 06 000000b4`8503f050 00007ffe`541392af     AcLayers!NS_FaultTolerantHeap::APIHook_RtlFreeHeap+0x41f
  12. 07 000000b4`8503f0b0 00007ffe`3773b17e     KERNELBASE!LocalFree+0x2f
  13. 08 000000b4`8503f0f0 00007ffe`37661d12     mscorlib_ni+0x58b17e
  14. 09 000000b4`8503f1a0 00007ffd`e49fe127     mscorlib_ni!System.Runtime.InteropServices.Marshal.FreeHGlobal+0x22 [f:\dd\ndp\clr\src\BCL\system\runtime\interopservices\marshal.cs @ 1212]
  15. ...
  16. 0:032> !clrstack
  17. OS Thread Id: 0x6250 (32)
  18.         Child SP               IP Call Site
  19. 000000b48503f118 00007ffe57baf0e4 [InlinedCallFrame: 000000b48503f118] Microsoft.Win32.Win32Native.LocalFree(IntPtr)
  20. 000000b48503f118 00007ffe3773b17e [InlinedCallFrame: 000000b48503f118] Microsoft.Win32.Win32Native.LocalFree(IntPtr)
  21. 000000b48503f0f0 00007ffe3773b17e DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr)
  22. 000000b48503f1a0 00007ffe37661d12 System.Runtime.InteropServices.Marshal.FreeHGlobal(IntPtr) [f:\dd\ndp\clr\src\BCL\system\runtime\interopservices\marshal.cs @ 1212]
  23. 000000b48503f1e0 00007ffde49fe127 b.B+A.MoveNext()
  24. 000000b48503f240 00007ffe376b3423 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 954]
  25. 000000b48503f310 00007ffe376b32b4 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 902]
  26. ...
  27. 000000b48503f5c0 00007ffde49fb04e DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int32, Int32, Int64)
复制代码
从卦中可以清晰的看到是 b.B+A.MoveNext 方法中调用了 FreeHGlobal 导致的NTHeap崩溃,如果你经验比较足的话,看到这个 FreeHGlobal 就应该想到 double free 问题,这是一个经典的问题。
3. 何为 double free

双释放即对一个 block 块进行二次释放,windows 的 RtlFreeHeap 方法会在业务逻辑中对这种情况直接判为异常,接下来你或许想知道这个 block 的地址是什么?这个可以用 !heap -s 观察,参考代码如下:
  1. 0:032> !heap -s
  2. ************************************************************************************************************************
  3.                                               NT HEAP STATS BELOW
  4. ************************************************************************************************************************
  5. Details:
  6. Heap address:  0000028c75bb0000
  7. Error address: 0000028c786018a0
  8. Error type: HEAP_FAILURE_BLOCK_NOT_BUSY
  9. Details:    The caller performed an operation (such as a free
  10.             or a size check) that is illegal on a free block.
  11. Follow-up:  Check the error's stack trace to find the culprit.
  12. Stack trace:
  13. Stack trace at 0x00007ffe57c72848
  14.     00007ffe57bae109: ntdll!RtlpLogHeapFailure+0x45
  15.     00007ffe57bbbb0e: ntdll!RtlFreeHeap+0x9d3ce
  16.     00007ffe17d17b3f: AcLayers!NS_FaultTolerantHeap::APIHook_RtlFreeHeap+0x41f
  17.     00007ffe541392af: KERNELBASE!LocalFree+0x2f
  18.     00007ffe3773b17e: mscorlib_ni+0x58b17e
  19.     00007ffe37661d12: mscorlib_ni!System.Runtime.InteropServices.Marshal.FreeHGlobal+0x22
  20.     00007ffde49fe127: +0xe49fe127
  21. LFH Key                   : 0x765363a7204cf973
  22. Termination on corruption : ENABLED
  23.           Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast
  24.                             (k)     (k)    (k)     (k) length      blocks cont. heap
  25. -------------------------------------------------------------------------------------
  26. 0000028c75bb0000 00000002   17920   9256  16364   2120   214     5    1      a   LFH
  27.     External fragmentation  23 % (214 free blocks)
  28. 0000028c75b40000 00008000      64      4     64      2     1     1    0      0      
  29. 0000028c75de0000 00001002    2636    132   1080     20     5     2    0      0   LFH
  30. 0000028c76190000 00001002    4680   2268   3124   1420    40     3    0      0   LFH
  31.     External fragmentation  62 % (40 free blocks)
  32. 0000028c76130000 00001002    2636    472   1080      5    27     2    0      0   LFH
  33. 0000028c767f0000 00041002      60      8     60      5     1     1    0      0      
  34. 0000028c77020000 00041002      60     16     60      2     2     1    0      0      
  35. -------------------------------------------------------------------------------------
复制代码
从卦中可以看到 Heap address:  0000028c75bb0000 即为 block 地址,接下来使用 !heap -x 0000028c786018a0 观察这个 block 块的状态,可以看到此时确实是 free 的。
  1. 0:032> !heap -x 0000028c786018a0
  2. Entry             User              Heap              Segment               Size  PrevSize  Unused    Flags
  3. -------------------------------------------------------------------------------------------------------------
  4. 0000028c786018a0  0000028c786018b0  0000028c75bb0000  0000028c785c80d0        e0      -            0  LFH;free
复制代码
到这里问题的成因我们是完全搞清楚了,接下来就是反推问题代码的时候了。
4. 问题代码在哪里

应该有朋友知道问题是在 b.B+A.MoveNext() 方法中,从名字上看这个项目应该是混淆的,有点搞哈。。。得要费点眼力,截图如下:
1.png

从卦中的 IntPtr intPtr = Interlocked.Exchange(ref b.A, IntPtr.Zero); 来看,这个 intPtr 是一个类级别变量,看样子是多个方法在操控类级别变量时没有合理的控制好,为了一探究竟,再次分析源代码,果然是的,截图如下:
2.png

到这里就真相大白了,让朋友修改源码自己控制好这个变量。
三:总结

这次生产事故是一个比较经典的 doublefree 问题,没接触过的话可能还是需要走一些弯路的,像我们这种老江湖,看到一二个特征这个问题就经注定解开!
3.jpeg


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册