Linux-0.11 kernel目录进程管理sys.c详解
Linux-0.11 kernel目录进程管理sys.c详解
模块简介
在sys.c模块中,有很多关于进程id、进程组id、用户id、用户组id的系统调用。 另外在该文件,诸如sys_ftime
,sys_break
等函数在Linux-0.11版本中尚未实现。
该程序中含有许多进程ID(pid),进程组ID(pgrp或pgid),用户ID(uid)、用户组ID(gid)、实际用户ID(ruid)、有效用户ID(euid)以及会话ID(session)等的操作函数。
一个用户有用户ID(uid)和用户组ID(gid)。这两个ID是passwd文件中对用户设置的,通常被称之为实际用户ID(ruid)和实际组ID(rgid)。而在每个文件的i节点信息中都保存着宿主的用户ID和组ID,主要用于访问或者执行文件时的权限判断操作。另外在一个进程的任务数据结构中,为了实现不同功能而保存了3种用户ID和组ID。如下所示:
类别 | 用户ID | 组ID |
---|---|---|
进程的 | uid - 用户ID。指明拥有该进程的用户 | gid - 组ID。指明拥有该进程的用户组 |
有效的 | euid - 有效用户ID。指明访问文件的权限 | egid - 有效组ID。指明访问文件的权限 |
保存的 | suid - 保存的用户ID。当执行文件的设置用户ID标志(set-user-ID置位时,suid中保存着执行文件的uid,否则suid等于进程的euid | sgid - 保存的组ID。当执行文件的设置组ID标志set-group-ID置位时,sgid中保存着执行文件的gid。否则sgid等于进程的egid。 |
函数详解
sys_setregid
int sys_setregid(int rgid, int egid)
该函数用于设置进程的实际组id或者有效组id。
首先看第一个if分支。有两种条件会修改rgid:
- current->gid 等于 rgid,等于啥都没有做,无效代码。
- 超级用户。
首先看第一个if分支。有四种条件会修改rgid:
- current->gid 等于 egid ,rgid等于要设置的egid,可以设置。
- current->egid 等于 egid,等于啥都没有做,无效代码。
- current->sgid等于 egid,sgid等于要设置的egid,可以设置。
- 超级用户。
这里的逻辑和现代系统一致。sys_setreuid
似乎和现代系统有略微区别。
if (rgid>0) {
if ((current->gid == rgid) ||
suser())
current->gid = rgid;
else
return(-EPERM);
}
if (egid>0) {
if ((current->gid == egid) ||
(current->egid == egid) ||
(current->sgid == egid) ||
suser())
current->egid = egid;
else
return(-EPERM);
}
return 0;
sys_time
int sys_time(long * tloc)
该函数的作用是返回从1970年1月1日 00:00:00开始到此刻的秒数。该方法是time
的系统调用, 入参是一个time_t
类型的数据。
#include <time.h>
time_t time(time_t *timer);
由于参数是一个指针,而其所指位置在用户空间,因此需要使用函数put_fs_long
来访问该值。在进入内核中运行时,段寄存器fs被默认地指向当前用户数据空间。因此该函数就可利用fs
来访问用户空间中的值。
int i;
i = CURRENT_TIME;
if (tloc) {
verify_area(tloc,4);
put_fs_long(i,(unsigned long *)tloc);
}
return i;
CURRENT_TIME
的定义如下所示, 等于系统的开机时间加上目前的滴答数/100。这里除以100的原因是定时器发出中断的频率是100Hz,也即没10ms发出一次时钟中断。
CURRENT_TIME (startup_time+jiffies/HZ)
sys_setreuid
int sys_setreuid(int ruid, int euid)
该函数的作用是设置实际用户id(ruid)和有效用户id(euid)。
现代系统的setuid
逻辑如下:
- 当可执行文件的开启了设置用户 id 位后,执行该文件后 (execve),该进程的 euid 和 suid 将被设置为该文件的 owner。
- 如果当前进程的 euid 是 root,则 setuid 会设置所有的 ruid、euid 和 suid。
- 如果当前进程的 euid 不是 root,则 setuid 只会设置 euid,且这个 euid 的可选值只能是 ruid 或 suid,也就是说,一个由开启了设置用户 id 位的可执行文件启动的进程,这个进程 euid 可以在 ruid 和 suid 之间来回切换。
这里的逻辑和现代系统略微不同。
首先看第一个if分支。有三种条件会修改ruid。
- current->euid 等于 ruid,这种情况现代系统不会修改ruid
- old_ruid 等于 ruid,由于old_ruid = current->uid,所以等于没有修改。(无效代码)
- suser,超级用户会修改ruid,和现代系统一致。
接着看第二个if分支。有三种条件会修改euid:
- old_ruid 等于euid ,这种情况和现代系统一致。
- current->euid 等于 euid,等于没有修改。(无效代码)
- suser,超级用户会修改euid,和现代系统一致。
int old_ruid = current->uid;
if (ruid>0) {
if ((current->euid==ruid) ||
(old_ruid == ruid) ||
suser())
current->uid = ruid;
else
return(-EPERM);
}
if (euid>0) {
if ((old_ruid == euid) ||
(current->euid == euid) ||
suser())
current->euid = euid;
else {
current->uid = old_ruid;
return(-EPERM);
}
}
return 0;
现代系统的行为可以参考Q&A。
sys_setuid
int sys_setuid()
该函数用设置任务uid。其内部调用sys_setreuid
函数实现。
return(sys_setreuid(uid, uid));
sys_stime
int sys_stime()
该函数的作用是获取开机时间的秒数。
调用进程必须具有超级用户权限。其中HZ=100,是内核系统运行频率。由于参数是一个指针,而其所指位置在用户空间,因此需要使用函数get_fs_long
来访问该值。在进入内核中运行时,段寄存器fs
被默认地指向用户数据空间。因此该函数就可利用fs
来访问用户空间中的值。函数参数提供的当前时间值减去系统已经运行的时间秒值(jeffies/HZ)
即是开机时间秒值。
if (!suser())
return -EPERM;
startup_time = get_fs_long((unsigned long *)tptr) - jiffies/HZ;
return 0;
sys_times
int sys_times(struct tms * tbuf)
该函数的作用是获取当前进程的时间统计值。
其通过put_fs_long
将PCB中和时间相关的数据拷贝到tbuf
中。utime
代表用户态运行时间,stime
代表内核态运行时间,cutime
代表子进程用户运行时间,cstime
代表子进程内核态运行时间。
if (tbuf) {
verify_area(tbuf,sizeof *tbuf);
put_fs_long(current->utime,(unsigned long *)&tbuf->tms_utime);
put_fs_long(current->stime,(unsigned long *)&tbuf->tms_stime);
put_fs_long(current->cutime,(unsigned long *)&tbuf->tms_cutime);
put_fs_long(current->cstime,(unsigned long *)&tbuf->tms_cstime);
}
return jiffies;
sys_brk
int sys_brk(unsigned long end_data_seg)
该函数的作用用于设置堆区的指针brk的值。
当参数end_data_seg
数值合理,并且系统确实有足够的内存,而且进程没有超越其最大数据段大小时,该函数设置数据段末尾为end_data_seg
指定的值。该值必须大于代码结尾并且要小于堆栈结尾16KB.返回值是数据段的新结尾值(如果返回值与要求值不同,则表明有错误发生)。该函数并不被用户直接调用,而由libc库函数进行包装,并且返回值也不一样。
if (end_data_seg >= current->end_code &&
end_data_seg < current->start_stack - 16384)
current->brk = end_data_seg;
return current->brk;
sys_setpgid
int sys_setpgid(int pid, int pgid)
该函数的作用是设置指定进程pid的进程组号为pgid。
int i;
if (!pid)
pid = current->pid;
if (!pgid)
pgid = current->pid;
for (i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->pid==pid) {
if (task[i]->leader)//如果不是会话leader,则没有权限
return -EPERM;
if (task[i]->session != current->session)//必须要属于同一个会话
return -EPERM;
task[i]->pgrp = pgid;
return 0;
}
return -ESRCH;
sys_getpgrp
int sys_getpgrp(void)
该函数用于返回当前进程的进程组号。
return current->pgrp;
sys_setsid
int sys_setsid(void)
该函数用于创建一个session,并设置进程为会话首领。
if (current->leader && !suser())//该进程已经是leader,但是不是超级用户,则返回-EPERM。
return -EPERM;
current->leader = 1;//设置leader = 1
current->session = current->pgrp = current->pid;//设置进程会话号
current->tty = -1;//设置进程没有控制中断
return current->pgrp;
sys_uname
int sys_uname(struct utsname * name)
该函数用于获取系统名称等信息。
static struct utsname thisname = {
"linux .0","nodename","release ","version ","machine "
};
int i;
if (!name) return -ERROR;
verify_area(name,sizeof *name);
for(i=0;i<sizeof *name;i++)
put_fs_byte(((char *) &thisname)[i],i+(char *) name);
return 0;
sys_umask
int sys_umask(int mask)
设置当前进程创建文件的属性屏蔽码为(mask & 0777)
。
0777
代表数字是一个八进制数字,即000111111111
。
int old = current->umask;
current->umask = mask & 0777;
return (old);
sys_setgid
int sys_setgid(int gid)
该函数用于设置进程组号。内部调用sys_setregid
实现。
return(sys_setregid(gid, gid));
未实现方法
sys_ftime
int sys_ftime()
未实现。
sys_break
int sys_break()
未实现。
sys_ptrace
int sys_ptrace()
用于当前进程对子进程进行调试。
sys_stty
int sys_stty()
改变并打印终端设置。
未实现。
sys_gtty
int sys_gtty()
获取进程终端信息。
未实现。
sys_rename
int sys_rename()
修改文件名。
未实现。
sys_prof
int sys_prof()
未实现。
sys_acct
int sys_acct()
未实现。
sys_phys
int sys_phys()
未实现。
sys_lock
int sys_lock()
未实现。
sys_mpx
int sys_mpx()
未实现。
sys_ulimit
int sys_ulimit()
未实现。
Q & A
1.ruid euid suid现代系统行为
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <grp.h>
int main() {
uid_t oruid, oeuid, osuid;
getresuid(&oruid, &oeuid, &osuid); // 非 POSIX.1 标准
uid_t ruid, euid, suid;
printf("origin\n");
getresuid(&ruid, &euid, &suid); // 非 POSIX.1 标准
printf("ruid: %d, euid: %d, suid: %d\n", ruid, euid, suid);
printf("\n");
printf("setuid(origin ruid = %d)\n", oruid);
setuid(oruid);
getresuid(&ruid, &euid, &suid); // 非 POSIX.1 标准
printf("ruid: %d, euid: %d, suid: %d\n", ruid, euid, suid);
printf("\n");
printf("setuid(origin suid = %d)\n", osuid);
setuid(osuid);
getresuid(&ruid, &euid, &suid); // 非 POSIX.1 标准
printf("ruid: %d, euid: %d, suid: %d\n", ruid, euid, suid);
return 0;
}
首先进行编译 sudo gcc 03-set-uid-bit.c
。
然后将开启该文件的设置用户 id 位,并将该可执行文件的 owner 设置为 root,并执行该测试程序 sudo chown root:root a.out && sudo chmod u+s a.out && ./a.out
,输出如下。
origin
ruid: 1000, euid: 0, suid: 0
setuid(origin ruid = 1000)
ruid: 1000, euid: 1000, suid: 1000
setuid(origin suid = 0)
ruid: 1000, euid: 1000, suid: 1000
然后将开启该文件的设置用户 id 位,并将该可执行文件的 owner 设置为 普通用户,并执行该测试程序 sudo chown root:root a.out && sudo chmod u+s a.out && ./a.out
,输出如下。
origin
ruid: 1000, euid: 1001, suid: 1001
setuid(origin ruid = 1000)
ruid: 1000, euid: 1000, suid: 1001
setuid(origin suid = 1001)
ruid: 1000, euid: 1001, suid: 1001
总结:
- 当可执行文件的开启了设置用户 id 位后,执行该文件后 (execve),该进程的 euid 和 suid 将被设置为该文件的 owner。
- 如果当前进程的 euid 是 root,则 setuid 会设置所有的 ruid、euid 和 suid。
- 如果当前进程的 euid 不是 root,则 setuid 只会设置 euid,且这个 euid 的可选值只能是 ruid 或 suid,也就是说,一个由开启了设置用户 id 位的可执行文件启动的进程,这个进程 euid 可以在 ruid 和 suid 之间来回切换。