1c1ed15f1Sopenharmony_ci#!/usr/bin/env python
2c1ed15f1Sopenharmony_ci# coding: utf-8
3c1ed15f1Sopenharmony_ci
4c1ed15f1Sopenharmony_ci"""
5c1ed15f1Sopenharmony_ciCopyright (c) 2023 Huawei Device Co., Ltd.
6c1ed15f1Sopenharmony_ciLicensed under the Apache License, Version 2.0 (the "License");
7c1ed15f1Sopenharmony_ciyou may not use this file except in compliance with the License.
8c1ed15f1Sopenharmony_ciYou may obtain a copy of the License at
9c1ed15f1Sopenharmony_ci
10c1ed15f1Sopenharmony_ci    http://www.apache.org/licenses/LICENSE-2.0
11c1ed15f1Sopenharmony_ci
12c1ed15f1Sopenharmony_ciUnless required by applicable law or agreed to in writing, software
13c1ed15f1Sopenharmony_cidistributed under the License is distributed on an "AS IS" BASIS,
14c1ed15f1Sopenharmony_ciWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15c1ed15f1Sopenharmony_ciSee the License for the specific language governing permissions and
16c1ed15f1Sopenharmony_cilimitations under the License.
17c1ed15f1Sopenharmony_ci
18c1ed15f1Sopenharmony_ci"""
19c1ed15f1Sopenharmony_ci
20c1ed15f1Sopenharmony_ciimport argparse
21c1ed15f1Sopenharmony_ciimport os
22c1ed15f1Sopenharmony_cifrom collections import defaultdict
23c1ed15f1Sopenharmony_cifrom check_common import read_json_file, traverse_file_in_each_type
24c1ed15f1Sopenharmony_ci
25c1ed15f1Sopenharmony_ciWHITELIST_FILE_NAME = "perm_group_whitelist.json"
26c1ed15f1Sopenharmony_ci
27c1ed15f1Sopenharmony_ci
28c1ed15f1Sopenharmony_ciclass PolicyDb(object):
29c1ed15f1Sopenharmony_ci    def __init__(self, attributes_map, allow_map, class_map):
30c1ed15f1Sopenharmony_ci        self.attributes_map = attributes_map
31c1ed15f1Sopenharmony_ci        self.allow_map = allow_map
32c1ed15f1Sopenharmony_ci        self.class_map = class_map
33c1ed15f1Sopenharmony_ci
34c1ed15f1Sopenharmony_ci
35c1ed15f1Sopenharmony_cidef simplify_string(string):
36c1ed15f1Sopenharmony_ci    return string.replace('(', '').replace(')', '').replace('\n', '').strip()
37c1ed15f1Sopenharmony_ci
38c1ed15f1Sopenharmony_ci
39c1ed15f1Sopenharmony_cidef deal_with_allow(cil_file, allow_map, attributes_map):
40c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
41c1ed15f1Sopenharmony_ci        for line in cil_read:
42c1ed15f1Sopenharmony_ci            if not line.startswith('(allow ') and not line.startswith('(auditallow '):
43c1ed15f1Sopenharmony_ci                continue
44c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
45c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
46c1ed15f1Sopenharmony_ci            # (allow A B (dir (getattr)))
47c1ed15f1Sopenharmony_ci            if len(elem_list) < 5:
48c1ed15f1Sopenharmony_ci                continue
49c1ed15f1Sopenharmony_ci            split_attribute(elem_list, allow_map, attributes_map)
50c1ed15f1Sopenharmony_ci
51c1ed15f1Sopenharmony_ci
52c1ed15f1Sopenharmony_cidef split_attribute(elem_list, allow_map, attributes_map):
53c1ed15f1Sopenharmony_ci    scontext = elem_list[1]
54c1ed15f1Sopenharmony_ci    tcontext = elem_list[2]
55c1ed15f1Sopenharmony_ci    tclass = elem_list[3]
56c1ed15f1Sopenharmony_ci    perm = elem_list[4:]
57c1ed15f1Sopenharmony_ci    if scontext not in attributes_map:
58c1ed15f1Sopenharmony_ci        # allow type self
59c1ed15f1Sopenharmony_ci        if tcontext == 'self':
60c1ed15f1Sopenharmony_ci            allow_map[(scontext, scontext)][tclass] += perm
61c1ed15f1Sopenharmony_ci        # allow type attribute
62c1ed15f1Sopenharmony_ci        elif tcontext in attributes_map:
63c1ed15f1Sopenharmony_ci            for tcon in attributes_map[tcontext]:
64c1ed15f1Sopenharmony_ci                allow_map[(scontext, tcon)][tclass] += perm
65c1ed15f1Sopenharmony_ci        # allow type type
66c1ed15f1Sopenharmony_ci        else:
67c1ed15f1Sopenharmony_ci            allow_map[(scontext, tcontext)][tclass] += perm
68c1ed15f1Sopenharmony_ci        return
69c1ed15f1Sopenharmony_ci
70c1ed15f1Sopenharmony_ci    for scon in attributes_map[scontext]:
71c1ed15f1Sopenharmony_ci        # allow attribute self
72c1ed15f1Sopenharmony_ci        if tcontext == 'self':
73c1ed15f1Sopenharmony_ci            allow_map[(scon, scon)][tclass] += perm
74c1ed15f1Sopenharmony_ci        # allow attribute attribute
75c1ed15f1Sopenharmony_ci        elif tcontext in attributes_map:
76c1ed15f1Sopenharmony_ci            for tcon in attributes_map[tcontext]:
77c1ed15f1Sopenharmony_ci                allow_map[(scon, tcon)][tclass] += perm
78c1ed15f1Sopenharmony_ci        # allow attribute type
79c1ed15f1Sopenharmony_ci        else:
80c1ed15f1Sopenharmony_ci            allow_map[(scon, tcontext)][tclass] += perm
81c1ed15f1Sopenharmony_ci
82c1ed15f1Sopenharmony_ci
83c1ed15f1Sopenharmony_cidef deal_with_typeattributeset(cil_file, attributes_map):
84c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
85c1ed15f1Sopenharmony_ci        for line in cil_read:
86c1ed15f1Sopenharmony_ci            if not line.startswith('(typeattributeset '):
87c1ed15f1Sopenharmony_ci                continue
88c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
89c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
90c1ed15f1Sopenharmony_ci            if len(elem_list) < 3:
91c1ed15f1Sopenharmony_ci                continue
92c1ed15f1Sopenharmony_ci            attributes_map[elem_list[1]] += elem_list[2:]
93c1ed15f1Sopenharmony_ci
94c1ed15f1Sopenharmony_ci
95c1ed15f1Sopenharmony_cidef deal_with_class(cil_file, class_map):
96c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
97c1ed15f1Sopenharmony_ci        common_map = defaultdict(list)
98c1ed15f1Sopenharmony_ci        for line in cil_read:
99c1ed15f1Sopenharmony_ci            if not line.startswith('(common '):
100c1ed15f1Sopenharmony_ci                continue
101c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
102c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
103c1ed15f1Sopenharmony_ci            if len(elem_list) < 3:
104c1ed15f1Sopenharmony_ci                continue
105c1ed15f1Sopenharmony_ci            common_map[elem_list[1]] += elem_list[2:]
106c1ed15f1Sopenharmony_ci
107c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
108c1ed15f1Sopenharmony_ci        for line in cil_read:
109c1ed15f1Sopenharmony_ci            if not line.startswith('(class '):
110c1ed15f1Sopenharmony_ci                continue
111c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
112c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
113c1ed15f1Sopenharmony_ci            if len(elem_list) > 2:
114c1ed15f1Sopenharmony_ci                class_map[elem_list[1]] += elem_list[2:]
115c1ed15f1Sopenharmony_ci
116c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
117c1ed15f1Sopenharmony_ci        for line in cil_read:
118c1ed15f1Sopenharmony_ci            if not line.startswith('(classcommon '):
119c1ed15f1Sopenharmony_ci                continue
120c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
121c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
122c1ed15f1Sopenharmony_ci            if len(elem_list) < 3:
123c1ed15f1Sopenharmony_ci                continue
124c1ed15f1Sopenharmony_ci            class_map[elem_list[1]] += common_map[elem_list[2]]
125c1ed15f1Sopenharmony_ci
126c1ed15f1Sopenharmony_ci
127c1ed15f1Sopenharmony_cidef generate_database(cil_file):
128c1ed15f1Sopenharmony_ci    attributes_map = defaultdict(list)
129c1ed15f1Sopenharmony_ci    class_map = defaultdict(list)
130c1ed15f1Sopenharmony_ci    allow_map = defaultdict(lambda: defaultdict(list))
131c1ed15f1Sopenharmony_ci    deal_with_typeattributeset(cil_file, attributes_map)
132c1ed15f1Sopenharmony_ci    deal_with_allow(cil_file, allow_map, attributes_map)
133c1ed15f1Sopenharmony_ci    deal_with_class(cil_file, class_map)
134c1ed15f1Sopenharmony_ci    return PolicyDb(attributes_map, allow_map, class_map)
135c1ed15f1Sopenharmony_ci
136c1ed15f1Sopenharmony_ci
137c1ed15f1Sopenharmony_ciclass CheckPermGroup(object):
138c1ed15f1Sopenharmony_ci    def __init__(self, check_class_list, check_perms):
139c1ed15f1Sopenharmony_ci        self.check_class_list = check_class_list
140c1ed15f1Sopenharmony_ci        self.check_perms = check_perms
141c1ed15f1Sopenharmony_ci
142c1ed15f1Sopenharmony_ci
143c1ed15f1Sopenharmony_cidef get_check_class_list(check_tclass, check_perms, class_map):
144c1ed15f1Sopenharmony_ci    if check_tclass == '*':
145c1ed15f1Sopenharmony_ci        check_class_list = []
146c1ed15f1Sopenharmony_ci        for tclass in class_map.keys():
147c1ed15f1Sopenharmony_ci            if set(check_perms) <= set(class_map[tclass]):
148c1ed15f1Sopenharmony_ci                check_class_list.append(tclass)
149c1ed15f1Sopenharmony_ci        return check_class_list
150c1ed15f1Sopenharmony_ci    return [check_tclass]
151c1ed15f1Sopenharmony_ci
152c1ed15f1Sopenharmony_ci
153c1ed15f1Sopenharmony_cidef get_perm_group_list(rule, class_map):
154c1ed15f1Sopenharmony_ci    check_perm_group_list = []
155c1ed15f1Sopenharmony_ci    perm_group_list = rule.get('perm_group')
156c1ed15f1Sopenharmony_ci    for perm_group in perm_group_list:
157c1ed15f1Sopenharmony_ci        check_tclass = perm_group.get('tclass')
158c1ed15f1Sopenharmony_ci        check_perms = perm_group.get('perm').split(' ')
159c1ed15f1Sopenharmony_ci        check_class_list = get_check_class_list(check_tclass, check_perms, class_map)
160c1ed15f1Sopenharmony_ci        check_perm_group_list.append(CheckPermGroup(check_class_list, check_perms))
161c1ed15f1Sopenharmony_ci    return check_perm_group_list
162c1ed15f1Sopenharmony_ci
163c1ed15f1Sopenharmony_ci
164c1ed15f1Sopenharmony_cidef get_whitelist(args, check_name, with_developer):
165c1ed15f1Sopenharmony_ci    whitelist_file_list = traverse_file_in_each_type(args.policy_dir_list, WHITELIST_FILE_NAME)
166c1ed15f1Sopenharmony_ci    contexts_list = []
167c1ed15f1Sopenharmony_ci    for path in whitelist_file_list:
168c1ed15f1Sopenharmony_ci        white_list = read_json_file(path).get('whitelist')
169c1ed15f1Sopenharmony_ci        for item in white_list:
170c1ed15f1Sopenharmony_ci            if item.get('name') != check_name:
171c1ed15f1Sopenharmony_ci                continue
172c1ed15f1Sopenharmony_ci            contexts_list.extend(item.get('user'))
173c1ed15f1Sopenharmony_ci            if with_developer:
174c1ed15f1Sopenharmony_ci                contexts_list.extend(item.get('developer'))
175c1ed15f1Sopenharmony_ci    return contexts_list
176c1ed15f1Sopenharmony_ci
177c1ed15f1Sopenharmony_ci
178c1ed15f1Sopenharmony_cidef check_perm_group(args, rule, policy_db, with_developer):
179c1ed15f1Sopenharmony_ci    check_name = rule.get('name')
180c1ed15f1Sopenharmony_ci    check_perm_group_list = get_perm_group_list(rule, policy_db.class_map)
181c1ed15f1Sopenharmony_ci    contexts_list = get_whitelist(args, check_name, with_developer)
182c1ed15f1Sopenharmony_ci
183c1ed15f1Sopenharmony_ci    non_exempt_violator_list = []
184c1ed15f1Sopenharmony_ci    violator_list = []
185c1ed15f1Sopenharmony_ci    for contexts in policy_db.allow_map.keys():
186c1ed15f1Sopenharmony_ci        check_result = 0
187c1ed15f1Sopenharmony_ci        for perm_group in check_perm_group_list:
188c1ed15f1Sopenharmony_ci            check_success = False
189c1ed15f1Sopenharmony_ci            for check_class in perm_group.check_class_list:
190c1ed15f1Sopenharmony_ci                check_success |= (set(perm_group.check_perms) <= set(policy_db.allow_map[contexts][check_class]))
191c1ed15f1Sopenharmony_ci            if check_success:
192c1ed15f1Sopenharmony_ci                check_result += 1
193c1ed15f1Sopenharmony_ci        if check_result != len(check_perm_group_list):
194c1ed15f1Sopenharmony_ci            continue
195c1ed15f1Sopenharmony_ci        violater = ' '.join(contexts)
196c1ed15f1Sopenharmony_ci        # all violation list
197c1ed15f1Sopenharmony_ci        violator_list.append(violater)
198c1ed15f1Sopenharmony_ci        # if not in whitelist
199c1ed15f1Sopenharmony_ci        if violater not in contexts_list:
200c1ed15f1Sopenharmony_ci            non_exempt_violator_list.append(violater)
201c1ed15f1Sopenharmony_ci
202c1ed15f1Sopenharmony_ci    if len(non_exempt_violator_list):
203c1ed15f1Sopenharmony_ci        print('\tcheck rule \'{}\' in {} mode failed, {}'.format(
204c1ed15f1Sopenharmony_ci            check_name, "developer" if with_developer else "user", rule.get('description')))
205c1ed15f1Sopenharmony_ci        print('\tviolation list (scontext tcontext):')
206c1ed15f1Sopenharmony_ci        for violation in non_exempt_violator_list:
207c1ed15f1Sopenharmony_ci            print('\t\t{}'.format(violation))
208c1ed15f1Sopenharmony_ci        print('\tThere are two solutions:\n',
209c1ed15f1Sopenharmony_ci              '\t1. Add the above list to whitelist file \'{}\' under \'{}\' in \'{}\' part of \'{}\'\n'.format(
210c1ed15f1Sopenharmony_ci                    WHITELIST_FILE_NAME, args.policy_dir_list, "developer" if with_developer else "user", check_name),
211c1ed15f1Sopenharmony_ci              '\t2. Change the policy to avoid violating rule \'{}\'\n'.format(check_name))
212c1ed15f1Sopenharmony_ci        return True
213c1ed15f1Sopenharmony_ci
214c1ed15f1Sopenharmony_ci    diff_list = list(set(contexts_list) - set(violator_list))
215c1ed15f1Sopenharmony_ci    if len(diff_list):
216c1ed15f1Sopenharmony_ci        print('\tcheck rule \'{}\' failed in whitelist file \'{}\'\n'.format(check_name, WHITELIST_FILE_NAME),
217c1ed15f1Sopenharmony_ci              '\tremove the following unnecessary whitelists in rule \'{}\' part \'{}\':'.format(
218c1ed15f1Sopenharmony_ci                    check_name, 'developer' if with_developer else 'user'))
219c1ed15f1Sopenharmony_ci        for diff in diff_list:
220c1ed15f1Sopenharmony_ci            print('\t\t{}'.format(diff))
221c1ed15f1Sopenharmony_ci        return True
222c1ed15f1Sopenharmony_ci    return False
223c1ed15f1Sopenharmony_ci
224c1ed15f1Sopenharmony_ci
225c1ed15f1Sopenharmony_cidef parse_args():
226c1ed15f1Sopenharmony_ci    parser = argparse.ArgumentParser()
227c1ed15f1Sopenharmony_ci    parser.add_argument(
228c1ed15f1Sopenharmony_ci        '--cil_file', help='the cil file path', required=True)
229c1ed15f1Sopenharmony_ci    parser.add_argument(
230c1ed15f1Sopenharmony_ci        '--developer_cil_file', help='the developer cil file path', required=True)
231c1ed15f1Sopenharmony_ci    parser.add_argument(
232c1ed15f1Sopenharmony_ci        '--policy-dir-list', help='the whitelist path list', required=True)
233c1ed15f1Sopenharmony_ci    parser.add_argument(
234c1ed15f1Sopenharmony_ci        '--config', help='the config file path', required=True)
235c1ed15f1Sopenharmony_ci    return parser.parse_args()
236c1ed15f1Sopenharmony_ci
237c1ed15f1Sopenharmony_ci
238c1ed15f1Sopenharmony_ciif __name__ == '__main__':
239c1ed15f1Sopenharmony_ci    input_args = parse_args()
240c1ed15f1Sopenharmony_ci    script_path = os.path.dirname(os.path.realpath(__file__))
241c1ed15f1Sopenharmony_ci
242c1ed15f1Sopenharmony_ci    user_policy_db = generate_database(input_args.cil_file)
243c1ed15f1Sopenharmony_ci    developer_policy_db = generate_database(input_args.developer_cil_file)
244c1ed15f1Sopenharmony_ci    check_rules = read_json_file(os.path.join(script_path, input_args.config)).get('check_rules')
245c1ed15f1Sopenharmony_ci    result = False
246c1ed15f1Sopenharmony_ci    for check_rule in check_rules:
247c1ed15f1Sopenharmony_ci        result |= check_perm_group(input_args, check_rule, user_policy_db, False)
248c1ed15f1Sopenharmony_ci        result |= check_perm_group(input_args, check_rule, developer_policy_db, True)
249c1ed15f1Sopenharmony_ci    if result:
250c1ed15f1Sopenharmony_ci        raise Exception(-1)
251