blob: 511a082aa135ba03f9f348d850511be501af77d0 [file] [log] [blame]
package com.android.tools.lint.checks
import com.android.SdkConstants.ANDROID_URI
import com.android.SdkConstants.ATTR_EXPORTED
import com.android.SdkConstants.TAG_ACTIVITY
import com.android.SdkConstants.TAG_ACTIVITY_ALIAS
import com.android.SdkConstants.TAG_INTENT_FILTER
import com.android.SdkConstants.TAG_PROVIDER
import com.android.SdkConstants.TAG_RECEIVER
import com.android.SdkConstants.TAG_SERVICE
import com.android.sdklib.AndroidVersion.VersionCodes.S
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Context
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.LintMap
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.XmlContext
import com.android.tools.lint.detector.api.XmlScanner
import com.android.utils.subtag
import org.w3c.dom.Element
class ExportedReceiverDetector : Detector(), XmlScanner {
override fun getApplicableElements() =
listOf(TAG_ACTIVITY, TAG_ACTIVITY_ALIAS, TAG_SERVICE, TAG_RECEIVER, TAG_PROVIDER)
override fun visitElement(context: XmlContext, element: Element) {
val intentFilter = element.subtag(TAG_INTENT_FILTER)
val exported = element.getAttributeNodeNS(ANDROID_URI, ATTR_EXPORTED)
if (intentFilter != null && exported == null) {
val fix = LintFix.create().set().todo(ANDROID_URI, ATTR_EXPORTED).build()
val incident = Incident(
ISSUE,
element,
context.getNameLocation(element),
"When using intent filters, please specify `android:exported` as well",
fix
)
context.report(incident, map())
}
}
override fun filterIncident(context: Context, incident: Incident, map: LintMap): Boolean {
if (context.mainProject.targetSdk >= S) {
incident.overrideSeverity(Severity.ERROR)
}
return true
}
companion object {
@JvmField
val ISSUE = Issue.create(
id = "IntentFilterExportedReceiver",
briefDescription = "Unspecified `android:exported` in manifest",
explanation = """
Apps targeting Android 12 and higher are required to specify an explicit value \
for `android:exported` when the corresponding component has an intent filter defined. \
Otherwise, installation will fail.
Previously, `android:exported` for components without any intent filters present \
used to default to `false`, and when intent filters were present, the default was \
`true`. Defaults which change value based on other values are confusing and lead to \
apps accidentally exporting components as a side-effect of adding intent filters. \
This is a security risk, and we have made this change to avoid introducing \
accidental vulnerabilities.
While the default without intent filters remains unchanged, it is now required to \
explicitly specify a value when intent filters are present. Any app failing to meet \
this requirement will fail to install on any Android version after Android 11.
We recommend setting `android:exported` to false (even on previous versions of Android \
prior to this requirement) unless you have a good reason to export a particular \
component.
""",
category = Category.SECURITY,
priority = 5,
severity = Severity.WARNING,
implementation = Implementation(
ExportedReceiverDetector::class.java,
Scope.MANIFEST_SCOPE
)
)
}
}