blob: 5c29c6766793b11c380477392e88fe161e8c3811 [file] [log] [blame]
#
# 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)