From 3506ad3f310d46ace11ecb9f1a0b6d8abc4951c4 Mon Sep 17 00:00:00 2001 From: Bowgo Tsai <bowgotsai@google.com> Date: Fri, 5 Jan 2018 14:52:04 +0800 Subject: [PATCH] Using a python script to build sepolicy Current sepolicy CIL files are built by several command-line tools in Android.mk. This change extracts some of the build logic into a python script to relief the effort in Android.mk. The first command is `build_sepolicy build_cil`. It's possible to add more sub-commands under the build_sepolicy script in the future. Bug: 64240127 Test: build and boot a device Test: checks the content of $OUT/vendor/etc/selinux/vendor_sepolicy.cil is the same as before Change-Id: I0b64f1088f413172e97b579b4f7799fa392762df --- Android.bp | 5 +- Android.mk | 38 ++++------- build/Android.bp | 34 ++++++++++ build/build_sepolicy.py | 140 ++++++++++++++++++++++++++++++++++++++++ build/file_utils.py | 49 ++++++++++++++ 5 files changed, 241 insertions(+), 25 deletions(-) create mode 100644 build/Android.bp create mode 100644 build/build_sepolicy.py create mode 100644 build/file_utils.py diff --git a/Android.bp b/Android.bp index 9952a8fd5..17853420b 100644 --- a/Android.bp +++ b/Android.bp @@ -1 +1,4 @@ -subdirs = ["tests"] +subdirs = [ + "tests", + "build", +] diff --git a/Android.mk b/Android.mk index 40c457061..0d4d67fb5 100644 --- a/Android.mk +++ b/Android.mk @@ -222,6 +222,7 @@ LOCAL_REQUIRED_MODULES += \ plat_and_mapping_sepolicy.cil.sha256 \ secilc \ plat_sepolicy_vers.txt \ + vendor_service_contexts \ # Include precompiled policy, unless told otherwise ifneq ($(PRODUCT_PRECOMPILED_SEPOLICY),false) @@ -233,6 +234,7 @@ LOCAL_REQUIRED_MODULES += sepolicy endif LOCAL_REQUIRED_MODULES += \ + build_sepolicy \ vendor_file_contexts \ vendor_mac_permissions.xml \ vendor_property_contexts \ @@ -247,10 +249,6 @@ LOCAL_REQUIRED_MODULES += \ searchpolicy \ vndservice_contexts \ -ifneq ($(PRODUCT_SEPOLICY_SPLIT),true) -LOCAL_REQUIRED_MODULES += vendor_service_contexts -endif - ifneq ($(TARGET_BUILD_VARIANT), user) LOCAL_REQUIRED_MODULES += \ selinux_denial_metadata \ @@ -540,31 +538,23 @@ $(PLAT_PUBLIC_POLICY) $(REQD_MASK_POLICY) $(PLAT_VENDOR_POLICY) $(BOARD_VENDOR_S $(transform-policy-to-conf) $(hide) sed '/dontaudit/d' $@ > $@.dontaudit -vendor_policy_raw := $(intermediates)/vendor_policy_raw.cil -$(vendor_policy_raw): PRIVATE_POL_CONF := $(vendor_policy.conf) -$(vendor_policy_raw): PRIVATE_REQD_MASK := $(reqd_policy_mask.cil) -$(vendor_policy_raw): $(HOST_OUT_EXECUTABLES)/checkpolicy $(vendor_policy.conf) \ -$(reqd_policy_mask.cil) - @mkdir -p $(dir $@) - $(hide) $(CHECKPOLICY_ASAN_OPTIONS) $< -C -M -c $(POLICYVERS) -o $@.tmp $(PRIVATE_POL_CONF) - $(hide) grep -Fxv -f $(PRIVATE_REQD_MASK) $@.tmp > $@ - -$(LOCAL_BUILT_MODULE) : PRIVATE_VERS := $(BOARD_SEPOLICY_VERS) -$(LOCAL_BUILT_MODULE) : PRIVATE_TGT_POL := $(vendor_policy_raw) -$(LOCAL_BUILT_MODULE) : PRIVATE_DEP_CIL_FILES := $(built_plat_cil) $(built_plat_pub_vers_cil) $(built_mapping_cil) -$(LOCAL_BUILT_MODULE) : PRIVATE_FILTER_CIL := $(built_plat_pub_vers_cil) -$(LOCAL_BUILT_MODULE) : $(plat_pub_policy.cil) $(vendor_policy_raw) \ - $(HOST_OUT_EXECUTABLES)/version_policy $(HOST_OUT_EXECUTABLES)/secilc \ +$(LOCAL_BUILT_MODULE): PRIVATE_POL_CONF := $(vendor_policy.conf) +$(LOCAL_BUILT_MODULE): PRIVATE_REQD_MASK := $(reqd_policy_mask.cil) +$(LOCAL_BUILT_MODULE): PRIVATE_BASE_CIL := $(plat_pub_policy.cil) +$(LOCAL_BUILT_MODULE): PRIVATE_VERS := $(BOARD_SEPOLICY_VERS) +$(LOCAL_BUILT_MODULE): PRIVATE_DEP_CIL_FILES := $(built_plat_cil) $(built_plat_pub_vers_cil) $(built_mapping_cil) +$(LOCAL_BUILT_MODULE): PRIVATE_FILTER_CIL := $(built_plat_pub_vers_cil) +$(LOCAL_BUILT_MODULE): $(HOST_OUT_EXECUTABLES)/build_sepolicy \ + $(vendor_policy.conf) $(reqd_policy_mask.cil) $(plat_pub_policy.cil) \ $(built_plat_cil) $(built_plat_pub_vers_cil) $(built_mapping_cil) @mkdir -p $(dir $@) - $(HOST_OUT_EXECUTABLES)/version_policy -b $< -t $(PRIVATE_TGT_POL) -n $(PRIVATE_VERS) -o $@.tmp - $(hide) grep -Fxv -f $(PRIVATE_FILTER_CIL) $@.tmp > $@ - $(hide) $(HOST_OUT_EXECUTABLES)/secilc -m -M true -G -N -c $(POLICYVERS) \ - $(PRIVATE_DEP_CIL_FILES) $@ -o /dev/null -f /dev/null + $(hide) $(HOST_OUT_EXECUTABLES)/build_sepolicy -a $(HOST_OUT_EXECUTABLES) build_cil \ + -i $(PRIVATE_POL_CONF) -m $(PRIVATE_REQD_MASK) -c $(CHECKPOLICY_ASAN_OPTIONS) \ + -b $(PRIVATE_BASE_CIL) -d $(PRIVATE_DEP_CIL_FILES) -f $(PRIVATE_FILTER_CIL) \ + -t $(PRIVATE_VERS) -p $(POLICYVERS) -o $@ built_vendor_cil := $(LOCAL_BUILT_MODULE) vendor_policy.conf := -vendor_policy_raw := ################################# include $(CLEAR_VARS) diff --git a/build/Android.bp b/build/Android.bp new file mode 100644 index 000000000..d3f1fc36b --- /dev/null +++ b/build/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +python_binary_host { + name: "build_sepolicy", + srcs: [ + "build_sepolicy.py", + "file_utils.py", + ], + required: [ + "checkpolicy", + "secilc", + "version_policy", + ], + version: { + py2: { + enabled: true, + }, + py3: { + enabled: false, + }, + }, +} diff --git a/build/build_sepolicy.py b/build/build_sepolicy.py new file mode 100644 index 000000000..ff2ff075d --- /dev/null +++ b/build/build_sepolicy.py @@ -0,0 +1,140 @@ +# Copyright 2018 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Command-line tool to build SEPolicy files.""" + +import argparse +import os +import subprocess +import sys + +import file_utils + + +# All supported commands in this module. +# For each command, need to add two functions. Take 'build_cil' for example: +# - setup_build_cil() +# - Sets up command parsers and sets default function to do_build_cil(). +# - do_build_cil() +_SUPPORTED_COMMANDS = ('build_cil',) + + +def run_host_command(args, **kwargs): + """Runs a host command and prints output.""" + if kwargs.get('shell'): + command_log = args + else: + command_log = ' '.join(args) # For args as a sequence. + + try: + subprocess.check_call(args, **kwargs) + except subprocess.CalledProcessError as err: + sys.stderr.write( + 'build_sepolicy - failed to run command: {!r} (ret:{})\n'.format( + command_log, err.returncode)) + sys.exit(err.returncode) + + +def do_build_cil(args): + """Builds a sepolicy CIL (Common Intermediate Language) file. + + This functions invokes some host utils (e.g., secilc, checkpolicy, + version_sepolicy) to generate a .cil file. + + Args: + args: the parsed command arguments. + """ + # Determines the raw CIL file name. + input_file_name = os.path.splitext(args.input_policy_conf)[0] + raw_cil_file = input_file_name + '_raw.cil' + # Builds the raw CIL. + file_utils.make_parent_dirs(raw_cil_file) + checkpolicy_cmd = [args.checkpolicy_env] + checkpolicy_cmd += [os.path.join(args.android_host_path, 'checkpolicy'), + '-C', '-M', '-c', args.policy_vers, + '-o', raw_cil_file, args.input_policy_conf] + # Using shell=True to setup args.checkpolicy_env variables. + run_host_command(' '.join(checkpolicy_cmd), shell=True) + file_utils.filter_out([args.reqd_mask], raw_cil_file) + + # Builds the output CIL by versioning the above raw CIL. + output_file = args.output_cil + if output_file is None: + output_file = input_file_name + '.cil' + file_utils.make_parent_dirs(output_file) + + run_host_command([os.path.join(args.android_host_path, 'version_policy'), + '-b', args.base_policy, '-t', raw_cil_file, + '-n', args.treble_sepolicy_vers, '-o', output_file]) + if args.filter_out_files: + file_utils.filter_out(args.filter_out_files, output_file) + + # Tests that the output file can be merged with the given CILs. + if args.dependent_cils: + merge_cmd = [os.path.join(args.android_host_path, 'secilc'), + '-m', '-M', 'true', '-G', '-N', '-c', args.policy_vers] + merge_cmd += args.dependent_cils # the give CILs to merge + merge_cmd += [output_file, '-o', '/dev/null', '-f', '/dev/null'] + run_host_command(merge_cmd) + + +def setup_build_cil(subparsers): + """Sets up command args for 'build_cil' command.""" + + # Required arguments. + parser = subparsers.add_parser('build_cil', help='build CIL files') + parser.add_argument('-i', '--input_policy_conf', required=True, + help='source policy.conf') + parser.add_argument('-m', '--reqd_mask', required=True, + help='the bare minimum policy.conf to use checkpolicy') + parser.add_argument('-b', '--base_policy', required=True, + help='base policy for versioning') + parser.add_argument('-t', '--treble_sepolicy_vers', required=True, + help='the version number to use for Treble-OTA') + parser.add_argument('-p', '--policy_vers', required=True, + help='SELinux policy version') + + # Optional arguments. + parser.add_argument('-c', '--checkpolicy_env', + help='environment variables passed to checkpolicy') + parser.add_argument('-f', '--filter_out_files', nargs='+', + help='the pattern files to filter out the output cil') + parser.add_argument('-d', '--dependent_cils', nargs='+', + help=('check the output file can be merged with ' + 'the dependent cil files')) + parser.add_argument('-o', '--output_cil', help='the output cil file') + + # The function that performs the actual works. + parser.set_defaults(func=do_build_cil) + + +def run(argv): + """Sets up command parser and execuates sub-command.""" + parser = argparse.ArgumentParser() + + # Adds top-level arguments. + parser.add_argument('-a', '--android_host_path', default='', + help='a path to host out executables') + + # Adds subparsers for each COMMAND. + subparsers = parser.add_subparsers(title='COMMAND') + for command in _SUPPORTED_COMMANDS: + globals()['setup_' + command](subparsers) + + args = parser.parse_args(argv[1:]) + args.func(args) + + +if __name__ == '__main__': + run(sys.argv) diff --git a/build/file_utils.py b/build/file_utils.py new file mode 100644 index 000000000..1559a9b9d --- /dev/null +++ b/build/file_utils.py @@ -0,0 +1,49 @@ +# Copyright 2018 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""File-related utilities.""" + + +import os +import shutil +import tempfile + + +def make_parent_dirs(file_path): + """Creates parent directories for the file_path.""" + if os.path.exists(file_path): + return + + parent_dir = os.path.dirname(file_path) + if parent_dir and not os.path.exists(parent_dir): + os.makedirs(parent_dir) + + +def filter_out(pattern_files, input_file): + """"Removes lines in input_file that match any line in pattern_files.""" + + # Prepares patterns. + patterns = [] + for f in pattern_files: + patterns.extend(open(f).readlines()) + + # Copy lines that are not in the pattern. + tmp_output = tempfile.NamedTemporaryFile() + with open(input_file, 'r') as in_file: + tmp_output.writelines(line for line in in_file.readlines() + if line not in patterns) + tmp_output.flush() + + # Replaces the input_file. + shutil.copyfile(tmp_output.name, input_file) -- GitLab