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.InMemoryDexClassLoader;
18 import dalvik.system.PathClassLoader;
19 import dalvik.system.VMRuntime;
20 import java.io.File;
21 import java.io.InputStream;
22 import java.lang.reflect.Constructor;
23 import java.lang.reflect.Method;
24 import java.nio.ByteBuffer;
25 import java.nio.file.Files;
26 import java.util.Arrays;
27 import java.util.zip.ZipEntry;
28 import java.util.zip.ZipFile;
29 
30 public class Main {
main(String[] args)31   public static void main(String[] args) throws Exception {
32     System.loadLibrary(args[0]);
33     prepareNativeLibFileName(args[0]);
34 
35     // Enable hidden API checks in case they are disabled by default.
36     init();
37 
38     // TODO there are sequential depencies between these test cases, and bugs
39     // in the production code may lead to subsequent tests to erroneously pass,
40     // or test the wrong thing. We rely on not deduping hidden API warnings
41     // here for the same reasons), meaning the code under test and production
42     // code are running in different configurations. Each test should be run in
43     // a fresh process to ensure that they are working correcting and not
44     // accidentally interfering with eachother.
45 
46     // Run test with both parent and child dex files loaded with class loaders.
47     // The expectation is that hidden members in parent should be visible to
48     // the child.
49     doTest(false, false, false);
50     doUnloading();
51 
52     // Now append parent dex file to boot class path and run again. This time
53     // the child dex file should not be able to access private APIs of the
54     // parent.
55     appendToBootClassLoader(DEX_PARENT_BOOT);
56     doTest(true, false, false);
57     doUnloading();
58 
59     // Now run the same test again, but with the blacklist exmemptions list set
60     // to "L" which matches everything.
61     doTest(true, false, true);
62     doUnloading();
63 
64     // And finally append to child to boot class path as well. With both in the
65     // boot class path, access should be granted.
66     appendToBootClassLoader(DEX_CHILD);
67     doTest(true, true, false);
68     doUnloading();
69   }
70 
doTest(boolean parentInBoot, boolean childInBoot, boolean whitelistAllApis)71   private static void doTest(boolean parentInBoot, boolean childInBoot, boolean whitelistAllApis)
72       throws Exception {
73     // Load parent dex if it is not in boot class path.
74     ClassLoader parentLoader = null;
75     if (parentInBoot) {
76       parentLoader = BOOT_CLASS_LOADER;
77     } else {
78       parentLoader = new PathClassLoader(DEX_PARENT, ClassLoader.getSystemClassLoader());
79     }
80 
81     // Load child dex if it is not in boot class path.
82     ClassLoader childLoader = null;
83     if (childInBoot) {
84       if (parentLoader != BOOT_CLASS_LOADER) {
85         throw new IllegalStateException(
86             "DeclaringClass must be in parent class loader of CallingClass");
87       }
88       childLoader = BOOT_CLASS_LOADER;
89     } else {
90       childLoader = new InMemoryDexClassLoader(readDexFile(DEX_CHILD), parentLoader);
91     }
92 
93     // Create a unique copy of the native library. Each shared library can only
94     // be loaded once, but for some reason even classes from a class loader
95     // cannot register their native methods against symbols in a shared library
96     // loaded by their parent class loader.
97     String nativeLibCopy = createNativeLibCopy(parentInBoot, childInBoot, whitelistAllApis);
98 
99     if (whitelistAllApis) {
100       VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"});
101     }
102 
103     // Invoke ChildClass.runTest
104     Class.forName("ChildClass", true, childLoader)
105         .getDeclaredMethod("runTest", String.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE)
106             .invoke(null, nativeLibCopy, parentInBoot, childInBoot, whitelistAllApis);
107 
108     VMRuntime.getRuntime().setHiddenApiExemptions(new String[0]);
109   }
110 
111   // Routine which tries to figure out the absolute path of our native library.
prepareNativeLibFileName(String arg)112   private static void prepareNativeLibFileName(String arg) throws Exception {
113     String libName = System.mapLibraryName(arg);
114     Method libPathsMethod = Runtime.class.getDeclaredMethod("getLibPaths");
115     libPathsMethod.setAccessible(true);
116     String[] libPaths = (String[]) libPathsMethod.invoke(Runtime.getRuntime());
117     nativeLibFileName = null;
118     for (String p : libPaths) {
119       String candidate = p + libName;
120       if (new File(candidate).exists()) {
121         nativeLibFileName = candidate;
122         break;
123       }
124     }
125     if (nativeLibFileName == null) {
126       throw new IllegalStateException("Didn't find " + libName + " in " +
127           Arrays.toString(libPaths));
128     }
129   }
130 
131   // Helper to read dex file into memory.
readDexFile(String jarFileName)132   private static ByteBuffer readDexFile(String jarFileName) throws Exception {
133     ZipFile zip = new ZipFile(new File(jarFileName));
134     ZipEntry entry = zip.getEntry("classes.dex");
135     InputStream is = zip.getInputStream(entry);
136     int offset = 0;
137     int size = (int) entry.getSize();
138     ByteBuffer buffer = ByteBuffer.allocate(size);
139     while (is.available() > 0) {
140       is.read(buffer.array(), offset, size - offset);
141     }
142     is.close();
143     zip.close();
144     return buffer;
145   }
146 
147   // Copy native library to a new file with a unique name so it does not
148   // conflict with other loaded instance of the same binary file.
createNativeLibCopy( boolean parentInBoot, boolean childInBoot, boolean whitelistAllApis)149   private static String createNativeLibCopy(
150       boolean parentInBoot, boolean childInBoot, boolean whitelistAllApis) throws Exception {
151     String tempFileName = System.mapLibraryName(
152         "hiddenapitest_" + (parentInBoot ? "1" : "0") + (childInBoot ? "1" : "0") +
153          (whitelistAllApis ? "1" : "0"));
154     File tempFile = new File(System.getenv("DEX_LOCATION"), tempFileName);
155     Files.copy(new File(nativeLibFileName).toPath(), tempFile.toPath());
156     return tempFile.getAbsolutePath();
157   }
158 
doUnloading()159   private static void doUnloading() {
160     // Do multiple GCs to prevent rare flakiness if some other thread is
161     // keeping the classloader live.
162     for (int i = 0; i < 5; ++i) {
163        Runtime.getRuntime().gc();
164     }
165   }
166 
167   private static String nativeLibFileName;
168 
169   private static final String DEX_PARENT =
170       new File(System.getenv("DEX_LOCATION"), "674-hiddenapi.jar").getAbsolutePath();
171   private static final String DEX_PARENT_BOOT =
172       new File(new File(System.getenv("DEX_LOCATION"), "res"), "boot.jar").getAbsolutePath();
173   private static final String DEX_CHILD =
174       new File(System.getenv("DEX_LOCATION"), "674-hiddenapi-ex.jar").getAbsolutePath();
175 
176   private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
177 
appendToBootClassLoader(String dexPath)178   private static native void appendToBootClassLoader(String dexPath);
init()179   private static native void init();
180 }
181