Android P SELinux (二) 开机初始化与策略文件编译过程

Android P SELinux (一) 基础概念
Android P SELinux (二) 开机初始化与策略文件编译过程
Android P SELinux (三) 权限检查原理与调试
Android P SELinux (四) CTS neverallow处理总结

本文主要围绕二进制策略文件的加载和编译过程

我们写的te规则,到底生成在哪里了?开机之后又是怎么加载使用的?

一、SELinux开机初始化

1. init.cpp

代码路径:android/system/core/init/init.cpp
main函数:

if (is_first_stage) {  // 第一阶段初始化
    ...
 
    // Set up SELinux, loading the SELinux policy.
    SelinuxSetupKernelLogging();
    SelinuxInitialize();
 
    ...
}
 
 
// Now set up SELinux for second stage.
SelinuxSetupKernelLogging();
SelabelInitialize();
SelinuxRestoreContext();

2. selinux.cpp

代码路径:android/system/core/init/selinux.cpp

2.1 SelinuxSetupKernelLogging

设置log callback,可以调用selinux_log把log写入到kmsg里面

2.2 SelinuxInitialize

selinux 初始化,从二进制策略文件里面读取策略,加载到内核

void SelinuxInitialize() {
    Timer t;
 
    LOG(INFO) << "Loading SELinux policy";
    if (!LoadPolicy()) { // 加载SELinux策略
        LOG(FATAL) << "Unable to load SELinux policy";
    }
 
    bool kernel_enforcing = (security_getenforce() == 1); // 从kernel获取SELinux的状态,和getenforce的实现一样
    bool is_enforcing = IsEnforcing();  // 从bootargs里面获取
    if (kernel_enforcing != is_enforcing) {
        if (security_setenforce(is_enforcing)) { // 如果kernel里面的SELinux状态和bootargs的不一致,要设置成bootargs里面传过来的值
            PLOG(FATAL) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
        }
    }
 
    if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {  // 由内核强制执行检查保护
        LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
    }
 
    // init's first stage can't set properties, so pass the time to the second stage.
    setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
}
 
bool LoadPolicy() { // 从Android8.0之后,因为Project Treble,system和vendor策略分离,所以Android P上走的是LoadSplitPolicy
    return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy();
}

IsEnforcing 从bootargs里面的androidboot.selinux取值; security_getenforce 从kernel取值,这个值和getenforce拿到的是一样的

2.3 checkreqprot

设置"checkreqprot"标记的初始值。

  • "0"表示由内核强制执行检查保护(包括其中隐含的所有执行保护)
  • "1"表示由应用程序自己主动请求执行检查保护
    默认值由内核在编译时确定,也可以在运行时通过/sys/fs/selinux/checkreqprot修改

2.4 LoadPolicy

policy的分割是从Android 8.0之后开始的,具体可以看看这篇博客:
Android8.0 SELinux详解

Android低版本用的是LoadMonolithicPolicy,8.0之后调用的是LoadSplitPolicy

sepolicy分离

#public - policy exported on which non-platform policy developers may write
#additional policy. types and attributes are versioned and included in
#delivered non-platform policy, which is to be combined with platform policy. 导出的策略,非平台策略开发人员可以在其上编写附加策略
类型和属性被版本化并包含在交付的非平台策略中,该策略将与平台策略相结合
使用BOARD_PLAT_PUBLIC_SEPOLICY_DIR来添加拓展

types和attributes生成在vendor分区的会带上版本,比如bootanim_28_0,实现在cil_android_attributize

#private - platform-only policy required for platform functionality but which
#is not exported to vendor policy developers and as such may not be assumed
#to exist. 平台功能所需的纯平台策略,但不会导出到供应商策略开发人员,因此可能不存在。

#vendor - vendor-only policy required for vendor functionality. This policy can
#reference the public policy but cannot reference the private policy. This
#policy is for components which are produced from the core/non-vendor tree and
#placed into a vendor partition. 供应商功能所需的供应商专用策略。此策略可以引用公共策略,但不能引用私有策略。此策略适用于从核心/非供应商树生成并放置到供应商分区中的组件。

#mapping - This contains policy statements which map the attributes
#exposed in the public policy of previous versions to the concrete types used
#in this policy to ensure that policy targeting attributes from public
#policy from an older platform version continues to work. 它包含策略语句,这些语句将以前版本的公共策略中公开的属性映射到此策略中使用的具体类型,以确保来自较旧平台版本的公共策略的策略目标属性继续工作。

2.5 LoadSplitPolicy

