Skip to content

[iOS] EXC_BAD_ACCESS in -[RCTNetworkTask URLRequest:didReceiveData:] — data race on callback blocks when an <Image> loading a bundled non-png/jpg asset is cancelled #59926

Open

Description

@sagnik2001

Description

RCTNetworkTask reads its callback block ivars (_downloadProgressBlock, _completionBlock, _incrementalDataBlock, _responseBlock, _uploadProgressBlock) on the request handler's thread without synchronization, while -invalidate (reached via -cancel) nils the same ivars on a different thread. When the two overlap, the handler thread retains or invokes an already-freed block → EXC_BAD_ACCESS.

This is easy to hit with <Image> and bundled assets that are not .png/.jpg (e.g. .webp, .gif, .jpeg):

  1. RCTIsImageAssetsPath only treats png/jpg as bundle assets, so RCTLocalAssetImageLoader declines a bundled .webp and the load falls through to RCTNetworking → RCTFileRequestHandler (an NSOperationQueue).
  2. RCTImageLoader always sets task.downloadProgressBlock on the task (_loadURLRequest:).
  3. RCTImageCache only caches results whose response is an NSHTTPURLResponse, so file:// image results are never cached — every mount re-runs the file request.
  4. When the <Image> unmounts or changes source mid-load, RCTImageLoader's cancellation block calls [task cancel] on _URLRequestQueue → -invalidate → _downloadProgressBlock = nil (released), concurrently with RCTFileRequestHandler's block calling -URLRequest:didReceiveData:, which reads _downloadProgressBlock and objc_retains it.

Two related issues widen the window:

  • RCTFileRequestHandler only checks isCancelled at the start of its block, so a cancelled task keeps receiving didReceiveResponse / didReceiveData / didCompleteWithError.
  • After -invalidate sets _requestToken = nil, -validateRequestToken: re-adopts whatever token is passed in (if (_requestToken == nil) _requestToken = requestToken;), so those late callbacks are not rejected.

