最近在Androd P平台上开发一个功能,有一段逻辑需要通过system_server进程对proc/vm/sys/下的某个节点进行写值操作。考虑到时间有限,目前最有效的方法就是借助init.rc中的on property触发机制来完成。
大致逻辑如下:
system_server:在必要处调用SystemProperties.set("sys.sysctl.xxxx","1"); //此处参考ProcessList.java中对与sys.sysctl.extra_free_kbytes的处理方式;
配置相关SELinux权限;
在init.target.rc/init.project.rc等类似的rc文件中添加:
on property sys.sysctl.xxxx=1
write /proc/sys/vm/xxx 1
一切都看上去那么美好,而且与sys.sysctl.extra_free_kbytes的处理方式几乎一模一样。
然而运行后才发现,这个触发器并没有任何效果!
初步推断有以下几种可能:
1、SELinux配置不正确/SystemProperties.set没有生效或者报错;
2、on property触发器没有接收到/没有处理此属性;
3、write节点被权限拒绝;
通过adb root/setenforce 0以后验证,属性是可以写成功的,因此排除1的可能;
通过adb root/setenforce 0以后直接echo节点,也是可以生效的,因此排除3的可能;
那么剩下的,就是2了:
查看dmesg发现开机解析rc文件时,有如下输出:
[ 4.165212] .(3)[1:init]init: /vendor/etc/init/hw/init.project.rc: 116: ParseTriggers() failed: unexported property tigger found: sys.sysctl.xxx
倒跟代码发现判断逻辑是这样的:
system/core/init/action_parser.cpp:
Result<Success> ParsePropertyTrigger(const std::string& trigger, Subcontext* subcontext,
std::map<std::string, std::string>* property_triggers) {
const static std::string prop_str("property:");
std::string prop_name(trigger.substr(prop_str.length()));
size_t equal_pos = prop_name.find('=');
if (equal_pos == std::string::npos) {
return Error() << "property trigger found without matching '='";
}
std::string prop_value(prop_name.substr(equal_pos + 1));
prop_name.erase(equal_pos);
if (!IsActionableProperty(subcontext, prop_name)) {
//此处报错
return Error() << "unexported property tigger found: " << prop_name;
}
if (auto [it, inserted] = property_triggers->emplace(prop_name, prop_value); !inserted) {
return Error() << "multiple property triggers found for same property";
}
return Success();
}
而 函数IsActionableProperty就在同一文件下:
bool IsActionableProperty(Subcontext* subcontext, const std::string& prop_name) {
static bool enabled = GetBoolProperty("ro.actionable_compatible_property.enabled", false);
if (subcontext == nullptr || !enabled) {
return true;
}
if (kExportedActionableProperties.count(prop_name) == 1) {
return true;
}
for (const auto& prefix : kPartnerPrefixes) {
if (android::base::StartsWith(prop_name, prefix)) {
return true;
}
}
return false;
}
kExportedActionableProperties与kPartnerPrefixes是定义在同目录下stable_properties.h中:
static constexpr const char* kPartnerPrefixes[] = {
"init.svc.vendor.", "ro.vendor.", "persist.vendor.", "vendor.", "init.svc.odm.", "ro.odm.",
"persist.odm.", "odm.", "ro.boot.",
};
static const std::set<std::string> kExportedActionableProperties = {
"dev.bootcomplete",
"init.svc.console",
"init.svc.mediadrm",
"init.svc.surfaceflinger",
"init.svc.zygote",
"persist.bluetooth.btsnoopenable",
"persist.sys.crash_rcu",
"persist.sys.usb.usbradio.config",
"persist.sys.zram_enabled",
"ro.board.platform",
"ro.bootmode",
"ro.build.type",
"ro.crypto.state",
"ro.crypto.type",
"ro.debuggable",
"sys.boot_completed",
"sys.boot_from_charger_mode",
"sys.retaildemo.enabled",
"sys.shutdown.requested",
"sys.usb.config",
"sys.usb.configfs",
"sys.usb.ffs.mtp.ready",
"sys.usb.ffs.ready",
"sys.user.0.ce_available",
"sys.vdso",
"vold.decrypt",
"vold.post_fs_data_done",
"vts.native_server.on",
"wlan.driver.status",
};
可以看到,我们的属性sys.sysctl.xxx既不在kExportedActionableProperties中,也不是kPartnerPrefixes所列的前缀开头,因此不能被识别为可以用来进行触发操作的有效的属性;
到这里,似乎都说通了,解决方法就是添加白名单,或者修改属性的前缀。
但是!有个问题,为什么sys.sysctl.extra_free_kbytes就可以呢?同样不在kExportedActionableProperties中,也不是kPartnerPrefixes所列的前缀开头。为什么就可以呢?
根据打log分析,发现该属性在此处便返回了:
if (subcontext == nullptr || !enabled) {
return true;
}
后者默认为true,应该是Google VTS要求项,因此不会变,也不能变;
那么就是前者为nullptr的原因咯。
逆向跟踪,看看subcontext在什么情况下为nullptr;
同样还在action_parser.cpp中:
Result<Success> ActionParser::ParseSection(std::vector<std::string>&& args,
const std::string& filename, int line) {
std::vector<std::string> triggers(args.begin() + 1, args.end());
if (triggers.size() < 1) {
return Error() << "Actions must have a trigger";
}
Subcontext* action_subcontext = nullptr;
if (subcontexts_) {
for (auto& subcontext : *subcontexts_) {
//此处判断这个rc文件(带完整路径)的前缀
if (StartsWith(filename, subcontext.path_prefix())) {
action_subcontext = &subcontext;
break;
}
}
}
std::string event_trigger;
std::map<std::string, std::string> property_triggers;
if (auto result = ParseTriggers(triggers, action_subcontext, &event_trigger, &property_triggers);
!result) {
return Error() << "ParseTriggers() failed: " << result.error();
}
auto action = std::make_unique<Action>(false, action_subcontext, filename, line, event_trigger,
property_triggers);
action_ = std::move(action);
return Success();
}
可以看到这里有做判断,只有当filename符合path_prefix()的要求时,action_subcontext才会被赋值,而action_subcontext就是后续传到ParseTrigger中的subcontext;
而这个前缀定义是在同目录下的subcontext.cpp中:
const std::string kInitContext = "u:r:init:s0";
const std::string kVendorContext = "u:r:vendor_init:s0";
const char* const paths_and_secontexts[2][2] = {
{"/vendor", kVendorContext.c_str()},
{"/odm", kVendorContext.c_str()},
};
至此,真相大白,我修改的init.project.rc存放在vendor下,因此会被上述的kExportedActionableProperties与kPartnerPrefixes所限制,而sys.sysctl.extra_free_kbytes是写在/init.rc,属于/根挂载点下的文件,不会被过滤;
鉴于Google对于VTS的要求愈发严格,因此不建议将自己新增的属性添加到/根挂载点的init.rc或/system下的init.xxx.rc中;
而是遵循kPartnerPrefixes的要求,使用符合规范的前缀的属性,老老实实添加在vendor下的各种rc文件中吧;
以上,踩坑经历讲述完毕。