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