Main.java revision 11b67b201adb173d5f6f1b7a3fd337fda91e200f
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
17import dalvik.system.InMemoryDexClassLoader;
18import dalvik.system.PathClassLoader;
19import java.io.File;
20import java.io.InputStream;
21import java.lang.reflect.Constructor;
22import java.lang.reflect.Method;
23import java.nio.ByteBuffer;
24import java.nio.file.Files;
25import java.util.Arrays;
26import java.util.zip.ZipEntry;
27import java.util.zip.ZipFile;
28
29public class Main {
30  public static void main(String[] args) throws Exception {
31    System.loadLibrary(args[0]);
32    prepareNativeLibFileName(args[0]);
33
34    // Run test with both parent and child dex files loaded with class loaders.
35    // The expectation is that hidden members in parent should be visible to
36    // the child.
37    doTest(false, false);
38
39    // Now append parent dex file to boot class path and run again. This time
40    // the child dex file should not be able to access private APIs of the parent.
41    appendToBootClassLoader(DEX_PARENT_BOOT);
42    doTest(true, false);
43
44    // And finally append to child to boot class path as well. With both in the
45    // boot class path, access should be granted.
46    appendToBootClassLoader(DEX_CHILD);
47    doTest(true, true);
48
49    System.out.println("Done");
50  }
51
52  private static void doTest(boolean parentInBoot, boolean childInBoot) throws Exception {
53    // Load parent dex if it is not in boot class path.
54    ClassLoader parentLoader = null;
55    if (parentInBoot) {
56      parentLoader = BOOT_CLASS_LOADER;
57    } else {
58      parentLoader = new PathClassLoader(DEX_PARENT, ClassLoader.getSystemClassLoader());
59    }
60
61    // Load child dex if it is not in boot class path.
62    ClassLoader childLoader = null;
63    if (childInBoot) {
64      if (parentLoader != BOOT_CLASS_LOADER) {
65        throw new IllegalStateException(
66            "DeclaringClass must be in parent class loader of CallingClass");
67      }
68      childLoader = BOOT_CLASS_LOADER;
69    } else {
70      childLoader = new InMemoryDexClassLoader(readDexFile(DEX_CHILD), parentLoader);
71    }
72
73    // Create a unique copy of the native library. Each shared library can only
74    // be loaded once, but for some reason even classes from a class loader
75    // cannot register their native methods against symbols in a shared library
76    // loaded by their parent class loader.
77    String nativeLibCopy = createNativeLibCopy(parentInBoot, childInBoot);
78
79    // Invoke ChildClass.runTest
80    Class.forName("ChildClass", true, childLoader)
81        .getDeclaredMethod("runTest", String.class, Boolean.TYPE, Boolean.TYPE)
82            .invoke(null, nativeLibCopy, parentInBoot, childInBoot);
83  }
84
85  // Routine which tries to figure out the absolute path of our native library.
86  private static void prepareNativeLibFileName(String arg) throws Exception {
87    String libName = System.mapLibraryName(arg);
88    Method libPathsMethod = Runtime.class.getDeclaredMethod("getLibPaths");
89    libPathsMethod.setAccessible(true);
90    String[] libPaths = (String[]) libPathsMethod.invoke(Runtime.getRuntime());
91    nativeLibFileName = null;
92    for (String p : libPaths) {
93      String candidate = p + libName;
94      if (new File(candidate).exists()) {
95        nativeLibFileName = candidate;
96        break;
97      }
98    }
99    if (nativeLibFileName == null) {
100      throw new IllegalStateException("Didn't find " + libName + " in " +
101          Arrays.toString(libPaths));
102    }
103  }
104
105  // Helper to read dex file into memory.
106  private static ByteBuffer readDexFile(String jarFileName) throws Exception {
107    ZipFile zip = new ZipFile(new File(jarFileName));
108    ZipEntry entry = zip.getEntry("classes.dex");
109    InputStream is = zip.getInputStream(entry);
110    int offset = 0;
111    int size = (int) entry.getSize();
112    ByteBuffer buffer = ByteBuffer.allocate(size);
113    while (is.available() > 0) {
114      is.read(buffer.array(), offset, size - offset);
115    }
116    is.close();
117    zip.close();
118    return buffer;
119  }
120
121  // Copy native library to a new file with a unique name so it does not conflict
122  // with other loaded instance of the same binary file.
123  private static String createNativeLibCopy(boolean parentInBoot, boolean childInBoot)
124      throws Exception {
125    String tempFileName = System.mapLibraryName(
126        "hiddenapitest_" + (parentInBoot ? "1" : "0") + (childInBoot ? "1" : "0"));
127    File tempFile = new File(System.getenv("DEX_LOCATION"), tempFileName);
128    Files.copy(new File(nativeLibFileName).toPath(), tempFile.toPath());
129    return tempFile.getAbsolutePath();
130  }
131
132  private static String nativeLibFileName;
133
134  private static final String DEX_PARENT =
135      new File(System.getenv("DEX_LOCATION"), "674-hiddenapi.jar").getAbsolutePath();
136  private static final String DEX_PARENT_BOOT =
137      new File(new File(System.getenv("DEX_LOCATION"), "res"), "boot.jar").getAbsolutePath();
138  private static final String DEX_CHILD =
139      new File(System.getenv("DEX_LOCATION"), "674-hiddenapi-ex.jar").getAbsolutePath();
140
141  private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
142
143  private static native void appendToBootClassLoader(String dexPath);
144}
145