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