在上篇文字《Linux 单用户模式patch解析》(传送门:Hello小崔:5.4 Linux 单用户模式commit代码解析)的最后有个疑问,为什么在单用户模式下useradd操作仍然可以执行成功(su操作却失败),单用户模式不应该屏蔽所有用户操作吗?难道是因为patch漏了对内核中useradd函数的禁用?
带着上面疑问,展开了我的探索之旅。
查找linux内核代码中useradd接口
开始想的是只要找到内核useradd函数,然后仿照patch的操作,使用#ifdef CONFIG_MULTIUSER屏蔽掉函数,然后添加到sys_in.c中,不就可以禁止useradd操作了吗?
然后linux代码下执行
#grep "useradd" -rn *
#grep "adduser" -rn *
竟然没找到相关函数。
#vim include/linux/syscalls.h
也没找到添加用户的接口,难道用户添加接口名称不是这些?
strace和ftrace查看用户态->内核态调用流程
既然无法直接搜到,那么我ftrace抓一下关键函数总可以了吧。
#cd /sys/kernel/debug/tracing
#echo 0 > tracing_on; echo "" > trace
#echo 1 > tracing_on ; useradd cbx; echo 0 > tracing_on
#cat trace > ftrace.log
#strace useradd cbx1 > strace.log
抓到ftrace.log和strace.log,仔细查看调用流程(篇幅有限,截取关键流程):
ftrace.log
passwd-850 [000] .... 923.403649: do_sys_open <-do_syscall_64
passwd-850 [000] .... 923.403653: getname <-do_sys_open
passwd-850 [000] .... 923.403656: getname_flags <-do_sys_open
passwd-850 [000] .... 923.403676: get_unused_fd_flags <-do_sys_open
passwd-850 [000] .... 923.403680: __alloc_fd <-do_sys_open
passwd-850 [000] .... 923.403692: do_filp_open <-do_sys_open
passwd-850 [000] .... 923.404027: put_unused_fd <-do_sys_open
passwd-850 [000] .... 923.404967: putname <-do_sys_open
passwd-850 [000] .... 923.404971: kmem_cache_free <-do_sys_open
passwd-850 [000] .... 923.404995: __ia32_sys_vfork <-do_syscall_64
addgroup-851 [001] .... 923.409951: __x64_sys_execve <-do_syscall_64
addgroup-851 [001] .... 923.409954: getname <-__x64_sys_execve
addgroup-851 [001] .... 923.409957: getname_flags <-__x64_sys_execve
addgroup-851 [001] .... 923.409981: __do_execve_file.isra.54 <-__x64_sys_execve
addgroup-851 [001] .... 923.411965: __x64_sys_execve <-do_syscall_64
addgroup-851 [001] .... 923.411968: getname <-__x64_sys_execve
addgroup-851 [001] .... 923.411971: getname_flags <-__x64_sys_execve
addgroup-851 [001] .... 923.411988: __do_execve_file.isra.54 <-__x64_sys_execve
passwd-850 [000] .... 923.425624: __x64_sys_wait4 <-do_syscall_64
passwd-850 [000] .... 923.425627: kernel_wait4 <-__do_sys_wait4
addgroup-851 [001] .... 923.430820: __x64_sys_newuname <-do_syscall_64
中间主要是do_sys_open,getname,up_write,down_write_killable,__ia32_sys_getuid,__x64_sys_setpgid等等。唯一相关的sys_getuid和sys_setpgid,但也不是add user相关的函数,其他都是打开文件,getname等。
strace.log
…
utime("/etc/passwd-", [2018/08/30-16:02:14, 2018/08/30-16:02:09]) = 0
close(5) = 0
munmap(0x7fcece621000, 4096) = 0
umask(0777) = 022
open("/etc/passwd+", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 5
umask(022) = 0777
fchown(5, 0, 0) = 0
fchmod(5, 0644) = 0
fstat(5, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcece621000
write(5, "at:x:25:25:Batch jobs daemon:/va"..., 3317) = 3317
fsync(5) = 0
close(5) = 0
munmap(0x7fcece621000, 4096) = 0
lstat("/etc/passwd", {st_mode=S_IFREG|0644, st_size=3285, ...}) = 0
rename("/etc/passwd+", "/etc/passwd") = 0
…
munmap(0x7fcece621000, 4096) = 0
utime("/etc/shadow-", [2018/08/30-16:02:14, 2018/08/30-16:02:09]) = 0
close(7) = 0
munmap(0x7fcece61f000, 4096) = 0
umask(0777) = 022
open("/etc/shadow+", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 5
…
也没有类似添加用户的系统调用发现。这时候隐隐感觉,内核可能真的没有添加用户的操作,那到底谁来管理用户呢。
从useradd源码入手
#which useradd
/usr/sbin/useradd
#rpm -qf /usr/sbin/useradd
shadow-4.1.5.1-14.4.x86_64
useradd命令来源于shadow命令包,下载,解压
#wget https://ftp.osuosl.org/pub/blfs/conglomeration/shadow/shadow-4.1.5.1.tar.bz2
#tar –xvf shadow-4.1.5.1.tar.bz2
#cd shadow-4.1.5.1/src/
# vim useradd.c
// 找到main函数
/*
* main - useradd command
*/
int main (int argc, char **argv)
{
…
#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
{
struct passwd *pampw;
pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
if (pampw == NULL) {
fprintf (stderr,
_("%s: Cannot determine your user name.n"),
Prog);
fail_exit (1);
}
//
retval = pam_start ("useradd", pampw->pw_name, &conv, &pamh);
}
这里
pam_start (“useradd”, pampw->pw_name, &conv, &pamh);
看来就是添加用户的操作函数,然后在shadow源码中竟然无法搜到pam_start()函数实现,难道调用其他库了。果断google发现,竟然发现了大家伙:PAM。
PAM介绍
参考:
PAM (简体中文) - ArchWiki
linux系统之pam模块-和风细雨-51CTO博客
https://www.ibm.com/developerworks/cn/linux/l-pam/index.html
深入 Linux PAM 体系结构
简单来说,使用PAM之前,login(telnet,ssh)之类操作应用程序都是在/etc/passwd中查找用户名,但是这种方式非常不安全,并且验证复杂。
Linux PAM( Pluggable Authentication Modules ) 提供了一个框架,用于进行系统级的用户认证。
PAM 可以使程序开发与认证方式细节分离,而是在程序运行时调用“认证”模型完成工作。认证模型可以由本地系统管理员通过配置进行选择。
PAM提供了一系列的模块(库)以及调用接口,支持认证管理(auth),账号管理(account),会话管理(session)和密码(password)管理。(PAM介绍、接口和配置可以自行查看上面链接介绍)
所以Linux对于用户管理任务交给了Linux-PAM,自己不参与实现,所以也就不会存在adduser接口。
后记
useradd原来通过shadow包最终调用到PAM接口,由PAM服务添加用户,实现用户、用户组管理。所以即使单用户模式下(CONFIG_MULTIUSER=n),无法通过修改内核代码阻止用户态的PAM管理(想修改也力不从心),所以仍然可以添加用户。