Skip to content

Commit

Permalink
fix: Crash in baggageEncodedDictionary (#4017)
Browse files Browse the repository at this point in the history
The baggageEncodedDictionary method sometimes crashes with a null
pointer exception in CFCharacterSetAddCharactersInString. It seems
mutableCopy of NSCharacterSet.alphanumericCharacterSet or when calling
stringByAddingPercentEncodingWithAllowedCharacters sometimes doesn't
work. Instead of trying to fix the underlying issue of a null pointer
exception, we can convert the code to Swift, designed for safety to
avoid such mistakes. This should prevent the crash.


Co-authored-by: Dhiogo Brustolin <dhiogo.brustolin@sentry.io>
  • Loading branch information
philipphofmann and brustolin authored May 28, 2024
1 parent b0e218b commit 77c9c47
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 93 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Fix retrieving GraphQL operation names crashing ([#3973](https://github.com/getsentry/sentry-cocoa/pull/3973))
- Fix SentryCrashExceptionApplication subclass problem (#3993)
- Fix wrong value for `In Foreground` flag on UIKit applications (#4005)
- Fix a crash in baggageEncodedDictionary (#4017)
- Session replay wrong video size (#4018)

## 8.26.0
Expand Down
8 changes: 8 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
622C08DB29E554B9002571D4 /* SentrySpanContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 622C08D929E554B9002571D4 /* SentrySpanContext+Private.h */; };
62375FB92B47F9F000CC55F1 /* SentryDependencyContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62375FB82B47F9F000CC55F1 /* SentryDependencyContainerTests.swift */; };
623C45B02A651D8200D9E88B /* SentryCoreDataTracker+Test.m in Sources */ = {isa = PBXBuildFile; fileRef = 623C45AF2A651D8200D9E88B /* SentryCoreDataTracker+Test.m */; };
624688192C048EF10006179C /* SentryBaggageSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624688182C048EF10006179C /* SentryBaggageSerialization.swift */; };
626866722BA89641006995EA /* MetricsAggregator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626866712BA89641006995EA /* MetricsAggregator.swift */; };
626866742BA89683006995EA /* BucketMetricsAggregatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626866732BA89683006995EA /* BucketMetricsAggregatorTests.swift */; };
626866762BA896AD006995EA /* TestMetricsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626866752BA896AD006995EA /* TestMetricsClient.swift */; };
Expand Down Expand Up @@ -131,6 +132,7 @@
62E146D02BAAE47600ED34FD /* LocalMetricsAggregator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E146CF2BAAE47600ED34FD /* LocalMetricsAggregator.swift */; };
62E146D22BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E146D12BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift */; };
62F226B729A37C120038080D /* SentryBooleanSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F226B629A37C120038080D /* SentryBooleanSerialization.m */; };
62F4DDA12C04CB9700588890 /* SentryBaggageSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F4DDA02C04CB9700588890 /* SentryBaggageSerializationTests.swift */; };
62FC69362BEDFF18002D3EF2 /* SentryLogExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FC69352BEDFF18002D3EF2 /* SentryLogExtensions.swift */; };
630435FE1EBCA9D900C4D3FA /* SentryNSURLRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 630435FC1EBCA9D900C4D3FA /* SentryNSURLRequest.h */; };
630435FF1EBCA9D900C4D3FA /* SentryNSURLRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 630435FD1EBCA9D900C4D3FA /* SentryNSURLRequest.m */; };
Expand Down Expand Up @@ -1060,6 +1062,7 @@
62375FB82B47F9F000CC55F1 /* SentryDependencyContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDependencyContainerTests.swift; sourceTree = "<group>"; };
623C45AE2A651C4500D9E88B /* SentryCoreDataTracker+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCoreDataTracker+Test.h"; sourceTree = "<group>"; };
623C45AF2A651D8200D9E88B /* SentryCoreDataTracker+Test.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryCoreDataTracker+Test.m"; sourceTree = "<group>"; };
624688182C048EF10006179C /* SentryBaggageSerialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryBaggageSerialization.swift; sourceTree = "<group>"; };
626866712BA89641006995EA /* MetricsAggregator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsAggregator.swift; sourceTree = "<group>"; };
626866732BA89683006995EA /* BucketMetricsAggregatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BucketMetricsAggregatorTests.swift; sourceTree = "<group>"; };
626866752BA896AD006995EA /* TestMetricsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMetricsClient.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1103,6 +1106,7 @@
62E146D12BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMetricsAggregatorTests.swift; sourceTree = "<group>"; };
62F226B629A37C120038080D /* SentryBooleanSerialization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryBooleanSerialization.m; sourceTree = "<group>"; };
62F226B829A37C270038080D /* SentryBooleanSerialization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryBooleanSerialization.h; sourceTree = "<group>"; };
62F4DDA02C04CB9700588890 /* SentryBaggageSerializationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryBaggageSerializationTests.swift; sourceTree = "<group>"; };
62FC69352BEDFF18002D3EF2 /* SentryLogExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogExtensions.swift; sourceTree = "<group>"; };
630435FC1EBCA9D900C4D3FA /* SentryNSURLRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryNSURLRequest.h; path = include/SentryNSURLRequest.h; sourceTree = "<group>"; };
630435FD1EBCA9D900C4D3FA /* SentryNSURLRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryNSURLRequest.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2058,6 +2062,7 @@
D8739CF62BECFF86007D2F66 /* Log */,
621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */,
621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */,
624688182C048EF10006179C /* SentryBaggageSerialization.swift */,
);
path = Helper;
sourceTree = "<group>";
Expand Down Expand Up @@ -3021,6 +3026,7 @@
children = (
849AC3FF29E0C1FF00889C16 /* SentryFormatterTests.swift */,
7B88F30324BC8E6500ADF90A /* SentrySerializationTests.swift */,
62F4DDA02C04CB9700588890 /* SentryBaggageSerializationTests.swift */,
15E0A8EF240F638200F044E3 /* SentrySerializationNilTests.m */,
7BA61EA525F21E660008CAA2 /* SentryLogTests.swift */,
7BA61EAB25F2206E0008CAA2 /* SentryLog+TestInit.h */,
Expand Down Expand Up @@ -4451,6 +4457,7 @@
D8F67B222BEAB6CC00C9197B /* SentryRRWebEvent.swift in Sources */,
7B7A599726B692F00060A676 /* SentryScreenFrames.m in Sources */,
7B3398652459C15200BD9C96 /* SentryEnvelopeRateLimit.m in Sources */,
624688192C048EF10006179C /* SentryBaggageSerialization.swift in Sources */,
D81988C92BEC19200020E36C /* SentryRRWebBreadcrumbEvent.swift in Sources */,
0A2D8D9628997845008720F6 /* NSLocale+Sentry.m in Sources */,
7B0DC730288698F70039995F /* NSMutableDictionary+Sentry.m in Sources */,
Expand Down Expand Up @@ -4825,6 +4832,7 @@
7B6CC50224EE5A42001816D7 /* SentryHubTests.swift in Sources */,
7BF9EF882722D13000B5BBEF /* SentryTestObjCRuntimeWrapper.m in Sources */,
7B2A70DF27D60904008B0D15 /* SentryTestThreadWrapper.swift in Sources */,
62F4DDA12C04CB9700588890 /* SentryBaggageSerializationTests.swift in Sources */,
7BE912AF272166DD00E49E62 /* SentryNoOpSpanTests.swift in Sources */,
62991A8F2BAC24ED0078A8B8 /* SentryMetricsAPITests.swift in Sources */,
6229416A2BB2F123004765D1 /* SentryNSDataUtilsTests.swift in Sources */,
Expand Down
5 changes: 2 additions & 3 deletions Sources/Sentry/SentryBaggage.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#import "SentryLog.h"
#import "SentryOptions+Private.h"
#import "SentryScope+Private.h"
#import "SentrySerialization.h"
#import "SentrySwift.h"
#import "SentryTraceContext.h"
#import "SentryTracer.h"
Expand Down Expand Up @@ -39,7 +38,7 @@ - (instancetype)initWithTraceId:(SentryId *)traceId

- (NSString *)toHTTPHeaderWithOriginalBaggage:(NSDictionary *_Nullable)originalBaggage
{
NSMutableDictionary *information
NSMutableDictionary<NSString *, NSString *> *information
= originalBaggage.mutableCopy ?: [[NSMutableDictionary alloc] init];

[information setValue:_traceId.sentryIdString forKey:@"sentry-trace_id"];
Expand Down Expand Up @@ -73,7 +72,7 @@ - (NSString *)toHTTPHeaderWithOriginalBaggage:(NSDictionary *_Nullable)originalB
[information setValue:_replayId forKey:@"sentry-replay_id"];
}

return [SentrySerialization baggageEncodedDictionary:information];
return [SentryBaggageSerialization encodeDictionary:information];
}

@end
4 changes: 2 additions & 2 deletions Sources/Sentry/SentryNetworkTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ - (void)addBaggageHeader:(SentryBaggage *)baggage
NSString *baggageHeader = @"";

if (baggage != nil) {
NSDictionary *originalBaggage = [SentrySerialization
decodeBaggage:sessionTask.currentRequest.allHTTPHeaderFields[SENTRY_BAGGAGE_HEADER]];
NSDictionary *originalBaggage = [SentryBaggageSerialization
decode:sessionTask.currentRequest.allHTTPHeaderFields[SENTRY_BAGGAGE_HEADER]];

if (originalBaggage[@"sentry-trace_id"] == nil) {
baggageHeader = [baggage toHTTPHeaderWithOriginalBaggage:originalBaggage];
Expand Down
51 changes: 0 additions & 51 deletions Sources/Sentry/SentrySerialization.m
Original file line number Diff line number Diff line change
Expand Up @@ -80,57 +80,6 @@ + (NSData *_Nullable)dataWithEnvelope:(SentryEnvelope *)envelope
return envelopeData;
}

+ (NSString *)baggageEncodedDictionary:(NSDictionary *)dictionary
{
NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:dictionary.count];

NSMutableCharacterSet *allowedSet = [NSCharacterSet.alphanumericCharacterSet mutableCopy];
[allowedSet addCharactersInString:@"-_."];
NSInteger currentSize = 0;

for (id key in dictionary.allKeys) {
id value = dictionary[key];
NSString *keyDescription =
[[key description] stringByAddingPercentEncodingWithAllowedCharacters:allowedSet];
NSString *valueDescription =
[[value description] stringByAddingPercentEncodingWithAllowedCharacters:allowedSet];

NSString *item = [NSString stringWithFormat:@"%@=%@", keyDescription, valueDescription];
if (item.length + currentSize <= SENTRY_BAGGAGE_MAX_SIZE) {
currentSize += item.length
+ 1; // +1 is to account for the comma that will be added for each extra itemapp
[items addObject:item];
}
}

return [[items sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
return [obj1 compare:obj2];
}] componentsJoinedByString:@","];
}

