
Heap analysis tooling for mempool
Preliminary note: we recommend you to use this as part of asatools but it can also be used standalone.
libmempool is a python script and GDB analysis tool to aid in the analysis
of mempool-related data structuress found inside various heaps on Cisco ASA
devices. Normally this information is embedded into a heap chunk, or is a
custom extension to some other common heap structure such as a dlmalloc
mstate structure.
Cisco uses the term mempool to describe regions of memory mapped for various purposes such as general allocations, DMA, etc. These regions typically contain their heap, such as dlmalloc. Allocations routines on these mempools are typically done using wrappers around the underyling heap allocator, and these wrappers inject mempool-specific metadata into the resulting allocations. We refer to this metadata as a mempool header or mh for short. Similarly we regularly will refer to a mempool as mp. The use of mh to describe a mempool header is also consistent with various strings found in the Cisco ASA lina binaries.
Although libmempool can be used as a stand-alone tool for analyzing some things related to mempool headers and data structures, it's greatest value comes from being used as a callback from other libraries such as libdlmalloc or libptmalloc.
It's worth noting that certain aspects of the mempool, such as the bins used to track in-use chunks, are embedded inside an dlmalloc 2.8.x mstate structure and follow the same bin sizing. This means almost inevitably you may to poke around at least the mstate encapsulating the mempool data using libdlmalloc.
libmempool has been tested with 32-bit / 64-bit Cisco ASA versions (both ASA5500-X series and GNS3) that use dlmalloc2.8 or glibc's ptmalloc2-based allocator. It has been tested on numerous ASA versions, including numerous 8.x.y and 9.x.y branches. However, it is entirely possible that it will break on some version.
To use libmempool stand-alone you simply need to import the libmempool.py
file into your project. This allows you to do some limited actions, like
register the mpcallback object, etc. This can be useful if you're doing
offline analysis of logged heap functionality.
To import into GDB, the script just requires GDB with python support. Although most modern GDB versions have moved towards python 3, some still expect 2.7. The script has been tested on both, but primarily development and testing is done with python 3.
(gdb) source libmempool_gdb.py
We separate most of the gdb-related logic out of libmempool.py into libmempool_gdb.py just to test abstracting things and so that you can easily use libmempool.py outside of GDB. This will likely change in the future, as we will eventually want to implement similar debug engine abstractions used by other heap analysis tools like libheap and shadow.
Although much of the value of libmempool comes from the mpcallback callback
function it exposes, there are a number of built-in GDB commands we can look
at.
(gdb) mphelp
[libmempool] mempool commands for gdb
[libmempool] mpheader -v -x <addr> : show chunk contents (-v for verbose, -x for data dump)
[libmempool] mpbinwalk [-v] [-p <addr>] <sz> : walk an mpbin and operate on each chunk in a bin
[libmempool] mpbin <addr> : determine to which bin an mp_header is associated to
[libmempool] mpmstate <addr> : display and cache a mempool mstate address
[libmempool] mphelp
Assuming we know the address of some mempool header, we can analyze it's data. Note this must be the address of the mempool header itself, and not the address of the core allocators chunk metadata. So, we can dump the contents as follows:
(gdb) mpheader 0x7fffbc1c1ca0
struct mp_header @ 0x7fffbc1c1ca0 {
mh_magic = 0xa11c0123
mh_len = 0x3
mh_refcount = 0x10000
mh_unused = 0x0
mh_fd_link = 0x7fffbc1c19e0 (OK)
mh_bk_link = 0x7ffff7ff7540 (-)
alloc_pc = 0x55555849e260 (-)
free_pc = 0x0 (-)
We can also dump the hex contents of the chunk using -x.
(gdb) mpheader -x 0x7fffbc1c1ca0
struct mp_header @ 0x7fffbc1c1ca0 {
mh_magic = 0xa11c0123
mh_len = 0x3
mh_refcount = 0x10000
mh_unused = 0x0
mh_fd_link = 0x7fffbc1c19e0 (OK)
mh_bk_link = 0x7ffff7ff7540 (-)
alloc_pc = 0x55555849e260 (-)
free_pc = 0x0 (-)
0x3 bytes of chunk data:
0x7fffbc1c1cd0: 0x55 0x04 0x03
Mempools have the concept of bins, which are doubley linked lists sized
the same as dlmalloc bins, but that are are used track inuse chunks rather
than free chunks. This is done for memory usage bookkeeping on Cisco devices.
Often times you might find a chunk that contains a mempool header, but you
don't yet know where the mstate structure on the heap lives. To get your
bearings, you can use the mpbin command, which will give you the address of
the mempool bin that an inuse chunk currently lives in. For instance:
(gdb) mpbin 0x7fffbc1c1ca0
[libmempool] Found bin start at 0x7ffff7ff7540
[libmempool] Cached new mp_mstate @ 0x7ffff7ff73c0
[libmempool] mp_smallbin[08] - sz: 0x00000040 cnt: 0x00d3, mh_fd_link: 0x7fffbc1c1ca0
This note only found the start of the mempool portion of the mstate, but also
cached it, and lists the specific bin that the chunk exists in. We could
optionally use the mpbinwalk command to list all 0xd3 chunks from this bin,
or only up to a specific number:
(gdb) mpbinwalk 0x40
[libmempool] mp_header @ 0x7ffff7ff7540 - mh_len: 0x00000000, alloc_pc: 0x00000000 [BIN HEAD]
[libmempool] mp_header @ 0x7fffbc1c1ca0 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[libmempool] mp_header @ 0x7fffbc1c19e0 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[libmempool] mp_header @ 0x7fffbc1c1750 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[libmempool] mp_header @ 0x7fffbc1bffa0 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[libmempool] mp_header @ 0x7fffbc1bff60 - mh_len: 0x00000003, alloc_pc: 0x5555584a1288
[libmempool] mp_header @ 0x7fffbc1c0050 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[...]
Now if we want to dump the whole mempool portion of the mstate structure (so
all of the bins and related stats) we can use the mpmstate command: