当前位置:   article > 正文

Android系统启动流程(二)——Selinux初始化(基于Android13)_安卓 selinux

安卓 selinux

1 简述

Selinux,全称为Security-Enhanced Linux,即安全增强型Linux,是Linux中的一种安全管控策略。传统的Linux访问控制通过DAC(Discretionary Access Control)控制,即给用户、用户组、其他用户授予不同的读写和可执行权限来控制文件的访问。Selinux基于MAC(Mandatory Access Control)控制,基于安全上下文和安全策略的安全机制,只有定义了允许访问的规则的才能访问,否则默认不能访问。
MAC在DAC的基础之上,首先进行DAC检查,然后才进行MAC检查,检查通过后才能进行访问。

2 Selinux基础

违背了selinux规则会打印日志,日志关键字为avc

LocTimerMsgTask: type=1400 audit(0.0:8): avc: denied { call } for scontext=u:r:location:s0 tcontext=u:r:qtidataservices_app:s0:c56,c256,c512,c768 tclass=binder permissive=0
  • 1

上面就是一个典型的selinux问题,透露出了以下信息

scontext=u:r:location:s0 请求访问者的selinux标签,这个通常是进程
tcontext=u:r:qtidataservices_app:s0 被请求者的selinux标签,通常是文件或者服务等
tclass=binder 被请求的类型是binder
denied { call } 表示请求者对访问者请求被selinux拒绝的操作是call操作
permissive=0 selinux拒绝了这个访问
  • 1
  • 2
  • 3
  • 4
  • 5

接下来一一解析上面的,首先是selinux标签

  • u:r:location:s0这个就是selinux的上下文信息,其中u指用户user,r指角色role,location是类型type,s0是range,表示安全级别。所以selinux的基本格式为user:role:type[:range]。在Android中,只有一个user,一个range。而所有的进程,都是role都是r,所有的文件,role都是object_r。
  • tclass表示的是所要访问的对象的类别,通常有file,binder,dir等
  • call是操作,针对不同的class,有不同的操作,比如对于file来说,有create,remove等文件的操作,对于binder来说,就有bind,call这种操作
  • permissive是selinux的模式,selinux可以设置两种模式,通常默认是enforcing模式,即强制模式,所有没有通过selinux检查的操作都被阻止,并打印日志,这里的permissive=0表示的就是enforcing模式。还有一种就是permissive宽容模式,违反selinux规则的操作并不会被阻止,二是允许其访问,但是会打印avc日志。

对于上面的avc报错,如果需要修改的话,需要添加如下:

allow location qtidataservices_app:binder {call};
  • 1

这个就是对avc日志的修改,允许location这个type,对qtidataservices_app的binder进行call的操作。这一行通常添加到location.te文件中,因为很多模块都有自己的te文件进行管理。
通常在domain.te中定义了相关的selinux的neverallow规则,如果在添加allow规则时,编译出现neverallow规则,则需要进行相应的修改。

3 init初始化selinux

system/core/init/main.cpp

if (!strcmp(argv[1], "selinux_setup")) {
    return SetupSelinux(argv);
}
  • 1
  • 2
  • 3

system/core/init/selinux.cpp

int SetupSelinux(char** argv) {
	...
	// Read the policy before potentially killing snapuserd.
    std::string policy;
    ReadPolicy(&policy); //读取策略文件
    CleanupApexSepolicy();
    ...
    LoadSelinuxPolicy(policy); //感觉像是写入内核,外部依赖的selinux开源项目代码
    ...
	SelinuxSetEnforcement(); //设置selinux模式为enforcing
	...
    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args)); //再次调用init可执行文件,参数为second_stage
    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3.1 读取selinux

首先看ReadPolicy

void ReadPolicy(std::string* policy) {
    PolicyFile policy_file;

    bool ok = IsSplitPolicyDevice() ? OpenSplitPolicy(&policy_file)
                                    : OpenMonolithicPolicy(&policy_file);
    if (!ok) {
        LOG(FATAL) << "Unable to open SELinux policy";
    }

    if (!android::base::ReadFdToString(policy_file.fd, policy)) {
        PLOG(FATAL) << "Failed to read policy file: " << policy_file.path;
    }
}
bool IsSplitPolicyDevice() {
    return access(plat_policy_cil_file, R_OK) != -1;
}
constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