RCTNetworkTask.mm is unchanged on main (only constinit was added to currentRequestId in #59489). Related reports closed without a fix: #32129 (same didReceiveData frame), #54310 (the other side of the same race — objc_release in -invalidate).

Suggested fix (happy to send a PR):

  • Early-return from the four URLRequest: delegate methods when _atomicStatus == RCTNetworkTaskFinished.
  • Guard the five callback block properties with a mutex (custom accessors) and snapshot them into locals before dispatching.
  • In -invalidate, swap the ivars to locals under the lock and let them release after the lock is dropped.

Steps to reproduce

  1. Clone https://github.com/sagnik2001/rn-image-networktask-race-repro (template reproducer, RN 0.88.1). App.tsx remounts 60 <Image>s sourced from two bundled .webp files on every frame.
  2. cd ReproducerApp && yarn install && cd ios && bundle exec pod install
  3. In Xcode: Edit Scheme → Run → Build Configuration: Release (in Debug, assets are served by Metro over HTTP and take a different path). Optional: Diagnostics → Malloc Scribble makes the crash faster.
  4. Run on a physical device. Relaunch a few times if it doesn't crash within ~3 minutes — it is a race.
  5. The app crashes with EXC_BAD_ACCESS.

Results on iPhone 15 (iOS 26.6.2), Release, Malloc Scribble on:

  • RN 0.88.1 (this reproducer): 1 of 3 runs crashed (after 4 s); the other two survived 200 s.
  • RN 0.86.0 (same screen in our app): 4 of 4 runs crashed, after 108 s, 101 s, 59 s and 13 s.
  • Control: the identical screen with the same images converted to .png (handled by RCTLocalAssetImageLoader, never creates an RCTNetworkTask) ran 2 × 240 s with no crash.

React Native Version

0.88.1 (also 0.86.0)

Affected Platforms

Runtime - iOS

Output of npx @react-native-community/cli info

System:
  OS: macOS 26.6.2
  CPU: (10) arm64 Apple M4
  Memory: 16.00 GB
Binaries:
  Node: 22.23.2
  Yarn: 1.22.22
  npm: 10.9.8
  Watchman: 2025.06.30.00
Managers:
  CocoaPods: 1.16.2
SDKs:
  iOS SDK:
    Platforms: DriverKit 25.5, iOS 26.5, macOS 26.5, tvOS 26.5, visionOS 26.5, watchOS 26.5
IDEs:
  Xcode: 26.6/17F113
npmPackages:
  react-native: 0.86.0 (app), 0.88.1 (reproducer)
iOS:
  newArchEnabled: true

Stacktrace or Logs

Production crash (Crashlytics):

Crashed: NSOperationQueue 0x13bef8e00 (QOS: UNSPECIFIED)
0  libobjc.A.dylib   objc_retain_x0 + 16
1  libobjc.A.dylib   objc_retain + 16
2  MyApp             -[RCTNetworkTask URLRequest:didReceiveData:] (RCTNetworkTask.mm:213)
3  MyApp             __50-[RCTFileRequestHandler sendRequest:withDelegate:]_block_invoke (RCTFileRequestHandler.mm:92)
4  Foundation        <deduplicated_symbol> + 24
5  Foundation        -[NSBlockOperation main] + 88
6  Foundation        __NSOPERATION_IS_INVOKING_MAIN__ + 16
7  Foundation        -[NSOperation start] + 640
8  Foundation        __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 16
9  Foundation        __NSOQSchedule_f + 164
10 libdispatch.dylib _dispatch_block_async_invoke2 + 140

Local reproducer crash (3 of 4 runs; PC is 0 because the dispatched ^{ downloadProgressBlock(length, total); } tail-calls the freed inner block):

Exception Type:  EXC_BAD_ACCESS (SIGKILL)
Exception Subtype: KERN_PROTECTION_FAILURE at 0x0000000000000000
ESR: Instruction Abort, Translation fault

Thread 4 Crashed:: Dispatch queue: com.meta.react.turbomodulemanager.queue
0   ???                           0x0
1   libdispatch.dylib             _dispatch_call_block_and_release + 32
2   libdispatch.dylib             _dispatch_client_callout + 16
3   libdispatch.dylib             _dispatch_lane_serial_drain + 740

Thread 10:: Dispatch queue: com.facebook.react.ImageLoaderURLRequestQueue
0   MyApp                         -[RCTNetworkTask requestToken]
1   MyApp                         __30-[RCTImageLoader dequeueTasks]_block_invoke
2   libdispatch.dylib             _dispatch_call_block_and_release

MANDATORY Reproducer

https://github.com/sagnik2001/rn-image-networktask-race-repro

Screenshots and Videos

No response

Activity

  1. sagnik2001 commented on Nov 11, 2026

    @sagnik2001
    Author

    Update: more runs on 0.88.1 and a FastImage control

    Same reproducer, same device (iPhone 15, iOS 26.6.2), Release, Malloc Scribble on. Each run was capped at 200 s.

    Image component Runs Crashed Time to crash
    RN <Image> (bundled .webp) 5 5 50 s, 8 s, 161 s, 32 s, 2 s
    @d11/react-native-fast-image 8.13.0 (same .webp, same per-frame remount) 3 0 none in 200 s

    So 0.88.1 crashes consistently. The 1-in-3 result in the original report was a small sample.

    All four crash reports the device wrote are EXC_BAD_ACCESS in the same race. The fifth crash, at 8 s, left no report. Three of the four match the trace in the issue (PC 0x0 on com.meta.react.turbomodulemanager.queue). The fourth shows the same race from the RCTFileRequestHandler side, copying an already-freed block:

    Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
    Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000008
    
    Thread Crashed:: Dispatch queue: NSOperationQueue (QOS: UNSPECIFIED)
    0  libsystem_blocks.dylib  _Block_copy
    1  Foundation              -[NSBlockOperation main]
    2  Foundation              __NSOPERATION_IS_INVOKING_MAIN__
    3  Foundation              -[NSOperation start]
    4  Foundation              __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__
    

    FastImage loads through SDWebImage and never creates an RCTNetworkTask. Its runs emitted zero topProgress events, against up to ~4,700 per <Image> run. Combined with the earlier .png control (bundled png goes through RCTLocalAssetImageLoader, no crashes), this points to the RCTFileRequestHandler → RCTNetworkTask path as the cause, not webp decoding or the remount pattern.

  2. dylancom commented on Nov 14, 2026

    @dylancom

    Got the same crashes reported in Sentry with remote images (iOS26 & iOS27).
    Having a hard time to reproduce it myself though.

    Application Specific Information:
    Exception 1, Code 1, Subcode 4611686018427435728 >
    KERN_INVALID_ADDRESS at 0x400000000000bad0.
    
    Thread 21 Crashed:
    0   libobjc.A.dylib                 0x30de51b3c         objc_release
    1   React                           0x1045f780c         -[RCTNetworkTask URLRequest:didCompleteWithError:]
    2   React                           0x1045f5d88         -[RCTHTTPRequestHandler URLSession:task:didCompleteWithError:]
    3   CFNetwork                       0x33f0a2358         __79-[__NSCFURLSessionDelegateWrapper task:didCompleteWithError:completionHandler:]_block_invoke
    4   Foundation                      0x30ec34bc0         __NSINDEXSET_IS_CALLING_OUT_TO_A_BOOL_BLOCK__
    5   Foundation                      0x30ec347cc         -[NSBlockOperation main]
    6   Foundation                      0x30ec348f4         __NSOPERATION_IS_INVOKING_MAIN__
    7   Foundation                      0x30ec34490         -[NSOperation start]
    8   Foundation                      0x30ec3476c         __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__
    9   Foundation                      0x30ec33924         __NSOQSchedule_f
    10  libdispatch.dylib               0x389d4ae80         _dispatch_block_async_invoke2
    11  libdispatch.dylib               0x389d551e0         _dispatch_client_callout
    12  libdispatch.dylib               0x389d40144         _dispatch_continuation_pop
    13  libdispatch.dylib               0x389d3f7c0         _dispatch_async_redirect_invoke
    14  libdispatch.dylib               0x389d4d8fc         _dispatch_root_queue_drain
    15  libdispatch.dylib               0x389d4e094         _dispatch_worker_thread2
    16  libsystem_pthread.dylib         0x3d3a45370         _pthread_wqthread
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions