Skip to content

Commit 638f899

Browse files
committed
ref: Address CWE-676 use of potentially dangerous functions
Replace sscanf with strtoull/strtod in report store and JSON codec to avoid CWE-676 flagged APIs on Apple platforms (scanf_s is unavailable). Add CWE-676 safety comments at all memcpy, strlen, and calloc sites documenting bounds or null-termination guarantees. Keep calloc for zero-initialization; add overflow guards where count*size could overflow (dirContents, image renderer). Add unit tests for report ID parsing and floating-point decode. Fixes #2785
1 parent 0e4a9dc commit 638f899

16 files changed

Lines changed: 187 additions & 33 deletions

Sources/Sentry/SentryAsyncSafeLog.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ static void
8282
writeToLog(const char *const str)
8383
{
8484
if (g_fd >= 0) {
85+
// CWE-676: str is from format/va_args; always null-terminated.
8586
int bytesToWrite = (int)strlen(str);
8687
const char *pos = str;
8788
while (bytesToWrite > 0) {

Sources/Sentry/SentrySessionReplaySyncC.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ sentrySessionReplaySync_start(const char *const path)
2222
free(crashReplay.path);
2323
}
2424

25+
// CWE-676: path is API input; caller must provide null-terminated string.
2526
size_t buffer_size = sizeof(char) * (strlen(path) + 1); // Add a byte for the null-terminator.
2627
crashReplay.path = malloc(buffer_size);
2728

Sources/SentryCrash/Recording/SentryCrash.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ - (NSData *)loadCrashReportJSONWithID:(int64_t)reportID
400400
{
401401
char *report = sentrycrash_readReport(reportID);
402402
if (report != NULL) {
403+
// CWE-676: sentrycrash_readReport returns null-terminated buffer.
403404
return [NSData dataWithBytesNoCopy:report length:strlen(report) freeWhenDone:YES];
404405
}
405406
return nil;

Sources/SentryCrash/Recording/SentryCrashCachedData.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,15 @@ createCache(void)
114114
return NULL;
115115
}
116116

117+
// CWE-676: calloc used for zero-initialization; prefer over malloc. Do not replace with malloc.
117118
SentryCrashThreadCacheData *cache = calloc(1, sizeof(*cache));
118119
if (cache == NULL) {
119120
SENTRY_ASYNC_SAFE_LOG_ERROR("Failed to allocate thread cache");
120121
goto cleanup_threads;
121122
}
122123

123124
cache->count = (int)threadCount;
125+
// threadCount is mach_msg_type_number_t (uint32_t); product cannot overflow size_t on 64-bit.
124126
cache->machThreads = calloc(threadCount, sizeof(*cache->machThreads));
125127
cache->pthreads = calloc(threadCount, sizeof(*cache->pthreads));
126128
cache->threadNames = calloc(threadCount, sizeof(*cache->threadNames));

Sources/SentryCrash/Recording/SentryCrashReport.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ static void
234234
addJSONElement(const SentryCrashReportWriter *const writer, const char *const key,
235235
const char *const jsonElement, bool closeLastContainer)
236236
{
237+
// CWE-676: jsonElement is internal JSON fragment; null-terminated.
237238
int jsonResult = sentrycrashjson_addJSONElement(
238239
getJsonContext(writer), key, jsonElement, (int)strlen(jsonElement), closeLastContainer);
239240
if (jsonResult != SentryCrashJSON_OK) {
@@ -779,6 +780,7 @@ writeAddressReferencedByString(
779780
const SentryCrashReportWriter *const writer, const char *const key, const char *string)
780781
{
781782
uint64_t address = 0;
783+
// CWE-676: string is report key value from our writer; null-terminated.
782784
if (string == NULL
783785
|| !sentrycrashstring_extractHexValue(string, (int)strlen(string), &address)) {
784786
return;
@@ -1471,6 +1473,7 @@ sentrycrashreport_writeRecrashReport(
14711473
SentryCrashBufferedWriter bufferedWriter;
14721474
static char tempPath[SentryCrashFU_MAX_PATH_LENGTH];
14731475
strlcpy(tempPath, path, sizeof(tempPath) - 10);
1476+
// CWE-676: tempPath just set by strlcpy; null-terminated. Overwrite ".json" with ".old".
14741477
strlcpy(tempPath + strlen(tempPath) - 5, ".old", 5);
14751478
SENTRY_ASYNC_SAFE_LOG_INFO("Writing recrash report to %s", path);
14761479

Sources/SentryCrash/Recording/SentryCrashReportFixer.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ onIntegerElement(const char *const name, const int64_t value, void *const userDa
135135
char buffer[21];
136136
sentrycrashdate_utcStringFromTimestamp((time_t)value, buffer);
137137

138+
// CWE-676: sentrycrashdate_utcStringFromTimestamp null-terminates buffer.
138139
result = sentrycrashjson_addStringElement(
139140
context->encodeContext, name, buffer, (int)strlen(buffer));
140141
} else {
@@ -163,6 +164,7 @@ onStringElement(const char *const name, const char *const value, void *const use
163164
FixupContext *context = (FixupContext *)userData;
164165
const char *stringValue = value;
165166

167+
// CWE-676: stringValue from JSON decode callback; decode produces null-terminated strings.
166168
int result = sentrycrashjson_addStringElement(
167169
context->encodeContext, name, stringValue, (int)strlen(stringValue));
168170

@@ -216,6 +218,7 @@ addJSONData(const char *data, int length, void *userData)
216218
if (length > context->outputBytesLeft) {
217219
return SentryCrashJSON_ERROR_DATA_TOO_LONG;
218220
}
221+
// CWE-676: Bounds checked above (length <= outputBytesLeft).
219222
memcpy(context->outputPtr, data, length);
220223
context->outputPtr += length;
221224
context->outputBytesLeft -= length;
@@ -249,6 +252,7 @@ sentrycrashcrf_fixupCrashReport(const char *crashReport)
249252
"Failed to allocate string buffer of size %ul", stringBufferLength);
250253
return NULL;
251254
}
255+
// CWE-676: crashReport is API input; caller must provide null-terminated string.
252256
int crashReportLength = (int)strlen(crashReport);
253257
int fixedReportLength = (int)(crashReportLength * 1.5);
254258
char *fixedReport = malloc((unsigned)fixedReportLength);
@@ -269,7 +273,7 @@ sentrycrashcrf_fixupCrashReport(const char *crashReport)
269273
sentrycrashjson_beginEncode(&encodeContext, true, addJSONData, &fixupContext);
270274

271275
int errorOffset = 0;
272-
int result = sentrycrashjson_decode(crashReport, (int)strlen(crashReport), stringBuffer,
276+
int result = sentrycrashjson_decode(crashReport, crashReportLength, stringBuffer,
273277
stringBufferLength, &callbacks, &fixupContext, &errorOffset);
274278
*fixupContext.outputPtr = '\0';
275279
free(stringBuffer);

Sources/SentryCrash/Recording/SentryCrashReportStore.c

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,26 +75,36 @@ getCrashReportPathByID(int64_t id, char *pathBuffer)
7575
static int64_t
7676
getReportIDFromFilename(const char *filename)
7777
{
78-
char scanFormat[SentryCrashCRS_MAX_PATH_LENGTH];
79-
snprintf(scanFormat, sizeof(scanFormat), "%s-report-%%" PRIx64 ".json", g_appName);
80-
81-
int64_t reportID = 0;
82-
sscanf(filename, scanFormat, &reportID);
83-
84-
return reportID;
78+
// Parse report ID from "AppName-report-<hex>.json" without sscanf (CWE-676).
79+
// g_appName set at init; null-terminated.
80+
const size_t appNameLen = strlen(g_appName);
81+
if (strncmp(filename, g_appName, appNameLen) != 0) {
82+
return 0;
83+
}
84+
const char *rest = filename + appNameLen;
85+
if (strncmp(rest, "-report-", 8) != 0) {
86+
return 0;
87+
}
88+
const char *hexStart = rest + 8;
89+
char *endPtr = NULL;
90+
const uint64_t id = strtoull(hexStart, &endPtr, 16);
91+
if (endPtr == hexStart) {
92+
return 0;
93+
}
94+
// Expect ".json" suffix
95+
if (strcmp(endPtr, ".json") != 0) {
96+
return 0;
97+
}
98+
return (int64_t)id;
8599
}
86100

87101
static int64_t
88102
getReportIDFromFilePath(const char *filepath)
89103
{
90-
char scanFormat[SentryCrashCRS_MAX_PATH_LENGTH];
91-
snprintf(
92-
scanFormat, sizeof(scanFormat), "%s/%s-report-%%" PRIx64 ".json", g_reportsPath, g_appName);
93-
94-
int64_t reportID = 0;
95-
sscanf(filepath, scanFormat, &reportID);
96-
97-
return reportID;
104+
// Parse report ID by taking the basename (after last '/') and reusing filename parsing.
105+
const char *lastSlash = strrchr(filepath, '/');
106+
const char *filename = lastSlash != NULL ? lastSlash + 1 : filepath;
107+
return getReportIDFromFilename(filename);
98108
}
99109

100110
static int

Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ addPair(SentryCrashImageToOriginalCxaThrowPair pair)
127127
return;
128128
}
129129
}
130+
// CWE-676: Fixed-size struct copy; array space ensured by realloc above.
130131
memcpy(&g_cxa_originals[g_cxa_originals_count++], &pair,
131132
sizeof(SentryCrashImageToOriginalCxaThrowPair));
132133
}

Sources/SentryCrash/Recording/Tools/SentryCrashFileUtils.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <dirent.h>
3333
#include <errno.h>
3434
#include <fcntl.h>
35+
#include <stdint.h>
3536
#include <stdio.h>
3637
#include <stdlib.h>
3738
#include <string.h>
@@ -120,6 +121,11 @@ dirContents(const char *path, char ***entries, int *count)
120121
goto done;
121122
}
122123

124+
// CWE-676: calloc used for zero-initialization; prefer over malloc. Guard against overflow.
125+
if (entryCount > SIZE_MAX / sizeof(char *)) {
126+
SENTRY_ASYNC_SAFE_LOG_ERROR("Directory entry count too large: %d", entryCount);
127+
goto done;
128+
}
123129
entryList = calloc((unsigned)entryCount, sizeof(char *));
124130
if (entryList != NULL) {
125131
struct dirent *ent;
@@ -184,6 +190,7 @@ deletePathContents(const char *path, bool deleteTopLevelPathAlso)
184190
int bufferLength = SentryCrashFU_MAX_PATH_LENGTH;
185191
char *pathBuffer = malloc((unsigned)bufferLength);
186192
snprintf(pathBuffer, bufferLength, "%s/", path);
193+
// CWE-676: pathBuffer just written by snprintf; null-terminated.
187194
char *pathPtr = pathBuffer + strlen(pathBuffer);
188195
int pathRemainingLength = bufferLength - (int)(pathPtr - pathBuffer);
189196

@@ -324,6 +331,7 @@ bool
324331
sentrycrashfu_writeStringToFD(const int fd, const char *const string)
325332
{
326333
if (*string != 0) {
334+
// CWE-676: string is API input; caller must provide null-terminated string.
327335
int bytesToWrite = (int)strlen(string);
328336
const char *pos = string;
329337
while (bytesToWrite > 0) {
@@ -471,6 +479,7 @@ sentrycrashfu_writeBufferedWriter(
471479
if (length > writer->bufferLength) {
472480
return sentrycrashfu_writeBytesToFD(writer->fd, data, length);
473481
}
482+
// CWE-676: length <= bufferLength - position ensured by flush/early return above.
474483
memcpy(writer->buffer + writer->position, data, length);
475484
writer->position += length;
476485
return true;
@@ -546,6 +555,7 @@ sentrycrashfu_readBufferedReader(SentryCrashBufferedReader *reader, char *dstBuf
546555
}
547556
int bytesToCopy = bytesInReader <= bytesRemaining ? bytesInReader : bytesRemaining;
548557
char *pSrc = reader->buffer + reader->dataStartPos;
558+
// CWE-676: bytesToCopy = min(bytesInReader, bytesRemaining); both bounds valid.
549559
memcpy(pDst, pSrc, bytesToCopy);
550560
pDst += bytesToCopy;
551561
reader->dataStartPos += bytesToCopy;
@@ -575,6 +585,7 @@ sentrycrashfu_readBufferedReaderUntilChar(
575585
bytesToCopy = bytesToChar;
576586
}
577587
}
588+
// CWE-676: bytesToCopy bounded by bytesInReader and bytesRemaining.
578589
memcpy(pDst, pSrc, bytesToCopy);
579590
pDst += bytesToCopy;
580591
reader->dataStartPos += bytesToCopy;

Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodec.c

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <limits.h>
3535
#include <math.h>
3636
#include <stdio.h>
37+
#include <stdlib.h>
3738
#include <string.h>
3839
#include <unistd.h>
3940

@@ -267,6 +268,7 @@ sentrycrashjson_beginElement(SentryCrashJSONEncodeContext *const context, const
267268
SENTRY_ASYNC_SAFE_LOG_DEBUG("Name was null inside an object");
268269
return SentryCrashJSON_ERROR_INVALID_DATA;
269270
}
271+
// CWE-676: name from encode API; null-terminated.
270272
unlikely_if((result = addQuotedEscapedString(context, name, (int)strlen(name)))
271273
!= SentryCrashJSON_OK)
272274
{
@@ -322,6 +324,7 @@ sentrycrashjson_addFloatingPointElement(
322324
unlikely_if(result != SentryCrashJSON_OK) { return result; }
323325
char buff[50];
324326
snprintf(buff, sizeof(buff), "%lg", value);
327+
// CWE-676: snprintf null-terminates buff.
325328
return addJSONData(context, buff, (int)strlen(buff));
326329
}
327330

@@ -333,6 +336,7 @@ sentrycrashjson_addIntegerElement(
333336
unlikely_if(result != SentryCrashJSON_OK) { return result; }
334337
char buff[30];
335338
snprintf(buff, sizeof(buff), "%" PRId64, value);
339+
// CWE-676: snprintf null-terminates buff.
336340
return addJSONData(context, buff, (int)strlen(buff));
337341
}
338342

@@ -344,6 +348,7 @@ sentrycrashjson_addUIntegerElement(
344348
unlikely_if(result != SentryCrashJSON_OK) { return result; }
345349
char buff[30];
346350
snprintf(buff, sizeof(buff), "%" PRIu64, value);
351+
// CWE-676: snprintf null-terminates buff.
347352
return addJSONData(context, buff, (int)strlen(buff));
348353
}
349354

@@ -363,6 +368,7 @@ sentrycrashjson_addStringElement(SentryCrashJSONEncodeContext *const context,
363368
int result = sentrycrashjson_beginElement(context, name);
364369
unlikely_if(result != SentryCrashJSON_OK) { return result; }
365370
if (length == SentryCrashJSON_SIZE_AUTOMATIC) {
371+
// CWE-676: value from encode API; null-terminated.
366372
length = (int)strlen(value);
367373
}
368374
return addQuotedEscapedString(context, value, length);
@@ -959,6 +965,7 @@ decodeString(SentryCrashJSONDecodeContext *context, char *dstBuffer, int dstBuff
959965
// If no escape characters were encountered, we can fast copy.
960966
likely_if(fastCopy)
961967
{
968+
// CWE-676: length < dstBufferLength checked above.
962969
memcpy(dstBuffer, src, length);
963970
dstBuffer[length] = 0;
964971
return SentryCrashJSON_OK;
@@ -1248,8 +1255,7 @@ decodeElement(const char *const name, SentryCrashJSONDecodeContext *context)
12481255
}
12491256

12501257
// our buffer is not necessarily NULL-terminated, so
1251-
// it would be undefined to call sscanf/sttod etc. directly.
1252-
// instead we create a temporary string.
1258+
// we create a temporary null-terminated string and use strtod (CWE-676: avoid sscanf).
12531259
double value;
12541260
int len = (int)(context->bufferPtr - start);
12551261
if (len >= context->stringBufferLength) {
@@ -1265,9 +1271,7 @@ decodeElement(const char *const name, SentryCrashJSONDecodeContext *context)
12651271
strncpy(context->stringBuffer, start, len);
12661272
context->stringBuffer[len] = '\0';
12671273

1268-
// Parses a floating point number from the string buffer into value using %lg format
1269-
// %lg uses shortest decimal representation and removes trailing zeros
1270-
sscanf(context->stringBuffer, "%lg", &value);
1274+
value = strtod(context->stringBuffer, NULL);
12711275

12721276
value *= sign;
12731277
return context->callbacks->onFloatingPointElement(name, value, context->userData);
@@ -1339,6 +1343,7 @@ updateDecoder_readFile(struct JSONFromFileContext *context)
13391343
unlikely_if(remainingLength < bufferLength / 2)
13401344
{
13411345
int fillLength = bufferLength - remainingLength;
1346+
// CWE-676: remainingLength <= bufferLength; start has bufferLength bytes.
13421347
memcpy(start, ptr, remainingLength);
13431348
context->decodeContext->bufferPtr = start;
13441349
int bytesRead = (int)read(context->fd, start + remainingLength, (unsigned)fillLength);
@@ -1415,6 +1420,7 @@ addJSONFromFile_onStringElement(
14151420
const char *const name, const char *const value, void *const userData)
14161421
{
14171422
JSONFromFileContext *context = (JSONFromFileContext *)userData;
1423+
// CWE-676: value from JSON decode; decode produces null-terminated strings.
14181424
int result
14191425
= sentrycrashjson_addStringElement(context->encodeContext, name, value, (int)strlen(value));
14201426
context->updateDecoderCallback(context);

0 commit comments

Comments
 (0)