1 /*
2  * Copyright (C) 2018 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 android.signature.cts;
18 
19 import android.util.Log;
20 
21 import java.lang.reflect.Executable;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.util.List;
26 
27 public class DexMemberChecker {
28     public static final String TAG = "DexMemberChecker";
29 
30     public interface Observer {
classAccessible(boolean accessible, DexMember member)31         void classAccessible(boolean accessible, DexMember member);
fieldAccessibleViaReflection(boolean accessible, DexField field)32         void fieldAccessibleViaReflection(boolean accessible, DexField field);
fieldAccessibleViaJni(boolean accessible, DexField field)33         void fieldAccessibleViaJni(boolean accessible, DexField field);
methodAccessibleViaReflection(boolean accessible, DexMethod method)34         void methodAccessibleViaReflection(boolean accessible, DexMethod method);
methodAccessibleViaJni(boolean accessible, DexMethod method)35         void methodAccessibleViaJni(boolean accessible, DexMethod method);
36     }
37 
init()38     public static void init() {
39         System.loadLibrary("cts_dexchecker");
40     }
41 
call_VMDebug_allowHiddenApiReflectionFrom(Class<?> klass)42     private static void call_VMDebug_allowHiddenApiReflectionFrom(Class<?> klass) throws Exception {
43       Method method = null;
44 
45       try {
46         Class<?> vmdebug = Class.forName("dalvik.system.VMDebug");
47         method = vmdebug.getDeclaredMethod("allowHiddenApiReflectionFrom", Class.class);
48       } catch (Exception ex) {
49         // Could not find the method. Report the problem as a RuntimeException.
50         throw new RuntimeException(ex);
51       }
52 
53       try {
54         method.invoke(null, klass);
55       } catch (InvocationTargetException ex) {
56         // Rethrow the original exception.
57         Throwable cause = ex.getCause();
58         // Please the compiler's 'throws' static analysis.
59         if (cause instanceof Exception) {
60           throw (Exception) cause;
61         } else {
62           throw (Error) cause;
63         }
64       }
65     }
66 
requestExemptionFromHiddenApiChecks()67     public static boolean requestExemptionFromHiddenApiChecks() throws Exception {
68       try {
69         call_VMDebug_allowHiddenApiReflectionFrom(DexMemberChecker.class);
70         return true;
71       } catch (SecurityException ex) {
72         return false;
73       }
74     }
75 
checkSingleMember(DexMember dexMember, DexMemberChecker.Observer observer)76     public static void checkSingleMember(DexMember dexMember, DexMemberChecker.Observer observer) {
77         checkSingleMember(dexMember, /* reflection= */ true, /* jni= */ true, observer);
78     }
79 
checkSingleMember(DexMember dexMember, boolean reflection, boolean jni, DexMemberChecker.Observer observer)80     public static void checkSingleMember(DexMember dexMember, boolean reflection, boolean jni,
81             DexMemberChecker.Observer observer) {
82         Class<?> klass = findClass(dexMember);
83         if (klass == null) {
84             // Class not found. Therefore its members are not visible.
85             observer.classAccessible(false, dexMember);
86             return;
87         }
88         observer.classAccessible(true, dexMember);
89 
90         if (dexMember instanceof DexField) {
91             DexField field = (DexField) dexMember;
92             if (reflection) {
93                 observer.fieldAccessibleViaReflection(
94                         hasMatchingField_Reflection(klass, field),
95                         field);
96             }
97             if (jni) {
98                 try {
99                     observer.fieldAccessibleViaJni(hasMatchingField_JNI(klass, field), field);
100                 } catch (ClassNotFoundException | Error e) {
101                     if ((e instanceof NoClassDefFoundError) && !(e.getCause() instanceof Error)) {
102                         throw (NoClassDefFoundError) e;
103                     }
104 
105                     // Could not initialize the class. Skip JNI test.
106                     Log.w(TAG, "JNI failed for " + dexMember.toString(), e);
107                 }
108             }
109         } else if (dexMember instanceof DexMethod) {
110             DexMethod method = (DexMethod) dexMember;
111             if (reflection) {
112                 try {
113                     observer.methodAccessibleViaReflection(
114                             hasMatchingMethod_Reflection(klass, method), method);
115                 } catch (ClassNotFoundException e) {
116                     Log.w(TAG, "Failed resolution of " + dexMember.toString(), e);
117                 }
118             }
119             if (jni) {
120                 try {
121                     observer.methodAccessibleViaJni(hasMatchingMethod_JNI(klass, method), method);
122                 } catch (Error e) {
123                     if ((e instanceof NoClassDefFoundError) && !(e.getCause() instanceof Error)) {
124                         throw e;
125                     }
126 
127                     // Could not initialize the class. Skip JNI test.
128                     Log.w(TAG, "JNI failed for " + dexMember.toString(), e);
129                 }
130             }
131         } else {
132             throw new IllegalStateException("Unexpected type of dex member");
133         }
134     }
135 
typesMatch(Class<?>[] classes, List<String> typeNames)136     private static boolean typesMatch(Class<?>[] classes, List<String> typeNames) {
137         if (classes.length != typeNames.size()) {
138             return false;
139         }
140         for (int i = 0; i < classes.length; ++i) {
141             if (!classes[i].getTypeName().equals(typeNames.get(i))) {
142                 return false;
143             }
144         }
145         return true;
146     }
147 
findClass(DexMember dexMember)148     private static Class<?> findClass(DexMember dexMember) {
149         try {
150             // Try to find the class. Do not initialize it - we do not want to run
151             // static initializers.
152             return Class.forName(dexMember.getJavaClassName(), /* initialize */ false,
153                 DexMemberChecker.class.getClassLoader());
154         } catch (ClassNotFoundException ex) {
155             return null;
156         }
157     }
158 
159     @SuppressWarnings("ReturnValueIgnored")
hasMatchingField_Reflection(Class<?> klass, DexField dexField)160     private static boolean hasMatchingField_Reflection(Class<?> klass, DexField dexField) {
161         try {
162             klass.getDeclaredField(dexField.getName());
163             return true;
164         } catch (NoSuchFieldException ex) {
165             return false;
166         } catch (NoClassDefFoundError ex) {
167             // The field has a type that cannot be loaded.
168             return true;
169         }
170     }
171 
hasMatchingField_JNI(Class<?> klass, DexField dexField)172     private static boolean hasMatchingField_JNI(Class<?> klass, DexField dexField)
173             throws ClassNotFoundException {
174         // If we fail to resolve the type of the field, we will throw a ClassNotFoundException.
175         DexMember.typeToClass(dexField.getDexType());
176 
177         try {
178             Field ifield = getField_JNI(klass, dexField.getName(), dexField.getDexType());
179             if (ifield.getDeclaringClass() == klass) {
180               return true;
181             }
182         } catch (NoSuchFieldError e) {
183             // Not found.
184         }
185 
186         try {
187             Field sfield = getStaticField_JNI(klass, dexField.getName(), dexField.getDexType());
188             if (sfield.getDeclaringClass() == klass) {
189               return true;
190             }
191         } catch (NoSuchFieldError e) {
192             // Not found.
193         }
194 
195         return false;
196     }
197 
hasMatchingMethod_Reflection(Class<?> klass, DexMethod dexMethod)198     private static boolean hasMatchingMethod_Reflection(Class<?> klass, DexMethod dexMethod)
199                 throws ClassNotFoundException {
200         // If we fail to resolve all parameters or return type, we will throw
201         // ClassNotFoundException.
202         Class<?>[] parameterClasses = dexMethod.getJavaParameterClasses();
203         Class<?> returnClass = DexMember.typeToClass(dexMethod.getDexType());
204 
205         if (dexMethod.isConstructor()) {
206             try {
207                 if (klass.getDeclaredConstructor(parameterClasses) != null) {
208                     return true;
209                 }
210             } catch (NoSuchMethodException e) {
211               return false;
212             }
213         } else if (!dexMethod.isStaticConstructor()) {
214             List<String> methodParams = dexMethod.getJavaParameterTypes();
215             String methodReturnType = dexMethod.getJavaType();
216             try {
217                 // First try with getDeclaredMethods, hoping all parameter and return types can be
218                 // resolved.
219                 for (Method method : klass.getDeclaredMethods()) {
220                     if (method.getName().equals(dexMethod.getName())
221                             && method.getReturnType().getTypeName().equals(methodReturnType)
222                             && typesMatch(method.getParameterTypes(), methodParams)) {
223                         return true;
224                     }
225                 }
226             } catch (NoClassDefFoundError ncdfe) {
227                 // Try with getMethods, which does not check parameter and return types are
228                 // resolved, but only returns public methods.
229                 for (Method method : klass.getMethods()) {
230                     if (method.getName().equals(dexMethod.getName())
231                             && method.getDeclaringClass() == klass
232                             && method.getReturnType().getTypeName().equals(methodReturnType)
233                             && typesMatch(method.getParameterTypes(), methodParams)) {
234                         return true;
235                     }
236                 }
237                 // Last chance, try with getDeclaredMethod.
238                 try {
239                     Method m = klass.getDeclaredMethod(dexMethod.getName(), parameterClasses);
240                     if (m.getReturnType().getTypeName().equals(dexMethod.getJavaType())) {
241                         return true;
242                     }
243                     // This means we found a method with a different return type. We cannot make
244                     // any conclusion here: the method may exisit or not. However, given we have
245                     // not found the method through getMethods and getDeclaredMethods, we know
246                     // this method won't be accessible through reflection.
247                 } catch (NoSuchMethodException nsme) {
248                   return false;
249                 }
250             }
251         }
252         return false;
253     }
254 
hasMatchingMethod_JNI(Class<?> klass, DexMethod dexMethod)255     private static boolean hasMatchingMethod_JNI(Class<?> klass, DexMethod dexMethod) {
256         try {
257             Executable imethod = getMethod_JNI(
258                 klass, dexMethod.getName(), dexMethod.getDexSignature());
259             if (imethod.getDeclaringClass() == klass) {
260                 return true;
261             }
262         } catch (NoSuchMethodError e) {
263             // Not found.
264         }
265 
266         if (!dexMethod.isConstructor()) {
267             try {
268                 Executable smethod =
269                     getStaticMethod_JNI(klass, dexMethod.getName(), dexMethod.getDexSignature());
270                 if (smethod.getDeclaringClass() == klass) {
271                     return true;
272                 }
273             } catch (NoSuchMethodError e) {
274                 // Not found.
275             }
276         }
277 
278         return false;
279     }
280 
getField_JNI(Class<?> klass, String name, String type)281     private static native Field getField_JNI(Class<?> klass, String name, String type);
getStaticField_JNI(Class<?> klass, String name, String type)282     private static native Field getStaticField_JNI(Class<?> klass, String name, String type);
getMethod_JNI(Class<?> klass, String name, String signature)283     private static native Executable getMethod_JNI(Class<?> klass, String name, String signature);
getStaticMethod_JNI(Class<?> klass, String name, String signature)284     private static native Executable getStaticMethod_JNI(Class<?> klass, String name,
285             String signature);
286 
287 }
288