1 /*
2  * Copyright (C) 2016 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 package android.jni.cts;
18 
19 import android.content.pm.ApplicationInfo;
20 import android.content.pm.PackageManager;
21 import android.content.pm.PackageManager.NameNotFoundException;
22 import android.os.Build;
23 
24 import androidx.test.InstrumentationRegistry;
25 
26 import dalvik.system.PathClassLoader;
27 
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.FileReader;
31 import java.io.FilenameFilter;
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Set;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41 
42 class LinkerNamespacesHelper {
43     private final static String PUBLIC_CONFIG_DIR = "/system/etc/";
44     private final static String SYSTEM_EXT_CONFIG_DIR = "/system_ext/etc/";
45     private final static String PRODUCT_CONFIG_DIR = "/product/etc/";
46     private final static Pattern EXTENSION_CONFIG_FILE_PATTERN = Pattern.compile(
47             "public\\.libraries-([A-Za-z0-9\\-_.]+)\\.txt");
48     private final static String VENDOR_CONFIG_FILE = "/vendor/etc/public.libraries.txt";
49     private final static String[] PUBLIC_SYSTEM_LIBRARIES = {
50         "libaaudio.so",
51         "libamidi.so",
52         "libandroid.so",
53         "libbinder_ndk.so",
54         "libc.so",
55         "libcamera2ndk.so",
56         "libdl.so",
57         "libEGL.so",
58         "libGLESv1_CM.so",
59         "libGLESv2.so",
60         "libGLESv3.so",
61         "libjnigraphics.so",
62         "liblog.so",
63         "libmediandk.so",
64         "libm.so",
65         "libnativewindow.so",
66         "libOpenMAXAL.so",
67         "libOpenSLES.so",
68         "libRS.so",
69         "libstdc++.so",
70         "libsync.so",
71         "libvulkan.so",
72         "libz.so"
73     };
74 
75     // System libraries that may exist in some types of builds.
76     private final static String[] OPTIONAL_SYSTEM_LIBRARIES = {
77       "libclang_rt.hwasan-aarch64-android.so"
78     };
79 
80     // Libraries listed in public.libraries.android.txt that are located in APEXes
81     private final static String[] PUBLIC_APEX_LIBRARIES = {
82         // Libraries in /apex/com.android.i18n/${LIB}
83         "libicu.so",
84         "libicui18n.so",
85         "libicuuc.so",
86         // Libraries in /apex/com.android.art/${LIB}
87         "libnativehelper.so",
88         // Libraries in /apex/com.android.neuralnetworks/${LIB}
89         "libneuralnetworks.so",
90     };
91 
92     // The grey-list.
93     private final static String[] PRIVATE_SYSTEM_LIBRARIES = {
94         "libandroid_runtime.so",
95         "libbinder.so",
96         "libcrypto.so",
97         "libcutils.so",
98         "libexpat.so",
99         "libgui.so",
100         "libmedia.so",
101         "libskia.so",
102         "libssl.so",
103         "libstagefright.so",
104         "libsqlite.so",
105         "libui.so",
106         "libutils.so",
107         "libvorbisidec.so",
108     };
109 
110     private final static String WEBVIEW_PLAT_SUPPORT_LIB = "libwebviewchromium_plat_support.so";
111 
112     static enum Bitness { ALL, ONLY_32, ONLY_64 }
113 
readPublicLibrariesFile(File file)114     private static List<String> readPublicLibrariesFile(File file) throws IOException {
115         List<String> libs = new ArrayList<>();
116         if (file.exists()) {
117             try (BufferedReader br = new BufferedReader(new FileReader(file))) {
118                 String line;
119                 final boolean is64Bit = android.os.Process.is64Bit();
120                 while ((line = br.readLine()) != null) {
121                     line = line.trim();
122                     if (line.isEmpty() || line.startsWith("#")) {
123                         continue;
124                     }
125                     String[] tokens = line.split(" ");
126                     if (tokens.length < 1 || tokens.length > 3) {
127                         throw new RuntimeException("Malformed line: '" + line + "' in " + file);
128                     }
129                     String soname = tokens[0];
130                     Bitness bitness = Bitness.ALL;
131                     int i = tokens.length;
132                     while(--i >= 1) {
133                         if (tokens[i].equals("nopreload")) {
134                             continue;
135                         }
136                         else if (tokens[i].equals("32") || tokens[i].equals("64")) {
137                             if (bitness != Bitness.ALL) {
138                                 throw new RuntimeException("Malformed line: '" + line +
139                                         "' in " + file + ". Bitness can be specified only once");
140                             }
141                             bitness = tokens[i].equals("32") ? Bitness.ONLY_32 : Bitness.ONLY_64;
142                         } else {
143                             throw new RuntimeException("Unrecognized token '" + tokens[i] +
144                                   "' in " + file);
145                         }
146                     }
147                     if ((is64Bit && bitness == Bitness.ONLY_32) ||
148                         (!is64Bit && bitness == Bitness.ONLY_64)) {
149                         // skip unsupported bitness
150                         continue;
151                     }
152                     libs.add(soname);
153                 }
154             }
155         }
156         return libs;
157     }
158 
readExtensionConfigFiles(String configDir, List<String> libs)159     private static String readExtensionConfigFiles(String configDir, List<String> libs) throws IOException {
160         File[] configFiles = new File(configDir).listFiles(
161                 new FilenameFilter() {
162                     public boolean accept(File dir, String name) {
163                         return EXTENSION_CONFIG_FILE_PATTERN.matcher(name).matches();
164                     }
165                 });
166         if (configFiles == null) return null;
167 
168         for (File configFile: configFiles) {
169             String fileName = configFile.toPath().getFileName().toString();
170             Matcher configMatcher = EXTENSION_CONFIG_FILE_PATTERN.matcher(fileName);
171             if (configMatcher.matches()) {
172                 String companyName = configMatcher.group(1);
173                 // a lib in public.libraries-acme.txt should be
174                 // libFoo.acme.so
175                 List<String> libNames = readPublicLibrariesFile(configFile);
176                 for (String lib : libNames) {
177                     if (lib.endsWith("." + companyName + ".so")) {
178                         libs.add(lib);
179                     } else {
180                         return "Library \"" + lib + "\" in " + configFile.toString()
181                                 + " must have company name " + companyName + " as suffix.";
182                     }
183                 }
184             }
185         }
186         return null;
187     }
188 
runAccessibilityTest()189     public static String runAccessibilityTest() throws IOException {
190         List<String> systemLibs = new ArrayList<>();
191         List<String> apexLibs = new ArrayList<>();
192 
193         Collections.addAll(systemLibs, PUBLIC_SYSTEM_LIBRARIES);
194         Collections.addAll(systemLibs, OPTIONAL_SYSTEM_LIBRARIES);
195         // System path could contain public ART libraries on foreign arch. http://b/149852946
196         if (isForeignArchitecture()) {
197             Collections.addAll(systemLibs, PUBLIC_APEX_LIBRARIES);
198         }
199 
200         if (InstrumentationRegistry.getContext().getPackageManager().
201                 hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
202             systemLibs.add(WEBVIEW_PLAT_SUPPORT_LIB);
203         }
204 
205         Collections.addAll(apexLibs, PUBLIC_APEX_LIBRARIES);
206 
207         // Check if /system/etc/public.libraries-company.txt,
208         // /system_ext/etc/public.libraries-company.txt
209         // and /product/etc/public.libraries-company.txt files are well-formed. The
210         // libraries however are not loaded for test;
211         // It is done in another test CtsUsesNativeLibraryTest because since Android S
212         // those libs are not available unless they are explicited listed in the app
213         // manifest.
214         List<String> oemLibs = new ArrayList<>();
215         String oemLibsError = readExtensionConfigFiles(PUBLIC_CONFIG_DIR, oemLibs);
216         if (oemLibsError != null) return oemLibsError;
217 
218         List<String> systemextLibs = new ArrayList<>();
219         String systemextLibsError = readExtensionConfigFiles(SYSTEM_EXT_CONFIG_DIR, systemextLibs);
220         if (systemextLibsError != null) return systemextLibsError;
221 
222         List<String> productLibs = new ArrayList<>();
223         String productLibsError = readExtensionConfigFiles(PRODUCT_CONFIG_DIR, productLibs);
224         if (productLibsError != null) return productLibsError;
225 
226         // Make sure that the libs in grey-list are not exposed to apps. In fact, it
227         // would be better for us to run this check against all system libraries which
228         // are not NDK libs, but grey-list libs are enough for now since they have been
229         // the most popular violators.
230         Set<String> greyListLibs = new HashSet<>();
231         Collections.addAll(greyListLibs, PRIVATE_SYSTEM_LIBRARIES);
232         // Note: check for systemLibs isn't needed since we already checked
233         // /system/etc/public.libraries.txt against NDK and
234         // /system/etc/public.libraries-<company>.txt against lib<name>.<company>.so.
235         List<String> vendorLibs = readPublicLibrariesFile(new File(VENDOR_CONFIG_FILE));
236         for (String lib : vendorLibs) {
237             if (greyListLibs.contains(lib)) {
238                 return "Internal library \"" + lib + "\" must not be available to apps.";
239             }
240         }
241 
242         return runAccessibilityTestImpl(systemLibs.toArray(new String[systemLibs.size()]),
243                                         apexLibs.toArray(new String[apexLibs.size()]));
244     }
245 
runAccessibilityTestImpl(String[] publicSystemLibs, String[] publicApexLibs)246     private static native String runAccessibilityTestImpl(String[] publicSystemLibs,
247                                                           String[] publicApexLibs);
248 
invokeIncrementGlobal(Class<?> clazz)249     private static void invokeIncrementGlobal(Class<?> clazz) throws Exception {
250         clazz.getMethod("incrementGlobal").invoke(null);
251     }
invokeGetGlobal(Class<?> clazz)252     private static int invokeGetGlobal(Class<?> clazz) throws Exception  {
253         return (Integer)clazz.getMethod("getGlobal").invoke(null);
254     }
255 
getApplicationInfo(String packageName)256     private static ApplicationInfo getApplicationInfo(String packageName) {
257         PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
258         try {
259             return pm.getApplicationInfo(packageName, 0);
260         } catch (NameNotFoundException nnfe) {
261             throw new RuntimeException(nnfe);
262         }
263     }
264 
getSourcePath(String packageName)265     private static String getSourcePath(String packageName) {
266         String sourcePath = getApplicationInfo(packageName).sourceDir;
267         if (sourcePath == null) {
268             throw new IllegalStateException("No source path path found for " + packageName);
269         }
270         return sourcePath;
271     }
272 
getNativePath(String packageName)273     private static String getNativePath(String packageName) {
274         String nativePath = getApplicationInfo(packageName).nativeLibraryDir;
275         if (nativePath == null) {
276             throw new IllegalStateException("No native path path found for " + packageName);
277         }
278         return nativePath;
279     }
280 
isAlreadyOpenedError(UnsatisfiedLinkError e, String libFilePath)281     private static boolean isAlreadyOpenedError(UnsatisfiedLinkError e, String libFilePath) {
282         // If one of the public system libraries are already opened in the bootclassloader, consider
283         // this try as success, because dlopen to the lib is successful.
284         String baseName = new File(libFilePath).getName();
285         return e.getMessage().contains("Shared library \"" + libFilePath +
286             "\" already opened by ClassLoader") &&
287             Arrays.asList(PUBLIC_SYSTEM_LIBRARIES).contains(baseName);
288     }
289 
loadWithSystemLoad(String libFilePath)290     private static String loadWithSystemLoad(String libFilePath) {
291         try {
292             System.load(libFilePath);
293         } catch (UnsatisfiedLinkError e) {
294             // all other exceptions are just thrown
295             if (!isAlreadyOpenedError(e, libFilePath)) {
296                 return "System.load() UnsatisfiedLinkError: " + e.getMessage();
297             }
298         }
299         return "";
300     }
301 
loadWithSystemLoadLibrary(String libFileName)302     private static String loadWithSystemLoadLibrary(String libFileName) {
303         // Drop 'lib' and '.so' from the base name
304         String libName = libFileName.substring(3, libFileName.length()-3);
305         try {
306             System.loadLibrary(libName);
307         } catch (UnsatisfiedLinkError e) {
308             if (!isAlreadyOpenedError(e, libFileName)) {
309                 return "System.loadLibrary(\"" + libName + "\") UnsatisfiedLinkError: " +
310                     e.getMessage();
311             }
312         }
313         return "";
314     }
315 
316     // Verify the behaviour of native library loading in class loaders.
317     // In this test:
318     //    - libjninamespacea1, libjninamespacea2 and libjninamespaceb depend on libjnicommon
319     //    - loaderA will load ClassNamespaceA1 (loading libjninamespacea1)
320     //    - loaderA will load ClassNamespaceA2 (loading libjninamespacea2)
321     //    - loaderB will load ClassNamespaceB (loading libjninamespaceb)
322     //    - incrementGlobal/getGlobal operate on a static global from libjnicommon
323     //      and each class should get its own view on it.
324     //
325     // This is a test case for 2 different scenarios:
326     //    - loading native libraries in different class loaders
327     //    - loading native libraries in the same class loader
328     // Ideally we would have 2 different tests but JNI doesn't allow loading the same library in
329     // different class loaders. So to keep the number of native libraries manageable we just
330     // re-use the same class loaders for the two tests.
runClassLoaderNamespaces()331     public static String runClassLoaderNamespaces() throws Exception {
332         // Test for different class loaders.
333         // Verify that common dependencies get a separate copy in each class loader.
334         // libjnicommon should be loaded twice:
335         // in the namespace for loaderA and the one for loaderB.
336         String apkPath = getSourcePath("android.jni.cts");
337         String nativePath = getNativePath("android.jni.cts");
338         PathClassLoader loaderA = new PathClassLoader(
339                 apkPath, nativePath, ClassLoader.getSystemClassLoader());
340         Class<?> testA1Class = loaderA.loadClass("android.jni.cts.ClassNamespaceA1");
341         PathClassLoader loaderB = new PathClassLoader(
342                 apkPath, nativePath, ClassLoader.getSystemClassLoader());
343         Class<?> testBClass = loaderB.loadClass("android.jni.cts.ClassNamespaceB");
344 
345         int globalA1 = invokeGetGlobal(testA1Class);
346         int globalB = invokeGetGlobal(testBClass);
347         if (globalA1 != 0 || globalB != 0) {
348             return "Expected globals to be 0/0: globalA1=" + globalA1 + " globalB=" + globalB;
349         }
350 
351         invokeIncrementGlobal(testA1Class);
352         globalA1 = invokeGetGlobal(testA1Class);
353         globalB = invokeGetGlobal(testBClass);
354         if (globalA1 != 1 || globalB != 0) {
355             return "Expected globals to be 1/0: globalA1=" + globalA1 + " globalB=" + globalB;
356         }
357 
358         invokeIncrementGlobal(testBClass);
359         globalA1 = invokeGetGlobal(testA1Class);
360         globalB = invokeGetGlobal(testBClass);
361         if (globalA1 != 1 || globalB != 1) {
362             return "Expected globals to be 1/1: globalA1=" + globalA1 + " globalB=" + globalB;
363         }
364 
365         // Test for the same class loaders.
366         // Verify that if we load ClassNamespaceA2 into loaderA we get the same view on the
367         // globals.
368         Class<?> testA2Class = loaderA.loadClass("android.jni.cts.ClassNamespaceA2");
369 
370         int globalA2 = invokeGetGlobal(testA2Class);
371         if (globalA1 != 1 || globalA2 !=1) {
372             return "Expected globals to be 1/1: globalA1=" + globalA1 + " globalA2=" + globalA2;
373         }
374 
375         invokeIncrementGlobal(testA1Class);
376         globalA1 = invokeGetGlobal(testA1Class);
377         globalA2 = invokeGetGlobal(testA2Class);
378         if (globalA1 != 2 || globalA2 != 2) {
379             return "Expected globals to be 2/2: globalA1=" + globalA1 + " globalA2=" + globalA2;
380         }
381 
382         invokeIncrementGlobal(testA2Class);
383         globalA1 = invokeGetGlobal(testA1Class);
384         globalA2 = invokeGetGlobal(testA2Class);
385         if (globalA1 != 3 || globalA2 != 3) {
386             return "Expected globals to be 2/2: globalA1=" + globalA1 + " globalA2=" + globalA2;
387         }
388         // On success we return null.
389         return null;
390     }
391 
runDlopenPublicLibraries()392     public static String runDlopenPublicLibraries() {
393         String error = null;
394         List<String> publicLibs = new ArrayList<>();
395         Collections.addAll(publicLibs, PUBLIC_SYSTEM_LIBRARIES);
396         Collections.addAll(publicLibs, PUBLIC_APEX_LIBRARIES);
397         for (String lib : publicLibs) {
398             String result = LinkerNamespacesHelper.tryDlopen(lib);
399             if (result != null) {
400                 if (error == null) {
401                     error = "";
402                 }
403                 error += result + "\n";
404             }
405         }
406         return error;
407     }
408 
tryDlopen(String lib)409     public static native String tryDlopen(String lib);
410 
isForeignArchitecture()411     private static boolean isForeignArchitecture() {
412         int libAbi = getLibAbi();
413         String cpuAbi = android.os.SystemProperties.get("ro.product.cpu.abi");
414         if ((libAbi == 1 || libAbi == 2) && !cpuAbi.startsWith("arm")) {
415             return true;
416         } else if ((libAbi == 3 || libAbi == 4) && !cpuAbi.startsWith("x86")) {
417             return true;
418         }
419         return false;
420     }
421 
422     /**
423      * @return ABI type of the JNI library. 1: ARM64, 2:ARM, 3: x86_64, 4: x86, 0: others
424      */
getLibAbi()425     private static native int getLibAbi();
426 }
427 
428 class ClassNamespaceA1 {
429     static {
430         System.loadLibrary("jninamespacea1");
431     }
432 
incrementGlobal()433     public static native void incrementGlobal();
getGlobal()434     public static native int getGlobal();
435 }
436 
437 class ClassNamespaceA2 {
438     static {
439         System.loadLibrary("jninamespacea2");
440     }
441 
incrementGlobal()442     public static native void incrementGlobal();
getGlobal()443     public static native int getGlobal();
444 }
445 
446 class ClassNamespaceB {
447     static {
448         System.loadLibrary("jninamespaceb");
449     }
450 
incrementGlobal()451     public static native void incrementGlobal();
getGlobal()452     public static native int getGlobal();
453 }
454