Repair slab info command (#1699)

* fix: repair slab info command

* CI: remove unnecessary assertion
pull/1703/head
theguy147 3 years ago committed by GitHub
parent ed051af109
commit 6ffc3de08f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,9 +5,6 @@ Some of the code here was inspired from https://github.com/NeatMonster/slabdbg
"""
import argparse
import sys
from typing import Iterator
from typing import List
from typing import Union
import gdb
from tabulate import tabulate
@ -18,7 +15,9 @@ import pwndbg.commands
import pwndbg.gdblib.kernel.slab
from pwndbg.commands import CommandCategory
from pwndbg.gdblib.kernel import kconfig
from pwndbg.gdblib.kernel import krelease
from pwndbg.gdblib.kernel import per_cpu
from pwndbg.gdblib.kernel.slab import get_slab_key
from pwndbg.gdblib.kernel.slab import oo_objects
from pwndbg.gdblib.kernel.slab import oo_order
@ -133,52 +132,75 @@ def _rx(val: int) -> str:
def print_slab(
slab: gdb.Value, freelist: Union[Iterator[int], List[int]], indent, verbose, is_partial
slab: gdb.Value,
cpu_cache: gdb.Value,
slab_cache: gdb.Value,
indent,
verbose: bool,
is_partial: bool = False,
) -> None:
page_address = int(slab.address)
virt_address = pwndbg.gdblib.kernel.page_to_virt(page_address)
indent.print(f"- {C.green('Slab')} @ {_yx(virt_address)} [{_rx(page_address)}]:")
slab_address = int(slab.address)
offset = int(slab_cache["offset"])
random = int(slab_cache["random"]) if "SLAB_FREELIST_HARDENED" in kconfig() else 0
address = pwndbg.gdblib.kernel.page_to_virt(slab_address)
indent.print(f"- {C.green('Slab')} @ {_yx(address)} [{_rx(slab_address)}]:")
with indent:
if is_partial:
freelists = [list(walk_freelist(slab["freelist"], offset, random))]
inuse = slab["inuse"]
else:
# `freelist` is a generator, we need to evaluate it now and save the
# result in case we want to print it later
freelist = list(freelist)
freelists = [
list(walk_freelist(cpu_cache["freelist"], offset, random)),
list(walk_freelist(slab["freelist"], offset, random)),
]
# `inuse` will always equal `objects` for the active slab, so we
# need to subtract the length of the freelist
inuse = int(slab["inuse"]) - len(freelist)
inuse = int(slab["inuse"]) - len(freelists[0])
indent.print(f"{C.blue('In-Use')}: {inuse}/{slab['objects']}")
objects = int(slab["objects"])
indent.print(f"{C.blue('In-Use')}: {inuse}/{objects}")
indent.print(f"{C.blue('Frozen')}:", slab["frozen"])
indent.print(f"{C.blue('Freelist')}:", _yx(int(slab["freelist"])))
if verbose:
with indent:
# TODO: Should I print just free objects or all objects?
for entry in freelist:
indent.print("-", _yx(int(entry)))
def print_cpu_cache(cpu_cache, offset, random, cpu_partial, indent, verbose) -> None:
address = int(cpu_cache)
indent.print(f"{C.green('Per-CPU Data')} @ {_yx(address)}:")
size = int(slab_cache["size"])
for address in range(address, address + objects * size, size):
cur_freelist = next(
(freelist for freelist in freelists if address in freelist), None
)
if cur_freelist is None:
indent.print("-", hex(int(address)), "(in-use)")
continue
next_free_idx = cur_freelist.index(address) + 1
next_free = (
cur_freelist[next_free_idx] if len(cur_freelist) > next_free_idx else 0
)
indent.print("-", _yx(int(address)), f"(next: {next_free:#018x})")
def print_cpu_cache(cpu_cache: gdb.Value, slab_cache: gdb.Value, verbose: bool, indent) -> None:
indent.print(f"{C.green('Per-CPU Data')} @ {_yx(int(cpu_cache))}:")
with indent:
freelist = cpu_cache["freelist"]
indent.print(f"{C.blue('Freelist')}:", _yx(int(freelist)))
# TODO: Is the `if page:` a null pointer check or something else?
page = cpu_cache["page"]
if page:
slab_key = get_slab_key()
active_slab = cpu_cache[slab_key]
if active_slab:
indent.print(f"{C.green('Active Slab')}:")
with indent:
freelist = walk_freelist(freelist, offset, random)
print_slab(
page.dereference(),
# Use the CPU cache freelist for the active slab
freelist,
active_slab.dereference(),
cpu_cache,
slab_cache,
indent,
verbose,
is_partial=False,
@ -186,16 +208,35 @@ def print_cpu_cache(cpu_cache, offset, random, cpu_partial, indent, verbose) ->
else:
indent.print("Active Slab: (none)")
slab = cpu_cache["partial"]
if slab:
partial_slab = cpu_cache["partial"]
if partial_slab:
slabs_key = f"{get_slab_key()}s"
if krelease() >= (5, 16):
# calculate approx obj count in half-full slabs (as done in kernel)
# Note, this is a very bad approximation and could/should probably
# be replaced by a more accurate method os removed from pwndbg
oo = oo_objects(int(slab_cache["oo"]["x"]))
slabs = int(partial_slab[slabs_key])
pobjects = (slabs * oo) // 2
else:
pobjects = partial_slab["pobjects"]
cpu_partial = int(slab_cache["cpu_partial"])
indent.print(
f"{C.green('Partial Slabs')} [{slab['pages']}] [PO: {slab['pobjects']}/{cpu_partial}]:"
f"{C.green('Partial Slabs')} [{partial_slab[slabs_key]}] [PO: ~{pobjects}/{cpu_partial}]:"
)
while slab:
page = slab.dereference()
freelist = walk_freelist(page["freelist"], offset, random)
print_slab(page, freelist, indent, verbose, is_partial=True)
slab = page["next"]
while partial_slab:
cur_slab = partial_slab.dereference()
print_slab(
cur_slab,
cpu_cache,
slab_cache,
indent,
verbose,
is_partial=True,
)
partial_slab = cur_slab["next"]
else:
indent.print("Partial Slabs: (none)")
@ -218,8 +259,7 @@ def slab_info(name: str, verbose: bool) -> None:
else:
indent.print(f"{C.blue('Flags')}: (none)")
offset = int(cache["offset"])
indent.print(f"{C.blue('Offset')}:", offset)
indent.print(f"{C.blue('Offset')}:", int(cache["offset"]))
indent.print(f"{C.blue('Size')}:", int(cache["size"]))
indent.print(f"{C.blue('Align')}:", int(cache["align"]))
indent.print(f"{C.blue('Object Size')}:", int(cache["object_size"]))
@ -227,11 +267,7 @@ def slab_info(name: str, verbose: bool) -> None:
# TODO: Handle multiple CPUs
cpu_cache = per_cpu(cache["cpu_slab"])
random = 0
if "SLAB_FREELIST_HARDENED" in kconfig():
random = int(cache["random"])
print_cpu_cache(cpu_cache, offset, random, int(cache["cpu_partial"]), indent, verbose)
print_cpu_cache(cpu_cache, cache, verbose, indent)
# TODO: print_node_cache

@ -16,6 +16,15 @@ def get_cache(target_name: str):
return slab_cache
def get_slab_key() -> str:
# In Linux kernel version 5.17 a slab struct was introduced instead of the previous page struct
try:
gdb.lookup_type("struct slab")
return "slab"
except gdb.error:
return "page"
OO_SHIFT = 16
OO_MASK = (1 << OO_SHIFT) - 1

@ -51,4 +51,14 @@ def test_command_slab_list():
def test_command_slab_info():
pass # TODO
if not pwndbg.gdblib.kernel.has_debug_syms():
res = gdb.execute("slab info kmalloc-512", to_string=True)
assert "may only be run when debugging a Linux kernel with debug" in res
return
res = gdb.execute("slab info -v kmalloc-512", to_string=True)
assert "kmalloc-512" in res
assert "Freelist" in res
res = gdb.execute("slab info -v does_not_exit", to_string=True)
assert "not found" in res

Loading…
Cancel
Save