xattr介绍

扩展属性是与文件和目录永久关联的name->value对,也可以理解为KV对。属性可以是定义的也可以是未定义的。如果已定义,则其值可以为空或非空。

扩展属性是对与系统中所有inode关联的普通属性(即**stat**(2)数据)的扩展。它们通常用于为文件系统提供其他功能-例如,可以使用扩展属性来实现其他安全功能,例如访问控制列表(ACL)。

具有文件或目录搜索权限的用户可以使用listxattr检索为该文件或目录定义的属性名称列表。

扩展属性作为原子对象访问。读取(getxattr)会检索属性的整个值,并将其存储在缓冲区中。写入(setxattr)将所有以前的值替换为新值。

扩展属性消耗的空间可以计入文件所有者和文件组的磁盘配额。

xattr的规格限制如下:

#define XATTR_NAME_MAX   255	/* name最大为255B */ 
#define XATTR_SIZE_MAX 65536	/* value最大为64KB */
#define XATTR_LIST_MAX 65536	/* 所以name的长度和最大为64KB*/

扩展属性命名空间

属性名称是以null终止的字符串。属性名称始终以完全限定的namespace.attribute形式指定,例如user.mime_typetrusted.md5sum,system.posix_acl_access或security.selinux。

命名空间机制用于定义扩展属性的不同类。存在这些不同的类有几个原因。例如,处理一个名称空间的扩展属性所需的权限和功能可能与另一名称空间不同。

当前,安全性,系统,可信和用户扩展属性类的定义如下。将来可能会添加其他类。

Extended security attributes

安全属性命名空间由内核安全模块(如"Security Enhanced Linux" selinux,安全增强型Linux)使用,并用于实现文件功能(请参阅**capabilities**(7))。对安全属性的读写权限取决于安全模块为每个安全属性实施的策略。当未加载安全模块时,则所有进程都具有对扩展安全属性的读权限,而写权限仅限于具有CAP_SYS_ADMIN功能的进程

System extended attributes

内核使用系统扩展属性来存储系统对象,例如访问控制列表。对系统属性的读写权限取决于内核中文件系统为每个系统属性实现的策略。

Trusted extended attributes

受信任的扩展属性仅对具有CAP_SYS_ADMIN功能的进程可见并且可访问。此类中的属性用于在用户空间(即内核外部)中实现将信息保留在普通进程不应该访问的扩展属性中的机制。

User extended attributes

可以将用户扩展属性分配给文件和目录,以存储任意附加信息,例如mime类型,字符集或文件编码。用户属性的访问权限由文件权限位定义:需要读取权限才能检索属性值,而需要写入者权限才能更改属性值。

常规文件和目录的文件权限位的解释与特殊文件和符号链接的文件权限位的解释不同。对于常规文件和目录,文件许可权位定义对文件内容的访问,而对于设备专用文件,它们定义对特殊文件所描述的设备的访问。在访问检查中不使用符号链接的文件许可权。这些差异将使用户以无法由组或世界可写特殊文件和目录的磁盘配额控制的方式消耗文件系统资源。

因此,用户扩展属性仅允许用于常规文件和目录,并且对用户扩展属性的访问仅限于所有者和具有具有粘性位的目录的适当功能的用户(请参见**chmod**(1)手册页粘性位的说明)。

文件系统差异

内核和文件系统可能会限制可与文件关联的扩展属性的最大数量和大小。 VFS施加了以下限制:属性名称限制为255个字节,属性值限制为64KB。可以返回的属性名称列表也限制为64 kB(请参阅**listxattr**(2)中的BUGS)。

某些文件系统,例如Reiserfs(以及以前的ext2和ext3),要求使用user_xattr挂载选项挂载文件系统,以便使用用户扩展属性。

在当前的ext2,ext3和ext4文件系统实现中,文件的所有扩展属性的名称和值使用的总字节数必须适合单个文件系统块(1024、2048或4096字节,具体取决于何时指定的块大小)。文件系统已创建)。

在Btrfs,XFS和Reiserfs文件系统实现中,对与文件关联的扩展属性的数量没有实际限制,并且用于在磁盘上存储扩展属性信息的算法是可伸缩的。

在JFS,XFS和Reiserfs文件系统实现中,EA值中使用的字节数限制是VFS施加的上限。

在Btrfs文件系统实现中,用于名称,值和实现开销字节的总字节数限制为文件系统节点大小的值(默认为16 kB)。

