笔记和练习博客总目录见:开始读TLPI。
本章介绍了扩展属性(EAs),它们允许以名称-值对的形式将任意元数据与文件 i 节点关联。EAs 在 Linux 2.6 版本中被添加。
16.1 Overview
EAs 用于实现访问控制列表(第17章)和文件功能(第39章)。然而,EAs 的设计足够通用,也可以用于其他目的。例如,EAs 可以用于记录文件版本号、文件的 MIME 类型或字符集信息,或(指向)图形图标。
SUSv3 中未指定 EAs。然而,在一些其他 UNIX 实现中提供了类似功能,尤其是现代 BSD(参见 extattr(2))和 Solaris 9 及更高版本(参见 fsattr(5))。
EAs 需要底层文件系统的支持。Btrfs、ext2、ext3、ext4、JFS、Reiserfs 和 XFS 都提供了这种支持。
每个文件系统对 EAs 的支持是可选的,由内核配置选项中的 “文件系统” 菜单控制。自 Linux 2.6.7 起,Reiserfs 支持 EAs。
EA namespaces
EAs 的名称形式为 namespace.name。命名空间组件用于将 EAs 分隔为功能上不同的类别。名称组件在给定命名空间内唯一标识一个 EA。
命名空间支持四个值:user、trusted、system 和 security。这四种类型的 EA 使用方式如下:
- 用户 EAs 可以由无特权进程操作,但需遵守文件权限检查:要读取用户 EA 的值,需要文件的读取权限;要修改用户 EA 的值,需要文件的写入权限。(缺少所需权限会导致 EACCES 错误。)为了将用户 EAs 与 ext2、ext3、ext4 或 Reiserfs 文件系统上的文件关联,底层文件系统必须以 user_xattr 选项挂载:
$mount-ouser_xattr device directory- Trusted EA 像用户扩展属性一样,可以被用户进程操作。不同之处在于,操作Trusted EA 的进程必须具有特权(CAP_SYS_ADMIN)。
- System EA由内核用于将系统对象与文件关联。目前,唯一支持的对象类型是访问控制列表(第17章)。
- Security EA用于存储操作系统安全模块的文件安全标签,以及将能力与可执行文件关联(第39.3.2节)。Security EA最初是为支持安全增强型Linux(SELinux,http://www.nsa.gov/research/selinux/)而设计的。
一个 i-node 可能有多个关联的扩展属性(EA),这些属性可以位于相同的命名空间或不同的命名空间中。每个命名空间内的 EA 名称是不同的集合。在用户和受信任命名空间中,EA 名称可以是任意字符串。在系统命名空间中,仅允许内核明确许可的名称(例如,用于访问控制列表的名称)。
JFS 支持另一个命名空间 os2,这在其他文件系统中未实现。提供 os2 命名空间是为了支持传统的 OS/2 文件系统扩展属性。进程无需具有特权即可创建 os2 EA。
💡 那这些EAs谁在用?典型的包括SELinux用security.selinux存放安全上下文信息,Linux Capabilities使用security.capability存放细粒度权限信息。或者用户应用自定义其使用规则。
Creating and viewing EAs from the shell
在 shell 中,我们可以使用 setfattr(1) 和 getfattr(1) 命令来设置和查看文件的扩展属性(EAs):
$touchtfile# -n name, Specifies the name of the extended attribute to set.# -v value, Specifies the new value of the extended attribute.$ setfattr-nuser.x-v"The past is not dead."tfile $ setfattr-nuser.y-v"In fact, it's not even past."tfile $ getfattr-nuser.x tfile# file: tfileuser.x="The past is not dead."# -d, Dump the values of all matched extended attributes.$ getfattr-dtfile# file: tfileuser.x="The past is not dead."user.y="In fact, it's not even past."$ setfattr-nuser.x tfile $ getfattr-dtfile# file: tfileuser.x=""user.y="In fact, it's not even past."# -x name, Remove the named extended attribute entirely.$ setfattr-xuser.y tfile $ getfattr-dtfile# file: tfileuser.x=""$ $ getfattr-m- tfile# file: tfilesecurity.selinux user.x前一个 shell 会话所演示的一个要点是,EA 的值可能是一个空字符串,这与未定义的 EA 不同。(在 shell 会话结束时,user.x 的值是空字符串,而 user.y 是未定义的。)
默认情况下,getfattr 只列出用户 EA 的值。可以使用 -m 选项来指定一个正则表达式模式,以选择要显示的 EA 名称:
$ getfattr-m'pattern'filepattern 的默认值是^user..我们可以使用以下命令将所有 EA 列到一个文件中:
$ getfattr-m-file16.2 Extended Attribute Implementation Details
在本节中,我们扩展前一节的概述,以补充一些关于 EA 实现的细节。
Restrictions on user extended attributes
只能将用户扩展属性(EAs)放置在文件和目录上。其他类型的文件由于以下原因被排除在外:
对于符号链接,所有用户的所有权限都是启用的,并且这些权限无法更改。(如第18.2节中详细说明,符号链接的权限在Linux上没有意义。)这意味着权限不能用于阻止任意用户在符号链接上放置用户EA。解决此问题的方法是阻止所有用户在符号链接上创建用户EA。
对于设备文件、套接字和FIFO,权限控制用户对执行底层对象I/O的访问。操纵这些权限以控制用户EA的创建会与此目的发生冲突。
此外,如果目录上设置了粘滞位(第15.4.5节),非特权进程就无法在其他用户拥有的目录上放置用户扩展属性(EA)。
这可以防止任意用户向像 /tmp 这样可公开写入的目录附加扩展属性(EA),因为这些目录允许任意用户操作目录上的扩展属性,但设置了粘滞位以防止用户删除目录中其他用户拥有的文件。
Implementation limits
Linux VFS 对所有文件系统的扩展属性(EA)施加了以下限制:
- EA 名称的长度限制为 255 个字符。
- EA 值限制为 64 kB。
此外,一些文件系统对与文件相关的 EA 的大小和数量施加了更严格的限制: - 在 ext2、ext3 和 ext4 上,文件上所有 EA 的名称和值使用的总字节数限制为单个逻辑磁盘块的大小(第 14.3 节):1024、2048 或 4096 字节。
- 在 JFS 上,文件上所有 EA 的名称和值使用的总字节数上限为 128 kB。
16.3 System Calls for Manipulating Extended Attributes
在本节中,我们将查看用于更新、检索和删除扩展属性(EA)的系统调用。
Creating and modifying EAs
setxattr()、lsetxattr() 和 fsetxattr() 系统调用设置文件某个扩展属性(EA)的值。
#include<sys/xattr.h>intsetxattr(constchar*pathname,constchar*name,constvoid*value,size_tsize,intflags);intlsetxattr(constchar*pathname,constchar*name,constvoid*value,size_tsize,intflags);intfsetxattr(intfd,constchar*name,constvoid*value,size_tsize,intflags);这三个调用之间的差异类似于 stat()、lstat() 和 fstat()(第15.1节)之间的差异:
- setxattr() 通过路径名识别文件,如果它是符号链接,则会取消引用该文件名;
- lsetxattr() 通过路径名识别文件,但不会取消引用符号链接;
- fsetxattr() 通过打开的文件描述符 fd 识别文件。
同样的区别也适用于本节余下部分描述的其他系统调用组。
name 参数是一个以空字符结尾的字符串,用于定义 EA 的名称。
value 参数是一个指向缓冲区的指针,用于定义 EA 的新值。
size 参数指定该缓冲区的长度。
默认情况下,如果具有给定名称的 EA 不存在,这些系统调用会创建一个新的 EA;如果 EA 已经存在,则会替换其值。
flags 参数提供了对这种行为的更精细控制。它可以指定为 0 以获得默认行为,或者指定为以下常量之一:
- XATTR_CREATE
如果具有给定名称的扩展属性(EA)已存在,则失败(EEXIST)。 - XATTR_REPLACE
如果具有给定名称的扩展属性(EA)尚不存在,则失败(ENODATA)。
下面是使用 setxattr() 创建用户扩展属性(EA)的示例:
char*value;value="The past is not dead.";if(setxattr(pathname,"user.x",value,strlen(value),0)==-1)errExit("setxattr");Retrieving the value of an EA
getxattr()、lgetxattr() 和 fgetxattr() 系统调用用于获取扩展属性(EA)的值。
#include<sys/xattr.h>ssize_tgetxattr(constchar*pathname,constchar*name,void*value,size_tsize);ssize_tlgetxattr(constchar*pathname,constchar*name,void*value,size_tsize);ssize_tfgetxattr(intfd,constchar*name,void*value,size_tsize);name 参数是一个以空字符结尾的字符串,用于标识我们想要获取其值的扩展属性(EA)。EA 值会被返回到由 value 指向的缓冲区中。这个缓冲区必须由调用者分配,并且其长度必须在 size 中指定。成功时,这些系统调用会返回复制到 value 中的字节数。
如果文件没有具有给定名称的属性,这些系统调用会以错误 ENODATA 失败。如果 size 太小,这些系统调用会以错误 ERANGE 失败。
可以将 size 指定为 0,在这种情况下 value 会被忽略,但系统调用仍然返回 EA 值的大小。这提供了一种机制,以便确定后续调用实际获取 EA 值所需的值缓冲区的大小。然而,请注意,我们仍然无法保证在随后尝试获取值时返回的大小足够。因为与此同时另一个进程可能已经将更大的值分配给该属性,或者完全移除了该属性。
Removing an EA
removexattr()、lremovexattr() 和 fremovexattr() 系统调用从文件中移除 EA。
#include<sys/xattr.h>intremovexattr(constchar*pathname,constchar*name);intlremovexattr(constchar*pathname,constchar*name);intfremovexattr(intfd,constchar*name);名称中给出的以空结尾的字符串标识要删除的扩展属性(EA)。
尝试删除不存在的扩展属性会失败,并返回错误 ENODATA。
Retrieving the names of all EAs associated with a file
listxattr()、llistxattr() 和 flistxattr() 系统调用返回一个包含与文件相关的所有扩展属性(EA)名称的列表。
#include<sys/xattr.h>ssize_tlistxattr(constchar*pathname,char*list,size_tsize);ssize_tllistxattr(constchar*pathname,char*list,size_tsize);ssize_tflistxattr(intfd,char*list,size_tsize);EA 名称的列表以一系列以 null 结尾的字符串的形式返回,这些字符串存储在由 list 指向的缓冲区中。该缓冲区的大小必须在 size 中指定。成功时,这些系统调用会返回复制到 list 中的字节数。
💡 名称列表作为无序的以空字符结尾的字符数组返回(属性名称由空字节 (‘0’) 分隔),如下所示:
user.name1\0system.name1\0user.name2\0与 getxattr() 类似,也可以将 size 指定为 0,此时 list 会被忽略,但系统调用会返回实际需要的缓冲区大小,以便随后调用能够实际检索 EA 名称列表(假设其保持不变)。
要检索与文件关联的 EA 名称列表,只需要文件可访问(即我们对 pathname 中包含的所有目录具有执行权限)。文件本身不需要任何权限。
出于安全原因,列表中返回的 EA 名称可能会排除调用进程无法访问的属性。例如,大多数文件系统会在非特权进程调用 listxattr() 时,从返回的列表中省略受信任属性。但请注意前一句中的“可能”,这表明文件系统实现没有义务这样做。因此,我们需要考虑到使用列表中返回的 EA 名称进行后续 getxattr() 调用可能失败的可能性,因为该进程没有获取该 EA 值所需的权限。(如果另一个进程在 listxattr() 和 getxattr() 调用之间删除了某个属性,也可能发生类似的失败。)
Example program
清单16-1中的程序检索并显示命令行中列出的所有文件的EA(扩展属性)的名称和值。对于每个文件,程序使用listxattr()来检索与文件关联的所有EA的名称,然后执行一个循环,对每个名称调用getxattr()一次,以检索相应的值。默认情况下,属性值以纯文本形式显示。如果提供了–x选项,则属性值以十六进制字符串显示。下面的Shell会话日志演示了该程序的使用:
$touchtfile $ setfattr-nuser.x-v"The past is not dead."tfile $ setfattr-nuser.y-v"In fact, it's not even past."tfile $ ./xattr_view tfile tfile:name=security.selinux;value=unconfined_u:object_r:user_home_t:s0name=user.x;value=The past is not dead.name=user.y;value=In fact, it's not even past.Listing 16-1: Display file extended attributes
// xattr/xattr_view.c// 代码略16.4 Summary
从版本 2.6 开始,Linux 支持扩展属性,这允许将任意元数据与文件关联,形式为名称-值对。
16.5 Exercise
参见 TLPI 第16章 练习:Extended Attributes。