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