1 /* 2 * Copyright (C) 2017 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 import dalvik.system.VMRuntime; 18 import java.lang.invoke.MethodHandles; 19 import java.lang.invoke.MethodType; 20 import java.util.function.Consumer; 21 22 public class ChildClass { 23 enum PrimitiveType { 24 TInteger('I', Integer.TYPE, Integer.valueOf(0)), 25 TLong('J', Long.TYPE, Long.valueOf(0)), 26 TFloat('F', Float.TYPE, Float.valueOf(0)), 27 TDouble('D', Double.TYPE, Double.valueOf(0)), 28 TBoolean('Z', Boolean.TYPE, Boolean.valueOf(false)), 29 TByte('B', Byte.TYPE, Byte.valueOf((byte) 0)), 30 TShort('S', Short.TYPE, Short.valueOf((short) 0)), 31 TCharacter('C', Character.TYPE, Character.valueOf('0')); 32 PrimitiveType(char shorty, Class klass, Object value)33 PrimitiveType(char shorty, Class klass, Object value) { 34 mShorty = shorty; 35 mClass = klass; 36 mDefaultValue = value; 37 } 38 39 public char mShorty; 40 public Class mClass; 41 public Object mDefaultValue; 42 } 43 44 enum Hiddenness { 45 Whitelist(PrimitiveType.TShort), 46 LightGreylist(PrimitiveType.TBoolean), 47 DarkGreylist(PrimitiveType.TByte), 48 Blacklist(PrimitiveType.TCharacter), 49 BlacklistAndCorePlatformApi(PrimitiveType.TInteger); 50 Hiddenness(PrimitiveType type)51 Hiddenness(PrimitiveType type) { mAssociatedType = type; } 52 public PrimitiveType mAssociatedType; 53 } 54 55 enum Visibility { 56 Public(PrimitiveType.TInteger), 57 Package(PrimitiveType.TFloat), 58 Protected(PrimitiveType.TLong), 59 Private(PrimitiveType.TDouble); 60 Visibility(PrimitiveType type)61 Visibility(PrimitiveType type) { mAssociatedType = type; } 62 public PrimitiveType mAssociatedType; 63 } 64 65 enum Behaviour { 66 Granted, 67 Warning, 68 Denied, 69 } 70 71 // This needs to be kept in sync with DexDomain in Main. 72 enum DexDomain { 73 CorePlatform, 74 Platform, 75 Application 76 } 77 78 private static final boolean booleanValues[] = new boolean[] { false, true }; 79 runTest(String libFileName, int parentDomainOrdinal, int childDomainOrdinal, boolean everythingWhitelisted)80 public static void runTest(String libFileName, int parentDomainOrdinal, 81 int childDomainOrdinal, boolean everythingWhitelisted) throws Exception { 82 System.load(libFileName); 83 84 parentDomain = DexDomain.values()[parentDomainOrdinal]; 85 childDomain = DexDomain.values()[childDomainOrdinal]; 86 87 configMessage = "parentDomain=" + parentDomain.name() + ", childDomain=" + childDomain.name() 88 + ", everythingWhitelisted=" + everythingWhitelisted; 89 90 // Check expectations about loading into boot class path. 91 boolean isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null); 92 boolean expectedParentInBoot = (parentDomain != DexDomain.Application); 93 if (isParentInBoot != expectedParentInBoot) { 94 throw new RuntimeException("Expected ParentClass " + 95 (expectedParentInBoot ? "" : "not ") + "in boot class path"); 96 } 97 boolean isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null); 98 boolean expectedChildInBoot = (childDomain != DexDomain.Application); 99 if (isChildInBoot != expectedChildInBoot) { 100 throw new RuntimeException("Expected ChildClass " + (expectedChildInBoot ? "" : "not ") + 101 "in boot class path"); 102 } 103 ChildClass.everythingWhitelisted = everythingWhitelisted; 104 105 boolean isSameBoot = (isParentInBoot == isChildInBoot); 106 boolean isDebuggable = VMRuntime.getRuntime().isJavaDebuggable(); 107 108 // Run meaningful combinations of access flags. 109 for (Hiddenness hiddenness : Hiddenness.values()) { 110 final Behaviour expected; 111 final boolean invokesMemberCallback; 112 // Warnings are now disabled whenever access is granted, even for 113 // greylisted APIs. This is the behaviour for release builds. 114 if (everythingWhitelisted || hiddenness == Hiddenness.Whitelist) { 115 expected = Behaviour.Granted; 116 invokesMemberCallback = false; 117 } else if (parentDomain == DexDomain.CorePlatform && childDomain == DexDomain.Platform) { 118 expected = (hiddenness == Hiddenness.BlacklistAndCorePlatformApi) 119 ? Behaviour.Granted : Behaviour.Denied; 120 invokesMemberCallback = false; 121 } else if (isSameBoot) { 122 expected = Behaviour.Granted; 123 invokesMemberCallback = false; 124 } else if (hiddenness == Hiddenness.Blacklist || 125 hiddenness == Hiddenness.BlacklistAndCorePlatformApi) { 126 expected = Behaviour.Denied; 127 invokesMemberCallback = true; 128 } else { 129 expected = Behaviour.Warning; 130 invokesMemberCallback = true; 131 } 132 133 for (boolean isStatic : booleanValues) { 134 String suffix = (isStatic ? "Static" : "") + hiddenness.name(); 135 136 for (Visibility visibility : Visibility.values()) { 137 // Test reflection and JNI on methods and fields 138 for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) { 139 String baseName = visibility.name() + suffix; 140 checkField(klass, "field" + baseName, isStatic, visibility, expected, 141 invokesMemberCallback); 142 checkMethod(klass, "method" + baseName, isStatic, visibility, expected, 143 invokesMemberCallback); 144 } 145 146 // Check whether one can use a class constructor. 147 checkConstructor(ParentClass.class, visibility, hiddenness, expected); 148 149 // Check whether one can use an interface default method. 150 String name = "method" + visibility.name() + "Default" + hiddenness.name(); 151 checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected, 152 invokesMemberCallback); 153 } 154 155 // Test whether static linking succeeds. 156 checkLinking("LinkFieldGet" + suffix, /*takesParameter*/ false, expected); 157 checkLinking("LinkFieldSet" + suffix, /*takesParameter*/ true, expected); 158 checkLinking("LinkMethod" + suffix, /*takesParameter*/ false, expected); 159 checkLinking("LinkMethodInterface" + suffix, /*takesParameter*/ false, expected); 160 } 161 162 // Check whether Class.newInstance succeeds. 163 checkNullaryConstructor(Class.forName("NullaryConstructor" + hiddenness.name()), expected); 164 } 165 } 166 167 static final class RecordingConsumer implements Consumer<String> { 168 public String recordedValue = null; 169 170 @Override accept(String value)171 public void accept(String value) { 172 recordedValue = value; 173 } 174 } 175 checkMemberCallback(Class<?> klass, String name, boolean isPublic, boolean isField, boolean expectedCallback)176 private static void checkMemberCallback(Class<?> klass, String name, 177 boolean isPublic, boolean isField, boolean expectedCallback) { 178 try { 179 RecordingConsumer consumer = new RecordingConsumer(); 180 VMRuntime.setNonSdkApiUsageConsumer(consumer); 181 try { 182 if (isPublic) { 183 if (isField) { 184 klass.getField(name); 185 } else { 186 klass.getMethod(name); 187 } 188 } else { 189 if (isField) { 190 klass.getDeclaredField(name); 191 } else { 192 klass.getDeclaredMethod(name); 193 } 194 } 195 } catch (NoSuchFieldException|NoSuchMethodException ignored) { 196 // We're not concerned whether an exception is thrown or not - we're 197 // only interested in whether the callback is invoked. 198 } 199 200 boolean actualCallback = consumer.recordedValue != null && 201 consumer.recordedValue.contains(name); 202 if (expectedCallback != actualCallback) { 203 if (expectedCallback) { 204 throw new RuntimeException("Expected callback for member: " + name); 205 } else { 206 throw new RuntimeException("Did not expect callback for member: " + name); 207 } 208 } 209 } finally { 210 VMRuntime.setNonSdkApiUsageConsumer(null); 211 } 212 } 213 checkField(Class<?> klass, String name, boolean isStatic, Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback)214 private static void checkField(Class<?> klass, String name, boolean isStatic, 215 Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback) throws Exception { 216 217 boolean isPublic = (visibility == Visibility.Public); 218 boolean canDiscover = (behaviour != Behaviour.Denied); 219 220 if (klass.isInterface() && (!isStatic || !isPublic)) { 221 // Interfaces only have public static fields. 222 return; 223 } 224 225 // Test discovery with reflection. 226 227 if (Reflection.canDiscoverWithGetDeclaredField(klass, name) != canDiscover) { 228 throwDiscoveryException(klass, name, true, "getDeclaredField()", canDiscover); 229 } 230 231 if (Reflection.canDiscoverWithGetDeclaredFields(klass, name) != canDiscover) { 232 throwDiscoveryException(klass, name, true, "getDeclaredFields()", canDiscover); 233 } 234 235 if (Reflection.canDiscoverWithGetField(klass, name) != (canDiscover && isPublic)) { 236 throwDiscoveryException(klass, name, true, "getField()", (canDiscover && isPublic)); 237 } 238 239 if (Reflection.canDiscoverWithGetFields(klass, name) != (canDiscover && isPublic)) { 240 throwDiscoveryException(klass, name, true, "getFields()", (canDiscover && isPublic)); 241 } 242 243 // Test discovery with JNI. 244 245 if (JNI.canDiscoverField(klass, name, isStatic) != canDiscover) { 246 throwDiscoveryException(klass, name, true, "JNI", canDiscover); 247 } 248 249 // Test discovery with MethodHandles.lookup() which is caller 250 // context sensitive. 251 252 final MethodHandles.Lookup lookup = MethodHandles.lookup(); 253 if (JLI.canDiscoverWithLookupFindGetter(lookup, klass, name, int.class) 254 != canDiscover) { 255 throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findGetter()", 256 canDiscover); 257 } 258 if (JLI.canDiscoverWithLookupFindStaticGetter(lookup, klass, name, int.class) 259 != canDiscover) { 260 throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findStaticGetter()", 261 canDiscover); 262 } 263 264 // Test discovery with MethodHandles.publicLookup() which can only 265 // see public fields. Looking up setters here and fields in 266 // interfaces are implicitly final. 267 268 final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); 269 if (JLI.canDiscoverWithLookupFindSetter(publicLookup, klass, name, int.class) 270 != canDiscover) { 271 throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findSetter()", 272 canDiscover); 273 } 274 if (JLI.canDiscoverWithLookupFindStaticSetter(publicLookup, klass, name, int.class) 275 != canDiscover) { 276 throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findStaticSetter()", 277 canDiscover); 278 } 279 280 if (canDiscover) { 281 // Test that modifiers are unaffected. 282 283 if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) { 284 throwModifiersException(klass, name, true); 285 } 286 287 // Test getters and setters when meaningful. 288 289 if (!Reflection.canGetField(klass, name)) { 290 throwAccessException(klass, name, true, "Field.getInt()"); 291 } 292 if (!Reflection.canSetField(klass, name)) { 293 throwAccessException(klass, name, true, "Field.setInt()"); 294 } 295 if (!JNI.canGetField(klass, name, isStatic)) { 296 throwAccessException(klass, name, true, "getIntField"); 297 } 298 if (!JNI.canSetField(klass, name, isStatic)) { 299 throwAccessException(klass, name, true, "setIntField"); 300 } 301 } 302 303 // Test that callbacks are invoked correctly. 304 checkMemberCallback(klass, name, isPublic, true /* isField */, invokesMemberCallback); 305 } 306 checkMethod(Class<?> klass, String name, boolean isStatic, Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback)307 private static void checkMethod(Class<?> klass, String name, boolean isStatic, 308 Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback) throws Exception { 309 310 boolean isPublic = (visibility == Visibility.Public); 311 if (klass.isInterface() && !isPublic) { 312 // All interface members are public. 313 return; 314 } 315 316 boolean canDiscover = (behaviour != Behaviour.Denied); 317 318 // Test discovery with reflection. 319 320 if (Reflection.canDiscoverWithGetDeclaredMethod(klass, name) != canDiscover) { 321 throwDiscoveryException(klass, name, false, "getDeclaredMethod()", canDiscover); 322 } 323 324 if (Reflection.canDiscoverWithGetDeclaredMethods(klass, name) != canDiscover) { 325 throwDiscoveryException(klass, name, false, "getDeclaredMethods()", canDiscover); 326 } 327 328 if (Reflection.canDiscoverWithGetMethod(klass, name) != (canDiscover && isPublic)) { 329 throwDiscoveryException(klass, name, false, "getMethod()", (canDiscover && isPublic)); 330 } 331 332 if (Reflection.canDiscoverWithGetMethods(klass, name) != (canDiscover && isPublic)) { 333 throwDiscoveryException(klass, name, false, "getMethods()", (canDiscover && isPublic)); 334 } 335 336 // Test discovery with JNI. 337 338 if (JNI.canDiscoverMethod(klass, name, isStatic) != canDiscover) { 339 throwDiscoveryException(klass, name, false, "JNI", canDiscover); 340 } 341 342 // Test discovery with MethodHandles.lookup(). 343 344 final MethodHandles.Lookup lookup = MethodHandles.lookup(); 345 final MethodType methodType = MethodType.methodType(int.class); 346 if (JLI.canDiscoverWithLookupFindVirtual(lookup, klass, name, methodType) != canDiscover) { 347 throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findVirtual()", 348 canDiscover); 349 } 350 351 if (JLI.canDiscoverWithLookupFindStatic(lookup, klass, name, methodType) != canDiscover) { 352 throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findStatic()", 353 canDiscover); 354 } 355 356 // Finish here if we could not discover the method. 357 358 if (canDiscover) { 359 // Test that modifiers are unaffected. 360 361 if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) { 362 throwModifiersException(klass, name, false); 363 } 364 365 // Test whether we can invoke the method. This skips non-static interface methods. 366 if (!klass.isInterface() || isStatic) { 367 if (!Reflection.canInvokeMethod(klass, name)) { 368 throwAccessException(klass, name, false, "invoke()"); 369 } 370 if (!JNI.canInvokeMethodA(klass, name, isStatic)) { 371 throwAccessException(klass, name, false, "CallMethodA"); 372 } 373 if (!JNI.canInvokeMethodV(klass, name, isStatic)) { 374 throwAccessException(klass, name, false, "CallMethodV"); 375 } 376 } 377 } 378 379 // Test that callbacks are invoked correctly. 380 checkMemberCallback(klass, name, isPublic, false /* isField */, invokesMemberCallback); 381 } 382 checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness, Behaviour behaviour)383 private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness, 384 Behaviour behaviour) throws Exception { 385 386 boolean isPublic = (visibility == Visibility.Public); 387 String signature = "(" + visibility.mAssociatedType.mShorty + 388 hiddenness.mAssociatedType.mShorty + ")V"; 389 String fullName = "<init>" + signature; 390 Class<?> args[] = new Class[] { visibility.mAssociatedType.mClass, 391 hiddenness.mAssociatedType.mClass }; 392 Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue, 393 hiddenness.mAssociatedType.mDefaultValue }; 394 MethodType methodType = MethodType.methodType(void.class, args); 395 396 boolean canDiscover = (behaviour != Behaviour.Denied); 397 398 // Test discovery with reflection. 399 400 if (Reflection.canDiscoverWithGetDeclaredConstructor(klass, args) != canDiscover) { 401 throwDiscoveryException(klass, fullName, false, "getDeclaredConstructor()", canDiscover); 402 } 403 404 if (Reflection.canDiscoverWithGetDeclaredConstructors(klass, args) != canDiscover) { 405 throwDiscoveryException(klass, fullName, false, "getDeclaredConstructors()", canDiscover); 406 } 407 408 if (Reflection.canDiscoverWithGetConstructor(klass, args) != (canDiscover && isPublic)) { 409 throwDiscoveryException( 410 klass, fullName, false, "getConstructor()", (canDiscover && isPublic)); 411 } 412 413 if (Reflection.canDiscoverWithGetConstructors(klass, args) != (canDiscover && isPublic)) { 414 throwDiscoveryException( 415 klass, fullName, false, "getConstructors()", (canDiscover && isPublic)); 416 } 417 418 // Test discovery with JNI. 419 420 if (JNI.canDiscoverConstructor(klass, signature) != canDiscover) { 421 throwDiscoveryException(klass, fullName, false, "JNI", canDiscover); 422 } 423 424 // Test discovery with MethodHandles.lookup() 425 426 final MethodHandles.Lookup lookup = MethodHandles.lookup(); 427 if (JLI.canDiscoverWithLookupFindConstructor(lookup, klass, methodType) != canDiscover) { 428 throwDiscoveryException(klass, fullName, false, "MethodHandles.lookup().findConstructor", 429 canDiscover); 430 } 431 432 final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); 433 if (JLI.canDiscoverWithLookupFindConstructor(publicLookup, klass, methodType) != canDiscover) { 434 throwDiscoveryException(klass, fullName, false, 435 "MethodHandles.publicLookup().findConstructor", 436 canDiscover); 437 } 438 439 if (canDiscover) { 440 // Test whether we can invoke the constructor. 441 442 if (!Reflection.canInvokeConstructor(klass, args, initargs)) { 443 throwAccessException(klass, fullName, false, "invoke()"); 444 } 445 if (!JNI.canInvokeConstructorA(klass, signature)) { 446 throwAccessException(klass, fullName, false, "NewObjectA"); 447 } 448 if (!JNI.canInvokeConstructorV(klass, signature)) { 449 throwAccessException(klass, fullName, false, "NewObjectV"); 450 } 451 } 452 } 453 checkNullaryConstructor(Class<?> klass, Behaviour behaviour)454 private static void checkNullaryConstructor(Class<?> klass, Behaviour behaviour) 455 throws Exception { 456 boolean canAccess = (behaviour != Behaviour.Denied); 457 458 if (Reflection.canUseNewInstance(klass) != canAccess) { 459 throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") + 460 "be able to construct " + klass.getName() + ". " + configMessage); 461 } 462 } 463 checkLinking(String className, boolean takesParameter, Behaviour behaviour)464 private static void checkLinking(String className, boolean takesParameter, Behaviour behaviour) 465 throws Exception { 466 boolean canAccess = (behaviour != Behaviour.Denied); 467 468 if (Linking.canAccess(className, takesParameter) != canAccess) { 469 throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") + 470 "be able to verify " + className + "." + configMessage); 471 } 472 } 473 throwDiscoveryException(Class<?> klass, String name, boolean isField, String fn, boolean canAccess)474 private static void throwDiscoveryException(Class<?> klass, String name, boolean isField, 475 String fn, boolean canAccess) { 476 throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() + 477 "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " + 478 configMessage); 479 } 480 throwAccessException(Class<?> klass, String name, boolean isField, String fn)481 private static void throwAccessException(Class<?> klass, String name, boolean isField, 482 String fn) { 483 throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") + 484 klass.getName() + "." + name + " using " + fn + ". " + configMessage); 485 } 486 throwModifiersException(Class<?> klass, String name, boolean isField)487 private static void throwModifiersException(Class<?> klass, String name, boolean isField) { 488 throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() + 489 "." + name + " to not expose hidden modifiers"); 490 } 491 492 private static DexDomain parentDomain; 493 private static DexDomain childDomain; 494 private static boolean everythingWhitelisted; 495 496 private static String configMessage; 497 } 498