思考
今天想到一个问题,为什么普通用户可以修改自己的密码?
我们知道/etc/passwd和/etc/shadow里面存放着用户的信息和密码,如果这两个文件被禁止写入,那么就不能创建用户和密码。
可是/etc/passwd的属主和属组都是root,普通用户应该只能读取才对啊?
回到我们的问题“普通用户修改密码”,用正常人的思维来捋一下。
假如:项目部的小A,现在有个合同需要盖章,但是这个章只有主管才有,那么这个合同能签,只有三种办法:
1.合同拿回去,给主管盖。(很明显不是这种,因为我们不是使用su或sudo)
2.问主管要章,自己来盖。
所以普通用户修改密码,也就是使用passwd的时候,是以“主管”的名义也就是root来执行的,我们查看一下/usr/bin/passwd,发现他的属主x位变成了s
执行的时候使用了S位的权限。
推断一下:我们使用passwd时,系统会默认提权使用root权限运行passwd.
内核管理机制
那么这个S位是啥呢?经过查找,发现Linux关于权限的除了UID,GID之外还有SUID(有效用户id),SGID(有效用户组id),粘滞位等
Linux内核会给每个进程关联两个和进程ID无关的用户ID:一个是真实用户ID,还有一个是有效用户ID或者称为setuid(set user ID)。
真实用户ID用于标识为谁运行进程。有效用户ID用于为新创建的文件分配所有权、检查文件访问许可,通过系统调用向其它进程发送信号的许可检查。
内核允许一个进程以exec调用一个setuid程序或者显式执行setuid系统调用的方式改变它的有效用户ID。 所谓setuid程序是指一个设置了许可模式字段中的setuid bit的可执行文件。
当一个进程exec调用一个setuid程序的时候,内核会把进程表以及u区中的有效用户ID设置成该文件所有者的ID。为了区分这两个 字段,我们把进程表中的那个字段称作保存用户ID。
setuid系统调用的语法是 setuid(uid) ,其中,uid是新的用户ID,该系统调用的结果取决于有效用户ID的当前值。如果调用进程的有效用户ID是超级用户,内核会把进程表以及u区中的真实和 有效用户ID都设置成uid。如果调用进程的有效用户ID不是超级用户,仅当uid等于真实用户ID或保存用户ID时,内核才会把u区中的有效用户ID设 置成uid。否则,该系统调用将返回错误。一般来说,一个进程会在fork系统调用期间从父进程那儿继承它的真实和有效用户ID,这些数值即使经过 exec系统调用也会保持不变。 存储在u区中的有效用户ID是最近一次setuid系统调用或是exec一个setuid程序的结果;只有它会被用于文件访问许可。进程表中的保存用户 ID使得一个进程可以通过执行setuid系统调用把有效用户ID设置成它的值,以此来恢复最初的有效用户ID。
非root用户是不可能通过setuid或者seteuid取得其他权限(包括root权限)的,它只能恢复原来的权限。允许通过setuid或者seteuid取得root权限是非常危险的,这样他就可以在程序的后边做任何想做的事了(包括kill掉你的系统)。只能通过exec一个设置了setuid位的可执行程序,来取得其他(程序文件所有者)权限(包括root权限)。例如用户执行passwd即可获得root权限(su的属主为root)。
源码分析
我们可以打开/usr/include/bits/stat.h来查看文件中的struct stat字段和文件类型详细信息
[root@localhost bits]# vim /usr/include/bits/stat.h
下面是这个字段的结构
struct stat {
mode_t st_mode; //文件对应的模式,文件,目录等
ino_t st_ino; //inode节点号
dev_t st_dev; //设备号码
dev_t st_rdev; //特殊设备号码
nlink_t st_nlink; //文件的连接数
uid_t st_uid; //文件所有者
gid_t st_gid; //文件所有者对应的组
off_t st_size; //普通文件,对应的文件字节数
time_t st_atime; //文件最后被访问的时间
time_t st_mtime; //文件内容最后被修改的时间
time_t st_ctime; //文件状态改变时间
blksize_t st_blksize; //文件内容对应的块大小
blkcnt_t st_blocks; //伟建内容对应的块数量
};
其中stat结构体中的st_mode 定义了下列几种情况:
S_IFMT 0170000 文件类型的位遮罩
S_IFSOCK 0140000 #scoket
S_IFLNK 0120000 #符号连接
S_IFREG 0100000 #一般文件
S_IFBLK 0060000 #区块装置
S_IFDIR 0040000 #目录
S_IFCHR 0020000 #字符装置
S_IFIFO 0010000 #先进先出
S_ISUID 04000 #文件的(set user-id on execution)位,即SUID
S_ISGID 02000 #文件的(set group-id on execution)位,即SGID
S_ISVTX 01000 #文件的sticky位,即黏滞位
S_IRUSR(S_IREAD) 00400 #文件所有者具可读取权限
S_IWUSR(S_IWRITE)00200 #文件所有者具可写入权限
S_IXUSR(S_IEXEC) 00100 #文件所有者具可执行权限
S_IRGRP 00040 #用户组具可读取权限
S_IWGRP 00020 #用户组具可写入权限
S_IXGRP 00010 #用户组具可执行权限
S_IROTH 00004 #其他用户具可读取权限
S_IWOTH 00002 #其他用户具可写入权限
S_IXOTH 00001 #其他用户具可执行权限
上述的文件类型在系统接口中定义了检查这些类型的宏定义:
- S_ISLNK (st_mode) 判断是否为符号连接
- S_ISREG (st_mode) 是否为一般文件
- S_ISDIR (st_mode) 是否为目录
- S_ISCHR (st_mode) 是否为字符装置文件
- S_ISBLK (s3e) 是否为先进先出
- S_ISSOCK (st_mode) 是否为socket
若一目录具有sticky位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名,在linux中,最典型的就是/tmp目录
OK,抛砖引玉完了,不足之处还望大佬们多多指教!