10

I have a flutter app that generates a lot of hidden API warnings:

Accessing hidden method Landroid/view/accessibility/AccessibilityNodeInfo;->getSourceNodeId()J (greylist,test-api, reflection, allowed)
Accessing hidden method Landroid/view/accessibility/AccessibilityRecord;->getSourceNodeId()J (greylist, reflection, allowed)
Accessing hidden field Landroid/view/accessibility/AccessibilityNodeInfo;->mChildNodeIds:Landroid/util/LongArray; (greylist, reflection, allowed)
Accessing hidden method Landroid/util/LongArray;->get(I)J (greylist, reflection, allowed)
...
Accessing hidden method Landroid/database/sqlite/SQLiteDatabase;-><clinit>()V (blacklist, linking, denied)
Accessing hidden field Landroid/database/sqlite/SQLiteDatabase;->DEBUG_CLOSE_IDLE_CONNECTIONS:Z (greylist-max-o, linking, denied)
Accessing hidden field Landroid/database/sqlite/SQLiteDatabase;->sActiveDatabases:Ljava/util/WeakHashMap; (greylist-max-o, linking, denied)
Accessing hidden field Landroid/database/sqlite/SQLiteDatabase;->CONFLICT_VALUES:[Ljava/lang/String; (greylist, linking, allowed)

And so on. I haven't written any code to use Android's SQLiteDatabase or AccessibilityNodeInfo, but this doesn't provide any information about which bit of code is calling these hidden methods. It could be in Flutter, or one of the several plugins I have. Short of mutilating my code to remove these plugins, is there anything I can do to find out what code is calling these? I.e. to get a stack trace.

Does Android have an option to crash on hidden field accesses rather than just logging them, or something similar?

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • 1
    It's not necessary that it will only come from your code, could be coming from a library or even framework libraries (RoomDB, WorkManager, etc.) or any other DB related android library or a flutter plugin. Also, these are most probably `gray-listed` methods so your app won't crash but only log these methods. – Darshan Feb 20 '21 at 11:24
  • Yeah that's my point. "It could be in Flutter, or one of the several plugins I have." And at least one of the methods says "blacklist" so I'm not sure why that doesn't cause a crash. – Timmmm Feb 20 '21 at 11:46
  • One way could be using whole project search in Android Studio, for mac you can use `Command` + `Shift` + `F` & select Scope to All Places, and try searching for `SQLiteDatabase`. This might give some idea. The other idea would be to find what plugin might be internally using data storage with sql. – Darshan Feb 20 '21 at 11:52
  • 3
    This should help : https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces#test-strictmode-api - looks like you can listen for violations, and get a `Throwable` with the stacktrace. Never used flutter or dart so not sure if strictmode is something you can use though. – Mark Feb 20 '21 at 12:05
  • Hmm I tried searching the entire directory for `AccessibilityNodeInfo` and only these files matched: `build\app\intermediates\incremental\debug-mergeJavaRes\zip-cache\e3AQjVpvQE9qCfHGKhbQLWcdi94=`, `build\app\intermediates\incremental\debug-mergeNativeLibs\zip-cache\e3AQjVpvQE9qCfHGKhbQLWcdi94=`, `build\app\intermediates\dex\debug\mergeExtDexDebug\classes.dex`. – Timmmm Feb 20 '21 at 12:13
  • Also the only plugins I'm using are [`flutter_background`](https://github.com/JulianAssmann/flutter_background) and [`location`](https://github.com/Lyokone/flutterlocation). Neither accesses SQLite as far as I can tell. It's very weird. I do use SQLite in a native library but it's using a bundled copy and written in Rust so I don't think it would trigger these warnings. – Timmmm Feb 20 '21 at 12:14
  • I think the issue at https://stackoverflow.com/questions/64948927/using-android-11-gives-a-lot-of-output-log-about-accessing-sqlitedatabase-interf is related. – Bink Apr 07 '21 at 14:31

1 Answers1

5

Aha, thanks to Mark Keen's link, I was able to create a custom Application for my Flutter app that logs the stack track for strict mode violations. Create a MainApplication.kt Kotlin file like this:

package com.yourdomain.flutter_app

import android.app.Application
import android.os.StrictMode
import android.os.StrictMode.OnVmViolationListener
import android.os.StrictMode.VmPolicy
import android.os.strictmode.Violation
import android.util.Log
import io.flutter.view.FlutterMain
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.Executor


class CurrentThreadExecutor : Executor {
    override fun execute(r: Runnable) {
        r.run()
    }
}

class StacktraceLogger : OnVmViolationListener {
    override fun onVmViolation(v: Violation) {
        val sw = StringWriter()
        val pw = PrintWriter(sw)
        v.printStackTrace(pw)

        Log.e("STRICTMODE", sw.toString())
    }
}

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val policy = VmPolicy.Builder()
                .detectAll()
                .detectNonSdkApiUsage()
                .penaltyListener(CurrentThreadExecutor(), StacktraceLogger())
                .build()
        StrictMode.setVmPolicy(policy)

        FlutterMain.startInitialization(applicationContext)
    }
}

