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 os 21import re 22import shutil 23import subprocess 24import tempfile 25 26SYSTEM_CIL_HASH = "system.cil.sha256" 27PREBUILD_SEPOLICY_SYSTEM_CIL_HASH = "prebuild_sepolicy.system.cil.sha256" 28 29# list of all macros and te for sepolicy build 30SEPOLICY_TYPE_LIST = ["security_classes", 31 "initial_sids", 32 "access_vectors", 33 "glb_perm_def.spt", 34 "glb_never_def.spt", 35 "mls", 36 "policy_cap", 37 "glb_te_def.spt", 38 "attributes", 39 ".te", 40 "glb_roles.spt", 41 "users", 42 "initial_sid_contexts", 43 "fs_use", 44 "virtfs_contexts", 45 ] 46 47POLICY_TYPE_LIST = ["allow", "auditallow", "dontaudit", 48 "allowx", "auditallowx", "dontauditx", 49 "neverallow", "neverallowx", ] 50 51 52class PolicyDirList(object): 53 def __init__(self, min_policy_dir_list, system_policy_dir_list, vendor_policy_dir_list, public_policy_dir_list): 54 self.min_policy_dir_list = min_policy_dir_list 55 self.system_policy_dir_list = system_policy_dir_list 56 self.vendor_policy_dir_list = vendor_policy_dir_list 57 self.public_policy_dir_list = public_policy_dir_list 58 59 60class PolicyFileList(object): 61 def __init__(self, min_policy_file_list, system_policy_file_list, vendor_policy_file_list, public_policy_file_list): 62 self.min_policy_file_list = min_policy_file_list 63 self.system_policy_file_list = system_policy_file_list 64 self.vendor_policy_file_list = vendor_policy_file_list 65 self.public_policy_file_list = public_policy_file_list 66 67 68def traverse_folder_in_dir_name(search_dir, folder_suffix): 69 folder_list = [] 70 for root, dirs, _ in os.walk(search_dir): 71 for dir_i in dirs: 72 if dir_i == folder_suffix: 73 folder_list.append(os.path.join(root, dir_i)) 74 return folder_list 75 76 77def traverse_folder_in_type(search_dir, file_suffix, build_root): 78 policy_file_list = [] 79 flag = 0 80 for root, _, files in os.walk(search_dir): 81 for each_file in files: 82 if each_file.endswith(file_suffix): 83 path = os.path.join(root, each_file) 84 rel_path = os.path.relpath(path, build_root) 85 flag |= check_empty_row(rel_path) 86 policy_file_list.append(rel_path) 87 policy_file_list.sort() 88 return policy_file_list, flag 89 90 91def traverse_file_in_each_type(folder_list, sepolicy_type_list, build_root): 92 policy_files_list = [] 93 err = 0 94 for policy_type in sepolicy_type_list: 95 for folder in folder_list: 96 type_file_list, flag = traverse_folder_in_type( 97 folder, policy_type, build_root) 98 err |= flag 99 if len(type_file_list) == 0: 100 continue 101 policy_files_list.extend(type_file_list) 102 if err: 103 raise Exception(err) 104 return policy_files_list 105 106 107def check_empty_row(policy_file): 108 """ 109 Check whether the last line of te file is empty. 110 :param policy_file: te file 111 :return: 112 """ 113 err = 0 114 with open(policy_file, 'r') as fp: 115 lines = fp.readlines() 116 if len(lines) == 0: 117 return 0 118 last_line = lines[-1] 119 if '\n' not in last_line: 120 print("".join([policy_file, " : need an empty line at end\n"])) 121 err = 1 122 return err 123 124 125def run_command(in_cmd): 126 cmdstr = " ".join(in_cmd) 127 ret = subprocess.run(cmdstr, shell=True).returncode 128 if ret != 0: 129 raise Exception(ret) 130 131 132def build_conf(args, output_conf, file_list): 133 m4_args = ["-D", "build_with_debug=" + args.debug_version] 134 m4_args += ["-D", "build_with_updater=" + args.updater_version] 135 build_conf_cmd = ["m4", "-s", "--fatal-warnings"] + m4_args + file_list 136 with open(output_conf, 'w') as fd: 137 ret = subprocess.run(build_conf_cmd, shell=False, stdout=fd).returncode 138 if ret != 0: 139 raise Exception(ret) 140 141 142def build_cil(args, output_cil, input_conf): 143 check_policy_cmd = [os.path.join(args.tool_path, "checkpolicy"), 144 input_conf, 145 "-M -C -c 31", 146 "-o " + output_cil] 147 run_command(check_policy_cmd) 148 149 150def add_version(version, string): 151 return "".join([string, "_", version]) 152 153 154def simplify_string(string): 155 return string.replace('(', '').replace(')', '').replace('\n', '') 156 157 158def deal_with_roletype(version, cil_write, elem_list, type_set, file, line): 159 if len(elem_list) < 3: 160 print('Error: invalid roletype in %s:%d' % (file, line)) 161 raise Exception(1) 162 163 sub_string = simplify_string(elem_list[2]) 164 if sub_string in type_set: 165 cil_write.write('(typeattribute ' + add_version(version, sub_string) + ')\n') 166 elem_list[2] = elem_list[2].replace( 167 sub_string, add_version(version, sub_string)) 168 cil_write.write(" ".join(elem_list)) 169 170 171def deal_with_typeattribute(version, cil_write, elem_list, type_set, file, line): 172 if len(elem_list) < 2: 173 print('Error: invalid typeattribute in %s:%d' % (file, line)) 174 raise Exception(1) 175 176 sub_string = simplify_string(elem_list[1]) 177 if sub_string.startswith("base_typeattr_"): 178 elem_list[1] = elem_list[1].replace( 179 sub_string, add_version(version, sub_string)) 180 cil_write.write(" ".join(elem_list)) 181 182 183def deal_with_typeattributeset(version, cil_write, elem_list, type_set, file, line): 184 if len(elem_list) < 2: 185 print('Error: invalid typeattributeset in %s:%d' % (file, line)) 186 raise Exception(1) 187 188 for index, elem in enumerate(elem_list[1:]): 189 sub_string = simplify_string(elem) 190 if sub_string.startswith("base_typeattr_") or sub_string in type_set: 191 elem_list[index + 1] = elem.replace(sub_string, add_version(version, sub_string)) 192 cil_write.write(" ".join(elem_list)) 193 194 195def deal_with_policy(version, cil_write, elem_list, type_set, file, line): 196 if len(elem_list) < 4: 197 print('Error: invalid policy in %s:%d' % (file, line)) 198 raise Exception(1) 199 200 for index, elem in enumerate(elem_list[1:3]): 201 sub_string = simplify_string(elem) 202 if sub_string.startswith("base_typeattr_") or sub_string in type_set: 203 elem_list[index + 1] = elem.replace(sub_string, add_version(version, sub_string)) 204 cil_write.write(" ".join(elem_list)) 205 206 207def deal_with_type(version, cil_write, elem_list, file, line): 208 if len(elem_list) < 2: 209 print('Error: invalid type in %s:%d' % (file, line)) 210 raise Exception(1) 211 212 sub_string = simplify_string(elem_list[1]) 213 cil_write.write(" ".join(['(typeattributeset', add_version(version, sub_string), '(', sub_string, '))\n'])) 214 cil_write.write(" ".join(['(expandtypeattribute', '(', add_version(version, sub_string), ') true)\n'])) 215 cil_write.write(" ".join(['(typeattribute', add_version(version, sub_string), ')\n'])) 216 217 218def build_version_cil(version, cil_file_input, cil_file_output, type_set): 219 index = 0 220 with open(cil_file_input, 'r') as cil_read, open(cil_file_output, 'w') as cil_write: 221 for line in cil_read: 222 index += 1 223 if not line.startswith('('): 224 continue 225 226 elem_list = line.split(' ') 227 if not elem_list: 228 continue 229 230 if elem_list[0] == '(type': 231 cil_write.write(line) 232 elif elem_list[0] == '(roletype': 233 deal_with_roletype(version, cil_write, elem_list, type_set, cil_file_input, line) 234 elif elem_list[0] == '(typeattribute': 235 deal_with_typeattribute(version, cil_write, elem_list, type_set, cil_file_input, line) 236 elif elem_list[0] == '(typeattributeset': 237 deal_with_typeattributeset(version, cil_write, elem_list, type_set, cil_file_input, line) 238 elif simplify_string(elem_list[0]) in POLICY_TYPE_LIST: 239 deal_with_policy(version, cil_write, elem_list, type_set, cil_file_input, line) 240 else: 241 cil_write.write(line) 242 243 244def build_type_version_cil(version, cil_file_input, cil_file_output): 245 index = 0 246 with open(cil_file_input, 'r') as cil_read, open(cil_file_output, 'w') as cil_write: 247 for line in cil_read: 248 index += 1 249 if not line.startswith('('): 250 continue 251 252 elem_list = line.split(' ') 253 if not elem_list: 254 continue 255 256 if elem_list[0] == '(type': 257 deal_with_type(version, cil_write, elem_list, line, index) 258 259 260def get_type_set(cil_file_input): 261 pattern_type = re.compile(r'^\(type (.*)\)$') 262 pattern_typeattribute = re.compile(r'^\(type_attribute (base_typeattr_[0-9]+)\)$') 263 type_set = set() 264 with open(cil_file_input, 'r') as cil_read: 265 for line in cil_read: 266 match_type = pattern_type.match(line) 267 match_typeattribute = pattern_typeattribute.match(line) 268 if match_type: 269 type_set.add(match_type.group(1)) 270 elif match_typeattribute: 271 type_set.add(match_typeattribute.group(1)) 272 return type_set 273 274 275def build_binary_policy(tool_path, output_policy, check_neverallow, cil_list): 276 build_policy_cmd = [os.path.join(tool_path, "secilc"), 277 " ".join(cil_list), 278 "-m -M true -G -c 31", 279 "-f /dev/null", 280 "-o " + output_policy] 281 if not check_neverallow: 282 build_policy_cmd.append("-N") 283 run_command(build_policy_cmd) 284 285 286def prepare_build_path(dir_list, root_dir, build_dir_list, sepolicy_path): 287 build_policy_list = [os.path.join(sepolicy_path, "base"), os.path.join(sepolicy_path, "ohos_policy")] 288 build_policy_list += dir_list.split(":") 289 290 for i in build_policy_list: 291 if i == "" or i == "default": 292 continue 293 path = os.path.join(root_dir, i) 294 if (os.path.exists(path)): 295 build_dir_list.append(path) 296 else: 297 print("following path not exists!! {}".format(path)) 298 exit(-1) 299 300 301def get_policy_dir_list(args): 302 sepolicy_path = os.path.join(args.source_root_dir, "base/security/selinux_adapter/sepolicy/") 303 dir_list = [] 304 prepare_build_path(args.policy_dir_list, args.source_root_dir, dir_list, sepolicy_path) 305 min_policy_dir_list = [os.path.join(sepolicy_path, "min")] 306 system_policy = [] 307 public_policy = [] 308 vendor_policy = [] 309 310 for item in dir_list: 311 public_policy += traverse_folder_in_dir_name(item, "public") 312 system_policy += traverse_folder_in_dir_name(item, "system") 313 vendor_policy += traverse_folder_in_dir_name(item, "vendor") 314 315 # list of all policy folders 316 system_policy_dir_list = public_policy + system_policy 317 vendor_policy_dir_list = public_policy + vendor_policy + min_policy_dir_list 318 public_policy_dir_list = public_policy + min_policy_dir_list 319 320 # add temp dirs base/te folders 321 system_policy_dir_list.append(os.path.join(sepolicy_path, "base/te")) 322 vendor_policy_dir_list.append(os.path.join(sepolicy_path, "base/te")) 323 public_policy_dir_list.append(os.path.join(sepolicy_path, "base/te")) 324 325 return PolicyDirList(min_policy_dir_list, system_policy_dir_list, vendor_policy_dir_list, public_policy_dir_list) 326 327 328def get_policy_file_list(args, dir_list_object): 329 build_root = os.path.abspath(os.path.join(args.tool_path, "../../..")) 330 # list of all policy files 331 system_policy_file_list = traverse_file_in_each_type( 332 dir_list_object.system_policy_dir_list, SEPOLICY_TYPE_LIST, build_root) 333 vendor_policy_file_list = traverse_file_in_each_type( 334 dir_list_object.vendor_policy_dir_list, SEPOLICY_TYPE_LIST, build_root) 335 public_policy_file_list = traverse_file_in_each_type( 336 dir_list_object.public_policy_dir_list, SEPOLICY_TYPE_LIST, build_root) 337 min_policy_file_list = traverse_file_in_each_type( 338 dir_list_object.min_policy_dir_list, SEPOLICY_TYPE_LIST, build_root) 339 340 return PolicyFileList(min_policy_file_list, system_policy_file_list, vendor_policy_file_list, 341 public_policy_file_list) 342 343 344def filter_out(pattern_file, input_file): 345 patterns = [] 346 with open(pattern_file, 'r') as pat_file: 347 patterns.extend(pat_file.readlines()) 348 349 tmp_output = tempfile.NamedTemporaryFile() 350 with open(input_file, 'r') as in_file: 351 tmp_output.writelines(line.encode(encoding='utf-8') for line in in_file.readlines() 352 if line not in patterns) 353 tmp_output.write("\n".encode(encoding='utf-8')) 354 tmp_output.flush() 355 shutil.copyfile(tmp_output.name, input_file) 356 357 358def generate_hash_file(input_file_list, output_file): 359 build_policy_cmd = ["cat", 360 " ".join(input_file_list), 361 "| sha256sum", 362 "| cut -d' ' -f1", 363 ">", 364 output_file] 365 run_command(build_policy_cmd) 366 367 368def generate_version_file(args, output_file): 369 cmd = ["echo", args.vendor_policy_version, 370 ">", output_file] 371 run_command(cmd) 372 373 374def generate_default_policy(args, system_policy_file_list, vendor_policy_file_list, min_policy_file_list): 375 output_path = os.path.abspath(os.path.dirname(args.dst_file)) 376 system_output_conf = os.path.join(output_path, "system.conf") 377 vendor_output_conf = os.path.join(output_path, "vendor.conf") 378 min_output_conf = os.path.join(output_path, "min.conf") 379 380 system_cil_path = os.path.join(output_path, "system.cil") 381 vendor_cil_path = os.path.join(output_path, "vendor.cil") 382 min_cil_path = os.path.join(output_path, "min.cil") 383 384 # build system.conf 385 build_conf(args, system_output_conf, system_policy_file_list) 386 # build system.cil 387 build_cil(args, system_cil_path, system_output_conf) 388 389 # build vendor.conf 390 build_conf(args, vendor_output_conf, vendor_policy_file_list) 391 # build vendor.cil 392 build_cil(args, vendor_cil_path, vendor_output_conf) 393 394 # build min.conf 395 build_conf(args, min_output_conf, min_policy_file_list) 396 # build min.cil 397 build_cil(args, min_cil_path, min_output_conf) 398 399 filter_out(min_cil_path, vendor_cil_path) 400 401 return [vendor_cil_path, system_cil_path] 402 403 404def generate_special_policy(args, system_policy_file_list, vendor_policy_file_list, public_policy_file_list, 405 min_policy_file_list): 406 output_path = os.path.abspath(os.path.dirname(args.dst_file)) 407 system_output_conf = os.path.join(output_path, "system.conf") 408 vendor_output_conf = os.path.join(output_path, "vendor.conf") 409 public_output_conf = os.path.join(output_path, "public.conf") 410 min_output_conf = os.path.join(output_path, "min.conf") 411 412 vendor_origin_cil_path = os.path.join(output_path, "vendor_origin.cil") 413 public_origin_cil_path = os.path.join(output_path, "public_origin.cil") 414 min_cil_path = os.path.join(output_path, "min.cil") 415 416 # output file 417 system_cil_path = os.path.join(output_path, "system.cil") 418 vendor_cil_path = os.path.join(output_path, "vendor.cil") 419 public_version_cil_path = os.path.join(output_path, "public.cil") 420 type_version_cil_path = os.path.join(output_path, "".join([args.vendor_policy_version, ".cil"])) 421 422 # build system.conf 423 build_conf(args, system_output_conf, system_policy_file_list) 424 # build system.cil 425 build_cil(args, system_cil_path, system_output_conf) 426 427 # build min.cil 428 build_conf(args, min_output_conf, min_policy_file_list) 429 build_cil(args, min_cil_path, min_output_conf) 430 431 # build public.cil 432 build_conf(args, public_output_conf, public_policy_file_list) 433 build_cil(args, public_origin_cil_path, public_output_conf) 434 type_set = get_type_set(public_origin_cil_path) 435 filter_out(min_cil_path, public_origin_cil_path) 436 build_version_cil(args.vendor_policy_version, public_origin_cil_path, public_version_cil_path, type_set) 437 438 # build vendor.cil 439 build_conf(args, vendor_output_conf, vendor_policy_file_list) 440 build_cil(args, vendor_origin_cil_path, vendor_output_conf) 441 filter_out(min_cil_path, vendor_origin_cil_path) 442 build_version_cil(args.vendor_policy_version, vendor_origin_cil_path, vendor_cil_path, type_set) 443 filter_out(public_version_cil_path, vendor_cil_path) 444 445 build_type_version_cil(args.vendor_policy_version, public_origin_cil_path, type_version_cil_path) 446 447 if args.components == "system": 448 generate_hash_file([system_cil_path, type_version_cil_path], 449 os.path.join(output_path, SYSTEM_CIL_HASH)) 450 451 elif args.components == "vendor": 452 generate_hash_file([system_cil_path, type_version_cil_path], os.path.join( 453 output_path, PREBUILD_SEPOLICY_SYSTEM_CIL_HASH)) 454 455 version_file = os.path.join(output_path, "version") 456 generate_version_file(args, version_file) 457 458 return [vendor_cil_path, system_cil_path, type_version_cil_path, public_version_cil_path] 459 460 461def compile_sepolicy(args): 462 dir_list_object = get_policy_dir_list(args) 463 file_list_object = get_policy_file_list(args, dir_list_object) 464 465 cil_list = [] 466 if args.components == "default" or args.updater_version == "enable": 467 cil_list += generate_default_policy(args, file_list_object.system_policy_file_list, 468 file_list_object.vendor_policy_file_list, 469 file_list_object.min_policy_file_list) 470 else: 471 cil_list += generate_special_policy(args, file_list_object.system_policy_file_list, 472 file_list_object.vendor_policy_file_list, 473 file_list_object.public_policy_file_list, 474 file_list_object.min_policy_file_list) 475 476 build_binary_policy(args.tool_path, args.dst_file, True, cil_list) 477 478 479def main(args): 480 # check both debug and release sepolicy 481 origin_debug_version = args.debug_version 482 if args.debug_version == "true": 483 args.debug_version = "false" 484 compile_sepolicy(args) 485 else: 486 args.debug_version = "true" 487 compile_sepolicy(args) 488 489 # build target policy according to desire debug_version 490 args.debug_version = origin_debug_version 491 compile_sepolicy(args) 492