xattr POSIX API介绍

setxattr

API定义

#include <sys/types.h>
#include <sys/xattr.h>

int setxattr(const char *path, const char *name,
             const void *value, size_t size, int flags);
int lsetxattr(const char *path, const char *name,
              const void *value, size_t size, int flags);
int fsetxattr(int fd, const char *name,
              const void *value, size_t size, int flags);

API介绍

  1. setxattr()通过文件路径path和扩展属性名称name,设置扩展属性的值。 size参数指定扩展属性值的大小(以字节为单位);零长度值是允许的。
  2. lsetxattr()与setxattr()相同,只是在符号链接的情况下,扩展属性是在链接本身而不是它所引用的文件上设置的。
  3. fsetxattr()与setxattr()相同,只是用open打开的文件句柄fd代替了path
  4. name为扩展属性的名称,字符串类型。该名称需要包括相关的命名空间名前缀,前缀结尾字符为.例如用户态的命名空间前缀为user.。
  5. value为扩展属性的值,可以是文本或二进制数据块。
  6. flags,默认情况下(即标志为零),语义为,如果扩展属性不存在,则将创建扩展属性;如果属性已存在,则将替换该值。可以设置以下值,来修改语义:
  • XATTR_CREATE 0x1 执行纯创建,如果指定属性已存在,创建将失败。
  • XATTR_REPLACE 0x2 执行纯替换操作,如果指定属性不存在,该操作将失败。

返回值介绍

成功时,返回0。失败时,将返回-1并正确设置errno。相关错误码说明如下:

  • EDQUOT 磁盘配额限制意味着没有足够的剩余空间来存储扩展属性。
  • EEXIST 指定了XATTR_CREATE,并且该属性已经存在。
  • ENODATA 指定了XATTR_REPLACE,并且该属性不存在。
  • ENOSPC 剩余空间不足,无法存储扩展属性。
  • ENOTSUP 名称的命名空间前缀无效,或文件系统不支持扩展属性,或者禁用了扩展属性。
  • EPERM 该文件被标记为不可变或仅附加。
  • ERANGE 名称或值的大小超过了文件系统特定的限制。
  • stat(2)中记录的错误也可能发生。

getxattr

API定义

#include <sys/types.h>
#include <sys/xattr.h>

ssize_t getxattr(const char *path, const char *name,
                 void *value, size_t size);
ssize_t lgetxattr(const char *path, const char *name,
                 void *value, size_t size);
ssize_t fgetxattr(int fd, const char *name,
                 void *value, size_t size);

API介绍

  1. getxattr()根据文件路径path和扩展属性名称name,获取其扩展属性的值。属性值放置在value所指向的缓冲区中, size指定该缓冲区的大小。返回值为放在value中的字节数。注意:size最大值为64KB。
  2. lgetxattr()与getxattr()相同,只是在符号链接的情况下,获取符号链接本身的扩展属性,而不是它所引用的文件。
  3. fgetxattr()与getxattr()相同,只是用open打开的文件句柄fd代替了path
  4. size指定为零,则这些接口的返回值为扩展属性的当前大小(并使值保持不变)。这可以用来确定在后续调用中应提供的缓冲区的大小。 注意:两次调用之间属性值可能会发生变化,因此仍然有必要检查第二次调用的返回状态。

返回值介绍

成功后,这些调用将返回非负值,该值是扩展属性值的大小(以字节为单位)。失败时,将返回-1并正确设置errno。相关错误码说明如下:

  • E2BIG 属性值的大小大于允许的最大大小;无法检索该属性。例如,在支持非常大的属性值的文件系统(例如NFSv4)上可能会发生这种情况。
  • ENODATA name的扩展属性不存在,或者进程无法访问此属性。
  • ENOTSUP 文件系统不支持或禁用扩展属性。
  • ERANGE 值缓冲区的大小(size)太小,无法保存结果。
  • stat(2)中记录的错误也可能发生。

removexattr

API定义

#include <sys/types.h>
#include <sys/xattr.h>

int removexattr(const char *path, const char *name);
int lremovexattr(const char *path, const char *name);
int fremovexattr(int fd, const char *name);

API介绍

  1. removexattr()删除由名称标识并与文件系统中给定路径关联的扩展属性。
  2. lremovexattr()与removexattr()相同,只是在符号链接的情况下,删除链接本身而不是它引用的文件的扩展属性。
  3. fremovexattr()与removexattr()相同,只是用open打开的文件句柄fd代替了path。。

