support for printing DMA buf information (#3312)

* support for printing DMA buffer information

* supported detection of `CONFIG_DEBUG_FS`

* changes based on comments
pull/3320/head^2
jxuanli 2 months ago committed by GitHub
parent c2c31fc01e
commit 581a7df9e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -79,6 +79,7 @@
- [kchecksec](kernel/kchecksec.md) - Checks for kernel hardening configuration options.
- [kcmdline](kernel/kcmdline.md) - Return the kernel commandline (/proc/cmdline).
- [kconfig](kernel/kconfig.md) - Outputs the kernel config.
- [kdmabuf](kernel/kdmabuf.md) - Prints DMA buf info
- [kdmesg](kernel/kdmesg.md) - Displays the kernel ring buffer (dmesg) contents.
- [klookup](kernel/klookup.md) - Lookup kernel symbols
- [kmod](kernel/kmod.md) - Displays the loaded Linux kernel modules.

@ -0,0 +1,17 @@
<!-- THIS PART OF THIS FILE IS AUTOGENERATED. DO NOT MODIFY IT. See scripts/generate-docs.sh -->
# kdmabuf
```text
usage: kdmabuf [-h]
```
Prints DMA buf info
### Optional arguments
|Short|Long|Help|
| :--- | :--- | :--- |
|-h|--help|show this help message and exit|
<!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. -->
<!-- ------------\>8---- ----\>8---- ----\>8------------ -->

@ -671,3 +671,9 @@ def modules() -> pwndbg.dbg_mod.Value:
if (syms := arch_symbols()) is not None:
return syms.modules()
return None
def db_list() -> pwndbg.dbg_mod.Value:
if (syms := arch_symbols()) is not None:
return syms.db_list()
return None

@ -0,0 +1,107 @@
from __future__ import annotations
from typing import Tuple
import pwndbg.aglib.kernel
def find_dmabuf_offsets(dmabuf) -> Tuple[int, int, int]:
MAX = 0x30
sg_table_off, exp_name_off, list_node_off = None, None, None
ptrsize = pwndbg.aglib.arch.ptrsize
heap_buffer = pwndbg.aglib.memory.read_pointer_width(dmabuf + 2 * ptrsize)
for i in range(1, MAX):
# see load_dmabuf_typeinfo (struct dma_buf) for an explanation
# this loop is searching the `size` field from `list_node`
size = pwndbg.aglib.memory.read_pointer_width(dmabuf - (i + 5) * ptrsize)
file = pwndbg.aglib.memory.read_pointer_width(dmabuf - (i + 4) * ptrsize)
attachments_prev = pwndbg.aglib.memory.read_pointer_width(dmabuf - (i + 3) * ptrsize)
attachments_next = pwndbg.aglib.memory.read_pointer_width(dmabuf - (i + 2) * ptrsize)
ops = pwndbg.aglib.memory.read_pointer_width(dmabuf - (i + 1) * ptrsize)
vmapping_counter = pwndbg.aglib.memory.read_pointer_width(dmabuf - i * ptrsize)
if pwndbg.aglib.memory.is_kernel(size):
continue
if not pwndbg.aglib.memory.is_kernel(file):
continue
if not (
pwndbg.aglib.memory.is_kernel(attachments_next)
and pwndbg.aglib.memory.is_kernel(attachments_prev)
):
continue
if not pwndbg.aglib.memory.is_kernel(ops):
continue
if pwndbg.aglib.memory.is_kernel(vmapping_counter):
continue
# (i + 5) * ptrsize is the distance from the `size` to `list_node`
list_node_off = (i + 5) * ptrsize
break
assert list_node_off is not None, "cannot determine the offset of list_node"
dmabuf -= list_node_off
for i in range(5, MAX):
ptr = pwndbg.aglib.memory.read_pointer_width(dmabuf + i * ptrsize)
try:
if len(pwndbg.aglib.memory.string(ptr).decode()) == 0:
continue
except Exception:
continue
exp_name_off = i * ptrsize
break
assert exp_name_off is not None, "cannot determine the offset of exp_name"
sz = pwndbg.aglib.memory.read_pointer_width(dmabuf)
for i in range(MAX):
if pwndbg.aglib.memory.read_pointer_width(heap_buffer + i * ptrsize) == sz:
sg_table_off = (i + 1) * ptrsize
break
assert sg_table_off is not None, "cannot determine the offset of sg_table"
return sg_table_off, exp_name_off, list_node_off
def load_dmabuf_typeinfo(first_dmabuf: int):
# reaching here means priv exists
if pwndbg.aglib.typeinfo.lookup_types("struct dma_buf") is not None:
return
sg_table_off, exp_name_off, list_node_off = find_dmabuf_offsets(first_dmabuf)
result = pwndbg.aglib.kernel.symbol.COMMON_TYPES
result += f"""
typedef unsigned long dma_addr_t;
struct scatterlist {{
unsigned long page_link; // either to a page or scatterlist
unsigned int offset;
unsigned int length;
dma_addr_t dma_address;
/*** potentially has 8 more bytes
#ifdef CONFIG_NEED_SG_DMA_LENGTH
unsigned int dma_length;
#endif
#ifdef CONFIG_NEED_SG_DMA_FLAGS
unsigned int dma_flags;
#endif
***/
}};
struct sg_table {{
struct scatterlist *sgl; /* the list */
unsigned int nents; /* number of mapped entries */
unsigned int orig_nents; /* original size of list */
}};
struct system_heap_buffer {{
char _pad[{sg_table_off}];
struct sg_table sg_table;
/* rest of the fields are irrelevant */
}};
struct dma_buf {{
size_t size; // (i + 5) is here
void *file;
struct list_head attachments;
void *ops; // const struct dma_buf_ops *
unsigned vmapping_counter;
char _pad1[{exp_name_off - pwndbg.aglib.arch.ptrsize * 6}];
const char *exp_name;
const char *name;
char _pad2[{list_node_off - exp_name_off - pwndbg.aglib.arch.ptrsize * 2}];
struct list_head list_node;
struct system_heap_buffer *priv; // treating the voidptr as system_heap_buffer
/* rest of the fields are irrelevant */
}};
"""
header_file_path = pwndbg.commands.cymbol.create_temp_header_file(result)
pwndbg.commands.cymbol.add_structure_from_header(header_file_path, "dmabuf_structs", True)

@ -114,6 +114,7 @@ def kversion_cint(kversion: Tuple[int, int, int] = None):
#########################################
COMMON_TYPES = """
#include <stdint.h>
#include <stddef.h>
#include <linux/version.h>
typedef unsigned char u8;
typedef char s8;
@ -310,6 +311,20 @@ class ArchSymbols:
return None
return pwndbg.aglib.memory.get_typed_pointer("unsigned long", modules)
def db_list(self):
if pwndbg.aglib.kernel.krelease() >= (6, 10):
debugfs_list = pwndbg.aglib.symbol.lookup_symbol("debugfs_list")
# TODO: fallback not supported for >= v6.10, should look at dma_buf_debug_show later if needed
# though the symbol should exist if the function symbol exist
return debugfs_list
db_list = pwndbg.aglib.symbol.lookup_symbol("db_list")
if db_list:
return db_list
db_list = self._db_list()
if db_list is None:
return None
return pwndbg.aglib.memory.get_typed_pointer("struct list_head", db_list)
def _node_data(self):
raise NotImplementedError()
@ -322,6 +337,9 @@ class ArchSymbols:
def _modules(self):
raise NotImplementedError()
def _db_list(self):
raise NotImplementedError()
class x86_64Symbols(ArchSymbols):
# mov reg, [... - 0x...]
@ -383,6 +401,19 @@ class x86_64Symbols(ArchSymbols):
disass = self.disass("find_module_all")
return self.qword_mov_reg_ripoff(disass)
def _db_list(self):
offset = 0x10 # offset of the lock
name = (
"dma_buf_file_release"
if pwndbg.aglib.kernel.krelease() >= (5, 10)
else "dma_buf_release"
)
disass = self.disass(name)
result = self.qword_mov_reg_const(disass)
if result is not None:
return result - offset
return None
class Aarch64Symbols(ArchSymbols):
# adrp x?, <kernel address>
@ -448,3 +479,16 @@ class Aarch64Symbols(ArchSymbols):
if m is None:
return None
return sum(int(m.group(i), 16) for i in [2, 3, 4])
def _db_list(self):
offset = 0x10 # offset of the lock
name = (
"dma_buf_file_release"
if pwndbg.aglib.kernel.krelease() >= (5, 10)
else "dma_buf_release"
)
disass = self.disass(name)
result = self.qword_adrp_add_const(disass)
if result is not None:
return result - offset
return None

@ -923,6 +923,7 @@ def load_commands() -> None:
import pwndbg.commands.kchecksec
import pwndbg.commands.kcmdline
import pwndbg.commands.kconfig
import pwndbg.commands.kdmabuf
import pwndbg.commands.kdmesg
import pwndbg.commands.klookup
import pwndbg.commands.kmod

@ -0,0 +1,96 @@
from __future__ import annotations
import argparse
import pwndbg.aglib.kernel
import pwndbg.aglib.kernel.dmabuf
import pwndbg.color.message as M
from pwndbg.aglib.kernel.macros import for_each_entry
from pwndbg.commands import CommandCategory
from pwndbg.lib.exception import IndentContextManager
SG_CHAIN = 0x1
SG_END = 0x2
parser = argparse.ArgumentParser(description="Prints DMA buf info")
def print_dmabuf(dmabuf, idx, indent):
size = int(dmabuf["size"])
file = int(dmabuf["file"])
exp_name = pwndbg.aglib.memory.string(int(dmabuf["exp_name"])).decode()
name = int(dmabuf["name"])
desc = indent.prefix(f"[0x{idx:02x}] DMA-buf") + f" @ {indent.addr_hex(int(dmabuf))}"
desc += f" [size: {indent.aux_hex(size)}, file: {indent.aux_hex(file)}, exporter: {exp_name}]"
if name != 0:
desc += f" (name: {pwndbg.aglib.memory.string(name)})"
indent.print(desc)
def print_sgl(sgl, indent):
sgl_type_len = pwndbg.aglib.typeinfo.lookup_types("struct scatterlist").sizeof
next_sgl = int(sgl)
idx = 0
while True:
sgl = pwndbg.aglib.memory.get_typed_pointer("struct scatterlist", next_sgl)
page_link = int(sgl["page_link"])
page = page_link & ~(SG_CHAIN | SG_END)
if page_link & SG_CHAIN:
next_sgl = page
continue
virt = pwndbg.aglib.kernel.page_to_virt(page)
phys = pwndbg.aglib.kernel.virt_to_phys(virt)
offset = int(sgl["offset"])
length = int(sgl["length"])
desc = "- " + indent.prefix(f"[0x{idx:02x}] {indent.addr_hex(virt)}")
desc += f" (len: {indent.aux_hex(length)}, off: {indent.aux_hex(offset)}) [page: {indent.aux_hex(page)}, phys: {indent.aux_hex(phys)}]"
idx += 1
indent.print(desc)
if page_link & SG_END:
break
next_sgl += sgl_type_len
tmp = pwndbg.aglib.memory.read_pointer_width(next_sgl)
if not pwndbg.aglib.memory.is_kernel(tmp):
next_sgl += pwndbg.aglib.arch.ptrsize
tmp = pwndbg.aglib.memory.read_pointer_width(next_sgl)
if not pwndbg.aglib.memory.is_kernel(tmp):
break
# adapted from https://github.com/bata24/gef/tree/dev
@pwndbg.commands.Command(parser, category=CommandCategory.KERNEL)
@pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWithKernelDebugSymbols
@pwndbg.commands.OnlyWhenPagingEnabled
def kdmabuf():
db_name = "db_list"
if pwndbg.aglib.kernel.krelease() >= (6, 10):
db_name = "debugfs_list"
if "CONFIG_DEBUG_FS" not in pwndbg.aglib.kernel.kconfig():
print(M.warn("dma_buf->priv does not exist"))
db_list = pwndbg.aglib.kernel.db_list()
if db_list is None:
print(M.warn(f"{db_name} not found"))
return
db_list = pwndbg.aglib.memory.get_typed_pointer("struct list_head", db_list)
if int(db_list) == int(db_list["next"]):
print(M.warn(f"{db_name} ({hex(int(db_list))}) is empty"))
return
indent = IndentContextManager()
if not pwndbg.aglib.kernel.has_debug_info():
pwndbg.aglib.kernel.dmabuf.load_dmabuf_typeinfo(int(db_list["next"]))
for idx, e in enumerate(for_each_entry(db_list.dereference(), "struct dma_buf", "list_node")):
print_dmabuf(e, idx, indent)
priv = e["priv"]
if not pwndbg.aglib.memory.is_kernel(int(priv)):
indent.print(M.warn("(no entries)"))
continue
nents = int(priv["sg_table"]["nents"])
if nents == 0:
indent.print(M.warn("(no entries)"))
continue
with indent:
desc = indent.prefix("system_heap_buffer")
desc += f" @ {indent.addr_hex(int(priv))} [nents: {indent.aux_hex(nents)}]"
indent.print(desc)
print_sgl(priv["sg_table"]["sgl"], indent)

@ -62,6 +62,8 @@ class Kconfig(UserDict): # type: ignore[type-arg]
self.data["CONFIG_KASAN"] = "y"
if self.CONFIG_SYSFS:
self.data["CONFIG_SYSFS"] = "y"
if self.CONFIG_DEBUG_FS:
self.data["CONFIG_DEBUG_FS"] = "y"
def get_key(self, name: str) -> str | None:
# First attempt to lookup the value assuming the user passed in a name
@ -171,6 +173,10 @@ class Kconfig(UserDict): # type: ignore[type-arg]
def CONFIG_SYSFS(self) -> bool:
return pwndbg.aglib.symbol.lookup_symbol("sysfs_kf_seq_show") is not None
@property
def CONFIG_DEBUG_FS(self) -> bool:
return pwndbg.aglib.symbol.lookup_symbol("debugfs_attr_read") is not None
def update_with_file(self, file_path):
for line in open(file_path, "r").read().splitlines():
split = line.split("=")

Loading…
Cancel
Save