CVE-2023-40103 AI-generated analysis
platform/frameworks/base, libs/androidfw AssetManager2 and core/jni/android_util_AssetManager.cpp plus android.content.res Java wrappers (AssetManager, StringBlock, XmlBlock)
Two frameworks/base commits make AssetManager2 hold promoted (strong) ApkAssets references for the whole duration of a scoped "operation" instead of re-promoting weak pointers per access, and make the Java StringBlock/XmlBlock/AssetManager wrappers null out native pointers after destruction and reject calls on a gone native object.
Root cause
On the Java side, StringBlock.mNative and XmlBlock.mNative were final and kept the freed native address after nativeDestroy(), XmlBlock did not close its StringBlock when its open count reached zero, and AssetManager.getAttributeResolutionStack() did not call ensureValidLocked(); per the commit message, a racing call could reach an already freed native object instead of getting a null. On the native side, AssetManager2 stored its ApkAssets only as weak pointers (std::vector<ApkAssetsWPtr> apk_assets_) and each method (FindEntry, GetStringPoolForCookie, OpenDir, OpenNonAsset, GetNonSystemOverlays, GetLastResourceResolution, Theme::SetTo, JNI NativeGetResourceStringArray, ...) promoted the weak pointer locally for each access, so no strong reference was held across a whole JNI call. The native commit itself is described as a performance change; its exact security contribution is not stated in the material.
How the fix works
- Commit 58fa254a5d1a, libs/androidfw/include/androidfw/AssetManager2.h: apk_assets_ becomes mutable std::vector<std::pair<ApkAssetsWPtr, ApkAssetsPtr>> (weak pointer plus cached promoted strong pointer); adds struct ScopedOperation, [[nodiscard]] StartOperation() const, FinishOperation() const, counter number_of_running_scoped_operations_, and replaces the GetApkAssets() vector getter with GetApkAssets(ApkAssetsCookie) const returning const ApkAssetsPtr& plus GetApkAssetsCount().
- Commit 58fa254a5d1a, libs/androidfw/AssetManager2.cpp: StartOperation() increments the counter and returns a ScopedOperation whose destructor calls FinishOperation(); FinishOperation() clears all cached strong pointers only when the last nested operation ends. GetApkAssets(cookie) DCHECKs that an operation is running, returns a static empty pointer for cookie < 0 or >= size, and otherwise promotes once and caches the result in pair.second for the rest of the operation.
- Commit 58fa254a5d1a, libs/androidfw/AssetManager2.cpp: the internal users of apk_assets_[..].promote() shown in the diff are converted to StartOperation() + GetApkAssets(i): BuildDynamicRefTable (also pre-populates the strong pointer from the caller's list), DumpToLog, GetStringPoolForCookie, GetOverlayablesToString, ContainsAllocatedTable, GetNonSystemOverlays, GetResourceConfigurations, GetResourceLocales, OpenDir, both OpenNonAsset overloads, FindEntry, GetLastResourceResolution, and Theme::SetTo (which now starts operations on both source and destination managers and iterates with GetApkAssetsCount()/GetApkAssets()). ResetResourceResolution() is simplified to last_resolution_ = Resolution{}.
- Commit 58fa254a5d1a, core/jni/android_util_AssetManager.cpp: adds ScopedLockedAssetsOperation (holds the ScopedLock<AssetManager2> and an AssetManager2::ScopedOperation) and LockAndStartAssetManager(jlong); the Native* JNI entry points in the diff that previously took ScopedLock<AssetManager2> now use it, so the ApkAssets stay promoted for the whole JNI call, including entry points that run AttributeResolution (ApplyStyle, ResolveAttrs, RetrieveAttributes) and Theme operations under that lock. NativeGetResourceStringArray replaces GetApkAssets()[cookie].promote() with GetApkAssets(cookie).
- Commit 58fa254a5d1a, tools/aapt2/process/SymbolTable.cpp LookupAttributeInTable, libs/androidfw/tests/AssetManager2_test.cpp, libs/androidfw/tests/Idmap_test.cpp: adapted to the new API (GetApkAssetsCount(), StartOperation(), GetApkAssets(cookie)); a new GetApkAssets test verifies strong counts rise during an operation and drop back after it.
- Commit c3bc12c484ef, core/java/android/content/res/StringBlock.java: mNative is no longer final and is set to 0 after nativeDestroy() in close().
- Commit c3bc12c484ef, core/java/android/content/res/XmlBlock.java: decOpenCountLocked() now closes mStrings before nativeDestroy(mNative) and sets mNative = 0; mNative is no longer final. newParser() returns null when mNative == 0 (shown in code context).
- Commit c3bc12c484ef, core/java/android/content/res/AssetManager.java: ensureValidLocked() additionally throws RuntimeException when mObject == 0 even if mOpen is true; getAttributeResolutionStack() now calls ensureValidLocked() before nativeAttributeResolutionStack().
Completeness
- The JNI entry points shown in the diff are converted to LockAndStartAssetManager, so those Java-initiated AssetManager2 operations hold strong references for their full duration; whether any lock site remains unconverted is not fully visible in the material.
- The listed internal AssetManager2 methods start their own operation; GetBag is not shown doing so, and aapt2 LookupAttributeInTable adds its own StartOperation() before calling it.
- GetApkAssets(cookie) includes a range check returning an empty pointer for out-of-range cookies.
- Strong references are released only when the outermost operation finishes; nothing in the diff changes how ApkAssets are released from Java (ApkAssets/ResourcesManager).
- Java side: only StringBlock and XmlBlock clear mNative; only getAttributeResolutionStack() gains an ensureValidLocked() call in AssetManager.java. Whether every other AssetManager native-call path already called ensureValidLocked() is not shown in the material.
- Theme native pointers (theme_ptr reinterpret_cast in the JNI functions) are validated only via the pre-existing CHECK(theme->GetAssetManager() == ...) and are not otherwise touched by these commits.
Behavior changes
- AssetManager2 API change: GetApkAssets() returning the weak-pointer vector is removed; callers must use StartOperation() plus GetApkAssets(cookie) and GetApkAssetsCount(). GetApkAssets() DCHECKs an operation is running.
- ApkAssets strong references are held by the AssetManager2 for the duration of any operation and released at FinishOperation(); destruction of an ApkAssets may therefore be deferred until the current operation ends.
- Out-of-range cookies passed to GetApkAssets() yield an empty pointer.
- FinishOperation() logs a warning (ALOGW) if called with no running operation.
- AssetManager.ensureValidLocked() throws RuntimeException("AssetManager is open but the native object is gone") when mObject == 0; getAttributeResolutionStack() can now throw this.
- XmlBlock now closes its StringBlock when its open count reaches zero; StringBlock and XmlBlock have mNative == 0 after close, so newParser() returns null on a closed XmlBlock.
- ResetResourceResolution() now resets the whole Resolution struct rather than a listed subset of fields.
Per major
- Android 14: same fix: both commits (58fa254a5d1a and c3bc12c484ef) are in android-security-14.0.0_r3 (Merged-In I26ef88df4f6267b5e8b89f1588f2382db74030c0 and I817dc2f5ef24e1fafeba3ce4c1c440abe70ad3ed); 8/8 lines of the patched region present at android-security-14.0.0_r29.
- Android 15: not affected per the bulletin; 8/8 lines of the patched region present at android-security-15.0.0_r17.
- Android 16: not affected per the bulletin; 8/8 lines of the patched region present at android-security-16.0.0_r8.
- Android 17: not affected per the bulletin; 8/8 lines of the patched region present at android-security-17.0.0_r1.
Unknowns
- The exact sequence that releases an ApkAssets or native object during an in-flight call is not shown in the material.
- How the native commit (described as a performance change) contributes to the security fix is not stated in the material.
- Whether the former direct apk_assets_[cookie] indexing sites lacked bounds checks is not visible from the diff hunks.
- Whether all ScopedLock<AssetManager2> sites in android_util_AssetManager.cpp were converted (e.g. NativeThemeRebase's second manager) is not fully visible.
- Whether other AssetManager.java native-call methods already called ensureValidLocked() is not visible in the material.
- The bulletin lists only Android 14 as affected; the material does not show why other versions are not listed.