Linux 下的文件权限管理分为三组:拥有者、组、其它用户,文件权限分为读、写、执行,但其实并不仅仅如此,还有 setuid、setgid、sticky bit 这一组标志,本文通过一个可执行文件的权限 4755 展开介绍 setuid、setgid 和 sticky bit 的概念,希望本文对读者理解 Linux 文件权限管理能有所帮助。

欢迎访问我的博客:https://whowin.gitee.io


1. 概述


2. 问题的提出

前不久在折腾 openwrt 时,需要给 openwrt 装上 sudo(openwrt 默认是不安装 sudo 的),安装很成功,用起来也没什么问题,但是重启了以后就不能用了,后来发现原因之一是这个 openwrt 在启动的时候将 /usr/bin/ 目录下的所有文件的权限(permission)都改为了 0755,这是一个正常的可执行文件的权限(permission),但是对 sudo 这个可执行文件却是不行的,sudo 的文件权限(permission)必须是 4755;

解决这个问题倒是不难,在 openwrt 启动时,执行一下 chmod 4755 /usr/bin/sudo 就行了;

这件事让我想写这篇文章,因为我们看惯了诸如 0755、0750等可执行文件属性,这个 4755 的文件属性并不多见。


3. 有哪些文件的属性是4755

ls -al /usr/bin/sudo

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(1)

图1:4755的文件属性怎么显示

可以看到平时常见的 rwx 中的 x 变成了 s,这个 s 正是 chmod 4755 /usr/bin/sudo 命令中,那个 “4” 造成的

假定我们有一个用户 demo,我们已经把这个用户加入到了 sudoers 中,那么 demo 用户使用 sudo 命令就可以提权了;

sudo 这个可执行文件的拥有者(owner)一定是 root,执行这个文件时,至少需要读取用户密码文件 /etc/shadow,这个密码文件的拥有者是 root,权限是 640,所以读取这个文件是要有 root 权限,也就是说,执行 sudo 是需要有 root 权限的;

问题是,当我们执行这个文件时,当前用户是 demo,而 demo 是没有 root 权限的,只有执行 sudo 经过提权才能短暂地拥有 root 权限;

这里产生了一个无法调和的矛盾,demo 用户需要通过执行 sudo 获得 root 权限,但执行 sudo 也需要 root 权限;

这就是为什么 sudo 这个文件的属性是 4755 的原因了,当一个可执行文件的属性是 4755 时,任何用户执行这个文件时,将被以该文件拥有者的权限运行,这个问题后面会有详细的描述;

下面指令可以显示 /usr/bin/ 目录下哪些文件的属性是 4755

ls -al /usr/bin|grep "^-rws"

在我的 ubuntu 下会显示出这些文件

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(2)

图2:/usr/bin/目录下4755属性的文件

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(3)

图3:/bin/目录下4755属性的文件

还可以用 find 命令查找系统中所有的权限为 4755 的文件

sudo find / -perm 4755 -type f

4. uid、euid、gid、egid

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(4)

图4:当前用户的uid和gid

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(5)

图5:root用户的uid和gid

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(6)

图6:whowin用户执行touch命令

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(7)

图7:用户权限不够

5. setuid、setgid 和 sticky bit

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(8)

图8:Linux文件权限示意图

  1. 首先我们编一个打开文件的小程序 open_file,并建立一个只有 root 用户可以读取的文件 rootfile.txt

程序 open_file.c 的源代码

#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> int main(int argc, char **argv) { int fd = 0; if (argc != 2) { printf("Usage: %s [filename]\n", argv[0]); return -1; } fd = open(argv[1], O_RDONLY); if (fd == -1) { fprintf(stderr, "%s\n", strerror(errno)); return -1; } close(fd); printf("Open file %s successfully.\n", argv[1]); return 0; }

建立一个只有 root 可以读的文件 rootfile.txt

echo "This file's owner is root.">rootfile.txt chmod 600 rootfile.txt sudo chown root:root rootfile.txt ls -l rootfile.txt cat rootfile.txt

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(9)

图9:编译程序并建立一个只有root可读的文件

我们看到当前用户 whowin 是不能读取文件 rootfile.txt 的

2. 用程序 open_file 打开 rootfile.txt

./open_file rootfile.txt

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(10)

图10:程序open_file无法打开文件rootfile.txt

