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