安卓服务自杀之谜
在把ndstub服务自启动弄好后,格蠹的小伙伴又开始配置gird服务自启动了,按照ndstub的经验来说,配置gird服务应该会比较顺利,但是没想到又踩到了另一个坑里面去,于是写下此文,帮助遇到相同问题的有缘人!
服务为何频频自杀?
dmesg为何深夜惨叫?
logcat为何屡遭毒手?
服务无法启动究竟是何人所为?
SELinux究竟是人是鬼?
究竟是道德的沦丧?还是人性的缺失?
让我们一起来走近科学……
2392与2393
格蠹的小伙伴按照ndstub的方式去配置gird后,”不小心“又踩到了另一个坑内。。。
大致情况就是系统会不停的给服务发SIGKILL,此后演变成了服务不停重启,系统不停的发SIGKILL。
隐约有种感觉,这次的事情好像大了啊!
starting service 'gird'...
03-14 03:44:07.890 148 148 W auditd : type=1401 audit(0.0:2454): op=security_compute_sid invalid_context=u:r:gird:s0 scontext=u:r:init:s0 tcontext=u:object_r:gird_exec:s0 tclass=process
03-14 03:44:07.924 2392 2392 I gird : gird rev. 1.0 started with pid 2392
03-14 03:44:07.923 148 148 W auditd : type=1401 audit(0.0:2455): op=security_compute_sid invalid_context=u:r:gird:s0 scontext=u:r:gird:s0 tcontext=u:r:gird:s0 tclass=unix_dgram_socket
03-14 03:44:07.923 148 148 W auditd : type=1401 audit(0.0:2456): op=security_compute_sid invalid_context=u:r:gird:s0 scontext=u:r:gird:s0 tcontext=u:r:gird:s0 tclass=unix_dgram_socket
03-14 03:44:07.926 2393 2393 E gird : gird change pid with 2393
03-14 03:44:07.925 148 148 W auditd : type=1401 audit(0.0:2457): op=security_compute_sid invalid_context=u:r:gird:s0 scontext=u:r:gird:s0 tcontext=u:r:gird:s0 tclass=unix_dgram_socket
01-01 00:09:12.642 0 0 I init : Service 'gird' (pid 2392) exited with status 0
01-01 00:09:12.642 0 0 I init : Sending signal 9 to service 'gird' (pid 2392) process group...
仔细看一下上面的内容,大致可以分成3个部分2392、2393、init。
2392进程起来后,由于是后台服务,所以根据代码逻辑,gird会fork出来一个子进程,创建完子进程后,父进程会退出Service 'gird' (pid 2392) exited with status 0
,但是这个时候系统又发送了SIGKILL9给2392进程组,此时2392就连带着2393一块消失了。
这操作瞬间让我想到狼人杀!夜晚猎人被狼刀了,白天猎人开枪带走平民,最后狼人获得胜利!这简直就是一模一样的操作啊,本着狼人杀结束后复盘的惯例,我们也要复盘一下,看看2393它为什么会消失!
Daemon
先手动起一下gird。
rk3328_box:/ # /system/bin/gird ?
/system/bin/gird ?
发现没有问题,再测试一下gird的功能,发现是可以正常工作的。
问题来了啊,凭什么手动起,系统不发SIGKILL,当成服务启动,就会发啊!
经过上面的测试我们可以知道
- deamon去fork进程的时候成功了,确实出来一个2393的进程
- gird相关功能没有问题,应该不会导致系统发SIGKILL
- gird服务结束的是比较早的
- 2392父进程父进程退出是应该
- 由于系统发送SIGKILL,导致2392的一个进程组都挂掉了
那么现在就该把视野聚焦在main函数内了。看看main函数,在它里面唯一能和内核关联到一起的,就只有这个daemon了。
if ((argc < 2) && daemon(1, 1) == -1)
{
syslog(LOG_LOCAL6 | LOG_ERR, "Fatal Error: Failed to create GEDU IR Control Daemon, error: %d\n", errno);
exit(EXIT_FAILURE);
}
但是由于2393的存在,我们可以知道daemon没有fork进程失败。但是手工起gird的时候跳过了daemon,难道通过daemon的方式起来就会有问题?
改下代码,默认就不以daemon的方式启动,加-s参数才以daemon的方式启动,再试一次。
终于没问题了。。。
不过这又是为什么,安卓怎么这么奇怪,后台服务不让用daemon的方式启动,
KillProcessGroup
通过搜索sending signal可以发现,是下面代码做出SIGKILL的动作。看这样子安卓是惯犯了,杀死这些进程,不是因为它们做了什么越界的行为,而是安卓有意为之啊。
考虑到安卓进程保活是一个难题,在这也就不详细展开讲了,对这方面有更多需求的同学,可以继续探究下去。
void Service::KillProcessGroup(int signal, bool report_oneshot) {
// If we've already seen a successful result from killProcessGroup*(), then we have removed
// the cgroup already and calling these functions a second time will simply result in an error.
// This is true regardless of which signal was sent.
// These functions handle their own logging, so no additional logging is needed.
if (!process_cgroup_empty_) {
LOG(INFO) << "Sending signal " << signal << " to service '" << name_ << "' (pid " << pid_
<< ") process group...";
int max_processes = 0;
int r;
if (signal == SIGTERM) {
r = killProcessGroupOnce(proc_attr_.uid, pid_, signal, &max_processes);
} else {
r = killProcessGroup(proc_attr_.uid, pid_, signal, &max_processes);
}
if (report_oneshot && max_processes > 0) {
LOG(WARNING)
<< "Killed " << max_processes
<< " additional processes from a oneshot process group for service '" << name_
<< "'. This is new behavior, previously child processes would not be killed in "
"this case.";
}
if (r == 0) process_cgroup_empty_ = true;
}
if (oom_score_adjust_ != DEFAULT_OOM_SCORE_ADJUST) {
LmkdUnregister(name_, pid_);
}
参考
https://source.android.google.cn/security/selinux?hl=zh-cn
https://events.static.linuxfound.org/sites/events/files/slides/abs2014_seforandroid_smalley.pdf