权限不够是因为 Linux 在执行 open_file 程序时将当前用户 whowin 的权限赋予了 open_file 进程,而 whowin 没有读取文件 rootfile.txt 的权限;

3. 将程序 open_file 的所有者改为 root 并再次尝试打开文件 rootfile.txt

sudo chown root:root open_file ls -l open_file ./open_file rootfile.txt

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(11)

图11:将文件所有者改为root并再次执行

权限还是不够,尽管 open_file 的拥有者变成了 root,whowin 仍有执行 open_file 的权限,但读取 rootfile.txt 时仍然使用的是 whowin 的权限,也就是说 Linux 在执行 open_file 时将 whowin 的权限赋予了 open_file 进程,而没有将这个文件的拥有者 root 的权限赋予 open_file 进程

4. 将 open_file 的权限改为 4755 并再次尝试打开文件 rootfile.txt

sudo chmod 4755 open_file ls -l open_file ./open_file rootfile.txt

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(12)

图12:将程序的权限改为4755并再次执行

第一个惊喜是,当我们把 open_file 的权限改为 4755 后,ls -l open_file 显示的权限从 rwx 变成了 rws

第二个惊喜是 open_file 居然可以打开文件 rootfile.txt 了,这说明 open_file 的权限被改为 4755 后,Linux 在执行 open_file 时不像以前一样把当前用户 whowin 的权限赋予 open_file 进程,而是把 open_file 的拥有者 root 的权限赋予了 open_file 进程,这才使得 open_file 进程有足够的权限打开文件 rootfile.txt;

setuid

setuid 是文件权限的一个标志位,当这个标志位置为 1 时,Linux 在执行这个文件时将会把这个文件的所有者的权限赋予这个程序的进程,而不是像普通可执行文件那样将执行该文件的用户的权限赋予这个程序的进程;

换句话说,当一个文件设置了 setuid 标志后,用户执行这个文件时,在这个程序的进程中 uid 为用户的 uid,euid 为这个文件拥有者的 uid;

chmod u s [文件名]

或者:

chmod 4755 [文件名]

ls -l rootfile.txt sudo chmod u s rootfile.txt ls -l rootfile.txt sudo chmod u-s rootfile.txt ls -l rootfile.txt

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(13)

图13:设置非可执行文件的setuid

setgid

理解了 setuid 之后 setgid 就比较容易理解了,setgid 也是文件权限的一个标志位,当设置该标志位后,用户执行该文件时,将把该文件所在的组的权限赋予这个程序进程的组权限,而不是使用当前用户的组权限去执行该程序;

换句话说,当一个文件设置了 setgid 标志后,用户执行这个文件时,在这个程序的进程中 gid 为用户的 gid,egid 为这个文件拥有者的 gid;

chmod g s [文件名]

或者:

chmod 2755 [文件名]

sticky bit

sticky bit 是一个比较特殊的东西,这个标志只有用于目录时才有效,当一个目录被设置 sticky bit 后,在这个目录下的所有文件只有文件的拥有者和 root 可以删除,不管这个文件的权限是什么(哪怕是 0777 的权限),所以 sticky bit 又可以被称为 “限制删除标志

最典型的例子是 /tmp 目录,每个用户都可以向这个文件中写入文件,假定有 whowin 和 demo 用户都在这个目录下写了文件,如果 demo 用户删除了 whowin 用户写入的文件,就有可能出现问题,所以这个目录被设置了 sticky bit,其中的文件只有拥有者和 root 可以删除;

设置 sticky bit 可以使用以下命令

chmod t [目录名]

或者:

chmod 1777 [目录名]

当一个目录被设置 sticky bit 后,在其他用户权限中的可执行位将被改为 t

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(14)

图14:显示设置sticky bit的目录

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(15)

图15:Linux文件权限标志示意图

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(16)

图16:在sticky目录上同时设置三个标志

chmod u-s [文件名] chmod g-s [文件名] chmod -t [目录名]

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(17)

图17:chmod 0755全部无法清除标志

6. uid 和 euid 的实验

#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main(void) { uid_t uid; uid_t euid; uid = getuid(); euid = geteuid(); printf("uid = %d\neuid = %d\n", uid, euid); return 0; }

gcc get_euid.c -o get_euid

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(18)

图18:没有设置setuid时的euid

linux存储管理策略(Linux文件权限setuidsetgid和sticky)(19)

图19:设置setuid后的euid

7. 后记

,