zoukankan      html  css  js  c++  java
  • 在Linux下为什么普通用户可以修改自己密码

    思考

    今天想到一个问题,为什么普通用户可以修改自己的密码?
    我们知道/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,抛砖引玉完了,不足之处还望大佬们多多指教!

  • 相关阅读:
    探秘小程序(9):父页面与自定义组件交互
    探秘小程序(8):scroll-view组件
    探秘小程序(7):view组件
    探秘小程序(6):微信支付
    探秘小程序(5):用户信息
    通向全栈之路(6)—无密码ssh连接
    探秘小程序(4):授权
    探秘小程序(3):登录
    探秘小程序(2):自定义组件
    [Python]json对象转换出错expected string or buffer python
  • 原文地址:https://www.cnblogs.com/hxlinux/p/13527155.html
Copyright © 2011-2022 走看看