CVE-2025-48543 AI-generated analysis
platform/art, runtime JNI (runtime/jni/jni_internal.cc, runtime/jni/check_jni.cc)
Adds an instantiability check to three ART JNI object creation entry points in jni_internal.cc (AllocObject, NewObjectV and NewObjectA, per the matching CheckJNI call sites). Creating an instance of an abstract class or interface through them now throws java.lang.InstantiationException. The fix also relaxes the CheckJNI helper so it only rejects array classes.
Root cause
In runtime/jni/jni_internal.cc, three object creation functions in class JNI decoded the jclass, returned if it was nullptr, and then went straight to allocation, with a special case for the String class. None of them checked that the class is instantiable; the test's info.txt says "we forgot to check if a class is instantiable". The only guard was in CheckJNI: ScopedCheck::CheckInstantiableNonArray aborted on !IsInstantiableNonArray(). CheckJNI is a debugging layer that is normally off on production builds (general platform knowledge). The regression test (test/863-serialization) reaches the unchecked path from Java through ObjectInputStream.readObject on a serialized stream that refers to java.text.DateFormat. On the fixed runtime this fails with an InvalidClassException whose cause is an InstantiationException.
How the fix works
- runtime/jni/jni_internal.cc, first hunk in class JNI (AllocObject, the variant that picks gc::AllocatorType for the String case): after `if (c == nullptr) return nullptr;` and before the IsStringClass() branch, adds `if (UNLIKELY(!c->IsInstantiable()))`. When the class is not instantiable it calls soa.Self()->ThrowNewExceptionF("Ljava/lang/InstantiationException;", "Can't instantiate %s %s", ...) and returns nullptr. The message says "interface" or "abstract class" depending on c->IsInterface(), followed by c->PrettyDescriptor().
- runtime/jni/jni_internal.cc, second and third hunks (NewObjectV and NewObjectA, the paths that replace String.<init> with a StringFactory call): the same check is inserted at the same position.
- runtime/jni/check_jni.cc: ScopedCheck::CheckInstantiableNonArray is renamed to CheckNonArray, and its condition changes from `!c->IsInstantiableNonArray()` to `c->IsArrayClass()`. Its three call sites (CheckJNI::AllocObject, CheckJNI::NewObjectV and CheckJNI::NewObjectA) now call CheckNonArray. CheckJNI therefore still aborts for array classes but no longer aborts for abstract classes or interfaces; it forwards those to the base JNI, which now throws.
- test/863-serialization (info.txt, Main.java, empty expected-stdout and expected-stderr): regression test that deserializes a fixed byte stream (ConcurrentHashMap with java.text.DateFormat entries). It expects an InvalidClassException whose cause is an InstantiationException.
Completeness
- Covers the three JNI object creation implementations touched in jni_internal.cc. The variadic NewObject entry point does not appear in the diff.
- The check uses mirror::Class::IsInstantiable(). The exception message treats the failing cases as interfaces and abstract classes. Array classes are still rejected by CheckJNI (CheckNonArray); the diff does not show how the base JNI path handles them.
- Allocation paths outside JNI (reflection, Unsafe, interpreter or compiler new-instance) are not touched by this commit. The material does not show whether they already have equivalent checks.
Behavior changes
- JNI AllocObject, NewObjectV and NewObjectA now return nullptr with a pending java.lang.InstantiationException ("Can't instantiate interface|abstract class <descriptor>") when the jclass is an abstract class or interface.
- Under CheckJNI, passing an abstract class or interface no longer aborts with "can't make objects of type ..."; only array classes still trigger that abort.
- Java deserialization of the test stream now fails with InvalidClassException caused by InstantiationException (test 863-serialization).
Per major
- Android 14: Same fix: platform/art caeef036e31b in android-security-14.0.0_r22 (Change-Id I4ccf22f85b4ae0325e9f8e29503149bbda533e86). 6/8 lines of the patched region are reported present at android-security-14.0.0_r29.
- Android 15: Same fix: platform/art 444fc40dfb04 in android-security-15.0.0_r10. The hunks are identical; only line offsets differ (jni_internal.cc at 924/951/977 instead of 921/948/974). 6/8 lines present at android-security-15.0.0_r17.
- Android 16: Same fix: platform/art 3eb64751e5a7 in android-security-16.0.0_r1. Identical hunks at offsets 922/949/975. 6/8 lines present at android-security-16.0.0_r8.
- Android 17: Not affected: the bulletin does not list Android 17. 6/8 lines of the patched region are reported present at android-security-17.0.0_r1.
Unknowns
- The memory-safety or privilege consequence of holding an instance of an abstract class or interface (how the EoP arises) is not described in the material.
- Which system process or IPC surface could reach this path is not shown; only the standalone test is given.
- The bulletin lists Android 13 as affected, but the material contains no Android 13 fix commit, so it cannot show whether the hunks apply there.
- Whether the variadic JNI NewObject goes through one of the patched functions is not shown in the diff.
- How the base JNI path handles array classes is not shown.
- Whether allocation paths outside JNI already enforce instantiability is not shown.