返回值介绍

成功时,返回零。失败时,将返回-1并正确设置errno。相关错误码说明如下:

  • ENODATA name的扩展属性不存在。
  • ENOTSUP 文件系统不支持或禁用扩展属性。
  • stat(2)中记录的错误也可能发生。

listxattr

API定义

#include <sys/types.h>
#include <sys/xattr.h>

ssize_t listxattr(const char *path, char *list, size_t size);
ssize_t llistxattr(const char *path, char *list, size_t size);
ssize_t flistxattr(int fd, char *list, size_t size);

API介绍

  1. listxattr()检索与文件路径path关联的扩展属性名称的列表。检索到的列表放在list中,该list是调用方分配的缓冲区,其大小(以字节为单位)在参数size中指定。该list是由一组一个接一个(以null即\0结尾)的名称字符串组成。list中可能会省略调用过程无法访问的扩展属性的名称。返回属性名称列表的实际长度(字节数)。
  2. llistxattr()与listxattr()相同,只是在符号链接的情况下,该链接检索的是与链接本身关联的扩展属性的名称列表,而不是它所引用的文件。
  3. flistxattr()与listxattr()相同,只是用open打开的文件句柄fd代替了path
  4. list举例:user.name1\0user.name2\0system.posix_acl_access\0system.posix_acl_default\0system.name1\0
  5. size指定为零,则这些调用将返回扩展属性名称列表的当前大小(并使列表保持不变)。这可以用来确定在后续调用中应提供的缓冲区的大小。 注意:两次调用之间扩展属性名称列表可能会发生变化,因此仍然有必要检查第二次调用的返回状态。

返回值介绍

成功后,将返回一个非负数,以指示扩展属性名称列表的大小。失败时,将返回-1并正确设置errno。相关错误码说明如下:

  • E2BIG 扩展属性名称列表的大小大于允许的最大大小。该列表无法检索。例如,在每个文件支持无限数量的扩展属性的文件系统(例如XFS)上可能会发生这种情况。参见错误。
  • ENOTSUP 文件系统不支持或禁用扩展属性。
  • ERANGE 列表缓冲区的大小太小,无法保存结果。
  • stat(2)中记录的错误也可能发生

xattr ext4实现

xattr相关公共部分的宏定义

//include/uapi/linux/xattr.h
// 定义的setxattr的语义标识
#define XATTR_CREATE	0x1	/* set value, fail if attr already exists */
#define XATTR_REPLACE	0x2	/* set value, fail if attr does not exist */

// 定义了扩展属性名称的命名空间名的前缀,如果user. security.等
#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)

#define XATTR_POSIX_ACL_ACCESS  "posix_acl_access"
#define XATTR_NAME_POSIX_ACL_ACCESS XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_ACCESS
#define XATTR_POSIX_ACL_DEFAULT  "posix_acl_default"
#define XATTR_NAME_POSIX_ACL_DEFAULT XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_DEFAULT

//include/uapi/linux/limits.h
#define XATTR_NAME_MAX   255	/* # chars in an extended attribute name */
#define XATTR_SIZE_MAX 65536	/* size of an extended attribute value (64k) */
#define XATTR_LIST_MAX 65536	/* size of extended attribute namelist (64k) */

ext4相关操作函数定义

// fs/ext4/namei.c
// 其中setxattr、getxattr和removexattr都是走相应generic系列函数
// listxattr->ext4_listxattr, get_acl->ext4_get_acl
const struct inode_operations ext4_dir_inode_operations = {
	.create		= ext4_create,
	.lookup		= ext4_lookup,
	.link		= ext4_link,
	.unlink		= ext4_unlink,
	.symlink	= ext4_symlink,
	.mkdir		= ext4_mkdir,
	.rmdir		= ext4_rmdir,
	.mknod		= ext4_mknod,
	.rename		= ext4_rename,
	.setattr	= ext4_setattr,
	.setxattr	= generic_setxattr,
	.getxattr	= generic_getxattr,
	.listxattr	= ext4_listxattr,
	.removexattr	= generic_removexattr,
	.get_acl	= ext4_get_acl,
	.fiemap         = ext4_fiemap,
};

