From b209ada2292933adbe4c2faf39bae3936d2dc8f9 Mon Sep 17 00:00:00 2001 From: disconnect3d Date: Fri, 30 Sep 2022 04:02:21 +0200 Subject: [PATCH] Fix disable_colors formatting & test ctx disasm showing fds This commit adds a test for context disasm showing of file descriptors file paths in syscalls like read() or close(). It also fixes a small issue when Pwndbg is run with PWNDBG_DISABLE_COLORS=1 This issue was that executing: ``` pi '{a:2}'.format(a=pwndbg.color.context.prefix(pwndbg.config.code_prefix)) ``` Failed when Pwndbg was run with disabled colors. It failed because our generate color functions in pwndbg/color/* ended up not processing the input argument -- which here is a Pwndbg config Paramater object -- so that we got a very non obvious exception: ``` Exception occurred: context: unsupported format string passed to Parameter.__format__ () ``` This issue could hypothetically also exist if our config value would be empty I think. So with the fix in this commit, where we do str(x) over the color funciton argument should fix this issue in all cases. --- pwndbg/color/__init__.py | 7 +++- pwndbg/commands/context.py | 4 +-- tests/binaries/use-fds.c | 17 +++++++++ tests/test_context_commands.py | 63 ++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 tests/binaries/use-fds.c create mode 100644 tests/test_context_commands.py diff --git a/pwndbg/color/__init__.py b/pwndbg/color/__init__.py index 70fe6cba..d455ec02 100644 --- a/pwndbg/color/__init__.py +++ b/pwndbg/color/__init__.py @@ -131,7 +131,12 @@ def generateColorFunctionInner(old, new): def generateColorFunction(config): - function = lambda x: x + # the `x` here may be a config Parameter object + # and if we run with disable_colors or if the config value + # is empty, we need to ensure we cast it to string + # so it can be properly formatted e.g. with: + # "{config_param:5}".format(config_param=some_config_parameter) + function = lambda x: str(x) if disable_colors: return function diff --git a/pwndbg/commands/context.py b/pwndbg/commands/context.py index f3e0c4eb..3782adc5 100644 --- a/pwndbg/commands/context.py +++ b/pwndbg/commands/context.py @@ -655,7 +655,7 @@ def get_filename_and_formatted_source(): source = source[start:end] # Compute the prefix_sign length - prefix_sign = pwndbg.config.code_prefix + prefix_sign = C.prefix(str(pwndbg.config.code_prefix)) prefix_width = len(prefix_sign) # Format the output @@ -666,7 +666,7 @@ def get_filename_and_formatted_source(): fmt = C.highlight(fmt) line = fmt.format( - prefix_sign=C.prefix(prefix_sign) if line_number == closest_line else "", + prefix_sign=prefix_sign if line_number == closest_line else "", prefix_width=prefix_width, line_number=line_number, num_width=num_width, diff --git a/tests/binaries/use-fds.c b/tests/binaries/use-fds.c new file mode 100644 index 00000000..79b76ea7 --- /dev/null +++ b/tests/binaries/use-fds.c @@ -0,0 +1,17 @@ +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + char buf[16] = {0}; + + // read 0 bytes so it won't block + read(STDOUT_FILENO, buf, 0); + + int fd = open(argv[0], 0); + + read(fd, buf, sizeof(buf)); + + close(fd); +} diff --git a/tests/test_context_commands.py b/tests/test_context_commands.py new file mode 100644 index 00000000..d845536a --- /dev/null +++ b/tests/test_context_commands.py @@ -0,0 +1,63 @@ +import re + +import gdb + +import pwndbg.commands +import tests + +USE_FDS_BINARY = tests.binaries.get("use-fds.out") + + +def test_context_disasm_show_fd_filepath(start_binary): + """ + Tests context disasm command and whether it shows properly opened fd filepath + """ + start_binary(USE_FDS_BINARY) + + # Run until main + gdb.execute("break main") + gdb.execute("continue") + + # Stop on read(0, ...) -> should show /dev/pts/X + gdb.execute("nextcall") + + out = pwndbg.commands.context.context_disasm() + assert "[ DISASM / x86-64 / set emulate on ]" in out[0] # Sanity check + + call_read_line_idx = out.index(next(line for line in out if "" in line)) + lines_after_call_read = out[call_read_line_idx:] + + line_call_read, line_fd, line_buf, line_nbytes, *_rest = lines_after_call_read + + assert "call read@plt" in line_call_read + + line_fd = line_fd.strip() + assert re.match(r"fd:\s+0x1 \(/dev/pts/\d+\)", line_fd) + + line_buf = line_buf.strip() + assert re.match(r"buf:\s+0x[0-9a-f]+ ◂— 0x0", line_buf) + + line_nbytes = line_nbytes.strip() + assert re.match(r"nbytes:\s+0x0", line_nbytes) + + # Stop on open(...) + gdb.execute("nextcall") + # Stop on read(...) -> should show use-fds.out + gdb.execute("nextcall") + + out = pwndbg.commands.context.context_disasm() + assert "[ DISASM / x86-64 / set emulate on ]" in out[0] # Sanity check + + call_read_line_idx = out.index(next(line for line in out if "" in line)) + lines_after_call_read = out[call_read_line_idx:] + + line_call_read, line_fd, line_buf, line_nbytes, *_rest = lines_after_call_read + + line_fd = line_fd.strip() + assert re.match(r"fd:\s+0x3 \([a-z/]*pwndbg/tests/binaries/use-fds.out\)", line_fd) + + line_buf = line_buf.strip() + assert re.match(r"buf:\s+0x[0-9a-f]+ ◂— 0x0", line_buf) + + line_nbytes = line_nbytes.strip() + assert re.match(r"nbytes:\s+0x10", line_nbytes)