Skip to content

[Bug][iOS] EXC_BAD_ACCESS in il2cpp::vm::ClassMatches during FirebaseApp.CreateAndTrack on iOS 26 — crash persists despite ContinueWithOnMainThread main-thread init #1436

@dogukankse

Description

@dogukankse

Description

Summary

On iOS devices (heavily concentrated on iOS 26.x), a minority but non-trivial share of cold launches crash inside FirebaseApp.CreateAndTrack with EXC_BAD_ACCESS (SIGBUS) / KERN_PROTECTION_FAILURE. The crash site is il2cpp::vm::ClassMatches + 12 at Image.cpp:326, reached via il2cpp::vm::Image::FromTypeNameParseInfo + 304 at Image.cpp:377. The crash persists even when the community-recommended mitigations (frame-defer + ContinueWithOnMainThread) are in place — the main thread crashes while honoring those guards.

Environment

Component Version
Firebase Unity SDK 13.3.0
Firebase iOS (Cocoapods, bundled) 12.4.0
Unity 2022.3 LTS (IL2CPP scripting backend, iOS target)
DI container Zenject (Firebase init wrapped in a custom IInitializable)
UniTask Cysharp.Threading.Tasks (latest stable at the time of release)
Xcode build toolchain Xcode 16.x

Device distribution of affected crashes (14-day window, production):

  • iPhone ~98%, iPad ~2%
  • iOS 26.x ~70% (iOS 26.3 dominant, iOS 26.4 secondary)
  • iOS 18.x ~25%
  • Remainder on iOS 16/17

Impact and frequency

  • ~611 unique device events over 14 days on a single recent app version (not a cumulative figure across versions).
  • Rate is a minority of the install base (~single-digit %), not 100% reproducible — strongly indicative of a race condition rather than deterministic stripping or a missing symbol.
  • Non-zero presence on iOS 18.x confirms the issue is not purely an iOS 26 beta artifact; iOS 26 appears to amplify an underlying race.

Exception

Exception Type:     EXC_BAD_ACCESS (SIGBUS)
Exception Subtype:  KERN_PROTECTION_FAILURE
Termination Reason: SIGNAL 10 Bus error
Triggered Thread:   0 (main thread)

Important: KERN_PROTECTION_FAILURE indicates the faulting address resolves to a valid mapped page but the CPU's memory-protection bits rejected the access. This is the classic signature of a torn or stale pointer being dereferenced, not a null pointer returned from a stripped-type lookup.

Crash stack (symbolicated, main-thread-init build)

User-code frames are redacted; Firebase / Unity / IL2CPP symbols preserved verbatim:

Thread 0 Crashed:
0   UnityFramework   il2cpp::vm::ClassMatches(___Il2CppMetadataTypeHandle const*, char const*, bool, char const*) + 12        (Image.cpp:326)
1   UnityFramework   il2cpp::vm::Image::FromTypeNameParseInfo(Il2CppImage const*, ...) + 304                                    (Image.cpp:377)
2   UnityFramework   FirebaseApp_CreateAndTrack_* + 1016                                                                        (Firebase.App.cpp:10051)
3   UnityFramework   [REDACTED user_init_controller]_<StartFirebaseApp>b__3_0                                                   (user controller)
4   UnityFramework   Action_1_Invoke_ClosedInstInvoker
5   UnityFramework   U3CU3Ec__DisplayClass4_1_1_<ContinueWithOnMainThread>b__1 + 44                                             (Generics.cpp:23712)
6   UnityFramework   RuntimeInvoker_TrueByte
7   UnityFramework   Func_1_Invoke_ClosedInstInvoker                                                                            (Generics__6.cpp:30923)
8   UnityFramework   U3CU3Ec__DisplayClass5_0_1_<RunAsync>b__0 + 112                                                             (Generics.cpp:24825)
9   UnityFramework   ExceptionAggregator_Wrap + 36                                                                              (Firebase.Platform.cpp:1907)
10  UnityFramework   Dispatcher_PollJobs + 108                                                                                  (Firebase.Platform.cpp:2082)
11  UnityFramework   FirebaseHandler_Update + 232                                                                               (Firebase.Platform.cpp:3640)
12  UnityFramework   il2cpp::vm::Runtime::InvokeWithThrow + 100                                                                 (Runtime.cpp:608)
13  UnityFramework   il2cpp::vm::Runtime::Invoke + 80                                                                           (Runtime.cpp:594)
14  UnityFramework   ScriptingInvocation::Invoke + 120                                                                          (ScriptingInvocation.cpp:298)
15  UnityFramework   MonoBehaviour::CallUpdateMethod + 276                                                                      (MonoBehaviour.cpp:593)
16  UnityFramework   void BaseBehaviourManager::CommonUpdate<BehaviourManager>() + 100                                          (Behaviour.cpp:158)
17  UnityFramework   ExecutePlayerLoop(NativePlayerLoopSystem*) + 140                                                           (PlayerLoop.cpp:406)
...

