Skip to content

Commit 005ad55

Browse files
committed
fix: Avoid mutating live network requests
Copy the task's current request before adding trace propagation headers so CFNetwork never observes concurrent in-place mutations. Fixes #8519
1 parent 554842f commit 005ad55

5 files changed

Lines changed: 77 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
- Fix incorrect `duration` sent for active sessions (#8612)
1919
- Session `duration` is now set only when the session ends. Active sessions (including on error increments) no longer emit a bogus `duration`.
20+
- Fix a race caused by mutating `URLSessionTask.currentRequest` during trace header propagation (#8519)
2021
- Fix a race that could prevent consecutive app hangs from being reported (#8627)
2122
- Fix malformed itms-services URL in SentryDistribution updater (#8567)
2223

Sources/Sentry/SentryTracePropagation.m

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,18 @@ + (void)addBaggageHeader:(SentryBaggage *)baggage
3838
}
3939
}
4040

41-
if ([request isKindOfClass:[NSMutableURLRequest class]]) {
42-
NSMutableURLRequest *mutableRequest = (NSMutableURLRequest *)request;
43-
[SentryTracePropagation addHeaderFieldsToRequest:mutableRequest
41+
// CFNetwork may read currentRequest concurrently, so never mutate it in place.
42+
SEL setCurrentRequestSelector = NSSelectorFromString(@"setCurrentRequest:");
43+
if ([sessionTask respondsToSelector:setCurrentRequestSelector]) {
44+
NSMutableURLRequest *newRequest = [request mutableCopy];
45+
[SentryTracePropagation addHeaderFieldsToRequest:newRequest
4446
traceHeader:traceHeader
4547
baggageHeader:baggageHeader
4648
propagateTraceparent:propagateTraceparent];
47-
} else {
48-
SEL setCurrentRequestSelector = NSSelectorFromString(@"setCurrentRequest:");
49-
if ([sessionTask respondsToSelector:setCurrentRequestSelector]) {
50-
NSMutableURLRequest *newRequest = [request mutableCopy];
51-
[SentryTracePropagation addHeaderFieldsToRequest:newRequest
52-
traceHeader:traceHeader
53-
baggageHeader:baggageHeader
54-
propagateTraceparent:propagateTraceparent];
5549

56-
void (*func)(id, SEL, id param)
57-
= (void *)[sessionTask methodForSelector:setCurrentRequestSelector];
58-
func(sessionTask, setCurrentRequestSelector, newRequest);
59-
}
50+
void (*func)(id, SEL, id param)
51+
= (void *)[sessionTask methodForSelector:setCurrentRequestSelector];
52+
func(sessionTask, setCurrentRequestSelector, newRequest);
6053
}
6154
}
6255

Tests/SentryTests/Integrations/Performance/Network/SentryTracePropagationTests.swift

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,36 @@ final class SentryTracePropagationTests: XCTestCase {
170170
XCTAssertNil(task.currentRequest?.value(forHTTPHeaderField: "sentry-trace"))
171171
}
172172

173-
private func createSessionTask(method: String = "GET") throws -> URLSessionDownloadTaskMock {
173+
func testAddBaggageHeader_whenCurrentRequestIsMutable_shouldNotMutateItInPlace() throws {
174+
// -- Arrange --
175+
let request = URLRequest(url: try XCTUnwrap(URL(string: "https://www.domain.com/api")))
176+
let task = MutableRequestTaskMock(request: request)
177+
let traceHeader = TraceHeader(
178+
trace: SentryId(),
179+
spanId: SpanId(),
180+
sampled: .yes
181+
)
182+
183+
// -- Act --
184+
SentryTracePropagation.addBaggageHeader(
185+
Baggage(),
186+
traceHeader: traceHeader,
187+
propagateTraceparent: true,
188+
tracePropagationTargets: [try XCTUnwrap(NSRegularExpression(pattern: ".*"))],
189+
toRequest: task
190+
)
191+
192+
// -- Assert --
193+
XCTAssertEqual(task.setCurrentRequestCallCount, 1)
194+
XCTAssertNil(task.initialCurrentRequest.value(forHTTPHeaderField: "sentry-trace"))
195+
XCTAssertEqual(task.currentRequest?.value(forHTTPHeaderField: "sentry-trace"), traceHeader.value())
196+
}
197+
198+
private func createSessionTask(method: String = "GET") throws -> URLSessionDataTaskMock {
174199
let url = try XCTUnwrap(URL(string: "https://www.domain.com/api?query=value&query2=value2#fragment"))
175200
var request = URLRequest(url: url)
176201
request.httpMethod = method
177-
return URLSessionDownloadTaskMock(request: request)
202+
return URLSessionDataTaskMock(request: request)
178203
}
179204

180205
}

Tests/SentryTests/Integrations/Performance/Network/URLSessionTaskMock.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,11 @@ static int64_t const DATA_BYTES_SENT = 652;
8686

8787
@end
8888

89+
@interface MutableRequestTaskMock : URLSessionDataTaskMock
90+
91+
@property (nonatomic, readonly) NSMutableURLRequest *initialCurrentRequest;
92+
@property (nonatomic, readonly) NSUInteger setCurrentRequestCallCount;
93+
94+
@end
95+
8996
NS_ASSUME_NONNULL_END

Tests/SentryTests/Integrations/Performance/Network/URLSessionTaskMock.m

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,37 @@ - (NSURLRequest *)currentRequest
325325
}
326326

327327
@end
328+
329+
@implementation MutableRequestTaskMock {
330+
NSMutableURLRequest *_initialCurrentRequest;
331+
NSUInteger _setCurrentRequestCallCount;
332+
}
333+
334+
#pragma clang diagnostic push
335+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
336+
- (instancetype)initWithRequest:(NSURLRequest *)request
337+
{
338+
if (self = [super initWithRequest:request]) {
339+
_initialCurrentRequest = (NSMutableURLRequest *)[super currentRequest];
340+
}
341+
return self;
342+
}
343+
#pragma clang diagnostic pop
344+
345+
- (NSMutableURLRequest *)initialCurrentRequest
346+
{
347+
return _initialCurrentRequest;
348+
}
349+
350+
- (NSUInteger)setCurrentRequestCallCount
351+
{
352+
return _setCurrentRequestCallCount;
353+
}
354+
355+
- (void)setCurrentRequest:(NSURLRequest *)request
356+
{
357+
_setCurrentRequestCallCount++;
358+
[super setCurrentRequest:request];
359+
}
360+
361+
@end

0 commit comments

Comments
 (0)