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