11扩展属性和访问控制表

11扩展属性和访问控制表

许多文件系统都提供了一些特性,扩展了VFS层提供的标准功能。虚拟文件系统不可能为想到每个特性都提供具体的数据结构。超出标准的UNIX文件模型的附加特性,通常需要将一个组扩展属性关联到每个文件系统对象。但内核能够提供的是一个框架,容许增加特定于文件系统的扩展。扩展属性(extended attribute,xattrs)是能够关联到文件的(或多或少)任意属性。由于每个文件通常都只关联了所有可能扩展属性的一个子集,扩展属性存储在常规的inode数据结构之外,以避免增加该结构在内存中的长度和浪费磁盘空间。这实际上容许使用一个通用的属性集合,而不会对文件系统性能或磁盘空间需求有任何显著影响。

扩展的一种用途是实现访问控制表(access control list),对UNIX风格的权限模型进行扩展。它们允许实现细粒度的访问控制,不仅仅使用用户、组、其他用户的概念,而是将各个用户及其允许的操作组成一个明确的列表,关联到文件。这种列表很自然地融合到扩展属性模型中。扩展属性的另一种用途是为SE-Linux提供标记信息

11.1 扩展属性

从文件系统用户的角度来看,一个扩展属性就是与文件系统对象关联的一个“名称/值”对。名称是一个普通的字符串,内核对值的内容不作限制。它可以是文本串,但也可以包含任意的二进制数据。属性可以定义,也可以不定义(如果文件没有关联属性,就是这种情形)。如果定义了属性,可以有值,也可以没有

属性名称会按命名空间细分。这意味着,访问属性也需要给出命名空间。按照符号约定,用一个点来分隔命名空间和属性名(例如user.mime_type)。内核使用宏定义了有效的顶层命名空间的列表。形如XATTR_*_PREFIX。在从用户空间传递来一个名字串,需要与命名空间前缀比较时,一组辅助性的宏XATTR_*_PREFIX_LEN是比较有用的。

//include/linux/xattr.h
#define XATTR_OS2_PREFIX "os2."
#define XATTR_OS2_PREFIX_LEN (sizeof (XATTR_OS2_PREFIX) - 1)

#define XATTR_SECURITY_PREFIX	"security."
#define XATTR_SECURITY_PREFIX_LEN (sizeof (XATTR_SECURITY_PREFIX) - 1)

#define XATTR_SYSTEM_PREFIX "system."
#define XATTR_SYSTEM_PREFIX_LEN (sizeof (XATTR_SYSTEM_PREFIX) - 1)

#define XATTR_TRUSTED_PREFIX "trusted."
#define XATTR_TRUSTED_PREFIX_LEN (sizeof (XATTR_TRUSTED_PREFIX) - 1)

#define XATTR_USER_PREFIX "user."
#define XATTR_USER_PREFIX_LEN (sizeof (XATTR_USER_PREFIX) - 1)

内核提供了几个系统调用来读取和操作扩展属性

setxattr用于设置或替换某个扩展属性的值,或创建一个新的扩展属性
getxattr获取某个扩展属性的值
removexattr删除一个扩展属性
listxattr列出与给定的文件系统对象相关的所有扩展属性

这些系统调用都还有前缀为l的变体。这些变体不跟踪符号链接,而是对符号链接本身的扩展属性进行操作。另外,前缀为f的变体不处理文件名,而是对通过文件描述符指定的对象进行处理

11.1.1 到虚拟文件系统的接口

虚拟文件系统向用户空间提供了一个抽象层,使得所有应用程序都可以使用扩展属性,而无需考虑底层文件系统实现如何在磁盘上存储该信息。尽管VFS为扩展属性提供了一个抽象层,这并不意味着每个文件系统都必须实现该特性。实际上,情况刚好相反。内核中的大多数文件系统都不支持扩展属性。但我们也应该注意到,Linux上所有主要的硬盘文件系统(Ext3、reiserfs、xfs等)都支持扩展属性

  1. 数据结构
    内核使用了一个简单的字符串来表示名称,而用一个void指针来表示值在内存中的存储位置

    扩展属性操作函数

    //include/linux/fs.h
    struct inode_operations { 
        ... 
        int (*setxattr) (struct dentry *, const char *,const void *,size_t,int); 
        ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t); 
        ssize_t (*listxattr) (struct dentry *, char *, size_t); 
        int (*removexattr) (struct dentry *, const char*); 
        ... 
    }
    

    文件系统可以为这些操作提供定制实现,但内核也提供了一组通用的处理程序。例如,Ext3文件系统即使用了内核的通用实现

    //include/linux/xattr.h
    //与块设备间传输信息,处理扩展属性的函数集合
    struct xattr_handler {
        char *prefix;//命名空间,该操作即应用到这个命名空间的属性上, XATTR_*_PREFIX 中的任何值
        size_t (*list)(struct inode *inode, char *list, size_t list_size,
                const char *name, size_t name_len);//列出与一个文件相关的所有扩展属性
        int (*get)(struct inode *inode, const char *name, void *buffer,
            size_t size);//从底层块设备读取扩展属性
        int (*set)(struct inode *inode, const char *name, const void *buffer,
            size_t size, int flags);//向底层块设备写入扩展属性
    };
    
    //include/linux/fs.h
    //超级块结构体
    struct super_block {
        ...
        struct xattr_handler	**s_xattr;//数组,指向用于处理扩展属性的函数指针
        ...
    };
    

    super_block中处理程序在数组(s_xattr)中出现的次序不是固定的。内核通过比较处理程序的前缀与所述扩展属性的命名空间前缀,可以找到一个适当的次序

    在这里插入图片描述

  2. 系统调用
    每个扩展属性操作(get、set和list)都对应了3个系统调用,三者的区别在于指定目标的方式。为避免代码复制,这些系统调用在结构上分为两部分:
    (1) 查找与目标对象相关联的dentry实例;
    (2) 其他工作委托给一个函数,该函数对3个系统调用是通用的。
    查找dentry实例时,可使用user_path_walk或user_path_walk_link,或直接读取包含在file实例中的指针,具体的做法取决于使用了哪个系统调用。在找到dentry实例后,就为3个系统调用建立了一个公共的基础。

    fs/xattr.c:
    在这里插入图片描述

    在这里插入图片描述

    listxattr函数流程:

    1. 修改用户空间程序给出的列表的最大程度,使之不超过内核所允许的扩展属性列表的最大长度XATTR_LIST_MAX,并分配所需的内存
    2. 调用inode_operations的listxattr方法,用各个名称/值对来填充分配的空间
    3. 将结果复制回用户空间
  3. 内核提供的扩展属性默认处理函数

    //fs/xattr.c
    ssize_t generic_getxattr(struct dentry *dentry, const char *name, void *buffer, size_t size)
    ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size)
    //内核设置文件扩展属性的默认实现
    int generic_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags)
    int generic_removexattr(struct dentry *dentry, const char *name)
    

    generic_setxattr函数流程:

    1. 查找扩展属性的命名空间的 xattr_handler 实例,通过遍历超级块中的属性函数数组比较前缀与属性名相同的
    2. 调用特定于文件系统的set函数(由具体文件系统实现)

    generic_listxattr函数流程:

    1. 如果保存结果的缓冲区为NULL,返回需要保存的buffer大小
    2. buffer不为NULL,将结果保存到buffer中,并返回大小

11.1.2 Ext3中的实现

  1. 数据结构

    Ext3采纳了一些提高编码效率的良好建议.按照标识号来访问处理程序函数,而不是按照字符串标识符。这简化了许多操作,并能够更高效地利用磁盘空间,因为与前缀字符串相比,标识号只需要存储一个数字

    //fs/ext3/xattr.c
    static struct xattr_handler *ext3_xattr_handler_map[] = {
        [EXT3_XATTR_INDEX_USER]		     = &ext3_xattr_user_handler,
    #ifdef CONFIG_EXT3_FS_POSIX_ACL
        [EXT3_XATTR_INDEX_POSIX_ACL_ACCESS]  = &ext3_xattr_acl_access_handler,
        [EXT3_XATTR_INDEX_POSIX_ACL_DEFAULT] = &ext3_xattr_acl_default_handler,
    #endif
        [EXT3_XATTR_INDEX_TRUSTED]	     = &ext3_xattr_trusted_handler,
    #ifdef CONFIG_EXT3_FS_SECURITY
        [EXT3_XATTR_INDEX_SECURITY]	     = &ext3_xattr_security_handler,
    #endif
    };
    

    Ext3扩展属性的磁盘布局:
    在这里插入图片描述

    说明:
    扩展属性所占的空间,由一个标识头开始,接下来是一系列数据项的列表。每个数据项都包含了属性名称和指向属性值存储区域的一个指针。在向文件添加新的扩展属性时,列表向下增长。
    属性值存储在扩展属性数据空间的尾部,值表与属性名称表相对增长。属性值可以按任意次序存储,通常与属性名排序不同
    这种类型的结构可以放置在两个地方:

    1. inode末尾的未使用空间
    2. 磁盘上一个独立的数据块

    第一种情况,只有使用了允许动态inode长度的新文件系统格式时(即,EXT3_DYNAMIC_REV),才可能出现。空闲空间的长度保存在ext3_inode_info->i_extra_isize。这两种方案可以同时使用,但所有扩展属性头和值的总长度不能超过一个数据块加上inode中空闲空间的长度。除了inode中的空闲空间之外,最多只能再使用一个数据块来存储扩展属性。实际上,所需的空间通常比一个完整的磁盘块要少得多。
    如果两个文件的扩展属性集合是相同的,那么二者可以共享同一磁盘表示

    //fs/ext3/xattr.h
    //用来实现ext3扩展属性在磁盘上存储的布局的头部结构
    struct ext3_xattr_header {
        __le32	h_magic;	/* 用于标识的魔数 *//* magic number for identification */
        __le32	h_refcount;	/* 引用计数 *//* reference count */
        __le32	h_blocks;	/* 使用磁盘块的数目,目前这个值总是1 *//* number of disk blocks used */
        __le32	h_hash;		/* 所有属性的散列值 *//* hash value of all attributes */
        __u32	h_reserved[4];	/* 目前为0 *//* zero right now */
    };
    
    //扩展属性的数据项结构,各个数据项的长度不见得是相同的,因为属性名的长度是可变的
    struct ext3_xattr_entry {
        __u8	e_name_len;	/* 属性名字长度 *//* length of name */
        __u8	e_name_index;	/* 属性名索引,用于索引 ext3_xattr_handler_map 表 *//* attribute name index */
        __le16	e_value_offs;	/* 属性值在所处磁盘块中的偏移量.如果扩展属性存储在inode内,则将ext3_value_offs用作偏移量,表示第一个数据项的开始位置 *//* offset in disk block of value */
        __le32	e_value_block;	/* 存储属性的磁盘块 *//* disk block attribute is stored on (n/i) */
        __le32	e_value_size;	/* 属性值长度 *//* size of attribute value */
        __le32	e_hash;		/* 属性名和值的散列值 *//* hash value of name and value */
        char	e_name[0];	/* 属性名,存储在磁盘上长度是不确定的 *//* attribute name */
    };
    
  2. 实现

    由于对不同的属性命名空间来说,处理程序的实现十分相似,所以以下讨论仅限于user命名空间的实现。其他命名空间的处理程序函数,仅有少许不同或完全相同。ext3_xattr_user_handler定义如下

    //fs/ext3/xattr_user.c
    struct xattr_handler ext3_xattr_user_handler = {
        .prefix	= XATTR_USER_PREFIX,
        .list	= ext3_xattr_user_list,
        .get	= ext3_xattr_user_get,
        .set	= ext3_xattr_user_set,
    };
    
    • 获取扩展属性 ext3_xattr_user_get
      在这里插入图片描述

      在这里插入图片描述

      在这里插入图片描述

    • 设置扩展属性 ext3_xattr_user_set
      在这里插入图片描述

      在这里插入图片描述

      ext3_xattr_set_handle函数流程:

      1. 获取inode位置
      2. 查找扩展属性数据,如果失败则从磁盘中找
      3. 操作属性,删除或修改或新建
        1. 如果传递到函数的数据缓冲区为NULL,则删除现存扩展属性。
        2. 如果数据缓冲区包含了属性值,则替换现存扩展属性的值或创建一个新的扩展属性。按照手册页setxattr(2)的文档,标志XATTR_REPLACEXATTR_CREATE分别表示在调用之前,所述属性必须是存在的还是不存在的
    • 列出扩展属性 ext3_listxattr
      ext3_listxattr 函数流程:

      1. 在inode或块中读取属性并将属性拷贝到缓冲中给用户层

