Skip to content

Adding detailed info to message shown on protection key violation#3531

Merged
k4lizen merged 22 commits into
pwndbg:devfrom
Acee11:memory-protection-keys
Jan 11, 2026
Merged

Adding detailed info to message shown on protection key violation#3531
k4lizen merged 22 commits into
pwndbg:devfrom
Acee11:memory-protection-keys

Conversation

@Acee11

@Acee11 Acee11 commented Dec 23, 2025

Copy link
Copy Markdown
Contributor
  • I read the contributing documentation.
  • I am providing a screenshot of the (new feature) / (fixed bug).

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:
before

After:
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

@k4lizen k4lizen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread pwndbg/aglib/signal.py Outdated
import pwndbg.lib.arch

if pwndbg.dbg.is_gdblib_available():
import gdb

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't be having import gdb in the debugger [ag]nostic [lib]rary. (https://pwndbg.re/dev/contributing/dev-notes/#aglib)

Comment thread pwndbg/aglib/signal.py Outdated

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"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Stumbled upon this through net searching: https://lists.llvm.org/pipermail/lldb-dev/2022-January/017189.html
  2. Typed apropos siginfo in LLDB which returned the thread siginfo command.
  3. 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this was pretty helpful. I've removed all references to gdb from aglib.

Comment thread pwndbg/aglib/signal.py Outdated
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

fault_mem_page may be None.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread pwndbg/aglib/signal.py
LONG_SEGV_DESCRIPTIONS: Dict[
pwndbg.lib.arch.PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, dict[Any, Callable[[], Optional[str]]]
] = {
"x86-64": LONG_SEGV_DESCRIPTIONS_X86_64,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mypy didn't catch it for some reason, but I've pushed some changes that should fix it

Comment thread pwndbg/aglib/signal.py Outdated

return desc_short, desc_long

except gdb.error:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would probably be except pwndbg.dbg_mod.Error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread pwndbg/commands/context.py Outdated
else:
try:
desc_short, desc_long = pwndbg.aglib.signal.get_segv_information()
si_addr = gdb.parse_and_eval("$_siginfo._sifields._sigfault.si_addr")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@disconnect3d disconnect3d Dec 27, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? Shouldn't we check this only against some kind of "selected thread"?

@@ -0,0 +1,37 @@
#define _GNU_SOURCE

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

with open("/proc/cpuinfo", "r") as f:
cpuinfo = f.read()
if "pku" not in cpuinfo:
pytest.skip("PKU not supported on this CPU")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@k4lizen k4lizen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! LGTM!

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread pwndbg/aglib/signal.py
"SIGXCPU",
"SIGXFSZ",
"SIGWINCH",
]

@k4lizen k4lizen Jan 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think an enum would make most sense here

@k4lizen k4lizen enabled auto-merge (squash) January 6, 2026 12:56
@k4lizen k4lizen merged commit 5a95649 into pwndbg:dev Jan 11, 2026
28 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants