作者:张佩】 【原文:http://www.yiiyee.cn/Blog/0x19-1/

内核在管理内存的时候,为了提高内存使用效率,对于小片内存的申请(小于一个PAGE大小),都是通过内存池来操作的。系统里面有两种不同的内存池:分页内存池和非分页内存池。这二者的区别是很明显的:分页内存池所使用的内存页面,随时有可能被分页出去;而非分页内存池所使用的虚拟页面,总是留驻在物理内存中。

对于运行在高中断级别(>=DISPATCH_LEVEL 2)上的代码,它使用的内存只应该是从非分页内存池中申请的。因为系统无法在这些中断级上处理页错误。

除了上面的区别外,系统对两个内存池的管理是极类似的。那么,系统是怎么管理这些内存池的呢?当请求者申请内存的时候,池管理器首先会检查自己当前的储备内存,以确认能否满足申请者的要求,如果可以,则从储备内存中进行分配;否则就重新申请一个新的内存页,并从新内存页中划出一块给申请者,剩下的留作储备内存继续使用。

池管理器把一个页面分成若干个小块(可称为:Entry),第一个Entry是从页的起始地址开始的,而最后一个Entry则恰好到达页尾。每个Entry都有一个描述符,管理器通过这些描述符有效管理这些无数的小块内存。描述符被定义为POOL_HEADER,下面是这个结构体在32位Win7系统上的定义:

  1. 0: kd> dt nt!_pool_header
  2. +0x000 PreviousSize : Pos 0, 9 Bits
  3. +0x000 PoolIndex : Pos 9, 7 Bits
  4. +0x002 BlockSize : Pos 0, 9 Bits
  5. +0x002 PoolType : Pos 9, 7 Bits
  6. //+0x000 Ulong1 : Uint4B
  7. +0x004 PoolTag : Uint4B
  8. //+0x004 AllocatorBackTraceIndex : Uint2B
  9. //+0x006 PoolTagHash : Uint2B

在64位系统上,这个结构体定义略有区别,但不影响本文描述。这个描述符位于每个Entry的头部,所以它的名字POOL_HEADER是名副其实的。里面包含5个成员变量,介绍如下:

  1. PreviouseSize:前一个Entry的长度,粒度为8字节,值1表示8字节。
  2. BlockSize:当前Entry的长度,粒度为8字节,故值1表示8字节长。
  3. PoolIndex:不详。
  4. PoolType:包含当前Entry的一些属性,比如当前块是Free还是Allocated。如果试图释放一个Free状态的内存块,就会出错。
  5. PoolTag:客户申请内存时设置的Tag值,这个Tag值可便于调试和分析。

PriviouseSize和BlockSize这两个变量很有趣,把它们两个结合在一起有很强大的作用,系统用它们来检查内存池页面的完整性。如果完整性被破坏,系统就会报第一个参数为0x20的BAD_POOL_CALLER(0x19)蓝屏。

