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