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