diff --git a/Sources/Sentry/SentryAsyncSafeLog.c b/Sources/Sentry/SentryAsyncSafeLog.c index 5c9594eeb4d..f1f3286094e 100644 --- a/Sources/Sentry/SentryAsyncSafeLog.c +++ b/Sources/Sentry/SentryAsyncSafeLog.c @@ -82,6 +82,7 @@ static void writeToLog(const char *const str) { if (g_fd >= 0) { + // CWE-676: str is from format/va_args; always null-terminated. int bytesToWrite = (int)strlen(str); const char *pos = str; while (bytesToWrite > 0) { diff --git a/Sources/Sentry/SentrySessionReplaySyncC.c b/Sources/Sentry/SentrySessionReplaySyncC.c index d96e6b73932..dd47307a6be 100644 --- a/Sources/Sentry/SentrySessionReplaySyncC.c +++ b/Sources/Sentry/SentrySessionReplaySyncC.c @@ -22,6 +22,7 @@ sentrySessionReplaySync_start(const char *const path) free(crashReplay.path); } + // CWE-676: path is API input; caller must provide null-terminated string. size_t buffer_size = sizeof(char) * (strlen(path) + 1); // Add a byte for the null-terminator. crashReplay.path = malloc(buffer_size); diff --git a/Sources/SentryCrash/Recording/SentryCrash.m b/Sources/SentryCrash/Recording/SentryCrash.m index 786672a8074..2fdb7ce6538 100644 --- a/Sources/SentryCrash/Recording/SentryCrash.m +++ b/Sources/SentryCrash/Recording/SentryCrash.m @@ -400,6 +400,7 @@ - (NSData *)loadCrashReportJSONWithID:(int64_t)reportID { char *report = sentrycrash_readReport(reportID); if (report != NULL) { + // CWE-676: sentrycrash_readReport returns null-terminated buffer. return [NSData dataWithBytesNoCopy:report length:strlen(report) freeWhenDone:YES]; } return nil; diff --git a/Sources/SentryCrash/Recording/SentryCrashCachedData.c b/Sources/SentryCrash/Recording/SentryCrashCachedData.c index fd7d0d11881..027db8413d2 100644 --- a/Sources/SentryCrash/Recording/SentryCrashCachedData.c +++ b/Sources/SentryCrash/Recording/SentryCrashCachedData.c @@ -114,6 +114,7 @@ createCache(void) return NULL; } + // CWE-676: calloc used for zero-initialization; prefer over malloc. Do not replace with malloc. SentryCrashThreadCacheData *cache = calloc(1, sizeof(*cache)); if (cache == NULL) { SENTRY_ASYNC_SAFE_LOG_ERROR("Failed to allocate thread cache"); @@ -121,6 +122,7 @@ createCache(void) } cache->count = (int)threadCount; + // threadCount is mach_msg_type_number_t (uint32_t); product cannot overflow size_t on 64-bit. cache->machThreads = calloc(threadCount, sizeof(*cache->machThreads)); cache->pthreads = calloc(threadCount, sizeof(*cache->pthreads)); cache->threadNames = calloc(threadCount, sizeof(*cache->threadNames)); diff --git a/Sources/SentryCrash/Recording/SentryCrashReport.c b/Sources/SentryCrash/Recording/SentryCrashReport.c index 6973e6f16ed..fb48cbdb7e8 100644 --- a/Sources/SentryCrash/Recording/SentryCrashReport.c +++ b/Sources/SentryCrash/Recording/SentryCrashReport.c @@ -234,6 +234,7 @@ static void addJSONElement(const SentryCrashReportWriter *const writer, const char *const key, const char *const jsonElement, bool closeLastContainer) { + // CWE-676: jsonElement is internal JSON fragment; null-terminated. int jsonResult = sentrycrashjson_addJSONElement( getJsonContext(writer), key, jsonElement, (int)strlen(jsonElement), closeLastContainer); if (jsonResult != SentryCrashJSON_OK) { @@ -779,6 +780,7 @@ writeAddressReferencedByString( const SentryCrashReportWriter *const writer, const char *const key, const char *string) { uint64_t address = 0; + // CWE-676: string is report key value from our writer; null-terminated. if (string == NULL || !sentrycrashstring_extractHexValue(string, (int)strlen(string), &address)) { return; @@ -1471,6 +1473,7 @@ sentrycrashreport_writeRecrashReport( SentryCrashBufferedWriter bufferedWriter; static char tempPath[SentryCrashFU_MAX_PATH_LENGTH]; strlcpy(tempPath, path, sizeof(tempPath) - 10); + // CWE-676: tempPath just set by strlcpy; null-terminated. Overwrite ".json" with ".old". strlcpy(tempPath + strlen(tempPath) - 5, ".old", 5); SENTRY_ASYNC_SAFE_LOG_INFO("Writing recrash report to %s", path); diff --git a/Sources/SentryCrash/Recording/SentryCrashReportFixer.c b/Sources/SentryCrash/Recording/SentryCrashReportFixer.c index e4e4f1540cd..a4397316310 100644 --- a/Sources/SentryCrash/Recording/SentryCrashReportFixer.c +++ b/Sources/SentryCrash/Recording/SentryCrashReportFixer.c @@ -135,6 +135,7 @@ onIntegerElement(const char *const name, const int64_t value, void *const userDa char buffer[21]; sentrycrashdate_utcStringFromTimestamp((time_t)value, buffer); + // CWE-676: sentrycrashdate_utcStringFromTimestamp null-terminates buffer. result = sentrycrashjson_addStringElement( context->encodeContext, name, buffer, (int)strlen(buffer)); } else { @@ -163,6 +164,7 @@ onStringElement(const char *const name, const char *const value, void *const use FixupContext *context = (FixupContext *)userData; const char *stringValue = value; + // CWE-676: stringValue from JSON decode callback; decode produces null-terminated strings. int result = sentrycrashjson_addStringElement( context->encodeContext, name, stringValue, (int)strlen(stringValue)); @@ -216,6 +218,7 @@ addJSONData(const char *data, int length, void *userData) if (length > context->outputBytesLeft) { return SentryCrashJSON_ERROR_DATA_TOO_LONG; } + // CWE-676: Bounds checked above (length <= outputBytesLeft). memcpy(context->outputPtr, data, length); context->outputPtr += length; context->outputBytesLeft -= length; @@ -249,6 +252,7 @@ sentrycrashcrf_fixupCrashReport(const char *crashReport) "Failed to allocate string buffer of size %ul", stringBufferLength); return NULL; } + // CWE-676: crashReport is API input; caller must provide null-terminated string. int crashReportLength = (int)strlen(crashReport); int fixedReportLength = (int)(crashReportLength * 1.5); char *fixedReport = malloc((unsigned)fixedReportLength); @@ -269,7 +273,7 @@ sentrycrashcrf_fixupCrashReport(const char *crashReport) sentrycrashjson_beginEncode(&encodeContext, true, addJSONData, &fixupContext); int errorOffset = 0; - int result = sentrycrashjson_decode(crashReport, (int)strlen(crashReport), stringBuffer, + int result = sentrycrashjson_decode(crashReport, crashReportLength, stringBuffer, stringBufferLength, &callbacks, &fixupContext, &errorOffset); *fixupContext.outputPtr = '\0'; free(stringBuffer); diff --git a/Sources/SentryCrash/Recording/SentryCrashReportStore.c b/Sources/SentryCrash/Recording/SentryCrashReportStore.c index ff6649e3ee9..04bd59296ae 100644 --- a/Sources/SentryCrash/Recording/SentryCrashReportStore.c +++ b/Sources/SentryCrash/Recording/SentryCrashReportStore.c @@ -75,26 +75,36 @@ getCrashReportPathByID(int64_t id, char *pathBuffer) static int64_t getReportIDFromFilename(const char *filename) { - char scanFormat[SentryCrashCRS_MAX_PATH_LENGTH]; - snprintf(scanFormat, sizeof(scanFormat), "%s-report-%%" PRIx64 ".json", g_appName); - - int64_t reportID = 0; - sscanf(filename, scanFormat, &reportID); - - return reportID; + // Parse report ID from "AppName-report-.json" without sscanf (CWE-676). + // g_appName set at init; null-terminated. + const size_t appNameLen = strlen(g_appName); + if (strncmp(filename, g_appName, appNameLen) != 0) { + return 0; + } + const char *rest = filename + appNameLen; + if (strncmp(rest, "-report-", 8) != 0) { + return 0; + } + const char *hexStart = rest + 8; + char *endPtr = NULL; + const uint64_t id = strtoull(hexStart, &endPtr, 16); + if (endPtr == hexStart) { + return 0; + } + // Expect ".json" suffix + if (strcmp(endPtr, ".json") != 0) { + return 0; + } + return (int64_t)id; } static int64_t getReportIDFromFilePath(const char *filepath) { - char scanFormat[SentryCrashCRS_MAX_PATH_LENGTH]; - snprintf( - scanFormat, sizeof(scanFormat), "%s/%s-report-%%" PRIx64 ".json", g_reportsPath, g_appName); - - int64_t reportID = 0; - sscanf(filepath, scanFormat, &reportID); - - return reportID; + // Parse report ID by taking the basename (after last '/') and reusing filename parsing. + const char *lastSlash = strrchr(filepath, '/'); + const char *filename = lastSlash != NULL ? lastSlash + 1 : filepath; + return getReportIDFromFilename(filename); } static int diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.c b/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.c index e94cb3bcdab..f131d37888e 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.c +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.c @@ -127,6 +127,7 @@ addPair(SentryCrashImageToOriginalCxaThrowPair pair) return; } } + // CWE-676: Fixed-size struct copy; array space ensured by realloc above. memcpy(&g_cxa_originals[g_cxa_originals_count++], &pair, sizeof(SentryCrashImageToOriginalCxaThrowPair)); } diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashFileUtils.c b/Sources/SentryCrash/Recording/Tools/SentryCrashFileUtils.c index 8197363d91b..8f3c36eccf2 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashFileUtils.c +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashFileUtils.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,11 @@ dirContents(const char *path, char ***entries, int *count) goto done; } + // CWE-676: calloc used for zero-initialization; prefer over malloc. Guard against overflow. + if (entryCount > SIZE_MAX / sizeof(char *)) { + SENTRY_ASYNC_SAFE_LOG_ERROR("Directory entry count too large: %d", entryCount); + goto done; + } entryList = calloc((unsigned)entryCount, sizeof(char *)); if (entryList != NULL) { struct dirent *ent; @@ -184,6 +190,7 @@ deletePathContents(const char *path, bool deleteTopLevelPathAlso) int bufferLength = SentryCrashFU_MAX_PATH_LENGTH; char *pathBuffer = malloc((unsigned)bufferLength); snprintf(pathBuffer, bufferLength, "%s/", path); + // CWE-676: pathBuffer just written by snprintf; null-terminated. char *pathPtr = pathBuffer + strlen(pathBuffer); int pathRemainingLength = bufferLength - (int)(pathPtr - pathBuffer); @@ -324,6 +331,7 @@ bool sentrycrashfu_writeStringToFD(const int fd, const char *const string) { if (*string != 0) { + // CWE-676: string is API input; caller must provide null-terminated string. int bytesToWrite = (int)strlen(string); const char *pos = string; while (bytesToWrite > 0) { @@ -471,6 +479,7 @@ sentrycrashfu_writeBufferedWriter( if (length > writer->bufferLength) { return sentrycrashfu_writeBytesToFD(writer->fd, data, length); } + // CWE-676: length <= bufferLength - position ensured by flush/early return above. memcpy(writer->buffer + writer->position, data, length); writer->position += length; return true; @@ -546,6 +555,7 @@ sentrycrashfu_readBufferedReader(SentryCrashBufferedReader *reader, char *dstBuf } int bytesToCopy = bytesInReader <= bytesRemaining ? bytesInReader : bytesRemaining; char *pSrc = reader->buffer + reader->dataStartPos; + // CWE-676: bytesToCopy = min(bytesInReader, bytesRemaining); both bounds valid. memcpy(pDst, pSrc, bytesToCopy); pDst += bytesToCopy; reader->dataStartPos += bytesToCopy; @@ -575,6 +585,7 @@ sentrycrashfu_readBufferedReaderUntilChar( bytesToCopy = bytesToChar; } } + // CWE-676: bytesToCopy bounded by bytesInReader and bytesRemaining. memcpy(pDst, pSrc, bytesToCopy); pDst += bytesToCopy; reader->dataStartPos += bytesToCopy; diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodec.c b/Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodec.c index 6d9bff4cf95..5d0837fdbe0 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodec.c +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodec.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -267,6 +268,7 @@ sentrycrashjson_beginElement(SentryCrashJSONEncodeContext *const context, const SENTRY_ASYNC_SAFE_LOG_DEBUG("Name was null inside an object"); return SentryCrashJSON_ERROR_INVALID_DATA; } + // CWE-676: name from encode API; null-terminated. unlikely_if((result = addQuotedEscapedString(context, name, (int)strlen(name))) != SentryCrashJSON_OK) { @@ -322,6 +324,7 @@ sentrycrashjson_addFloatingPointElement( unlikely_if(result != SentryCrashJSON_OK) { return result; } char buff[50]; snprintf(buff, sizeof(buff), "%lg", value); + // CWE-676: snprintf null-terminates buff. return addJSONData(context, buff, (int)strlen(buff)); } @@ -333,6 +336,7 @@ sentrycrashjson_addIntegerElement( unlikely_if(result != SentryCrashJSON_OK) { return result; } char buff[30]; snprintf(buff, sizeof(buff), "%" PRId64, value); + // CWE-676: snprintf null-terminates buff. return addJSONData(context, buff, (int)strlen(buff)); } @@ -344,6 +348,7 @@ sentrycrashjson_addUIntegerElement( unlikely_if(result != SentryCrashJSON_OK) { return result; } char buff[30]; snprintf(buff, sizeof(buff), "%" PRIu64, value); + // CWE-676: snprintf null-terminates buff. return addJSONData(context, buff, (int)strlen(buff)); } @@ -363,6 +368,7 @@ sentrycrashjson_addStringElement(SentryCrashJSONEncodeContext *const context, int result = sentrycrashjson_beginElement(context, name); unlikely_if(result != SentryCrashJSON_OK) { return result; } if (length == SentryCrashJSON_SIZE_AUTOMATIC) { + // CWE-676: value from encode API; null-terminated. length = (int)strlen(value); } return addQuotedEscapedString(context, value, length); @@ -959,6 +965,7 @@ decodeString(SentryCrashJSONDecodeContext *context, char *dstBuffer, int dstBuff // If no escape characters were encountered, we can fast copy. likely_if(fastCopy) { + // CWE-676: length < dstBufferLength checked above. memcpy(dstBuffer, src, length); dstBuffer[length] = 0; return SentryCrashJSON_OK; @@ -1248,8 +1255,7 @@ decodeElement(const char *const name, SentryCrashJSONDecodeContext *context) } // our buffer is not necessarily NULL-terminated, so - // it would be undefined to call sscanf/sttod etc. directly. - // instead we create a temporary string. + // we create a temporary null-terminated string and use strtod (CWE-676: avoid sscanf). double value; int len = (int)(context->bufferPtr - start); if (len >= context->stringBufferLength) { @@ -1265,9 +1271,7 @@ decodeElement(const char *const name, SentryCrashJSONDecodeContext *context) strncpy(context->stringBuffer, start, len); context->stringBuffer[len] = '\0'; - // Parses a floating point number from the string buffer into value using %lg format - // %lg uses shortest decimal representation and removes trailing zeros - sscanf(context->stringBuffer, "%lg", &value); + value = strtod(context->stringBuffer, NULL); value *= sign; return context->callbacks->onFloatingPointElement(name, value, context->userData); @@ -1339,6 +1343,7 @@ updateDecoder_readFile(struct JSONFromFileContext *context) unlikely_if(remainingLength < bufferLength / 2) { int fillLength = bufferLength - remainingLength; + // CWE-676: remainingLength <= bufferLength; start has bufferLength bytes. memcpy(start, ptr, remainingLength); context->decodeContext->bufferPtr = start; int bytesRead = (int)read(context->fd, start + remainingLength, (unsigned)fillLength); @@ -1415,6 +1420,7 @@ addJSONFromFile_onStringElement( const char *const name, const char *const value, void *const userData) { JSONFromFileContext *context = (JSONFromFileContext *)userData; + // CWE-676: value from JSON decode; decode produces null-terminated strings. int result = sentrycrashjson_addStringElement(context->encodeContext, name, value, (int)strlen(value)); context->updateDecoderCallback(context); diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c b/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c index 56a0e8ab781..abcc4c32f96 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c @@ -139,6 +139,7 @@ sentrycrashmc_getContextForSignal( SENTRY_ASYNC_SAFE_LOG_DEBUG( "Get context from signal user context and put into %p.", destinationContext); _STRUCT_MCONTEXT *sourceContext = ((SignalUserContext *)signalUserContext)->UC_MCONTEXT; + // CWE-676: Fixed-size copy; source and destination are same struct type. memcpy(&destinationContext->machineContext, sourceContext, sizeof(destinationContext->machineContext)); destinationContext->thisThread = (thread_t)sentrycrashthread_self(); diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashObjC.c b/Sources/SentryCrash/Recording/Tools/SentryCrashObjC.c index bf9f4a4dc06..0fe470a4b9a 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashObjC.c +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashObjC.c @@ -1104,6 +1104,7 @@ sentrycrashobjc_ivarValue(const void *const objectPtr, int ivarIndex, void *dst) // Naively assume they want "value". if (isTaggedPointerNSDate(objectPtr)) { CFTimeInterval value = extractTaggedNSDate(objectPtr); + // CWE-676: Fixed-size copy; caller provides at least sizeof(value) bytes. memcpy(dst, &value, sizeof(value)); return true; } @@ -1111,6 +1112,7 @@ sentrycrashobjc_ivarValue(const void *const objectPtr, int ivarIndex, void *dst) // TODO: Correct to assume 64-bit signed int? What does the actual // ivar say? int64_t value = extractTaggedNSNumber(objectPtr); + // CWE-676: Fixed-size copy; caller provides at least sizeof(value) bytes. memcpy(dst, &value, sizeof(value)); return true; } @@ -1550,6 +1552,7 @@ taggedDateDescription(const void *object, char *buffer, int bufferLength) #define NSNUMBER_CASE(CFTYPE, RETURN_TYPE, CAST_TYPE, DATA) \ case CFTYPE: { \ RETURN_TYPE result; \ + /* CWE-676: DATA is __CFNumber _pad; fixed-size copy matching RETURN_TYPE. */ \ memcpy(&result, DATA, sizeof(result)); \ return (CAST_TYPE)result; \ } diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashSysCtl.c b/Sources/SentryCrash/Recording/Tools/SentryCrashSysCtl.c index ad8b89cf8f9..ce36a8bee91 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashSysCtl.c +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashSysCtl.c @@ -264,6 +264,7 @@ sentrycrashsysctl_getMacAddress(const char *const name, char *const macAddressBu struct if_msghdr *msgHdr = (struct if_msghdr *)ifBuffer; struct sockaddr_dl *sockaddr = (struct sockaddr_dl *)&msgHdr[1]; + // CWE-676: MAC address is always 6 bytes; caller provides 6-byte buffer (see header). memcpy(macAddressBuffer, LLADDR(sockaddr), 6); free(ifBuffer); diff --git a/Sources/Swift/Core/Tools/ViewCapture/SentryGraphicsImageRenderer.swift b/Sources/Swift/Core/Tools/ViewCapture/SentryGraphicsImageRenderer.swift index f58c1f52f73..7d67fefe692 100644 --- a/Sources/Swift/Core/Tools/ViewCapture/SentryGraphicsImageRenderer.swift +++ b/Sources/Swift/Core/Tools/ViewCapture/SentryGraphicsImageRenderer.swift @@ -48,9 +48,15 @@ final class SentryGraphicsImageRenderer { let bytesPerRow = bytesPerPixel * pixelsPerRow let bitsPerComponent = 8 // 8 bits for each of RGB component - // Allocate memory for raw image data and initializes every byte in the allocated memory to 0. - guard let rawData = calloc(pixelsPerColumn * bytesPerRow, MemoryLayout.size) else { - SentrySDKLog.error("Unable to allocate memory for image data") + // CWE-676: calloc used for zero-initialization; prefer over malloc. Guard against overflow. + let byteCount = pixelsPerColumn.multipliedReportingOverflow(by: bytesPerRow) + guard !byteCount.overflow, byteCount.partialValue > 0, + let rawData = calloc(byteCount.partialValue, MemoryLayout.size) else { + if byteCount.overflow { + SentrySDKLog.error("Image dimensions cause allocation overflow") + } else { + SentrySDKLog.error("Unable to allocate memory for image data") + } return UIImage() } defer { diff --git a/Tests/SentryTests/SentryCrash/SentryCrashJSONCodec_Tests.m b/Tests/SentryTests/SentryCrash/SentryCrashJSONCodec_Tests.m index a1fadfe1d49..d8bb14e272a 100644 --- a/Tests/SentryTests/SentryCrash/SentryCrashJSONCodec_Tests.m +++ b/Tests/SentryTests/SentryCrash/SentryCrashJSONCodec_Tests.m @@ -884,6 +884,33 @@ - (void)testSerializeDeserializeNANDouble XCTAssertTrue([[result objectAtIndex:0] isKindOfClass:[NSNull class]]); } +- (void)testDecodeFloatingPoint_whenVariousFormats_shouldParseLikeStrtod +{ + // CWE-676: Verifies floating-point decode path uses strtod (replacing sscanf "%lg") correctly. + NSError *error = nil; + + // -- Arrange & Act & Assert: decimal + id result1 = [SentryCrashJSONCodec decode:toData(@"[1.5]") options:0 error:&error]; + XCTAssertNotNil(result1, @""); + XCTAssertNil(error, @""); + XCTAssertEqualWithAccuracy([[result1 objectAtIndex:0] doubleValue], 1.5, 0.0001, @""); + + // -- Zero + id result2 = [SentryCrashJSONCodec decode:toData(@"[0]") options:0 error:&error]; + XCTAssertNotNil(result2, @""); + XCTAssertEqual([[result2 objectAtIndex:0] doubleValue], 0.0, @""); + + // -- Scientific notation + id result3 = [SentryCrashJSONCodec decode:toData(@"[1e10]") options:0 error:&error]; + XCTAssertNotNil(result3, @""); + XCTAssertEqualWithAccuracy([[result3 objectAtIndex:0] doubleValue], 1e10, 1e3, @""); + + // -- Negative zero + id result4 = [SentryCrashJSONCodec decode:toData(@"[-0.0]") options:0 error:&error]; + XCTAssertNotNil(result4, @""); + XCTAssertEqual([[result4 objectAtIndex:0] doubleValue], -0.0, @""); +} + - (void)testSerializeDeserializeChar { NSError *error = (NSError *)self; diff --git a/Tests/SentryTests/SentryCrash/SentryCrashReportStore_Tests.m b/Tests/SentryTests/SentryCrash/SentryCrashReportStore_Tests.m index 69e5066040a..a7b6a2020e2 100644 --- a/Tests/SentryTests/SentryCrash/SentryCrashReportStore_Tests.m +++ b/Tests/SentryTests/SentryCrash/SentryCrashReportStore_Tests.m @@ -31,6 +31,8 @@ #import "SentryCrashReportStore.h" #include +#include +#include #define REPORT_PREFIX @"CrashReport-SentryCrashTest" @@ -50,16 +52,22 @@ @implementation SentryCrashReportStore_Tests - (int64_t)getReportIDFromPath:(NSString *)path { - const char *filename = path.lastPathComponent.UTF8String; - char scanFormat[100]; - snprintf( - scanFormat, sizeof(scanFormat), "%s-report-%%" PRIx64 ".json", self.appName.UTF8String); - - int64_t reportID = 0; - sscanf(filename, scanFormat, &reportID); - - return reportID; + const char *appName = self.appName.UTF8String; + const size_t appNameLen = strlen(appName); + if (strncmp(filename, appName, appNameLen) != 0) { + return 0; + } + if (strncmp(filename + appNameLen, "-report-", 8) != 0) { + return 0; + } + const char *hexStart = filename + appNameLen + 8; + char *endPtr = NULL; + const uint64_t id = strtoull(hexStart, &endPtr, 16); + if (endPtr == hexStart || strcmp(endPtr, ".json") != 0) { + return 0; + } + return (int64_t)id; } - (void)setUp @@ -137,6 +145,74 @@ - (void)expectReports:(NSArray *)reportIDs areStrings:(NSArray *)reportStrings } } +- (void)testGetReportIDFromPath_whenValidFilename_shouldReturnReportID +{ + // -- Arrange -- + self.appName = @"myapp"; + NSString *path = + [self.tempPath stringByAppendingPathComponent:@"myapp-report-0000000000000001.json"]; + + // -- Act -- + int64_t reportID = [self getReportIDFromPath:path]; + + // -- Assert -- + XCTAssertEqual(reportID, 1); +} + +- (void)testGetReportIDFromPath_whenValidHexID_shouldReturnReportID +{ + // -- Arrange -- + self.appName = @"myapp"; + NSString *path = [self.tempPath stringByAppendingPathComponent:@"myapp-report-abc123def.json"]; + + // -- Act -- + int64_t reportID = [self getReportIDFromPath:path]; + + // -- Assert -- + XCTAssertEqual(reportID, (int64_t)0xabc123def); +} + +- (void)testGetReportIDFromPath_whenWrongPrefix_shouldReturnZero +{ + // -- Arrange -- + self.appName = @"myapp"; + NSString *path = + [self.tempPath stringByAppendingPathComponent:@"other-report-0000000000000001.json"]; + + // -- Act -- + int64_t reportID = [self getReportIDFromPath:path]; + + // -- Assert -- + XCTAssertEqual(reportID, 0); +} + +- (void)testGetReportIDFromPath_whenInvalidSuffix_shouldReturnZero +{ + // -- Arrange -- + self.appName = @"myapp"; + NSString *path = + [self.tempPath stringByAppendingPathComponent:@"myapp-report-0000000000000001.txt"]; + + // -- Act -- + int64_t reportID = [self getReportIDFromPath:path]; + + // -- Assert -- + XCTAssertEqual(reportID, 0); +} + +- (void)testGetReportIDFromPath_whenNoReportSegment_shouldReturnZero +{ + // -- Arrange -- + self.appName = @"myapp"; + NSString *path = [self.tempPath stringByAppendingPathComponent:@"myapp-0000000000000001.json"]; + + // -- Act -- + int64_t reportID = [self getReportIDFromPath:path]; + + // -- Assert -- + XCTAssertEqual(reportID, 0); +} + - (void)testReportStorePathExists { [self prepareReportStoreWithPathEnd:@"somereports/blah/2/x"];