Make mallocng tests pass on fedora (#3491)

* cleanup mallocng tests

* lint
pull/3482/merge
k4lizen 1 day ago committed by GitHub
parent 5a29ea00b5
commit 9c95466903
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -19,7 +19,9 @@ import pwndbg.aglib.heap.heap
import pwndbg.aglib.memory as memory
import pwndbg.aglib.stack
import pwndbg.aglib.typeinfo
import pwndbg.auxv
import pwndbg.color.message as message
import pwndbg.search
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L14
# Slot granularity.
@ -1055,19 +1057,19 @@ class MallocContext:
assert len(size_classes) == 48
for i in range(len(size_classes)):
for _ in range(len(size_classes)):
cur_active = next_int(ptrsize)
self.active.append(cur_active)
for i in range(len(size_classes)):
for _ in range(len(size_classes)):
cur_usage = next_int(size_tsize)
self.usage_by_class.append(cur_usage)
for i in range(32):
for _ in range(32):
cur_seq = next_int(uint8size)
self.unmap_seq.append(cur_seq)
for i in range(32):
for _ in range(32):
cur_bounce = next_int(uint8size)
self.bounces.append(cur_bounce)
@ -1213,7 +1215,11 @@ class Mallocng(pwndbg.aglib.heap.heap.MemoryAllocator):
if pwndbg.dbg.selected_inferior().is_dynamically_linked():
for addr, mapname in possible:
if mapname.endswith("libc.so"):
# Unlike glibc, for musl the libc and ld are loaded as one object file.
# On some distro's the object file shows up as libc
# (e.g. /usr/lib/musl/lib/libc.so on archlinux) and on some as ld
# (e.g. /usr/lib/ld-musl-x86_64.so.1 on fedora).
if mapname.endswith("libc.so") or "/ld-musl-" in mapname:
maybe_ctx = MallocContext(addr)
if maybe_ctx.looks_valid():
self.ctx_addr = addr
@ -1223,7 +1229,18 @@ class Mallocng(pwndbg.aglib.heap.heap.MemoryAllocator):
known_invalid.add(addr)
for addr, mapname in possible:
if "libc" in mapname and addr not in known_invalid:
if addr in known_invalid:
continue
# It is probably better for this matching to be overly eager than overly restrictive,
# we can fix it later if we ever actually encounter false positives.
# The .startswith() logic is there for CTF-type setups when the libc/ld is in the same folder as the binary.
if (
"/libc" in mapname
or "/ld-" in mapname
or mapname.startswith("libc")
or mapname.startswith("ld-")
):
maybe_ctx = MallocContext(addr)
if maybe_ctx.looks_valid():
self.ctx_addr = addr
@ -1234,7 +1251,7 @@ class Mallocng(pwndbg.aglib.heap.heap.MemoryAllocator):
print(
message.warn(
"Couldn't find __malloc_context in a 'libc' mapping, trying elsewhere."
"Couldn't find __malloc_context in a 'libc' or 'ld-' mapping, trying elsewhere."
)
)
else:

@ -4,17 +4,23 @@
void break_here() {};
int main () {
char* buffer1 = malloc(0x20);
char* buffer2 = malloc(0x20);
char* buffer3 = malloc(0x20);
// In some environments (e.g. on fedora 41), musl will allocate a slot with a string like
// "/usr/x86_64-linux-musl/lib64" while on some environments (like archlinux) this string
// won't be allocated at all (it seems the `sys_path` symbol points to this string). We make
// sure to allocate 0x50 so we don't end up in the same group as that string to have consistent
// tests. We also need to be careful about slots with slack because they may bring indeterminism
// (e.g. between the static and dynamic run).
char* buffer1 = malloc(0x50);
char* buffer2 = malloc(0x50);
char* buffer3 = malloc(0x50);
char* buffer4 = malloc(0x211);
char* buffer5 = malloc(0x211);
break_here();
memset(buffer1, 0xA, 0x20);
memset(buffer2, 0xB, 0x20);
memset(buffer3, 0xC, 0x20);
memset(buffer1, 0xA, 0x50);
memset(buffer2, 0xB, 0x50);
memset(buffer3, 0xC, 0x50);
memset(buffer4, 0xD, 0x211);
memset(buffer5, 0xE, 0x211);

@ -44,8 +44,8 @@ async def test_mallocng_slot_user(ctrl: Controller, binary: str):
f" start: {re_addr} ",
f" user start: {re_addr} aka `p`",
rf" end: {re_addr} start \+ stride - 4",
" stride: 0x30 distance between adjacent slots",
""" user size: 0x20 aka "nominal size", `n`""",
" stride: 0x60 distance between adjacent slots",
""" user size: 0x50 aka "nominal size", `n`""",
r" slack: 0x0 \(0x0\) slot's unused memory \/ 0x10",
" state: allocated ",
"in-band",
@ -77,15 +77,15 @@ async def test_mallocng_slot_user(ctrl: Controller, binary: str):
cyclic_idx = 17
# Check stride
assert "stride" in buffer2_out[stride_idx] and " 0x30 " in buffer2_out[stride_idx]
assert "stride" in buffer2_out[stride_idx] and " 0x60 " in buffer2_out[stride_idx]
assert "stride" in buffer4_out[stride_idx] and " 0x2a0 " in buffer4_out[stride_idx]
# Check user size
assert "user size" in buffer2_out[user_size_idx] and " 0x20 " in buffer2_out[user_size_idx]
assert "user size" in buffer2_out[user_size_idx] and " 0x50 " in buffer2_out[user_size_idx]
assert "user size" in buffer4_out[user_size_idx] and " 0x211 " in buffer4_out[user_size_idx]
# Check slack
assert "slack" in buffer2_out[slack_idx] and " 0x0 " in buffer2_out[slack_idx]
assert "slack" in buffer2_out[slack_idx] and " 0x0 (0x0)" in buffer2_out[slack_idx]
assert "slack" in buffer4_out[slack_idx] and " 0x8 (0x80) " in buffer4_out[slack_idx]
# Check allocation status
@ -93,7 +93,7 @@ async def test_mallocng_slot_user(ctrl: Controller, binary: str):
assert "state" in buffer4_out[state_idx] and " allocated " in buffer4_out[state_idx]
# Check offset
assert "offset" in buffer2_out[offset_idx] and " 0x3 (0x30) " in buffer2_out[offset_idx]
assert "offset" in buffer2_out[offset_idx] and " 0x6 (0x60) " in buffer2_out[offset_idx]
if binary == HEAP_MALLOCNG_STATIC:
# Because it's cyclic
assert "offset" in buffer4_out[offset_idx] and " 0x1 (0x10) " in buffer4_out[offset_idx]
@ -218,7 +218,7 @@ async def test_mallocng_group(ctrl: Controller, binary: str):
"group",
f" @ {re_addr} - {re_addr}",
f" meta: {re_addr} ",
" active_idx: 0x9 ",
" active_idx: 0x4 ",
f" storage: {re_addr} start of slots",
"---",
" group size: 0x1f0 ",
@ -227,16 +227,16 @@ async def test_mallocng_group(ctrl: Controller, binary: str):
f" prev: {re_addr} ",
f" next: {re_addr} ",
f" mem: {re_addr} the group",
" avail_mask: 0x3f8 0b00000000000000000000001111111000",
" avail_mask: 0x18 0b00000000000000000000000000011000",
" freed_mask: 0x0 0b00000000000000000000000000000000",
r" last_idx: 0x9 \(cnt: 0xa\) index of last slot",
r" last_idx: 0x4 \(cnt: 0x5\) index of last slot",
" freeable: True ",
r" sizeclass: 0x2 \(stride: 0x30\) ",
r" sizeclass: 0x5 \(stride: 0x60\) ",
" maplen: 0x0 ",
"",
rf"Group nested in slot of another group \({re_addr}\).",
"",
"Slot statuses: UUUAAAAAAA",
"Slot statuses: UUUAA",
r" \(U: Inuse \(allocated\) / F: Freed / A: Available\)",
]
@ -440,13 +440,23 @@ async def test_mallocng_vis(ctrl: Controller, binary: str):
"LEGEND: .*",
"LEGEND: .*",
"",
rf"{re_addr}0\t0x[0-9a-fA-F]{{16}}\t0x0000ff0000000009\t................",
# the dots match anything but w/e
rf"{re_addr}0\t0x[0-9a-fA-F]{{16}}\t0x0000ff0000000004\t................",
rf"{re_addr}0\t0x0a0a0a0a0a0a0a0a\t0x0a0a0a0a0a0a0a0a\t................",
rf"{re_addr}0\t0x0a0a0a0a0a0a0a0a\t0x0a0a0a0a0a0a0a0a\t................",
rf"{re_addr}0\t0x0a0a0a0a0a0a0a0a\t0x0a0a0a0a0a0a0a0a\t................",
rf"{re_addr}0\t0x0a0a0a0a0a0a0a0a\t0x0a0a0a0a0a0a0a0a\t................",
rf"{re_addr}0\t0x0a0a0a0a0a0a0a0a\t0x0a0a0a0a0a0a0a0a\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000ff000000000c\t................",
rf"{re_addr}0\t0x0b0b0b0b0b0b0b0b\t0x0b0b0b0b0b0b0b0b\t................",
rf"{re_addr}0\t0x0b0b0b0b0b0b0b0b\t0x0b0b0b0b0b0b0b0b\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0006a2000000000c\t................ 2 \+ \(5 << 5\)",
rf"{re_addr}0\t0x0b0b0b0b0b0b0b0b\t0x0b0b0b0b0b0b0b0b\t................",
rf"{re_addr}0\t0x0b0b0b0b0b0b0b0b\t0x0b0b0b0b0b0b0b0b\t................",
rf"{re_addr}0\t0x0b0b0b0b0b0b0b0b\t0x0b0b0b0b0b0b0b0b\t................",
rf"{re_addr}0\t0x0000000000000000\t0x000ca2000000000c\t................ 2 \+ \(5 << 5\)",
rf"{re_addr}0\t0x0c0c0c0c0c0c0c0c\t0x0c0c0c0c0c0c0c0c\t................",
rf"{re_addr}0\t0x0c0c0c0c0c0c0c0c\t0x0c0c0c0c0c0c0c0c\t................",
rf"{re_addr}0\t0x0c0c0c0c0c0c0c0c\t0x0c0c0c0c0c0c0c0c\t................",
rf"{re_addr}0\t0x0c0c0c0c0c0c0c0c\t0x0c0c0c0c0c0c0c0c\t................",
rf"{re_addr}0\t0x0c0c0c0c0c0c0c0c\t0x0c0c0c0c0c0c0c0c\t................",
rf"{re_addr}0\t0x0000000000000000\t0x000000000000000c\t................",
@ -462,15 +472,6 @@ async def test_mallocng_vis(ctrl: Controller, binary: str):
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
rf"{re_addr}0\t0x0000000000000000\t0x0000000000000000\t................",
]
assert len(expected_out) == len(vis_out)
@ -502,8 +503,8 @@ async def test_mallocng_dump(ctrl: Controller, binary: str):
dump_out = await ctrl.execute_and_capture("ng-dump")
assert "meta_area" in dump_out
assert "group @" in dump_out
assert "(slot size: 0x30)" in dump_out # buffer{1,2,3}
assert "(slot size: 0x60)" in dump_out # buffer{1,2,3}
assert "(slot size: 0x2a0)" in dump_out # buffer{4,5}
# 10 slots in the buffer{1,2,3} group.
for idx in range(10):
# 5 slots in the buffer{1,2,3} group.
for idx in range(5):
assert f"[{idx}]" in dump_out

Loading…
Cancel
Save