IsSplitPolicyDevice用于判断设备是不是分策略设备,android8之后将策略拆分为平台策略和供应商策略。所以目前的版本,这个函数返回为true,是根据手机中这个plat_sepolicy.cil文件是否存在来判断的。
在这里插入图片描述
很明显,这个文件是存在的。
所以将调用OpenSplitPolicy,盲猜这个就是实际添加策略的函数了,传入的参数是policy_file,首先看看PolicyFile是个啥

struct PolicyFile {
    unique_fd fd;
    std::string path;
};
  • 1
  • 2
  • 3
  • 4

PolicyFile是一个结构体,包含一个文件描述符和一个string字符串的path。

bool OpenSplitPolicy(PolicyFile* policy_file) {
    // IMPLEMENTATION NOTE: Split policy consists of three or more CIL files:
    // * platform -- policy needed due to logic contained in the system image,
    // * vendor -- 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.
    // * (optional) policy needed due to logic on product, system_ext, odm, or apex.
    // secilc is invoked to compile the above three policy files into a single monolithic policy
    // file. This file is then loaded into the kernel.
    //android8以后策略分离。分离的策略至少包含3部分以上,platform平台策略,vendor供应商策略,mapping用于向后兼容的策略。其他的策略则包括product,system_ext,odm或者是apex。secilc就是将上面的这些策略文件编译成一个单个的整体策略文件,然后加载到内核中。

    const auto userdebug_plat_sepolicy = GetUserdebugPlatformPolicyFile();
    const bool use_userdebug_policy = userdebug_plat_sepolicy.has_value();
    if (use_userdebug_policy) {
        LOG(INFO) << "Using userdebug system sepolicy " << *userdebug_plat_sepolicy;
    }

    // Load precompiled policy from vendor image, if a matching policy is found there. The policy
    // must match the platform policy on the system image.
    // use_userdebug_policy requires compiling sepolicy with userdebug_plat_sepolicy.cil.
    // Thus it cannot use the precompiled policy from vendor image.
    if (!use_userdebug_policy) {
        if (auto res = FindPrecompiledSplitPolicy(); res.ok()) {
            unique_fd fd(open(res->c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
            if (fd != -1) {
                policy_file->fd = std::move(fd);
                policy_file->path = std::move(*res);
                return true;
            }
        } else {
            LOG(INFO) << res.error();
        }
    }
    // No suitable precompiled policy could be loaded

    LOG(INFO) << "Compiling SELinux policy";

    // 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 plat_mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");

    std::string plat_compat_cil_file("/system/etc/selinux/mapping/" + vend_plat_vers +
                                     ".compat.cil");
    if (access(plat_compat_cil_file.c_str(), F_OK) == -1) {
        plat_compat_cil_file.clear();
    }

    std::string system_ext_policy_cil_file("/system_ext/etc/selinux/system_ext_sepolicy.cil");
    if (access(system_ext_policy_cil_file.c_str(), F_OK) == -1) {
        system_ext_policy_cil_file.clear();
    }

    std::string system_ext_mapping_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +
                                        ".cil");
    if (access(system_ext_mapping_file.c_str(), F_OK) == -1) {
        system_ext_mapping_file.clear();
    }

    std::string system_ext_compat_cil_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +
                                           ".compat.cil");
    if (access(system_ext_compat_cil_file.c_str(), F_OK) == -1) {
        system_ext_compat_cil_file.clear();
    }

    std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil");
    if (access(product_policy_cil_file.c_str(), F_OK) == -1) {
        product_policy_cil_file.clear();
    }

    std::string product_mapping_file("/product/etc/selinux/mapping/" + vend_plat_vers + ".cil");
    if (access(product_mapping_file.c_str(), F_OK) == -1) {
        product_mapping_file.clear();
    }

    std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");
    if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
        LOG(ERROR) << "Missing " << vendor_policy_cil_file;
        return false;
    }

    std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
    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();
    }

    // apex_sepolicy.cil is default but optional.
    std::string apex_policy_cil_file("/dev/selinux/apex_sepolicy.cil");
    if (access(apex_policy_cil_file.c_str(), F_OK) == -1) {
        apex_policy_cil_file.clear();
    }
    const std::string version_as_string = std::to_string(SEPOLICY_VERSION);

    // clang-format off
    std::vector<const char*> compile_args {
        "/system/bin/secilc",
        use_userdebug_policy ? *userdebug_plat_sepolicy : plat_policy_cil_file,
        "-m", "-M", "true", "-G", "-N",
        "-c", version_as_string.c_str(),
        plat_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_compat_cil_file.empty()) {
        compile_args.push_back(plat_compat_cil_file.c_str());
    }
    if (!system_ext_policy_cil_file.empty()) {
        compile_args.push_back(system_ext_policy_cil_file.c_str());
    }
    if (!system_ext_mapping_file.empty()) {
        compile_args.push_back(system_ext_mapping_file.c_str());
    }
    if (!system_ext_compat_cil_file.empty()) {
        compile_args.push_back(system_ext_compat_cil_file.c_str());
    }
    if (!product_policy_cil_file.empty()) {
        compile_args.push_back(product_policy_cil_file.c_str());
    }
    if (!product_mapping_file.empty()) {
        compile_args.push_back(product_mapping_file.c_str());
    }
    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());
    }
    if (!apex_policy_cil_file.empty()) {
        compile_args.push_back(apex_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);

    policy_file->fd = std::move(compiled_sepolicy_fd);
    policy_file->path = compiled_sepolicy;
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166

接下来分析下这个函数

// 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

首先,使用mkostemp函数创建了一个临时的文件,并返回其文件描述符compiled_sepolicy_fd

std::string system_ext_policy_cil_file("/system_ext/etc/selinux/system_ext_sepolicy.cil");
if (access(system_ext_policy_cil_file.c_str(), F_OK) == -1) {
    system_ext_policy_cil_file.clear();
}
  • 1
  • 2
  • 3
  • 4

类似这种是遍历各个镜像包中对应目录下是否有编译后的selinux规则文件

// clang-format off
std::vector<const char*> compile_args {
    "/system/bin/secilc",
    use_userdebug_policy ? *userdebug_plat_sepolicy : plat_policy_cil_file,
    "-m", "-M", "true", "-G", "-N",
    "-c", version_as_string.c_str(),
    plat_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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这里创建了一个vector,并存储了一些默认参数。第一个参数是一个可执行文件。

if (!plat_compat_cil_file.empty()) {
    compile_args.push_back(plat_compat_cil_file.c_str());
}
  • 1
  • 2
  • 3

上面遍历了哪些镜像中存在cil文件,这里把存在的都添加到compile_args这个vector中。

if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
    unlink(compiled_sepolicy);
    return false;
}
unlink(compiled_sepolicy);
  • 1
  • 2
  • 3
  • 4
  • 5

添加完成后调用ForkExecveAndWaitForCompletion函数,传入的参数是第一个就是上面的那个可执行文件,第二个参数是整个vector的数据指针。

// Forks, executes the provided program in the child, and waits for the completion in the parent.
// Child's stderr is captured and logged using LOG(ERROR).
bool ForkExecveAndWaitForCompletion(const char* filename, char* const argv[]) {
    // Create a pipe used for redirecting child process's output.
    // * pipe_fds[0] is the FD the parent will use for reading.
    // * pipe_fds[1] is the FD the child will use for writing.
    int pipe_fds[2];
    if (pipe(pipe_fds) == -1) {
        PLOG(ERROR) << "Failed to create pipe";
        return false;
    }

    pid_t child_pid = fork();
    if (child_pid == -1) {
        PLOG(ERROR) << "Failed to fork for " << filename;
        return false;
    }

    if (child_pid == 0) {
        // fork succeeded -- this is executing in the child process

        // Close the pipe FD not used by this process
        close(pipe_fds[0]);

        // Redirect stderr to the pipe FD provided by the parent
        if (TEMP_FAILURE_RETRY(dup2(pipe_fds[1], STDERR_FILENO)) == -1) {
            PLOG(ERROR) << "Failed to redirect stderr of " << filename;
            _exit(127);
            return false;
        }
        close(pipe_fds[1]);

        if (execv(filename, argv) == -1) {
            PLOG(ERROR) << "Failed to execve " << filename;
            return false;
        }
        // Unreachable because execve will have succeeded and replaced this code
        // with child process's code.
        _exit(127);
        return false;
    } else {
        // fork succeeded -- this is executing in the original/parent process

        // Close the pipe FD not used by this process
        close(pipe_fds[1]);

        // Log the redirected output of the child process.
        // It's unfortunate that there's no standard way to obtain an istream for a file descriptor.
        // As a result, we're buffering all output and logging it in one go at the end of the
        // invocation, instead of logging it as it comes in.
        const int child_out_fd = pipe_fds[0];
        std::string child_output;
        if (!android::base::ReadFdToString(child_out_fd, &child_output)) {
            PLOG(ERROR) << "Failed to capture full output of " << filename;
        }
        close(child_out_fd);
        if (!child_output.empty()) {
            // Log captured output, line by line, because LOG expects to be invoked for each line
            std::istringstream in(child_output);
            std::string line;
            while (std::getline(in, line)) {
                LOG(ERROR) << filename << ": " << line;
            }
        }

        // Wait for child to terminate
        int status;
        if (TEMP_FAILURE_RETRY(waitpid(child_pid, &status, 0)) != child_pid) {
            PLOG(ERROR) << "Failed to wait for " << filename;
            return false;
        }

        if (WIFEXITED(status)) {
            int status_code = WEXITSTATUS(status);
            if (status_code == 0) {
                return true;
            } else {
                LOG(ERROR) << filename << " exited with status " << status_code;
            }
        } else if (WIFSIGNALED(status)) {
            LOG(ERROR) << filename << " killed by signal " << WTERMSIG(status);
        } else if (WIFSTOPPED(status)) {
            LOG(ERROR) << filename << " stopped by signal " << WSTOPSIG(status);
        } else {
            LOG(ERROR) << "waitpid for " << filename << " returned unexpected status: " << status;
        }

        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

创建了两个管道,用于子进程和父进程之间的通信。子进程中通过execv执行传入的可执行文件,执行参数就是存储各个镜像包中selinux规则cil的文件路径,父进程等待子进程执行完成后返回。这里就是将所有的策略文件编译成一个整体。
这里执行完成后执行unlink函数应该是减少一个文件的访问进程数。因为ForkExecveAndWaitForCompletion中创建了一个子进程,也访问了这个文件描述符,但是子进程已经执行完了。

policy_file->fd = std::move(compiled_sepolicy_fd);
policy_file->path = compiled_sepolicy;
  • 1
  • 2

最后将这个临时fd和fd的路径封装到policy_file中返回。

3.2 加载selinux

void ReadPolicy(std::string* policy) {
    PolicyFile policy_file;

    bool ok = IsSplitPolicyDevice() ? OpenSplitPolicy(&policy_file)
                                    : OpenMonolithicPolicy(&policy_file);
    if (!ok) {
        LOG(FATAL) << "Unable to open SELinux policy";
    }

    if (!android::base::ReadFdToString(policy_file.fd, policy)) {
        PLOG(FATAL) << "Failed to read policy file: " << policy_file.path;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

ReadFdToString将policy从fd中读取出来,返回给policy这个字符串。
读取policy结束后,接下来分析LoadSelinuxPolicy,加载policy,这个函数传入的参数就是ReadPolicy中的policy,是一个字符串。

static void LoadSelinuxPolicy(std::string& policy) {
    LOG(INFO) << "Loading SELinux policy";

    set_selinuxmnt("/sys/fs/selinux");
    if (security_load_policy(policy.data(), policy.size()) < 0) {
        PLOG(FATAL) << "SELinux:  Could not load policy";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

sys/fs目录是linux中描述文件系统的,手机中存在selinux目录。
external/selinux/libselinux/src/init.c

void set_selinuxmnt(const char *mnt)
{
	selinux_mnt = strdup(mnt);
}
``
这段代码已经位于selinux核心库中了,这里是设置selinux文件目录,即sys/fs/selinux。
***external/selinux/libselinux/src/load_policy.c***
```cpp
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

将selinux的cil文件路径写到了sys/fs/selinux/load中,应该就是将编译后的整个策略文件加载进内核。后面怎么处理,目前尚不清楚,查阅发现selinux很多检查处于内核,所以后面应该是内核处理访问权限,并报avc日志。

3.3 设置selinux模式

selinux分为enforcing强制模式和permissive宽容模式,默认是开启强制模式的

void SelinuxSetEnforcement() {
    bool kernel_enforcing = (security_getenforce() == 1);
    bool is_enforcing = IsEnforcing();
    if (kernel_enforcing != is_enforcing) {
        if (security_setenforce(is_enforcing)) {
            PLOG(FATAL) << "security_setenforce(" << (is_enforcing ? "true" : "false")
                        << ") failed";
        }
    }

    if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result.ok()) {
        LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

首先判断内核设置的selinux模式
external/selinux/libselinux/src/getenforce.c

int security_getenforce(void)
{
	int fd, ret, enforce = 0;
	char path[PATH_MAX];
	char buf[20];

	if (!selinux_mnt) {
		errno = ENOENT;
		return -1;
	}

	snprintf(path, sizeof path, "%s/enforce", selinux_mnt);
	fd = open(path, O_RDONLY | O_CLOEXEC);
	if (fd < 0)
		return -1;

	memset(buf, 0, sizeof buf);
	ret = read(fd, buf, sizeof buf - 1);
	close(fd);
	if (ret < 0)
		return -1;

	if (sscanf(buf, "%d", &enforce) != 1)
		return -1;

	return !!enforce;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

这里是读取sys/fs/selinux/enforce来获取内核设置的selinux模式。
再来看看IsEnforcing函数

bool IsEnforcing() {
    if (ALLOW_PERMISSIVE_SELINUX) {
        return StatusFromProperty() == SELINUX_ENFORCING;
    }
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

ALLOW_PERMISSIVE_SELINUX是在init的bp文件中添加的宏定义,对于debug模式的才设置为1,非debug模式设置为0。所以对于非debug模式,默认为enforcing。

EnforcingStatus StatusFromProperty() {
    EnforcingStatus status = SELINUX_ENFORCING;

    ImportKernelCmdline([&](const std::string& key, const std::string& value) {
        if (key == "androidboot.selinux" && value == "permissive") {
            status = SELINUX_PERMISSIVE;
        }
    });

    if (status == SELINUX_ENFORCING) {
        ImportBootconfig([&](const std::string& key, const std::string& value) {
            if (key == "androidboot.selinux" && value == "permissive") {
                status = SELINUX_PERMISSIVE;
            }
        });
    }

    return status;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这里判断enforcing模式有两个判断,一个是启动模块发送给内核的cmdline参数,另一个是属性androidboot.selinux是否为permissive,只要其中一个为permissive,则是permissive的。

void SelinuxSetEnforcement() {
    bool kernel_enforcing = (security_getenforce() == 1);
    bool is_enforcing = IsEnforcing();
    if (kernel_enforcing != is_enforcing) {
        if (security_setenforce(is_enforcing)) {
            PLOG(FATAL) << "security_setenforce(" << (is_enforcing ? "true" : "false")
                        << ") failed";
        }
    }

    if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result.ok()) {
        LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

判断之后,如果内核的selinux与IsEnforcing获取到的不一致,则以获取的为准。所以如果我们想把整个都设置成permissive的,则将is_enforcing这个变量设置成false即可。也可以通过IsEnforcing函数里面的判断,通过设置cmdline和属性来达到。

const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
  • 1
  • 2
  • 3

接下来回到selinux设置的主函数,这里selinux设置完成之后再次调用init,执行init的第二阶段启动。

4 总结

selinux初始化流程为:

  1. init进程中初始化第一阶段完成文件系统挂载及环境变量设置后,开始执行selinux的初始化工作
  2. 首先读取各个镜像中的cil文件,cil文件是各平台te文件编译之后的整合文件
  3. 调用secilc可执行文件编译各个cil文件,编译成一个整体的文件,并保存在dev/sepolicy.XXXXXX临时文件中
  4. 读取上面临时文件中编译后的单个selinux策略文件内容并加载到内核
  5. 设置selinux启动模式(默认为enforcing)
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/262733
推荐阅读
相关标签
  

闽ICP备14008679号