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