| # |
| # Copyright (C) 2016 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. |
| # |
| |
| |
| """Functions for generating SELinux policy files.""" |
| |
| |
| import glob |
| import os |
| import shutil |
| import subprocess |
| import tempfile |
| |
| import error |
| from core import tool |
| |
| |
| class Error(error.Error): |
| """General SELinux-related failure.""" |
| |
| |
| class SELinuxCommandError(Error): |
| """Raised when an SELinux-related command fails.""" |
| description = 'SELinux command error' |
| |
| |
| POLICYVERS = 29 |
| |
| MLS_SENS = 1 |
| MLS_CATS = 1024 |
| |
| SEPOLICY_BUILD_FILES = [ |
| 'security_classes', |
| 'initial_sids', |
| 'access_vectors', |
| 'global_macros', |
| 'neverallow_macros', |
| 'mls_macros', |
| 'mls', |
| 'policy_capabilities', |
| 'te_macros', |
| 'attributes', |
| 'ioctl_defines', |
| 'ioctl_macros', |
| '*.te', |
| 'roles', |
| 'users', |
| 'initial_sid_contexts', |
| 'fs_use', |
| 'genfs_contexts', |
| 'port_contexts' |
| ] |
| |
| FC = 'file_contexts' |
| SEPOLICY_DIRS_FILE = 'sepolicy_dirs.txt' |
| |
| |
| def _ExpandSepolicyPaths(base, dirs, files): |
| """Generates all the possible combinations of '/base/dir/file'.""" |
| |
| policy_files = [] |
| for filename in files: |
| for d in dirs: |
| policy_files.extend(glob.glob(os.path.join(base, d, filename))) |
| |
| return policy_files |
| |
| |
| def _RunM4(inputs, output, opts=None): |
| # TODO(jorgelo): Use HostToolWrapper once it's easier to redirect output. |
| subprocess.check_call('m4 %s -s %s > %s' % (' '.join(opts) if opts else '', |
| ' '.join(inputs), |
| output), |
| shell=True) |
| |
| |
| def _RunSepolicyAnalyze(build_out, _input_file, _output_file): |
| # Currently unused, keep for completeness. Will be included in the build |
| # soon. |
| # pylint: disable=unused-variable |
| sepolicy_analize = tool.HostToolWrapper('sepolicy-analyze', build_out) |
| |
| |
| def GetBoardComboSepolicyDirs(platform): |
| """Gets the BOARD_SEPOLICY_DIRS variable from the build.""" |
| cmds = ['cd "{}"'.format(platform.os.root), |
| '. build/envsetup.sh', |
| 'lunch "{}-{}"'.format(platform.device.name, platform.build_type), |
| 'get_build_var BOARD_SEPOLICY_DIRS'] |
| |
| # Link all commands with && to exit immediately if one fails. |
| cmdline = ' && '.join(cmds) |
| |
| try: |
| output = subprocess.check_output(cmdline, shell=True, |
| executable='bash', |
| stderr=subprocess.STDOUT) |
| except Exception as e: |
| raise SELinuxCommandError( |
| 'Failed to get BOARD_SEPOLICY_DIRS: {}'.format(e)) |
| |
| lines = output.splitlines() |
| if len(lines) < 1: |
| raise SELinuxCommandError( |
| 'Failed to get BOARD_SEPOLICY_DIRS: Output was empty') |
| |
| return lines[-1].strip().split() |
| |
| |
| def SaveSepolicyDirsCache(platform_out, sepolicy_dirs): |
| """Saves the BOARD_SEPOLICY_DIRS cache file.""" |
| try: |
| dirs_file = os.path.join(platform_out, SEPOLICY_DIRS_FILE) |
| with open(dirs_file, 'w') as fw: |
| fw.write('\n'.join(sepolicy_dirs)) |
| except IOError as e: |
| raise SELinuxCommandError( |
| 'Failed to save BOARD_SEPOLICY_DIRS cache: {}'.format(e)) |
| |
| |
| def LoadCachedSepolicyDirs(platform): |
| """Loads and parses the BOARD_SEPOLICY_DIRS cache file.""" |
| cache_path = os.path.join(platform.build_cache, SEPOLICY_DIRS_FILE) |
| |
| board_sepolicy_dirs = None |
| if os.access(cache_path, os.R_OK): |
| # Cache exists. |
| with open(cache_path, 'r') as fr: |
| board_sepolicy_dirs = [line.strip() for line in fr.readlines()] |
| if len(board_sepolicy_dirs) == 0: |
| # Cache file was empty. |
| board_sepolicy_dirs = None |
| |
| if not board_sepolicy_dirs: |
| # Cache did not exist, or was empty. |
| board_sepolicy_dirs = GetBoardComboSepolicyDirs(platform) |
| SaveSepolicyDirsCache(platform.build_cache, board_sepolicy_dirs) |
| |
| return board_sepolicy_dirs |
| |
| |
| def BuildSepolicy(platform, output_path): |
| """Builds the main 'sepolicy' SELinux policy file. |
| |
| This needs to be built before attempting to build |
| the 'file_contexts.bin' file. |
| """ |
| interm_dir = tempfile.mkdtemp() |
| runner = tool.HostToolRunner(platform) |
| # TODO: platformify |
| try: |
| os_path = platform.os.root |
| main_sepolicy_dir = os.path.join(os_path, 'system', 'sepolicy') |
| |
| all_paths = [main_sepolicy_dir] |
| all_paths.extend(LoadCachedSepolicyDirs(platform)) |
| |
| policy_inputs = _ExpandSepolicyPaths(os_path, all_paths, |
| SEPOLICY_BUILD_FILES) |
| policy_conf = os.path.join(interm_dir, 'policy.conf') |
| m4_opts = ['-D mls_num_sens=%s' % MLS_SENS, |
| '-D mls_num_cats=%s' % MLS_CATS, |
| '-D target_build_variant=%s' % platform.build_type] |
| _RunM4(policy_inputs, policy_conf, m4_opts) |
| |
| runner.run('checkpolicy', |
| ('-M -c %d -o %s %s' % (POLICYVERS, output_path, |
| policy_conf)).split()) |
| finally: |
| shutil.rmtree(interm_dir, ignore_errors=True) |
| |
| |
| def BuildFileContexts(platform, sepolicy_path, output_path, |
| additional_context_dir=None): |
| """Builds the file_contexts bin SELinux policy file.""" |
| |
| interm_dir = tempfile.mkdtemp() |
| runner = tool.HostToolRunner(platform) |
| try: |
| os_path = platform.os.root |
| |
| fc_local = os.path.join(interm_dir, 'file_contexts.local.tmp') |
| fc_device = os.path.join(interm_dir, 'file_contexts.device.tmp') |
| fc_device_sorted = os.path.join(interm_dir, |
| 'file_contexts.device.sorted.tmp') |
| fc_concat = os.path.join(interm_dir, 'file_contexts.concat.tmp') |
| |
| main_sepolicy_dir = os.path.join(os_path, 'system', 'sepolicy') |
| main_fc = os.path.join(main_sepolicy_dir, FC) |
| |
| _RunM4([main_fc], fc_local) |
| board_sepolicy_dirs = LoadCachedSepolicyDirs(platform) |
| |
| if additional_context_dir: |
| board_sepolicy_dirs.append(additional_context_dir) |
| |
| _RunM4(_ExpandSepolicyPaths(os_path, board_sepolicy_dirs, [FC]), |
| fc_device) |
| runner.run('checkfc', ['-e', sepolicy_path, fc_device]) |
| runner.run('fc_sort', [fc_device, fc_device_sorted]) |
| |
| _RunM4([fc_local, fc_device_sorted], fc_concat) |
| runner.run('checkfc', ['-e', sepolicy_path, fc_concat]) |
| runner.run('sefcontext_compile', ['-o', output_path, fc_concat]) |
| finally: |
| shutil.rmtree(interm_dir, ignore_errors=True) |