bool LoadSplitPolicy() {
    // IMPLEMENTATION NOTE: Split policy consists of three CIL files:
    // * platform -- policy needed due to logic contained in the system image,
    // * non-platform -- policy needed due to logic contained in the vendor image,
    // * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy
    //   with newer versions of platform policy.
    //
    // secilc is invoked to compile the above three policy files into a single monolithic policy
    // file. This file is then loaded into the kernel.
 
    // Load precompiled policy from vendor image, if a matching policy is found there. The policy
    // must match the platform policy on the system image.
    std::string precompiled_sepolicy_file;
    if (FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) { // 先找odm分区,再找vendor分区
        unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));  // open file
        if (fd != -1) {
            if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) { // 加载到kernel
                LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file;
                return false;
            }
            return true;
        }
    }
    // No suitable precompiled policy could be loaded
 
    LOG(INFO) << "Compiling SELinux policy";
 
    // Determine the highest policy language version supported by the kernel
    set_selinuxmnt("/sys/fs/selinux");
    int max_policy_version = security_policyvers();
    if (max_policy_version == -1) {
        PLOG(ERROR) << "Failed to determine highest policy version supported by kernel";
        return false;
    }
 
    // We store the output of the compilation on /dev because this is the most convenient tmpfs
    // storage mount available this early in the boot sequence.
    char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX";
    unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC));
    if (compiled_sepolicy_fd < 0) {
        PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy;
        return false;
    }
 
    // Determine which mapping file to include
    std::string vend_plat_vers;
    if (!GetVendorMappingVersion(&vend_plat_vers)) {
        return false;
    }
    std::string mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");
 
    // vendor_sepolicy.cil and plat_pub_versioned.cil are the new design to replace
    // nonplat_sepolicy.cil.
    std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
    std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");
 
    if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
        // For backward compatibility.
        // TODO: remove this after no device is using nonplat_sepolicy.cil.
        vendor_policy_cil_file = "/vendor/etc/selinux/nonplat_sepolicy.cil";
        plat_pub_versioned_cil_file.clear();
    } else if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
        LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;
        return false;
    }
 
    // odm_sepolicy.cil is default but optional.
    std::string odm_policy_cil_file("/odm/etc/selinux/odm_sepolicy.cil");
    if (access(odm_policy_cil_file.c_str(), F_OK) == -1) {
        odm_policy_cil_file.clear();
    }
    const std::string version_as_string = std::to_string(max_policy_version);
 
    // clang-format off
    std::vector<const char*> compile_args {
        "/system/bin/secilc",
        plat_policy_cil_file,
        "-m", "-M", "true", "-G", "-N",
        // Target the highest policy language version supported by the kernel
        "-c", version_as_string.c_str(),
        mapping_file.c_str(),
        "-o", compiled_sepolicy,
        // We don't care about file_contexts output by the compiler
        "-f", "/sys/fs/selinux/null",  // /dev/null is not yet available
    };
    // clang-format on
 
    if (!plat_pub_versioned_cil_file.empty()) {
        compile_args.push_back(plat_pub_versioned_cil_file.c_str());
    }
    if (!vendor_policy_cil_file.empty()) {
        compile_args.push_back(vendor_policy_cil_file.c_str());
    }
    if (!odm_policy_cil_file.empty()) {
        compile_args.push_back(odm_policy_cil_file.c_str());
    }
    compile_args.push_back(nullptr);
 
    if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
        unlink(compiled_sepolicy);
        return false;
    }
    unlink(compiled_sepolicy);
 
    LOG(INFO) << "Loading compiled SELinux policy";
    if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) {
        LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy;
        return false;
    }
 
    return true;
}

LoadSplitPolicy 加载selinux 策略时,首先从预先编译好的二进制文件precompiled_sepolicy读取

如果没有找到的话,再用secilc命令编译所有的cil,重新得到一个二进制策略文件

2.6 FindPrecompiledSplitPolicy

这个函数会查找两个地方,/odm/etc/selinux和/vendor/etc/selinux

  • /vendor/etc/selinux/precompiled_sepolicy
  • /odm/etc/selinux/precompiled_sepolicy

如果有odm分区,precompiled_sepolicy会放在odm分区,否则在vendor分区,因此读取的时候也是先找odm再找vendor

bool FindPrecompiledSplitPolicy(std::string* file) {
    file->clear();
    // If there is an odm partition, precompiled_sepolicy will be in
    // odm/etc/selinux. Otherwise it will be in vendor/etc/selinux.
    static constexpr const char vendor_precompiled_sepolicy[] =
        "/vendor/etc/selinux/precompiled_sepolicy";
    static constexpr const char odm_precompiled_sepolicy[] =
        "/odm/etc/selinux/precompiled_sepolicy";
    if (access(odm_precompiled_sepolicy, R_OK) == 0) {
        *file = odm_precompiled_sepolicy;
    } else if (access(vendor_precompiled_sepolicy, R_OK) == 0) {
        *file = vendor_precompiled_sepolicy;
    } else {
        PLOG(INFO) << "No precompiled sepolicy";
        return false;
    }
 
        // 接下来下面会对哈希值做校验,分别是:
    // /system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256
    // /vendor/etc/selinux/precompiled_sepolicy.plat_and_mapping.sha256
    std::string actual_plat_id;
    if (!ReadFirstLine("/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256", &actual_plat_id)) {
        PLOG(INFO) << "Failed to read "
                      "/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256";
        return false;
    }
 
    std::string precompiled_plat_id;
    std::string precompiled_sha256 = *file + ".plat_and_mapping.sha256";
    if (!ReadFirstLine(precompiled_sha256.c_str(), &precompiled_plat_id)) {
        PLOG(INFO) << "Failed to read " << precompiled_sha256;
        file->clear();
        return false;
    }
    if ((actual_plat_id.empty()) || (actual_plat_id != precompiled_plat_id)) {
        file->clear();
        return false;
    }
    return true;
}

