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