Extended Attributes (xattr)
Attaching arbitrary metadata to files and directories
What are extended attributes?
Extended attributes (xattr) allow attaching name-value pairs to files and directories. They extend the traditional POSIX metadata (owner, permissions, timestamps) with arbitrary key-value data. Xattr support was introduced in Linux 2.4 (man page).
#include <sys/xattr.h>
/* Set an extended attribute */
setxattr("/path/to/file", "user.comment", "reviewed", 8, 0);
/* Get an extended attribute */
char value[256];
ssize_t len = getxattr("/path/to/file", "user.comment", value, sizeof(value));
/* List all attributes */
char list[4096];
ssize_t list_len = listxattr("/path/to/file", list, sizeof(list));
/* list is NUL-separated: "user.comment\0user.author\0" */
/* Remove an attribute */
removexattr("/path/to/file", "user.comment");
There are also l variants (lgetxattr, lsetxattr) that don't follow symlinks, and f variants (fgetxattr, fsetxattr) that operate on open file descriptors.
Namespace prefixes
Every xattr name must begin with a recognized namespace prefix:
| Namespace | Prefix | Who can set/get | Purpose |
|---|---|---|---|
user |
user. |
Any process (with write permission on file) | General application use |
system |
system. |
Kernel and privileged processes | NFSv4 ACLs, POSIX ACLs |
security |
security. |
Kernel security modules (LSMs) | SELinux labels, AppArmor |
trusted |
trusted. |
CAP_SYS_ADMIN only | Container runtimes, OverlayFS |
user namespace
Any process with write permission on the file can set user.* attributes:
# Tag a file for application use
setfattr -n user.reviewed -v "2024-01-15" report.pdf
getfattr -n user.reviewed report.pdf
# file: report.pdf
# user.reviewed="2024-01-15"
# List all user attributes
getfattr -d report.pdf
Restrictions: - Not supported on symlinks or device files (to prevent privilege escalation via root-owned devices) - Subject to filesystem quota if enabled
security namespace
Used by Linux Security Modules. You rarely set these directly:
# SELinux file context label
getfattr -n security.selinux /etc/passwd
# security.selinux="system_u:object_r:passwd_file_t:s0"
# AppArmor (uses security.apparmor)
getfattr -n security.apparmor /usr/bin/python3
Setting security.* requires CAP_SYS_ADMIN or LSM-specific permissions.
trusted namespace
Requires CAP_SYS_ADMIN. Used by:
- OverlayFS: trusted.overlay.opaque, trusted.overlay.whiteout for directory/file hiding
- Container runtimes: Store container metadata on image layers
- Quota systems: Some implementations store quota data in trusted.*
# OverlayFS opaque directory marker
getfattr -n trusted.overlay.opaque /some/overlayfs/dir
# trusted.overlay.opaque="y"
system namespace
Used for POSIX ACLs (NFSv4 and traditional):
# POSIX ACL stored as system.posix_acl_access
setfacl -m u:alice:rw /shared/file
getfattr -n system.posix_acl_access /shared/file
# Returns binary ACL encoding
Filesystem support
# Check if filesystem supports xattr
mount | grep " / " | grep -o 'user_xattr\|xattr'
# or
tune2fs -l /dev/sda1 | grep "Default mount options"
# ext4: supports all namespaces by default
# tmpfs: supports user.* and trusted.* (no security by default)
# btrfs: full xattr support
# XFS: full xattr support (uses B-tree per inode for many attrs)
# NFS: depends on server; NFSv4.2 has native xattr support
# OverlayFS: delegates to upper/lower filesystem
Kernel implementation
xattr dispatch: struct xattr_handler
Each filesystem registers handlers for each namespace:
/* include/linux/xattr.h */
struct xattr_handler {
const char *name; /* exact name match (e.g., "system.posix_acl_access") */
const char *prefix; /* prefix match (e.g., "user.") */
int flags;
bool (*list)(struct dentry *dentry);
int (*get)(const struct xattr_handler *,
struct dentry *, struct inode *,
const char *name, void *buffer, size_t size);
int (*set)(const struct xattr_handler *,
struct mnt_idmap *, struct dentry *, struct inode *,
const char *name, const void *value, size_t size, int flags);
};
ext4's xattr handler table:
/* fs/ext4/xattr.c */
static const struct xattr_handler * const ext4_xattr_handler_map[] = {
[EXT4_XATTR_INDEX_USER] = &ext4_xattr_user_handler,
[EXT4_XATTR_INDEX_POSIX_ACL_ACCESS] = &posix_acl_access_xattr_handler,
[EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT] = &posix_acl_default_xattr_handler,
[EXT4_XATTR_INDEX_TRUSTED] = &ext4_xattr_trusted_handler,
[EXT4_XATTR_INDEX_SECURITY] = &ext4_xattr_security_handler,
NULL
};
const struct xattr_handler * const *ext4_xattr_handlers = ext4_xattr_handler_map;
VFS xattr syscall path
/* fs/xattr.c */
SYSCALL_DEFINE5(setxattr, const char __user *, pathname,
const char __user *, name,
const void __user *, value, size_t, size, int, flags)
{
struct path path;
user_path_at(AT_FDCWD, pathname, LOOKUP_FOLLOW, &path);
error = setxattr(mnt_idmap(path.mnt), path.dentry, name, kvalue, size, flags);
}
int setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
const char __user *name, const void __user *value,
size_t size, int flags)
{
/* Parse namespace prefix: "user.", "security.", etc. */
handler = xattr_resolve_name(dentry->d_inode, &kname);
/* Check namespace-specific permissions */
error = handler->set(handler, idmap, dentry, inode, kname,
kvalue, size, flags);
}
Storage in ext4
ext4 stores xattrs in two places:
- Inode body (EA inode inline): small attributes stored directly in inode's unused space
- Dedicated xattr block: a 4KB block holding multiple attributes in a key-value format
ext4 inode
├── i_extra_isize (points to end of fixed inode)
└── [xattr inline area]
├── entry: name_index=1("user.") name_len=3 value_len=5 → "foo" = "hello"
└── ...
[if overflow] → separate xattr block (4096 bytes)
├── header: magic, refcount, hash
├── entries: name/value pairs sorted by name
└── values: stored at end of block (grow backwards)
# Inspect xattr storage in ext4
debugfs -R "stat <inode_number>" /dev/sda1
# i_extra_isize: 28
# Extended attributes stored in inode body / block
Security module integration
SELinux and other LSMs use security.* xattrs to label files:
/* security/selinux/hooks.c */
static int selinux_inode_init_security(struct inode *inode,
struct inode *dir,
const struct qstr *qstr,
const char **name,
void **value,
size_t *len)
{
/* Set security.selinux on new file based on parent context */
xattrs[0].name = XATTR_SELINUX_SUFFIX; /* "selinux" */
xattrs[0].value = context;
xattrs[0].value_len = clen;
return 0;
}
/* Called from vfs_create → security_inode_init_security */
# Relabel a file (restore SELinux context from policy):
restorecon /path/to/file
# Check context:
ls -Z /etc/passwd
# system_u:object_r:passwd_file_t:s0 /etc/passwd
# Recursively relabel after restore:
restorecon -R /var/www/html/
fscrypt and xattr
fscrypt stores its per-file encryption policy as an xattr:
/* fs/crypto/policy.c */
/* fscrypt stores the encryption context via the filesystem's set_context op: */
static int fscrypt_set_context(struct inode *inode, void *fs_data)
{
/* Stored using a filesystem-internal xattr index (EXT4_XATTR_INDEX_ENCRYPTION),
* not directly accessible as a named user/trusted xattr */
return inode->i_sb->s_cop->set_context(inode, &ci->ci_policy,
fscrypt_context_size(&ci->ci_policy), fs_data);
}
/* ext4 uses: EXT4_XATTR_INDEX_ENCRYPTION → internal xattr, not exposed as trusted.fscrypt */
# Check if a file is encrypted (use fscrypt tools, not getfattr):
fscryptctl get_policy /encrypted/file
# or:
lsattr /encrypted/file | grep -o 'E'
OverlayFS whiteout and opaque markers
OverlayFS uses trusted.* xattrs to implement its layering:
# Opaque directory: tells overlayfs to stop looking at lower layers
getfattr -n trusted.overlay.opaque /overlay/upper/somedir
# trusted.overlay.opaque="y"
# Origin: tracks which lower inode this was copied from
getfattr -n trusted.overlay.origin /overlay/upper/file
/* fs/overlayfs/xattr.c */
int ovl_check_setxattr(struct ovl_fs *ofs, struct dentry *upperdentry,
enum ovl_xattr ox, const void *value, size_t size, int xerr)
{
/* ox == OVL_XATTR_OPAQUE: trusted.overlay.opaque */
return ovl_do_setxattr(ofs, upperdentry, ox, value, size);
}
Further reading
- VFS Objects — inodes that store xattrs
- OverlayFS — uses trusted.overlay.* xattrs
- dm-crypt and fscrypt — fscrypt encryption policy xattr
- LSM Framework — SELinux/AppArmor use security.* xattrs
fs/xattr.c— VFS xattr dispatchfs/ext4/xattr.c— ext4 xattr storage implementation