这次拿到的dump文件就是这样的一个例子,运行自动分析命令:

  1. 0: kd> !analyze -v
  2. *******************************************************************
  3. * *
  4. * Bugcheck Analysis *
  5. * *
  6. *******************************************************************
  7.  
  8. BAD_POOL_HEADER (19)
  9. The pool is already corrupt at the time of the current request.
  10. This may or may not be due to the caller.
  11. The internal pool links must be walked to figure out a possible cause of
  12. the problem, and then special pool applied to the suspect tags or the driver
  13. verifier to a suspect driver.
  14. Arguments:
  15. Arg1: 00000020, a pool block header size is corrupt.
  16. Arg2: 8739ed50, The pool entry we were looking for within the page.
  17. Arg3: 8739ed88, The next pool entry.
  18. Arg4: 18070009, (reserved)
  19.  
  20. Debugging Details:
  21. -------------------------------------------------------
  22. BUGCHECK_STR: 0x19_20
  23.  
  24. POOL_ADDRESS: 8739ed50 Nonpaged pool
  25.  
  26. DEFAULT_BUCKET_ID: WIN7_DRIVER_FAULT
  27.  
  28. PROCESS_NAME: xxxusermode.exe
  29.  
  30. CURRENT_IRQL: 2
  31.  
  32. IRP_ADDRESS: 013e8e48
  33.  
  34. LAST_CONTROL_TRANSFER: from 82c8c588 to 82d2cc6b
  35.  
  36. STACK_TEXT:
  37. 99f8f8d8 82c8c588 8739ed58 00000000 b99f0218 nt!ExFreePoolWithTag+0x1b1
  38. 99f8f924 82c8403f 013e8e88 99f8f96c 99f8f964 nt!IopCompleteRequest+0xe6
  39. 99f8f974 82f3db64 00000000 b13e8e48 b13e8e48 nt!IopfCompleteRequest+0x3b4
  40. 99f8f9dc 95af0c5a 8a1c8978 86c97008 99f8f9fc nt!IovCompleteRequest+0x133
  41. 99f8f9ec 95aef48f 86c94180 b13e8e48 99f8fa14 ks!CKsFilter::DispatchDeviceIoControl+0x68
  42. 99f8f9fc 95adf0ba 86c94180 b13e8e48 b13e8e48 ks!KsDispatchIrp+0xb0
  43. 99f8fa14 82f3d6c3 86c94180 b13e8e48 8a98fa48 ks!CKsDevice::PassThroughIrp+0x46
  44. 99f8fa38 82c42bd5 00000000 b13e8e48 86c94180 nt!IovCallDriver+0x258
  45. 99f8fa4c 82e36bf9 8a98fa48 b13e8e48 b13e8fd8 nt!IofCallDriver+0x1b
  46. 99f8fa6c 82e39de2 86c94180 8a98fa48 00000000 nt!IopSynchronousServiceTail+0x1f8
  47. 99f8fb08 82e80764 86c94180 b13e8e48 00000000 nt!IopXxxControlFile+0x6aa
  48. 99f8fb3c 8932cc90 000003c0 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
  49. WARNING: Stack unwind information not available. Following frames may be wrong.
  50. 99f8fd04 82c498a6 000003c0 00000000 00000000 DgSafe+0x19c90
  51. 99f8fd04 77967094 000003c0 00000000 00000000 nt!KiSystemServicePostCall
  52. 0027ed44 00000000 00000000 00000000 00000000 0x77967094
  53.  
  54. STACK_COMMAND: kb
  55.  
  56. FOLLOWUP_IP:
  57. ks!CKsFilter::DispatchDeviceIoControl+68
  58. 95af0c5a 8bc7 mov eax,edi
  59.  
  60. SYMBOL_STACK_INDEX: 4
  61.  
  62. SYMBOL_NAME: ks!CKsFilter::DispatchDeviceIoControl+68
  63.  
  64. FOLLOWUP_NAME: MachineOwner
  65.  
  66. MODULE_NAME: ks
  67.  
  68. IMAGE_NAME: ks.sys
  69.  
  70. DEBUG_FLR_IMAGE_TIMESTAMP: 4ce799d9
  71.  
  72. FAILURE_BUCKET_ID: 0x19_20_VRF_ks!CKsFilter::DispatchDeviceIoControl+68
  73.  
  74. BUCKET_ID: 0x19_20_VRF_ks!CKsFilter::DispatchDeviceIoControl+68
  75.  
  76. Followup: MachineOwner
  77. ---------

注意几个地方:

  1. 蓝屏参数,里面明确指出了错误原因是系统发现有一个pool entry的长度不对,并指出了当前操作的entry(0x8739ed50)和它的下一个entry(0x8739ed88)。为什么同时给出下一个entry,还要下面继续分析。
  2. 当前进程名,看是否对应了客户程序。
  3. 调用栈的第0帧,函数是ExFreePoolWithTag。可以知道是在释放内存时出错的。

Windows的内核DDI在32位系统上是使用STDCALL调用协议的,就是通过栈传递参数。这样我们就能得到它的参数:

99f8f8d8 82c8c588 8739ed58 00000000 b99f0218 nt!ExFreePoolWithTag+0x1b1

还原一下,它的调用是这样的:

ExFreePoolWithTag (8739ed58, 0);

对照第一点的两个地址,它和当前操作Entry的地址是很近似的。相差了8个字节,正好是POOL_HEADER结构体的长度:

  1. 0: kd> dt nt!_pool_header 8739ed50
  2. +0x000 PreviousSize : 0y000001001 (0x9) // 9 * 8 = 0x48
  3. +0x000 PoolIndex : 0y0000000 (0)
  4. +0x002 BlockSize : 0y000000111 (0x7) // 7 * 8 = 0x38
  5. +0x002 PoolType : 0y0001100 (0xc)
  6. +0x000 Ulong1 : 0x18070009
  7. +0x004 PoolTag : 0x7070534b
  8. +0x004 AllocatorBackTraceIndex : 0x534b
  9. +0x006 PoolTagHash : 0x7070