And then point your AndroidManifest.xml to it:

    <application
        android:name="com.yourdomain.flutter_app.MainApplication"

Then when you run it you should see a stack trace. In this case the AccessibilityNodeInfo stuff does appear to be a Flutter issue:

2021-02-20 12:43:11.986 3291-3291/uk.co.timhutt.flutter_app E/STRICTMODE: android.os.strictmode.NonSdkApiUsedViolation: Landroid/view/accessibility/AccessibilityNodeInfo;->getSourceNodeId()J
        at android.os.StrictMode.lambda$static$1(StrictMode.java:416)
        at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
        at java.lang.Class.getDeclaredMethodInternal(Native Method)
        at java.lang.Class.getPublicMethodRecursive(Class.java:2079)
        at java.lang.Class.getMethod(Class.java:2066)
        at java.lang.Class.getMethod(Class.java:1693)
        at io.flutter.view.AccessibilityViewEmbedder$ReflectionAccessors.<init>(AccessibilityViewEmbedder.java:446)
        at io.flutter.view.AccessibilityViewEmbedder$ReflectionAccessors.<init>(AccessibilityViewEmbedder.java:429)
        at io.flutter.view.AccessibilityViewEmbedder.<init>(AccessibilityViewEmbedder.java:70)
        at io.flutter.view.AccessibilityBridge.<init>(AccessibilityBridge.java:346)
        at io.flutter.embedding.android.FlutterView.attachToFlutterEngine(FlutterView.java:919)
        at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onCreateView(FlutterActivityAndFragmentDelegate.java:294)
        at io.flutter.embedding.android.FlutterActivity.createFlutterView(FlutterActivity.java:520)
        at io.flutter.embedding.android.FlutterActivity.onCreate(FlutterActivity.java:414)
        at android.app.Activity.performCreate(Activity.java:8000)
        at android.app.Activity.performCreate(Activity.java:7984)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7660)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

Update: The SQLite methods

Weirdly, it still didn't print any stack traces for the SQLite errors, but I noticed that the first errors were for "reflection" whereas those were for "linking". I am linking with a Rust library that uses a bundled copy of SQLite - using this dependency:

rusqlite = { version = "0.24.2", features = ["bundled"] }

And then loading it in Dart:

final DynamicLibrary mapRenderNative = Platform.isAndroid
    ? DynamicLibrary.open("libmap_render.so")
    : DynamicLibrary.process();

When I commented that line out and ran wgradle clean (necessary it seems), the errors went away! Wtf? It seems like Android intercepts dlopen() and checks if you link against any of the forbidden symbols.

But the really weird thing is that my libmap_render.so doesn't link with SQLite (because it's statically linked into it):

>llvm-readelf -d target\aarch64-linux-android\debug\libmap_render.so
Dynamic section at offset 0x52768 contains 23 entries:
  Tag                Type           Name/Value
  0x0000000000000001 (NEEDED)       Shared library: [libdl.so]
  0x0000000000000001 (NEEDED)       Shared library: [libc.so]
  0x000000000000001e (FLAGS)        BIND_NOW
  0x000000006ffffffb (FLAGS_1)      NOW
  0x0000000000000007 (RELA)         0x988
  0x0000000000000008 (RELASZ)       9768 (bytes)
  0x0000000000000009 (RELAENT)      24 (bytes)
  0x000000006ffffff9 (RELACOUNT)    405
  0x0000000000000017 (JMPREL)       0x2fb0
  0x0000000000000002 (PLTRELSZ)     888 (bytes)
  0x0000000000000003 (PLTGOT)       0x54940
  0x0000000000000014 (PLTREL)       RELA
  0x0000000000000006 (SYMTAB)       0x308
  0x000000000000000b (SYMENT)       24 (bytes)
  0x0000000000000005 (STRTAB)       0x79c
  0x000000000000000a (STRSZ)        491 (bytes)
  0x000000006ffffef5 (GNU_HASH)     0x778
  0x000000000000001a (FINI_ARRAY)   0x54758
  0x000000000000001c (FINI_ARRAYSZ) 16 (bytes)
  0x000000006ffffff0 (VERSYM)       0x6e0
  0x000000006ffffffe (VERNEED)      0x734
  0x000000006fffffff (VERNEEDNUM)   2
  0x0000000000000000 (NULL)         0x0

I also couldn't find any SQLite symbols in the symbol table, relocations, etc. So I have literally no idea what Android is doing. Does it literally scan the binary?

I'll open a new question for that I think.

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • thanks for documenting what you found. If you opened a new question for the last part, can you link to it here? – LarsH Apr 20 '21 at 23:46