Enforce Treble-only neverallows

This changes SELinuxNeverallowRulesTest so that it enforces
Treble-only neverallows only for Treble devices. The companion
change in system/sepolicy is the one which makes CTS include
Treble-only neverallows and annotate them appropriate so that this
test knows which neverallows are Treble-only.

Test: make cts && \
      cts-tradefed run singleCommand cts --skip-device-info \
          --skip-preconditions --skip-connectivity-check \
          --abi arm64-v8a --module CtsSecurityHostTestCases \
          -t android.cts.security.SELinuxNeverallowRulesTest
Bug: 37082262

Change-Id: I60738d644c0b6bf9ff684a52b62a20b22719bcbb
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index 767939a..8ed60e6 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -235,8 +235,17 @@
         }
     }
 
-    private boolean isFullTrebleDevice() throws Exception {
-        return PropertyUtil.getFirstApiLevel(mDevice) > 25;
+    // NOTE: cts/tools/selinux depends on this method. Rename/change with caution.
+    /**
+     * Returns {@code true} if this device is required to be a full Treble device.
+     */
+    public static boolean isFullTrebleDevice(ITestDevice device)
+            throws DeviceNotAvailableException {
+        return PropertyUtil.getFirstApiLevel(device) > 25;
+    }
+
+    private boolean isFullTrebleDevice() throws DeviceNotAvailableException {
+        return isFullTrebleDevice(mDevice);
     }
 
     /**
diff --git a/tools/selinux/SELinuxNeverallowTestFrame.py b/tools/selinux/SELinuxNeverallowTestFrame.py
index 31ee446..7e9c304 100644
--- a/tools/selinux/SELinuxNeverallowTestFrame.py
+++ b/tools/selinux/SELinuxNeverallowTestFrame.py
@@ -73,6 +73,10 @@
         devicePolicyFile.deleteOnExit();
         mDevice.pullFile("/sys/fs/selinux/policy", devicePolicyFile);
     }
+
+    private boolean isFullTrebleDevice() throws Exception {
+        return android.security.cts.SELinuxHostTest.isFullTrebleDevice(mDevice);
+    }
 """
 src_body = ""
 src_footer = """}
@@ -82,6 +86,12 @@
     @RestrictedBuildTest
     public void testNeverallowRules() throws Exception {
         String neverallowRule = "$NEVERALLOW_RULE_HERE$";
+        boolean fullTrebleOnly = $FULL_TREBLE_ONLY_BOOL_HERE$;
+
+        if ((fullTrebleOnly) && (!isFullTrebleDevice())) {
+            // This test applies only to Treble devices but this device isn't one
+            return;
+        }
 
         /* run sepolicy-analyze neverallow check on policy file using given neverallow rules */
         ProcessBuilder pb = new ProcessBuilder(sepolicyAnalyze.getAbsolutePath(),
diff --git a/tools/selinux/SELinuxNeverallowTestGen.py b/tools/selinux/SELinuxNeverallowTestGen.py
index 6194e2d..ec29e45 100755
--- a/tools/selinux/SELinuxNeverallowTestGen.py
+++ b/tools/selinux/SELinuxNeverallowTestGen.py
@@ -4,29 +4,76 @@
 import sys
 import SELinuxNeverallowTestFrame
 
-usage = "Usage: ./gen_SELinux_CTS_neverallows.py <input policy file> <output cts java source>"
+usage = "Usage: ./SELinuxNeverallowTestGen.py <input policy file> <output cts java source>"
+
+
+class NeverallowRule:
+    statement = ''
+    treble_only = False
+
+    def __init__(self, statement):
+        self.statement = statement
+        self.treble_only = False
+
 
 # extract_neverallow_rules - takes an intermediate policy file and pulls out the
 # neverallow rules by taking all of the non-commented text between the 'neverallow'
 # keyword and a terminating ';'
-# returns: a list of strings representing these rules
+# returns: a list of rules
 def extract_neverallow_rules(policy_file):
     with open(policy_file, 'r') as in_file:
         policy_str = in_file.read()
+
+        # full-Treble only tests are inside sections delimited by BEGIN_TREBLE_ONLY
+        # and END_TREBLE_ONLY comments.
+
+        # uncomment TREBLE_ONLY section delimiter lines
+        remaining = re.sub(
+            r'^\s*#\s*(BEGIN_TREBLE_ONLY|END_TREBLE_ONLY)',
+            r'\1',
+            policy_str,
+            flags = re.M)
         # remove comments
-        no_comments = re.sub(r'#.+?$', r'', policy_str, flags = re.M)
+        remaining = re.sub(r'#.+?$', r'', remaining, flags = re.M)
         # match neverallow rules
-        return re.findall(r'^\s*(neverallow\s.+?;)', no_comments, flags = re.M |re.S);
+        lines = re.findall(
+            r'^\s*(neverallow\s.+?;|BEGIN_TREBLE_ONLY|END_TREBLE_ONLY)',
+            remaining,
+            flags = re.M |re.S)
+
+        # extract neverallow rules from the remaining lines
+        rules = list()
+        treble_only_depth = 0
+        for line in lines:
+            if line.startswith("BEGIN_TREBLE_ONLY"):
+                treble_only_depth += 1
+                continue
+            elif line.startswith("END_TREBLE_ONLY"):
+                if treble_only_depth < 1:
+                    exit("ERROR: END_TREBLE_ONLY outside of TREBLE_ONLY section")
+                treble_only_depth -= 1
+                continue
+            rule = NeverallowRule(line)
+            rule.treble_only = (treble_only_depth > 0)
+            rules.append(rule)
+
+        if treble_only_depth != 0:
+            exit("ERROR: end of input while inside TREBLE_ONLY section")
+        return rules
 
 # neverallow_rule_to_test - takes a neverallow statement and transforms it into
 # the output necessary to form a cts unit test in a java source file.
 # returns: a string representing a generic test method based on this rule.
-def neverallow_rule_to_test(neverallow_rule, test_num):
-    squashed_neverallow = neverallow_rule.replace("\n", " ")
+def neverallow_rule_to_test(rule, test_num):
+    squashed_neverallow = rule.statement.replace("\n", " ")
     method  = SELinuxNeverallowTestFrame.src_method
     method = method.replace("testNeverallowRules()",
         "testNeverallowRules" + str(test_num) + "()")
-    return method.replace("$NEVERALLOW_RULE_HERE$", squashed_neverallow)
+    method = method.replace("$NEVERALLOW_RULE_HERE$", squashed_neverallow)
+    method = method.replace(
+        "$FULL_TREBLE_ONLY_BOOL_HERE$",
+        "true" if rule.treble_only else "false")
+    return method
 
 if __name__ == "__main__":
     # check usage