Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUM-3183 fix: Fix crash on accessing request.allHTTPHeaderFields #1843

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [IMPROVEMENT] Crash errors now include up-to-date global RUM attributes. See [#1834][]
- [FEATURE] `DatadogTrace` now supports OpenTelemetry. See [#1828][]
- [FIX] Fix crash on accessing request.allHTTPHeaderFields. See [#1843][]
- [FEATURE] Support for trace context injection configuration to allow selective injection. See [#1835][]
- [FEATURE] `DatadogWebViewTracking` is now available for Obj-C. See [#1854][]
- [FEATURE] RUM "stop session", "get session ID" and "evaluate feature flag" APIs are now available for Obj-C. See [#1853][]
Expand Down Expand Up @@ -661,6 +662,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO
[#1774]: https://github.com/DataDog/dd-sdk-ios/pull/1774
[#1763]: https://github.com/DataDog/dd-sdk-ios/pull/1763
[#1767]: https://github.com/DataDog/dd-sdk-ios/pull/1767
[#1843]: https://github.com/DataDog/dd-sdk-ios/pull/1843
[#1798]: https://github.com/DataDog/dd-sdk-ios/pull/1798
[#1776]: https://github.com/DataDog/dd-sdk-ios/pull/1776
[#1834]: https://github.com/DataDog/dd-sdk-ios/pull/1834
Expand Down
12 changes: 12 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@
614798A22A45A48F0095CB02 /* DatadogTrace.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2C1A55A29C4F2DF00946C31 /* DatadogTrace.framework */; };
614798A32A45A4980095CB02 /* DatadogTrace.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; };
6147E3B3270486920092BC9F /* TraceConfigurationE2ETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6147E3B2270486920092BC9F /* TraceConfigurationE2ETests.swift */; };
614A708E2BF754D800D9AF42 /* ImmutableRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614A708D2BF754D700D9AF42 /* ImmutableRequest.swift */; };
614A708F2BF754D800D9AF42 /* ImmutableRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614A708D2BF754D700D9AF42 /* ImmutableRequest.swift */; };
614B78ED296D7B63009C6B92 /* DatadogCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */; };
614B78EE296D7B63009C6B92 /* DatadogCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */; };
614B78F1296D7B63009C6B92 /* LowPowerModePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614B78EC296D7B63009C6B92 /* LowPowerModePublisherTests.swift */; };
Expand All @@ -355,6 +357,8 @@
615192CE2BD6948B0005A782 /* HTTPHeadersWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615192CC2BD6948B0005A782 /* HTTPHeadersWriterTests.swift */; };
615192D02BD6B7C90005A782 /* DatadogTracer+InjectAndExtract.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615192CF2BD6B7C90005A782 /* DatadogTracer+InjectAndExtract.swift */; };
615192D12BD6B7C90005A782 /* DatadogTracer+InjectAndExtract.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615192CF2BD6B7C90005A782 /* DatadogTracer+InjectAndExtract.swift */; };
6156A9072BF75A7C00DF66C3 /* ImmutableRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6156A9062BF75A7C00DF66C3 /* ImmutableRequestTests.swift */; };
6156A9082BF75A7C00DF66C3 /* ImmutableRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6156A9062BF75A7C00DF66C3 /* ImmutableRequestTests.swift */; };
61570005246AADFA00E96950 /* DatadogObjc.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61133BF0242397DA00786299 /* DatadogObjc.framework */; };
615A4A8324A3431600233986 /* Trace+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615A4A8224A3431600233986 /* Trace+objc.swift */; };
615A4A8924A34FD700233986 /* DDTracerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615A4A8824A34FD700233986 /* DDTracerTests.swift */; };
Expand Down Expand Up @@ -2332,6 +2336,7 @@
61494CB024C839460082C633 /* RUMResourceScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMResourceScope.swift; sourceTree = "<group>"; };
61494CB424C864680082C633 /* RUMResourceScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMResourceScopeTests.swift; sourceTree = "<group>"; };
61494CB924CB126F0082C633 /* RUMUserActionScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMUserActionScope.swift; sourceTree = "<group>"; };
614A708D2BF754D700D9AF42 /* ImmutableRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImmutableRequest.swift; sourceTree = "<group>"; };
614B0A4A24EBC43D00A2A780 /* RUMUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMUser.swift; sourceTree = "<group>"; };
614B0A4E24EBDC6B00A2A780 /* RUMConnectivityInfoProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMConnectivityInfoProvider.swift; sourceTree = "<group>"; };
614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatadogCoreTests.swift; sourceTree = "<group>"; };
Expand All @@ -2345,6 +2350,7 @@
615519252461BCE7002A85CF /* Datadog.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.xcconfig; sourceTree = "<group>"; };
615519262461BCE7002A85CF /* Datadog.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.local.xcconfig; sourceTree = "<group>"; };
61569894256D0E9A00C6AADA /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = "<group>"; };
6156A9062BF75A7C00DF66C3 /* ImmutableRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImmutableRequestTests.swift; sourceTree = "<group>"; };
6156CB8D24DDA1B5008CB2B2 /* RUMContextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMContextProvider.swift; sourceTree = "<group>"; };
615950EA291C029700470E0C /* SessionReplayDependencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayDependencyTests.swift; sourceTree = "<group>"; };
615950ED291C058F00470E0C /* SessionReplayDependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayDependency.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5555,6 +5561,7 @@
D2160CC029C0DED100FAA9A5 /* URLSession */ = {
isa = PBXGroup;
children = (
614A708D2BF754D700D9AF42 /* ImmutableRequest.swift */,
D295A16429F299C9007C0E9A /* URLSessionInterceptor.swift */,
D2160CC129C0DED100FAA9A5 /* URLSessionTaskInterception.swift */,
3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */,
Expand Down Expand Up @@ -6239,6 +6246,7 @@
D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */,
D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */,
D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */,
6156A9062BF75A7C00DF66C3 /* ImmutableRequestTests.swift */,
);
path = NetworkInstrumentation;
sourceTree = "<group>";
Expand Down Expand Up @@ -8438,6 +8446,7 @@
D23039F5298D5236001A1FA3 /* AnyEncodable.swift in Sources */,
D2303A00298D5236001A1FA3 /* DatadogExtended.swift in Sources */,
D23039E6298D5236001A1FA3 /* Sysctl.swift in Sources */,
614A708E2BF754D800D9AF42 /* ImmutableRequest.swift in Sources */,
D2160CF429C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */,
D23039E1298D5236001A1FA3 /* AppState.swift in Sources */,
D2DE63532A30A7CA00441A54 /* CoreRegistry.swift in Sources */,
Expand Down Expand Up @@ -9366,6 +9375,7 @@
D2DA2369298D57AA00C6C7E6 /* AnyEncodable.swift in Sources */,
D2DA236A298D57AA00C6C7E6 /* DatadogExtended.swift in Sources */,
D2DA236B298D57AA00C6C7E6 /* Sysctl.swift in Sources */,
614A708F2BF754D800D9AF42 /* ImmutableRequest.swift in Sources */,
D2160CF529C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */,
D2DA236C298D57AA00C6C7E6 /* AppState.swift in Sources */,
D2DE63542A30A7CA00441A54 /* CoreRegistry.swift in Sources */,
Expand Down Expand Up @@ -9449,6 +9459,7 @@
D2216EC32A96649500ADAEC8 /* FeatureBaggageTests.swift in Sources */,
D2160CDC29C0DF6700FAA9A5 /* HostsSanitizerTests.swift in Sources */,
615192CD2BD6948B0005A782 /* HTTPHeadersWriterTests.swift in Sources */,
6156A9072BF75A7C00DF66C3 /* ImmutableRequestTests.swift in Sources */,
D2F44FB8299AA1DA0074B0D9 /* DataCompressionTests.swift in Sources */,
D2160CE029C0DF6700FAA9A5 /* URLSessionDelegateAsSuperclassTests.swift in Sources */,
D2EBEE3B29BA163E00B15732 /* B3HTTPHeadersReaderTests.swift in Sources */,
Expand Down Expand Up @@ -9496,6 +9507,7 @@
D2216EC42A96649700ADAEC8 /* FeatureBaggageTests.swift in Sources */,
D2160CDD29C0DF6700FAA9A5 /* HostsSanitizerTests.swift in Sources */,
615192CE2BD6948B0005A782 /* HTTPHeadersWriterTests.swift in Sources */,
6156A9082BF75A7C00DF66C3 /* ImmutableRequestTests.swift in Sources */,
D2F44FB9299AA1DB0074B0D9 /* DataCompressionTests.swift in Sources */,
D2160CE129C0DF6700FAA9A5 /* URLSessionDelegateAsSuperclassTests.swift in Sources */,
D2EBEE3F29BA163F00B15732 /* B3HTTPHeadersReaderTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ extension NetworkInstrumentationFeature {
interception.register(trace: traceContext)
}

if let origin = request.allHTTPHeaderFields?[TracingHTTPHeaders.originField] {
if let origin = request.ddOriginHeaderValue {
interception.register(origin: origin)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import Foundation

/// An immutable version of `URLRequest`.
///
/// Introduced in response to concerns raised in https://github.com/DataDog/dd-sdk-ios/issues/1638
/// it makes a copy of request attributes, safeguarding against potential thread safety issues arising from concurrent
/// mutations (see more context in https://github.com/DataDog/dd-sdk-ios/pull/1767 ).
public struct ImmutableRequest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/suggestion I guess this could be merged with URLSessionTaskInterception, or an inner type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this PR we just move this type to separate file (it was added earlier). While merging types is simple in code, updating tests is harder as it adds more permutations to cover. For the sake of isolated testing, let's keep it this way.

/// The URL of the request.
public let url: URL?
/// The HTTP method of the request.
public let httpMethod: String?
/// The value of `x-datadog-origin` header (if any).
public let ddOriginHeaderValue: String?
/// A reference to the original `URLRequest` object provided during initialization. Direct use is discouraged
/// due to thread safety concerns. Instead, necessary attributes should be accessed through `ImmutableRequest` fields.
public let unsafeOriginal: URLRequest

public init(request: URLRequest) {
self.url = request.url
self.httpMethod = request.httpMethod
// RUM-3183: As observed in https://github.com/DataDog/dd-sdk-ios/issues/1638, accessing `request.allHTTPHeaderFields` is not
// safe and can lead to crashes with undefined root cause. To avoid issues we should prefer `request.value(forHTTPHeaderField:)`
// when interacting with `URLRequest`.
self.ddOriginHeaderValue = request.value(forHTTPHeaderField: TracingHTTPHeaders.originField)
self.unsafeOriginal = request
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public struct URLSessionInterceptor {
// MARK: - Private

private func extractTraceID(from request: URLRequest) -> TraceID? {
guard let headers = request.allHTTPHeaderFields else {
guard let headers = request.allHTTPHeaderFields else { // swiftlint:disable:this unsafe_all_http_header_fields
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 It is IMO fine to ignore this warning here as URLSessionInterceptor is only used by Alamofire extension, which should be deprecated and removed. We have no crash reports pointing this usage, hence this should be safe.

Alternatively, we could use value(forHTTPHeaderField:) to read (one by one) all headers required by DD, B3 and W3C header readers in following lines.

Copy link
Member

@maxep maxep May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the alternative mentioned, it's clearer 👍 thanks for the change :)
sorry, wrong line..

Yes, this is fine 👍

return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,6 @@ public struct ResourceCompletion {
}
}

/// An immutable version of `URLRequest`.
///
/// Introduced in response to concerns raised in https://github.com/DataDog/dd-sdk-ios/issues/1638
/// it makes a copy of request attributes, safeguarding against potential thread safety issues arising from concurrent
/// mutations (see more context in https://github.com/DataDog/dd-sdk-ios/pull/1767 ).
public struct ImmutableRequest {
/// The URL of the request.
public let url: URL?
/// The HTTP method of the request.
public let httpMethod: String?
/// The HTTP header fields of the request.
public let allHTTPHeaderFields: [String: String]?
/// A reference to the original `URLRequest` object provided during initialization. Direct use is discouraged
/// due to thread safety concerns. Instead, necessary attributes should be accessed through `ImmutableRequest` fields.
public let unsafeOriginal: URLRequest

public init(request: URLRequest) {
self.url = request.url
self.httpMethod = request.httpMethod
self.allHTTPHeaderFields = request.allHTTPHeaderFields
self.unsafeOriginal = request
}
}

/// Encapsulates key metrics retrieved either from `URLSessionTaskMetrics` or any other relevant data source.
/// Reference: https://developer.apple.com/documentation/foundation/urlsessiontasktransactionmetrics
public struct ResourceMetrics {
Expand Down
4 changes: 3 additions & 1 deletion DatadogInternal/Sources/Upload/URLRequestBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ public struct URLRequestBuilder {
}
}

request.allHTTPHeaderFields = headers
headers.forEach { field, value in
request.setValue(value, forHTTPHeaderField: field)
}
return request
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import XCTest
import TestUtilities
import DatadogInternal

class ImmutableRequestTests: XCTestCase {
func testReadingURL() {
let original: URLRequest = .mockWith(url: "https://example.com")
let immutable = ImmutableRequest(request: original)
XCTAssertEqual(immutable.url, original.url)
}

func testReadingHTTPMethod() {
let original: URLRequest = .mockWith(httpMethod: .mockRandom())
let immutable = ImmutableRequest(request: original)
XCTAssertEqual(immutable.httpMethod, original.httpMethod)
}

func testReadingDatadogOriginHeader() {
let expectedValue: String = .mockRandom(length: 128)
let original: URLRequest = .mockWith(
headers: [
TracingHTTPHeaders.originField: expectedValue
]
)
let immutable = ImmutableRequest(request: original)
XCTAssertEqual(immutable.ddOriginHeaderValue, expectedValue)
}

func testPreservingUnsafeOriginal() {
let original: URLRequest = .mockAny()
let immutable = ImmutableRequest(request: original)
XCTAssertEqual(immutable.unsafeOriginal, original)
}
}
9 changes: 9 additions & 0 deletions tools/lint/sources.swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ custom_rules:
- doccomment
message: "`UIApplication.shared` is unavailable in some environments. Check `UIApplication.managedShared`."
severity: error
unsafe_all_http_header_fields: # prevents from using `URLRequest.allHTTPHeaderFields` API
included: Sources
name: "Unsafe API: `URLRequest.allHTTPHeaderFields`"
regex: '\.allHTTPHeaderFields'
excluded_match_kinds:
- comment
- doccomment
message: "`URLRequest.allHTTPHeaderFields` is considered unsafe. Prefer using `URLRequest.value(forHTTPHeaderField:)`. See full context in https://github.com/DataDog/dd-sdk-ios/pull/1843"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥇

severity: error
required_reason_api_name: # prevents from declaring symbols that may conflict with Apple's Required Reason APIs
included: Sources
name: "Required Reason API Conflict"
Expand Down