在内核级别,用户 ID 和 组 ID 是每个线程的属性,然而,POSIX 标准要求同一个进程里的多个线程共享相同的认证信息(credentials)。
在 NPTL (Native POSIX Threads Library)线程实现中,通过对某些系统调用进行封装,从而支持了这一要求。这里的封装函数包括 setuid(),它通过信号技术确保了当其中某个线程改变认证信息后,其他所有线程也会跟着改变对应的认证信息。
在 glibc 中,setuid() 最终会调用 __nptl_setxid(),其定义在 nptl/allocatestack.c 中:
int
attribute_hidden
__nptl_setxid (struct xid_command *cmdp)
{
...
list_for_each (runp, &__stack_user)
{
struct pthread *t = list_entry (runp, struct pthread, list);
if (t == self)
continue;
signalled += setxid_signal_thread (cmdp, t);
}
...
}
上述函数调用了 setxid_signal_thread() 来给其他线程发信号,其定义在 nptl/allocatestack.c 中:
static int
setxid_signal_thread (struct xid_command *cmdp, struct pthread *t)
{
if ((t->cancelhandling & SETXID_BITMASK) == 0)
return 0;
int val;
pid_t pid = __getpid ();
INTERNAL_SYSCALL_DECL (err);
val = INTERNAL_SYSCALL_CALL (tgkill, err, pid, t->tid, SIGSETXID);
/* If this failed, it must have had not started yet or else exited. */
if (!INTERNAL_SYSCALL_ERROR_P (val, err))
{
atomic_increment (&cmdp->cntr);
return 1;
}
else
return 0;
}
可以看到上述函数通过 tgkill() 系统调用给当前进程的 t->tid 线程发了一个 SIGSETXID 信号。
SIGSETXID 信号的处理函数是 sighandler_setxid(),其定义在 ./nptl/nptl-init.c 中:
/* We use the SIGSETXID signal in the setuid, setgid, etc. implementations to
tell each thread to call the respective setxid syscall on itself. This is
the handler. */
static void
sighandler_setxid (int sig, siginfo_t *si, void *ctx)
{
int result;
...
/* Safety check. It would be possible to call this function for
other signals and send a signal from another process. This is not
correct and might even be a security problem. Try to catch as
many incorrect invocations as possible. */
if (sig != SIGSETXID
|| si->si_pid != __getpid ()
|| si->si_code != SI_TKILL)
return;
INTERNAL_SYSCALL_DECL (err);
result = INTERNAL_SYSCALL_NCS (__xidcmd->syscall_no, err, 3, __xidcmd->id[0],
__xidcmd->id[1], __xidcmd->id[2]);
...
}
上述函数通过 INTERNAL_SYSCALL_NCS() 宏进行了系统调用,其中 __xidcmd->syscall_no 是 setuid() 的系统调用号。
不过,如果在线程中 通过 syscall() 来旁路系统调用后,上述 POSIX 要求就不满足了,例如:
//setuid(1001);
syscall(SYS_setuid, 1001);