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