11.1.3 Ext2中的实现

Ext2中的扩展属性的实现与Ext3的实现非常相似,但由于Ext3中的一些特性在Ext2中是不可用的,这是二者扩展属性的实现的差别

  1. 由于Ext2并不支持动态的inode长度,所以磁盘上的inode中没有足够空间存储扩展属性的数据。因此,扩展属性总是存储在一个独立的数据块中。这简化了一些函数,因为无须区分扩展属性的不同存储位置了
  2. Ext2并不使用日志,因此所有日志相关函数的调用都是不必要的。这也使得一些只处理句柄操作的包装器函数变得不必要

11.2 访问控制表

POSIX访问控制表(ACL)是POSIX标准定义的一种扩展,用于细化Linux的自主访问控制(DAC)模型。可以参考手册页acl(5)进一步了解相关概念。ACL借助扩展属性实现,修改ACL所用的方法与其他扩展属性也是相同的。内核对其他扩展属性的内容并不感兴趣,但ACL扩展属性将集成到inode的权限检查中。尽管文件系统可以自由选择用于表示扩展属性的物理格式,但内核仍然定义了用于表示访问控制表的交换结构。对于承载访问控制表的扩展属性,必须使用下列命名空间

//include/linux/posix_acl_xattr.h
#define POSIX_ACL_XATTR_ACCESS	"system.posix_acl_access"
#define POSIX_ACL_XATTR_DEFAULT	"system.posix_acl_default"

用户层程序getfacl、setfacl和chacl用于获取、设置和修改ACL的内容。它们使用可以操作扩展属性的标准系统调用,并不需要与内核进行非标准交互。许多其他实用程序(例如ls)也内建了对访问控制表的支持。

11.2.1 通用实现

