问题描述
a78项目在烧录GSI之后无法开机,init部分不断重启(正常可以开机),log类似如下:
[ 4.688635][T1600001] init: init: Opening SELinux policy
[ 4.690264][T1600001] init: init: Falling back to standard signature check. TODO implement support for fsverity SEPolicy.
[ 4.691655][T1600001] init: init: Error: Apex SEPolicy failed signature check
[ 4.692858][T1600001] init: init: Loading APEX Sepolicy from /system/etc/selinux/apex/SEPolicy.zip
[ 4.693995][T1600001] init: init: Failed to open package /system/etc/selinux/apex/SEPolicy.zip: No such file or directory
# 此处后续没有sepolicy编译加载的log,
[ 5.380006][T1400305] secilc: [name:exit&]secilc (305) used greatest stack depth: 10624 bytes left
问题分析
上述log初步怀疑是selinux加载出现异常,查看selinux加载代码(alps/system/core/init/selinux.cpp):
int SetupSelinux(char** argv) {
// ...
PrepareApexSepolicy();
// Read the policy before potentially killing snapuserd.
std::string policy;
ReadPolicy(&policy);
//...
LoadSelinuxPolicy(policy);
}
void ReadPolicy(std::string* policy) {
PolicyFile policy_file;
bool ok = IsSplitPolicyDevice() ? OpenSplitPolicy(&policy_file)
: OpenMonolithicPolicy(&policy_file);
}
bool OpenSplitPolicy(PolicyFile* policy_file) {
// ...
if (!use_userdebug_policy) {
// 查找预编译sepolicy,正常会走,烧录gsi之后不会执行(hash值判定失败,会重新compile)
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;
}
}
}
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;
}
正常开机selinux初始化时,会对比,system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256与vendor/etc/selinux/precompiled_sepolicy.system_ext_sepolicy_and_mapping.sha256中的哈希值(包括其他分区的,这里写system_ext是因为我们客制化的权限都添加在了system_ext中),如果发现哈希值不一致,那么就会调用seclic命令在线编译一次(烧录GSI之后,一定会走编译,因为哈希值一定不一样),此哈希值是在编译时期,根据添加的所有的权限文件内容,生成的,命令类似(cat $(in) | sha256sum | cut -d' ' -f1 > $(out),所以如果system,vendor的编译内容不一致,那么生成的哈希值一定会不一样。
在烧录GSI之后,会使用secilc命令重新编译sepolicy,编译成功之后继续加载政策, 因此怀疑是否是此处编译出现异常。因为ForkExecveAndWaitForCompletion此函数回去执行编译命令,如果编译失败,就会直接退出进程,会导致init进程异常。
// 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;
因此准备验证是否是编译问题:
首先,将gsi中的system.img挂载在电脑上(直接双击打开就行,使用mount挂载反而会失败,可能是命令参数问题),然后根据代码中需要的参数准备文件:
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
};
plat_policy_cil_file:/system/etc/selinux/plat_sepolicy.cil,使用GSI中的文件version_as_string:30,来自于SEPOLICY_VERSION,在system/core/init/Android.bp中指定:-DSEPOLICY_VERSION=30plat_mapping_file:"/system/etc/selinux/mapping/" + vend_plat_vers + ".cil,使用GSI文件,vend_plat_vers在/vendor/etc/selinux/plat_sepolicy_vers.txt指定,一般为平台版本,此处是34,所以这里使用34.0.cil文件- 后面再接
vendor部分的参数 完整命令如下:
/home5/develop/u0/alps/out/full_g78v78c2k_dfl_eea-userdebug/out_hal/host/linux-x86/bin/secilc ./plat_sepolicy.cil -m -M true -G -N -c 30 ./34.0.cil -o ./output ./plat_pub_versioned.cil ./vendor_sepolicy.cil
执行结果出现错误:
Type proc_agold is invalid for role object_r
Invalid context
Invalid genfscon at ./vendor_sepolicy.cil:45
Failed to verify cil database
Failed to verify cil database
Post process failed
Failed to compile cildb: -1
提示proc_agold找不到,但是在vendor的plat_pub_versioned是有定义的。
这个类型代码中是定义在common中的,表示为private与vendor公用,说明此处这样定义会找不到。
修改方案
调整原先存在vendor中genfs_context文件位置,放在private中,type定义还是在common中。
原先存放vendor中,在烧录了GSI之后,在线编译sepolicy时,
会找不到定义的类型(即使放在了common中,但是无法识别到)导致sepolicy编译报错,从而无法开机。
修改之后,将 vendor 中的 plat_pub_versioned.cil,vendor_sepolicy.cil替换上述命令中的对应的文件,再次执行验证,无错误信息输出,编译成功,说明修改符合预期。
FAQ
由于部分文件,设备需要在开机才创建,无法提标记权限,因此会有很多文件,设备使用genfscon命令标记,在使用此命令的时候需要注意:
- 如果文件,设备要绑定的
type,要在system,vendor端使用,那么type要在common中定义,即system,vendor都能读到,但是genfscon一定要在private绑定,不要在vendor绑定, - 如果文件,设备要绑定的
type,只在system端使用,那么type可以在private定义,同时genfscon也在private中绑定 - 如果文件,设备要绑定的
type,只在vendor端使用,那么type必须在vendor定义,同时genfscon也在vendor中绑定