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