用于实现ACL的通用代码包含在两个文件中:fs/posix_acl.c包含了分配新ACL、复制ACL、进行扩展权限检查等功能的代码;而fs/xattr_acl.c包含的函数用于在扩展属性和ACL的通用表示之间进行转换,并且转换是双向的。所有通用数据结构都定义在include/linux/posix_acl.h和include/linux/posix_acl_xattr.h

  1. 数据结构

    ACL相关数据的内存表示:

    //include/linux/posix_acl.h
    
    /* a_type field in acl_user_posix_entry_t */
    //用于acl_user_posix_entry_t中的a_type字段,ACL类型
    #define ACL_TYPE_ACCESS		(0x8000)
    #define ACL_TYPE_DEFAULT	(0x4000)
    
    /* e_tag entry in struct posix_acl_entry */
    //用于struct posix_acl_entry中的e_tag项,ACL标记
    #define ACL_USER_OBJ		(0x01)
    #define ACL_USER		(0x02)
    #define ACL_GROUP_OBJ		(0x04)
    #define ACL_GROUP		(0x08)
    #define ACL_MASK		(0x10)
    #define ACL_OTHER		(0x20)
    
    /* permissions in the e_perm field */
    //e_perm字段中的权限值,ACL权限
    #define ACL_READ		(0x04)
    #define ACL_WRITE		(0x02)
    #define ACL_EXECUTE		(0x01)
    //#define ACL_ADD		(0x08)
    //#define ACL_DELETE		(0x10)
    
    //用于存储与ACL相关的所有数据的内存表示,表项
    struct posix_acl_entry {
        short			e_tag;//标记,ACL_USER_OBJ 等
        unsigned short		e_perm;//权限 ACL_READ 等
        unsigned int		e_id;//用户/组 id
    };
    
    //用于存储与ACL相关的所有数据的内存表示的主要数据结构,Access Control List(访问控制列表),inode的ACL
    struct posix_acl {
        atomic_t		a_refcount;//引用计数
        unsigned int		a_count;//ACL项的数目
        struct posix_acl_entry	a_entries[0];//表项
    };
    

    ACL用扩展属性的表示,用于与用户层的交互

    //include/linux/posix_acl_xattr.h
    //用于ACL的扩展属性表示,用于与用户层的外部交互
    typedef struct {
        __le16			e_tag;
        __le16			e_perm;
        __le32			e_id;
    } posix_acl_xattr_entry;
    
    //用于ACL的扩展属性表示,用于与用户层的外部交互
    typedef struct {
        __le32			a_version;
        posix_acl_xattr_entry	a_entries[0];
    } posix_acl_xattr_header;
    

    用于内部和外部表示的结构十分相似,但用于外部表示的类型明确指定了字节序和位长。此外,对磁盘表示来说,引用计数是不必要的

    有两个函数用于在两种表示之间来回转换:posix_acl_from_xattrposix_acl_to_xattr

  2. 权限检查
    对涉及访问控制表的权限检查,内核通常需要底层文件系统的支持。或者文件系统自行实现所有的权限检查(通过struct inode_operations的permission函数),或者文件系统提供一个回调函数供generic_permission使用。内核中的大多数文件系统,都使用了后一种方法。
    generic_permission中使用的回调函数如下(请注意,check_acl表示回调函数)

    //fs/namei.c
    //通用权限检查函数,check_acl 为检查ACL权限的函数
    int generic_permission(struct inode *inode, int mask,
            int (*check_acl)(struct inode *inode, int mask))
    

    generic_permission 调用的 check_acl 回调函数最后会调用内核实现的 posix_acl_permission 函数

    //fs/posix_acl.c
    /* acl权限检查
    * inode:指向inode结构体的指针,
    * acl:指向acl(的内存表示)的指针,
    * want:要检查的权限( mode 中的 MAY_READ 、 MAY_WRITE 或 MAY_EXEC 权限位)。
    * 如果授予访问权限,则函数返回0,否则返回相应的错误码
    */
    int posix_acl_permission(struct inode *inode, const struct posix_acl *acl, int want)
    

11.2.2 Ext3中的实现