//fs/ext4/xattr.c
const struct xattr_handler *ext4_xattr_handlers[] = {
	&ext4_xattr_user_handler,
	&ext4_xattr_trusted_handler,
#ifdef CONFIG_EXT4_FS_POSIX_ACL
	&ext4_xattr_acl_access_handler,
	&ext4_xattr_acl_default_handler,
#endif
#ifdef CONFIG_EXT4_FS_SECURITY
	&ext4_xattr_security_handler,
#endif
	NULL
};

//fs/ext4/xattr_user.c ext4 user.的xattr的处理函数
const struct xattr_handler ext4_xattr_user_handler = {
	.prefix	= XATTR_USER_PREFIX,
	.list	= ext4_xattr_user_list,
	.get	= ext4_xattr_user_get,
	.set	= ext4_xattr_user_set,
};

setxattr

stap信息

#attr -s "test5" -V "1123" /mnt/ext4/xattr_test/1.txt
#attr 系统调用是SyS_lsetxattr attr会在xattr name前添加user.
generic_getxattr fileName:attr xattrName:security.capability called by execname:bash pid:2425
setxattr fileName:1.txt xattrName:user.test5 called by execname:attr pid:2425
generic_setxattr fileName:1.txt xattrName:user.test5 called by execname:attr pid:2425
ext4_xattr_set_handle xattrName:test5, called by execname:attr pid:2425
 0xffffffffc0752aa0 : ext4_xattr_set_handle+0x0/0x490 [ext4]
 0xffffffffc0753027 : ext4_xattr_set+0xf7/0x150 [ext4]
 0xffffffffc0753a6f : ext4_xattr_user_set+0x3f/0x50 [ext4]
 0xffffffffad076be8 : generic_setxattr+0x68/0x80 [kernel]
 0xffffffffad0773f5 : __vfs_setxattr_noperm+0x65/0x1b0 [kernel]
 0xffffffffad0775f5 : vfs_setxattr+0xb5/0xc0 [kernel]
 0xffffffffad07774c : setxattr+0x14c/0x1e0 [kernel]
 0xffffffffad077b3f : SyS_lsetxattr+0xaf/0xf0 [kernel]
 0xffffffffad593f92 : system_call_fastpath+0x25/0x2a [kernel]
 0x7fd2a4ee7d19
 
 #测试程序 ./setxattr /mnt/ext4/xattr_test/1.txt user.test2 test2-123
generic_getxattr fileName:setxattr xattrName:security.capability called by execname:bash pid:2424
setxattr fileName:1.txt xattrName:user.test2 called by execname:setxattr pid:2424
generic_setxattr fileName:1.txt xattrName:user.test2 called by execname:setxattr pid:2424
ext4_xattr_set_handle xattrName:test2, called by execname:setxattr pid:2424
 0xffffffffc0752aa0 : ext4_xattr_set_handle+0x0/0x490 [ext4]
 0xffffffffc0753027 : ext4_xattr_set+0xf7/0x150 [ext4]
 0xffffffffc0753a6f : ext4_xattr_user_set+0x3f/0x50 [ext4]
 0xffffffffad076be8 : generic_setxattr+0x68/0x80 [kernel]
 0xffffffffad0773f5 : __vfs_setxattr_noperm+0x65/0x1b0 [kernel]
 0xffffffffad0775f5 : vfs_setxattr+0xb5/0xc0 [kernel]
 0xffffffffad07774c : setxattr+0x14c/0x1e0 [kernel]
 0xffffffffad077a47 : sys_setxattr+0xb7/0x100 [kernel]
 0xffffffffad593f92 : system_call_fastpath+0x25/0x2a [kernel]
 0x7f7c27dbd66a
 # 注意调用setxattr时,需要传入的xattr name带前缀,否则不支持。
 # ./setxattr /mnt/ext4/xattr_test/1.txt "test6" "11111"
 # setxattr name:test6 value:11111 ret:-1 errno:95, err_str:Operation not supported

#smbd 创建文件5.txt,或rename后
#smbd 系统调用是sys_setxattr
setxattr fileName:5.txt xattrName:user.DOSATTRIB called by execname:smbd  pid:1915
generic_setxattr xattrName:user.DOSATTRIB called by execname:smbd  pid:1915
ext4_xattr_set_handle xattrName:DOSATTRIB, called by execname:smbd  pid:1915
 0xffffffffc091eaa0 : ext4_xattr_set_handle+0x0/0x490 [ext4]
 0xffffffffc091f027 : ext4_xattr_set+0xf7/0x150 [ext4]
 0xffffffffc091fa6f : ext4_xattr_user_set+0x3f/0x50 [ext4]
 0xffffffffa9a76be8 : generic_setxattr+0x68/0x80 [kernel]
 0xffffffffa9a773f5 : __vfs_setxattr_noperm+0x65/0x1b0 [kernel]
 0xffffffffa9a775f5 : vfs_setxattr+0xb5/0xc0 [kernel]
 0xffffffffa9a7774c : setxattr+0x14c/0x1e0 [kernel]
 0xffffffffa9a77b3f : SyS_lsetxattr+0xaf/0xf0 [kernel]
 0xffffffffa9f93f92 : system_call_fastpath+0x25/0x2a [kernel]
 0x7f5b684f9d19

代码流程

// 以user.的前缀举例
system_call
  setxattr
    vfs_setxattr
      __vfs_setxattr_noperm
        generic_setxattr
          ext4_xattr_user_set
            ext4_xattr_set
              ext4_xattr_set_handle

// 代码如下
// fs/xattr.c
static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
	 size_t size, int flags)
{
    // flag检查必须是create或replace, create时如果name已经存在返回EEXIST
    if (flags & ~(XATTR_CREATE|XATTR_REPLACE))
		return -EINVAL;
    // 用户态xattr name拷贝到内核态
    error = strncpy_from_user(kname, name, sizeof(kname));
    if (size) {
        //用户态xattr value拷贝到内核态
		kvalue = kmalloc(size, GFP_KERNEL | __GFP_NOWARN);
		copy_from_user(kvalue, value, size)

         // 更新acl的uid和gid
         // XATTR_NAME_POSIX_ACL_ACCESS system.posix_acl_access
         // XATTR_NAME_POSIX_ACL_DEFAULT system.posix_acl_default
         if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
		    (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
			posix_acl_fix_xattr_from_user(kvalue, size);
	}
	error = vfs_setxattr(d, kname, kvalue, size, flags);
}

int
vfs_setxattr(struct dentry *dentry, const char *name, const void *value,
		size_t size, int flags)
{
    struct inode *inode = dentry->d_inode;
	error = xattr_permission(inode, name, MAY_WRITE); // 权限检查
	mutex_lock(&inode->i_mutex);
	error = security_inode_setxattr(dentry, name, value, size, flags);
	error = __vfs_setxattr_noperm(dentry, name, value, size, flags);
    mutex_unlock(&inode->i_mutex);
	return error;
}

int __vfs_setxattr_noperm(struct dentry *dentry, const char *name,
		const void *value, size_t size, int flags)
{
    if (inode->i_op->setxattr) { // ext4_dir_inode_operations.setxattr = generic_setxattr
        error = inode->i_op->setxattr(dentry, name, value, size, flags);
		if (!error) {
			fsnotify_xattr(dentry);
			security_inode_post_setxattr(dentry, name, value,
						     size, flags);
		}
    }
}

int
generic_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags)
{
	const struct xattr_handler *handler;

	if (size == 0)
		value = "";  /* empty EA, do not remove */
    // 根据name的前缀获取相应的xattr的handler,如果不带前缀或没有找到相应前缀,返回不支持。
    // 这里已user. 对应的handler = ext4_xattr_user_handler, 此处的name会偏移到user.后面
	handler = xattr_resolve_name(dentry->d_sb->s_xattr, &name);
	if (!handler)
		return -EOPNOTSUPP;
    // ext4_xattr_user_handler->set = ext4_xattr_user_set,这里注意下,value的size可以为0,但是value不     // 为NULL,注意和generic_removexattr的区分
	return handler->set(dentry, name, value, size, flags, handler->flags);
}


//fs/ext4/xattr_user.c
static int
ext4_xattr_user_set(struct dentry *dentry, const char *name,
		    const void *value, size_t size, int flags, int type)
{
	if (strcmp(name, "") == 0)
		return -EINVAL;
	if (!test_opt(dentry->d_sb, XATTR_USER))
		return -EOPNOTSUPP;
	return ext4_xattr_set(dentry->d_inode, EXT4_XATTR_INDEX_USER,
			      name, value, size, flags);
}

//fs/ext4/xattr.c
int
ext4_xattr_set(struct inode *inode, int name_index, const char *name,
	       const void *value, size_t value_len, int flags)
{
	handle_t *handle;
	int error, retries = 0;
	int credits = ext4_jbd2_credits_xattr(inode);
	// 记录日志,提交日志
	handle = ext4_journal_start(inode, EXT4_HT_XATTR, credits);
	error = ext4_xattr_set_handle(handle, inode, name_index, name,
					      value, value_len, flags); //xattr 信息记录到日志
	error2 = ext4_journal_stop(handle);
	return error;
}

/*
 * ext4_xattr_set_handle()
 *
 * Create, replace or remove an extended attribute for this inode.  Value
 * is NULL to remove an existing extended attribute, and non-NULL to
 * either replace an existing extended attribute, or create a new extended
 * attribute. The flags XATTR_REPLACE and XATTR_CREATE
 * specify that an extended attribute must exist and must not exist
 * previous to the call, respectively.
 *
 * Returns 0, or a negative error number on failure.
 */
int
ext4_xattr_set_handle(handle_t *handle, struct inode *inode, int name_index,
		      const char *name, const void *value, size_t value_len,
		      int flags)
{
    ext4_xattr_ibody_find(inode, &i, &is); //先在inode信息里面寻找,inode的结尾部分存储xattr
    ext4_xattr_block_find(inode, &i, &bs); //inode没找到,再去xattr的block里面寻找
    //根据找到的结果,判断是否可以create、replace或remove
    //inode可以set,优先保存在inode信息里面,然后再保存到block中
    ext4_xattr_ibody_set(handle, inode, &i, &is); 
    ext4_xattr_block_set(handle, inode, &i, &bs);
    
    
}

ext4_xattr_set_handle的调用关系如下:

扩展vfat文件系统大小 文件系统扩展属性_扩展属性

acl、user、security和trusted的setxattr最终都会调用ext4_xattr_set_handle将xattr信息记录到日志,并最终落盘。

getxattr

stap信息

# 测试程序 ./getxattr /mnt/ext4/xattr_test/1.txt user.test2
generic_getxattr fileName:getxattr xattrName:security.capability called by execname:bash pid:2393
getxattr fileName:1.txt xattrName:user.test2 called by execname:getxattr pid:2393
generic_getxattr fileName:1.txt xattrName:user.test2 called by execname:getxattr pid:2393
ext4_xattr_get xattrName:test2, called by execname:getxattr pid:2393
 0xffffffffc07526c0 : ext4_xattr_get+0x0/0x2a0 [ext4]
 0xffffffffc0753abc : ext4_xattr_user_get+0x3c/0x50 [ext4]
 0xffffffffad076a62 : generic_getxattr+0x52/0x70 [kernel]
 0xffffffffad076eea : vfs_getxattr+0x8a/0xb0 [kernel]
 0xffffffffad077026 : getxattr+0xa6/0x180 [kernel]
 0xffffffffad077cf4 : SyS_getxattr+0x64/0xc0 [kernel]
 0xffffffffad593f92 : system_call_fastpath+0x25/0x2a [kernel]
 0x7f8ff337d51a

代码流程

// fs/xattr.c
static ssize_t
getxattr(struct dentry *d, const char __user *name, void __user *value,
	 size_t size) {
    
    error = strncpy_from_user(kname, name, sizeof(kname)); // 用户态xattr name拷贝到内核态
    error = vfs_getxattr(d, kname, kvalue, size);
    if (error > 0) { //将xattr内容拷贝到用户太
		if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
		    (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
			posix_acl_fix_xattr_to_user(kvalue, size);
		if (size && copy_to_user(value, kvalue, error))
    }
}
ssize_t
vfs_getxattr(struct dentry *dentry, const char *name, void *value, size_t size) {
    error = xattr_permission(inode, name, MAY_READ); // xattr权限检查
    error = security_inode_getxattr(dentry, name); //获取安全相关属性
    if (inode->i_op->getxattr) // ext4_dir_inode_operations.getxattr = generic_getxattr
		error = inode->i_op->getxattr(dentry, name, value, size);
}

ssize_t
generic_getxattr(struct dentry *dentry, const char *name, void *buffer, size_t size)
{
	const struct xattr_handler *handler;
    // xattr的generic系列函数就是根据name的前缀获取相应的handler
	// 根据name的前缀获取相应的xattr的handler,ext4_xattr_user_handler
	handler = xattr_resolve_name(dentry->d_sb->s_xattr, &name);
	if (!handler)
		return -EOPNOTSUPP; 
    // ext4_xattr_user_get
	return handler->get(dentry, name, buffer, size, handler->flags);
}

//fs/ext4/xattr_user.c
static int
ext4_xattr_user_get(struct dentry *dentry, const char *name,
		    void *buffer, size_t size, int type)
{
	if (strcmp(name, "") == 0)
		return -EINVAL;
	if (!test_opt(dentry->d_sb, XATTR_USER))
		return -EOPNOTSUPP;
	return ext4_xattr_get(dentry->d_inode, EXT4_XATTR_INDEX_USER,
			      name, buffer, size);
}

//fs/ext4/xattr.c
int
ext4_xattr_get(struct inode *inode, int name_index, const char *name,
	       void *buffer, size_t buffer_size)
{
	int error;
	// 先去inode信息里面获取xattr,没找到再去block里面获取
	down_read(&EXT4_I(inode)->xattr_sem);
	error = ext4_xattr_ibody_get(inode, name_index, name, buffer,
				     buffer_size);
	if (error == -ENODATA)
		error = ext4_xattr_block_get(inode, name_index, name, buffer,
					     buffer_size);
	up_read(&EXT4_I(inode)->xattr_sem);
	return error;
}

ext4_xattr_get的调用关系如下:

扩展vfat文件系统大小 文件系统扩展属性_扩展vfat文件系统大小_02

removexattr

stap信息

generic_getxattr fileName:removexattr xattrName:security.capability called by execname:bash pid:2421
removexattr fileName:1.txt xattrName:user.test2 called by execname:removexattr pid:2421
generic_removexattr fileName:1.txt xattrName:user.test2 called by execname:removexattr pid:2421
ext4_xattr_set_handle xattrName:test2, called by execname:removexattr pid:2421
 0xffffffffc0752aa0 : ext4_xattr_set_handle+0x0/0x490 [ext4]
 0xffffffffc0753027 : ext4_xattr_set+0xf7/0x150 [ext4]
 0xffffffffc0753a6f : ext4_xattr_user_set+0x3f/0x50 [ext4]
 0xffffffffad076c4c : generic_removexattr+0x4c/0x60 [kernel]
 0xffffffffad077267 : vfs_removexattr+0x87/0x130 [kernel]
 0xffffffffad077365 : removexattr+0x55/0x80 [kernel]
 0xffffffffad078184 : SyS_removexattr+0x94/0xd0 [kernel]
 0xffffffffad593f92 : system_call_fastpath+0x25/0x2a [kernel]
 0x7fe0e1633637
 #removexattr最终还是调用了ext4_xattr_set_handle,通过判断value是否为NULL区分是set还是remove。value为NULL是在generic_removexattr中设置的。

代码流程

// fs/xattr.c
static long
removexattr(struct dentry *d, const char __user *name)
{
	int error;
	char kname[XATTR_NAME_MAX + 1];
	error = strncpy_from_user(kname, name, sizeof(kname));
	return vfs_removexattr(d, kname);
}

int
vfs_removexattr(struct dentry *dentry, const char *name)
{
	struct inode *inode = dentry->d_inode;
	int error;
	if (!inode->i_op->removexattr)
		return -EOPNOTSUPP;

	error = xattr_permission(inode, name, MAY_WRITE);
	error = security_inode_removexattr(dentry, name);
     // ext4_dir_inode_operations.getxattr = generic_removexattr
	error = inode->i_op->removexattr(dentry, name);
	if (!error) {
		fsnotify_xattr(dentry);
		evm_inode_post_removexattr(dentry, name);
	}
	return error;
}

int
generic_removexattr(struct dentry *dentry, const char *name)
{
	const struct xattr_handler *handler;
	handler = xattr_resolve_name(dentry->d_sb->s_xattr, &name);
    // 这里需要注意 remove实际调用的是set,其中value=NULL,size=0, flag=XATTR_REPLACE
    // ext4_xattr_user_set
	return handler->set(dentry, name, NULL, 0,
			    XATTR_REPLACE, handler->flags);
}

listxattr

stap信息

# ./listxattr /mnt/ext4/xattr_test/4.txt
# security.selinux
# user.DOSATTRIB
listxattr fileName:4.txt called by execname:listxattr pid:3050
ext4_xattr_list fileName:4.txt called by execname:listxattr pid:3050
ext4_xattr_list_entries fileName:4.txt called by execname:listxattr pid:3050
ext4_xattr_security_list fileName:4.txt xattrName:selinux called by execname:listxattr pid:3050
 0xffffffffc07586c0 : ext4_xattr_security_list+0x0/0x70 [ext4]
 0xffffffffc0750987 : ext4_xattr_list_entries+0x67/0xc0 [ext4]
 0xffffffffc07524f1 : ext4_listxattr+0x2d1/0x360 [ext4]
 0xffffffffad076f55 : vfs_listxattr+0x45/0x70 [kernel]
 0xffffffffad077158 : listxattr+0x58/0xe0 [kernel]
 0xffffffffad077f2e : sys_listxattr+0x5e/0xb0 [kernel]
 0xffffffffad593f92 : system_call_fastpath+0x25/0x2a [kernel]
 0x7f9392751547
ext4_xattr_list_entries fileName:4.txt called by execname:listxattr pid:3050
ext4_xattr_user_list fileName:4.txt xattrName:DOSATTRIB called by execname:listxattr pid:3050
 0xffffffffc0753ad0 : ext4_xattr_user_list+0x0/0x80 [ext4]
 0xffffffffc0750987 : ext4_xattr_list_entries+0x67/0xc0 [ext4]
 0xffffffffc075237f : ext4_listxattr+0x15f/0x360 [ext4]
 0xffffffffad076f55 : vfs_listxattr+0x45/0x70 [kernel]
 0xffffffffad077158 : listxattr+0x58/0xe0 [kernel]
 0xffffffffad077f2e : sys_listxattr+0x5e/0xb0 [kernel]
 0xffffffffad593f92 : system_call_fastpath+0x25/0x2a [kernel]
 0x7f9392751547

代码流程

// fs/xattr.c
static ssize_t
listxattr(struct dentry *d, char __user *list, size_t size)
{
    // 申请内核态list内存空间
	klist = kmalloc(size, __GFP_NOWARN | GFP_KERNEL);
	error = vfs_listxattr(d, klist, size);
    // 将结果拷贝到用户态
    copy_to_user(list, klist, error)
	return error;
}

ssize_t
vfs_listxattr(struct dentry *d, char *list, size_t size)
{
	ssize_t error;
	error = security_inode_listxattr(d);
    // ext4_dir_inode_operations.listxattr = ext4_listxattr
	if (d->d_inode->i_op->listxattr) {
		error = d->d_inode->i_op->listxattr(d, list, size);
	return error;
}
    
// fs/ext4/xattr.c
ssize_t
ext4_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
	return ext4_xattr_list(dentry, buffer, size);
}
    
static int
ext4_xattr_list(struct dentry *dentry, char *buffer, size_t buffer_size)
{
	int ret, ret2;
    // 先获取inode里面的xattr的name,在获取block里面的xattr的name
    // ext4_xattr_ibody_list和ext4_xattr_block_list最后都会调用
    // ext4_xattr_list_entries将name拼接到buffer里面
	ret = ret2 = ext4_xattr_ibody_list(dentry, buffer, buffer_size);
    // 更新buffer的偏移位置
	if (buffer) {
		buffer += ret;
		buffer_size -= ret;
	}
	ret = ext4_xattr_block_list(dentry, buffer, buffer_size);
	ret += ret2;
	return ret;
}
    
static int
ext4_xattr_list_entries(struct dentry *dentry, struct ext4_xattr_entry *entry,
			char *buffer, size_t buffer_size)
{
	size_t rest = buffer_size;

    // 遍历xattr的name
	for (; !IS_LAST_ENTRY(entry); entry = EXT4_XATTR_NEXT(entry)) {
        // 根据xattr的name的索引,获取xattr的name所属的命名空间的操作句柄,
        // 包括user、acl、trusted、security
		const struct xattr_handler *handler =
			ext4_xattr_handler(entry->e_name_index);

		if (handler) {
             // ext4_xattr_user_handler调用ext4_xattr_user_list
             // 主要作用就是将前缀添加到name前面,user.name
			size_t size = handler->list(dentry, buffer, rest,
						    entry->e_name,
						    entry->e_name_len,
						    handler->flags);
			if (buffer) {
				if (size > rest)
					return -ERANGE;
				buffer += size;
			}
			rest -= size;
		}
	}
	return buffer_size - rest;
}