1#!/usr/bin/env python
2# coding: utf-8
3
4"""
5Copyright (c) 2024 Huawei Device Co., Ltd.
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17
18"""
19
20import argparse
21import os
22from check_common import read_json_file, traverse_file_in_each_type
23
24WHITELIST_FILE_NAME = "ioctl_xperm_whitelist.json"
25
26ALLOW_TCONTEXT_CLASS_LIST = ["data_log_sanitizer_file file", "proc_attr dir", "self dir", "self fifo_file",
27                     "self file", "self lnk_file", "self unix_stream_socket"]
28
29
30class PolicyDb(object):
31    def __init__(self, allowx_set, allow_set, typetransition_set):
32        self.allowx_set = allowx_set
33        self.allow_set = allow_set
34        self.typetransition_set = typetransition_set
35
36
37def simplify_string(string):
38    return string.strip().replace('(', '').replace(')', '')
39
40
41def split_allow_rule(elem_list, allow_set, allowx_set):
42    if len(elem_list) < 5:
43        print("not an allow/allowx rule: {}".format(elem_list))
44        return
45    rulename = elem_list[0]
46    scontext = elem_list[1]
47    tcontext = elem_list[2]
48    tclass = elem_list[3]
49    if rulename == 'allow' and 'ioctl' in elem_list[4:]:
50        keycontent = f'{scontext} {tcontext} {tclass}'
51        allow_set.add(keycontent)
52    if rulename == 'allowx' and 'ioctl' == tclass:
53        keycontent = f'{scontext} {tcontext} {elem_list[4]}'
54        allowx_set.add(keycontent)
55
56
57def split_typetransition(elem_list, typetransition_set):
58    if len(elem_list) < 5:
59        print("not a typetransition rule: {}".format(elem_list))
60        return
61    rulename = elem_list[0]
62    source_t = elem_list[1]
63    target_t = elem_list[2]
64    tclass = elem_list[3]
65    default_t = elem_list[4]
66    if tclass == 'process':
67        keycontent = f'{source_t} {target_t} file'
68        typetransition_set.add(keycontent)
69
70
71def deal_with_allow(cil_file, allow_set, allowx_set, typetransition_set):
72    with open(cil_file, 'r', encoding='utf-8') as cil_read:
73        for line in cil_read:
74            if line.startswith('(typetransition '):
75                # (typetransition A B process C)
76                sub_string = simplify_string(line)
77                elem_list = sub_string.split(' ')
78                split_typetransition(elem_list, typetransition_set)
79
80            if line.startswith('(allow ') or line.startswith('(allowx '):
81                sub_string = simplify_string(line)
82                elem_list = sub_string.split(' ')
83                # (allow A B (file (ioctl x x x)))
84                # (allowx A B (ioctl file (x x x)))
85                split_allow_rule(elem_list, allow_set, allowx_set)
86
87
88def generate_database(args, with_developer):
89    allowx_set = set()
90    allow_set = set()
91    typetransition_set = set()
92    if with_developer:
93        deal_with_allow(args.developer_cil_file, allow_set, allowx_set, typetransition_set)
94    else:
95        deal_with_allow(args.cil_file, allow_set, allowx_set, typetransition_set)
96
97    return PolicyDb(allowx_set, allow_set, typetransition_set)
98
99
100def get_whitelist(args, with_developer):
101    whitelist_file_list = traverse_file_in_each_type(args.policy_dir_list, WHITELIST_FILE_NAME)
102    contexts_list = []
103    for path in whitelist_file_list:
104        white_list = read_json_file(path).get('whitelist')
105        contexts_list.extend(white_list.get('user'))
106        if with_developer:
107            contexts_list.extend(white_list.get('developer'))
108    return contexts_list
109
110
111def check(args, with_developer):
112    policy_db = generate_database(args, with_developer)
113    contexts_list = get_whitelist(args, with_developer)
114    diff_set = policy_db.allow_set - policy_db.allowx_set - policy_db.typetransition_set - set(contexts_list)
115    notallow = list()
116    for diff in diff_set:
117        if not (diff.endswith(tuple(ALLOW_TCONTEXT_CLASS_LIST))) :
118            notallow.append(diff)
119
120    if len(notallow) > 0 :
121        print('check ioctl rule in {} mode failed.'.format("developer" if with_developer else "user"))
122        print('violation list (allow scontext tcontext:tclass ioctl)')
123        for e in sorted(notallow):
124            elem_list = e.split(' ')
125            print('\tallow {} ioctl;'.format(elem_list[0] + ' ' + elem_list[1] + ':' + elem_list[2]))
126        print('please add "allowxperm" rule based on the above list.')
127    return len(notallow) > 0
128
129
130def parse_args():
131    parser = argparse.ArgumentParser()
132    parser.add_argument('--cil_file', help='the cil file path', required=True)
133    parser.add_argument('--developer_cil_file', help='the developer cil file path', required=True)
134    parser.add_argument('--policy-dir-list', help='policy dirs need to be included', required=True)
135
136    return parser.parse_args()
137
138
139if __name__ == '__main__':
140    input_args = parse_args()
141    print("check xperm input_args: {}".format(input_args))
142    result = check(input_args, False)
143    if result:
144        raise Exception(-1)
145    result = check(input_args, True)
146    if result:
147        raise Exception(-1)
148
149