由于ACL基于扩展属性实现,并借助了许多通用的辅助例程,所以Ext3对ACL的实现相当简洁

  1. 数据结构
    ACL的磁盘表示的格式与通用的POSIX辅助函数所需的内存表示非常类似

    //fs/ext3/acl.h
    //ext3文件系统对ACL的表示,用于磁盘存储
    typedef struct {
        __le16		e_tag;
        __le16		e_perm;
        __le32		e_id;
    } ext3_acl_entry;
    
    //ext3_acl_entry的另一个版本,少了e_id节省磁盘存储空间
    typedef struct {
        __le16		e_tag;
        __le16		e_perm;
    } ext3_acl_entry_short;
    
    //ACL项的列表头
    typedef struct {
        __le32		a_version;//区分ACL实现的不同版本,目前为EXT3_ACL_VERSION(即0x0001)
    } ext3_acl_header;
    
    //include/linux/ext3_fs_i.h
    //Ext3 inode的内存表示
    struct ext3_inode_info {
        ...
    #ifdef CONFIG_EXT3_FS_POSIX_ACL
        struct posix_acl	*i_acl;//该posix_acl用作与inode关联的常规ACL列表,如果该信息不在内存中该值为EXT3_ACL_NOT_CACHED [定义为 (void *)-1 ]
        struct posix_acl	*i_default_acl;//指向默认ACL,可以关联到目录,并由子目录继承,如果该信息不在内存中该值为EXT3_ACL_NOT_CACHED [定义为 (void *)-1 ]
    #endif
    };
    
  2. 磁盘和内存表示之间的转换
    有两个转换函数可用于磁盘和内存表示之间的转换:ext3_acl_to_diskext3_acl_from_disk。二者的实现在fs/ext3/acl.c中。
    后者从inode中包含的信息获取裸数据,剥去头信息,将ACL列表中每个ACL项的数据从小端序格式转换为适用于系统本机CPU的格式。
    与此相对的函数是ext3_acl_to_disk,工作原理类似:它遍历给定的posix_acl实例中的所有ACL项,将其中包含的数据从特定于CPU的格式转换为小端序格式,并指定适当的位长。

  3. inode初始化 ext3_init_acl

    • ext3_new_inode
      • ext3_init_acl

    函数流程:

    1. 检查父目录inode是否支持acl,如果支持则获取父目录的acl,如果目录的超级块不支持acl或没有默认acl则将进程的umask应用到文件
    2. 如果当前inode表示目录则使用父目录的acl
    3. 如果不是目录,对默认 ACL 的内存表示创建一个副本
      1. 从inode创建进程指定的访问权限中,删除默认ACL不能授予的所有权限
  4. 获取ACL内存表示 ext3_get_acl
    在这里插入图片描述

    函数流程:

    1. 检查ACL的内存表示是否已经缓存到了 ext3_inode_info->i_acl,如果已经缓存,创建该表示的一个副本,将其作为结果返回
    2. 如果ACL并无缓存,从扩展属性子系统获取裸数据转为内存表示,并更新 ext3_inode_info 中acl的缓存字段
  5. 修改ACL ext3_acl_chmod

    • ext3_setattr
      • ext3_acl_chmod 负责保持ACL为最新数据并维护其一致性,ext3_setattr最后调用该函数,新权限已经设置到inode中
        在这里插入图片描述

    函数流程:

    1. 从inode中获取acl
    2. 创建acl副本
    3. 根据inode->i_mode修改acl
    4. 记录硬盘操作日志
    5. 将修改后的ACL数据写回
    6. 释放ACL副本
  6. 权限检查 ext3_check_acl

    • ext3_permission
      • generic_permission
        • check_acl(ext3中为 ext3_check_acl)
          • posix_acl_permission
            在这里插入图片描述

11.2.3 Ext2中的实现

Ext2对ACL的实现,几乎与Ext3完全相同。差别甚至比二者在扩展属性实现方面的差别还小,因为对于ACL,句柄相关的部分并没有划分为独立的函数。因此,将所有函数和数据结构中的ext3_前缀替换为ext2_

总结

许多文件系统都提供了一些特性,扩展了VFS层提供的标准功能
扩展属性存储在 inode 数据结构外

扩展属性所占的空间,由一个标识头开始,接下来是一系列数据项的列表。每个数据项都包含了属性名称和指向属性值存储区域的一个指针。在向文件添加新的扩展属性时,列表向下增长。
属性值存储在扩展属性数据空间的尾部,值表与属性名称表相对增长。属性值可以按任意次序存储,通常与属性名排序不同
扩展属性结构可以放置在两个地方:

  1. inode末尾的未使用空间
  2. 磁盘上一个独立的数据块

扩展属性也可以用于 posix的访问控制表(ACL),对UNIX风格的权限模型进行扩展

=========================================
涉及的命令和配置:

手册页attr(5)对属性有详细说明
手册页acl(5)对posix访问控制表有详细说明

请注意,另一篇很好的综述性文章是Andreas Grünbacher的Usenix论文[Grü03],其中给出了ACL的一般性概述,以及Linux支持的各种文件系统ACL实现的当前状态。Andreas Grünbacher是Ext2和Ext3文件系统中ACL支持的主要开发者之一

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值