Skip to content

Background Assets: fix EXC_BAD_ACCESS in AssetPackManifest on iOS 26 (non-blittable baw_err) - #1

Closed
petarkarov wants to merge 1 commit into
mainfrom
fix/baw-err-non-blittable-bool
Closed

petarkarov wants to merge 1 commit into
mainfrom
fix/baw-err-non-blittable-bool

Conversation

@petarkarov

Copy link
Copy Markdown
Owner

Summary

Apple.BackgroundAssets.Error.baw_err declares its _static field as a C# bool. A C# bool is not blittable, so IL2CPP generates a marshaled copy for every struct that embeds baw_err, including the union baw_assetpackmanifest_res. The generated unmarshal code copies the union's success member and then its failure member into the same bytes, so the second copy overwrites the first. On iOS 26 this destroys the manifest's asset-pack array pointer, and the next call into the native wrapper crashes.

Changing the field to byte makes baw_err blittable. IL2CPP then passes the union through untouched and the crash disappears. The field is never read on the C# side, so there is no behavior change beyond the fix.

Symptom

On any iOS 26 device (tested on 26.5), the first call to AssetPackManifest.GetAssetPack() or GetAllAssetPacks() after AssetPackManager.GetManifestAsync() crashes:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000001
0  BackgroundAssetsWrapper  baw_assetpackmanifest_assetpack + 412
1  UnityFramework           AssetPackManifest_GetAssetPack_...

The faulting instruction is ldr x1, [x21] where x21 is cManifest.compat.assetpackv, and its value is 1.

iOS 27 is not affected: there the manifest is stored as a single opaque pointer in the first 8 bytes of the union, which the overwrite happens to leave intact.

Root cause

IL2CPP output for the reverse P/Invoke wrapper of ManifestCallback (Unity 6000.4.0f1):

void baw_assetpackmanifest_res_..._marshal_pinvoke_back(const ..._marshaled_pinvoke& marshaled, ...& unmarshaled)
{
    unmarshaled.___success = marshaled.___success;                       // 16 bytes: {assetpackc, assetpackv}
    baw_err_..._marshal_pinvoke_back(marshaled.___failure, temp);        // temp = {description, _static = 1}, rest 0
    unmarshaled.___failure = temp;                                       // overwrites the same 16 bytes
}

success and failure overlap at offset 0. After the second assignment, bytes 8..15 contain 0x0000000000000001 (the bool converted from the marshaled int32), which is where baw_assetpackmanifest_compat.assetpackv lives on iOS 26.

A secondary mismatch is fixed by the same change: C bool is 1 byte, but C# bool is marshaled as a 4-byte BOOL by default, so _static was also being read from padding.

Fix

 [StructLayout(LayoutKind.Sequential)]
 internal struct baw_err {
     internal IntPtr description;
-    bool _static;
+    byte _static;
 }

With this change the generated wrapper takes baw_assetpackmanifest_res_t... directly instead of a _marshaled_pinvoke variant, and no copy is performed.

Verification

  • Environment: Unity 6000.4.0f1, Xcode 26.3.
  • Before: crash reproduced on iPhone 12, iOS 26.5, in 5 consecutive TestFlight builds across two Unity projects, always at the same address.
  • After: same device, same flow, manifest and asset pack resolve correctly and the on-demand pack downloads. Also verified no regression on an iPhone 14 Pro, iOS 27.0.
  • Confirmed in the exported Xcode project that ReversePInvokeWrapper_AssetPackManager_ManifestCallback now takes the raw struct.

Notes

The same pattern applies to every other union that embeds baw_err (baw_assetpack_status_res, baw_assetpackmanager_assetpack_update_res, and the plain baw_err callbacks). They didn't crash only because their overlapping bytes happened to carry the same values, so this change makes them correct as well.

…(non-blittable baw_err)

`Apple.BackgroundAssets.Error.baw_err` declared its `_static` field as a
C# `bool`. A C# `bool` is not blittable, so IL2CPP generated a marshaled
copy for every struct that embeds `baw_err`, including the union
`baw_assetpackmanifest_res`. The generated unmarshal code copied the
union's `success` member and then its `failure` member into the same
bytes, so the second copy overwrote the first. On iOS 26 this destroyed
the manifest's asset-pack array pointer, and the next call into the
native wrapper crashed.

Changing the field to `byte` makes `baw_err` blittable, so IL2CPP
passes the union through untouched and the crash disappears. The field
is never read on the C# side, so there is no behavior change beyond
the fix.
@petarkarov

Copy link
Copy Markdown
Owner Author

Opened against the wrong base by mistake — correct PR is apple#114.

@petarkarov petarkarov closed this Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant