13-stat 结构体 st_mode 字段

上一篇我们使用了 stat 函数取得了 test.txt 文件的相关属性,这些属性都保存在一个叫 struct stat 的结构体中:

 struct stat {
    dev_t     st_dev;         /* 包含这个文件的设备 ID */
    ino_t     st_ino;         /* inode 编号 */
    mode_t    st_mode;        /* 访问权限 */
    nlink_t   st_nlink;       /* 硬链接数量 */
    uid_t     st_uid;         /* 用户ID */
    gid_t     st_gid;         /* 组ID */
    dev_t     st_rdev;        /* 设备ID */
    off_t     st_size;        /* 文件占用的字节数 */
    blksize_t st_blksize;     /* 文件系统块大小 */
    blkcnt_t  st_blocks;      /* 文件占用了几个512字节 */
    time_t    st_atime;       /* 最后访问时间 */
    time_t    st_mtime;       /* 最后更改时间 */
    time_t    st_ctime;       /* 最后状态更改时间 */
};

本篇我们只介绍 st_mode 字段。在上一篇中,我们得到的 st_mode 字段的10进制值是 33204. 记住这个值,待会我们要分析。

1 一堆 ID

  • 实际用户 ID、实际组 ID
  • 有效用户 ID、有效组 ID
  • 设置用户 ID、设置组 ID

1、实际用户ID(uid)和实际用户组ID(gid):标识我是谁。也就是登录用户的uid和gid,比如我的Linux以allen登录,在Linux运行的所有的命令的实际用户ID都是allen的uid,实际用户组ID都是allen的gid(可以用id命令查看)。

2、有效用户ID(euid)和有效用户组ID(egid):进程用来决定我们对资源的访问权限。一般情况下,有效用户ID等于实际用户ID,有效用户组ID等于实际用户组ID。当设置-用户-ID(SUID)位设置,则有效用户ID等于文件的所有者的uid,而不是实际用户ID;同样,如果设置了设置-用户组-ID(SGID)位,则有效用户组ID等于文件所有者的gid,而不是实际用户组ID。

  1. 你在登录 linux 时输入的帐号,就是实际用户。比方说我登录的帐号是 allen,那么实际用户就是 allen.
  2. 当你以 sudo 执行命令时,比如 sudo rm text.txt,这时候 ,在执行这条命令的时候 ,实际用户是 allen,有效用户是 root。
  3. 假设当前实际用户是 allen,你执行的文件 a.out 的所有者是 david,这时候你在执行这条命令的时候 ,实际用户是 allen,有效用户也是 allen,如果 a.out 这个文件设置用户 ID 标志打开,那么执行 a.out 的时候,实际用户是 allen, 有效用户是 david.

2 st_mode 的结构

st_mode 主要包含了 3 部分信息:

  • 15-12 位保存文件类型
  • 11-9 位保存执行文件时设置的信息
  • 8-0 位保存文件访问权限

图1 展示了 st_mode 各个位的结构。


这里写图片描述
图1 st_mode 结构(图中花括号里的数字都是 2 进制)

2.1 黏着位(sticky)

要删除一个文件,你不一定要有这个文件的写权限,但你一定要有这个文件的上级目录的写权限。也就是说,你即使没有一个文件的写权限,但你有这个文件的上级目录的写权限,你也可以把这个文件给删除,而如果没有一个目录的写权限,也就不能在这个目录下创建文件。

如何才能使一个目录既可以让任何用户写入文件,又不让用户删除这个目录下他人的文件,sticky就是能起到这个作用。sticky一般只用在目录上,用在文件上起不到什么作用。

在一个目录上设了sticky位后,(如/home,权限为1777)所有的用户都可以在这个目录下创建文件,但只能删除自己创建的文件(root除外),这就对所有用户能写的目录下的用户文件启到了保护的作用。

如果用户对目录有写权限,则可以删除其中的文件和子目录,即使该用户不是这些文件的所有者,而且也没有读或写许可。黏着位出现执行许可的位置上,用t表示,设置了该位后,其它用户就不可以删除不属于他的文件和目录。但是该目录下的目录不继承该权限,要再设置才可使用。

普通文件的sticky位会被linux内核忽略。

目录的sticky位表示这个目录里的文件只能被owner和root删除 。

/tmp常被我们用来存放临时文件,是所有用户。但是我们不希望别的用户随随便便的就删除了自己的文件,于是便有了黏着位,它的作用便是让用户只能删除属于自己的文件。

$ ls -dl /tmp  
drwxrwxrwt 15 root root  .........  // 注意 other 用户的权限位,x 变成了 t

那么原来的执行标志x到哪里去了呢? 系统是这样规定的, 假如本来在该位上有x, 则这些特别标志 (suid, sgid, sticky) 显示为小写字母 (s, s, t). 否则, 显示为大写字母 (S, S, T) 。

另外:

chmod 777 abc  
chmod +t abc  

等价于

chmod 1777 abc 

3 实例分析

3.1 普通文件实例

上一篇我们得到的 st_mode 10进制值是 33204,转换成 8 进制后为 100664.

把它填到图1中后,是这样:


这里写图片描述
图2 33204 填到 st_mode 中

根据图2 我们可以得到如下信息:

  • 1000: 这是一个常规文件
  • 000: 执行时设置信息为空,黏着位为 0
  • 110-110-100: 用户权限为 RW-,组员权限为RW-,其他人权限为R--

3.2 带设置-用户-ID标志的文件分析

我们要分析的文件是这样的:

-rwsr-xr-x  1 root  root  7612 1128 14:58 append
-rw-r--r--  1 root  root     6 1128 14:52 test.txt

append 是一个可执行文件,它的作用是以追加的方式打开 test.txt,代码如下:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
        int fd = open("test.txt", O_WRONLY | O_APPEND);
        if (fd == -1) {
                perror("open");
                return -1;
        }

        printf("uid: %d\n", getuid());
        printf("euid: %d\n", geteuid());

        close(fd);
        return 0;
}

append 文件生成方式如下:

$ gcc append.c -o append
$ sudo chown root:root append
$ sudo chmod u+s append

当前实际用户并不是 root,而是 allen.


一般来说,直接执行 ./append 会出现访问被禁止,因为 test.txt 文件的所有者并不是 allen. 然而,这里的会正常执行,结果显示如下:

uid:1000
euid:0

即实际用户 ID 是 allen, 有效用户 id 是 root. 为什么直接执行 ./append 后有效用户 id 不是 allen,而变成 了 root? 这里实际上是 append 文件的 suid 位被置 1 了。

通过 stat 函数,我们得到的 append 的 st_mode 值为 35309. 转换成 8 进制后为 104755. 将其填充到图 1 的结构中后是这样的:


这里写图片描述
图3 填充 st_mode

分析图3,可以知道:
- 1000:这是一个普通文件
- 100:suid 为 1
- 111-101-101:用户权限为 RWX,组员权限为R-X,其他人权限为R-X

所以这里的 append 文件在执行的时候,发现 suid 标志被打开,它会把有效用户id (euid) 设置成 append 文件的所有者(root)。


4 一些常用的宏

前面我们通过手工分析 st_mode 字段,实际上是很不方便的。实际写程序,你可以使用 st_mode & 掩码来得到 st_mode 中特定的部分。比如:

  • st_mode & 0170000 : 得到文件类型
  • st_mode & 0007000 : 得到执行文件时设置信息
  • st_mode & 0000777 : 得到权限位
  • st_mode & 00100: 判断所有者是否可执行
    ……

如果在程序中真这么写,估计很少有人愿意去看。实际上,可以使用 linux 预定义的一些宏来代替这些生硬的数字。这些宏定义在 sys/stat.h 头文件中。

#define S_IFMT  00170000
#define S_IFSOCK 0140000
#define S_IFLNK  0120000
#define S_IFREG  0100000
#define S_IFBLK  0060000
#define S_IFDIR  0040000
#define S_IFCHR  0020000
#define S_IFIFO  0010000
#define S_ISUID  0004000
#define S_ISGID  0002000
#define S_ISVTX  0001000

#define S_ISLNK(m)  (((m) & S_IFMT) == S_IFLNK)
#define S_ISREG(m)  (((m) & S_IFMT) == S_IFREG)
#define S_ISDIR(m)  (((m) & S_IFMT) == S_IFDIR)
#define S_ISCHR(m)  (((m) & S_IFMT) == S_IFCHR)
#define S_ISBLK(m)  (((m) & S_IFMT) == S_IFBLK)
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)

#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100

#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010

#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001

总结

本篇的内容有点繁而多,需要掌握的知识点如下:

  • 实际用户 ID,实际用户组 ID
  • 有效用户 ID,有效用户组 ID
  • 设置用户 ID,设置用户组 ID
  • st_mode 字段结构(文件类型-执行时文件设置信息-权限位)
  • 黏着位

参考资料:
[1] 实际用户ID、有效用户ID、设置用户ID
[2] UID, EUID, SUID, FSUID
[3] 黏着位

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值