This is a post-mortem where the very dangerous permission, READ_PHONE_STATE
, unintentionally sneaked into our app. Here’s how this could happen, how we debugged and finally how we solved it.
Prologue#
Sprint comes to an end and we’re happy to deliver a new release of our app. After rolling it out to our beta community without issues, we move ahead to production.
While everything looks fine at first, after a while we see users complaining:
Honestly we were completely taken aback by this… But sure enough looking at the play store:
Root cause#
If you ever run into a similar issue, the Android Studio merged manifest view is the way to go. Just open your manifest and click the Merged manifest
tab at the bottom.
Sure enough, the READ_PHONE_STATE
permission is there.
Unfortunately, this view couldn’t help us find where the permission was merged from:
- Double-clicking the permission led us back to normal manifest view
- Color coding palette is so subtle that we couldn’t see what color the permission was highlighted in
Fortunately, the manifest merger also prints a log file to build/outputs/logs
that describes where everything is merged from.
This file gave a clear answer:
uses-permission#android.permission.READ_PHONE_STATE
IMPLIED from /app/src/debug/AndroidManifest.xml:8:1-15:12
reason: hue.libraries.translations has a targetSdkVersion < 4
Wow… That’s nasty!
A while ago we decided to move all our translations to a new module, with an empty manifest and a bare-bones build.gradle
file:
apply plugin: 'com.android.library'
android {
compileSdkVersion Config.compileSdk
}
And because we didn’t explicitly set the targetSdk, a targetSdk of 1 is assumed and hence we end up with a dangerous permission!
To be fair, the documentation does warn you about this:
But still… wow!
Solution#
While a solution could be to simply set the targetSdk in our translations module. This wouldn’t prevent something similar from happening in the future.
Therefore we decided to set the targetSdk (and others) for all our modules in the top level build.gradle
file. This also keeps submodule build.gradle
files lean.
subprojects {
afterEvaluate { project ->
if (project.plugins.findPlugin('android') ?: project.plugins.findPlugin('android-library')) {
android {
buildToolsVersion Config.buildTools
compileSdkVersion Config.compileSdk
defaultConfig {
minSdkVersion Config.minSdk
targetSdkVersion Config.compileSdk
}
compileOptions {
sourceCompatibility Config.javaVersion
targetCompatibility Config.javaVersion
}
}
}
}
}
Additionally, we also wanted to protect ourselves against 3rd party library developers that could make this mistake. To do so, you can inform the manifest merger to remove the permission while merging by adding the following to your manifest:
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove"/>
That is overkill you say?
Well, Firebase AND Google play services already made this booboo in the past.
… wow!
Wrap-up#
Not explicitly setting your target SDK version will cause a dangerous permission to sneak into your app. Make sure you set the target SDK in every module and protect yourself from 3rd party libraries.
If you’ve made it this far you should probably follow me on Mastodon. Feel free leave a comment below!