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_ciimport subprocess
24c1ed15f1Sopenharmony_cifrom check_common import read_json_file, traverse_file_in_each_type
25c1ed15f1Sopenharmony_ci
26c1ed15f1Sopenharmony_ciBASELINE_SUFFIX = ".baseline"
27c1ed15f1Sopenharmony_ci
28c1ed15f1Sopenharmony_ci
29c1ed15f1Sopenharmony_ciclass PolicyDb(object):
30c1ed15f1Sopenharmony_ci    def __init__(self, attributes_map, allow_map, class_map):
31c1ed15f1Sopenharmony_ci        self.attributes_map = attributes_map
32c1ed15f1Sopenharmony_ci        self.allow_map = allow_map
33c1ed15f1Sopenharmony_ci        self.class_map = class_map
34c1ed15f1Sopenharmony_ci
35c1ed15f1Sopenharmony_ci
36c1ed15f1Sopenharmony_cidef simplify_string(string):
37c1ed15f1Sopenharmony_ci    return string.replace('(', '').replace(')', '').replace('\n', '').strip()
38c1ed15f1Sopenharmony_ci
39c1ed15f1Sopenharmony_ci
40c1ed15f1Sopenharmony_cidef deal_with_allow(cil_file, allow_map, attributes_map):
41c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
42c1ed15f1Sopenharmony_ci        for line in cil_read:
43c1ed15f1Sopenharmony_ci            line = line.strip()
44c1ed15f1Sopenharmony_ci            if not line.startswith('(allow ') and not line.startswith('(auditallow '):
45c1ed15f1Sopenharmony_ci                continue
46c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
47c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
48c1ed15f1Sopenharmony_ci            # (allow A B (dir (getattr)))
49c1ed15f1Sopenharmony_ci            if len(elem_list) < 5:
50c1ed15f1Sopenharmony_ci                continue
51c1ed15f1Sopenharmony_ci            split_attribute(elem_list, allow_map, attributes_map)
52c1ed15f1Sopenharmony_ci
53c1ed15f1Sopenharmony_ci
54c1ed15f1Sopenharmony_cidef split_attribute(elem_list, allow_map, attributes_map):
55c1ed15f1Sopenharmony_ci    scontext = elem_list[1]
56c1ed15f1Sopenharmony_ci    tcontext = elem_list[2]
57c1ed15f1Sopenharmony_ci    tclass = elem_list[3]
58c1ed15f1Sopenharmony_ci    perm = elem_list[4:]
59c1ed15f1Sopenharmony_ci    if scontext not in attributes_map:
60c1ed15f1Sopenharmony_ci        # allow type self
61c1ed15f1Sopenharmony_ci        if tcontext == 'self':
62c1ed15f1Sopenharmony_ci            allow_map[scontext][(scontext, tclass)] += perm
63c1ed15f1Sopenharmony_ci        # allow type attribute
64c1ed15f1Sopenharmony_ci        elif tcontext in attributes_map:
65c1ed15f1Sopenharmony_ci            for tcon in attributes_map[tcontext]:
66c1ed15f1Sopenharmony_ci                allow_map[scontext][(tcon, tclass)] += perm
67c1ed15f1Sopenharmony_ci        # allow type type
68c1ed15f1Sopenharmony_ci        else:
69c1ed15f1Sopenharmony_ci            allow_map[scontext][(tcontext, tclass)] += perm
70c1ed15f1Sopenharmony_ci        return
71c1ed15f1Sopenharmony_ci
72c1ed15f1Sopenharmony_ci    for scon in attributes_map[scontext]:
73c1ed15f1Sopenharmony_ci        # allow attribute self
74c1ed15f1Sopenharmony_ci        if tcontext == 'self':
75c1ed15f1Sopenharmony_ci            allow_map[scon][(scon, tclass)] += perm
76c1ed15f1Sopenharmony_ci        # allow attribute attribute
77c1ed15f1Sopenharmony_ci        elif tcontext in attributes_map:
78c1ed15f1Sopenharmony_ci            for tcon in attributes_map[tcontext]:
79c1ed15f1Sopenharmony_ci                allow_map[scon][(tcon, tclass)] += perm
80c1ed15f1Sopenharmony_ci        # allow attribute type
81c1ed15f1Sopenharmony_ci        else:
82c1ed15f1Sopenharmony_ci            allow_map[scon][(tcontext, tclass)] += perm
83c1ed15f1Sopenharmony_ci
84c1ed15f1Sopenharmony_ci
85c1ed15f1Sopenharmony_cidef deal_with_typeattributeset(cil_file, attributes_map):
86c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
87c1ed15f1Sopenharmony_ci        for line in cil_read:
88c1ed15f1Sopenharmony_ci            if not line.startswith('(typeattributeset '):
89c1ed15f1Sopenharmony_ci                continue
90c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
91c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
92c1ed15f1Sopenharmony_ci            if len(elem_list) < 3:
93c1ed15f1Sopenharmony_ci                continue
94c1ed15f1Sopenharmony_ci            attributes_map[elem_list[1]] += elem_list[2:]
95c1ed15f1Sopenharmony_ci
96c1ed15f1Sopenharmony_ci
97c1ed15f1Sopenharmony_cidef deal_with_class(cil_file, class_map):
98c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
99c1ed15f1Sopenharmony_ci        common_map = defaultdict(list)
100c1ed15f1Sopenharmony_ci        for line in cil_read:
101c1ed15f1Sopenharmony_ci            if not line.startswith('(common '):
102c1ed15f1Sopenharmony_ci                continue
103c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
104c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
105c1ed15f1Sopenharmony_ci            if len(elem_list) < 3:
106c1ed15f1Sopenharmony_ci                continue
107c1ed15f1Sopenharmony_ci            common_map[elem_list[1]] += elem_list[2:]
108c1ed15f1Sopenharmony_ci
109c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
110c1ed15f1Sopenharmony_ci        for line in cil_read:
111c1ed15f1Sopenharmony_ci            if not line.startswith('(class '):
112c1ed15f1Sopenharmony_ci                continue
113c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
114c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
115c1ed15f1Sopenharmony_ci            if len(elem_list) > 2:
116c1ed15f1Sopenharmony_ci                class_map[elem_list[1]] += elem_list[2:]
117c1ed15f1Sopenharmony_ci
118c1ed15f1Sopenharmony_ci    with open(cil_file, 'r', encoding='utf-8') as cil_read:
119c1ed15f1Sopenharmony_ci        for line in cil_read:
120c1ed15f1Sopenharmony_ci            if not line.startswith('(classcommon '):
121c1ed15f1Sopenharmony_ci                continue
122c1ed15f1Sopenharmony_ci            sub_string = simplify_string(line)
123c1ed15f1Sopenharmony_ci            elem_list = sub_string.split(' ')
124c1ed15f1Sopenharmony_ci            if len(elem_list) < 3:
125c1ed15f1Sopenharmony_ci                continue
126c1ed15f1Sopenharmony_ci            class_map[elem_list[1]] += common_map[elem_list[2]]
127c1ed15f1Sopenharmony_ci
128c1ed15f1Sopenharmony_ci
129c1ed15f1Sopenharmony_cidef generate_database(cil_file):
130c1ed15f1Sopenharmony_ci    attributes_map = defaultdict(list)
131c1ed15f1Sopenharmony_ci    class_map = defaultdict(list)
132c1ed15f1Sopenharmony_ci    allow_map = defaultdict(lambda: defaultdict(list))
133c1ed15f1Sopenharmony_ci    deal_with_typeattributeset(cil_file, attributes_map)
134c1ed15f1Sopenharmony_ci    deal_with_allow(cil_file, allow_map, attributes_map)
135c1ed15f1Sopenharmony_ci    deal_with_class(cil_file, class_map)
136c1ed15f1Sopenharmony_ci    return PolicyDb(attributes_map, allow_map, class_map)
137c1ed15f1Sopenharmony_ci
138c1ed15f1Sopenharmony_ci
139c1ed15f1Sopenharmony_cidef build_conf(output_conf, file_list, with_developer=False):
140c1ed15f1Sopenharmony_ci    m4_args = []
141c1ed15f1Sopenharmony_ci    if with_developer:
142c1ed15f1Sopenharmony_ci        m4_args += ["-D", "build_with_developer=enable"]
143c1ed15f1Sopenharmony_ci    build_conf_cmd = ["m4", "-s", "--fatal-warnings"] + m4_args + file_list
144c1ed15f1Sopenharmony_ci    with open(output_conf, 'w', encoding="utf-8") as fd:
145c1ed15f1Sopenharmony_ci        ret = subprocess.run(build_conf_cmd, shell=False, stdout=fd).returncode
146c1ed15f1Sopenharmony_ci        if ret != 0:
147c1ed15f1Sopenharmony_ci            raise Exception(ret)
148c1ed15f1Sopenharmony_ci
149c1ed15f1Sopenharmony_ci
150c1ed15f1Sopenharmony_cidef get_baseline_file(args):
151c1ed15f1Sopenharmony_ci    script_path = os.path.dirname(os.path.realpath(__file__))
152c1ed15f1Sopenharmony_ci    baseline_file_list = [os.path.join(script_path, "config/glb_def.txt")]
153c1ed15f1Sopenharmony_ci    baseline_file_list += traverse_file_in_each_type(args.policy_dir_list, BASELINE_SUFFIX)
154c1ed15f1Sopenharmony_ci    return baseline_file_list
155c1ed15f1Sopenharmony_ci
156c1ed15f1Sopenharmony_ci
157c1ed15f1Sopenharmony_cidef generate_baseline_database(args, domain, attributes_map, with_developer):
158c1ed15f1Sopenharmony_ci    baseline_file_list = get_baseline_file(args)
159c1ed15f1Sopenharmony_ci    output_path = os.path.dirname(os.path.realpath(args.developer_cil_file if with_developer else args.cil_file))
160c1ed15f1Sopenharmony_ci    output_baseline = os.path.join(output_path, domain + BASELINE_SUFFIX)
161c1ed15f1Sopenharmony_ci    build_conf(output_baseline, baseline_file_list, with_developer)
162c1ed15f1Sopenharmony_ci    baseline_map = defaultdict(lambda: defaultdict(list))
163c1ed15f1Sopenharmony_ci    deal_with_allow(output_baseline, baseline_map, attributes_map)
164c1ed15f1Sopenharmony_ci    return baseline_map[domain]
165c1ed15f1Sopenharmony_ci
166c1ed15f1Sopenharmony_ci
167c1ed15f1Sopenharmony_cidef check_baseline(args, domain, policy_db, with_developer):
168c1ed15f1Sopenharmony_ci    baseline_map = generate_baseline_database(args, domain, policy_db.attributes_map, with_developer)
169c1ed15f1Sopenharmony_ci    none_baseline_list = set()
170c1ed15f1Sopenharmony_ci    domain_policy = policy_db.allow_map[domain]
171c1ed15f1Sopenharmony_ci    baseline_diff = domain_policy.keys() ^ baseline_map.keys()
172c1ed15f1Sopenharmony_ci    for diff in baseline_diff:
173c1ed15f1Sopenharmony_ci        expect_perm = ''.join(['(', ' '.join(set(baseline_map.get(diff, ''))), ')))'])
174c1ed15f1Sopenharmony_ci        expect = ' '.join(['expect rule: (allow', domain, ' ('.join(diff), expect_perm])
175c1ed15f1Sopenharmony_ci        actual_perm = ''.join(['(', ' '.join(set(domain_policy.get(diff, ''))), ')))'])
176c1ed15f1Sopenharmony_ci        actual = ' '.join(['actual rule: (allow', domain, ' ('.join(diff), actual_perm])
177c1ed15f1Sopenharmony_ci        none_baseline_list.add('; '.join([expect, actual]))
178c1ed15f1Sopenharmony_ci
179c1ed15f1Sopenharmony_ci    for contexts in domain_policy.keys():
180c1ed15f1Sopenharmony_ci        if set(baseline_map.get(contexts, '')) != set(domain_policy.get(contexts, '')):
181c1ed15f1Sopenharmony_ci            expect_perm = ''.join(['(', ' '.join(set(baseline_map.get(contexts, ''))), ')))'])
182c1ed15f1Sopenharmony_ci            expect = ' '.join(['expect rule: (allow', domain, ' ('.join(contexts), expect_perm])
183c1ed15f1Sopenharmony_ci            actual_perm = ''.join(['(', ' '.join(set(domain_policy.get(contexts, ''))), ')))'])
184c1ed15f1Sopenharmony_ci            actual = ' '.join(['actual rule: (allow', domain, ' ('.join(contexts), actual_perm])
185c1ed15f1Sopenharmony_ci            none_baseline_list.add('; '.join([expect, actual]))
186c1ed15f1Sopenharmony_ci
187c1ed15f1Sopenharmony_ci    if len(none_baseline_list):
188c1ed15f1Sopenharmony_ci        print('\tcheck \'{}\' baseline in {} mode failed'.format(domain, "developer" if with_developer else "user"))
189c1ed15f1Sopenharmony_ci        for violation in none_baseline_list:
190c1ed15f1Sopenharmony_ci            print('\t\t{}'.format(violation))
191c1ed15f1Sopenharmony_ci        print('\tThere are two solutions:\n',
192c1ed15f1Sopenharmony_ci            '\t1. Add the above actual rule to baseline file \'{}\' under \'{}\'{}\n'.format(
193c1ed15f1Sopenharmony_ci                domain + BASELINE_SUFFIX, args.policy_dir_list, " and add developer_only" if with_developer else ""),
194c1ed15f1Sopenharmony_ci            '\t2. Change the policy to satisfy expect rule\n')
195c1ed15f1Sopenharmony_ci        return True
196c1ed15f1Sopenharmony_ci    return False
197c1ed15f1Sopenharmony_ci
198c1ed15f1Sopenharmony_ci
199c1ed15f1Sopenharmony_cidef parse_args():
200c1ed15f1Sopenharmony_ci    parser = argparse.ArgumentParser()
201c1ed15f1Sopenharmony_ci    parser.add_argument(
202c1ed15f1Sopenharmony_ci        '--cil_file', help='the cil file path', required=True)
203c1ed15f1Sopenharmony_ci    parser.add_argument(
204c1ed15f1Sopenharmony_ci        '--developer_cil_file', help='the developer cil file path', required=True)
205c1ed15f1Sopenharmony_ci    parser.add_argument(
206c1ed15f1Sopenharmony_ci        '--config', help='the config file path', required=True)
207c1ed15f1Sopenharmony_ci    parser.add_argument(
208c1ed15f1Sopenharmony_ci        '--policy-dir-list', help='the policy dir list', required=True)
209c1ed15f1Sopenharmony_ci    return parser.parse_args()
210c1ed15f1Sopenharmony_ci
211c1ed15f1Sopenharmony_ci
212c1ed15f1Sopenharmony_ciif __name__ == '__main__':
213c1ed15f1Sopenharmony_ci    input_args = parse_args()
214c1ed15f1Sopenharmony_ci    script_dir = os.path.dirname(os.path.realpath(__file__))
215c1ed15f1Sopenharmony_ci
216c1ed15f1Sopenharmony_ci    user_policy_db = generate_database(input_args.cil_file)
217c1ed15f1Sopenharmony_ci    developer_policy_db = generate_database(input_args.developer_cil_file)
218c1ed15f1Sopenharmony_ci    baselines = read_json_file(os.path.join(script_dir, input_args.config)).get('baseline')
219c1ed15f1Sopenharmony_ci    check_result = False
220c1ed15f1Sopenharmony_ci    for label_name in baselines:
221c1ed15f1Sopenharmony_ci        check_result |= check_baseline(input_args, label_name, user_policy_db, False)
222c1ed15f1Sopenharmony_ci        check_result |= check_baseline(input_args, label_name, developer_policy_db, True)
223c1ed15f1Sopenharmony_ci    if check_result:
224c1ed15f1Sopenharmony_ci        raise Exception(-1)
225