[RFC][PATCH] overlayfs: Redirect xattr ops on security.evm to security.evm_overlayfs

Roberto Sassu
2023-12-08T17:25:17Z

From: Roberto Sassu <roberto.sassu@huawei.com>

EVM updates the HMAC in security.evm whenever there is a setxattr or
removexattr operation on one of its protected xattrs (e.g. security.ima).

Unfortunately, since overlayfs redirects those xattrs operations on the
lower filesystem, the EVM HMAC cannot be calculated reliably, since lower
inode attributes on which the HMAC is calculated are different from upper
inode attributes (for example i_generation and s_uuid).

Although maybe it is possible to align such attributes between the lower
and the upper inode, another idea is to map security.evm to another name
(security.evm_overlayfs) during an xattr operation, so that it does not
collide with security.evm set by the lower filesystem.

Whenever overlayfs wants to set security.evm, it is actually setting
security.evm_overlayfs calculated with the upper inode attributes. The
lower filesystem continues to update security.evm.

This seems to make things working again, and even allowing IMA appraisal
to succeed on both the lower and the upper inode.

Example:

# mount -t overlay overlay \
-o lowerdir=data,upperdir=root/data,workdir=root/data_work mnt

# echo "appraise fsname=overlay" > /sys/kernel/security/ima/policy
# echo "appraise fsuid=<lower fs UUID>" > /sys/kernel/security/ima/policy

# cd mnt
# echo test > test-file
evm: security.ima: (34) [0404f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93...]
evm: hmac_misc: (24) [1300000000000000cd9e816c0000000000000000a4810000]
evm: uuid: [28b23254946744c0b6ba34b12e85a26f]
evm: digest: [b186cc901ead302572c6b271db85e4e5cd41c6ce]
evm: security.ima: (34) [0404f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93...]
evm: hmac_misc: (24) [1300000000000000000000000000000000000000a4810000]
evm: uuid: [589286d4df13456ea82a9aca97660302]
evm: digest: [b90586afd1703a6cbf290d9150465f8bdd48fb8a]

The first 4 lines show the HMAC calculation on the lower inode (ext4), the
remaining 4 the HMAC calculation on the upper inode (overlay).

Now, after mapping security.evm to security.evm_overlayfs, this is the
result of the getfattr command on overlayfs:

# getfattr -m - -d -e hex test-file
# file: test-file
security.evm=0x02b90586afd1703a6cbf290d9150465f8bdd48fb8a
security.ima=0x0404f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93...

Instead, this is the result of the getfattr command on the lower fs:

# getfattr -m - -d -e hex ../root/data/test-file
# file: ../root/data/test-file
security.evm=0x02b186cc901ead302572c6b271db85e4e5cd41c6ce
security.evm_overlayfs=0x02b90586afd1703a6cbf290d9150465f8bdd48fb8a
security.ima=0x0404f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93...

Both HMACs are stored on the lower inode.

Trying IMA appraisal, the result is that both the access from overlayfs and
from the lower fs succeed. From overlayfs:

# cat test-file
evm: security.ima: (34) [0404f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93...]
evm: hmac_misc: (24) [1300000000000000000000000000000000000000a4810000]
evm: uuid: [589286d4df13456ea82a9aca97660302]
evm: digest: [b90586afd1703a6cbf290d9150465f8bdd48fb8a]
test

From the lower fs:

# cat ../root/data/test-file
evm: security.ima: (34) [0404f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93...]
evm: hmac_misc: (24) [1300000000000000cd9e816c0000000000000000a4810000]
evm: uuid: [28b23254946744c0b6ba34b12e85a26f]
evm: digest: [b186cc901ead302572c6b271db85e4e5cd41c6ce]
test

security.evm_overlayfs is hidden from listxattr in overlayfs.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
fs/overlayfs/xattrs.c | 9 +++++++++
include/uapi/linux/xattr.h | 4 ++++
2 files changed, 13 insertions(+)

diff --git a/fs/overlayfs/xattrs.c b/fs/overlayfs/xattrs.c
index 383978e4663c..1141d2fa01db 100644
--- a/fs/overlayfs/xattrs.c
+++ b/fs/overlayfs/xattrs.c
@@ -65,6 +65,9 @@ static int ovl_xattr_set(struct dentry *dentry, struct inode *inode, const char
goto out;

old_cred = ovl_override_creds(dentry->d_sb);
+ if (!strcmp(name, XATTR_NAME_EVM))
+ name = XATTR_NAME_EVM_OVERLAYFS;
+
if (value) {
err = ovl_do_setxattr(ofs, realdentry, name, value, size,
flags);
@@ -88,6 +91,9 @@ static int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char
const struct cred *old_cred;
struct path realpath;

+ if (!strcmp(name, XATTR_NAME_EVM))
+ name = XATTR_NAME_EVM_OVERLAYFS;
+
ovl_i_path_real(inode, &realpath);
old_cred = ovl_override_creds(dentry->d_sb);
res = vfs_getxattr(mnt_idmap(realpath.mnt), realpath.dentry, name, value, size);
@@ -101,6 +107,9 @@ static bool ovl_can_list(struct super_block *sb, const char *s)
if (ovl_is_private_xattr(sb, s))
return false;

+ if (!strcmp(s, XATTR_NAME_EVM_OVERLAYFS))
+ return false;
+
/* List all non-trusted xattrs */
if (strncmp(s, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) != 0)
return true;
diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h
index 9463db2dfa9d..93930300f69e 100644
--- a/include/uapi/linux/xattr.h
+++ b/include/uapi/linux/xattr.h
@@ -51,6 +51,10 @@
#define XATTR_EVM_SUFFIX "evm"
#define XATTR_NAME_EVM XATTR_SECURITY_PREFIX XATTR_EVM_SUFFIX

+#define XATTR_EVM_OVERLAYFS_SUFFIX "evm_overlayfs"
+#define XATTR_NAME_EVM_OVERLAYFS \
+ XATTR_SECURITY_PREFIX XATTR_EVM_OVERLAYFS_SUFFIX
+
#define XATTR_IMA_SUFFIX "ima"
#define XATTR_NAME_IMA XATTR_SECURITY_PREFIX XATTR_IMA_SUFFIX

--
2.34.1