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 * Asserts that no types use the update_provider attribute. 643 */ 644 @Test testNoExemptionsForUpdateInterfaces()645 public void testNoExemptionsForUpdateInterfaces() throws Exception { 646 Set<String> types = sepolicyAnalyzeGetTypesAssociatedWithAttribute( 647 "update_provider"); 648 if (!types.isEmpty()) { 649 List<String> sortedTypes = new ArrayList<>(types); 650 Collections.sort(sortedTypes); 651 fail("Use of the \"update_provider\" attribute is prohibited. " 652 + "The following types were found using this attribute: " + sortedTypes); 653 } 654 } 655 656 /** 657 * Tests that mlstrustedsubject does not include untrusted_app 658 * and that mlstrustedobject does not include app_data_file. 659 * This helps prevent circumventing the per-user isolation of 660 * normal apps via levelFrom=user. 661 * 662 * @throws Exception 663 */ 664 @CddTest(requirement="9.7") 665 @Test testMLSAttributes()666 public void testMLSAttributes() throws Exception { 667 assertNotInAttribute("mlstrustedsubject", "untrusted_app"); 668 assertNotInAttribute("mlstrustedobject", "app_data_file"); 669 } 670 671 /** 672 * Tests that the seapp_contexts file on the device is valid. 673 * 674 * @throws Exception 675 */ 676 @CddTest(requirement="9.7") 677 @Test testValidSeappContexts()678 public void testValidSeappContexts() throws Exception { 679 /* obtain seapp_contexts file from running device 680 * 681 * PLEASE KEEP IN SYNC WITH: 682 * external/selinux/libselinux/src/android/android_seapp.c 683 */ 684 File platformSeappFile = createTempFile("plat_seapp_contexts", ".tmp"); 685 File systemExtSeappFile = createTempFile("system_ext_seapp_contexts", ".tmp"); 686 File productSeappFile = createTempFile("product_seapp_contexts", ".tmp"); 687 File vendorSeappFile = createTempFile("vendor_seapp_contexts", ".tmp"); 688 File odmSeappFile = createTempFile("odm_seapp_contexts", ".tmp"); 689 if (mDevice.pullFile("/system/etc/selinux/plat_seapp_contexts", platformSeappFile)) { 690 mDevice.pullFile("/system_ext/etc/selinux/system_ext_seapp_contexts", 691 systemExtSeappFile); 692 mDevice.pullFile("/product/etc/selinux/product_seapp_contexts", productSeappFile); 693 mDevice.pullFile("/vendor/etc/selinux/vendor_seapp_contexts", vendorSeappFile); 694 mDevice.pullFile("/odm/etc/selinux/odm_seapp_contexts", odmSeappFile); 695 } else { 696 mDevice.pullFile("/plat_seapp_contexts", platformSeappFile); 697 mDevice.pullFile("/system_ext_seapp_contexts", systemExtSeappFile); 698 mDevice.pullFile("/product_seapp_contexts", productSeappFile); 699 mDevice.pullFile("/vendor_seapp_contexts", vendorSeappFile); 700 mDevice.pullFile("/odm_seapp_contexts", odmSeappFile); 701 } 702 703 /* retrieve the checkseapp executable from jar */ 704 checkSeapp = copyResourceToTempFile("/checkseapp"); 705 checkSeapp.setExecutable(true); 706 707 /* retrieve the AOSP seapp_neverallows file from jar */ 708 seappNeverAllowFile = copyResourceToTempFile("/plat_seapp_neverallows"); 709 710 /* run checkseapp on seapp_contexts */ 711 String errorString = tryRunCommand(checkSeapp.getAbsolutePath(), 712 "-p", devicePolicyFile.getAbsolutePath(), 713 seappNeverAllowFile.getAbsolutePath(), 714 platformSeappFile.getAbsolutePath(), 715 systemExtSeappFile.getAbsolutePath(), 716 productSeappFile.getAbsolutePath(), 717 vendorSeappFile.getAbsolutePath(), 718 odmSeappFile.getAbsolutePath()); 719 assertTrue("The seapp_contexts file was invalid:\n" 720 + errorString, errorString.length() == 0); 721 722 /* run checkseapp on vendor contexts to find coredomain violations, starting from V */ 723 int vsrVersion = getVSRApiLevel(getDevice()); 724 if (vsrVersion > 34) /* V or later */ { 725 errorString = tryRunCommand(checkSeapp.getAbsolutePath(), 726 "-p", devicePolicyFile.getAbsolutePath(), 727 "-c", /* coredomain check */ 728 vendorSeappFile.getAbsolutePath(), 729 odmSeappFile.getAbsolutePath()); 730 assertTrue("vendor seapp_contexts contains coredomain:\n" 731 + errorString, errorString.length() == 0); 732 } 733 } 734 735 /** 736 * Asserts that the actual file contains all the lines from the expected file. 737 * It does not guarantee the order of the lines. 738 * 739 * @param expectedFile 740 * The file with the expected contents. 741 * @param actualFile 742 * The actual file being checked. 743 */ assertContainsAllLines(File expectedFile, File actualFile)744 private void assertContainsAllLines(File expectedFile, File actualFile) throws Exception { 745 List<String> expectedLines = Files.readAllLines(expectedFile.toPath()); 746 List<String> actualLines = Files.readAllLines(actualFile.toPath()); 747 748 expectedLines.replaceAll(String::trim); 749 actualLines.replaceAll(String::trim); 750 751 HashSet<String> expected = new HashSet(expectedLines); 752 HashSet<String> actual = new HashSet(actualLines); 753 754 /* remove all seen lines from expected, ignoring new entries */ 755 expected.removeAll(actual); 756 assertTrue("Line removed: " + String.join("\n", expected), expected.isEmpty()); 757 } 758 759 /** 760 * Tests that the seapp_contexts file on the device contains 761 * the standard AOSP entries. 762 * 763 * @throws Exception 764 */ 765 @CddTest(requirement="9.7") 766 @Test testAospSeappContexts()767 public void testAospSeappContexts() throws Exception { 768 769 /* obtain seapp_contexts file from running device */ 770 File platformSeappFile = createTempFile("seapp_contexts", ".tmp"); 771 if (!mDevice.pullFile("/system/etc/selinux/plat_seapp_contexts", platformSeappFile)) { 772 mDevice.pullFile("/plat_seapp_contexts", platformSeappFile); 773 } 774 /* retrieve the AOSP seapp_contexts file from jar */ 775 File aospSeappFile = copyResourceToTempFile("/plat_seapp_contexts"); 776 777 assertContainsAllLines(aospSeappFile, platformSeappFile); 778 } 779 780 /** 781 * Tests that the plat_file_contexts file on the device contains 782 * the standard AOSP entries. 783 * 784 * @throws Exception 785 */ 786 @CddTest(requirement="9.7") 787 @Test testAospFileContexts()788 public void testAospFileContexts() throws Exception { 789 790 /* retrieve the checkfc executable from jar */ 791 checkFc = copyResourceToTempFile("/checkfc"); 792 checkFc.setExecutable(true); 793 794 /* retrieve the AOSP file_contexts file from jar */ 795 aospFcFile = copyResourceToTempFile("/plat_file_contexts"); 796 797 /* run checkfc -c plat_file_contexts plat_file_contexts */ 798 String result = tryRunCommand(checkFc.getAbsolutePath(), 799 "-c", aospFcFile.getAbsolutePath(), 800 devicePlatFcFile.getAbsolutePath()).trim(); 801 assertTrue("The file_contexts file did not include the AOSP entries:\n" 802 + result + "\n", 803 result.equals("equal") || result.equals("subset")); 804 } 805 806 /** 807 * Tests that the property_contexts file on the device contains 808 * the standard AOSP entries. 809 * 810 * @throws Exception 811 */ 812 @CddTest(requirement="9.7") 813 @Test testAospPropertyContexts()814 public void testAospPropertyContexts() throws Exception { 815 816 /* obtain property_contexts file from running device */ 817 devicePcFile = createTempFile("plat_property_contexts", ".tmp"); 818 // plat_property_contexts may be either in /system/etc/sepolicy or in / 819 if (!mDevice.pullFile("/system/etc/selinux/plat_property_contexts", devicePcFile)) { 820 mDevice.pullFile("/plat_property_contexts", devicePcFile); 821 } 822 823 // Retrieve the AOSP property_contexts file from JAR. 824 // The location of this file in the JAR has nothing to do with the location of this file on 825 // Android devices. See build script of this CTS module. 826 aospPcFile = copyResourceToTempFile("/plat_property_contexts"); 827 828 assertContainsAllLines(aospPcFile, devicePcFile); 829 } 830 831 /** 832 * Tests that the service_contexts file on the device contains 833 * the standard AOSP entries. 834 * 835 * @throws Exception 836 */ 837 @CddTest(requirement="9.7") 838 @Test testAospServiceContexts()839 public void testAospServiceContexts() throws Exception { 840 841 /* obtain service_contexts file from running device */ 842 deviceSvcFile = createTempFile("service_contexts", ".tmp"); 843 if (!mDevice.pullFile("/system/etc/selinux/plat_service_contexts", deviceSvcFile)) { 844 mDevice.pullFile("/plat_service_contexts", deviceSvcFile); 845 } 846 847 /* retrieve the AOSP service_contexts file from jar */ 848 aospSvcFile = copyResourceToTempFile("/plat_service_contexts"); 849 850 assertContainsAllLines(aospSvcFile, deviceSvcFile); 851 } 852 853 /** 854 * Tests that the file_contexts file(s) on the device is valid. 855 * 856 * @throws Exception 857 */ 858 @CddTest(requirement="9.7") 859 @Test testValidFileContexts()860 public void testValidFileContexts() throws Exception { 861 862 /* retrieve the checkfc executable from jar */ 863 checkFc = copyResourceToTempFile("/checkfc"); 864 checkFc.setExecutable(true); 865 866 /* combine plat and vendor policies for testing */ 867 File combinedFcFile = createTempFile("combined_file_context", ".tmp"); 868 appendTo(combinedFcFile.getAbsolutePath(), devicePlatFcFile.getAbsolutePath()); 869 appendTo(combinedFcFile.getAbsolutePath(), deviceVendorFcFile.getAbsolutePath()); 870 871 /* run checkfc sepolicy file_contexts */ 872 String errorString = tryRunCommand(checkFc.getAbsolutePath(), 873 devicePolicyFile.getAbsolutePath(), 874 combinedFcFile.getAbsolutePath()); 875 assertTrue("file_contexts was invalid:\n" 876 + errorString, errorString.length() == 0); 877 } 878 879 /** 880 * Tests that the property_contexts file on the device is valid. 881 * 882 * @throws Exception 883 */ 884 @CddTest(requirement="9.7") 885 @Test testValidPropertyContexts()886 public void testValidPropertyContexts() throws Exception { 887 888 /* retrieve the checkfc executable from jar */ 889 File propertyInfoChecker = copyResourceToTempFile("/property_info_checker"); 890 propertyInfoChecker.setExecutable(true); 891 892 /* obtain property_contexts file from running device */ 893 devicePcFile = createTempFile("property_contexts", ".tmp"); 894 // plat_property_contexts may be either in /system/etc/sepolicy or in / 895 if (!mDevice.pullFile("/system/etc/selinux/plat_property_contexts", devicePcFile)) { 896 mDevice.pullFile("/plat_property_contexts", devicePcFile); 897 } 898 899 /* run property_info_checker on property_contexts */ 900 String errorString = tryRunCommand(propertyInfoChecker.getAbsolutePath(), 901 devicePolicyFile.getAbsolutePath(), 902 devicePcFile.getAbsolutePath()); 903 assertTrue("The property_contexts file was invalid:\n" 904 + errorString, errorString.length() == 0); 905 } 906 907 /** 908 * Tests that the service_contexts file on the device is valid. 909 * 910 * @throws Exception 911 */ 912 @CddTest(requirement="9.7") 913 @Test testValidServiceContexts()914 public void testValidServiceContexts() throws Exception { 915 916 /* retrieve the checkfc executable from jar */ 917 checkFc = copyResourceToTempFile("/checkfc"); 918 checkFc.setExecutable(true); 919 920 /* obtain service_contexts file from running device */ 921 deviceSvcFile = createTempFile("service_contexts", ".tmp"); 922 mDevice.pullFile("/service_contexts", deviceSvcFile); 923 924 /* run checkfc -s on service_contexts */ 925 String errorString = tryRunCommand(checkFc.getAbsolutePath(), 926 "-s", devicePolicyFile.getAbsolutePath(), 927 deviceSvcFile.getAbsolutePath()); 928 assertTrue("The service_contexts file was invalid:\n" 929 + errorString, errorString.length() == 0); 930 } 931 isMac()932 public static boolean isMac() { 933 String os = System.getProperty("os.name").toLowerCase(); 934 return (os.startsWith("mac") || os.startsWith("darwin")); 935 } 936 assertSepolicyTests(String test, String testExecutable, boolean includeVendorSepolicy)937 private void assertSepolicyTests(String test, String testExecutable, 938 boolean includeVendorSepolicy) throws Exception { 939 sepolicyTests = copyResourceToTempFile(testExecutable); 940 sepolicyTests.setExecutable(true); 941 942 List<String> args = new ArrayList<String>(); 943 args.add(sepolicyTests.getAbsolutePath()); 944 args.add("-f"); 945 args.add(devicePlatFcFile.getAbsolutePath()); 946 args.add("--test"); 947 args.add(test); 948 949 if (includeVendorSepolicy) { 950 args.add("-f"); 951 args.add(deviceVendorFcFile.getAbsolutePath()); 952 args.add("-p"); 953 args.add(devicePolicyFile.getAbsolutePath()); 954 } else { 955 args.add("-p"); 956 args.add(deviceSystemPolicyFile.getAbsolutePath()); 957 } 958 959 String errorString = tryRunCommand(args.toArray(new String[0])); 960 assertTrue(errorString, errorString.length() == 0); 961 962 sepolicyTests.delete(); 963 } 964 965 /** 966 * Tests that all types on /data have the data_file_type attribute. 967 * 968 * @throws Exception 969 */ 970 @Test testDataTypeViolators()971 public void testDataTypeViolators() throws Exception { 972 assertSepolicyTests("TestDataTypeViolations", "/sepolicy_tests", 973 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 974 } 975 976 /** 977 * Tests that all types in /sys/fs/bpf have the bpffs_type attribute. 978 * 979 * @throws Exception 980 */ 981 @Test testBpffsTypeViolators()982 public void testBpffsTypeViolators() throws Exception { 983 assertSepolicyTests("TestBpffsTypeViolations", "/sepolicy_tests", 984 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 33) /* includeVendorSepolicy */); 985 } 986 987 /** 988 * Tests that all types in /proc have the proc_type attribute. 989 * 990 * @throws Exception 991 */ 992 @Test testProcTypeViolators()993 public void testProcTypeViolators() throws Exception { 994 assertSepolicyTests("TestProcTypeViolations", "/sepolicy_tests", 995 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 996 } 997 998 /** 999 * Tests that all types in /sys have the sysfs_type attribute. 1000 * 1001 * @throws Exception 1002 */ 1003 @Test testSysfsTypeViolators()1004 public void testSysfsTypeViolators() throws Exception { 1005 assertSepolicyTests("TestSysfsTypeViolations", "/sepolicy_tests", 1006 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 1007 } 1008 1009 /** 1010 * Tests that all types on /vendor have the vendor_file_type attribute. 1011 * 1012 * @throws Exception 1013 */ 1014 @Test testVendorTypeViolators()1015 public void testVendorTypeViolators() throws Exception { 1016 assertSepolicyTests("TestVendorTypeViolations", "/sepolicy_tests", 1017 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 1018 } 1019 1020 /** 1021 * Tests that tracefs files(/sys/kernel/tracing and /d/tracing) are correctly labeled. 1022 * 1023 * @throws Exception 1024 */ 1025 @Test testTracefsTypeViolators()1026 public void testTracefsTypeViolators() throws Exception { 1027 assertSepolicyTests("TestTracefsTypeViolations", "/sepolicy_tests", 1028 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 30) /* includeVendorSepolicy */); 1029 } 1030 1031 /** 1032 * Tests that debugfs files(from /sys/kernel/debug) are correctly labeled. 1033 * 1034 * @throws Exception 1035 */ 1036 @Test testDebugfsTypeViolators()1037 public void testDebugfsTypeViolators() throws Exception { 1038 assertSepolicyTests("TestDebugfsTypeViolations", "/sepolicy_tests", 1039 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 30) /* includeVendorSepolicy */); 1040 } 1041 1042 /** 1043 * Tests that all domains with entrypoints on /system have the coredomain 1044 * attribute, and that all domains with entrypoints on /vendor do not have the 1045 * coredomain attribute. 1046 * 1047 * @throws Exception 1048 */ 1049 @Test testCoredomainViolators()1050 public void testCoredomainViolators() throws Exception { 1051 assertSepolicyTests("CoredomainViolations", "/sepolicy_tests", 1052 PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */); 1053 } 1054 1055 /** 1056 * Tests that all labels on /dev have the dev_type attribute. 1057 * 1058 * @throws Exception 1059 */ 1060 @Test testDevTypeViolators()1061 public void testDevTypeViolators() throws Exception { 1062 int vsrVersion = getVSRApiLevel(getDevice()); 1063 assumeTrue("Skipping test: dev_type is enforced for W or later", vsrVersion > 202404); 1064 assertSepolicyTests("TestDevTypeViolations", "/sepolicy_tests", true); 1065 } 1066 1067 /** 1068 * Tests that the policy defines no booleans (runtime conditional policy). 1069 * 1070 * @throws Exception 1071 */ 1072 @CddTest(requirement="9.7") 1073 @Test testNoBooleans()1074 public void testNoBooleans() throws Exception { 1075 1076 /* run sepolicy-analyze booleans check on policy file */ 1077 String errorString = tryRunCommand(mSepolicyAnalyze.getAbsolutePath(), 1078 devicePolicyFile.getAbsolutePath(), "booleans"); 1079 assertTrue("The policy contained booleans:\n" 1080 + errorString, errorString.length() == 0); 1081 } 1082 1083 /** 1084 * Tests that taking a bugreport does not produce any dumpstate-related 1085 * SELinux denials. 1086 * 1087 * @throws Exception 1088 */ 1089 @Test testNoBugreportDenials()1090 public void testNoBugreportDenials() throws Exception { 1091 // Take a bugreport and get its logcat output. 1092 mDevice.executeAdbCommand("logcat", "-c"); 1093 mDevice.getBugreport(); 1094 String log = mDevice.executeAdbCommand("logcat", "-d"); 1095 // Find all the dumpstate-related types and make a regex that will match them. 1096 Set<String> types = sepolicyAnalyzeGetTypesAssociatedWithAttribute("hal_dumpstate_server"); 1097 types.add("dumpstate"); 1098 String typeRegex = types.stream().collect(Collectors.joining("|")); 1099 Pattern p = Pattern.compile("avc: *denied.*scontext=u:(?:r|object_r):(?:" + typeRegex + "):s0.*"); 1100 // Fail if logcat contains such a denial. 1101 Matcher m = p.matcher(log); 1102 StringBuilder errorString = new StringBuilder(); 1103 while (m.find()) { 1104 errorString.append(m.group()); 1105 errorString.append("\n"); 1106 } 1107 assertTrue("Found illegal SELinux denial(s): " + errorString, errorString.length() == 0); 1108 } 1109 1110 /** 1111 * Tests that important domain labels are being appropriately applied. 1112 */ 1113 1114 /** 1115 * Asserts that no processes are running in a domain. 1116 * 1117 * @param domain 1118 * The domain or SELinux context to check. 1119 */ assertDomainEmpty(String domain)1120 private void assertDomainEmpty(String domain) throws DeviceNotAvailableException { 1121 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1122 String msg = "Expected no processes in SELinux domain \"" + domain + "\"" 1123 + " Found: \"" + procs + "\""; 1124 assertNull(msg, procs); 1125 } 1126 1127 /** 1128 * Asserts that a domain exists and that only one, well defined, process is 1129 * running in that domain. 1130 * 1131 * @param domain 1132 * The domain or SELinux context to check. 1133 * @param executable 1134 * The path of the executable or application package name. 1135 */ assertDomainOne(String domain, String executable)1136 private void assertDomainOne(String domain, String executable) throws DeviceNotAvailableException { 1137 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1138 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(executable); 1139 String msg = "Expected 1 process in SELinux domain \"" + domain + "\"" 1140 + " Found \"" + procs + "\""; 1141 assertNotNull(msg, procs); 1142 assertEquals(msg, 1, procs.size()); 1143 1144 msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1145 + "Found: \"" + procs + "\""; 1146 assertEquals(msg, executable, procs.get(0).procTitle); 1147 1148 msg = "Expected 1 process with executable \"" + executable + "\"" 1149 + " Found \"" + procs + "\""; 1150 assertNotNull(msg, exeProcs); 1151 assertEquals(msg, 1, exeProcs.size()); 1152 1153 msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1154 + "Found: \"" + procs + "\""; 1155 assertEquals(msg, domain, exeProcs.get(0).label); 1156 } 1157 1158 /** 1159 * Asserts that a domain may exist. If a domain exists, the cardinality of 1160 * the domain is verified to be 1 and that the correct process is running in 1161 * that domain. If the process is running, it is running in that domain. 1162 * 1163 * @param domain 1164 * The domain or SELinux context to check. 1165 * @param executable 1166 * The path of the executable or application package name. 1167 */ assertDomainZeroOrOne(String domain, String executable)1168 private void assertDomainZeroOrOne(String domain, String executable) 1169 throws DeviceNotAvailableException { 1170 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1171 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(executable); 1172 if (procs != null) { 1173 String msg = "Expected 1 process in SELinux domain \"" + domain + "\"" 1174 + " Found: \"" + procs + "\""; 1175 assertEquals(msg, 1, procs.size()); 1176 1177 msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1178 + "Found: \"" + procs.get(0) + "\""; 1179 assertEquals(msg, executable, procs.get(0).procTitle); 1180 } 1181 if (exeProcs != null) { 1182 String msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1183 + " Instead found it running in the domain \"" + exeProcs.get(0).label + "\""; 1184 assertNotNull(msg, procs); 1185 1186 msg = "Expected 1 process with executable \"" + executable + "\"" 1187 + " Found: \"" + procs + "\""; 1188 assertEquals(msg, 1, exeProcs.size()); 1189 1190 msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\"" 1191 + "Found: \"" + procs.get(0) + "\""; 1192 assertEquals(msg, domain, exeProcs.get(0).label); 1193 } 1194 } 1195 1196 /** 1197 * Asserts that a domain must exist, and that the cardinality is greater 1198 * than or equal to 1. 1199 * 1200 * @param domain 1201 * The domain or SELinux context to check. 1202 * @param executables 1203 * The path of the allowed executables or application package names. 1204 */ assertDomainN(String domain, String... executables)1205 private void assertDomainN(String domain, String... executables) 1206 throws DeviceNotAvailableException { 1207 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1208 String msg = "Expected 1 or more processes in SELinux domain but found none."; 1209 assertNotNull(msg, procs); 1210 1211 Set<String> execList = new HashSet<String>(Arrays.asList(executables)); 1212 1213 for (ProcessDetails p : procs) { 1214 msg = "Expected one of \"" + execList + "\" in SELinux domain \"" + domain + "\"" 1215 + " Found: \"" + p + "\""; 1216 assertTrue(msg, execList.contains(p.procTitle)); 1217 } 1218 1219 for (String exe : executables) { 1220 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(exe); 1221 1222 if (exeProcs != null) { 1223 for (ProcessDetails p : exeProcs) { 1224 msg = "Expected executable \"" + exe + "\" in SELinux domain \"" 1225 + domain + "\"" + " Found: \"" + p + "\""; 1226 assertEquals(msg, domain, p.label); 1227 } 1228 } 1229 } 1230 } 1231 1232 /** 1233 * Asserts that a domain, if it exists, is only running the listed executables. 1234 * 1235 * @param domain 1236 * The domain or SELinux context to check. 1237 * @param executables 1238 * The path of the allowed executables or application package names. 1239 */ assertDomainHasExecutable(String domain, String... executables)1240 private void assertDomainHasExecutable(String domain, String... executables) 1241 throws DeviceNotAvailableException { 1242 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1243 1244 if (procs != null) { 1245 Set<String> execList = new HashSet<String>(Arrays.asList(executables)); 1246 1247 for (ProcessDetails p : procs) { 1248 String msg = "Expected one of \"" + execList + "\" in SELinux domain \"" 1249 + domain + "\"" + " Found: \"" + p + "\""; 1250 assertTrue(msg, execList.contains(p.procTitle)); 1251 } 1252 } 1253 } 1254 1255 /** 1256 * Asserts that an executable exists and is only running in the listed domains. 1257 * 1258 * @param executable 1259 * The path of the executable to check. 1260 * @param domains 1261 * The list of allowed domains. 1262 */ assertExecutableExistsAndHasDomain(String executable, String... domains)1263 private void assertExecutableExistsAndHasDomain(String executable, String... domains) 1264 throws DeviceNotAvailableException { 1265 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(executable); 1266 Set<String> domainList = new HashSet<String>(Arrays.asList(domains)); 1267 1268 String msg = "Expected 1 or more processes for executable \"" + executable + "\"."; 1269 assertNotNull(msg, exeProcs); 1270 1271 for (ProcessDetails p : exeProcs) { 1272 msg = "Expected one of \"" + domainList + "\" for executable \"" + executable 1273 + "\"" + " Found: \"" + p.label + "\""; 1274 assertTrue(msg, domainList.contains(p.label)); 1275 } 1276 } 1277 1278 /** 1279 * Asserts that an executable, if it exists, is only running in the listed domains. 1280 * 1281 * @param executable 1282 * The path of the executable to check. 1283 * @param domains 1284 * The list of allowed domains. 1285 */ assertExecutableHasDomain(String executable, String... domains)1286 private void assertExecutableHasDomain(String executable, String... domains) 1287 throws DeviceNotAvailableException { 1288 List<ProcessDetails> exeProcs = ProcessDetails.getExeMap(mDevice).get(executable); 1289 Set<String> domainList = new HashSet<String>(Arrays.asList(domains)); 1290 1291 if (exeProcs != null) { 1292 for (ProcessDetails p : exeProcs) { 1293 String msg = "Expected one of \"" + domainList + "\" for executable \"" + executable 1294 + "\"" + " Found: \"" + p.label + "\""; 1295 assertTrue(msg, domainList.contains(p.label)); 1296 } 1297 } 1298 } 1299 1300 /* Init is always there */ 1301 @CddTest(requirement="9.7") 1302 @Test testInitDomain()1303 public void testInitDomain() throws DeviceNotAvailableException { 1304 assertDomainHasExecutable("u:r:init:s0", "/system/bin/init"); 1305 assertDomainHasExecutable("u:r:vendor_init:s0", "/system/bin/init"); 1306 assertExecutableExistsAndHasDomain("/system/bin/init", "u:r:init:s0", "u:r:vendor_init:s0"); 1307 } 1308 1309 /* Ueventd is always there */ 1310 @CddTest(requirement="9.7") 1311 @Test testUeventdDomain()1312 public void testUeventdDomain() throws DeviceNotAvailableException { 1313 assertDomainOne("u:r:ueventd:s0", "/system/bin/ueventd"); 1314 } 1315 1316 /* healthd may or may not exist */ 1317 @CddTest(requirement="9.7") 1318 @Test testHealthdDomain()1319 public void testHealthdDomain() throws DeviceNotAvailableException { 1320 assertDomainZeroOrOne("u:r:healthd:s0", "/system/bin/healthd"); 1321 } 1322 1323 /* Servicemanager is always there */ 1324 @CddTest(requirement="9.7") 1325 @Test testServicemanagerDomain()1326 public void testServicemanagerDomain() throws DeviceNotAvailableException { 1327 assertDomainOne("u:r:servicemanager:s0", "/system/bin/servicemanager"); 1328 } 1329 1330 /* Vold is always there */ 1331 @CddTest(requirement="9.7") 1332 @Test testVoldDomain()1333 public void testVoldDomain() throws DeviceNotAvailableException { 1334 assertDomainOne("u:r:vold:s0", "/system/bin/vold"); 1335 } 1336 1337 /* netd is always there */ 1338 @CddTest(requirement="9.7") 1339 @Test testNetdDomain()1340 public void testNetdDomain() throws DeviceNotAvailableException { 1341 assertDomainN("u:r:netd:s0", "/system/bin/netd", "/system/bin/iptables-restore", "/system/bin/ip6tables-restore"); 1342 } 1343 1344 /* Surface flinger is always there */ 1345 @CddTest(requirement="9.7") 1346 @Test testSurfaceflingerDomain()1347 public void testSurfaceflingerDomain() throws DeviceNotAvailableException { 1348 assertDomainOne("u:r:surfaceflinger:s0", "/system/bin/surfaceflinger"); 1349 } 1350 1351 /* Zygote is always running */ 1352 @CddTest(requirement="9.7") 1353 @Test testZygoteDomain()1354 public void testZygoteDomain() throws DeviceNotAvailableException { 1355 assertDomainN("u:r:zygote:s0", "zygote", "zygote64", "usap32", "usap64"); 1356 } 1357 1358 /* Checks drmserver for devices that require it */ 1359 @CddTest(requirement="9.7") 1360 @Test testDrmServerDomain()1361 public void testDrmServerDomain() throws DeviceNotAvailableException { 1362 assertDomainHasExecutable("u:r:drmserver:s0", "/system/bin/drmserver", "/system/bin/drmserver32", "/system/bin/drmserver64"); 1363 } 1364 1365 /* Installd is always running */ 1366 @CddTest(requirement="9.7") 1367 @Test testInstalldDomain()1368 public void testInstalldDomain() throws DeviceNotAvailableException { 1369 assertDomainOne("u:r:installd:s0", "/system/bin/installd"); 1370 } 1371 1372 /* keystore is always running */ 1373 @CddTest(requirement="9.7") 1374 @Test testKeystoreDomain()1375 public void testKeystoreDomain() throws DeviceNotAvailableException { 1376 assertDomainOne("u:r:keystore:s0", "/system/bin/keystore2"); 1377 } 1378 1379 /* System server better be running :-P */ 1380 @CddTest(requirement="9.7") 1381 @Test testSystemServerDomain()1382 public void testSystemServerDomain() throws DeviceNotAvailableException { 1383 assertDomainOne("u:r:system_server:s0", "system_server"); 1384 } 1385 1386 /* Watchdogd may or may not be there */ 1387 @CddTest(requirement="9.7") 1388 @Test testWatchdogdDomain()1389 public void testWatchdogdDomain() throws DeviceNotAvailableException { 1390 assertDomainZeroOrOne("u:r:watchdogd:s0", "/system/bin/watchdogd"); 1391 } 1392 1393 /* logd may or may not be there */ 1394 @CddTest(requirement="9.7") 1395 @Test testLogdDomain()1396 public void testLogdDomain() throws DeviceNotAvailableException { 1397 assertDomainZeroOrOne("u:r:logd:s0", "/system/bin/logd"); 1398 } 1399 1400 /* lmkd may or may not be there */ 1401 @CddTest(requirement="9.7") 1402 @Test testLmkdDomain()1403 public void testLmkdDomain() throws DeviceNotAvailableException { 1404 assertDomainZeroOrOne("u:r:lmkd:s0", "/system/bin/lmkd"); 1405 } 1406 1407 /* Wifi may be off so cardinality of 0 or 1 is ok */ 1408 @CddTest(requirement="9.7") 1409 @Test testWpaDomain()1410 public void testWpaDomain() throws DeviceNotAvailableException { 1411 assertDomainZeroOrOne("u:r:wpa:s0", "/system/bin/wpa_supplicant"); 1412 } 1413 1414 /* permissioncontroller, if running, always runs in permissioncontroller_app */ 1415 @CddTest(requirement="9.7") 1416 @Test testPermissionControllerDomain()1417 public void testPermissionControllerDomain() throws DeviceNotAvailableException { 1418 assertExecutableHasDomain("com.google.android.permissioncontroller", "u:r:permissioncontroller_app:s0"); 1419 assertExecutableHasDomain("com.android.permissioncontroller", "u:r:permissioncontroller_app:s0"); 1420 } 1421 1422 /* vzwomatrigger may or may not be running */ 1423 @CddTest(requirement="9.7") 1424 @Test testVzwOmaTriggerDomain()1425 public void testVzwOmaTriggerDomain() throws DeviceNotAvailableException { 1426 assertDomainZeroOrOne("u:r:vzwomatrigger_app:s0", "com.android.vzwomatrigger"); 1427 } 1428 1429 /* gmscore, if running, always runs in gmscore_app */ 1430 @CddTest(requirement="9.7") 1431 @Test testGMSCoreDomain()1432 public void testGMSCoreDomain() throws DeviceNotAvailableException { 1433 assertExecutableHasDomain("com.google.android.gms", "u:r:gmscore_app:s0"); 1434 assertExecutableHasDomain("com.google.android.gms.ui", "u:r:gmscore_app:s0"); 1435 assertExecutableHasDomain("com.google.android.gms.persistent", "u:r:gmscore_app:s0"); 1436 assertExecutableHasDomain("com.google.android.gms:snet", "u:r:gmscore_app:s0"); 1437 } 1438 1439 /* 1440 * Nothing should be running in this domain, cardinality test is all thats 1441 * needed 1442 */ 1443 @CddTest(requirement="9.7") 1444 @Test testInitShellDomain()1445 public void testInitShellDomain() throws DeviceNotAvailableException { 1446 assertDomainEmpty("u:r:init_shell:s0"); 1447 } 1448 1449 /* 1450 * Nothing should be running in this domain, cardinality test is all thats 1451 * needed 1452 */ 1453 @CddTest(requirement="9.7") 1454 @Test testRecoveryDomain()1455 public void testRecoveryDomain() throws DeviceNotAvailableException { 1456 assertDomainEmpty("u:r:recovery:s0"); 1457 } 1458 1459 /* 1460 * Nothing should be running in this domain, cardinality test is all thats 1461 * needed 1462 */ 1463 @CddTest(requirement="9.7") 1464 @RestrictedBuildTest 1465 @Test testSuDomain()1466 public void testSuDomain() throws DeviceNotAvailableException { 1467 assertDomainEmpty("u:r:su:s0"); 1468 } 1469 1470 /* 1471 * All kthreads should be in kernel context. 1472 */ 1473 @CddTest(requirement="9.7") 1474 @Test testKernelDomain()1475 public void testKernelDomain() throws DeviceNotAvailableException { 1476 String domain = "u:r:kernel:s0"; 1477 List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain); 1478 if (procs != null) { 1479 for (ProcessDetails p : procs) { 1480 assertTrue("Non Kernel thread \"" + p + "\" found!", p.isKernel()); 1481 } 1482 } 1483 } 1484 1485 private static class ProcessDetails { 1486 public String label; 1487 public String user; 1488 public int pid; 1489 public int ppid; 1490 public String procTitle; 1491 1492 private static HashMap<String, ArrayList<ProcessDetails>> procMap; 1493 private static HashMap<String, ArrayList<ProcessDetails>> exeMap; 1494 private static int kernelParentThreadpid = -1; 1495 ProcessDetails(String label, String user, int pid, int ppid, String procTitle)1496 ProcessDetails(String label, String user, int pid, int ppid, String procTitle) { 1497 this.label = label; 1498 this.user = user; 1499 this.pid = pid; 1500 this.ppid = ppid; 1501 this.procTitle = procTitle; 1502 } 1503 1504 @Override toString()1505 public String toString() { 1506 return "label: " + label 1507 + " user: " + user 1508 + " pid: " + pid 1509 + " ppid: " + ppid 1510 + " cmd: " + procTitle; 1511 } 1512 1513 createProcMap(ITestDevice tDevice)1514 private static void createProcMap(ITestDevice tDevice) throws DeviceNotAvailableException { 1515 1516 /* take the output of a ps -Z to do our analysis */ 1517 CollectingOutputReceiver psOut = new CollectingOutputReceiver(); 1518 // TODO: remove "toybox" below and just run "ps" 1519 tDevice.executeShellCommand("toybox ps -A -o label,user,pid,ppid,cmdline", psOut); 1520 String psOutString = psOut.getOutput(); 1521 Pattern p = Pattern.compile( 1522 "^([\\w_:,]+)\\s+([\\w_]+)\\s+(\\d+)\\s+(\\d+)\\s+(\\p{Graph}+)(\\s\\p{Graph}+)*\\s*$" 1523 ); 1524 procMap = new HashMap<String, ArrayList<ProcessDetails>>(); 1525 exeMap = new HashMap<String, ArrayList<ProcessDetails>>(); 1526 for(String line : psOutString.split("\n")) { 1527 Matcher m = p.matcher(line); 1528 if(m.matches()) { 1529 String domainLabel = m.group(1); 1530 // clean up the domainlabel 1531 String[] parts = domainLabel.split(":"); 1532 if (parts.length > 4) { 1533 // we have an extra categories bit at the end consisting of cxxx,cxxx ... 1534 // just make the domain out of the first 4 parts 1535 domainLabel = String.join(":", parts[0], parts[1], parts[2], parts[3]); 1536 } 1537 1538 String user = m.group(2); 1539 int pid = Integer.parseInt(m.group(3)); 1540 int ppid = Integer.parseInt(m.group(4)); 1541 String procTitle = m.group(5); 1542 ProcessDetails proc = new ProcessDetails(domainLabel, user, pid, ppid, procTitle); 1543 if (procMap.get(domainLabel) == null) { 1544 procMap.put(domainLabel, new ArrayList<ProcessDetails>()); 1545 } 1546 procMap.get(domainLabel).add(proc); 1547 if (procTitle.equals("[kthreadd]") && ppid == 0) { 1548 kernelParentThreadpid = pid; 1549 } 1550 if (exeMap.get(procTitle) == null) { 1551 exeMap.put(procTitle, new ArrayList<ProcessDetails>()); 1552 } 1553 exeMap.get(procTitle).add(proc); 1554 } 1555 } 1556 } 1557 getProcMap(ITestDevice tDevice)1558 public static HashMap<String, ArrayList<ProcessDetails>> getProcMap(ITestDevice tDevice) 1559 throws DeviceNotAvailableException{ 1560 if (procMap == null) { 1561 createProcMap(tDevice); 1562 } 1563 return procMap; 1564 } 1565 getExeMap(ITestDevice tDevice)1566 public static HashMap<String, ArrayList<ProcessDetails>> getExeMap(ITestDevice tDevice) 1567 throws DeviceNotAvailableException{ 1568 if (exeMap == null) { 1569 createProcMap(tDevice); 1570 } 1571 return exeMap; 1572 } 1573 isKernel()1574 public boolean isKernel() { 1575 return (pid == kernelParentThreadpid || ppid == kernelParentThreadpid); 1576 } 1577 } 1578 tryRunCommand(String... command)1579 private static String tryRunCommand(String... command) throws Exception { 1580 ProcessBuilder pb = new ProcessBuilder(command); 1581 pb.redirectOutput(ProcessBuilder.Redirect.PIPE); 1582 pb.redirectErrorStream(true); 1583 Process p = pb.start(); 1584 p.waitFor(); 1585 BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); 1586 StringBuilder result = new StringBuilder(); 1587 String line; 1588 while ((line = reader.readLine()) != null) { 1589 result.append(line); 1590 result.append("\n"); 1591 } 1592 return result.toString(); 1593 } 1594 createTempFile(String name, String ext)1595 private static File createTempFile(String name, String ext) throws IOException { 1596 File ret = File.createTempFile(name, ext); 1597 ret.deleteOnExit(); 1598 return ret; 1599 } 1600 } 1601