xref: /aosp_15_r20/cts/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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