Adding detailed info to message shown on protection key violation#3531
Conversation
k4lizen
left a comment
There was a problem hiding this comment.
Pretty cool feature, thanks for the PR!
I think its fine to not implement the parse_tid_smaps for LLDB if you don't want to, it just won't have that extra info and thats that.
We can't use gdb-only stuff in pwndbg/aglib/ though, so that needs to be fixed.
Also, we would appreciate a test for this.
| import pwndbg.lib.arch | ||
|
|
||
| if pwndbg.dbg.is_gdblib_available(): | ||
| import gdb |
There was a problem hiding this comment.
We can't be having import gdb in the debugger [ag]nostic [lib]rary. (https://pwndbg.re/dev/contributing/dev-notes/#aglib)
|
|
||
| def get_segv_pkuerr_description() -> Optional[str]: | ||
| """Retrieve the long description for SEGV_PKUERR, if applicable.""" | ||
| fault_addr = int(gdb.parse_and_eval("$_siginfo._sifields._sigfault.si_addr")) |
There was a problem hiding this comment.
okay first of all, you should be using pwndbg.dbg.evaluate_expression() to get the value of an expression like this.
But more importantly, this doesn't fly in LLDB. As it doesn't expose this info through a variable like this. I found the way to do this in LLDB like this:
- Stumbled upon this through net searching: https://lists.llvm.org/pipermail/lldb-dev/2022-January/017189.html
- Typed
apropos siginfoin LLDB which returned thethread siginfocommand. - Intuitively I know that this is then likely to be exposed in SBThread: https://lldb.llvm.org/python_api/lldb.SBThread.html#lldb.SBThread.GetSiginfo
To implement this properly, you should go to pwndbg/dbg_mod/__init__.py:Thread and implement a def siginfo() function which uses evaluate_expression for gdb and SBThread.GetSiginfo for lldb. It should probably return a class SigInfo or something like that.
then here you can use pwndbg.dbg.selected_thread().siginfo() and get the fault address.
There was a problem hiding this comment.
Thank you, this was pretty helpful. I've removed all references to gdb from aglib.
| def get_segv_pkuerr_description() -> Optional[str]: | ||
| """Retrieve the long description for SEGV_PKUERR, if applicable.""" | ||
| fault_addr = int(gdb.parse_and_eval("$_siginfo._sifields._sigfault.si_addr")) | ||
| fault_mem_page = pwndbg.aglib.vmmap.find(fault_addr) |
| LONG_SEGV_DESCRIPTIONS: Dict[ | ||
| pwndbg.lib.arch.PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, dict[Any, Callable[[], Optional[str]]] | ||
| ] = { | ||
| "x86-64": LONG_SEGV_DESCRIPTIONS_X86_64, |
There was a problem hiding this comment.
My mypy didn't catch it for some reason, but I've pushed some changes that should fix it
|
|
||
| return desc_short, desc_long | ||
|
|
||
| except gdb.error: |
There was a problem hiding this comment.
this would probably be except pwndbg.dbg_mod.Error
| else: | ||
| try: | ||
| desc_short, desc_long = pwndbg.aglib.signal.get_segv_information() | ||
| si_addr = gdb.parse_and_eval("$_siginfo._sifields._sigfault.si_addr") |
There was a problem hiding this comment.
if you do the above changes, you can also make this this whole section debugger agnostic by calling pwndbg.dbg.selected_thread().siginfo() or whatever. and running this function with @pwndbg.dbg.event_handler instead of gdb.events.*.connect
There was a problem hiding this comment.
I was almost able to make it debugger agnostic. The problematic part was one related to rr:
if pwndbg.dbg.is_gdblib_available() and _is_rr_present():
# When users use rr (https://rr-project.org or https://github.com/mozilla/rr)
# we can't access $_siginfo, so lets just show current pc
# see also issue 476
if pwndbg.aglib.regs.pc is not None:
result.append(message.signal(f"current pc: {pwndbg.aglib.regs.pc:#x}"))
return_is_rr_present uses some gdb dependent hacks inside - globals_list_literal_str = gdb.execute("python print(list(globals().keys()))", to_string=True), so I just added if pwndbg.dbg.is_gdblib_available() in the code above
I wasn't able to preserve this part:
if isinstance(signal, gdb.ExitedEvent):
# Booooo old gdb
if hasattr(signal, "exit_code"):
result.append(message.exit(f"Exited: {signal.exit_code}"))I don't see any way to check if program stopped because it exited. On the other hand, this wasn't really useful, it was just duplicating info that was already given to you by GDB.
| return self.process.GetState() == lldb.eStateStopped and any( | ||
| ( | ||
| thread.GetStopReason() == lldb.eStopReasonBreakpoint | ||
| for thread in self.process.threads |
There was a problem hiding this comment.
Is this correct? Shouldn't we check this only against some kind of "selected thread"?
| @@ -0,0 +1,37 @@ | |||
| #define _GNU_SOURCE | |||
There was a problem hiding this comment.
Can we some comments here and there to make it clear what this example is about?
- It should probably point to
man 7 pkeys - We should inform how to check if ur CPU supports PKU (cat /proc/cpuinfo | grep pku ?)
- It should have a comment for memcpy that it should crash on SIGSEGV with pku?
| with open("/proc/cpuinfo", "r") as f: | ||
| cpuinfo = f.read() | ||
| if "pku" not in cpuinfo: | ||
| pytest.skip("PKU not supported on this CPU") |
There was a problem hiding this comment.
It's worth to mention, creating tests for this PR is a bit tricky. Tests are executed on github runners, and seems like their CPU/kernel doesn't support PKU yet, so in the place where test is supposed to segfault, it just passes normally, because no memory protection key exists. QEMU userspace emulation also doesn't work, since it's kernel/CPU related feature, and we don't really have a setup for running tests under full system emulation(or actually we do, but only for kernel debugging). Such setup would really be useful, but this seems like a work for separate PR. I don't see any other way to test it, so I decided to just skip this test if PKU is not enabled
| def save_signal(signal: gdb.Event) -> None: | ||
| @pwndbg.dbg.event_handler(EventType.STOP, EventHandlerPriority.UPDATE_ARCH_AND_TYPEINFO) | ||
| @pwndbg.dbg.event_handler(EventType.EXIT, EventHandlerPriority.UPDATE_ARCH_AND_TYPEINFO) | ||
| @pwndbg.dbg.event_handler(EventType.CONTINUE, EventHandlerPriority.UPDATE_ARCH_AND_TYPEINFO) |
There was a problem hiding this comment.
i think I would add a new EventHandlerPriority called SAVE_SIGNAL after UPDATE_ARCH_AND_TYPEINFO, just for naming clarity's sake, but its not an issue.
| "SIGXCPU", | ||
| "SIGXFSZ", | ||
| "SIGWINCH", | ||
| ] |
There was a problem hiding this comment.
nit: I think an enum would make most sense here
This PR adds more details to the message shown when SIGSEGV is caused by violating memory protection keys(https://man7.org/linux/man-pages/man7/pkeys.7.html).
Before:

After:

One of the notable changes is that pwndbg is now parsing /proc/$TID/smaps (however fallback to /proc/$TID/maps is also implemented), as it's only place that contains info about protection keys assigned to memory pages.
This is also skeleton code that allows for adding more info, as there are more SIGSEGV subtypes available, e.g. ones related to arm MTE, but this PR focuses on PKU