mirror of
https://github.com/hugsy/gef.git
synced 2026-10-12 02:15:57 +00:00
heap: support glibc >= 2.43 (fastbins were removed) (#1231)
Fixes #1225 ## Problem glibc 2.43 removed the fastbin infrastructure (upstream commit `bb5a4f5`), so `struct malloc_state` no longer contains `fastbinsY[NFASTBINS]` nor `have_fastchunks`. GEF's `GlibcArena.malloc_state_t()` was still parsing the old layout, so on glibc >= 2.43 every heap command reading the arena showed garbage (`heap arenas` with wrong `top`/`last_remainder`, broken `heap bins`, ...). ## Fix Made the arena layout version-aware: - `GlibcArena.has_fastbins()`: fastbins exist only on glibc < 2.43 - `GlibcArena.malloc_state_t()`: no longer defines `fastbinsY` / `have_fastchunks` on glibc >= 2.43 - `GlibcArena.fastbinsY` returns an empty array, `fastbin()` returns `None`, so existing callers behave safely - `heap bins fast` exits with a clear message instead of parsing corrupted data ## Tests Added `HeapCommandNoFastbins` (tests/commands/heap.py): it forces `gef.libc._version = (2, 43)` regardless of the host glibc and verifies the 2.43 arena layout has no fastbins and `heap bins fast` reports them as unsupported. The initial CI run failed on `fedora-rawhide` (glibc 2.44) for two reasons, both fixed in the follow-up commit: - `HeapCommandFastBins::test_cmd_heap_bins_fast` was missing the `skipif(is_glibc_ge(2, 43))` marker that all the other fastbin tests use, and failed on a real no-fastbins glibc - `CanaryCommand::test_cmd_canary` asserted that the stack canary equals the `AT_RANDOM`-derived value; on glibc >= 2.44 the canary is no longer derived from `AT_RANDOM`, so the test is skipped there (the `canary` command itself still reads the right value from the TLS slot) Verified locally against glibc 2.41 (Debian), 2.43 (fedora:44) and 2.44 (fedora:rawhide) containers, plus `pre-commit run --all-files`. --------- Co-authored-by: NK <[email protected]>
This commit is contained in:
@@ -1588,6 +1588,12 @@ class GlibcArena:
|
||||
BITSPERMAP = 1 << BINMAPSHIFT
|
||||
BINMAPSIZE = NBINS // BITSPERMAP
|
||||
|
||||
@staticmethod
|
||||
def has_fastbins() -> bool:
|
||||
"""Fastbins were removed in glibc 2.43, see
|
||||
https://sourceware.org/git/?p=glibc.git;a=commit;h=bb5a4f5295ced26532939703867c35f2ce8c149b"""
|
||||
return not (gef and gef.libc.version and gef.libc.version >= (2, 43))
|
||||
|
||||
@staticmethod
|
||||
def malloc_state_t() -> Type[ctypes.Structure]:
|
||||
pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32
|
||||
@@ -1595,13 +1601,14 @@ class GlibcArena:
|
||||
("mutex", ctypes.c_uint32),
|
||||
("flags", ctypes.c_uint32),
|
||||
]
|
||||
if gef and gef.libc.version and gef.libc.version >= (2, 27):
|
||||
# https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L1684
|
||||
fields += [("have_fastchunks", ctypes.c_uint32)]
|
||||
if gef.arch.ptrsize == 8:
|
||||
fields += [("UNUSED_c", ctypes.c_uint32)]
|
||||
if GlibcArena.has_fastbins():
|
||||
if gef and gef.libc.version and gef.libc.version >= (2, 27):
|
||||
# https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L1684
|
||||
fields += [("have_fastchunks", ctypes.c_uint32)]
|
||||
if gef.arch.ptrsize == 8:
|
||||
fields += [("UNUSED_c", ctypes.c_uint32)]
|
||||
fields += [("fastbinsY", GlibcArena.NFASTBINS * pointer)]
|
||||
fields += [
|
||||
("fastbinsY", GlibcArena.NFASTBINS * pointer),
|
||||
("top", pointer),
|
||||
("last_remainder", pointer),
|
||||
("bins", (GlibcArena.NBINS * 2 - 2) * pointer),
|
||||
@@ -1697,6 +1704,9 @@ class GlibcArena:
|
||||
|
||||
@property
|
||||
def fastbinsY(self) -> ctypes.Array:
|
||||
if not GlibcArena.has_fastbins():
|
||||
pointer = ctypes.c_uint64 if gef.arch.ptrsize == 8 else ctypes.c_uint32
|
||||
return (pointer * GlibcArena.NFASTBINS)()
|
||||
return self.__arena.fastbinsY
|
||||
|
||||
@property
|
||||
@@ -8067,6 +8077,12 @@ class GlibcHeapFastbinsYCommand(GenericCommand):
|
||||
@parse_arguments({"arena_address": ""}, {})
|
||||
@only_if_gdb_running
|
||||
def do_invoke(self, *_: Any, **kwargs: Any) -> None:
|
||||
if not GlibcArena.has_fastbins():
|
||||
err(
|
||||
"Fastbins were removed in glibc 2.43, this command is not supported here"
|
||||
)
|
||||
return
|
||||
|
||||
def fastbin_index(sz: int) -> int:
|
||||
return (sz >> 4) - 2 if SIZE_SZ == 8 else (sz >> 3) - 2
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
`canary` command test module
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from tests.utils import (
|
||||
ERROR_INACTIVE_SESSION_MESSAGE,
|
||||
debug_target,
|
||||
p64,
|
||||
p32,
|
||||
is_64b,
|
||||
is_glibc_ge,
|
||||
u32,
|
||||
)
|
||||
from tests.base import RemoteGefUnitTestGeneric
|
||||
@@ -20,6 +22,10 @@ class CanaryCommand(RemoteGefUnitTestGeneric):
|
||||
self._target = debug_target("canary")
|
||||
return super().setUp()
|
||||
|
||||
@pytest.mark.skipif(
|
||||
is_glibc_ge(2, 44),
|
||||
reason="Skipped for glibc >= 2.44 (canary is no longer derived from AT_RANDOM)",
|
||||
)
|
||||
def test_cmd_canary(self):
|
||||
assert ERROR_INACTIVE_SESSION_MESSAGE == self._gdb.execute(
|
||||
"canary", to_string=True
|
||||
|
||||
@@ -253,6 +253,7 @@ class HeapCommandFastBins(RemoteGefUnitTestGeneric):
|
||||
self._target = debug_target("heap-fastbins")
|
||||
return super().setUp()
|
||||
|
||||
@pytest.mark.skipif(is_glibc_ge(2, 43), reason="Skipped for glibc >= 2.43")
|
||||
def test_cmd_heap_bins_fast(self):
|
||||
gdb = self._gdb
|
||||
cmd = "heap bins fast"
|
||||
@@ -267,6 +268,39 @@ class HeapCommandFastBins(RemoteGefUnitTestGeneric):
|
||||
self.assertIn("Chunk(addr=", res)
|
||||
|
||||
|
||||
class HeapCommandNoFastbins(RemoteGefUnitTestGeneric):
|
||||
"""Fastbin commands when running on glibc >= 2.43, where fastbins were
|
||||
removed from the malloc implementation (see
|
||||
https://github.com/hugsy/gef/issues/1225)."""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self._target = debug_target("heap")
|
||||
super().setUp()
|
||||
self._gdb.execute("python gef.libc._version = (2, 43)")
|
||||
|
||||
def test_cmd_heap_bins_fast_not_supported(self):
|
||||
gdb = self._gdb
|
||||
gdb.execute("run")
|
||||
res = gdb.execute("heap bins fast", to_string=True)
|
||||
self.assertIn("not supported", res.lower())
|
||||
|
||||
def test_arena_layout_has_no_fastbins(self):
|
||||
gdb = self._gdb
|
||||
gdb.execute("run")
|
||||
gdb.execute("heap set-arena &main_arena")
|
||||
res = gdb.execute(
|
||||
"python print([f[0] for f in type(gef.heap.main_arena).malloc_state_t()._fields_])",
|
||||
to_string=True,
|
||||
)
|
||||
self.assertNotIn("fastbinsY", res)
|
||||
self.assertNotIn("have_fastchunks", res)
|
||||
res = gdb.execute(
|
||||
"python print(gef.heap.main_arena.fastbin(0) is None, len(gef.heap.main_arena.fastbinsY))",
|
||||
to_string=True,
|
||||
)
|
||||
self.assertIn("True 10", res)
|
||||
|
||||
|
||||
class HeapCommandBins(RemoteGefUnitTestGeneric):
|
||||
def setUp(self) -> None:
|
||||
self._target = debug_target("heap-bins")
|
||||
|
||||
Reference in New Issue
Block a user