Skip to content

wip(native): add remote DWARF unwinding + symbol names for Linux#1747

Open
jpnurmi wants to merge 1 commit into
masterfrom
jpnurmi/feat/native/libunwind-remote
Open

wip(native): add remote DWARF unwinding + symbol names for Linux#1747
jpnurmi wants to merge 1 commit into
masterfrom
jpnurmi/feat/native/libunwind-remote

Conversation

@jpnurmi
Copy link
Copy Markdown
Collaborator

@jpnurmi jpnurmi commented May 22, 2026

Add libunwind remote unwinding support to the native backend's crash daemon, enabling DWARF-based stack walking and symbol name resolution for all threads (not just the crashing thread) on Linux.

  • Re-add upstream libunwind src/ptrace/ (_UPT_* accessors)
  • Add unwind_remote CMake target with G-prefix + ptrace sources, only built when SENTRY_BACKEND=native on Linux
  • New sentry_remote_unwind.c using unw_init_remote() + unw_get_proc_name()
  • Integrate into daemon's build_stacktrace_for_thread() with fallback to pre-captured backtrace and FP-walking
Before After
before after

P.S. I'll add support for demangling symbols to the crash reporter.

Add libunwind remote unwinding support to the native backend's crash
daemon, enabling DWARF-based stack walking and symbol name resolution
for all threads (not just the crashing thread) on Linux.

- Re-add upstream libunwind src/ptrace/ (_UPT_* accessors)
- Add `unwind_remote` CMake target with G-prefix + ptrace sources,
  only built when SENTRY_BACKEND=native on Linux
- New sentry_remote_unwind.c using unw_init_remote() + unw_get_proc_name()
- Integrate into daemon's build_stacktrace_for_thread() with fallback
  to pre-captured backtrace and FP-walking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Fails
🚫 Please consider adding a changelog entry for the next release.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

### Features

- add remote DWARF unwinding + symbol names for Linux ([#1747](https://github.com/getsentry/sentry-native/pull/1747))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description or adding a skip-changelog label.

Generated by 🚫 dangerJS against a6504fa

Comment on lines +948 to +950
if (stack_buf) {
sentry_free(stack_buf);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Double-free of stack_buf when remote unwinding succeeds but all frames are filtered

When remote_count > 0 but every frame fails the is_valid_code_addr check (leaving frame_count == 0), stack_buf is freed at line 948–950 without being set to NULL, and the code falls through to the pre-captured backtrace block (line 993–994) or the final cleanup block (line 1109–1110), where stack_buf is freed a second time. Set stack_buf = NULL immediately after the free.

Evidence
  • stack_buf is allocated at line ~767 or ~801 and starts non-NULL when stack memory was captured.
  • The new block (lines 948–950) calls sentry_free(stack_buf) unconditionally when remote_count > 0, but never assigns stack_buf = NULL.
  • If every frame produced by sentry__remote_unwind_thread satisfies ip == 0 || !is_valid_code_addr(ip), frame_count stays 0 and execution falls out of the if (frame_count > 0) branch.
  • At line 993–994 (pre-captured backtrace path) and line 1109–1110 (final cleanup), both guarded by if (stack_buf), the now-dangling pointer passes the NULL check and is freed again, causing a double-free heap corruption in the crash daemon process.

Suggested fix: Null stack_buf immediately after freeing it in the new block so subsequent guards correctly skip the second free.

Suggested change
if (stack_buf) {
sentry_free(stack_buf);
}
stack_buf = NULL;

Identified by Warden security-review · AUG-SHC

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a6504fa. Configure here.


if (stack_buf) {
sentry_free(stack_buf);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing NULL assignment causes use-after-free and double-free

High Severity

After sentry_free(stack_buf) at line 949, stack_buf is not set to NULL. When frame_count == 0 (all remote frames failed is_valid_code_addr), the code falls through without returning. Subsequent code at line 993 will double-free stack_buf, and if that path doesn't execute, line 1028's if (stack_buf && ...) check passes on the dangling pointer, causing use-after-free in read_stack_value. The final cleanup at line 1109 can also trigger another free of already-freed memory.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a6504fa. Configure here.

}

if (tid > 0) {
sentry_remote_frame_t remote_frames[MAX_STACK_FRAMES];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Large 34KB stack allocation in crash handler path

Low Severity

sentry_remote_frame_t remote_frames[MAX_STACK_FRAMES] allocates 128 × 272 = ~34KB on the stack. Each sentry_remote_frame_t contains a 256-byte symbol buffer. Combined with the existing temp_frames[128] array and other locals, this adds significant stack pressure in a crash-handling context where reliability matters most.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a6504fa. Configure here.

"instruction_addr",
sentry__value_new_addr(remote_frames[i].ip));
sentry_value_set_by_key(temp_frames[frame_count], "trust",
sentry_value_new_string(i == 0 ? "context" : "cfi"));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The code uses the raw loop index i to assign "context" trust. If the first frame is skipped, the next valid frame is incorrectly assigned "cfi" trust.
Severity: LOW

Suggested Fix

The trust assignment check should use frame_count == 0 instead of i == 0. This ensures that the first valid frame added to the backtrace correctly receives "context" trust, regardless of how many preceding frames were skipped.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/backends/native/sentry_crash_daemon.c#L937

Potential issue: The logic for assigning trust to stack frames uses the raw loop index
`i` to identify the first frame, which should receive `"context"` trust. However, the
loop may skip frames if their instruction pointer is invalid. If the first frame at `i
== 0` is filtered out, the next valid frame becomes the first frame in the final
backtrace but is incorrectly assigned `"cfi"` trust instead of `"context"`. This
mislabeling can affect the reliability and quality of symbolication, as `"context"`
frames are considered more trustworthy. The same bug pattern exists for both remote
unwinding and pre-captured backtraces.

Also affects:

  • src/backends/native/sentry_crash_daemon.c:977-991

Did we get this right? 👍 / 👎 to inform future reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant