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