问题出在哪里呢? 运行一下!pool命令:

  1. 0: kd> !pool 8739ed50
  2. Pool page 8739ed50 region is Nonpaged pool
  3. 8739e000 size: d0 previous size: 0 (Free) Ntfx
  4. 8739e0d0 size: 68 previous size: d0 (Allocated) FMsl
  5. 8739e138 size: 68 previous size: 68 (Allocated) EtwR (Protected)
  6. 8739e1a0 size: 68 previous size: 68 (Allocated) EtwR (Protected)
  7. 8739e208 size: 68 previous size: 68 (Allocated) Mdl
  8. 8739e270 size: 18 previous size: 68 (Allocated) MmSi
  9. 8739e288 size: 20 previous size: 18 (Allocated) USBB
  10. 8739e2a8 size: 68 previous size: 20 (Allocated) FMsl
  11. 8739e310 size: 10 previous size: 68 (Free) NSpg
  12. 8739e320 size: 48 previous size: 10 (Allocated) Vad
  13. 8739e368 size: 10 previous size: 48 (Free) NKBS
  14. 8739e378 size: 48 previous size: 10 (Allocated) Vad
  15. 8739e3c0 size: c8 previous size: 48 (Allocated) File (Protected)
  16. 8739e488 size: 1f8 previous size: c8 (Allocated) z...
  17. 8739e680 size: 68 previous size: 1f8 (Allocated) EtwR (Protected)
  18. 8739e6e8 size: 18 previous size: 68 (Allocated) MmSi
  19. 8739e700 size: 40 previous size: 18 (Allocated) Even (Protected)
  20. 8739e740 size: 2e8 previous size: 40 (Allocated) Thre (Protected)
  21. 8739ea28 size: 68 previous size: 2e8 (Allocated) FMsl
  22. 8739ea90 size: 68 previous size: 68 (Allocated) FMsl
  23. 8739eaf8 size: 18 previous size: 68 (Allocated) MmSi
  24. 8739eb10 size: 40 previous size: 18 (Allocated) SeTl
  25. 8739eb50 size: 40 previous size: 40 (Allocated) Even (Protected)
  26. 8739eb90 size: 40 previous size: 40 (Allocated) Even (Protected)
  27. 8739ebd0 size: 38 previous size: 40 (Allocated) AlIn
  28. 8739ec08 size: 50 previous size: 38 (Free) z...
  29. 8739ec58 size: 40 previous size: 50 (Allocated) MmIo
  30. 8739ec98 size: 28 previous size: 40 (Allocated) VadS
  31. 8739ecc0 size: 48 previous size: 28 (Allocated) Vad
  32. 8739ed08 size: 48 previous size: 48 (Allocated) Vad
  33. *8739ed50 size: 38 previous size: 48 (Free ) *KSpp
  34. Pooltag KSpp : irp system buffer property/method/event parameter
  35.  
  36. 8739ed88 doesn't look like a valid small pool allocation, checking to see
  37. if the entire page is actually part of a large page allocation...
  38.  
  39. 8739ed88 is not a valid large pool allocation, checking large session pool...
  40. 8739ed88 is freed (or corrupt) pool
  41. Bad allocation size @8739ed88, zero is invalid
  42.  
  43. ***
  44. *** An error (or corruption) in the pool was detected;
  45. *** Attempting to diagnose the problem.
  46. ***
  47. *** Use !poolval 8739e000 for more details.
  48.  
  49. Pool page [ 8739e000 ] is __inVALID.
  50.  
  51. Analyzing linked list...
  52. [ 8739ed50 --> 8739edf0 (size = 0xa0 bytes)]: Corrupt region
  53.  
  54. Scanning for single bit errors...
  55.  
  56. None found

!pool在处理任何一个地址的时候,都会跳到地址所在页的起始处开始分析。当前系统的页大小为4K,可算出0x8739ed50对应的页起始地址是0x8739e000,也就是第一条Entry的地址。纵观从第一个entry开始往下,可以看到size和previous size这两个相映成趣的值,恰好是一个“之”型网络:

之型结构从第一个Entry开始,它的previous size为0;前一个entry的size应该和第二个Entry的previous size相等,否则就不对;一直到最后一个entry,都必须把这个关系保持下去。

看看现在这个链表,大概很快就知道问题出在哪里了。当前处理的Entry和它后面一个Entry之间的关系断裂了。

  1. 0: kd> dt nt!_pool_header 8739ed88
  2. +0x000 PreviousSize : 0y000000000 (0)
  3. +0x000 PoolIndex : 0y0000000 (0)
  4. +0x002 BlockSize : 0y000000000 (0)
  5. +0x002 PoolType : 0y0000000 (0)
  6. +0x000 Ulong1 : 0
  7. +0x004 PoolTag : 0
  8. +0x004 AllocatorBackTraceIndex : 0
  9. +0x006 PoolTagHash : 0
  10.  
  11. 0: kd> db 8739ed88 L8
  12. 8739ed88 00 00 00 00 00 00 00 00