Note frames 9–11: the continuation is reached via Firebase.Platform.Dispatcher.PollJobs from FirebaseHandler.Update on the Unity main thread — confirming ContinueWithOnMainThread is being honored. The crash fires anyway.

Historical context — Xcode bucket migration

Before the main-thread-init refactor, the identical crash (same top 3 frames, same exception type) was occurring on a background threadpool thread:

Triggered Thread: N (background worker)

0   UnityFramework   il2cpp::vm::ClassMatches + 12                 (Image.cpp:326)
1   UnityFramework   il2cpp::vm::Image::FromTypeNameParseInfo      (Image.cpp:377)
2   UnityFramework   FirebaseApp_CreateAndTrack + 1016              (Firebase.App.cpp:10051)
3   UnityFramework   [REDACTED user_init_controller]_<StartFirebaseApp>b__2_0
4   UnityFramework   Action_1_Invoke_ClosedInstInvoker
5   UnityFramework   Task_Execute                                    (mscorlib)
...
9   UnityFramework   ThreadPoolWorkQueue_Dispatch
12  UnityFramework   worker_thread                                   (ThreadPoolWorkerThread.cpp:250)
13  UnityFramework   il2cpp::vm::ThreadStart                         (Thread.cpp:801)
14  UnityFramework   il2cpp::os::ThreadImpl::ThreadStartWrapper     (ThreadImpl.cpp:123)
15  libsystem_pthread.dylib  _pthread_start

Because the Xcode crash grouping is primarily keyed off the main thread's state when the process dies, those older reports were bucketed under a generic "no useful main-thread stack" signature. Moving Firebase init to the main thread did not eliminate the race; it only changed how Xcode groups the resulting crashes (the crash now has a rich main-thread stack and a distinct signature).

Mitigations already in place (ineffective)

The following are both present in the currently shipping build where the 611-event crash count was collected. Neither eliminates the crash:

1. Frame-defer guard — defer Firebase init by one Update tick so IL2CPP's generic-metadata finalization on cold launch has time to settle before any Firebase reflection path runs:

public void Initialize()
{
    _initTask = new TaskCompletionSource<bool>();
    InitSdk().Forget();
}

private async UniTask InitSdk()
{
    await UniTask.Yield(PlayerLoopTiming.Update);
    StartFirebaseApp();
}

2. Main-thread continuation — marshal the CheckAndFixDependenciesAsync() continuation back to the Unity main thread before accessing FirebaseApp.DefaultInstance:

private void StartFirebaseApp()
{
    FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task =>
    {
        if (task.Result == DependencyStatus.Available)
        {
            _ = FirebaseApp.DefaultInstance;   // triggers CreateAndTrack → FromTypeNameParseInfo
            _initTask.TrySetResult(true);
        }
        else
        {
            _initTask.TrySetResult(false);
        }
    });
}

The crash stack explicitly contains <ContinueWithOnMainThread>b__1 (frame 5) → Dispatcher_PollJobs (frame 10) → FirebaseHandler_Update (frame 11) → MonoBehaviour::CallUpdateMethod (frame 15) — proving the main-thread marshal is being honored. Thread 0 still crashes.

