Fix #1256: fixes next cmds hangs on segfaults (#1268)

* Fix #1256: fixes next cmds hangs on segfaults

Before this commit the next/step commands like `nextret`, `stepret`,
`nextsyscall`, `nextproginstr` etc. would hang if they approach a
segfault. This commit fixes it by checking for ANY signals by executing
the GDB's `info prog` command and parsing its output.

* fix lint
This commit is contained in:
Disconnect3d 2022-10-11 09:33:09 +02:00 committed by GitHub
parent d42444274e
commit 478a569cb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 8 deletions

View File

@ -91,6 +91,10 @@ def break_next_interrupt(address=None):
def break_next_call(symbol_regex=None):
while pwndbg.gdblib.proc.alive:
# Break on signal as it may be a segfault
if pwndbg.gdblib.proc.stopped_with_signal:
return
ins = break_next_branch()
if not ins:
@ -115,6 +119,10 @@ def break_next_call(symbol_regex=None):
def break_next_ret(address=None):
while pwndbg.gdblib.proc.alive:
# Break on signal as it may be a segfault
if pwndbg.gdblib.proc.stopped_with_signal:
return
ins = break_next_branch(address)
if not ins:
@ -126,13 +134,14 @@ def break_next_ret(address=None):
def break_on_program_code():
"""
Breaks on next instruction that belongs to process' objfile code.
:return: True for success, False when process ended or when pc is at the code.
Breaks on next instruction that belongs to process' objfile code
:return: True for success, False when process ended or when pc is not at the code or if a signal occurred
"""
exe = pwndbg.gdblib.proc.exe
binary_exec_page_ranges = [
binary_exec_page_ranges = tuple(
(p.start, p.end) for p in pwndbg.vmmap.get() if p.objfile == exe and p.execute
]
)
pc = pwndbg.gdblib.regs.pc
for start, end in binary_exec_page_ranges:
@ -140,12 +149,18 @@ def break_on_program_code():
print(message.error("The pc is already at the binary objfile code. Not stepping."))
return False
while pwndbg.gdblib.proc.alive:
gdb.execute("si", from_tty=False, to_string=False)
proc = pwndbg.gdblib.proc
regs = pwndbg.gdblib.regs
while proc.alive:
# Break on signal as it may be a segfault
if proc.stopped_with_signal:
return False
o = gdb.execute("si", from_tty=False, to_string=True)
pc = pwndbg.gdblib.regs.pc
for start, end in binary_exec_page_ranges:
if start <= pc < end:
if start <= regs.pc < end:
return True
return False

View File

@ -42,6 +42,11 @@ class module(ModuleType):
@property
def alive(self):
"""
Informs whether the process has a thread. However, note that it will
still return True for a segfaulted thread. To detect that, consider
using the `stopped_with_signal` method.
"""
return gdb.selected_thread() is not None
@property
@ -56,6 +61,15 @@ class module(ModuleType):
"""
return gdb.selected_thread().is_stopped()
@property
def stopped_with_signal(self) -> bool:
"""
Returns whether the program has stopped with a signal
Can be used to detect segfaults (but will also detect other signals)
"""
return "It stopped with signal " in gdb.execute("info program", to_string=True)
@property
def exe(self):
"""

View File

@ -1,9 +1,11 @@
import gdb
import pytest
import pwndbg.gdblib.regs
import tests
REFERENCE_BINARY = tests.binaries.get("reference-binary.out")
CRASH_SIMPLE_BINARY = tests.binaries.get("crash_simple.out.hardcoded")
def test_command_nextproginstr_binary_not_running():
@ -40,3 +42,19 @@ def test_command_nextproginstr(start_binary):
# Ensure that nextproginstr won't jump now
out = gdb.execute("nextproginstr", to_string=True)
assert out == "The pc is already at the binary objfile code. Not stepping.\n"
@pytest.mark.parametrize(
"command",
("nextcall", "nextjump", "nextproginstr", "nextret", "nextsyscall", "stepret", "stepsyscall"),
)
def test_next_command_doesnt_freeze_crashed_binary(start_binary, command):
start_binary(REFERENCE_BINARY)
# The nextproginstr won't step if we are already on the binary address
# and interestingly, other commands won't step if the address can't be disassemblied
if command == "nextproginstr":
pwndbg.gdblib.regs.pc = 0x1234
# This should not halt/freeze the program
gdb.execute(command, to_string=True)