本该是POOL_HEADER的地方,内存已被清空为0。可能什么原因导致这个问题呢?最大的可能性,是驱动申请了一块地址为0x8739ed58d,大小为0x30的内存后,往内存中写入了超过0x30字节的内容,把后面本该属于下一个POOL_HEADER结构体的8个字节内容覆盖了。

对照这个分析进行调试,仔细观察出问题时的用户程序,发现用户程序发送了一个结构体给内核处理,但用户程序和内核定义对同一个结构体的定义不一致,内核所定义的 结构体比用户程序多出两个变量。最终问题得到了解决。

虫趣:BAD POOL CALLER (par1: 0x20)的更多相关文章

  1. Windows kernel pool 初探(2014.12)

    Windows kernel pool 1. 简介 Kernel pool类似于Windows用户层所使用Heap,其为内核组件提供系统资源.在系统初始化的时候,内存管理模块就创建了pool. 严格的 ...

  2. Improve Scalability With New Thread Pool APIs

    Pooled Threads Improve Scalability With New Thread Pool APIs Robert Saccone Portions of this article ...

  3. CLR thread pool

    Thread Pooling https://msdn.microsoft.com/en-us/library/windows/desktop/ms686756(v=vs.85).aspx Threa ...

  4. 共享内存 share pool (1):heap /extent /chunk/

    相关概念 CHUNK: Shared pool物理层面上由许多内存块组成,这些内在块称为chunk.但是chunk是大小不一的,在内存中一个chunk是连续的. EXTENT:由多个连续的chunk组 ...

  5. Pool:小对象缓存or复用

    对象复用 使用链表作为pool来保存要复用的对象. pool字段 obtain recycle 案例1 android.os.Message private static Message sPool; ...

  6. Erlang pool management -- Emysql pool

    从这篇开始,这一系列主要分析在开源社区中,Erlang 相关pool 的管理和使用. 在开源社区,Emysql 是Erlang 较为受欢迎的一个MySQL 驱动. Emysql 对pool 的管理和使 ...

  7. 启动SpringBoot web项目出现 Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3,....

    详细错误信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> ...

  8. 02-Nov-2017 07:11:56.475 信息 [http-nio-8080-exec-10] com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource. Initializing c3p0 pool...

    报错: 02-Nov-2017 07:11:56.475 信息 [http-nio-8080-exec-10] com.mchange.v2.c3p0.impl.AbstractPoolBackedD ...

  9. js callee,caller学习

    原文地址:js callee,caller学习 /* * caller 返回一个对函数的引用,该函数调用了当前函数. * 如果函数是由顶层调用的,那么 caller包含的就是 null . * 如果在 ...

随机推荐

  1. 直接读取修改exe文件

    1. 前言 配置器的编写有很多的方式,主要是直接修改原始的受控端的程序,有的方式是把受控端和配置信息都放到控制端程序的内部,在需要配置受控端的时候直接输入配置信息,生成受控端:也有的方式是在外部直接修 ...

  2. VMware下centos桥接模式静态ip配置

    声明:本文转载自http://blog.csdn.net/ltr15036900300/article/details/48828207,非原创. 一.配置虚拟机centos网络 备份网络文件 [ro ...

  3. sublime text 3 使用简介

    2014年1月22日 09:47:50 2用了一段时间感觉不错,就是自带的高亮显示匹配标签或者代码块儿时有点儿不清楚,所以一直是sublime 开PHP,notepad++开html 现在想只用一个编 ...

  4. thymeleaf:访问静态方法

    <p class="left tel" th:if="${#strings.startsWith(T(net.common.util.tool.common.Req ...

  5. 远程不能访问CentOS的tomcat 8080端口

    一般认为是CentOS的iptabls防火墙的问题,方法如下: ps -ef | grep tomcat ker 4399 1 6 21:46 pts/1 00:00:01 /usr/java/jdk ...

  6. google地图的url参数

    Google Maps Intents for Android The Google Maps app for Android exposes several intents that you can ...

  7. activiti主要组件解析

    Activiti内部实现中,各主要部件关系 对外,提供Service服务,它是无状态的. 这些Service包括: protected RepositoryService repositoryServ ...

  8. kafka脚本

    为了便于使用,kafka提供了比较强大的Tools,把经常需要使用的整理一下 开关kafka Server bin/kafka-server-start.sh config/server.proper ...

  9. 5 个 Laravel Eloquent 小技巧

    1. 快速生成 Model & Migration 这并不是一个很多人知道的小技巧,为文章生成 Model 和 Migration. $ php artisan make:migration ...

  10. 《高性能MySQL》学习笔记

    第1章 MySQL架构与历史 1.2 并发控制 MySQL在两个层面实现并发控制:服务器层与存储引擎层. 读锁和写锁: 在处理并发读或写时,可以通过实现一个由两种锁组成的系统来解决问题. 这两种锁通常 ...