1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.android.lint.parcel 18 19 import com.android.tools.lint.detector.api.* 20 import com.intellij.psi.PsiMethod 21 import com.intellij.psi.PsiSubstitutor 22 import com.intellij.psi.PsiType 23 import com.intellij.psi.PsiTypeParameter 24 import org.jetbrains.uast.UCallExpression 25 import java.util.* 26 27 @Suppress("UnstableApiUsage") 28 class SaferParcelChecker : Detector(), SourceCodeScanner { getApplicableMethodNamesnull29 override fun getApplicableMethodNames(): List<String> = 30 MIGRATORS 31 .map(CallMigrator::method) 32 .map(Method::name) 33 34 override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { 35 if (!isAtLeastT(context)) return 36 val signature = getSignature(method) 37 val migrator = MIGRATORS.firstOrNull { it.method.signature == signature } ?: return 38 migrator.report(context, node, method) 39 } 40 getSignaturenull41 private fun getSignature(method: PsiMethod): String { 42 val name = UastLintUtils.getQualifiedName(method) 43 val signature = method.getSignature(PsiSubstitutor.EMPTY) 44 val parameters = 45 signature.parameterTypes.joinToString(transform = PsiType::getCanonicalText) 46 val types = signature.typeParameters.map(PsiTypeParameter::getName) 47 val prefix = if (types.isEmpty()) "" else types.joinToString(", ", "<", ">") + " " 48 return "$prefix$name($parameters)" 49 } 50 isAtLeastTnull51 private fun isAtLeastT(context: Context): Boolean { 52 val project = if (context.isGlobalAnalysis()) context.mainProject else context.project 53 return project.isAndroidProject && project.minSdkVersion.featureLevel >= 33 54 } 55 56 companion object { 57 @JvmField 58 val ISSUE_UNSAFE_API_USAGE: Issue = Issue.create( 59 id = "UnsafeParcelApi", 60 briefDescription = "Use of unsafe deserialization API", 61 explanation = """ 62 You are using a deprecated deserialization API that doesn't accept the expected class as\ 63 a parameter. This means that unexpected classes could be instantiated and\ 64 unexpected code executed. 65 66 Please migrate to the safer alternative that takes an extra Class<T> parameter. 67 """, 68 category = Category.SECURITY, 69 priority = 8, 70 severity = Severity.WARNING, 71 72 implementation = Implementation( 73 SaferParcelChecker::class.java, 74 Scope.JAVA_FILE_SCOPE 75 ) 76 ) 77 78 // Parcel 79 private val PARCEL_METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf()) 80 private val PARCEL_METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader")) 81 private val PARCEL_METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader")) 82 private val PARCEL_METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader")) 83 private val PARCEL_METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader")) 84 private val PARCEL_METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader")) 85 private val PARCEL_METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader")) 86 private val PARCEL_METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader")) 87 88 // Bundle 89 private val BUNDLE_METHOD_GET_SERIALIZABLE = Method("android.os.Bundle", "getSerializable", listOf("java.lang.String")) 90 private val BUNDLE_METHOD_GET_PARCELABLE = Method(listOf("T"), "android.os.Bundle", "getParcelable", listOf("java.lang.String")) 91 private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST = Method(listOf("T"), "android.os.Bundle", "getParcelableArrayList", listOf("java.lang.String")) 92 private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY = Method("android.os.Bundle", "getParcelableArray", listOf("java.lang.String")) 93 private val BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY = Method(listOf("T"), "android.os.Bundle", "getSparseParcelableArray", listOf("java.lang.String")) 94 95 // Intent 96 private val INTENT_METHOD_GET_SERIALIZABLE_EXTRA = Method("android.content.Intent", "getSerializableExtra", listOf("java.lang.String")) 97 private val INTENT_METHOD_GET_PARCELABLE_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableExtra", listOf("java.lang.String")) 98 private val INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA = Method("android.content.Intent", "getParcelableArrayExtra", listOf("java.lang.String")) 99 private val INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableArrayListExtra", listOf("java.lang.String")) 100 101 // TODO: Write migrators for methods below 102 private val PARCEL_METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader")) 103 104 private val MIGRATORS = listOf( 105 ReturnMigrator(PARCEL_METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")), 106 ContainerArgumentMigrator(PARCEL_METHOD_READ_LIST, 0, "java.util.List"), 107 ContainerReturnMigrator(PARCEL_METHOD_READ_ARRAY_LIST, "java.util.Collection"), 108 ContainerReturnMigrator(PARCEL_METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"), 109 ContainerArgumentMigrator(PARCEL_METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"), 110 ReturnMigratorWithClassLoader(PARCEL_METHOD_READ_SERIALIZABLE), 111 ArrayReturnMigrator(PARCEL_METHOD_READ_ARRAY, setOf("java.lang.Object")), 112 ArrayReturnMigrator(PARCEL_METHOD_READ_PARCELABLE_ARRAY, setOf("android.os.Parcelable")), 113 114 ReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE, setOf("android.os.Parcelable")), 115 ContainerReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST, "java.util.Collection", setOf("android.os.Parcelable")), 116 ArrayReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY, setOf("android.os.Parcelable")), 117 ContainerReturnMigrator(BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY, "android.util.SparseArray", setOf("android.os.Parcelable")), 118 ReturnMigrator(BUNDLE_METHOD_GET_SERIALIZABLE, setOf("java.io.Serializable")), 119 120 ReturnMigrator(INTENT_METHOD_GET_PARCELABLE_EXTRA, setOf("android.os.Parcelable")), 121 ContainerReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA, "java.util.Collection", setOf("android.os.Parcelable")), 122 ArrayReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA, setOf("android.os.Parcelable")), 123 ReturnMigrator(INTENT_METHOD_GET_SERIALIZABLE_EXTRA, setOf("java.io.Serializable")), 124 ) 125 } 126 } 127