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