1#!/usr/bin/env python
2
3import re
4import sys
5import SELinuxNeverallowTestFrame
6
7usage = "Usage: ./SELinuxNeverallowTestGen.py <input policy file> <output cts java source>"
8
9
10class NeverallowRule:
11    statement = ''
12    treble_only = False
13    compatible_property_only = False
14
15    def __init__(self, statement):
16        self.statement = statement
17        self.treble_only = False
18        self.compatible_property_only = False
19
20
21# extract_neverallow_rules - takes an intermediate policy file and pulls out the
22# neverallow rules by taking all of the non-commented text between the 'neverallow'
23# keyword and a terminating ';'
24# returns: a list of rules
25def extract_neverallow_rules(policy_file):
26    with open(policy_file, 'r') as in_file:
27        policy_str = in_file.read()
28
29        # full-Treble only tests are inside sections delimited by BEGIN_TREBLE_ONLY
30        # and END_TREBLE_ONLY comments.
31
32        # uncomment TREBLE_ONLY section delimiter lines
33        remaining = re.sub(
34            r'^\s*#\s*(BEGIN_TREBLE_ONLY|END_TREBLE_ONLY|BEGIN_COMPATIBLE_PROPERTY_ONLY|END_COMPATIBLE_PROPERTY_ONLY)',
35            r'\1',
36            policy_str,
37            flags = re.M)
38        # remove comments
39        remaining = re.sub(r'#.+?$', r'', remaining, flags = re.M)
40        # match neverallow rules
41        lines = re.findall(
42            r'^\s*(neverallow\s.+?;|BEGIN_TREBLE_ONLY|END_TREBLE_ONLY|BEGIN_COMPATIBLE_PROPERTY_ONLY|END_COMPATIBLE_PROPERTY_ONLY)',
43            remaining,
44            flags = re.M |re.S)
45
46        # extract neverallow rules from the remaining lines
47        rules = list()
48        treble_only_depth = 0
49        compatible_property_only_depth = 0
50        for line in lines:
51            if line.startswith("BEGIN_TREBLE_ONLY"):
52                treble_only_depth += 1
53                continue
54            elif line.startswith("END_TREBLE_ONLY"):
55                if treble_only_depth < 1:
56                    exit("ERROR: END_TREBLE_ONLY outside of TREBLE_ONLY section")
57                treble_only_depth -= 1
58                continue
59            elif line.startswith("BEGIN_COMPATIBLE_PROPERTY_ONLY"):
60                compatible_property_only_depth += 1
61                continue
62            elif line.startswith("END_COMPATIBLE_PROPERTY_ONLY"):
63                if compatible_property_only_depth < 1:
64                    exit("ERROR: END_COMPATIBLE_PROPERTY_ONLY outside of COMPATIBLE_PROPERTY_ONLY section")
65                compatible_property_only_depth -= 1
66                continue
67            rule = NeverallowRule(line)
68            rule.treble_only = (treble_only_depth > 0)
69            rule.compatible_property_only = (compatible_property_only_depth > 0)
70            rules.append(rule)
71
72        if treble_only_depth != 0:
73            exit("ERROR: end of input while inside TREBLE_ONLY section")
74        if compatible_property_only_depth != 0:
75            exit("ERROR: end of input while inside COMPATIBLE_PROPERTY_ONLY section")
76
77        return rules
78
79# neverallow_rule_to_test - takes a neverallow statement and transforms it into
80# the output necessary to form a cts unit test in a java source file.
81# returns: a string representing a generic test method based on this rule.
82def neverallow_rule_to_test(rule, test_num):
83    squashed_neverallow = rule.statement.replace("\n", " ")
84    method  = SELinuxNeverallowTestFrame.src_method
85    method = method.replace("testNeverallowRules()",
86        "testNeverallowRules" + str(test_num) + "()")
87    method = method.replace("$NEVERALLOW_RULE_HERE$", squashed_neverallow)
88    method = method.replace(
89        "$FULL_TREBLE_ONLY_BOOL_HERE$",
90        "true" if rule.treble_only else "false")
91    method = method.replace(
92        "$COMPATIBLE_PROPERTY_ONLY_BOOL_HERE$",
93        "true" if rule.compatible_property_only else "false")
94    return method
95
96if __name__ == "__main__":
97    # check usage
98    if len(sys.argv) != 3:
99        print usage
100        exit(1)
101    input_file = sys.argv[1]
102    output_file = sys.argv[2]
103
104    src_header = SELinuxNeverallowTestFrame.src_header
105    src_body = SELinuxNeverallowTestFrame.src_body
106    src_footer = SELinuxNeverallowTestFrame.src_footer
107
108    # grab the neverallow rules from the policy file and transform into tests
109    neverallow_rules = extract_neverallow_rules(input_file)
110    i = 0
111    for rule in neverallow_rules:
112        src_body += neverallow_rule_to_test(rule, i)
113        i += 1
114
115    with open(output_file, 'w') as out_file:
116        out_file.write(src_header)
117        out_file.write(src_body)
118        out_file.write(src_footer)
119