0%

Linux文件权限

一、文件

Linux文件分为:一般文件(通常简称为文件)和目录。
文件由“Inode节点和Block节点集”构成。

1.1、Inode节点

Inode节点存储文件的元数据,元数据主要用来进行文件控制。描述如下:

1
2
3
4
5
6
7
8
9
1、The size of the file in bytes.
2、Device ID (this identifies the device containing the file).
3、The User ID of the file's owner.
4、The Group ID of the file.
5、The file mode which determines the file type and how the file's owner, its group, and others can access the file.
6、Additional system and user flags to further protect the file (limit its use and modification).
7、Timestamps telling when the inode itself was last modified (ctime, inode change time), the file content last modified (mtime, modification time), and last accessed (atime, access time).
8、A link count telling how many hard links point to the inode.
9、Pointers to the disk blocks that store the file's contents (see inode pointer structure).

有两个元数据需要进行强调。
1、atime,mtime,ctime

名称 描述
atime 即“access time”,指的是最后访问Block节点中数据的时间,中文描述为“最近访问”
mtime 即“modification time”,指的是最后更改Block节点中数据的时间,中文描述为“最近更改”
ctime 即“inode change time”,指的是最后改动Inode节点中元数据的时间,中文描述为“最近改动”

助记:只需记住“change-改动-i”,另外两个很容易推导
2、权限
用于配置不同用户或者用户组(文件所属用户,文件所属用户组,其他用户或者用户组)对文件的操作权限,其实质是对文件的Block节点内容的操作权限。

1.2、Block节点

对于一般文件,Block节点存储一般文件的具体数据;对于目录,Block节点存储多条<Filename,InodeId>形式的记录数据,其中Filename是目录下子文件的文件名,InodeId是相应Inode节点的Id。

二、权限详解

2.1、权限含义

文件一般有“r读取,w修改,x执行”三种权限,对于一般文件和目录来说,这3种权限的含义是不同的。

文件类型 r读取权限含义 w修改权限含义 x执行权限含义
一般文件 是否允许读取Block节点中的内容 是否允许修改Block节点中的内容 是否允许执行Block节点中的内容
目录 是否允许读取Block节点中<Filename,InodeId>形式记录中的Filename 是否允许增加,删除和修改Block节点中<Filename,InodeId>形式记录 是否允许读取Block节点中<Filename,InodeId>形式记录中的InodeId,并加载InodeId对应的Inode节点

2.2、权限应用场景

由于操作文件的只能是进程,因此权限的应用场景是:进程欲操作文件,文件系统首先获取该进程的“<进程有效用户,进程有效用户组>”进程用户身份(在接下来的描述中,简称为“进程用户身份”),将其与该文件Inode节点元数据中的权限配置数据进行比对,获取到该进程对该文件所被允许的操作权限范围,假如欲进行的操作在允许的操作权限范围内,则顺利执行;否则,由于没有操作权限被禁止执行。
本文接下来的实验中,进程用户身份主要跟“文件所属用户”的权限配置数据进行比对,但是切勿忘记还可跟“文件所属用户组”和“其他用户或者用户组”的权限配置数据进行比对。
关于“进程用户身份”可详见链接

2.3、权限具体判定过程

下面介绍权限具体判定过程,分为一般文件和目录两种情况。

2.3.1、一般文件

2.3.1.1、r读取权限

进程读取一般文件内容的过程如下:

  1. 文件系统首先获取该进程的进程用户身份,判断该进程用户身份对该文件所在的目录是否具有x权限,只有具有该x权限,才能加载该一般文件的Inode节点,并读取Inode节点上的元数据,包括权限配置数据
  2. 比对“进程用户身份”和“该一般文件的权限配置数据”,看是否具有对该一般文件的r读取权限,如果有,可读取;否则,不可读取

实验:
有一个目录“b”,拥有者用户为“dsl”,其下有一个一般文件“a.txt”(内容为“hello world”),拥有者用户为“dsl”,以“dsl”用户执行less b/a.txt命令查看“b/a.txt”内容,相应创建运行进程的进程用户身份中的“进程有效用户”为“dsl”。变化目录“b”和一般文件“a.txt”的权限配置数据,得到不同的命令执行结果,如下表。

目录“b”的权限配置数据 一般文件“a.txt”的权限配置数据 命令执行结果
000 000 b/a.txt: 权限不够
000 400 b/a.txt: 权限不够
100 000 b/a.txt: 权限不够
100 400 hello world
2.3.1.2、w修改权限