最后面会对哈希值做一个校验,这两个文件都是编译阶段生成的

后面我们局部编译SELinux策略文件时,一定要注意只替换precompiled_sepolicy就行了

千万不要同时替换precompiled_sepolicy 和 precompiled_sepolicy.plat_and_mapping.sha256,而忘记了/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256,这个会导致你替换的策略文件不生效

二、SELinux Policy编译

3. 二进制策略文件的生成过程

本节涉及的代码集中在:

  • android/system/sepolicy/Android.mk
  • android/external/selinux/secilc
  • android/external/selinux/checkpolicy
  • android/external/selinux/libselinux
  • android/external/selinux/libsepol

其中Android mk中的一些变量:

PLAT_PUBLIC_POLICY=system/sepolicy/public
PLAT_PRIVATE_POLICY=system/sepolicy/private
PLAT_VENDOR_POLICY=system/sepolicy/vendor
REQD_MASK_POLICY=system/sepolicy/reqd_mask
BOARD_PLAT_PUBLIC_SEPOLICY_DIR    #对PLAT_PUBLIC_POLICY的拓展
BOARD_PLAT_PRIVATE_SEPOLICY_DIR   #对PLAT_PRIVATE_POLICY的拓展
 
BOARD_SEPOLICY_DIRS  #设备制造商客制化部分的策略文件

3.1 precompiled_sepolicy是如何生成的?

先看总的流程:
在这里插入图片描述
如果定义了BOARD_ODM_SEPOLICY_DIRS,那么还会有一个odm_sepolicy.cil,当然前提还要有odm分区

ifdef BOARD_ODM_SEPOLICY_DIRS
    all_cil_files += $(built_odm_cil)
endif

在 android/system/sepolicy/Android.mk 的注释里面,提到了SELinux策略文件编译成二进制策略文件的过程

第二步,是针对28.0.cil、plat_pub_versioned.cil、vendor_sepolicy.cil,而plat_sepolicy.cil不需要

并且,只有在system/sepolicy/public、system/sepolicy/private下定义的types和attributes才会有加上这个版本后缀

调用过程 (build_sepolicy -> ) version_policy -> cil_android_attributize

# build process for device:
# 1) convert policies to CIL:
#    - private + public platform policy to CIL
#    - mapping file to CIL (should already be in CIL form)
#    - non-platform public policy to CIL
#    - non-platform public + private policy to CIL
# 2) attributize policy
#    - run script which takes non-platform public and non-platform combined
#      private + public policy and produces attributized and versioned
#      non-platform policy
# 3) combine policy files
#    - combine mapping, platform and non-platform policy.
#    - compile output binary policy file

ACP描述的是一个Android专用的cp命令,在生成system.img镜像文件的过程中是需要用到的。普通的cp命令在不同的平台(Mac
OS
X、MinGW/Cygwin和Linux)的实现略有差异,并且可能会导致一些问题,于是Android编译系统就重写了自己的cp命令,使得它在不同平台下执行具有统一的行为,并且解决普通cp命令可能会出现的问题。例如,在Linux平台上,当我们把一个文件从NFS文件系统拷贝到本地文件系统时,普通的cp命令总是会认为在NFS文件系统上的文件比在本地文件系统上的文件要新,因为前者的时间戳精度是微秒,而后者的时间戳精度不是微秒。Android专用的cp命令源码可以参考build/tools/acp目录。

摘自链接:https://blog.csdn.net/kc58236582/article/details/49795865

分析Android.mk文件: android/system/sepolicy/Android.mk

BOARD_USES_ODMIMAGE 在 BoardConfig.mk 里面定义

这个是决定是否有odm分区,如果带odm分区,那么路径就在odm分区

LOCAL_MODULE := precompiled_sepolicy
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_TAGS := optional
LOCAL_PROPRIETARY_MODULE := true
 
ifeq ($(BOARD_USES_ODMIMAGE),true)
LOCAL_MODULE_PATH := $(TARGET_OUT_ODM)/etc/selinux
else
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc/selinux
endif

首先它依赖的是这些cil文件(CIL : Common Intermediate Language,通用中间语言)

  • /system/etc/selinux/plat_sepolicy.cil
  • /system/etc/selinux/mapping/28.0.cil
  • /vendor/etc/selinux/plat_pub_versioned.cil
  • /vendor/etc/selinux/vendor_sepolicy.cil

接着是通过secilc命令来生成precompiled_sepolicy

built_device=xxxx
built_plat_cil=out/target/product/${built_device}/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil
built_mapping_cil=out/target/product/${built_device}/obj/ETC/28.0.cil_intermediates/28.0.cil
built_plat_pub_vers_cil=out/target/product/${built_device}/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil
built_vendor_cil=out/target/product/${built_device}/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil
 
secilc -m -M true -G -c 30 ${built_plat_cil} ${built_mapping_cil} ${built_plat_pub_vers_cil} ${built_vendor_cil} -o precompiled_sepolicy -f /dev/null

secilc的源码在:android/external/selinux/secilc

secilc是用来编译cil中间文件的,将其生成二进制文件的

代码中有文档,其中有一张图片对于理解secilc非常重要