Hypotheses

  1. Cold-launch IL2CPP metadata race persists on main thread. The UniTask.Yield guard defers init by exactly one Update tick. If CheckAndFixDependenciesAsync() completes near-synchronously (warm cache), the continuation fires almost immediately, and FirebaseApp.DefaultInstance can touch IL2CPP type metadata while other main-thread work (generic method table construction, static cctors on the first access of certain UnityEngine.* types) is still mutating the metadata image. FromTypeNameParseInfo then reads a torn Il2CppMetadataTypeHandle — matching the KERN_PROTECTION_FAILURE signal type (not a null return, which would be the stripping signature).

  2. iOS 26 W^X/PAC enforcement amplifies a pre-existing race. The crash is elevated on iOS 26.x (~70% of events). iOS 26's stricter page-protection enforcement may be surfacing races that prior iOS versions silently tolerated — in particular if any Firebase init path (or class realization triggered by it) touches pages mid-remap.

  3. Stripping / link.xml are not the cause. The Firebase Unity SDK's bundled link.xml at Assets/Firebase/FirebaseApp/Internal/link.xml already preserves the types that FirebaseInterops.cs resolves by name (Firebase.Auth.FirebaseAuth, Firebase.AppCheck.FirebaseAppCheck). KERN_PROTECTION_FAILURE is inconsistent with stripping (which returns null, giving KERN_INVALID_ADDRESS), and a stripping root cause would crash every user on affected binary builds — not a single-digit percent.

What we have not yet tried

  • Upgrade to Firebase Unity SDK 13.10.0 (iOS Cocoapods 12.12.0). The CHANGELOG entries between 12.4.0 and 12.12.0 do not appear to document a fix for this specific stack; 12.12.0 additionally requires Xcode 26.2 + Swift 6.2.3, which is a non-trivial build-system change for teams still on Xcode 16.
  • Add FirebaseAppDelegateClassName to Info.plist. We left this out because the crash stack is at the IL2CPP metadata layer (ClassMatches / FromTypeNameParseInfo), not obviously on the AppDelegate swizzle path. If the Firebase team believes it would alter the relevant code path, we are happy to test it.

Questions for the Firebase team

  1. Is there a known IL2CPP metadata race inside FirebaseApp.CreateAndTrack (the Firebase.App.cpp:10051 call site) that can still fire even with ContinueWithOnMainThread applied?
  2. Does the call at Firebase.App.cpp:10051 perform a Type.GetType(string) / Assembly.GetType(string) lookup, and if so, which type is being resolved? If it is a Firebase-internal type already covered by the SDK's link.xml, what else could produce a KERN_PROTECTION_FAILURE at that offset?
  3. Any iOS 26 compatibility work planned for the Core / App init path that is not yet reflected in CHANGELOG entries between 12.4.0 and 12.12.0?
  4. What additional diagnostic breadcrumbs or environment probes would help characterize this race from a live Crashlytics report?

Related issues

Reproduction difficulty

We do not yet have a minimal Unity project that reliably reproduces this crash. The issue only manifests on a small fraction of user devices during cold launch, strongly suggesting timing/race sensitivity. We are happy to provide additional symbolicated .crash files or to test diagnostic builds if the team can suggest instrumentation.

Reproducing the issue

No response

Firebase Unity SDK Version

13.30

Unity editor version

2022.3..69

Installation Method

.unitypackage

Problematic Firebase Component(s)

No response

Other Firebase Component(s) in use

No response

Additional SDKs you are using

No response

Targeted Platform(s)

Apple Platforms

Unity editor platform

Mac

Scripting Runtime

IL2CPP

Release Distribution Type

Pre-built SDK from https://firebase.google.com/download/unity

Relevant Log Output

If using CocoaPods for Apple platforms, the project's Podfile.lock

Expand Podfile.lock snippet
👀 Replace this line with the contents of your Podfile.lock!

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