进程修改一般文件内容的过程如下:

  1. 文件系统首先获取该进程的进程用户身份,判断该进程用户身份对该文件所在的目录是否具有x权限,只有具有该x权限,才能加载该一般文件的Inode节点,并读取Inode节点上的元数据,包括权限配置数据
  2. 比对“进程用户身份”和“该一般文件的权限配置数据”,看是否具有对该一般文件的w修改权限,如果有,可修改;否则,不可修改

实验:
有一个目录“b”,拥有者用户为“dsl”,其下有一个一般文件“a.txt”(内容为空),拥有者用户为“dsl”,以“dsl”用户执行echo "hello world" > b/a.txt命令修改“b/a.txt”内容,相应创建运行进程的进程用户身份中的“进程有效用户”为“dsl”。变化目录“b”和一般文件“a.txt”的权限配置数据,得到不同的命令执行结果,如下表。

目录“b”的权限配置数据 一般文件“a.txt”的权限配置数据 命令执行结果
000 000 bash: b/a.txt: 权限不够
000 200 bash: b/a.txt: 权限不够
100 000 bash: b/a.txt: 权限不够
100 200 成功执行
2.3.1.3、x执行权限

进程执行一般文件内容的过程如下:

  1. 文件系统首先获取该进程的进程用户身份,判断该进程用户身份对该文件所在的目录是否具有x权限,只有具有该x权限,才能加载该一般文件的Inode节点,并读取Inode节点上的元数据,包括权限配置数据
  2. 比对“进程用户身份”和“该一般文件的权限配置数据”,看是否具有对该一般文件的rx执行权限(必须先“r读取”才能“x执行”),如果有,可执行;否则,不可执行

实验:
有一个目录“b”,拥有者用户为“dsl”,其下有一个一般文件“a.sh”(内容为“echo ‘hello world’”),拥有者用户为“dsl”,以“dsl”用户执行/bin/bash b/a.sh命令执行“b/a.sh”内容,相应创建运行进程的进程用户身份中的“进程有效用户”为“dsl”。变化目录“b”和一般文件“a.sh”的权限配置数据,得到不同的命令执行结果,如下表。

目录“b”的权限配置数据 一般文件“a.sh”的权限配置数据 命令执行结果
000 000 /bin/bash: b/a.sh: 权限不够
100 000 /bin/bash: b/a.sh: 权限不够
000 100 /bin/bash: b/a.sh: 权限不够
100 100 /bin/bash: b/a.sh: 权限不够
100 500 hello world

2.3.2、目录

2.3.2.1、r读取权限

进程读取目录内容的过程如下:

  1. 文件系统首先获取该进程的进程用户身份,判断该进程用户身份对该目录所在的目录是否具有x权限,只有具有该x权限,才能加载该目录的Inode节点,并读取Inode节点上的元数据,包括权限配置数据
  2. 比对“进程用户身份”和“该目录的权限配置数据”,看是否具有对该目录的r读取权限,如果有,可读取;否则,不可读取。需要注意的是,如果只拥有对目录的r读取权限,那么只能读取<Filename,InodeId>形式记录中的Filename,而不能读取InodeId,更不能进而加载InodeId对应的Inode节点

实验:
有一个目录“b”,拥有者用户为“dsl”,其下有一个目录“a”,拥有者用户为“dsl”,目录“a”下有两个一般文件(分别为“c.txt”和“d.txt”)和一个目录(“e”),以“dsl”用户执行ls b/a命令查看“b/a”目录下内容,相应创建运行进程的进程用户身份中的“进程有效用户”为“dsl”。变化目录“b”和目录“a”的权限配置数据,得到不同的命令执行结果,如下表。

目录“b”的权限配置数据 目录“a”的权限配置数据 命令执行结果
000 000 ls: 无法访问'b/a': 权限不够
000 400 ls: 无法访问'b/a': 权限不够
100 000 ls: 无法打开目录'b/a': 权限不够
100 400 ls: 无法访问'b/a/d.txt': 权限不够
ls: 无法访问'b/a/e': 权限不够
ls: 无法访问'b/a/c.txt': 权限不够
c.txt d.txt e
2.3.2.2、w修改权限

进程修改目录内容的过程如下:

  1. 文件系统首先获取该进程的进程用户身份,判断该进程用户身份对该目录所在的目录是否具有x权限,只有具有该x权限,才能加载该目录的Inode节点,并读取Inode节点上的元数据,包括权限配置数据
  2. 比对“进程用户身份”和“该目录的权限配置数据”,看是否具有对该目录的wx权限,如果有,可修改;否则,不可修改。需要注意的是,如果只拥有对目录的w修改权限,不能实现修改

实验:
有一个目录“b”,拥有者用户为“dsl”,其下有一个目录“a”,拥有者用户为“dsl”,目录“a”下有两个一般文件(分别为“c.txt”和“d.txt”)和一个目录(“e”),以“dsl”用户分别执行touch b/a/f.txtrm b/a/c.txtmv b/a/d.txt b/a/g.txt命令修改目录“b/a”下内容,相应创建运行进程的进程用户身份中的“进程有效用户”为“dsl”。变化目录“b”和目录“a”的权限配置数据,得到不同的命令执行结果,如下表。

目录“b”的权限配置数据 目录“a”的权限配置数据 touch b/a/f.txt命令执行结果 rm b/a/c.txt命令执行结果 mv b/a/d.txt b/a/g.txt命令执行结果
000 000 touch: 无法创建'b/a/f.txt': 权限不够 rm: 无法删除'b/a/c.txt': 权限不够 mv: failed to access 'b/a/g.txt': 权限不够
000 300 touch: 无法创建'b/a/f.txt': 权限不够 rm: 无法删除'b/a/c.txt': 权限不够 mv: failed to access 'b/a/g.txt': 权限不够
100 000 touch: 无法创建'b/a/f.txt': 权限不够 rm: 无法删除'b/a/c.txt': 权限不够 mv: failed to access 'b/a/g.txt': 权限不够
100 200 touch: 无法创建'b/a/f.txt': 权限不够 rm: 无法删除'b/a/c.txt': 权限不够 mv: failed to access 'b/a/g.txt': 权限不够
100 300 成功执行 成功执行 成功执行

备注:
对目录内容的修改本质上就是对<Filename,InodeId>形式记录的增加,删除和修改。要达到修改目录内容的目标,进程的进程用户身份不仅需要拥有对目录的w权限,也需要拥有对目录的x权限。原因描述如下:

  • 增加的情况,须加载Inode节点,修改元数据,比如“ctime”值,硬链接计数值
  • 删除的情况,须加载Inode节点,修改元数据,比如硬链接计数值
  • 修改的情况,实现方式为先“增加”,后“删除”(作者推测),因此须加载Inode节点
2.3.2.3、x执行权限

目录的x执行权限已在上述内容中进行描述,这里不再贅述。

2.4、特殊权限

2.4.1、SUID

2.4.1.1、一般文件上设置SUID权限

当设置了SUID权限的可执行文件被执行时,创建运行的进程的“进程有效用户”变为“该可执行文件的文件所属用户”,如果可执行文件的文件所属用户是“root”,那么创建运行的进程的“进程有效用户”是“root”。
实验:
比如有以下C源代码文件(文件名为“geteuid.c”):

1
2
3
4
5
6
7
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv) {
//geteuid()方法能够得到进程有效用户的UID
printf("euser uid is:%d\n", geteuid());
return 0;
}

执行gcc -o geteuid geteuid.c命令,编译得到一个二进制文件“geteuid”,将该二进制文件的文件所属用户设为“root”,权限配置数据为“4777”。当前用户为“dsl”,执行./geteuid命令,得到的打印结果如下,“0”UID对应的用户为“root”,从而得知“进程有效用户”为“root”。

1
euser uid is:0

备注:
基于安全策略,在Linux中,只能对二进制文件设置SUID权限,而不能对包括脚本文件在内的其他可执行文件设置SUID权限。

2.4.1.2、目录上设置SUID权限

目录上设置SUID权限没有任何效果。

2.4.2、SGIT

2.4.2.1、一般文件上设置SGIT权限

当设置了SGID权限的可执行文件被执行时,创建运行的进程的“进程有效用户组”变为“该可执行文件的文件所属用户组”,如果可执行文件的文件所属用户组是“root”,那么创建运行的进程的“进程有效用户组”是“root”。
实验:
比如有以下C源代码文件(文件名为“getegid.c”):

1
2
3
4
5
6
7
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv) {
//getegid()方法能够得到进程有效用户组的GID
printf("egroup gid is:%d\n", getegid());
return 0;
}

执行gcc -o getegid getegid.c命令,编译得到一个二进制文件“getegid”,将该二进制文件的文件所属用户组设为“root”,权限配置数据为“2777”。当前用户为“dsl”(对应的用户组为“dsl”),执行./getegid命令,得到的打印结果如下,“0”GID对应的用户组为“root”,从而得知“进程有效用户组”为“root”。

1
egroup gid is:0
2.4.2.2、目录上设置SGIT权限

目录A被设置了SGIT权限之后,在A目录下创建生成的一般文件和目录的文件所属用户组跟A的文件所属用户组一致。
实验:
有一个目录“a”,文件所属用户为“dsl”,文件所属用户组为“root”,权限配置数据为“2777”。以“dsl”用户在目录“a”下创建一般文件“b.txt”和目录“c”。分别执行stat b.txtstat c命令,得到如下两个结果片段:

1
2
文件:'b.txt'
权限:(0664/-rw-rw-r--) Uid:( 1000/ dsl) Gid:( 0/ root)
1
2
文件:'c'
权限:(2775/drwxrwsr-x) Uid:( 1000/ dsl) Gid:( 0/ root)

2.4.3、sticky-bit

2.4.3.1、一般文件上设置sticky-bit权限

When set, it instructed the operating system to retain the text segment of the program in swap space after the process exited. This speeds up subsequent executions by allowing the kernel to make a single operation of moving the program from swap to real memory.

2.4.3.2、目录上设置sticky-bit权限

目录A被设置sticky-bit权限,在A目录下创建生成的一般文件和目录具有如下特殊性质:只有它们(一般文件和目录)的文件所属用户(即既不能是“文件所属用户组”,也不能是“其他用户和用户组”)和“root”用户有权删除它们,其他任何用户或者用户组即便拥有对A目录的wx权限,也不可以删除它们。
最现成的例子是“/tmp”目录。“/tmp”是所有用户和用户组共享的临时文件夹,所有用户和用户组都拥有wx权限,这就可能使得出现如下情形:假如A用户在“/tmp”里创建了文件“a.file”,而B用户看了不爽,在“/tmp”里把它给删了(因为拥有wx权限),导致给A用户带来巨大的损失。事实上以上操作并不会被允许,因为“/tmp”目录被设置有sticky-bit权限,正如“drwxrwxrwt”权限配置数据中的最后一个“t”。
实验:
现有“testuser”和“dsl”用户,“testuser”用户在“/tmp”目录下创建了一般文件“a.txt”和目录“b”,“dsl”用户不能删除上述两个文件(尝试删除时,出现如下两个提示),只有“testuser”用户或者“root”用户才能够删除上述两个文件。

1
2
rm:是否删除有写保护的普通空文件 'a.txt'? yes
rm: 无法删除'a.txt': 不允许的操作
1
2
rm:是否删除有写保护的目录 'b'? yes
rm: 无法删除'b': 不允许的操作

三、其他

3.1、延伸

3.1.1、改变文件权限配置数据

使用“chmod”命令可改变文件权限配置数据,使用要求:执行“chmod”命令创建运行进程的进程用户身份中的“进程有效用户”必须为“文件的文件所属用户”或者“root”,另外,改变文件权限配置数据即改变文件Inode节点上的元数据,因此首先须加载Inode节点,这点跟以上的描述是一致的。

3.1.2、改变文件所属用户

使用“chown”命令可改变文件的文件所属用户,使用要求:执行“chown”命令创建运行进程的进程用户身份中的“进程有效用户”必须为“root”,另外,改变文件所属用户即改变文件Inode节点上的元数据,因此首先须加载Inode节点,这点跟以上的描述是一致的。

3.1.3、改变文件所属用户组

使用“chgrp”命令可改变文件的文件所属用户组,使用要求:执行“chgrp”命令创建运行进程的进程用户身份中的“进程有效用户”必须为“root”,另外,改变文件所属用户组即改变文件Inode节点上的元数据,因此首先须加载Inode节点,这点跟以上的描述是一致的。

3.1.4、读取Inode节点信息

使用“stat”命令可读取Inode节点信息,使用要求:须加载Inode节点。

3.1.5、修改Inode节点的“atime”和“mtime”值

使用“touch”命令可修改Inode节点的“atime”和“mtime”值(如果文件不存在,则首先创建文件),使用要求:须加载Inode节点。

3.2、基于实现视角

文件采用“Inode节点-Block节点体系”,操作系统提供有一系列针对该体系进行操作的原语,Shell命令(比如“ls,less,vim,chmod,chown,chgrp,stat,touch,rm,mv”等)调用这些操作原语对“Inode节点-Block节点体系”进行操作。示意图如图1。

图1

这些操作原语的使用性质和要求会表现为Shell命令的使用性质和要求,比如上述介绍的权限机制。关于这些操作原语的概要介绍如下表。

操作原语名称 描述 使用性质和要求 调用这些操作原语的Shell命令
“读取”原语 对于一般文件,读取Block节点中的内容;对于目录,读取Block节点中<Filename,InodeId>形式记录中的Filename 进程的“<进程有效用户,进程有效用户组>”进程用户身份具有“r读取”权限 less,vim等命令
“修改”原语 对于一般文件,修改Block节点中的内容;对于目录,增加,删除和修改Block节点中<Filename,InodeId>形式记录 进程的“<进程有效用户,进程有效用户组>”进程用户身份具有“w修改”权限 vim等命令
“执行”原语 对于一般文件,执行Block节点中的内容;对于目录,读取Block节点中<Filename,InodeId>形式记录中的InodeId,并加载InodeId对应的Inode节点 进程的“<进程有效用户,进程有效用户组>”进程用户身份具有“x执行”权限,另外如果为一般文件且设置有SUID权限(SGIT权限),则创建运行进程的“进程有效用户”变为“该可执行文件的文件所属用户”(创建运行进程的“进程有效用户组”变为“该可执行文件的文件所属用户组”) /
“删除文件”原语 删除文件 进程的“<进程有效用户,进程有效用户组>”进程用户身份对所在目录具有“wx”权限,如果所在目录设置有sticky-bit权限,那么须额外满足:进程用户身份中的“进程有效用户”必须为“文件的文件所属用户”或者“root” rm命令
“改变文件权限配置数据”原语 改变文件权限配置数据 进程用户身份中的“进程有效用户”必须为“文件的文件所属用户”或者“root” chmod命令
“改变文件所属用户”原语 改变文件所属用户 进程用户身份中的“进程有效用户”必须为“root” chown命令
“改变文件所属用户组”原语 改变文件所属用户组 进程用户身份中的“进程有效用户”必须为“root” chgrp命令
“读取Inode节点信息”原语 读取Inode节点信息 能够加载Inode节点 stat,ls等命令
“修改Inode节点‘atime’和‘mtime’值”原语 修改Inode节点“atime”和“mtime”值 能够加载Inode节点 touch等命令
“修改Inode节点‘atime’值”原语 修改Inode节点“atime”值 能够加载Inode节点 less,vim等命令
“修改Inode节点‘mtime’值”原语 修改Inode节点“mtime”值 能够加载Inode节点 vim等命令
“修改Inode节点‘ctime’值”原语 修改Inode节点“ctime”值 能够加载Inode节点 vim等命令

备注:
无需太深究这些操作原语的具体实现细节,比如前后两次使用“less或者vim”命令,第一次能够改变“atime”字段值,第二次不能改变“atime”字段值,推测在“修改Inode节点‘atime’值原语”的具体实现中应该有一套具体的修改机制。

3.3、上帝用户——root

root用户是上帝用户,具有任意权限。因此必能进行“加载Inode节点,增删改文件”等操作。


参考文献: [1]http://en.wikipedia.org/wiki/Inode [2]http://superuser.com/questions/520107/how-are-directory-structures-stored-in-unix-filesystem [3]http://www.cyberciti.biz/faq/how-linux-file-permissions-work/ [4]http://haifux.org/lectures/84-sil/users-processes-files-and-permissions/users-perms-lec.html [5]http://stackoverflow.com/questions/6305416/how-does-a-process-in-linux-decides-privileges-allotted-to-it [6]http://unix.stackexchange.com/questions/364/allow-setuid-on-shell-scripts [7]http://en.wikipedia.org/wiki/Setuid#setuid_and_setgid_on_directories [8]http://en.wikipedia.org/wiki/Sticky_bit [9]http://www.linuxquestions.org/questions/linux-software-2/rename-and-ctime-913753
您的支持将鼓励我继续分享!