From 59e306d7665ac63f061ce05f97357134b43b7603 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 26 May 2026 09:58:47 +0200 Subject: [PATCH 1/2] fix(native): escape JSON attachments File names and content types are arbitrary user-supplied strings that can contain characters that must be escaped in JSON. --- src/backends/native/sentry_crash_daemon.c | 60 +++++++++++++---------- src/sentry_json.c | 8 +-- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index be660d432..b38db64eb 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -113,32 +113,37 @@ write_attachment_to_envelope(int fd, const char *file_path, #endif // Write attachment item header - char header[SENTRY_CRASH_ENVELOPE_HEADER_SIZE]; - int header_written; + sentry_jsonwriter_t *jw = sentry__jsonwriter_new_sb(NULL); + if (!jw) { + SENTRY_WARN("Failed to create attachment header writer"); +#if defined(SENTRY_PLATFORM_UNIX) + close(attach_fd); +#elif defined(SENTRY_PLATFORM_WINDOWS) + _close(attach_fd); +#endif + return false; + } + + sentry__jsonwriter_write_object_start(jw); + sentry__jsonwriter_write_key(jw, "type"); + sentry__jsonwriter_write_str(jw, "attachment"); + sentry__jsonwriter_write_key(jw, "length"); + sentry__jsonwriter_write_int64(jw, file_size); + sentry__jsonwriter_write_key(jw, "attachment_type"); + sentry__jsonwriter_write_str(jw, + sentry__string_empty(attachment_type) ? SENTRY_ATTACHMENT_TYPE_GENERIC + : attachment_type); if (content_type) { - header_written = snprintf(header, sizeof(header), - "{\"type\":\"attachment\",\"length\":%lld," - "\"attachment_type\":\"%s\"," - "\"content_type\":\"%s\"," - "\"filename\":\"%s\"}\n", - file_size, - sentry__string_empty(attachment_type) - ? SENTRY_ATTACHMENT_TYPE_GENERIC - : attachment_type, - content_type, filename ? filename : "attachment"); - } else { - header_written = snprintf(header, sizeof(header), - "{\"type\":\"attachment\",\"length\":%lld," - "\"attachment_type\":\"%s\"," - "\"filename\":\"%s\"}\n", - file_size, - sentry__string_empty(attachment_type) - ? SENTRY_ATTACHMENT_TYPE_GENERIC - : attachment_type, - filename ? filename : "attachment"); - } - - if (header_written < 0 || header_written >= (int)sizeof(header)) { + sentry__jsonwriter_write_key(jw, "content_type"); + sentry__jsonwriter_write_str(jw, content_type); + } + sentry__jsonwriter_write_key(jw, "filename"); + sentry__jsonwriter_write_str(jw, filename ? filename : "attachment"); + sentry__jsonwriter_write_object_end(jw); + + size_t header_written = 0; + char *header = sentry__jsonwriter_into_string(jw, &header_written); + if (!header) { SENTRY_WARN("Failed to write attachment header"); #if defined(SENTRY_PLATFORM_UNIX) close(attach_fd); @@ -149,12 +154,15 @@ write_attachment_to_envelope(int fd, const char *file_path, } #if defined(SENTRY_PLATFORM_UNIX) - if (write(fd, header, header_written) != (ssize_t)header_written) { + if (write(fd, header, header_written) != (ssize_t)header_written + || write(fd, "\n", 1) != 1) { SENTRY_WARN("Failed to write attachment header to envelope"); } #elif defined(SENTRY_PLATFORM_WINDOWS) _write(fd, header, (unsigned int)header_written); + _write(fd, "\n", 1); #endif + sentry_free(header); // Copy attachment content char buf[SENTRY_CRASH_FILE_BUFFER_SIZE]; diff --git a/src/sentry_json.c b/src/sentry_json.c index 2f691be4c..785bac28b 100644 --- a/src/sentry_json.c +++ b/src/sentry_json.c @@ -267,8 +267,6 @@ write_json_str(sentry_jsonwriter_t *jw, const char *str) { // using unsigned here because utf-8 is > 127 :-) const unsigned char *ptr = (const unsigned char *)str; - write_char(jw, '"'); - const unsigned char *start = ptr; for (; *ptr; ptr++) { if (!needs_escaping[*ptr]) { @@ -322,8 +320,6 @@ write_json_str(sentry_jsonwriter_t *jw, const char *str) if (len) { jw->ops->write_buf(jw, (const char *)start, len); } - - write_char(jw, '"'); } static bool @@ -416,7 +412,9 @@ sentry__jsonwriter_write_str(sentry_jsonwriter_t *jw, const char *val) return; } if (can_write_item(jw)) { + write_char(jw, '"'); write_json_str(jw, val); + write_char(jw, '"'); } } @@ -445,7 +443,9 @@ void sentry__jsonwriter_write_key(sentry_jsonwriter_t *jw, const char *val) { if (can_write_item(jw)) { + write_char(jw, '"'); write_json_str(jw, val); + write_char(jw, '"'); write_char(jw, ':'); jw->last_was_key = true; } From e319974be7ae5d81f4242548b9704be2c0f56ced Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 30 May 2026 11:18:57 +0200 Subject: [PATCH 2/2] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76abc6fc9..e212d7cb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - Structured logs: respect printf argument widths when extracting log parameters to avoid stack-data disclosure and corrupted attributes on 32-bit platforms. ([#1752](https://github.com/getsentry/sentry-native/pull/1752)) - Fix a potential out-of-bounds read when parsing non-NUL-terminated `sentry-trace` headers. ([#1749](https://github.com/getsentry/sentry-native/pull/1749)) - Fix division by zero when breadcrumbs are disabled. ([#1767](https://github.com/getsentry/sentry-native/pull/1767)) +- Native: escape JSON attachments. ([#1771](https://github.com/getsentry/sentry-native/pull/1771)) ## 0.14.2