Why xattr
A standard stat() gives the inode a fixed set of fields: size, owners, mtime, permissions. Sometimes you want to store something else next to the file: a signature, a hash, a tag, a mime-type, the Origin URL of a download. xattr is the standard mechanism for that.
Classic user xattr:
user.mime-typefor GNOME / KDE iconsuser.charsetfor apache mod_mime on static contentuser.com.dropbox.attributesfor Dropbox markersuser.checksum.md5for custom hashes
System xattr are used heavily by the kernel:
security.selinuxis the SELinux label ("user_u:object_r:..._t")security.capabilityholds file capabilities (cap_net_bind_service)system.posix_acl_accessis where [[posix-acl|POSIX ACL]] are storedsystem.posix_acl_defaultis the default ACL of a directory
Namespaces
| Namespace | Who writes | Who reads | What |
|---|---|---|---|
user.* | anyone with write on the file | the owner and similar | free-form application data |
trusted.* | root only (CAP_SYS_ADMIN) | root | system tools, FUSE drivers |
system.* | the kernel | the kernel | ACL and similar structures |
security.* | LSM (SELinux, AppArmor, IMA) | LSM | security labels and capabilities |
Limits:
- Name ≤ 255 bytes
- Value ≤ 64 KiB on plain ext4 without
-O ea_inode(for larger values, a separate block) - Total per filesystem depends heavily on inode size; xfs with
-i size=1024fits more inline
getfattr, reading
$ getfattr -d file.txt # all user.* (the default)
# file: file.txt
user.mime-type="text/plain"
$ getfattr -d -m '.*' file.txt # all namespaces (root needed for security/trusted)
# file: file.txt
user.mime-type="text/plain"
security.selinux="unconfined_u:object_r:user_home_t:s0"
$ getfattr -n user.mime-type file.txt
# file: file.txt
user.mime-type="text/plain"
$ getfattr -n security.capability /usr/bin/ping
# file: /usr/bin/ping
security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAAA
Options:
| Option | What |
|---|---|
-d | dump, all attributes |
-n NAME | a specific one |
-m PATTERN | regex for the name; default ^user\. |
-h | if a symlink, do not follow |
-R | recursive |
--only-values | value only, no header |
setfattr, writing
setfattr -n user.tag -v "important" file.txt
setfattr -x user.tag file.txt # remove
setfattr -n user.json -v "$(cat data.json)" file
Binary values (hex):
setfattr -n user.bin -v 0xdeadbeef file.txt
Real uses
File capabilities
Linux capabilities split root privileges into about 40 bits.
A security.capability xattr on a binary gives it only what it needs:
# Let ping send ICMP without setuid
setcap cap_net_raw+ep /usr/bin/ping
getcap /usr/bin/ping # a frontend over getfattr
# The same directly
getfattr -n security.capability /usr/bin/ping
This is safer than setuid. See capabilities for more.
SELinux labels
$ getfattr -n security.selinux /etc/passwd
# file: etc/passwd
security.selinux="system_u:object_r:passwd_file_t:s0"
With cp and no --preserve=context, the SELinux label is lost and
the default for the target directory is applied instead. On a production
server this is a common cause of "the file copied, but the program can't read it".
ACL
POSIX ACL are physically stored as xattr:
$ getfattr -m '^system' file.txt
system.posix_acl_access=0sAgAAAAEABwD/...
Do not edit them directly. Use [[posix-acl|setfacl]].
IMA / EVM
Integrity Measurement Architecture stores signatures in
security.ima and security.evm. It is used on
hardened systems for measured boot and runtime integrity.
xattr and cp / tar / rsync
By default they are not carried over. This is the most common bug:
cp -a(archive) carries xattrcpwithout flags does not carry themtarneeds--xattrs(and--xattrs-include='*.*'to capture every namespace, otherwise onlyuser.*)rsyncneeds-X(--xattrs); also add-Afor ACL
Copy the SELinux context with cp:
cp --preserve=context file new
# or for a whole tree
cp -a /src /dst # -a implies -p with all attributes
Filesystem support
| Filesystem | xattr |
|---|---|
| ext4 | yes; for large values -O ea_inode |
| xfs | yes; fit in a large inode |
| btrfs | yes |
| tmpfs | yes (kernel >= 6.0 for the security namespace) |
| nfs | NFSv4, partial |
| fat/vfat | no |
| iso9660 | no (older RR extension, partial) |
With mount -o nouser_xattr the user namespace is off, the rest still work.
Size and performance
- In the space reserved inside the inode, it is free (part of the RAM/IO for the inode)
- Large values go to a separate block = +1 IO on read
- With very many xattr on one file you get a noticeable
ls -lslowdown, because the kernel may check each one
When something goes wrong
Operation not supportedmeans the mount has nouser_xattr, or the filesystem can't do it (vfat). On ext this has been the default for a long time, but thin FUSE mounts may not support it.Permission deniedonsetfattr -n trusted.*orsecurity.*means you need root and the matching capability (CAP_SYS_ADMINfor trusted).- xattr vanished after cp/tar/rsync means the right flag was missing.
- SELinux denied after a copy means the labels were not copied.
restorecon -Rvrestores the defaults for the target directory. No space leftwith free disk means ext4 may have hit the limit on ea blocks.tune2fs -l | grep -i ea. You may need-O ea_inode(which requires a remkfs).Argument list too longmeans the name or value exceeded the limits.
Alternatives
- Resource forks (macOS) are a similar idea, not carried into Linux
- A side-car file (
file.metadata.json) is simple and copies through cp/scp without flags, but it can drift out of sync - A database if there is a lot of metadata and it is queried often
- xfs project IDs for subtree quotas, not for arbitrary tags