android/external/selinux/secilc/docs/cil_design.jpeg
在这里插入图片描述

The parse tree consists of open parenthesis nodes and nodes for each
symbol or quoted strings. An open parenthesis is the parent of the
symbols or quoted strings that follow it, until a closed parenthesis
is reached.Close parenthesis and comments do not make it into the
parse tree. 解析树由开放的括号节点和每个符号或带引号的字符串的节点组成。
开括号是紧随其后的符号或带引号的字符串的父代,直到达到闭括号为止。闭括号和注释不将其放入语法分析树中。

The cil_build_ast function walks the parse tree and creates the ast. The
first node in each list is checked for keywords,and ast nodes are
created containing the data structures corresponding to the keyword.
Only declarations are handled in this step.Delared symbols are add to
symtabs for each node flavor, and in local or global namespace
symtabs.Strings for symbols that are references(not declarations) are
copied into the ast, and will be resolved later.The parse tree should
be destroyed following this step. cil_build_ast函数遍历解析树并创建ast。
检查每个列表中的第一个节点是否包含关键字,并创建包含与关键字相对应的数据结构的ast节点。
在此步骤中仅处理声明。对于每个节点类型,将符号添加到符号表中,并在本地或全局名称空间中将符号表添加。将引用(非声明)符号的字符串复制到ast中,稍后将对其进行解析。
此步骤之后,应该销毁解析树。

3.1 每一个cil是如何生成的?
拿其中一个来看看:plat_sepolicy.cil

include $(CLEAR_VARS)
 
LOCAL_MODULE := plat_sepolicy.cil
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(TARGET_OUT)/etc/selinux
 
include $(BUILD_SYSTEM)/base_rules.mk
 
# plat_policy.conf - A combination of the private and public platform policy
# which will ship with the device.  The platform will always reflect the most
# recent platform version and is not currently being attributized.
plat_policy.conf := $(intermediates)/plat_policy.conf
$(plat_policy.conf): PRIVATE_MLS_SENS := $(MLS_SENS)
$(plat_policy.conf): PRIVATE_MLS_CATS := $(MLS_CATS)
$(plat_policy.conf): PRIVATE_TARGET_BUILD_VARIANT := $(TARGET_BUILD_VARIANT)
$(plat_policy.conf): PRIVATE_TGT_ARCH := $(my_target_arch)
$(plat_policy.conf): PRIVATE_TGT_WITH_ASAN := $(with_asan)
$(plat_policy.conf): PRIVATE_ADDITIONAL_M4DEFS := $(LOCAL_ADDITIONAL_M4DEFS)
$(plat_policy.conf): PRIVATE_SEPOLICY_SPLIT := $(PRODUCT_SEPOLICY_SPLIT)
$(plat_policy.conf): PRIVATE_COMPATIBLE_PROPERTY := $(PRODUCT_COMPATIBLE_PROPERTY)
$(plat_policy.conf): $(call build_policy, $(sepolicy_build_files), \
$(PLAT_PUBLIC_POLICY) $(PLAT_PRIVATE_POLICY))
    $(transform-policy-to-conf)
    $(hide) sed '/dontaudit/d' $@ > $@.dontaudit
 
$(LOCAL_BUILT_MODULE): PRIVATE_ADDITIONAL_CIL_FILES := \
  $(call build_policy, $(sepolicy_build_cil_workaround_files), $(PLAT_PRIVATE_POLICY))
$(LOCAL_BUILT_MODULE): PRIVATE_NEVERALLOW_ARG := $(NEVERALLOW_ARG)
$(LOCAL_BUILT_MODULE): $(plat_policy.conf) $(HOST_OUT_EXECUTABLES)/checkpolicy \
  $(HOST_OUT_EXECUTABLES)/secilc \
  $(call build_policy, $(sepolicy_build_cil_workaround_files), $(PLAT_PRIVATE_POLICY)) \
  $(built_sepolicy_neverallows)
    @mkdir -p $(dir $@)
    $(hide) $(CHECKPOLICY_ASAN_OPTIONS) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -C -c \
        $(POLICYVERS) -o $@ $<
    $(hide) cat $(PRIVATE_ADDITIONAL_CIL_FILES) >> $@
    $(hide) $(HOST_OUT_EXECUTABLES)/secilc -m -M true -G -c $(POLICYVERS) $(PRIVATE_NEVERALLOW_ARG) $@ -o /dev/null -f /dev/null
 
built_plat_cil := $(LOCAL_BUILT_MODULE)
plat_policy.conf :=

sepolicy_build_files 包含了所有的te文件,这个是我们最熟悉的部分

还包括其他必需的文件,如宏定义global_macros 、 te_macros等

sepolicy_build_files := security_classes \
                        initial_sids \
                        access_vectors \
                        global_macros \
                        neverallow_macros \
                        mls_macros \
                        mls_decl \
                        mls \
                        policy_capabilities \
                        te_macros \
                        attributes \
                        ioctl_defines \
                        ioctl_macros \
                        *.te \
                        roles_decl \
                        roles \
                        users \
                        initial_sid_contexts \
                        fs_use \
                        genfs_contexts \
                        port_contexts

