-
Notifications
You must be signed in to change notification settings - Fork 135
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
RUM-3183 fix: Fix crash on accessing request.allHTTPHeaderFields
#1843
Conversation
As observed in #1638, accessing `request.allHTTPHeaderFields` is not safe and leads to crashes with undefined root cause. To avoid this, instead we use `request.value(forHTTPHeaderField:)` to only read headers known by the SDK.
cdaf482
to
1c252a0
Compare
var knownHTTPHeaderFields: [String: String] = [:] | ||
addHeaderIfExists(request: request, field: TracingHTTPHeaders.originField, to: &knownHTTPHeaderFields) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 We could consider adding other known headers here, notably the ones related to distributed tracing (x-datadog-*
, W3C and B3). Not doing this as these headers are not required for upstream code. The only reason for allHTTPHeaderFields
existence, was accessing the x-datadog-origin
header which is added here.
Alternatively, we could replace the entire knownHTTPHeaderFields
prop with datadogOriginField
. This way we will move even further away from the unsafe consideration of "all headers".
Open for discussion 🧠.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/concerning
This seems risky, by only looking for some headers. We will be blind where knownHTTPHeaderFields
is actually used.
Can we copy all headers for instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we copy all headers for instance?
I don't see any option without using the problematic allHTTPHeaderFields
API.
This seems risky, by only looking for some headers. We will be blind where knownHTTPHeaderFields is actually used.
Makes sense 👍. I changed it to the alternative approach by replacing knownHTTPHeaderFields
with ddOriginHeaderValue
prop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is those places where I prefer using assertions (debug only) but not sure how customer can selectively disable them for libraries only else it is too much of noise.
I'm okay.
Datadog ReportBranch report: ✅ 0 Failed, 3222 Passed, 0 Skipped, 2m 31.53s Total Time 🔻 Code Coverage Decreases vs Default Branch (9)
|
DatadogInternal/Sources/NetworkInstrumentation/URLSession/ImmutableRequest.swift
Outdated
Show resolved
Hide resolved
guard let headers = request.allHTTPHeaderFields else { | ||
guard let headers = request.allHTTPHeaderFields else { // swiftlint:disable:this unsafe_all_http_header_fields |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤞
guard let headers = request.allHTTPHeaderFields else { | ||
guard let headers = request.allHTTPHeaderFields else { // swiftlint:disable:this unsafe_all_http_header_fields |
There was a problem hiding this comment.
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 👍
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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥇
/// 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
…allHTTPHeaderFields
…allHTTPHeaderFields
What and why?
📦 🧯 Fixes #1638
As we observed in #1638, accessing
request.allHTTPHeaderFields
is not safe and leads to crashes with undefined root cause. To avoid this, instead we userequest.value(forHTTPHeaderField:)
to only read headers known by the SDK.How?
Removed usage of
.allHTTPHeaderFields
by replacing it with higher-level.value(forHTTPHeaderField:)
API.I also added linter rule to avoid using
allHTTPHeaderFields
if possible.Suspected problem with
.allHTTPHeaderFields
safetyI failed to reproduce the crash, although by exploring
swift-corelibs-foundation
code, I suspect it to relate to the Obj-c → Swift bridging, somewhere in this code. Symbols ran during the bridging correspond to some reported in #1638 stack traces. This may invalidate our earlier assumption on the problem being caused by lack of thread safety in.allHTTPHeaderFields
implementation.Review checklist
Custom CI job configuration (optional)
tools/