原因
non-SDK interfaceにアクセスした場合に発生します。
non-SDK interfaceとはAndroidが公式なInterfaceとして公開していないpackageメソッドやprivateメソッドのことを指します。これらのメソッドを使用している要因としては、
- 以前はinterfaceだったが、後の設計変更でnon-sdk化された
- 利便性のために敢えて@hideメソッドを利用している。Androidでは@hideメソッドは未完成のメソッドに付けられることがあり、将来的にリリースされる可能性があります(勿論そのまま公開されずに消える可能性もありますが)。コードを見て使えると判断した場合、特にthird partyが利用を始めている可能性があります。
逆にSDK interfaceとは以下で公開されているメソッド群です。
対策
使用箇所の特定
この章にStrictMode, Lint, veridex, debug appでのテスト方法が記載されています。
StrictMode
呼び出し元を調査したいhidden methodが決まっている、かつ、API28以上であるなら一番のおすすめはStrictModeです。理由はStrictModeのログを使用することで動的なReflectionで呼び出されるメソッドについても使用箇所を特定することができるからです(他の静的解析手法ではできません)。
使用方法は以下を最上位に追加するだけです。penaltyLog()の部分をCrushのように変更することで検出した際の動作を変更できます。penaltyLog()ではログだけ出力してくれます。
@RequiresApi(Build.VERSION_CODES.P) @HiltAndroidApp class MyApplication : Application() { init { activateNonSdkDetection() } private fun activateNonSdkDetection() { StrictMode.setVmPolicy( VmPolicy.Builder() .detectNonSdkApiUsage() .penaltyLog() .build() ) } }
以下のようなログを出力してくれます。タイトルのWarningはCoilライブラリのImageLoaderが使用していることがわかります。
2022-03-25 16:54:57.889 26356-26409/com.example.whatdo W/.example.whatd: Accessing hidden method Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setAlpnProtocols([B)V (light greylist, reflection) 2022-03-25 16:54:57.890 26356-26409/com.example.whatdo D/StrictMode: StrictMode policy violation: android.os.strictmode.NonSdkApiUsedViolation: Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setAlpnProtocols([B)V at android.os.StrictMode.lambda$static$1(StrictMode.java:428) at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2) at java.lang.Class.getDeclaredMethodInternal(Native Method) at java.lang.Class.getPublicMethodRecursive(Class.java:2075) at java.lang.Class.getMethod(Class.java:2063) at java.lang.Class.getMethod(Class.java:1690) at okhttp3.internal.platform.android.AndroidSocketAdapter.(AndroidSocketAdapter.kt:39) at okhttp3.internal.platform.android.StandardAndroidSocketAdapter.(StandardAndroidSocketAdapter.kt:34) at okhttp3.internal.platform.android.StandardAndroidSocketAdapter$Companion.buildIfSupported(StandardAndroidSocketAdapter.kt:59) at okhttp3.internal.platform.android.StandardAndroidSocketAdapter$Companion.buildIfSupported$default(StandardAndroidSocketAdapter.kt:52) at okhttp3.internal.platform.AndroidPlatform.(AndroidPlatform.kt:47) at okhttp3.internal.platform.AndroidPlatform$Companion.buildIfSupported(AndroidPlatform.kt:160) at okhttp3.internal.platform.Platform$Companion.findAndroidPlatform(Platform.kt:227) at okhttp3.internal.platform.Platform$Companion.findPlatform(Platform.kt:220) at okhttp3.internal.platform.Platform$Companion.access$findPlatform(Platform.kt:177) at okhttp3.internal.platform.Platform.(Platform.kt:178) at okhttp3.OkHttpClient.(OkHttpClient.kt:237) at okhttp3.OkHttpClient.(OkHttpClient.kt:222) at coil.ImageLoader$Builder$build$3.invoke(ImageLoader.kt:522) at coil.ImageLoader$Builder$build$3.invoke(ImageLoader.kt:522) at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74) at coil.fetch.HttpUriFetcher.executeNetworkRequest(HttpUriFetcher.kt:223) at coil.fetch.HttpUriFetcher.fetch(HttpUriFetcher.kt:76) at coil.intercept.EngineInterceptor.fetch(EngineInterceptor.kt:165) at coil.intercept.EngineInterceptor.execute(EngineInterceptor.kt:122) at coil.intercept.EngineInterceptor.access$execute(EngineInterceptor.kt:41) at coil.intercept.EngineInterceptor$intercept$2.invokeSuspend(EngineInterceptor.kt:75) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:39) at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
veridex
とりあえず一覧で確認したい場合はveridexをお勧めします。veridexを使用することで静的解析に基づいて一覧表示してくれます。以下のような感じ。
Windows環境の場合、WSLをinstallするところから始める必要があるので、installしていない人は面倒です。(ただ1時間もあればできるのでやる価値はあります)
置き換え
自プロジェクト内であれば、原則として置き換えましょう。置き換え先はAndroidが公式にリリースノートでアナウンスしてくれています。
例えばタイトルの場合は以下のようにjavax.net.sslを使用するように推奨しています。
Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getAlpnSelectedProtocol()[B # Use javax.net.ssl.SSLSocket#getApplicationProtocol(). Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setAlpnProtocols([B)V # Use javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[]). Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setAlpnProtocols([Ljava/lang/String;)V # Use javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[]). Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setHostname(Ljava/lang/String;)V # Use javax.net.ssl.SSLParameters#setServerNames. Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setUseSessionTickets(Z)V # Use android.net.ssl.SSLSockets#setUseSessionTickets.
置き換えできない場合は影響度の評価
公式サイトに影響度についてまとめてくれています。
- blacklist : 使用不可。クラッシュします。
- greylist-max-x :x(例えばp=Pieの意味)までは使用可能ですが、それ以上では使用不可。クラッシュします。
- greylist :使用可能。ただいつサイレントに変更が加えられるか不明なので原則として使用しないこと。
- whitelist :sdkなので使用可能
やはりgreylist-max-xまでは置き換えたいところです。Target APIを高く設定できなくなってしまうので。それ以下は一旦無視でも問題ないですが注視する必要はあります。
Third partyのライブラリの場合は、最新バージョンにすると修正が入っている可能性があります。あるいは自分で置き換えてPRを出すというのもありです。