Skip to content

Commit

Permalink
Add min-size and max-size filter to heap chunks command (#1025)
Browse files Browse the repository at this point in the history
This change adds `--min-size` and `--max-size` filter to heap chunks
command.

With heap summary, we can quickly use `--min-size` and `--max-size`
filter to find some sample chunks for further analysis.
  • Loading branch information
r12f authored Dec 21, 2023
1 parent f0d2818 commit fbda021
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 11 deletions.
11 changes: 11 additions & 0 deletions docs/commands/heap.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ gef➤ heap chunks --summary

![heap-chunks-summary](https://i.imgur.com/3HTgtwX.png)

Heap chunk command also supports filtering chunks by their size. To do so, simply provide the
`--min-size` or `--max-size` argument:

```text
gef➤ heap chunks --min-size 16 --max-size 32
```

![heap-chunks-size-filter](https://i.imgur.com/AWuCvFK.png)

The range is inclusive, so the above command will display all chunks with size >=16 and <=32.

### `heap chunk` command

This command gives visual information of a Glibc malloc-ed chunked. Simply provide the address to
Expand Down
35 changes: 24 additions & 11 deletions gef.py
Original file line number Diff line number Diff line change
Expand Up @@ -6350,7 +6350,7 @@ class GlibcHeapChunksCommand(GenericCommand):
the base address of a different arena can be passed"""

_cmdline_ = "heap chunks"
_syntax_ = f"{_cmdline_} [-h] [--all] [--allow-unaligned] [--summary] [arena_address]"
_syntax_ = f"{_cmdline_} [-h] [--all] [--allow-unaligned] [--summary] [--min-size MIN_SIZE] [--max-size MAX_SIZE] [arena_address]"
_example_ = (f"\n{_cmdline_}"
f"\n{_cmdline_} 0x555555775000")

Expand All @@ -6359,24 +6359,24 @@ def __init__(self) -> None:
self["peek_nb_byte"] = (16, "Hexdump N first byte(s) inside the chunk data (0 to disable)")
return

@parse_arguments({"arena_address": ""}, {("--all", "-a"): True, "--allow-unaligned": True, ("--summary", "-s"): True})
@parse_arguments({"arena_address": ""}, {("--all", "-a"): True, "--allow-unaligned": True, "--min-size": 0, "--max-size": 0, ("--summary", "-s"): True})
@only_if_gdb_running
def do_invoke(self, _: List[str], **kwargs: Any) -> None:
args = kwargs["arguments"]
if args.all or not args.arena_address:
for arena in gef.heap.arenas:
self.dump_chunks_arena(arena, print_arena=args.all, allow_unaligned=args.allow_unaligned, summary=args.summary)
self.dump_chunks_arena(arena, print_arena=args.all, allow_unaligned=args.allow_unaligned, min_size=args.min_size, max_size=args.max_size, summary=args.summary)
if not args.all:
return
try:
arena_addr = parse_address(args.arena_address)
arena = GlibcArena(f"*{arena_addr:#x}")
self.dump_chunks_arena(arena, allow_unaligned=args.allow_unaligned, summary=args.summary)
self.dump_chunks_arena(arena, allow_unaligned=args.allow_unaligned, min_size=args.min_size, max_size=args.max_size, summary=args.summary)
except gdb.error:
err("Invalid arena")
return

def dump_chunks_arena(self, arena: GlibcArena, print_arena: bool = False, allow_unaligned: bool = False, summary: bool = False) -> None:
def dump_chunks_arena(self, arena: GlibcArena, print_arena: bool = False, allow_unaligned: bool = False, min_size: int = 0, max_size: int = 0, summary: bool = False) -> None:
heap_addr = arena.heap_addr(allow_unaligned=allow_unaligned)
if heap_addr is None:
err("Could not find heap for arena")
Expand All @@ -6385,31 +6385,35 @@ def dump_chunks_arena(self, arena: GlibcArena, print_arena: bool = False, allow_
gef_print(str(arena))
if arena.is_main_arena():
heap_end = arena.top + GlibcChunk(arena.top, from_base=True).size
self.dump_chunks_heap(heap_addr, heap_end, arena, allow_unaligned=allow_unaligned, summary=summary)
self.dump_chunks_heap(heap_addr, heap_end, arena, allow_unaligned=allow_unaligned, min_size=min_size, max_size=max_size, summary=summary)
else:
heap_info_structs = arena.get_heap_info_list() or []
for heap_info in heap_info_structs:
if not self.dump_chunks_heap(heap_info.heap_start, heap_info.heap_end, arena, allow_unaligned=allow_unaligned, summary=summary):
if not self.dump_chunks_heap(heap_info.heap_start, heap_info.heap_end, arena, allow_unaligned=allow_unaligned, min_size=min_size, max_size=max_size, summary=summary):
break
return

def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, allow_unaligned: bool = False, summary: bool = False) -> bool:
def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, allow_unaligned: bool = False, min_size: int = 0, max_size: int = 0, summary: bool = False) -> bool:
nb = self["peek_nb_byte"]
chunk_iterator = GlibcChunk(start, from_base=True, allow_unaligned=allow_unaligned)
heap_summary = GlibcHeapArenaSummary()
for chunk in chunk_iterator:
heap_corrupted = chunk.base_address > end
should_process = self.should_process_chunk(chunk, min_size, max_size)

if not summary:
if chunk.base_address == arena.top:
if not summary and chunk.base_address == arena.top:
if should_process:
gef_print(
f"{chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}")
break
break

if heap_corrupted:
err("Corrupted heap, cannot continue.")
return False

if not should_process:
continue

if summary:
heap_summary.process_chunk(chunk)
else:
Expand All @@ -6423,6 +6427,15 @@ def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, allow_unalig

return True

def should_process_chunk(self, chunk: GlibcChunk, min_size: int, max_size: int) -> bool:
if chunk.size < min_size:
return False

if 0 < max_size < chunk.size:
return False

return True


@register
class GlibcHeapBinsCommand(GenericCommand):
Expand Down
30 changes: 30 additions & 0 deletions tests/commands/heap.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,36 @@ def test_cmd_heap_chunks_summary(self):
self.assertIn("== Chunk distribution by size", res)
self.assertIn("== Chunk distribution by flag", res)

def test_cmd_heap_chunks_min_size_filter(self):
cmd = "heap chunks --min-size 16"
target = _target("heap")
self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target))
res = gdb_run_silent_cmd(cmd, target=target)
self.assertNoException(res)
self.assertIn("Chunk(addr=", res)

cmd = "heap chunks --min-size 1048576"
target = _target("heap")
self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target))
res = gdb_run_silent_cmd(cmd, target=target)
self.assertNoException(res)
self.assertNotIn("Chunk(addr=", res)

def test_cmd_heap_chunks_max_size_filter(self):
cmd = "heap chunks --max-size 160"
target = _target("heap")
self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target))
res = gdb_run_silent_cmd(cmd, target=target)
self.assertNoException(res)
self.assertIn("Chunk(addr=", res)

cmd = "heap chunks --max-size 16"
target = _target("heap")
self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target))
res = gdb_run_silent_cmd(cmd, target=target)
self.assertNoException(res)
self.assertNotIn("Chunk(addr=", res)

def test_cmd_heap_bins_fast(self):
cmd = "heap bins fast"
before = ["set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0"]
Expand Down

0 comments on commit fbda021

Please sign in to comment.