+ (NSDictionary<NSString *, NSString *> *)decodeBaggage:(NSString *)baggage
{
if (baggage == nil || baggage.length == 0) {
return @{};
}

NSMutableDictionary *decoded = [[NSMutableDictionary alloc] init];

NSArray<NSString *> *properties = [baggage componentsSeparatedByString:@","];

for (NSString *property in properties) {
NSArray<NSString *> *parts = [property componentsSeparatedByString:@"="];
if (parts.count != 2) {
continue;
}
NSString *key = parts[0];
NSString *value = [parts[1] stringByRemovingPercentEncoding];
decoded[key] = value;
}

return decoded.copy;
}

+ (SentryEnvelope *_Nullable)envelopeWithData:(NSData *)data
{
SentryEnvelopeHeader *envelopeHeader = nil;
Expand Down
5 changes: 0 additions & 5 deletions Sources/Sentry/include/SentrySerialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@

NS_ASSUME_NONNULL_BEGIN

static int const SENTRY_BAGGAGE_MAX_SIZE = 8192;

@interface SentrySerialization : NSObject

+ (NSData *_Nullable)dataWithJSONObject:(id)jsonObject;

+ (NSData *_Nullable)dataWithSession:(SentrySession *)session;

+ (NSDictionary<NSString *, NSString *> *)decodeBaggage:(NSString *)baggage;
+ (NSString *)baggageEncodedDictionary:(NSDictionary *)dictionary;

+ (SentrySession *_Nullable)sessionWithData:(NSData *)sessionData;

+ (NSData *_Nullable)dataWithEnvelope:(SentryEnvelope *)envelope
Expand Down
54 changes: 54 additions & 0 deletions Sources/Swift/Helper/SentryBaggageSerialization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Foundation

@objcMembers
class SentryBaggageSerialization: NSObject {

private static let SENTRY_BAGGAGE_MAX_SIZE = 8_192

static func encodeDictionary(_ dictionary: [String: String]) -> String {
var items: [String] = []
items.reserveCapacity(dictionary.count)

var allowedSet = CharacterSet.alphanumerics
allowedSet.insert(charactersIn: "-_.")
var currentSize = 0

for (key, value) in dictionary {
guard let keyDescription = key.addingPercentEncoding(withAllowedCharacters: allowedSet),
let valueDescription = value.addingPercentEncoding(withAllowedCharacters: allowedSet), !keyDescription.isEmpty && !valueDescription.isEmpty else {
continue
}

let item = "\(keyDescription)=\(valueDescription)"
if item.count + currentSize <= SENTRY_BAGGAGE_MAX_SIZE {
currentSize += item.count + 1 // +1 is to account for the comma that will be added for each extra item
items.append(item)
}
}

return items.sorted().joined(separator: ",")
}

static func decode(_ baggage: String) -> [String: String] {
guard !baggage.isEmpty else {
return [:]
}

var decoded: [String: String] = [:]

let properties = baggage.components(separatedBy: ",")

for property in properties {
let parts = property.components(separatedBy: "=")
guard parts.count == 2 else {
continue
}
let key = parts[0]
if let value = parts[1].removingPercentEncoding {
decoded[key] = value
}
}

return decoded
}
}
57 changes: 57 additions & 0 deletions Tests/SentryTests/Helper/SentryBaggageSerializationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Foundation
@testable import Sentry
import XCTest

class SentryBaggageSerializationTests: XCTestCase {

func testDictionaryToBaggageEncoded() {
XCTAssertEqual(encodeDictionary(["key": "value"]), "key=value")
XCTAssertEqual(encodeDictionary(["key": "value", "key2": "value2"]), "key2=value2,key=value")
XCTAssertEqual(encodeDictionary(["key": "value&"]), "key=value%26")
XCTAssertEqual(encodeDictionary(["key": "value="]), "key=value%3D")
XCTAssertEqual(encodeDictionary(["key": "value "]), "key=value%20")
XCTAssertEqual(encodeDictionary(["key": "value%"]), "key=value%25")
XCTAssertEqual(encodeDictionary(["key": "value-_"]), "key=value-_")
XCTAssertEqual(encodeDictionary(["key": "value\n\r"]), "key=value%0A%0D")

let largeValue = String(repeating: "a", count: 8_188)

XCTAssertEqual(encodeDictionary(["key": largeValue]), "key=\(largeValue)")
XCTAssertEqual(encodeDictionary(["AKey": "something", "BKey": largeValue]), "AKey=something")
XCTAssertEqual(encodeDictionary(["AKey": "something", "BKey": largeValue, "CKey": "Other Value"]), "AKey=something,CKey=Other%20Value")
}

func testBaggageEmptyKey_ReturnsEmptyString() {
XCTAssertEqual(encodeDictionary(["key": ""]), "")
}

func testBaggageEmptyValue_ReturnsEmptyString() {
XCTAssertEqual(encodeDictionary(["": "value"]), "")
}

func testBaggageEmptyKeyAndValue_ReturnsEmptyString() {
XCTAssertEqual(encodeDictionary(["": ""]), "")
}

func testBaggageStringToDictionaryDecoded() {
XCTAssertEqual(decode("key=value"), ["key": "value"])
XCTAssertEqual(decode("key2=value2,key=value"), ["key": "value", "key2": "value2"])
XCTAssertEqual(decode("key=value%26"), ["key": "value&"])
XCTAssertEqual(decode("key=value%3D"), ["key": "value="])
XCTAssertEqual(decode("key=value%20"), ["key": "value "])
XCTAssertEqual(decode("key=value%25"), ["key": "value%"])
XCTAssertEqual(decode("key=value-_"), ["key": "value-_"])
XCTAssertEqual(decode("key=value%0A%0D"), ["key": "value\n\r"])
XCTAssertEqual(decode(""), [:])
XCTAssertEqual(decode("key"), [:])
XCTAssertEqual(decode("key="), ["key": ""])
}

private func encodeDictionary(_ dictionary: [String: String]) -> String {
return SentryBaggageSerialization.encodeDictionary(dictionary)
}

private func decode(_ baggage: String) -> [String: String] {
return SentryBaggageSerialization.decode(baggage)
}
}
32 changes: 0 additions & 32 deletions Tests/SentryTests/Helper/SentrySerializationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -274,38 +274,6 @@ class SentrySerializationTests: XCTestCase {

XCTAssertNil(actual)
}

func testDictionaryToBaggageEncoded() {
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": "value"]), "key=value")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": "value", "key2": "value2"]), "key2=value2,key=value")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": "value&"]), "key=value%26")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": "value="]), "key=value%3D")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": "value "]), "key=value%20")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": "value%"]), "key=value%25")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": "value-_"]), "key=value-_")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": "value\n\r"]), "key=value%0A%0D")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": ""]), "key=")

