• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
hasMatchingField_Reflection(Class<?> klass, DexField dexField)159     private static boolean hasMatchingField_Reflection(Class<?> klass, DexField dexField) {
160         try {
161             klass.getDeclaredField(dexField.getName());
162             return true;
163         } catch (NoSuchFieldException ex) {
164             return false;
165         } catch (NoClassDefFoundError ex) {
166             // The field has a type that cannot be loaded.
167             return true;
168         }
169     }
170 
hasMatchingField_JNI(Class<?> klass, DexField dexField)171     private static boolean hasMatchingField_JNI(Class<?> klass, DexField dexField)
172             throws ClassNotFoundException {
173         // If we fail to resolve the type of the field, we will throw a ClassNotFoundException.
174         DexMember.typeToClass(dexField.getDexType());
175 
176         try {
177             Field ifield = getField_JNI(klass, dexField.getName(), dexField.getDexType());
178             if (ifield.getDeclaringClass() == klass) {
179               return true;
180             }
181         } catch (NoSuchFieldError e) {
182             // Not found.
183         }
184 
185         try {
186             Field sfield = getStaticField_JNI(klass, dexField.getName(), dexField.getDexType());
187             if (sfield.getDeclaringClass() == klass) {
188               return true;
189             }
190         } catch (NoSuchFieldError e) {
191             // Not found.
192         }
193 
194         return false;
195     }
196 
hasMatchingMethod_Reflection(Class<?> klass, DexMethod dexMethod)197     private static boolean hasMatchingMethod_Reflection(Class<?> klass, DexMethod dexMethod)
198                 throws ClassNotFoundException {
199         // If we fail to resolve all parameters or return type, we will throw
200         // ClassNotFoundException.
201         Class<?>[] parameterClasses = dexMethod.getJavaParameterClasses();
202         Class<?> returnClass = DexMember.typeToClass(dexMethod.getDexType());
203 
204         if (dexMethod.isConstructor()) {
205             try {
206                 if (klass.getDeclaredConstructor(parameterClasses) != null) {
207                     return true;
208                 }
209             } catch (NoSuchMethodException e) {
210               return false;
211             }
212         } else if (!dexMethod.isStaticConstructor()) {
213             List<String> methodParams = dexMethod.getJavaParameterTypes();
214             String methodReturnType = dexMethod.getJavaType();
215             try {
216                 // First try with getDeclaredMethods, hoping all parameter and return types can be
217                 // resolved.
218                 for (Method method : klass.getDeclaredMethods()) {
219                     if (method.getName().equals(dexMethod.getName())
220                             && method.getReturnType().getTypeName().equals(methodReturnType)
221                             && typesMatch(method.getParameterTypes(), methodParams)) {
222                         return true;
223                     }
224                 }
225             } catch (NoClassDefFoundError ncdfe) {
226                 // Try with getMethods, which does not check parameter and return types are
227                 // resolved, but only returns public methods.
228                 for (Method method : klass.getMethods()) {
229                     if (method.getName().equals(dexMethod.getName())
230                             && method.getClass() == klass
231                             && method.getReturnType().getTypeName().equals(methodReturnType)
232                             && typesMatch(method.getParameterTypes(), methodParams)) {
233                         return true;
234                     }
235                 }
236                 // Last chance, try with getDeclaredMethod.
237                 try {
238                     Method m = klass.getDeclaredMethod(dexMethod.getName(), parameterClasses);
239                     if (m.getReturnType().getTypeName().equals(dexMethod.getJavaType())) {
240                         return true;
241                     }
242                     // This means we found a method with a different return type. We cannot make
243                     // any conclusion here: the method may exisit or not. However, given we have
244                     // not found the method through getMethods and getDeclaredMethods, we know
245                     // this method won't be accessible through reflection.
246                 } catch (NoSuchMethodException nsme) {
247                   return false;
248                 }
249             }
250         }
251         return false;
252     }
253 
hasMatchingMethod_JNI(Class<?> klass, DexMethod dexMethod)254     private static boolean hasMatchingMethod_JNI(Class<?> klass, DexMethod dexMethod) {
255         try {
256             Executable imethod = getMethod_JNI(
257                 klass, dexMethod.getName(), dexMethod.getDexSignature());
258             if (imethod.getDeclaringClass() == klass) {
259                 return true;
260             }
261         } catch (NoSuchMethodError e) {
262             // Not found.
263         }
264 
265         if (!dexMethod.isConstructor()) {
266             try {
267                 Executable smethod =
268                     getStaticMethod_JNI(klass, dexMethod.getName(), dexMethod.getDexSignature());
269                 if (smethod.getDeclaringClass() == klass) {
270                     return true;
271                 }
272             } catch (NoSuchMethodError e) {
273                 // Not found.
274             }
275         }
276 
277         return false;
278     }
279 
getField_JNI(Class<?> klass, String name, String type)280     private static native Field getField_JNI(Class<?> klass, String name, String type);
getStaticField_JNI(Class<?> klass, String name, String type)281     private static native Field getStaticField_JNI(Class<?> klass, String name, String type);
getMethod_JNI(Class<?> klass, String name, String signature)282     private static native Executable getMethod_JNI(Class<?> klass, String name, String signature);
getStaticMethod_JNI(Class<?> klass, String name, String signature)283     private static native Executable getStaticMethod_JNI(Class<?> klass, String name,
284             String signature);
285 
286 }
287