注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

用strace等待进程退出时遇到的"权限"问题  

2016-11-21 21:33:32|  分类: Linux内核 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、问题
有时候,重启一个进程分为两个独立的穿行步骤,第一步是停止进程,接下来启动进程,在进程退出之前不能再次启动同类进程。例如通常服务器进程都需要保证只有一个实例在运行,这种简单的单一性判断可以通过flock一个pid文件实现。所以在重启的时候首先要解决的问题就是如何判断进程已经完全退出,比较直观的通用方法是通过strace附加到目标进程上,当strace退出的时候,说明它附加的进程也已经退出了,这是一个通过最为熟悉的工具解决问题的思路。其它针对flock的实现方法是同样使用flock可执行程序来lock等待文件锁的释放,这种实现方法比较简单直观,我们先不讨论。
strace附加的时候按照正常的理解有两种可能,一种是附加的时候进程已经退出,此时附加失败;另一种是附加成功,通过同步等待strace的退出来判断被附加进程的退出。但是在实际执行的时候出现了第三种意想不到的现象,就是附加时提示“权限不足”的错误。这个错误有些让人莫名其妙,因为是同一个用户启动的服务器进程和strace进程,没有道理出现所谓的“权限不足”的情况。
为了演示这个现象,下面是一个模拟当时情况的简单shell命令,这里需要注意的时,在复现这个问题的时候,不要使用root用户。下面的命令执行了10次,出现了1一次权限问题,3次附加时进程已经退出的问题,剩余七次附加成功。当然这里并不是说这个概率是可以复现的,只是说明在执行的过程中的确有概率出现这个莫名其妙的权限问题。
tsecer@harry: whoami
tsecer
tsecer@harry: uname -a
Linux localhost.localdomain 3.11.10-301.fc20.x86_64 #1 SMP Thu Dec 5 14:01:17 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
tsecer@harry: for (( i = 0; i < 10; i++ )) do /usr/bin/ls > /dev/null & strace -qqp $!   2>&1 | grep PTRACE_ATTACH; done
[1] 24783
strace: attach: ptrace(PTRACE_ATTACH, ...): No such process
[1]+  Done                    /usr/bin/ls > /dev/null
[1] 24788
[1]+  Done                    /usr/bin/ls > /dev/null
[1] 24793
[1]+  Done                    /usr/bin/ls > /dev/null
[1] 24798
[1]+  Done                    /usr/bin/ls > /dev/null
[1] 24803
[1]+  Done                    /usr/bin/ls > /dev/null
[1] 24808
strace: attach: ptrace(PTRACE_ATTACH, ...): No such process
[1]+  Done                    /usr/bin/ls > /dev/null
[1] 24813
strace: attach: ptrace(PTRACE_ATTACH, ...): No such process
[1]+  Done                    /usr/bin/ls > /dev/null
[1] 24818
[1]+  Done                    /usr/bin/ls > /dev/null
[1] 24823
strace: attach: ptrace(PTRACE_ATTACH, ...): Operation not permitted
[2] 24828
[1]   Done                    /usr/bin/ls > /dev/null
[2]+  Done                    /usr/bin/ls > /dev/null
tsecer@harry: 
二、进程退出后内核流程
linux-2.6.21\kernel\exit.c
sys_exit===>>>do_exit
fastcall NORET_TYPE void do_exit(long code)
{
……
exit_mm(tsk);

if (group_dead)
acct_process();
exit_sem(tsk);
__exit_files(tsk);
__exit_fs(tsk);
exit_thread();
cpuset_exit(tsk);
exit_keys(tsk);

if (group_dead && tsk->signal->leader)
disassociate_ctty(1);

module_put(task_thread_info(tsk)->exec_domain->module);
if (tsk->binfmt)
module_put(tsk->binfmt->module);

tsk->exit_code = code;
proc_exit_connector(tsk);
exit_task_namespaces(tsk);
exit_notify(tsk);
        ……
}
这里的代码不用深究,对于我们遇到的问题只要注意到其中的exit_XXX之类的排比句中,进程内存空间的退出是较早的执行流程即可。
三、ptrace attach时的判断逻辑
linux-2.6.21\kernel\ptrace.c:sys_ptrace===>>>ptrace_attach===>>>may_attach
static int may_attach(struct task_struct *task)
{
/* May we inspect the given task?
* This check is used both for attaching with ptrace
* and for allowing access to sensitive information in /proc.
*
* ptrace_attach denies several cases that /proc allows
* because setting up the necessary parent/child relationship
* or halting the specified task is impossible.
*/
int dumpable = 0;
/* Don't let security modules deny introspection */
if (task == current)
return 0;
if (((current->uid != task->euid) ||
    (current->uid != task->suid) ||
    (current->uid != task->uid) ||
    (current->gid != task->egid) ||
    (current->gid != task->sgid) ||
    (current->gid != task->gid)) && !capable(CAP_SYS_PTRACE))
return -EPERM;
smp_rmb();
if (task->mm)
dumpable = task->mm->dumpable;
if (!dumpable && !capable(CAP_SYS_PTRACE))
return -EPERM;

return security_ptrace(current, task);
}
在判断是否可以附加的流程中,首先要求用户id和组id相同,由于我们的测试在同一个终端的用一个用户启动的,所以用户的判断是必定满足的。接下来的判断对于已经进入了exit系统调用,但是内核中资源还没有完全释放出来的进程就无法通过了。由于进程可能已经在exit中通过exit_mm释放了进程的地址空间,所以下面的指明无法执行,从而使用了dumpable的默认值0,而普通用户不具有capable(CAP_SYS_PTRACE)权限,所以此时出现权限不足的提示。
if (task->mm)
dumpable = task->mm->dumpable;
四、如果附加成功,是否strace退出的时候子进程的资源已全部释放?
从exit的系统代码来看,这个对于大部分用户态可见的资源来说,当通知父进程(strace)的时候,子进程的资源已经几乎释放殆尽,子进程进入ZOMBIE状态,在内核态只剩下一个task_struct结构。其实用户态最为关注的就是文件系统,下面再次看下进程退出的流程:
fastcall NORET_TYPE void do_exit(long code)
{
……
exit_notify(tsk);
#ifdef CONFIG_NUMA
mpol_free(tsk->mempolicy);
tsk->mempolicy = NULL;
#endif
/*
* This must happen late, after the PID is not
* hashed anymore:
*/
if (unlikely(!list_empty(&tsk->pi_state_list)))
exit_pi_state_list(tsk);
if (unlikely(current->pi_state_cache))
kfree(current->pi_state_cache);
/*
* Make sure we are holding no locks:
*/
debug_check_no_locks_held(tsk);

if (tsk->io_context)
exit_io_context();

if (tsk->splice_pipe)
__free_pipe_info(tsk->splice_pipe);

preempt_disable();
/* causes final put_task_struct in finish_task_switch(). */
tsk->state = TASK_DEAD;

schedule();
BUG();
/* Avoid "noreturn function does return".  */
for (;;)
cpu_relax(); /* For when BUG is null */
}
对于父进程的通知是通过exit_notify(tsk)完成,此时strace通过wait4阻塞的系统调用中唤醒并退出,由于exit_notify(tsk)前已经完成了所有用户可见资源的释放,所以此时在启动进程没有问题。所以对于开始提到的问题,如果使用root + strace判断进程是否已经完全退出,那么这个方案是可行的;相反,如果不是root用户,那么strace可能有效,也可能失败,也就是非root用户使用strace判断进程是否已经释放所有资源的方法并不可靠。
五、推荐的做法
网络上推荐的做法是向目标进程发送 0信号来判断目标进程是否存在,感觉这种方法其实还算是比较中规中矩的方法了,在没有其它“通用”方法的情况下,使用这个系统调用也行,但是缺点就是需要定时轮询,可能有一定延迟。从下面代码看,0信号只是检查了目标进程是否存在并且是否有权限,所以的确可以满足这里的需求。
sys_kill===>>>kill_something_info===>>>kill_pid_info===>>>group_send_sig_info
int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
unsigned long flags;
int ret;

ret = check_kill_permission(sig, info, p);

if (!ret && sig) {
ret = -ESRCH;
if (lock_task_sighand(p, &flags)) {
ret = __group_send_sig_info(sig, info, p);
unlock_task_sighand(p, &flags);
}
}

return ret;
}
  评论这张
 
阅读(91)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017