然后调用transform-policy-to-conf将策略文件转成conf文件,生成plat_policy.conf

define transform-policy-to-conf
@mkdir -p $(dir $@)
$(hide) m4 $(PRIVATE_ADDITIONAL_M4DEFS) \
    -D mls_num_sens=$(PRIVATE_MLS_SENS) -D mls_num_cats=$(PRIVATE_MLS_CATS) \
    -D target_build_variant=$(PRIVATE_TARGET_BUILD_VARIANT) \
    -D target_with_dexpreopt=$(WITH_DEXPREOPT) \
    -D target_arch=$(PRIVATE_TGT_ARCH) \
    -D target_with_asan=$(PRIVATE_TGT_WITH_ASAN) \
    -D target_full_treble=$(PRIVATE_SEPOLICY_SPLIT) \
    -D target_compatible_property=$(PRIVATE_COMPATIBLE_PROPERTY) \
    $(PRIVATE_TGT_RECOVERY) \
    -s $^ > $@
endef
.KATI_READONLY := transform-policy-to-conf

transform-policy-to-conf也是一个宏,实际上这个地方,就是调用 m4 这个命令做了一次转换

m4是一个宏处理器,可以把宏调用展开,编写te文件时直接调用宏会很方便,避免了大量的重复

有兴趣深入了解的请看链接:
M4 宏处理器
m4宏语言概览

以上命令参考如下:

m4 -D mls_num_sens=1 -D mls_num_cats=1024 -D target_build_variant=userdebug -D target_with_dexpreopt=true -D target_arch=arm -D target_with_asan=false -D target_full_treble=true -D target_compatible_property=true -s allsepolicyfile(like *.te te_macros) > output.conf

其他的cil也是类似的过程,会有一些差异,但是跟着mk来分析就行了

3.2 案例-策略文件编译生成cil

3.2.1 原始的testA.te

type testA, domain, binder_in_vendor_violators, vendor_executes_system_violators;
type testA_exec, exec_type, vendor_file_type, file_type;

init_daemon_domain(testA)

allow testA console_device:chr_file rw_file_perms;
allow testA testA_exec:file { execute_no_trans };
allow testA vendor_toolbox_exec:file { execute_no_trans };
allow testA vendor_data_file:dir create_dir_perms;
allow testA vendor_data_file:file create_file_perms;
allow testA vendor_file:file { execute_no_trans };
allow testA mnt_media_rw_file:dir { search };
allow testA vfat:file { getattr read open };
allow testA vfat:dir { search };
allow testA block_device:dir { search };
allow testA activity_service:service_manager { find };
allow testA system_server:binder { call transfer };
binder_use(testA)
allow testA shell_exec:file { read execute };
allow testA system_file:file { execute_no_trans };

3.2.2 conf文件

经过m4宏处理器处理后,所有的宏都被替换展开了

#line 1 "device/xxxx/common/sepolicy/testA.te"
type testA, domain, binder_in_vendor_violators, vendor_executes_system_violators;
type testA_exec, exec_type, vendor_file_type, file_type;
 

#line 4
 
#line 4
# Allow the necessary permissions.
#line 4
 
#line 4
# Old domain may exec the file and transition to the new domain.
#line 4
allow init testA_exec:file { getattr open read execute map };
#line 4
allow init testA:process transition;
#line 4
# New domain is entered by executing the file.
#line 4
allow testA testA_exec:file { entrypoint open read execute getattr map };
#line 4
# New domain can send SIGCHLD to its caller.
#line 4
 
#line 4
# Enable AT_SECURE, i.e. libc secure mode.
#line 4
dontaudit init testA:process noatsecure;
#line 4
# XXX dontaudit candidate but requires further study.
#line 4
allow init testA:process { siginh rlimitinh };
#line 4
 
#line 4
# Make the transition occur by default.
#line 4
type_transition init testA_exec:process testA;
#line 4
 
#line 4
 
#line 4
type testA_tmpfs, file_type;
#line 4
type_transition testA tmpfs:file testA_tmpfs;
#line 4
allow testA testA_tmpfs:file { read write getattr map };
#line 4
allow testA tmpfs:dir { getattr search };
#line 4
 
#line 4
 
 
allow testA console_device:chr_file { { getattr open read ioctl lock map } { open append write lock map } };
allow testA testA_exec:file { execute_no_trans };
allow testA vendor_toolbox_exec:file { execute_no_trans };
allow testA vendor_data_file:dir { create reparent rename rmdir setattr { { open getattr read search ioctl lock } { open search write add_name remove_name lock } } };
allow testA vendor_data_file:file { create rename setattr unlink { { getattr open read ioctl lock map } { open append write lock map } } };
allow testA vendor_file:file { execute_no_trans };
allow testA mnt_media_rw_file:dir { search };
allow testA vfat:file { getattr read open };
allow testA vfat:dir { search };
allow testA block_device:dir { search };
 
 
allow testA activity_service:service_manager { find };
allow testA system_server:binder { call transfer };
 
#line 22
# Call the servicemanager and transfer references to it.
#line 22
allow testA servicemanager:binder { call transfer };
#line 22
# servicemanager performs getpidcon on clients.
#line 22
allow servicemanager testA:dir search;
#line 22
allow servicemanager testA:file { read open };
#line 22
allow servicemanager testA:process getattr;
#line 22
# rw access to /dev/binder and /dev/ashmem is presently granted to
#line 22
# all domains in domain.te.
#line 22
 
