Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Add a `transfer_timeout` option for SDK-managed HTTP transports. ([#1741](https://github.com/getsentry/sentry-native/pull/1741))
- Apple: use `os_sync_wait_on_address` for the level-triggered waitable flag in the batcher on modern macOS(14.4+) and iOS(17.4+). ([#1765](https://github.com/getsentry/sentry-native/pull/1765))
- Native/macOS: add thread names. ([#1766](https://github.com/getsentry/sentry-native/pull/1766))
- Native/Windows: add `attach_wer_report` option for attaching `Report.wer` generated by WER. ([#1777](https://github.com/getsentry/sentry-native/pull/1777))

**Fixes**:

Expand Down
3 changes: 3 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,9 @@ main(int argc, char **argv)
sentry_options_set_crash_upload_mode(
options, SENTRY_CRASH_UPLOAD_MODE_ASYNC);
}
if (has_arg(argc, argv, "attach-wer-report")) {
sentry_options_set_attach_wer_report(options, true);
}

// E2E test mode: generate unique test ID for event correlation
char e2e_test_id[37] = { 0 };
Expand Down
12 changes: 12 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1922,6 +1922,18 @@ SENTRY_API void sentry_options_set_crash_upload_mode(
SENTRY_API sentry_crash_upload_mode_t sentry_options_get_crash_upload_mode(
const sentry_options_t *opts);

/**
* Enables or disables attaching WER (Windows Error Reporting) reports. This is
* disabled by default.
*
* When enabled, a `Report.wer` file generated by WER is attached to crash
* events.
*
* This setting only has an effect when using the `native` backend on Windows.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_attach_wer_report(
sentry_options_t *opts, int val);

/**
* Enables a wait for the crash report upload to be finished before shutting
* down. This is disabled by default.
Expand Down
9 changes: 7 additions & 2 deletions src/backends/native/sentry_crash_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ typedef enum {
SENTRY_CRASH_STATE_READY = 0,
SENTRY_CRASH_STATE_CRASHED = 1,
SENTRY_CRASH_STATE_PROCESSING = 2,
SENTRY_CRASH_STATE_CAPTURED = 3,
SENTRY_CRASH_STATE_DONE = 4
SENTRY_CRASH_STATE_PROCESSED = 3,
SENTRY_CRASH_STATE_CAPTURED = 4,
SENTRY_CRASH_STATE_DONE = 5
} sentry_crash_state_t;

/**
Expand Down Expand Up @@ -242,6 +243,9 @@ typedef struct {
// Additional thread contexts
DWORD num_threads;
sentry_thread_context_windows_t threads[SENTRY_CRASH_MAX_THREADS];

bool wer_enabled;
char wer_report_id[64];
} sentry_crash_platform_windows_t;

typedef struct {
Expand Down Expand Up @@ -281,6 +285,7 @@ typedef struct {
bool attach_screenshot; // Screenshot attachment enabled in parent process
bool attach_session_replay; // Session replay attachment enabled in parent
// process
bool attach_wer_report; // WER report attachment enabled in parent process
uint32_t session_replay_duration; // Requested session replay duration in
// ms
int cache_keep; // sentry_cache_keep_t
Expand Down
199 changes: 198 additions & 1 deletion src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
# include <fcntl.h>
# include <io.h>
# include <sys/stat.h>
# include <werapi.h>
# include <windows.h>

// Forward declaration for StackWalk64-based stack unwinding (defined later)
Expand Down Expand Up @@ -2211,6 +2212,155 @@
ctx->crashed_pid);
# endif // SENTRY_PLATFORM_XBOX
}

typedef HRESULT(WINAPI *WerStoreOpen_t)(REPORT_STORE_TYPES, HREPORTSTORE *);
typedef void(WINAPI *WerStoreClose_t)(HREPORTSTORE);
typedef HRESULT(WINAPI *WerStoreGetFirstReportKey_t)(HREPORTSTORE, PCWSTR *);
typedef HRESULT(WINAPI *WerStoreGetNextReportKey_t)(HREPORTSTORE, PCWSTR *);
typedef HRESULT(WINAPI *WerStoreQueryReportMetadataV2_t)(
HREPORTSTORE, PCWSTR, PWER_REPORT_METADATA_V2);
typedef void(WINAPI *WerFreeString_t)(PCWSTR);

static struct {
HMODULE module;
WerStoreOpen_t WerStoreOpen;
WerStoreClose_t WerStoreClose;
WerStoreGetFirstReportKey_t WerStoreGetFirstReportKey;
WerStoreGetNextReportKey_t WerStoreGetNextReportKey;
WerStoreQueryReportMetadataV2_t WerStoreQueryReportMetadataV2;
WerFreeString_t WerFreeString;
} g_wer = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };

# define WER_FAILED ((HMODULE)(intptr_t)-1)

static bool
resolve_wer(void)
{
if (g_wer.module) {
return g_wer.module != WER_FAILED;
}

g_wer.module = LoadLibraryW(L"wer.dll");

Check notice on line 2243 in src/backends/native/sentry_crash_daemon.c

View check run for this annotation

@sentry/warden / warden: security-review

DLL search-order hijacking via unqualified LoadLibraryW("wer.dll")

`resolve_wer()` loads `wer.dll` by bare name without `LOAD_LIBRARY_SEARCH_SYSTEM32`, so the default DLL search order looks in the crash daemon's application directory before `System32`. If the daemon is installed in a directory writable by a lower-privileged local user (common for per-user installs), a planted `wer.dll` will be loaded and executed in the daemon's process. Use `LoadLibraryExW(L"wer.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)` to pin resolution to the system directory.
Comment thread
sentry-warden[bot] marked this conversation as resolved.
Outdated
if (!g_wer.module) {
g_wer.module = WER_FAILED;
return false;
}

g_wer.WerStoreOpen
= (WerStoreOpen_t)GetProcAddress(g_wer.module, "WerStoreOpen");
g_wer.WerStoreClose
= (WerStoreClose_t)GetProcAddress(g_wer.module, "WerStoreClose");
g_wer.WerStoreGetFirstReportKey
= (WerStoreGetFirstReportKey_t)GetProcAddress(
g_wer.module, "WerStoreGetFirstReportKey");
g_wer.WerStoreGetNextReportKey = (WerStoreGetNextReportKey_t)GetProcAddress(
g_wer.module, "WerStoreGetNextReportKey");
g_wer.WerStoreQueryReportMetadataV2
= (WerStoreQueryReportMetadataV2_t)GetProcAddress(
g_wer.module, "WerStoreQueryReportMetadataV2");
g_wer.WerFreeString
= (WerFreeString_t)GetProcAddress(g_wer.module, "WerFreeString");

if (!g_wer.WerStoreOpen || !g_wer.WerStoreClose
|| !g_wer.WerStoreGetFirstReportKey || !g_wer.WerStoreGetNextReportKey
|| !g_wer.WerStoreQueryReportMetadataV2 || !g_wer.WerFreeString) {
FreeLibrary(g_wer.module);
g_wer.module = WER_FAILED;
return false;
}

return true;
}

static sentry_path_t *
find_wer_report(sentry_uuid_t *report_id)
{
if (!resolve_wer()) {
return NULL;
}

sentry_path_t *report_path = NULL;
REPORT_STORE_TYPES store_types[]
= { E_STORE_USER_ARCHIVE, E_STORE_MACHINE_ARCHIVE };
for (size_t i = 0;
!report_path && i < sizeof(store_types) / sizeof(store_types[0]); i++) {
HREPORTSTORE report_store;
if (g_wer.WerStoreOpen(store_types[i], &report_store) != S_OK) {
continue;
}

PCWSTR report_key = NULL;
WER_REPORT_METADATA_V2 report_data = { 0 };

HRESULT hr = g_wer.WerStoreGetFirstReportKey(report_store, &report_key);
while (SUCCEEDED(hr) && report_key && !report_path) {
if (g_wer.WerStoreQueryReportMetadataV2(
report_store, report_key, &report_data)
== S_OK) {
if (sentry__uuid_equal_native(report_id, &report_data.ReportId)
|| sentry__uuid_equal_native(
report_id, &report_data.ReportIntegratorId)) {
sentry_path_t *report_dir
= sentry__path_from_wstr(report_key);
if (report_dir) {
report_path
= sentry__path_join_str(report_dir, "Report.wer");
sentry__path_free(report_dir);
}
}
}
g_wer.WerFreeString(report_key);
if (!report_path) {
hr = g_wer.WerStoreGetNextReportKey(report_store, &report_key);
}
}
Comment thread
sentry[bot] marked this conversation as resolved.

g_wer.WerStoreClose(report_store);
}

return report_path;
}

/**
* Reads a WER report, converts it to UTF-16LE -> UTF-8, and writes to the .run
* directory.
*
* TODO: use sentry__path_copy: https://github.com/getsentry/sentry/issues/91336
*/
static bool
write_wer_report(sentry_path_t *report_path, sentry_path_t *run_folder)
{
sentry_path_t *run_path = sentry__path_join_str(run_folder, "Report.wer");
if (!run_path) {
return false;
}

size_t utf16_size = 0;
char *utf16 = sentry__path_read_to_buffer(report_path, &utf16_size);
if (!utf16) {
SENTRY_WARN("Failed to read WER report");
return false;
}

char *utf8 = sentry__string_from_wstr_n(
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
(const wchar_t *)utf16, utf16_size / sizeof(wchar_t));
if (!utf8) {
SENTRY_WARN("Failed to convert WER report to UTF-8");
return false;
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}

int rv = sentry__path_write_buffer(run_path, utf8, strlen(utf8));

sentry_free(utf8);
sentry_free(utf16);
sentry__path_free(run_path);

if (rv != 0) {
SENTRY_WARN("Failed to write WER report");
return false;
}
return true;
}
#endif // SENTRY_PLATFORM_WINDOWS

/**
Expand Down Expand Up @@ -2819,6 +2969,17 @@
}
}

// Add Report.wer if captured by WER
if (ctx->attach_wer_report && run_folder) {
sentry_path_t *report_path
= sentry__path_join_str(run_folder, "Report.wer");
if (report_path) {
write_attachment_to_envelope(
fd, report_path->path, "Report.wer", NULL, "text/plain");
sentry__path_free(report_path);
}
}

#if defined(SENTRY_PLATFORM_UNIX)
close(fd);
#elif defined(SENTRY_PLATFORM_WINDOWS)
Expand Down Expand Up @@ -3071,6 +3232,17 @@
}
}

// Add Report.wer if captured by WER
if (ctx->attach_wer_report && run_folder) {
sentry_path_t *report_path
= sentry__path_join_str(run_folder, "Report.wer");
if (report_path) {
write_attachment_to_envelope(
fd, report_path->path, "Report.wer", NULL, "text/plain");
sentry__path_free(report_path);
}
}

#if defined(SENTRY_PLATFORM_UNIX)
close(fd);
#elif defined(SENTRY_PLATFORM_WINDOWS)
Expand Down Expand Up @@ -3288,7 +3460,8 @@
}
#endif

// On Windows, capture modules and threads from the crashed process
// On Windows, capture modules, threads, and WER report for the crashed
// process
#if defined(SENTRY_PLATFORM_WINDOWS)
if (use_native_mode) {
if (ctx->module_count == 0) {
Expand All @@ -3300,6 +3473,30 @@
enumerate_threads_from_process(ctx);
}
}

if (ctx->platform.wer_enabled && ctx->attach_wer_report && run_folder) {
SENTRY_DEBUG("Waiting for WER, allowing app process to exit");
sentry__atomic_store(&ctx->state, SENTRY_CRASH_STATE_PROCESSED);
Comment thread
jpnurmi marked this conversation as resolved.
Comment thread
jpnurmi marked this conversation as resolved.

Comment thread
jpnurmi marked this conversation as resolved.
int elapsed_ms = 0;
while (elapsed_ms < SENTRY_CRASH_HANDLER_WAIT_TIMEOUT_MS) {
if (!sentry__string_empty(ctx->platform.wer_report_id)) {
sentry_uuid_t report_id
= sentry_uuid_from_string(ctx->platform.wer_report_id);
sentry_path_t *wer_report_path = find_wer_report(&report_id);
Comment thread
sentry[bot] marked this conversation as resolved.
if (wer_report_path) {
SENTRY_DEBUGF("Found WER report %s: %s",
ctx->platform.wer_report_id, wer_report_path->path);
if (write_wer_report(wer_report_path, run_folder)) {
break;
}
}
Comment thread
cursor[bot] marked this conversation as resolved.
}
Comment thread
sentry[bot] marked this conversation as resolved.

Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS);
elapsed_ms += SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS;
}
}
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
#endif

// Write envelope based on mode
Expand Down
11 changes: 3 additions & 8 deletions src/backends/native/sentry_crash_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -1026,17 +1026,12 @@ crash_exception_filter(EXCEPTION_POINTERS *exception_info)

// Wait for daemon to finish processing (keep process alive for
// minidump)
bool processing_started = false;
int elapsed_ms = 0;
while (elapsed_ms < SENTRY_CRASH_HANDLER_WAIT_TIMEOUT_MS) {
long state = sentry__atomic_fetch(&ctx->state);
if (state == SENTRY_CRASH_STATE_PROCESSING && !processing_started) {
// Daemon started processing (no logging - exception filter
// context)
processing_started = true;
} else if (state >= SENTRY_CRASH_STATE_CAPTURED) {
// Daemon captured crash data (no logging - exception filter
// context)
if (state >= SENTRY_CRASH_STATE_PROCESSED) {
// Daemon already processed crash data (no logging - exception
// filter context)
break;
}
Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS);
Expand Down
Loading
Loading