1 /* 2 * Copyright (C) 2014 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.security.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 import static org.junit.Assume.assumeTrue; 25 26 import android.platform.test.annotations.RestrictedBuildTest; 27 28 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 29 import com.android.compatibility.common.tradefed.targetprep.DeviceInfoCollector; 30 import com.android.compatibility.common.util.CddTest; 31 import com.android.compatibility.common.util.PropertyUtil; 32 import com.android.tradefed.build.IBuildInfo; 33 import com.android.tradefed.device.CollectingOutputReceiver; 34 import com.android.tradefed.device.DeviceNotAvailableException; 35 import com.android.tradefed.device.ITestDevice; 36 import com.android.tradefed.log.LogUtil.CLog; 37 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 38 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 39 import com.android.tradefed.util.FileUtil; 40 41 import org.json.JSONObject; 42 import org.junit.After; 43 import org.junit.Before; 44 import org.junit.Test; 45 import org.junit.runner.RunWith; 46 import org.w3c.dom.Document; 47 import org.w3c.dom.Element; 48 49 import java.io.BufferedReader; 50 import java.io.ByteArrayOutputStream; 51 import java.io.File; 52 import java.io.FileInputStream; 53 import java.io.FileOutputStream; 54 import java.io.IOException; 55 import java.io.InputStream; 56 import java.io.InputStreamReader; 57 import java.nio.file.Files; 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.Collections; 61 import java.util.HashMap; 62 import java.util.HashSet; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.Set; 66 import java.util.regex.Matcher; 67 import java.util.regex.Pattern; 68 import java.util.stream.Collectors; 69 70 import javax.xml.parsers.DocumentBuilder; 71 import javax.xml.parsers.DocumentBuilderFactory; 72 73 /** 74 * Host-side SELinux tests. 75 * 76 * These tests analyze the policy file in use on the subject device directly or 77 * run as the shell user to evaluate aspects of the state of SELinux on the test 78 * device which otherwise would not be available to a normal apk. 79 */ 80 @RunWith(DeviceJUnit4ClassRunner.class) 81 public class SELinuxHostTest extends BaseHostJUnit4Test { 82 83 // Keep in sync with AndroidTest.xml 84 private static final String DEVICE_INFO_DEVICE_DIR = "/sdcard/device-info-files/"; 85 // Keep in sync with com.android.compatibility.common.deviceinfo.VintfDeviceInfo 86 private static final String VINTF_DEVICE_CLASS = "VintfDeviceInfo"; 87 // Keep in sync with 88 // com.android.compatibility.common.deviceinfo.DeviceInfo#testCollectDeviceInfo() 89 private static final String DEVICE_INFO_SUFFIX = ".deviceinfo.json"; 90 private static final String VINTF_DEVICE_JSON = VINTF_DEVICE_CLASS + DEVICE_INFO_SUFFIX; 91 // Keep in sync with com.android.compatibility.common.deviceinfo.VintfDeviceInfo 92 private static final String SEPOLICY_VERSION_JSON_KEY = "sepolicy_version"; 93 private static final String PLATFORM_SEPOLICY_VERSION_JSON_KEY = "platform_sepolicy_version"; 94 95 private static final Map<ITestDevice, File> sCachedDevicePolicyFiles = new HashMap<>(1); 96 private static final Map<ITestDevice, File> sCachedDevicePlatFcFiles = new HashMap<>(1); 97 private static final Map<ITestDevice, File> sCachedDeviceVendorFcFiles = new HashMap<>(1); 98 private static final Map<ITestDevice, File> sCachedDeviceVendorManifest = new HashMap<>(1); 99 private static final Map<ITestDevice, File> sCachedDeviceVendorPolicy = new HashMap<>(1); 100 private static final Map<ITestDevice, File> sCachedDeviceVintfJson = new HashMap<>(1); 101 private static final Map<ITestDevice, File> sCachedDeviceSystemPolicy = new HashMap<>(1); 102 103 private File mSepolicyAnalyze; 104 private File checkSeapp; 105 private File checkFc; 106 private File aospFcFile; 107 private File aospPcFile; 108 private File aospSvcFile; 109 private File devicePolicyFile; 110 private File deviceSystemPolicyFile; 111 private File devicePlatFcFile; 112 private File deviceVendorFcFile; 113 private File devicePcFile; 114 private File deviceSvcFile; 115 private File seappNeverAllowFile; 116 private File copyLibcpp; 117 private File sepolicyTests; 118 119 private IBuildInfo mBuild; 120 121 /** 122 * A reference to the device under test. 123 */ 124 private ITestDevice mDevice; 125 copyResourceToTempFile(String resName)126 public static File copyResourceToTempFile(String resName) throws IOException { 127 InputStream is = SELinuxHostTest.class.getResourceAsStream(resName); 128 String tempFileName = "SELinuxHostTest" + resName.replace("/", "_"); 129 File tempFile = createTempFile(tempFileName, ".tmp"); 130 FileOutputStream os = new FileOutputStream(tempFile); 131 byte[] buf = new byte[1024]; 132 int len; 133 134 while ((len = is.read(buf)) != -1) { 135 os.write(buf, 0, len); 136 } 137 os.flush(); 138 os.close(); 139 return tempFile; 140 } 141 appendTo(String dest, String src)142 private static void appendTo(String dest, String src) throws IOException { 143 try (FileInputStream is = new FileInputStream(new File(src)); 144 FileOutputStream os = new FileOutputStream(new File(dest))) { 145 byte[] buf = new byte[1024]; 146 int len; 147 148 while ((len = is.read(buf)) != -1) { 149 os.write(buf, 0, len); 150 } 151 } 152 } 153 154 @Before setUp()155 public void setUp() throws Exception { 156 mDevice = getDevice(); 157 mBuild = getBuild(); 158 // Assumes every test in this file asserts a requirement of CDD section 9. 159 assumeSecurityModelCompat(); 160 161 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 162 mSepolicyAnalyze = copyResourceToTempFile("/sepolicy-analyze"); 163 mSepolicyAnalyze.setExecutable(true); 164 165 devicePolicyFile = getDevicePolicyFile(mDevice); 166 if (isSepolicySplit(mDevice)) { 167 devicePlatFcFile = getDeviceFile(mDevice, sCachedDevicePlatFcFiles, 168 "/system/etc/selinux/plat_file_contexts", "plat_file_contexts"); 169 deviceVendorFcFile = getDeviceFile(mDevice, sCachedDeviceVendorFcFiles, 170 "/vendor/etc/selinux/vendor_file_contexts", "vendor_file_contexts"); 171 deviceSystemPolicyFile = 172 android.security.cts.SELinuxHostTest.getDeviceSystemPolicyFile(mDevice); 173 } else { 174 devicePlatFcFile = getDeviceFile(mDevice, sCachedDevicePlatFcFiles, 175 "/plat_file_contexts", "plat_file_contexts"); 176 deviceVendorFcFile = getDeviceFile(mDevice, sCachedDeviceVendorFcFiles, 177 "/vendor_file_contexts", "vendor_file_contexts"); 178 } 179 } 180 181 @After cleanUp()182 public void cleanUp() throws Exception { 183 mSepolicyAnalyze.delete(); 184 } 185 assumeSecurityModelCompat()186 private void assumeSecurityModelCompat() throws Exception { 187 // This feature name check only applies to devices that first shipped with 188 // SC or later. 189 final int firstApiLevel = Math.min(PropertyUtil.getFirstApiLevel(mDevice), 190 PropertyUtil.getVendorApiLevel(mDevice)); 191 if (firstApiLevel >= 31) { 192 assumeTrue("Skipping test: FEATURE_SECURITY_MODEL_COMPATIBLE missing.", 193 getDevice().hasFeature("feature:android.hardware.security.model.compatible")); 194 } 195 } 196 197 /* 198 * IMPLEMENTATION DETAILS: We cache some host-side policy files on per-device basis (in case 199 * CTS supports running against multiple devices at the same time). HashMap is used instead 200 * of WeakHashMap because in the grand scheme of things, keeping ITestDevice and 201 * corresponding File objects from being garbage-collected is not a big deal in CTS. If this 202 * becomes a big deal, this can be switched to WeakHashMap. 203 */ getDeviceFile(ITestDevice device, Map<ITestDevice, File> cache, String deviceFilePath, String tmpFileName)204 private static File getDeviceFile(ITestDevice device, 205 Map<ITestDevice, File> cache, String deviceFilePath, 206 String tmpFileName) throws Exception { 207 if (!device.doesFileExist(deviceFilePath)){ 208 throw new Exception("File not found on the device: " + deviceFilePath); 209 } 210 File file; 211 synchronized (cache) { 212 file = cache.get(device); 213 } 214 if (file != null) { 215 return file; 216 } 217 file = createTempFile(tmpFileName, ".tmp"); 218 device.pullFile(deviceFilePath, file); 219 synchronized (cache) { 220 cache.put(device, file); 221 } 222 return file; 223 } 224 buildSystemPolicy(ITestDevice device, Map<ITestDevice, File> cache, String tmpFileName)225 private static File buildSystemPolicy(ITestDevice device, Map<ITestDevice, File> cache, 226 String tmpFileName) throws Exception { 227 File builtPolicyFile; 228 synchronized (cache) { 229 builtPolicyFile = cache.get(device); 230 } 231 if (builtPolicyFile != null) { 232 return builtPolicyFile; 233 } 234 235 builtPolicyFile = createTempFile(tmpFileName, ".tmp"); 236 237 File secilc = copyResourceToTempFile("/secilc"); 238 secilc.setExecutable(true); 239 240 File systemSepolicyCilFile = createTempFile("plat_sepolicy", ".cil"); 241 File fileContextsFile = createTempFile("file_contexts", ".txt"); 242 assertTrue(device.pullFile("/system/etc/selinux/plat_sepolicy.cil", systemSepolicyCilFile)); 243 244 List<String> command = new ArrayList<>(Arrays.asList( 245 secilc.getAbsolutePath(), 246 "-m", 247 "-M", 248 "true", 249 "-c", 250 "30", 251 "-o", 252 builtPolicyFile.getAbsolutePath(), 253 "-f", 254 fileContextsFile.getAbsolutePath(), 255 systemSepolicyCilFile.getAbsolutePath())); 256 257 File systemExtCilFile = createTempFile("system_ext_sepolicy", ".cil"); 258 File productCilFile = createTempFile("product_sepolicy", ".cil"); 259 if (device.pullFile("/system_ext/etc/selinux/system_ext_sepolicy.cil", systemExtCilFile)) { 260 command.add(systemExtCilFile.getAbsolutePath()); 261 } 262 if (device.pullFile("/product/etc/selinux/product_sepolicy.cil", productCilFile)) { 263 command.add(productCilFile.getAbsolutePath()); 264 } 265 266 String errorString = tryRunCommand(command.toArray(new String[0])); 267 assertTrue(errorString, errorString.length() == 0); 268 269 synchronized (cache) { 270 cache.put(device, builtPolicyFile); 271 } 272 return builtPolicyFile; 273 } 274 buildVendorPolicy(IBuildInfo build, ITestDevice device, Map<ITestDevice, File> cache, String tmpFileName)275 private static File buildVendorPolicy(IBuildInfo build, ITestDevice device, 276 Map<ITestDevice, File> cache, String tmpFileName) throws Exception { 277 File builtPolicyFile; 278 synchronized (cache) { 279 builtPolicyFile = cache.get(device); 280 } 281 if (builtPolicyFile != null) { 282 return builtPolicyFile; 283 } 284 285 builtPolicyFile = createTempFile(tmpFileName, ".tmp"); 286 287 File secilc = copyResourceToTempFile("/secilc"); 288 secilc.setExecutable(true); 289 290 int vendorVersion = getVendorSepolicyVersion(build, device); 291 292 File platSepolicyFile = copyResourceToTempFile("/" + vendorVersion + "_plat_sepolicy.cil"); 293 File platMappingFile = copyResourceToTempFile("/" + vendorVersion + "_mapping.cil"); 294 File vendorSepolicyCilFile = createTempFile("vendor_sepolicy", ".cil"); 295 File platPubVersionedCilFile = createTempFile("plat_pub_versioned", ".cil"); 296 File odmSepolicyCilFile = createTempFile("odm_sepolicy", ".cil"); 297 File fileContextsFile = createTempFile("file_contexts", ".txt"); 298 299 assertTrue(device.pullFile("/vendor/etc/selinux/vendor_sepolicy.cil", 300 vendorSepolicyCilFile)); 301 assertTrue(device.pullFile("/vendor/etc/selinux/plat_pub_versioned.cil", 302 platPubVersionedCilFile)); 303 304 List<String> command = new ArrayList<>(Arrays.asList( 305 secilc.getAbsolutePath(), 306 "-m", 307 "-M", 308 "true", 309 "-c", 310 "30", 311 "-N", 312 "-o", 313 builtPolicyFile.getAbsolutePath(), 314 "-f", 315 fileContextsFile.getAbsolutePath(), 316 platSepolicyFile.getAbsolutePath(), 317 platMappingFile.getAbsolutePath(), 318 vendorSepolicyCilFile.getAbsolutePath(), 319 platPubVersionedCilFile.getAbsolutePath())); 320 321 if (device.pullFile("/odm/etc/selinux/odm_sepolicy.cil", odmSepolicyCilFile)) { 322 command.add(odmSepolicyCilFile.getAbsolutePath()); 323 } 324 325 String errorString = tryRunCommand(command.toArray(new String[0])); 326 assertTrue(errorString, errorString.length() == 0); 327 328 synchronized (cache) { 329 cache.put(device, builtPolicyFile); 330 } 331 return builtPolicyFile; 332 } 333 334 /** 335 * Returns the host-side file containing the SELinux policy of the device under test. 336 */ getDevicePolicyFile(ITestDevice device)337 public static File getDevicePolicyFile(ITestDevice device) throws Exception { 338 return getDeviceFile(device, sCachedDevicePolicyFiles, "/sys/fs/selinux/policy", 339 "sepolicy"); 340 } 341 342 /** 343 * Returns the host-side file containing the system SELinux policy of the device under test. 344 */ getDeviceSystemPolicyFile(ITestDevice device)345 public static File getDeviceSystemPolicyFile(ITestDevice device) throws Exception { 346 return buildSystemPolicy(device, sCachedDeviceSystemPolicy, "system_sepolicy"); 347 } 348 349 /** 350 * Returns the host-side file containing the vendor SELinux policy of the device under test. 351 */ getDeviceVendorPolicyFile(IBuildInfo build, ITestDevice device)352 public static File getDeviceVendorPolicyFile(IBuildInfo build, ITestDevice device) 353 throws Exception { 354 return buildVendorPolicy(build, device, sCachedDeviceVendorPolicy, "vendor_sepolicy"); 355 } 356 357 /** 358 * Returns the major number of sepolicy version of device's vendor implementation. 359 */ getVendorSepolicyVersion(IBuildInfo build, ITestDevice device)360 public static int getVendorSepolicyVersion(IBuildInfo build, ITestDevice device) 361 throws Exception { 362 363 // Try different methods to get vendor SEPolicy version in the following order: 364 // 1. Retrieve from IBuildInfo as stored by DeviceInfoCollector (relies on #2) 365 // 2. If it fails, retrieve from device info JSON file stored on the device 366 // (relies on android.os.VintfObject) 367 // 3. If it fails, retrieve from raw VINTF device manifest files by guessing its path on 368 // the device 369 // Usually, the method #1 should work. If it doesn't, fallback to method #2 and #3. If 370 // none works, throw the error from method #1. 371 Exception buildInfoEx; 372 try { 373 return getVendorSepolicyVersionFromBuildInfo(build); 374 } catch (Exception ex) { 375 CLog.e("getVendorSepolicyVersionFromBuildInfo failed: " + ex); 376 buildInfoEx = ex; 377 } 378 try { 379 return getVendorSepolicyVersionFromDeviceJson(device); 380 } catch (Exception ex) { 381 CLog.e("getVendorSepolicyVersionFromDeviceJson failed: " + ex); 382 } 383 try { 384 return getVendorSepolicyVersionFromManifests(device); 385 } catch (Exception ex) { 386 CLog.e("getVendorSepolicyVersionFromManifests failed: " + ex); 387 throw new Exception("Unable to get the vendor policy version from the device:", 388 buildInfoEx); 389 } 390 } 391 392 /** 393 * Returns VSR (Vendor Software Requirements) api level. Returns 0 if the property 394 * ro.vendor.api_level doesn't exist 395 */ getVSRApiLevel(ITestDevice device)396 private static int getVSRApiLevel(ITestDevice device) throws Exception { 397 try { 398 return Integer.parseInt(device.getProperty("ro.vendor.api_level")); 399 } catch (Exception ex) { 400 CLog.e("getProperty(\"ro.vendor.api_level\") failed: ", ex); 401 return 0; 402 } 403 } 404 405 /** 406 * Retrieve the major number of sepolicy version from VINTF device info stored in the given 407 * IBuildInfo by {@link DeviceInfoCollector}. 408 */ getVendorSepolicyVersionFromBuildInfo(IBuildInfo build)409 private static int getVendorSepolicyVersionFromBuildInfo(IBuildInfo build) throws Exception { 410 File deviceInfoDir = build.getFile(DeviceInfoCollector.DEVICE_INFO_DIR); 411 File vintfJson = deviceInfoDir.toPath().resolve(VINTF_DEVICE_JSON).toFile(); 412 return getVendorSepolicyVersionFromJsonFile(vintfJson); 413 } 414 415 /** 416 * Retrieve the major number of sepolicy version from VINTF device info stored on the device by 417 * VintfDeviceInfo. 418 */ getVendorSepolicyVersionFromDeviceJson(ITestDevice device)419 private static int getVendorSepolicyVersionFromDeviceJson(ITestDevice device) throws Exception { 420 File vintfJson = getDeviceFile(device, sCachedDeviceVintfJson, 421 DEVICE_INFO_DEVICE_DIR + VINTF_DEVICE_JSON, VINTF_DEVICE_JSON); 422 return getVendorSepolicyVersionFromJsonFile(vintfJson); 423 } 424 425 /** 426 * Retrieve the major number of sepolicy version from the given JSON string that contains VINTF 427 * device info. 428 */ getVendorSepolicyVersionFromJsonFile(File vintfJson)429 private static int getVendorSepolicyVersionFromJsonFile(File vintfJson) throws Exception { 430 String content = FileUtil.readStringFromFile(vintfJson); 431 JSONObject object = new JSONObject(content); 432 String version = object.getString(SEPOLICY_VERSION_JSON_KEY); 433 return getSepolicyVersionFromMajorMinor(version); 434 } 435 436 /** 437 * Deprecated. 438 * Retrieve the major number of sepolicy version from raw device manifest XML files. 439 * Note that this is depends on locations of VINTF devices files at Android 10 and do not 440 * search new paths, hence this may not work on devices launching Android 11 and later. 441 */ getVendorSepolicyVersionFromManifests(ITestDevice device)442 private static int getVendorSepolicyVersionFromManifests(ITestDevice device) throws Exception { 443 String deviceManifestPath = null; 444 445 //check default path /vendor/etc/vintf/manifest.xml, prefer to use by default 446 if (device.doesFileExist("/vendor/etc/vintf/manifest.xml")) { 447 deviceManifestPath = "/vendor/etc/vintf/manifest.xml"; 448 } 449 450 //only if /vendor/etc/vintf/manifest.xml not exist, then check /vendor/etc/vintf/manifest_{vendorSku}.xml 451 String vendorSku = device.getProperty("ro.boot.product.vendor.sku"); 452 if (deviceManifestPath == null && vendorSku != null && vendorSku.length() > 0) { 453 String vendorSkuDeviceManifestPath = "/vendor/etc/vintf/manifest_"+ vendorSku + ".xml"; 454 if (device.doesFileExist(vendorSkuDeviceManifestPath)) { 455 deviceManifestPath = vendorSkuDeviceManifestPath; 456 } 457 } 458 459 //use /vendor/manifest.xml if above paths not exist 460 if (deviceManifestPath == null) { 461 deviceManifestPath = "/vendor/manifest.xml"; 462 } 463 464 CLog.i("getVendorSepolicyVersionFromManifests " + deviceManifestPath); 465 466 File vendorManifestFile = getDeviceFile(device, sCachedDeviceVendorManifest, 467 deviceManifestPath, "manifest.xml"); 468 469 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 470 DocumentBuilder db = dbf.newDocumentBuilder(); 471 Document doc = db.parse(vendorManifestFile); 472 Element root = doc.getDocumentElement(); 473 Element sepolicy = (Element) root.getElementsByTagName("sepolicy").item(0); 474 Element version = (Element) sepolicy.getElementsByTagName("version").item(0); 475 return getSepolicyVersionFromMajorMinor(version.getTextContent()); 476 } 477 478 /** 479 * Returns the major number of sepolicy version of system. 480 */ getSystemSepolicyVersion(IBuildInfo build)481 public static int getSystemSepolicyVersion(IBuildInfo build) throws Exception { 482 File deviceInfoDir = build.getFile(DeviceInfoCollector.DEVICE_INFO_DIR); 483 File vintfJson = deviceInfoDir.toPath().resolve(VINTF_DEVICE_JSON).toFile(); 484 String content = FileUtil.readStringFromFile(vintfJson); 485 JSONObject object = new JSONObject(content); 486 String version = object.getString(PLATFORM_SEPOLICY_VERSION_JSON_KEY); 487 return getSepolicyVersionFromMajorMinor(version); 488 } 489 490 /** 491 * Get the major number from an SEPolicy version string, e.g. "27.0" => 27. 492 */ getSepolicyVersionFromMajorMinor(String version)493 private static int getSepolicyVersionFromMajorMinor(String version) { 494 String sepolicyVersion = version.split("\\.")[0]; 495 return Integer.parseInt(sepolicyVersion); 496 } 497 498 /** 499 * Tests that the kernel is enforcing selinux policy globally. 500 * 501 * @throws Exception 502 */ 503 @CddTest(requirement="9.7") 504 @Test testGlobalEnforcing()505 public void testGlobalEnforcing() throws Exception { 506 CollectingOutputReceiver out = new CollectingOutputReceiver(); 507 mDevice.executeShellCommand("cat /sys/fs/selinux/enforce", out); 508 assertEquals("SELinux policy is not being enforced!", "1", out.getOutput()); 509 } 510 511 /** 512 * Tests that all domains in the running policy file are in enforcing mode 513 * 514 * @throws Exception 515 */ 516 @CddTest(requirement="9.7") 517 @RestrictedBuildTest 518 @Test testAllDomainsEnforcing()519 public void testAllDomainsEnforcing() throws Exception { 520 521 /* run sepolicy-analyze permissive check on policy file */ 522 String errorString = tryRunCommand(mSepolicyAnalyze.getAbsolutePath(), 523 devicePolicyFile.getAbsolutePath(), "permissive"); 524 assertTrue("The following SELinux domains were found to be in permissive mode:\n" 525 + errorString, errorString.length() == 0); 526 } 527 528 /** 529 * Asserts that specified type is not associated with the specified 530 * attribute. 531 * 532 * @param attribute 533 * The attribute name. 534 * @param type 535 * The type name. 536 */ assertNotInAttribute(String attribute, String badtype)537 private void assertNotInAttribute(String attribute, String badtype) throws Exception { 538 Set<String> actualTypes = sepolicyAnalyzeGetTypesAssociatedWithAttribute(attribute); 539 if (actualTypes.contains(badtype)) { 540 fail("Attribute " + attribute + " includes " + badtype); 541 } 542 } 543 readFully(InputStream in)544 private static final byte[] readFully(InputStream in) throws IOException { 545 ByteArrayOutputStream result = new ByteArrayOutputStream(); 546 byte[] buf = new byte[65536]; 547 int chunkSize; 548 while ((chunkSize = in.read(buf)) != -1) { 549 result.write(buf, 0, chunkSize); 550 } 551 return result.toByteArray(); 552 } 553 554 /** 555 * Runs sepolicy-analyze against the device's SELinux policy and returns the set of types 556 * associated with the provided attribute. 557 */ sepolicyAnalyzeGetTypesAssociatedWithAttribute( String attribute)558 private Set<String> sepolicyAnalyzeGetTypesAssociatedWithAttribute( 559 String attribute) throws Exception { 560 ProcessBuilder pb = 561 new ProcessBuilder( 562 mSepolicyAnalyze.getAbsolutePath(), 563 devicePolicyFile.getAbsolutePath(), 564 "attribute", 565 attribute); 566 pb.redirectOutput(ProcessBuilder.Redirect.PIPE); 567 pb.redirectErrorStream(true); 568 Process p = pb.start(); 569 int errorCode = p.waitFor(); 570 if (errorCode != 0) { 571 fail("sepolicy-analyze attribute " + attribute + " failed with error code " + errorCode 572 + ": " + new String(readFully(p.getInputStream()))); 573 } 574 try (BufferedReader in = 575 new BufferedReader(new InputStreamReader(p.getInputStream()))) { 576 Set<String> types = new HashSet<>(); 577 String type; 578 while ((type = in.readLine()) != null) { 579 types.add(type.trim()); 580 } 581 return types; 582 } 583 } 584 585 /** 586 * Returns {@code true} if this device is required to be a full Treble device. 587 */ isFullTrebleDevice(ITestDevice device)588 public static boolean isFullTrebleDevice(ITestDevice device) 589 throws DeviceNotAvailableException { 590 return PropertyUtil.getFirstApiLevel(device) > 26 && 591 PropertyUtil.propertyEquals(device, "ro.treble.enabled", "true"); 592 } 593 isFullTrebleDevice()594 private boolean isFullTrebleDevice() throws DeviceNotAvailableException { 595 return isFullTrebleDevice(mDevice); 596 } 597 598 /** 599 * Returns {@code true} if this device is required to enforce compatible property. 600 */ isCompatiblePropertyEnforcedDevice(ITestDevice device)601 public static boolean isCompatiblePropertyEnforcedDevice(ITestDevice device) 602 throws DeviceNotAvailableException { 603 return PropertyUtil.propertyEquals( 604 device, "ro.actionable_compatible_property.enabled", "true"); 605 } 606 607 /** 608 * Returns {@code true} if this device has sepolicy split across different paritions. 609 * This is possible even for devices launched at api level higher than 26. 610 */ isSepolicySplit(ITestDevice device)611 public static boolean isSepolicySplit(ITestDevice device) 612 throws DeviceNotAvailableException { 613 return PropertyUtil.getFirstApiLevel(device) > 34 /* Build.VERSION_CODES.UPSIDE_DOWN_CAKE */ 614 || device.doesFileExist("/system/etc/selinux/plat_file_contexts"); 615 } 616 617 /** 618 * Asserts that no HAL server domains are exempted from the prohibition of socket use with the 619 * only exceptions for the automotive device type. 620 */ 621 @Test testNoExemptionsForSocketsUseWithinHalServer()622 public void testNoExemptionsForSocketsUseWithinHalServer() throws Exception { 623 if (!isFullTrebleDevice()) { 624 return; 625 } 626 627 if (getDevice().hasFeature("feature:android.hardware.type.automotive")) { 628 return; 629 } 630 631 Set<String> types = sepolicyAnalyzeGetTypesAssociatedWithAttribute( 632 "hal_automotive_socket_exemption"); 633 if (!types.isEmpty()) { 634 List<String> sortedTypes = new ArrayList<>(types); 635 Collections.sort(sortedTypes); 636 fail("Policy exempts domains from ban on socket usage from HAL servers: " 637 + sortedTypes); 638 } 639 } 640 641 /** 642 * Tests that mlstrustedsubject does not include untrusted_app 643 * and that mlstrustedobject does not include app_data_file. 644 * This helps prevent circumventing the per-user isolation of 645 * normal apps via levelFrom=user. 646 * 647 * @throws Exception 648 */ 649 @CddTest(requirement="9.7") 650 @Test testMLSAttributes()651 public void testMLSAttributes() throws Exception { 652 assertNotInAttribute("mlstrustedsubject", "untrusted_app"); 653 assertNotInAttribute("mlstrustedobject", "app_data_file"); 654 } 655 656 /** 657 * Tests that the seapp_contexts file on the device is valid. 658 * 659 * @throws Exception 660 */ 661 @CddTest(requirement="9.7") 662 @Test testValidSeappContexts()663 public void testValidSeappContexts() throws Exception { 664 /* obtain seapp_contexts file from running device 665 * 666 * PLEASE KEEP IN SYNC WITH: 667 * external/selinux/libselinux/src/android/android_seapp.c 668 */ 669 File platformSeappFile = createTempFile("plat_seapp_contexts", ".tmp"); 670 File systemExtSeappFile = createTempFile("system_ext_seapp_contexts", ".tmp"); 671 File productSeappFile = createTempFile("product_seapp_contexts", ".tmp"); 672 File vendorSeappFile = createTempFile("vendor_seapp_contexts", ".tmp"); 673 File odmSeappFile = createTempFile("odm_seapp_contexts", ".tmp"); 674 if (mDevice.pullFile("/system/etc/selinux/plat_seapp_contexts", platformSeappFile)) { 675 mDevice.pullFile("/system_ext/etc/selinux/system_ext_seapp_contexts", 676 systemExtSeappFile); 677 mDevice.pullFile("/product/etc/selinux/product_seapp_contexts", productSeappFile); 678 mDevice.pullFile("/vendor/etc/selinux/vendor_seapp_contexts", vendorSeappFile); 679 mDevice.pullFile("/odm/etc/selinux/odm_seapp_contexts", odmSeappFile); 680 } else { 681 mDevice.pullFile("/plat_seapp_contexts", platformSeappFile); 682 mDevice.pullFile("/system_ext_seapp_contexts", systemExtSeappFile); 683 mDevice.pullFile("/product_seapp_contexts", productSeappFile); 684 mDevice.pullFile("/vendor_seapp_contexts", vendorSeappFile); 685 mDevice.pullFile("/odm_seapp_contexts", odmSeappFile); 686 } 687 688 /* retrieve the checkseapp executable from jar */ 689 checkSeapp = copyResourceToTempFile("/checkseapp"); 690 checkSeapp.setExecutable(true); 691 692 /* retrieve the AOSP seapp_neverallows file from jar */ 693 seappNeverAllowFile = copyResourceToTempFile("/plat_seapp_neverallows"); 694 695 /* run checkseapp on seapp_contexts */ 696 String errorString = tryRunCommand(checkSeapp.getAbsolutePath(), 697 "-p", devicePolicyFile.getAbsolutePath(), 698 seappNeverAllowFile.getAbsolutePath(), 699 platformSeappFile.getAbsolutePath(), 700 systemExtSeappFile.getAbsolutePath(), 701 productSeappFile.getAbsolutePath(), 702 vendorSeappFile.getAbsolutePath(), 703 odmSeappFile.getAbsolutePath()); 704 assertTrue("The seapp_contexts file was invalid:\n" 705 + errorString, errorString.length() == 0); 706 707 /* run checkseapp on vendor contexts to find coredomain violations, starting from V */ 708 int vsrVersion = getVSRApiLevel(getDevice()); 709 if (vsrVersion > 34) /* V or later */ { 710 errorString = tryRunCommand(checkSeapp.getAbsolutePath(), 711 "-p", devicePolicyFile.getAbsolutePath(), 712 "-c", /* coredomain check */ 713 vendorSeappFile.getAbsolutePath(), 714 odmSeappFile.getAbsolutePath()); 715 assertTrue("vendor seapp_contexts contains coredomain:\n" 716 + errorString, errorString.length() == 0); 717 } 718 } 719 720 /** 721 * Asserts that the actual file contains all the lines from the expected file. 722 * It does not guarantee the order of the lines. 723 * 724 * @param expectedFile 725 * The file with the expected contents. 726 * @param actualFile 727 * The actual file being checked. 728 */ assertContainsAllLines(File expectedFile, File actualFile)729 private void assertContainsAllLines(File expectedFile, File actualFile) throws Exception { 730 List<String> expectedLines = Files.readAllLines(expectedFile.toPath()); 731 List<String> actualLines = Files.readAllLines(actualFile.toPath()); 732 733 expectedLines.replaceAll(String::trim); 734 actualLines.replaceAll(String::trim); 735 736 HashSet<String> expected = new HashSet(expectedLines); 737 HashSet<String> actual = new HashSet(actualLines); 738 739 /* remove all seen lines from expected, ignoring new entries */ 740 expected.removeAll(actual); 741 assertTrue("Line removed: " + String.join("\n", expected), expected.isEmpty()); 742 } 743 744 /** 745 * Tests that the seapp_contexts file on the device contains 746 * the standard AOSP entries. 747 * 748 * @throws Exception 749 */ 750 @CddTest(requirement="9.7") 751 @Test testAospSeappContexts()752 public void testAospSeappContexts() throws Exception { 753 754 /* obtain seapp_contexts file from running device */ 755 File platformSeappFile = createTempFile("seapp_contexts", ".tmp"); 756 if (!mDevice.pullFile("/system/etc/selinux/plat_seapp_contexts", platformSeappFile)) { 757 mDevice.pullFile("/plat_seapp_contexts", platformSeappFile); 758 } 759 /* retrieve the AOSP seapp_contexts file from jar */ 760 File aospSeappFile = copyResourceToTempFile("/plat_seapp_contexts"); 761 762 assertContainsAllLines(aospSeappFile, platformSeappFile); 763 } 764 765 /** 766 * Tests that the plat_file_contexts file on the device contains 767 * the standard AOSP entries. 768 * 769 * @throws Exception 770 */ 771 @CddTest(requirement="9.7") 772 @Test testAospFileContexts()773 public void testAospFileContexts() throws Exception { 774 775 /* retrieve the checkfc executable from jar */ 776 checkFc = copyResourceToTempFile("/checkfc"); 777 checkFc.setExecutable(true); 778 779 /* retrieve the AOSP file_contexts file from jar */ 780 aospFcFile = copyResourceToTempFile("/plat_file_contexts"); 781 782 /* run checkfc -c plat_file_contexts plat_file_contexts */ 783 String result = tryRunCommand(checkFc.getAbsolutePath(), 784 "-c", aospFcFile.getAbsolutePath(), 785 devicePlatFcFile.getAbsolutePath()).trim(); 786 assertTrue("The file_contexts file did not include the AOSP entries:\n" 787 + result + "\n", 788 result.equals("equal") || result.equals("subset")); 789 } 790 791 /** 792 * Tests that the property_contexts file on the device contains 793 * the standard AOSP entries. 794 * 795 * @throws Exception 796 */ 797 @CddTest(requirement="9.7") 798 @Test testAospPropertyContexts()799 public void testAospPropertyContexts() throws Exception { 800 801 /* obtain property_contexts file from running device */ 802 devicePcFile = createTempFile("plat_property_contexts", ".tmp"); 803 // plat_property_contexts may be either in /system/etc/sepolicy or in / 804 if (!mDevice.pullFile("/system/etc/selinux/plat_property_contexts", devicePcFile)) { 805 mDevice.pullFile("/plat_property_contexts", devicePcFile); 806 } 807 808 // Retrieve the AOSP property_contexts file from JAR. 809 // The location of this file in the JAR has nothing to do with the location of this file on 810 // Android devices. See build script of this CTS module. 811 aospPcFile = copyResourceToTempFile("/plat_property_contexts"); 812 813 assertContainsAllLines(aospPcFile, devicePcFile); 814 } 815 816 /** 817 * Tests that the service_contexts file on the device contains 818 * the standard AOSP entries. 819 * 820 * @throws Exception 821 */ 822 @CddTest(requirement="9.7") 823 @Test testAospServiceContexts()824 public void testAospServiceContexts() throws Exception { 825 826 /* obtain service_contexts file from running device */ 827 deviceSvcFile = createTempFile("service_contexts", ".tmp"); 828 if (!mDevice.pullFile("/system/etc/selinux/plat_service_contexts", deviceSvcFile)) { 829 mDevice.pullFile("/plat_service_contexts", deviceSvcFile); 830 } 831 832 /* retrieve the AOSP service_contexts file from jar */ 833 aospSvcFile = copyResourceToTempFile("/plat_service_contexts"); 834 835 assertContainsAllLines(aospSvcFile, deviceSvcFile); 836 } 837 838 /** 839 * Tests that the file_contexts file(s) on the device is valid. 840 * 841 * @throws Exception 842 */ 843 @CddTest(requirement="9.7") 844 @Test testValidFileContexts()845 public void testValidFileContexts() throws Exception { 846 847 /* retrieve the checkfc executable from jar */ 848 checkFc = copyResourceToTempFile("/checkfc"); 849 checkFc.setExecutable(true); 850 851 /* combine plat and vendor policies for testing */ 852 File combinedFcFile = createTempFile("combined_file_context", ".tmp"); 853 appendTo(combinedFcFile.getAbsolutePath(), devicePlatFcFile.getAbsolutePath()); 854 appendTo(combinedFcFile.getAbsolutePath(), deviceVendorFcFile.getAbsolutePath()); 855 856 /* run checkfc sepolicy file_contexts */ 857 String errorString = tryRunCommand(checkFc.getAbsolutePath(), 858 devicePolicyFile.getAbsolutePath(), 859 combinedFcFile.getAbsolutePath()); 860 assertTrue("file_contexts was invalid:\n" 861 + errorString, errorString.length() == 0); 862 } 863 864 /** 865 * Tests that the property_contexts file on the device is valid. 866 * 867 * @throws Exception 868 */ 869 @CddTest(requirement="9.7") 870 @Test testValidPropertyContexts()871 public void testValidPropertyContexts() throws Exception { 872 873 /* retrieve the checkfc executable from jar */ 874 File propertyInfoChecker = copyResourceToTempFile("/property_info_checker"); 875 propertyInfoChecker.setExecutable(true); 876 877 /* obtain property_contexts file from running device */ 878 devicePcFile = createTempFile("property_contexts", ".tmp"); 879 // plat_property_contexts may be either in /system/etc/sepolicy or in / 880 if (!mDevice.pullFile("/system/etc/selinux/plat_property_contexts", devicePcFile)) { 881 mDevice.pullFile("/plat_property_contexts", devicePcFile); 882 } 883 884 /* run property_info_checker on property_contexts */ 885 String errorString = tryRunCommand(propertyInfoChecker.getAbsolutePath(), 886 devicePolicyFile.getAbsolutePath(), 887 devicePcFile.getAbsolutePath()); 888 assertTrue("The property_contexts file was invalid:\n" 889 + errorString, errorString.length() == 0); 890 } 891 892 /** 893 * Tests that the service_contexts file on the device is valid. 894 * 895 * @throws Exception 896 */ 897 @CddTest(requirement="9.7") 898 @Test testValidServiceContexts()899 public void testValidServiceContexts() throws Exception { 900 901 /* retrieve the checkfc executable from jar */ 902 checkFc = copyResourceToTempFile("/checkfc"); 903 checkFc.setExecutable(true); 904 905 /* obtain service_contexts file from running device */ 906 deviceSvcFile = createTempFile("service_contexts", ".tmp"); 907 mDevice.pullFile("/service_contexts", deviceSvcFile); 908 909 /* run checkfc -s on service_contexts */ 910 String errorString = tryRunCommand(checkFc.getAbsolutePath(), 911 "-s", devicePolicyFile.getAbsolutePath(), 912 deviceSvcFile.getAbsolutePath()); 913 assertTrue("The service_contexts file was invalid:\n" 914 + errorString, errorString.length() == 0); 915 } 916 isMac()917 public static boolean isMac() { 918 String os = System.getProperty("os.name").toLowerCase(); 919 return (os.startsWith("mac") || os.startsWith("darwin")); 920 } 921 assertSepolicyTests(String test, String testExecutable, boolean includeVendorSepolicy)922 private void assertSepolicyTests(String test, String testExecutable, 923 boolean includeVendorSepolicy) throws Exception { 924 sepolicyTests = copyResourceToTempFile(testExecutable); 925 sepolicyTests.setExecutable(true); 926 927 List<String> args = new ArrayList<String>(); 928 args.add(sepolicyTests.getAbsolutePath()); 929 args.add("-f"); 930 args.add(devicePlatFcFile.getAbsolutePath()); 931 args.add("--test"); 932 args.add(test); 933 934 if (includeVendorSepolicy) { 935 args.add("-f"); 936 args.add(deviceVendorFcFile.getAbsolutePath()); 937 args.add("-p"); 938 args.add(devicePolicyFile.getAbsolutePath()); 939 } else { 940 args.add("-p"); 941 args.add(deviceSystemPolicyFile.getAbsolutePath()); 942 } 943 944 String errorString = tryRunCommand(args.toArray(new String[0])); 945 assertTrue(errorString, errorString.length() == 0); 946 947 sepolicyTests.delete(); 948 } 949 950 /** 951 * Tests that all types on /data have the data_file_type attribute. 952 * 953 * @throws Exception 954 */ 955 @Test testDataTypeViolators()956 public void testDataTypeViolators() throws Exception { 957 assertSepolicyTests("TestDataTypeViolations", "/sepolicy_tests", 958 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 959 } 960 961 /** 962 * Tests that all types in /sys/fs/bpf have the bpffs_type attribute. 963 * 964 * @throws Exception 965 */ 966 @Test testBpffsTypeViolators()967 public void testBpffsTypeViolators() throws Exception { 968 assertSepolicyTests("TestBpffsTypeViolations", "/sepolicy_tests", 969 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 33) /* includeVendorSepolicy */); 970 } 971 972 /** 973 * Tests that all types in /proc have the proc_type attribute. 974 * 975 * @throws Exception 976 */ 977 @Test testProcTypeViolators()978 public void testProcTypeViolators() throws Exception { 979 assertSepolicyTests("TestProcTypeViolations", "/sepolicy_tests", 980 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 981 } 982 983 /** 984 * Tests that all types in /sys have the sysfs_type attribute. 985 * 986 * @throws Exception 987 */ 988 @Test testSysfsTypeViolators()989 public void testSysfsTypeViolators() throws Exception { 990 assertSepolicyTests("TestSysfsTypeViolations", "/sepolicy_tests", 991 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 992 } 993 994 /** 995 * Tests that all types on /vendor have the vendor_file_type attribute. 996 * 997 * @throws Exception 998 */ 999 @Test testVendorTypeViolators()1000 public void testVendorTypeViolators() throws Exception { 1001 assertSepolicyTests("TestVendorTypeViolations", "/sepolicy_tests", 1002 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 1003 } 1004 1005 /** 1006 * Tests that tracefs files(/sys/kernel/tracing and /d/tracing) are correctly labeled. 1007 * 1008 * @throws Exception 1009 */ 1010 @Test testTracefsTypeViolators()1011 public void testTracefsTypeViolators() throws Exception { 1012 assertSepolicyTests("TestTracefsTypeViolations", "/sepolicy_tests", 1013 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 30) /* includeVendorSepolicy */); 1014 } 1015 1016 /** 1017 * Tests that debugfs files(from /sys/kernel/debug) are correctly labeled. 1018 * 1019 * @throws Exception 1020 */ 1021 @Test testDebugfsTypeViolators()1022 public void testDebugfsTypeViolators() throws Exception { 1023 assertSepolicyTests("TestDebugfsTypeViolations", "/sepolicy_tests", 1024 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 30) /* includeVendorSepolicy */); 1025 } 1026 1027 /** 1028 * Tests that all domains with entrypoints on /system have the coredomain 1029 * attribute, and that all domains with entrypoints on /vendor do not have the 1030 * coredomain attribute. 1031 * 1032 * @throws Exception 1033 */ 1034 @Test testCoredomainViolators()1035 public void testCoredomainViolators() throws Exception { 1036 assertSepolicyTests("CoredomainViolations", "/sepolicy_tests", 1037 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 1038 } 1039 1040 /** 1041 * Tests that all labels on /dev have the dev_type attribute. 1042 * 1043 * @throws Exception 1044 */ 1045 @Test testDevTypeViolators()1046 public void testDevTypeViolators() throws Exception { 1047 int vsrVersion = getVSRApiLevel(getDevice()); 1048 assumeTrue("Skipping test: dev_type is enforced for W or later", vsrVersion > 202404); 1049 assertSepolicyTests("TestDevTypeViolations", "/sepolicy_tests", true); 1050 } 1051 1052 /** 1053 * Tests that the policy defines no booleans (runtime conditional policy). 1054 * 1055 * @throws Exception 1056 */ 1057 @CddTest(requirement="9.7") 1058 @Test testNoBooleans()1059 public void testNoBooleans() throws Exception { 1060 1061 /* run sepolicy-analyze booleans check on policy file */ 1062 String errorString = tryRunCommand(mSepolicyAnalyze.getAbsolutePath(), 1063 devicePolicyFile.getAbsolutePath(), "booleans"); 1064 assertTrue("The policy contained booleans:\n" 1065 + errorString, errorString.length() == 0); 1066 } 1067 1068 /** 1069 * Tests that taking a bugreport does not produce any dumpstate-related 1070 * SELinux denials. 1071 * 1072 * @throws Exception 1073 */ 1074 @Test testNoBugreportDenials()1075 public void testNoBugreportDenials() throws Exception { 1076 // Take a bugreport and get its logcat output. 1077 mDevice.executeAdbCommand("logcat", "-c"); 1078 mDevice.getBugreport(); 1079 String log = mDevice.executeAdbCommand("logcat", "-d"); 1080 // Find all the dumpstate-related types and make a regex that will match them. 1081 Set<String> types = sepolicyAnalyzeGetTypesAssociatedWithAttribute("hal_dumpstate_server"); 1082 types.add("dumpstate"); 1083 String typeRegex = types.stream().collect(Collectors.joining("|")); 1084 Pattern p = Pattern.compile("avc: *denied.*scontext=u:(?:r|object_r):(?:" + typeRegex + "):s0.*"); 1085 // Fail if logcat contains such a denial. 1086 Matcher m = p.matcher(log); 1087 StringBuilder errorString = new StringBuilder(); 1088 while (m.find()) { 1089 errorString.append(m.group()); 1090 errorString.append("\n"); 1091 } 1092 assertTrue("Found illegal SELinux denial(s): " + errorString, errorString.length() == 0); 1093 } 1094 1095 /** 1096 * Tests that important domain labels are being appropriately applied. 1097 */ 1098 1099 /** 1100 * Asserts that no processes are running in a domain. 1101 * 1102 * @param domain 1103 * The domain or SELinux context to check. 1104 */ assertDomainEmpty(String domain)1105 private void assertDomainEmpty(String domain) throws DeviceNotAvailableException { 1106 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1107 String msg = "Expected no processes in SELinux domain \"" + domain + "\"" 1108 + " Found: \"" + procs + "\""; 1109 assertNull(msg, procs); 1110 } 1111 1112 /** 1113 * Asserts that a domain exists and that only one, well defined, process is 1114 * running in that domain. 1115 * 1116 * @param domain 1117 * The domain or SELinux context to check. 1118 * @param executable 1119 * The path of the executable or application package name. 1120 */ assertDomainOne(String domain, String executable)1121 private void assertDomainOne(String domain, String executable) throws DeviceNotAvailableException { 1122 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1123 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(executable); 1124 String msg = "Expected 1 process in SELinux domain \"" + domain + "\"" 1125 + " Found \"" + procs + "\""; 1126 assertNotNull(msg, procs); 1127 assertEquals(msg, 1, procs.size()); 1128 1129 msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1130 + "Found: \"" + procs + "\""; 1131 assertEquals(msg, executable, procs.get(0).procTitle); 1132 1133 msg = "Expected 1 process with executable \"" + executable + "\"" 1134 + " Found \"" + procs + "\""; 1135 assertNotNull(msg, exeProcs); 1136 assertEquals(msg, 1, exeProcs.size()); 1137 1138 msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1139 + "Found: \"" + procs + "\""; 1140 assertEquals(msg, domain, exeProcs.get(0).label); 1141 } 1142 1143 /** 1144 * Asserts that a domain may exist. If a domain exists, the cardinality of 1145 * the domain is verified to be 1 and that the correct process is running in 1146 * that domain. If the process is running, it is running in that domain. 1147 * 1148 * @param domain 1149 * The domain or SELinux context to check. 1150 * @param executable 1151 * The path of the executable or application package name. 1152 */ assertDomainZeroOrOne(String domain, String executable)1153 private void assertDomainZeroOrOne(String domain, String executable) 1154 throws DeviceNotAvailableException { 1155 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1156 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(executable); 1157 if (procs != null) { 1158 String msg = "Expected 1 process in SELinux domain \"" + domain + "\"" 1159 + " Found: \"" + procs + "\""; 1160 assertEquals(msg, 1, procs.size()); 1161 1162 msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1163 + "Found: \"" + procs.get(0) + "\""; 1164 assertEquals(msg, executable, procs.get(0).procTitle); 1165 } 1166 if (exeProcs != null) { 1167 String msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1168 + " Instead found it running in the domain \"" + exeProcs.get(0).label + "\""; 1169 assertNotNull(msg, procs); 1170 1171 msg = "Expected 1 process with executable \"" + executable + "\"" 1172 + " Found: \"" + procs + "\""; 1173 assertEquals(msg, 1, exeProcs.size()); 1174 1175 msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1176 + "Found: \"" + procs.get(0) + "\""; 1177 assertEquals(msg, domain, exeProcs.get(0).label); 1178 } 1179 } 1180 1181 /** 1182 * Asserts that a domain must exist, and that the cardinality is greater 1183 * than or equal to 1. 1184 * 1185 * @param domain 1186 * The domain or SELinux context to check. 1187 * @param executables 1188 * The path of the allowed executables or application package names. 1189 */ assertDomainN(String domain, String... executables)1190 private void assertDomainN(String domain, String... executables) 1191 throws DeviceNotAvailableException { 1192 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1193 String msg = "Expected 1 or more processes in SELinux domain but found none."; 1194 assertNotNull(msg, procs); 1195 1196 Set<String> execList = new HashSet<String>(Arrays.asList(executables)); 1197 1198 for (ProcessDetails p : procs) { 1199 msg = "Expected one of \"" + execList + "\" in SELinux domain \"" + domain + "\"" 1200 + " Found: \"" + p + "\""; 1201 assertTrue(msg, execList.contains(p.procTitle)); 1202 } 1203 1204 for (String exe : executables) { 1205 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(exe); 1206 1207 if (exeProcs != null) { 1208 for (ProcessDetails p : exeProcs) { 1209 msg = "Expected executable \"" + exe + "\" in SELinux domain \"" 1210 + domain + "\"" + " Found: \"" + p + "\""; 1211 assertEquals(msg, domain, p.label); 1212 } 1213 } 1214 } 1215 } 1216 1217 /** 1218 * Asserts that a domain, if it exists, is only running the listed executables. 1219 * 1220 * @param domain 1221 * The domain or SELinux context to check. 1222 * @param executables 1223 * The path of the allowed executables or application package names. 1224 */ assertDomainHasExecutable(String domain, String... executables)1225 private void assertDomainHasExecutable(String domain, String... executables) 1226 throws DeviceNotAvailableException { 1227 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1228 1229 if (procs != null) { 1230 Set<String> execList = new HashSet<String>(Arrays.asList(executables)); 1231 1232 for (ProcessDetails p : procs) { 1233 String msg = "Expected one of \"" + execList + "\" in SELinux domain \"" 1234 + domain + "\"" + " Found: \"" + p + "\""; 1235 assertTrue(msg, execList.contains(p.procTitle)); 1236 } 1237 } 1238 } 1239 1240 /** 1241 * Asserts that an executable exists and is only running in the listed domains. 1242 * 1243 * @param executable 1244 * The path of the executable to check. 1245 * @param domains 1246 * The list of allowed domains. 1247 */ assertExecutableExistsAndHasDomain(String executable, String... domains)1248 private void assertExecutableExistsAndHasDomain(String executable, String... domains) 1249 throws DeviceNotAvailableException { 1250 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(executable); 1251 Set<String> domainList = new HashSet<String>(Arrays.asList(domains)); 1252 1253 String msg = "Expected 1 or more processes for executable \"" + executable + "\"."; 1254 assertNotNull(msg, exeProcs); 1255 1256 for (ProcessDetails p : exeProcs) { 1257 msg = "Expected one of \"" + domainList + "\" for executable \"" + executable 1258 + "\"" + " Found: \"" + p.label + "\""; 1259 assertTrue(msg, domainList.contains(p.label)); 1260 } 1261 } 1262 1263 /** 1264 * Asserts that an executable, if it exists, is only running in the listed domains. 1265 * 1266 * @param executable 1267 * The path of the executable to check. 1268 * @param domains 1269 * The list of allowed domains. 1270 */ assertExecutableHasDomain(String executable, String... domains)1271 private void assertExecutableHasDomain(String executable, String... domains) 1272 throws DeviceNotAvailableException { 1273 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(executable); 1274 Set<String> domainList = new HashSet<String>(Arrays.asList(domains)); 1275 1276 if (exeProcs != null) { 1277 for (ProcessDetails p : exeProcs) { 1278 String msg = "Expected one of \"" + domainList + "\" for executable \"" + executable 1279 + "\"" + " Found: \"" + p.label + "\""; 1280 assertTrue(msg, domainList.contains(p.label)); 1281 } 1282 } 1283 } 1284 1285 /* Init is always there */ 1286 @CddTest(requirement="9.7") 1287 @Test testInitDomain()1288 public void testInitDomain() throws DeviceNotAvailableException { 1289 assertDomainHasExecutable("u:r:init:s0", "/system/bin/init"); 1290 assertDomainHasExecutable("u:r:vendor_init:s0", "/system/bin/init"); 1291 assertExecutableExistsAndHasDomain("/system/bin/init", "u:r:init:s0", "u:r:vendor_init:s0"); 1292 } 1293 1294 /* Ueventd is always there */ 1295 @CddTest(requirement="9.7") 1296 @Test testUeventdDomain()1297 public void testUeventdDomain() throws DeviceNotAvailableException { 1298 assertDomainOne("u:r:ueventd:s0", "/system/bin/ueventd"); 1299 } 1300 1301 /* healthd may or may not exist */ 1302 @CddTest(requirement="9.7") 1303 @Test testHealthdDomain()1304 public void testHealthdDomain() throws DeviceNotAvailableException { 1305 assertDomainZeroOrOne("u:r:healthd:s0", "/system/bin/healthd"); 1306 } 1307 1308 /* Servicemanager is always there */ 1309 @CddTest(requirement="9.7") 1310 @Test testServicemanagerDomain()1311 public void testServicemanagerDomain() throws DeviceNotAvailableException { 1312 assertDomainOne("u:r:servicemanager:s0", "/system/bin/servicemanager"); 1313 } 1314 1315 /* Vold is always there */ 1316 @CddTest(requirement="9.7") 1317 @Test testVoldDomain()1318 public void testVoldDomain() throws DeviceNotAvailableException { 1319 assertDomainOne("u:r:vold:s0", "/system/bin/vold"); 1320 } 1321 1322 /* netd is always there */ 1323 @CddTest(requirement="9.7") 1324 @Test testNetdDomain()1325 public void testNetdDomain() throws DeviceNotAvailableException { 1326 assertDomainN("u:r:netd:s0", "/system/bin/netd", "/system/bin/iptables-restore", "/system/bin/ip6tables-restore"); 1327 } 1328 1329 /* Surface flinger is always there */ 1330 @CddTest(requirement="9.7") 1331 @Test testSurfaceflingerDomain()1332 public void testSurfaceflingerDomain() throws DeviceNotAvailableException { 1333 assertDomainOne("u:r:surfaceflinger:s0", "/system/bin/surfaceflinger"); 1334 } 1335 1336 /* Zygote is always running */ 1337 @CddTest(requirement="9.7") 1338 @Test testZygoteDomain()1339 public void testZygoteDomain() throws DeviceNotAvailableException { 1340 assertDomainN("u:r:zygote:s0", "zygote", "zygote64", "usap32", "usap64"); 1341 } 1342 1343 /* Checks drmserver for devices that require it */ 1344 @CddTest(requirement="9.7") 1345 @Test testDrmServerDomain()1346 public void testDrmServerDomain() throws DeviceNotAvailableException { 1347 assertDomainHasExecutable("u:r:drmserver:s0", "/system/bin/drmserver", "/system/bin/drmserver32", "/system/bin/drmserver64"); 1348 } 1349 1350 /* Installd is always running */ 1351 @CddTest(requirement="9.7") 1352 @Test testInstalldDomain()1353 public void testInstalldDomain() throws DeviceNotAvailableException { 1354 assertDomainOne("u:r:installd:s0", "/system/bin/installd"); 1355 } 1356 1357 /* keystore is always running */ 1358 @CddTest(requirement="9.7") 1359 @Test testKeystoreDomain()1360 public void testKeystoreDomain() throws DeviceNotAvailableException { 1361 assertDomainOne("u:r:keystore:s0", "/system/bin/keystore2"); 1362 } 1363 1364 /* System server better be running :-P */ 1365 @CddTest(requirement="9.7") 1366 @Test testSystemServerDomain()1367 public void testSystemServerDomain() throws DeviceNotAvailableException { 1368 assertDomainOne("u:r:system_server:s0", "system_server"); 1369 } 1370 1371 /* Watchdogd may or may not be there */ 1372 @CddTest(requirement="9.7") 1373 @Test testWatchdogdDomain()1374 public void testWatchdogdDomain() throws DeviceNotAvailableException { 1375 assertDomainZeroOrOne("u:r:watchdogd:s0", "/system/bin/watchdogd"); 1376 } 1377 1378 /* logd may or may not be there */ 1379 @CddTest(requirement="9.7") 1380 @Test testLogdDomain()1381 public void testLogdDomain() throws DeviceNotAvailableException { 1382 assertDomainZeroOrOne("u:r:logd:s0", "/system/bin/logd"); 1383 } 1384 1385 /* lmkd may or may not be there */ 1386 @CddTest(requirement="9.7") 1387 @Test testLmkdDomain()1388 public void testLmkdDomain() throws DeviceNotAvailableException { 1389 assertDomainZeroOrOne("u:r:lmkd:s0", "/system/bin/lmkd"); 1390 } 1391 1392 /* Wifi may be off so cardinality of 0 or 1 is ok */ 1393 @CddTest(requirement="9.7") 1394 @Test testWpaDomain()1395 public void testWpaDomain() throws DeviceNotAvailableException { 1396 assertDomainZeroOrOne("u:r:wpa:s0", "/system/bin/wpa_supplicant"); 1397 } 1398 1399 /* permissioncontroller, if running, always runs in permissioncontroller_app */ 1400 @CddTest(requirement="9.7") 1401 @Test testPermissionControllerDomain()1402 public void testPermissionControllerDomain() throws DeviceNotAvailableException { 1403 assertExecutableHasDomain("com.google.android.permissioncontroller", "u:r:permissioncontroller_app:s0"); 1404 assertExecutableHasDomain("com.android.permissioncontroller", "u:r:permissioncontroller_app:s0"); 1405 } 1406 1407 /* vzwomatrigger may or may not be running */ 1408 @CddTest(requirement="9.7") 1409 @Test testVzwOmaTriggerDomain()1410 public void testVzwOmaTriggerDomain() throws DeviceNotAvailableException { 1411 assertDomainZeroOrOne("u:r:vzwomatrigger_app:s0", "com.android.vzwomatrigger"); 1412 } 1413 1414 /* gmscore, if running, always runs in gmscore_app */ 1415 @CddTest(requirement="9.7") 1416 @Test testGMSCoreDomain()1417 public void testGMSCoreDomain() throws DeviceNotAvailableException { 1418 assertExecutableHasDomain("com.google.android.gms", "u:r:gmscore_app:s0"); 1419 assertExecutableHasDomain("com.google.android.gms.ui", "u:r:gmscore_app:s0"); 1420 assertExecutableHasDomain("com.google.android.gms.persistent", "u:r:gmscore_app:s0"); 1421 assertExecutableHasDomain("com.google.android.gms:snet", "u:r:gmscore_app:s0"); 1422 } 1423 1424 /* 1425 * Nothing should be running in this domain, cardinality test is all thats 1426 * needed 1427 */ 1428 @CddTest(requirement="9.7") 1429 @Test testInitShellDomain()1430 public void testInitShellDomain() throws DeviceNotAvailableException { 1431 assertDomainEmpty("u:r:init_shell:s0"); 1432 } 1433 1434 /* 1435 * Nothing should be running in this domain, cardinality test is all thats 1436 * needed 1437 */ 1438 @CddTest(requirement="9.7") 1439 @Test testRecoveryDomain()1440 public void testRecoveryDomain() throws DeviceNotAvailableException { 1441 assertDomainEmpty("u:r:recovery:s0"); 1442 } 1443 1444 /* 1445 * Nothing should be running in this domain, cardinality test is all thats 1446 * needed 1447 */ 1448 @CddTest(requirement="9.7") 1449 @RestrictedBuildTest 1450 @Test testSuDomain()1451 public void testSuDomain() throws DeviceNotAvailableException { 1452 assertDomainEmpty("u:r:su:s0"); 1453 } 1454 1455 /* 1456 * All kthreads should be in kernel context. 1457 */ 1458 @CddTest(requirement="9.7") 1459 @Test testKernelDomain()1460 public void testKernelDomain() throws DeviceNotAvailableException { 1461 String domain = "u:r:kernel:s0"; 1462 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1463 if (procs != null) { 1464 for (ProcessDetails p : procs) { 1465 assertTrue("Non Kernel thread \"" + p + "\" found!", p.isKernel()); 1466 } 1467 } 1468 } 1469 1470 private static class ProcessDetails { 1471 public String label; 1472 public String user; 1473 public int pid; 1474 public int ppid; 1475 public String procTitle; 1476 1477 private static HashMap<String, ArrayList<ProcessDetails>> procMap; 1478 private static HashMap<String, ArrayList<ProcessDetails>> exeMap; 1479 private static int kernelParentThreadpid = -1; 1480 ProcessDetails(String label, String user, int pid, int ppid, String procTitle)1481 ProcessDetails(String label, String user, int pid, int ppid, String procTitle) { 1482 this.label = label; 1483 this.user = user; 1484 this.pid = pid; 1485 this.ppid = ppid; 1486 this.procTitle = procTitle; 1487 } 1488 1489 @Override toString()1490 public String toString() { 1491 return "label: " + label 1492 + " user: " + user 1493 + " pid: " + pid 1494 + " ppid: " + ppid 1495 + " cmd: " + procTitle; 1496 } 1497 1498 createProcMap(ITestDevice tDevice)1499 private static void createProcMap(ITestDevice tDevice) throws DeviceNotAvailableException { 1500 1501 /* take the output of a ps -Z to do our analysis */ 1502 CollectingOutputReceiver psOut = new CollectingOutputReceiver(); 1503 // TODO: remove "toybox" below and just run "ps" 1504 tDevice.executeShellCommand("toybox ps -A -o label,user,pid,ppid,cmdline", psOut); 1505 String psOutString = psOut.getOutput(); 1506 Pattern p = Pattern.compile( 1507 "^([\\w_:,]+)\\s+([\\w_]+)\\s+(\\d+)\\s+(\\d+)\\s+(\\p{Graph}+)(\\s\\p{Graph}+)*\\s*$" 1508 ); 1509 procMap = new HashMap<String, ArrayList<ProcessDetails>>(); 1510 exeMap = new HashMap<String, ArrayList<ProcessDetails>>(); 1511 for(String line : psOutString.split("\n")) { 1512 Matcher m = p.matcher(line); 1513 if(m.matches()) { 1514 String domainLabel = m.group(1); 1515 // clean up the domainlabel 1516 String[] parts = domainLabel.split(":"); 1517 if (parts.length > 4) { 1518 // we have an extra categories bit at the end consisting of cxxx,cxxx ... 1519 // just make the domain out of the first 4 parts 1520 domainLabel = String.join(":", parts[0], parts[1], parts[2], parts[3]); 1521 } 1522 1523 String user = m.group(2); 1524 int pid = Integer.parseInt(m.group(3)); 1525 int ppid = Integer.parseInt(m.group(4)); 1526 String procTitle = m.group(5); 1527 ProcessDetails proc = new ProcessDetails(domainLabel, user, pid, ppid, procTitle); 1528 if (procMap.get(domainLabel) == null) { 1529 procMap.put(domainLabel, new ArrayList<ProcessDetails>()); 1530 } 1531 procMap.get(domainLabel).add(proc); 1532 if (procTitle.equals("[kthreadd]") && ppid == 0) { 1533 kernelParentThreadpid = pid; 1534 } 1535 if (exeMap.get(procTitle) == null) { 1536 exeMap.put(procTitle, new ArrayList<ProcessDetails>()); 1537 } 1538 exeMap.get(procTitle).add(proc); 1539 } 1540 } 1541 } 1542 getProcMap(ITestDevice tDevice)1543 public static HashMap<String, ArrayList<ProcessDetails>> getProcMap(ITestDevice tDevice) 1544 throws DeviceNotAvailableException{ 1545 if (procMap == null) { 1546 createProcMap(tDevice); 1547 } 1548 return procMap; 1549 } 1550 getExeMap(ITestDevice tDevice)1551 public static HashMap<String, ArrayList<ProcessDetails>> getExeMap(ITestDevice tDevice) 1552 throws DeviceNotAvailableException{ 1553 if (exeMap == null) { 1554 createProcMap(tDevice); 1555 } 1556 return exeMap; 1557 } 1558 isKernel()1559 public boolean isKernel() { 1560 return (pid == kernelParentThreadpid || ppid == kernelParentThreadpid); 1561 } 1562 } 1563 tryRunCommand(String... command)1564 private static String tryRunCommand(String... command) throws Exception { 1565 ProcessBuilder pb = new ProcessBuilder(command); 1566 pb.redirectOutput(ProcessBuilder.Redirect.PIPE); 1567 pb.redirectErrorStream(true); 1568 Process p = pb.start(); 1569 p.waitFor(); 1570 BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); 1571 StringBuilder result = new StringBuilder(); 1572 String line; 1573 while ((line = reader.readLine()) != null) { 1574 result.append(line); 1575 result.append("\n"); 1576 } 1577 return result.toString(); 1578 } 1579 createTempFile(String name, String ext)1580 private static File createTempFile(String name, String ext) throws IOException { 1581 File ret = File.createTempFile(name, ext); 1582 ret.deleteOnExit(); 1583 return ret; 1584 } 1585 } 1586