allow testA shell_exec:file { read execute };
allow testA system_file:file { execute_no_trans };

3.2.3 cil文件

接着使用checkpolicy命令,将conf文件转成cil文件:

ASAN_OPTIONS=detect_leaks=0 checkpolicy -M -C -c 30 -o plat_sepolicy.cil plat_policy.conf

这个过程中,checkpolicy还会检查是否策略文件是否存在问题,比如是否违反neverallow,语法错误,方案自定义策略文件访问平台私有策略等等;check_assertions检查是否有违反neverallow的规则。

checkpolicy源码在:android/external/selinux/checkpolicy

违反neverallow:

[  6% 3/47] build out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows
FAILED: out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows
/bin/bash -c "(rm -f out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows ) && (ASAN_OPTIONS=detect_leaks=0 out/host/linux-x86/bin/checkpolicy -M -c           30 -o out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/policy.conf )"
libsepol.report_failure: neverallow on line 671 of system/sepolicy/public/domain.te (or line 10586 of policy.conf) violated by allow testA servicemanager:binder { call transfer };
libsepol.report_failure: neverallow on line 638 of system/sepolicy/public/domain.te (or line 10522 of policy.conf) violated by allow testA activity_service:service_manager { find };
libsepol.check_assertions: 2 neverallow failures occurred
Error while expanding policy
out/host/linux-x86/bin/checkpolicy:  loading policy configuration from out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/policy.conf
ninja: build stopped: subcommand failed.
01:32:50 ninja failed with: exit status 1

语法错误:

[  6% 3/47] build out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows
FAILED: out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows
/bin/bash -c "(rm -f out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows ) && (ASAN_OPTIONS=detect_leaks=0 out/host/linux-x86/bin/checkpolicy -M -c           30 -o out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/policy.conf )"
device/xxxxxx/common/sepolicy/testA.te:11:ERROR 'syntax error' at token 'allow' on line 47452:
allow testA system_server { call transfer };
allow testA activity_service:service_manager { find }
checkpolicy:  error(s) encountered while parsing configuration
out/host/linux-x86/bin/checkpolicy:  loading policy configuration from out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/policy.conf
ninja: build stopped: subcommand failed.
10:17:55 ninja failed with: exit status 1

方案自定义策略文件访问平台私有策略:

FAILED: out/target/product/xxxx/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil
/bin/bash -c "out/host/linux-x86/bin/build_sepolicy -a out/host/linux-x86/bin build_cil                 -i out/target/product/xxxx/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_policy.conf -m out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/reqd_policy_mask.cil -c ASAN_OPTIONS=detect_leaks=0          -b out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/plat_pub_policy.cil -d out/target/product/xxxx/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil out/target/product/xxxx/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil out/target/product/xxxx/obj/ETC/28.0.cil_intermediates/28.0.cil -f out/target/product/xxxx/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil                 -t 28.0 -p 30 -o out/target/product/xxxx/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil"
device/xxxxxx/common/sepolicy/testA.te:18:ERROR 'unknown type storaged' at token ';' on line 34318:
allow hal_memtrack_default storaged:dir { search };
#allow hal_memtrack_default rootdaemon:dir { search };
checkpolicy:  error(s) encountered while parsing configuration
out/host/linux-x86/bin/checkpolicy:  loading policy configuration from out/target/product/xxxx/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_policy.conf
build_sepolicy - failed to run command: 'ASAN_OPTIONS=detect_leaks=0 out/host/linux-x86/bin/checkpolicy -C -M -c 30 -o out/target/product/xxxx/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_policy_raw.cil out/target/product/xxxx/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_policy.conf' (ret:1)
ninja: build stopped: subcommand failed.
10:24:07 ninja failed with: exit status 1

转出来cil之后:

(type testA)
(roletype object_r testA)
(type testA_exec)
(roletype object_r testA_exec)
(type testA_tmpfs)
(roletype object_r testA_tmpfs)
 
(typeattributeset file_type (...... testA_exec testA_tmpfs ......))
(typeattributeset exec_type (...... testA_exec ......))
(typeattributeset vendor_file_type (...... testA_exec ......))
(typeattributeset binder_in_vendor_violators (testA apkinstalldata xbug))
(typeattributeset vendor_executes_system_violators (testA apkinstalldata))
(typeattributeset domain (...... testA ......))
 
