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