let largeValue = String(repeating: "a", count: 8_188)

XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["key": largeValue]), "key=\(largeValue)")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["AKey": "something", "BKey": largeValue]), "AKey=something")
XCTAssertEqual(SentrySerialization.baggageEncodedDictionary(["AKey": "something", "BKey": largeValue, "CKey": "Other Value"]), "AKey=something,CKey=Other%20Value")
}

func testBaggageStringToDictionaryDecoded() {
XCTAssertEqual(SentrySerialization.decodeBaggage("key=value"), ["key": "value"])
XCTAssertEqual(SentrySerialization.decodeBaggage("key2=value2,key=value"), ["key": "value", "key2": "value2"])
XCTAssertEqual(SentrySerialization.decodeBaggage("key=value%26"), ["key": "value&"])
XCTAssertEqual(SentrySerialization.decodeBaggage("key=value%3D"), ["key": "value="])
XCTAssertEqual(SentrySerialization.decodeBaggage("key=value%20"), ["key": "value "])
XCTAssertEqual(SentrySerialization.decodeBaggage("key=value%25"), ["key": "value%"])
XCTAssertEqual(SentrySerialization.decodeBaggage("key=value-_"), ["key": "value-_"])
XCTAssertEqual(SentrySerialization.decodeBaggage("key=value%0A%0D"), ["key": "value\n\r"])
XCTAssertEqual(SentrySerialization.decodeBaggage(""), [:])
XCTAssertEqual(SentrySerialization.decodeBaggage("key"), [:])
XCTAssertEqual(SentrySerialization.decodeBaggage("key="), ["key": ""])
}

private func serializeEnvelope(envelope: SentryEnvelope) -> Data {
var serializedEnvelope: Data = Data()
Expand Down

0 comments on commit 77c9c47

Please sign in to comment.