(allow init_28_0 testA_exec (file (read getattr map execute open)))
(allow init_28_0 testA (process (transition)))
(allow testA testA_exec (file (read getattr map execute entrypoint open)))
(dontaudit init_28_0 testA (process (noatsecure)))
(allow init_28_0 testA (process (siginh rlimitinh)))
(typetransition init_28_0 testA_exec process testA)
(typetransition testA tmpfs_28_0 file testA_tmpfs)
(allow testA testA_tmpfs (file (read write getattr map)))
(allow testA tmpfs_28_0 (dir (getattr search)))
(allow testA console_device_28_0 (chr_file (ioctl read write getattr lock append map open)))
(allow testA testA_exec (file (execute_no_trans)))
(allow testA vendor_toolbox_exec_28_0 (file (execute_no_trans)))
(allow testA vendor_data_file_28_0 (dir (ioctl read write create getattr setattr lock rename add_name remove_name reparent search rmdir open)))
(allow testA vendor_data_file_28_0 (file (ioctl read write create getattr setattr lock append map unlink rename open)))
(allow testA vendor_file_28_0 (file (execute_no_trans)))
(allow testA mnt_media_rw_file_28_0 (dir (search)))
(allow testA vfat_28_0 (file (read getattr open)))
(allow testA vfat_28_0 (dir (search)))
(allow testA block_device_28_0 (dir (search)))
(allow testA property_socket_28_0 (sock_file (write)))
(allow testA init_28_0 (unix_stream_socket (connectto)))
(allow testA activity_service_28_0 (service_manager (find)))
(allow testA system_server_28_0 (binder (call transfer)))
(allow testA servicemanager_28_0 (binder (call transfer)))
(allow servicemanager_28_0 testA (dir (search)))
(allow servicemanager_28_0 testA (file (read open)))
(allow servicemanager_28_0 testA (process (getattr)))
(allow testA shell_exec_28_0 (file (read execute)))
(allow testA system_file_28_0 (file (execute_no_trans)))

3.2.4 检查

在最后会用secilc编译一次cil文件,检查是否有问题

经历过上面的一个过程,编译出所有需要的cil,最后就会调用secilc命令编译所有的cil文件,生成二进制策略文件precompiled_sepolicy

简单的说,这个过程就是:

te -> conf -> cil -> precompiled_sepolicy
cil这个在很早的Android版本,比如5.1的时候是还没有的,是后面的版本才添加的

4. 将策略文件加载到kernel

接着回到加载selinux 策略的初始化过程

4.1 selinux_android_load_policy_from_fd

android/external/selinux/libselinux/src/android/android_platform.c

int selinux_android_load_policy_from_fd(int fd, const char *description)
{
    int rc;
    struct stat sb;
    void *map = NULL;
    static int load_successful = 0;
 
    /*
        * Since updating policy at runtime has been abolished
        * we just check whether a policy has been loaded before
        * and return if this is the case.
        * There is no point in reloading policy.
        */
    if (load_successful){
        selinux_log(SELINUX_WARNING, "SELinux: Attempted reload of SELinux policy!/n");
        return 0;
    }
 
    set_selinuxmnt(SELINUXMNT);  //设置SELinux mount的位置  /sys/fs/selinux
    if (fstat(fd, &sb) < 0) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not stat %s:  %s\n",
                description, strerror(errno));
        return -1;
    }
    map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not map %s:  %s\n",
                description, strerror(errno));
        return -1;
    }
 
    rc = security_load_policy(map, sb.st_size); //加载sepolicy
    if (rc < 0) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not load policy:  %s\n",
                strerror(errno));
        munmap(map, sb.st_size);
        return -1;
    }
 
    munmap(map, sb.st_size);
    selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", description);
    load_successful = 1;
    return 0;
}

4.2 security_load_policy(libselinux)

android/external/selinux/libselinux/src/load_policy.c

int security_load_policy(void *data, size_t len)
{
    char path[PATH_MAX];
    int fd, ret;
 
    if (!selinux_mnt) {
        errno = ENOENT;
        return -1;
    }
 
    snprintf(path, sizeof path, "%s/load", selinux_mnt);
    fd = open(path, O_RDWR | O_CLOEXEC);
    if (fd < 0)
        return -1;
 
    ret = write(fd, data, len);
    close(fd);
    if (ret < 0)
        return -1;
    return 0;
}

这里是把文件内容读取出来后,直接写入节点:/sys/fs/selinux/load

4.3 /sys/fs/selinux/load

接着往下就要看kernel的代码了
android/common/security/selinux/selinuxfs.c

