layout | title | category | description | tags |
---|---|---|---|---|
post |
进程替换 |
进程 |
替换进程... |
进程替换 execve |
可以通过新代码替换现存的程序,即可以启用新程序。Linux提供的execve系统调用可以用于该目的。C标准库中有其他的exec变体,但最终都基于execve。
execve的入口点是体系结构相关的sys_execve函数,该函数很快将工作委托给体系结构无关的do_execve例程。
{% highlight c++ %} int do_execve(char * filename, char __user *__user *argv, char __user *__user *envp, struct pt_regs * regs){ } {% endhighlight %}
这里不仅用参数传递了寄存器集合和可执行文件的名称(filename),而且还传递了指向程序的参数和环境的指针,这里的记号稍微有些笨拙,因为argv和envp都是指针数组,而且指向的两个数组自身的指针以及数组中的所有指针都位于虚地址的用户空间部分。
do_execve代码流程图如下:
首先打开要执行的文件,内核找到相关的inode并生成一个文件描述符,用于寻址该文件。bprm_init接下来处理若干管理性任务,例如mm_alloc生成一个新的mm_struct实例来管理进程地址空间。init_new_context是一个特定于体系结构的函数,用于初始化该实例,而__bprm_mm_init则建立初始的栈。
prepare_binprm用于提供一些父进程相关的值,特别是有效的UID和GID,剩余的数据,参数列表直接复制到结构中。简约代码如下:
{% highlight c++ %} int prepare_binprm(struct linux_binprm bprm) { if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) { / Set-uid? */ if (mode & S_ISUID) { bprm->per_clear |= PER_CLEAR_ON_SETID; bprm->cred->euid = inode->i_uid; }
/* Set-gid? */
/*
* If setgid is set but no group execute bit then this
* is a candidate for mandatory locking, not a setgid
* executable.
*/
if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
bprm->per_clear |= PER_CLEAR_ON_SETID;
bprm->cred->egid = inode->i_gid;
}
}
} {% endhighlight %}
在确认文件来源卷在装载是没有置位MNT_NOSUID之后,内核会监测SUID或SGID是否置位。第一种情况很容易处理,如果S_ISUID置位,那么有效的UID和inode相同,否则使用进程的有效UID。SGID类似,但内核还需要确认组执行位也已经置位。
search_binary_handler用于在do_execve结束是查找一种适当的二进制格式,用于所要执行的特定文件。可以看作根据不同的可执行文件格式来选择适当的程序。二进制格式处理程序负责将新程序的数据加载到旧的地址空间中。通常,二进制格式处理程序执行下列操作。
- 释放原进程使用的所有资源。
- 将应用程序隐射到虚拟地址空间中。
- 设置进程的指令指针和其他特定于体系结构的寄存器,以便在调度程序选择该进程的时候开始执行。