realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.45k stars 1.75k forks source link

Realm in fat AAR #6684

Closed JirkaKrivanek closed 4 years ago

JirkaKrivanek commented 4 years ago

Goal

Have an AAR which embeds realm (also known as a FAT AAR, everything is inside, no external dependencies at all).

Additionally, and this is crucial, the final AAR obfuscation with the DexGuard (enhanced obfuscation, not just a ProGuard/R8 minification) is a must. Ideally, by the reverse engineering, it is NOT possible to easily identify there even is any REALM inside (but I doubt so).

To make things even more complicated, the final "fat" AAR is actually put together from multiple (many) other AARs, only one of them contains REALM (I came to the bigger project which already runs for long time and my task is to change the internal persisting facility from files to REALM).

Actual Results

Crash:

java.lang.ClassCastException: com.gemalto.mbi.common.internal.realm.A cannot be cast to io.realm.com_gemalto_mbi_common_internal_realm_ARealmProxyInterface at io.realm.com_gemalto_mbi_common_internal_realm_ARealmProxy.insert(com_gemalto_mbi_common_internal_realm_ARealmProxy.java:337) at io.realm.MbiRealmModuleMediator.insert(MbiRealmModuleMediator.java:117) at io.realm.Realm.insert(Realm.java:1195) at com.gemalto.mbi.common.internal.realm.MbiRealmPersistingFacility.writeBytes(MbiRealmPersistingFacility.java:227) at com.gemalto.mbi.common.internal.MbiPersisterImpl.writeString(MbiPersisterImpl.java:240) at com.gemalto.mbi.finland.di.ObjectFactory.lambda$getControllerComponent$0(ObjectFactory.java:107) at com.gemalto.mbi.finland.di.-$$Lambda$ObjectFactory$KfoXk-Ysdy-dcwVrfJEeEs3az1k.run(Unknown Source:0) at java.lang.Thread.run(Thread.java:764)

Steps & Code to Reproduce

There is this code generated by Realm tooling:

    public static long insert(Realm realm, com.gemalto.mbi.common.internal.realm.A object, Map<RealmModel,Long> cache) {
        if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) {
            return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex();
        }
        Table table = realm.getTable(com.gemalto.mbi.common.internal.realm.A.class);
        long tableNativePtr = table.getNativePtr();
        AColumnInfo columnInfo = (AColumnInfo) realm.getSchema().getColumnInfo(com.gemalto.mbi.common.internal.realm.A.class);
        long rowIndex = OsObject.createRow(table);
        cache.put(object, rowIndex);
        Table.nativeSetLong(tableNativePtr, columnInfo.aIndex, rowIndex, ((com_gemalto_mbi_common_internal_realm_ARealmProxyInterface) object).realmGet$a(), false);
        String realmGet$b = ((com_gemalto_mbi_common_internal_realm_ARealmProxyInterface) object).realmGet$b();
        if (realmGet$b != null) {
            Table.nativeSetString(tableNativePtr, columnInfo.bIndex, rowIndex, realmGet$b, false);
        }
        String realmGet$c = ((com_gemalto_mbi_common_internal_realm_ARealmProxyInterface) object).realmGet$c();
        if (realmGet$c != null) {
            Table.nativeSetString(tableNativePtr, columnInfo.cIndex, rowIndex, realmGet$c, false);
        }
        return rowIndex;
    }

Particularly, there is this cast

((com_gemalto_mbi_common_internal_realm_ARealmProxyInterface) object)

I studied the code: This cast is INVALID.

I do not understand how come that this cast works when I build the standalone APP based on REALM???

I do understand that the cast does NOT work (crash above) when I repackage the REALM into the fat AAR - as described above).

There must be some trickery behind, which I am missing...

How exactly does REALM build work?

Why there are NO dependencies (only gradle plugin doing some magic behind scenes)?

I tried to find some documentation on this but nothing (an tried to understand it from code but that is long run)...

And, I have not even started with the obfuscation - I am a bit afraid that it will introduce a lot of issues - exactly thanks to the magic behind scenes instead of the straightforward dependency system...

Version of Realm and tooling

Realm version(s): 6.0.1

Realm Sync feature enabled: No

Android Studio version: 3.5.3

Android Build Tools version: Latest

Gradle version: Latest

Which Android version and device(s): Android 9, Samsung A60 (and others), but that does NOT matter

cmelchior commented 4 years ago

It sounds like you might be obfuscating more than you can. Realm ships with some pre-existing proguard rules, so you will always be able to identify that Realm is used inside. Mostly because we have callbacks from native code and those methods cannot be renamed otherwise the native code cannot find them. Also, we have a few calls using reflection that would also break.

I'm not 100% sure why our code crashes this way. It could perhaps be because you are not using Realm modules correctly. See https://realm.io/docs/java/latest/#schemas

JirkaKrivanek commented 4 years ago

@cmelchior Regarding the over-obfuscation:

@cmelchior Regarding the insufficient obfuscation:

@cmelchior Regarding the Realm usage:

I am closing this ticket (even though it is not satisfying, I do understand that most of your users do not care about the security at all, therefore it is no priority for you)...