static struct tree_descr selinux_files[] = {
        [SEL_LOAD] = {"load", &sel_load_ops, S_IRUSR|S_IWUSR},
        [SEL_ENFORCE] = {"enforce", &sel_enforce_ops, S_IRUGO|S_IWUSR},
        [SEL_CONTEXT] = {"context", &transaction_ops, S_IRUGO|S_IWUGO},
        [SEL_ACCESS] = {"access", &transaction_ops, S_IRUGO|S_IWUGO},
        [SEL_CREATE] = {"create", &transaction_ops, S_IRUGO|S_IWUGO},
        [SEL_RELABEL] = {"relabel", &transaction_ops, S_IRUGO|S_IWUGO},
        [SEL_USER] = {"user", &transaction_ops, S_IRUGO|S_IWUGO},
        [SEL_POLICYVERS] = {"policyvers", &sel_policyvers_ops, S_IRUGO},
        [SEL_COMMIT_BOOLS] = {"commit_pending_bools", &sel_commit_bools_ops, S_IWUSR},
        [SEL_MLS] = {"mls", &sel_mls_ops, S_IRUGO},
        [SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR},
        [SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO},
        [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
        [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
        [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
        [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
        [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
        [SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
                    S_IWUGO},
        /* last one */ {""}
    };
 
static const struct file_operations sel_load_ops = {
    .write  = sel_write_load,
    .llseek = generic_file_llseek,
};

接着是 sel_write_load

static ssize_t sel_write_load(struct file *file, const char __user *buf,
                size_t count, loff_t *ppos)
 
{
    ......
 
    length = security_load_policy(data, count);
    if (length)
        goto out;
 
    ......
}

4.4 security_load_policy(kernel)

android/common/security/selinux/ss/services.c

if (!ss_initialized) {
        avtab_cache_init();
        rc = policydb_read(&policydb, fp);
        if (rc) {
            avtab_cache_destroy();
            goto out;
        }
 
        policydb.len = len;
        rc = selinux_set_mapping(&policydb, secclass_map,
                     ¤t_mapping,
                     ¤t_mapping_size);
        if (rc) {
            policydb_destroy(&policydb);
            avtab_cache_destroy();
            goto out;
        }
 
        rc = policydb_load_isids(&policydb, &sidtab);
        if (rc) {
            policydb_destroy(&policydb);
            avtab_cache_destroy();
            goto out;
        }
 
        security_load_policycaps();
        ss_initialized = 1;
        seqno = ++latest_granting;
        selinux_complete_init();
        avc_ss_reset(seqno);
        selnl_notify_policyload(seqno);
        selinux_status_update_policyload(seqno);
        selinux_netlbl_cache_invalidate();
        selinux_xfrm_notify_policyload();
        goto out;
    }

通过policydb_read把数据读取保存到policydb这个数据结构里面

policydb是一个结构体

定义在:android/common/security/selinux/ss/policydb.h

struct policydb {
    int mls_enabled;
 
    /* symbol tables */
    struct symtab symtab[SYM_NUM];
#define p_commons symtab[SYM_COMMONS]
#define p_classes symtab[SYM_CLASSES]
#define p_roles symtab[SYM_ROLES]
#define p_types symtab[SYM_TYPES]
#define p_users symtab[SYM_USERS]
#define p_bools symtab[SYM_BOOLS]
#define p_levels symtab[SYM_LEVELS]
#define p_cats symtab[SYM_CATS]
 
    /* symbol names indexed by (value - 1) */
    struct flex_array *sym_val_to_name[SYM_NUM];
 
    /* class, role, and user attributes indexed by (value - 1) */
    struct class_datum **class_val_to_struct;
    struct role_datum **role_val_to_struct;
    struct user_datum **user_val_to_struct;
    struct flex_array *type_val_to_struct_array;
 
    /* type enforcement access vectors and transitions */
    struct avtab te_avtab;
 
    /* role transitions */
    struct role_trans *role_tr;
 
    /* file transitions with the last path component */
    /* quickly exclude lookups when parent ttype has no rules */
    struct ebitmap filename_trans_ttypes;
    /* actual set of filename_trans rules */
    struct hashtab *filename_trans;
 
    /* bools indexed by (value - 1) */
    struct cond_bool_datum **bool_val_to_struct;
    /* type enforcement conditional access vectors and transitions */
    struct avtab te_cond_avtab;
    /* linked list indexing te_cond_avtab by conditional */
    struct cond_node *cond_list;
 
    /* role allows */
    struct role_allow *role_allow;
 
    /* security contexts of initial SIDs, unlabeled file systems,
       TCP or UDP port numbers, network interfaces and nodes */
    struct ocontext *ocontexts[OCON_NUM];
 
    /* security contexts for files in filesystems that cannot support
       a persistent label mapping or use another
       fixed labeling behavior. */
    struct genfs *genfs;
 
    /* range transitions table (range_trans_key -> mls_range) */
    struct hashtab *range_tr;
 
    /* type -> attribute reverse mapping */
    struct flex_array *type_attr_map_array;
 
    struct ebitmap policycaps;
 
    struct ebitmap permissive_map;
 
    /* length of this policy when it was loaded */
    size_t len;
 
    unsigned int policyvers;
 
    unsigned int reject_unknown : 1;
    unsigned int allow_unknown : 1;
 
    u16 process_class;
    u32 process_trans_perms;
};

将安全策略从用户空间加载到 SELinux LSM 模块中去了,这个数据结构很复杂

te规则最后会保存在

/* type enforcement access vectors and transitions */
struct avtab te_avtab;

另外还有服务、属性等初始化,有兴趣可以结合大佬的博客一起看看:

还记得最开头有提到,在android/device/xxxxxx/common/sepolicy和android/system/sepolicy都要添加 xxx.te,而有些方案却不用?

分析mk之后,不难发现,是因为去掉了这个:
在这里插入图片描述
然后检查对比的动作没有做了

三.总结

到这里,总结一下:

1、从Android8.0之后,加载的是 /(vendor|odm)/etc/selinux/precompiled_sepolicy ,以前的版本用的是 /sepolicy,如下图
在这里插入图片描述

2、有些平台上有odm分区,因此用的是 /odm/etc/selinux/precompiled_sepolicy,这也就是为什么前面提到的编译替换/vendor/etc/selinux/和/system/etc/selinux/没有效果的原因

关于单独编译和替换,下一篇会提到

四. 参考文章

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值