Skip to content

Thread-safety crash in AuthTokenAuthorizer due to DateFormatter concurrent access #46

@rfremont

Description

@rfremont

Hello,

We're experiencing intermittent crashes in AuthTokenAuthorizer.getHttpAuthorizationHeaders(request:) caused by a thread-safety issue with DateFormatter.

Crash stack trace:

Crashed: com.apple.root.user-initiated-qos.cooperative
0 libobjc.A.dylib 0x2414 objc_msgSend + 20
1 Foundation 0x43214 -[NSDateFormatter stringForObjectValue:] + 124
2 0x117af58 AuthTokenAuthorizer.getHttpAuthorizationHeaders(request:) + 33 (AuthTokenAuthorizer.swift:33)
3 libswift_Concurrency.dylib 0x2dc30 swift::runJobInEstablishedExecutorContext(swift::Job*) + 304
4 libswift_Concurrency.dylib 0x2e9d8 swift_job_runImpl(swift::Job*, swift::ExecutorRef) + 68
5 libdispatch.dylib 0x48c68 _dispatch_root_queue_drain + 328
6 libdispatch.dylib 0x49430 _dispatch_worker_thread2 + 160
7 libsystem_pthread.dylib 0x1b94 _pthread_wqthread + 224
8 libsystem_pthread.dylib 0x1720 start_wqthread + 8

Root cause:

In AuthTokenAuthorizer.swift, the formatter property is declared as a lazy var on a non-isolated class:

lazy var formatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.timeZone = TimeZone(secondsFromGMT: 0)
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.dateFormat = AuthTokenAuthorizer.AWSDateISO8601DateFormat2
    return formatter
}()

This is then used in getHttpAuthorizationHeaders(request:), which is an async method and can therefore be called concurrently from Swift's cooperative thread pool.

There are two issues here:

  1. lazy var is not thread-safe — concurrent first access from multiple threads leads to undefined behavior.

  2. DateFormatter is not thread-safe — as documented by Apple, concurrent calls to its formatting methods can corrupt its internal state, leading to the objc_msgSend crash we observe.

Suggested fix:

Create a local DateFormatter instance inside the method instead of sharing one across calls:

public func getHttpAuthorizationHeaders(request: URLRequest) async throws -> [String: String] {
    let formatter = DateFormatter()
    formatter.timeZone = TimeZone(secondsFromGMT: 0)
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.dateFormat = AuthTokenAuthorizer.AWSDateISO8601DateFormat2
    let date = formatter.string(from: Date())
    let token = try await fetchLatestAuthToken()
    return [AuthTokenAuthorizer.amzDateHeaderName: date,
            AuthTokenAuthorizer.authorizationHeaderName: token]
}

This eliminates any concurrency issue with negligible performance impact, since this method already performs a network call.

Environment:

iOS 17+
Swift 6
aws-appsync-apollo-extensions-swift 1.0.5

Thanks
Rudy

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions