2024-02-21 21:30:38

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 00/25] fs: use type-safe uid representation for filesystem capabilities

This series converts filesystem capabilities from passing around raw
xattr data to using a kernel-internal representation with type safe
uids, similar to the conversion done previously for posix ACLs.
Currently fscaps representations in the kernel have two different
instances of unclear or confused types:

- fscaps are generally passed around in the raw xattr form, with the
rootid sometimes containing the user uid value and at other times
containing the filesystem value.
- The existing kernel-internal representation of fscaps,
cpu_vfs_cap_data, uses the kuid_t type, but the value stored is
actually a vfsuid.

This series eliminates this confusion by converting the xattr data to
the kernel representation near the userspace and filesystem boundaries,
using the kernel representation within the vfs and commoncap code. The
internal representation is renamed to vfs_caps to reflect this broader
use, and the rootid is changed to a vfsuid_t to correctly identify the
type of uid which it contains.

New vfs interfaces are added to allow for getting and setting fscaps
using the kernel representation. This requires the addition of new inode
operations to allow overlayfs to handle fscaps properly; all other
filesystems fall back to a generic implementation. The top-level vfs
xattr interfaces will now reject fscaps xattrs, though the lower-level
interfaces continue to accept them for reading and writing the raw xattr
data.

Based on previous feedback, new security hooks are added for fscaps
operations. These are really only needed for EVM, and the selinux and
smack implementations just peform the same operations that the
equivalent xattr hooks would have done. Note too that this has not yet
been updated based on the changes to make EVM into an LSM.

The remainder of the changes are preparatory work, addition of helpers
for converting between the xattr and kernel fscaps representation, and
various updates to use the kernel representation and new interfaces.

I have tested this code with xfstests, ltp, libcap2, and libcap-ng with
no regressions found.

To: Christian Brauner <[email protected]>
To: Serge Hallyn <[email protected]>
To: Paul Moore <[email protected]>
To: Eric Paris <[email protected]>
To: James Morris <[email protected]>
To: Alexander Viro <[email protected]>
To: Miklos Szeredi <[email protected]>
To: Amir Goldstein <[email protected]>
Cc: <[email protected]>
Cc: <[email protected]>
Cc: <[email protected]>
Cc: <[email protected]>
Cc: <[email protected]>
Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>

--- Changes in v2:
- Documented new inode operations in
Documentation/filesystems/{vfs,locking}.rst.
- Changed types for sizes in function arguments and return values to
size_t/ssize_t.
- Renamed flags arguments to setxattr_flags for clarity.
- Removed memory allocation when reading fscaps xattrs.
- Updated get_vfs_caps_from_disk() to use vfs_get_fscaps() and updated
comments to explain how these functions are different.
- Updates/fixes to kernel-doc comments.
- Remove unnecessary type cast.
- Rename __vfs_{get,remove}_fscaps() to vfs_{get,remove}_fscaps_nosec().
- Add missing fsnotify_xattr() call in vfs_set_fscaps().
- Add fscaps security hooks along with appropriate handlers in selinux,
smack, and evm.
- Remove remove_fscaps inode op in favor of passing NULL to set_fscaps.
- Added static asserts for compatibility of vfs_cap_data and
vfs_ns_cap_data.
- ovl: remove unnecessary check around ovl_copy_up(), and add check
before copyint up fscaps for removal that the fscaps actually exist on
the lower inode.
- ovl: install fscaps handlers for all inode types
- Add is_fscaps_xattr() helper and use it in place of open-coded strcmps
- Link to v1: https://lore.kernel.org/r/[email protected]

---
Seth Forshee (DigitalOcean) (25):
mnt_idmapping: split out core vfs[ug]id_t definitions into vfsid.h
mnt_idmapping: include cred.h
capability: add static asserts for comapatibility of vfs_cap_data and vfs_ns_cap_data
capability: rename cpu_vfs_cap_data to vfs_caps
capability: use vfsuid_t for vfs_caps rootids
capability: provide helpers for converting between xattrs and vfs_caps
capability: provide a helper for converting vfs_caps to xattr for userspace
xattr: add is_fscaps_xattr() helper
commoncap: use is_fscaps_xattr()
xattr: use is_fscaps_xattr()
security: add hooks for set/get/remove of fscaps
selinux: add hooks for fscaps operations
smack: add hooks for fscaps operations
evm: add support for fscaps security hooks
security: call evm fscaps hooks from generic security hooks
fs: add inode operations to get/set/remove fscaps
fs: add vfs_get_fscaps()
fs: add vfs_set_fscaps()
fs: add vfs_remove_fscaps()
ovl: add fscaps handlers
ovl: use vfs_{get,set}_fscaps() for copy-up
fs: use vfs interfaces for capabilities xattrs
commoncap: remove cap_inode_getsecurity()
commoncap: use vfs fscaps interfaces
vfs: return -EOPNOTSUPP for fscaps from vfs_*xattr()

Documentation/filesystems/locking.rst | 4 +
Documentation/filesystems/vfs.rst | 17 ++
MAINTAINERS | 1 +
fs/overlayfs/copy_up.c | 72 ++---
fs/overlayfs/dir.c | 2 +
fs/overlayfs/inode.c | 72 +++++
fs/overlayfs/overlayfs.h | 5 +
fs/xattr.c | 280 +++++++++++++++++-
include/linux/capability.h | 23 +-
include/linux/evm.h | 39 +++
include/linux/fs.h | 12 +
include/linux/lsm_hook_defs.h | 7 +
include/linux/mnt_idmapping.h | 67 +----
include/linux/security.h | 38 ++-
include/linux/vfsid.h | 74 +++++
include/linux/xattr.h | 5 +
include/uapi/linux/capability.h | 13 +
kernel/auditsc.c | 9 +-
security/commoncap.c | 529 ++++++++++++++++++----------------
security/integrity/evm/evm_main.c | 60 ++++
security/security.c | 80 +++++
security/selinux/hooks.c | 26 ++
security/smack/smack_lsm.c | 71 +++++
23 files changed, 1144 insertions(+), 362 deletions(-)
---
base-commit: 841c35169323cd833294798e58b9bf63fa4fa1de
change-id: 20230512-idmap-fscap-refactor-63b61fa0a36f

Best regards,
--
Seth Forshee (DigitalOcean) <[email protected]>



2024-02-21 21:30:41

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 07/25] capability: provide a helper for converting vfs_caps to xattr for userspace

cap_inode_getsecurity() implements a handful of policies for capability
xattrs read by userspace:

- It returns EINVAL if the on-disk capability is in v1 format.

- It masks off all bits in magic_etc except for the version and
VFS_CAP_FLAGS_EFFECTIVE.

- v3 capabilities are converted to v2 format if the rootid returned to
userspace would be 0 or if the rootid corresponds to root in an
ancestor user namespace.

- It returns EOVERFLOW for a v3 capability whose rootid does not map to
a valid id in current_user_ns() or to root in an ancestor namespace.

These policies must be maintained when converting vfs_caps to an xattr
for userspace. Provide a vfs_caps_to_user_xattr() helper which will
enforce these policies.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
include/linux/capability.h | 4 +++
security/commoncap.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 82 insertions(+)

diff --git a/include/linux/capability.h b/include/linux/capability.h
index a0893ac4664b..eb06d7c6224b 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -218,6 +218,10 @@ ssize_t vfs_caps_to_xattr(struct mnt_idmap *idmap,
struct user_namespace *dest_userns,
const struct vfs_caps *vfs_caps,
void *data, size_t size);
+ssize_t vfs_caps_to_user_xattr(struct mnt_idmap *idmap,
+ struct user_namespace *dest_userns,
+ const struct vfs_caps *vfs_caps,
+ void *data, size_t size);

/* audit system wants to get cap info from files as well */
int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
diff --git a/security/commoncap.c b/security/commoncap.c
index 7531c9634997..289530e58c37 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -791,6 +791,84 @@ ssize_t vfs_caps_to_xattr(struct mnt_idmap *idmap,
return ret;
}

+/**
+ * vfs_caps_to_user_xattr - convert vfs_caps to caps xattr for userspace
+ *
+ * @idmap: idmap of the mount the inode was found from
+ * @dest_userns: user namespace for ids in xattr data
+ * @vfs_caps: source vfs_caps data
+ * @data: destination buffer for rax xattr caps data
+ * @size: size of the @data buffer
+ *
+ * Converts a kernel-internal capability into the raw security.capability
+ * xattr format. Implements the following policies required for fscaps
+ * returned to userspace:
+ *
+ * - Returns -EINVAL if the on-disk capability is in v1 format.
+ * - Masks off all bits in magic_etc except for the version and
+ * VFS_CAP_FLAGS_EFFECTIVE.
+ * - Converts v3 capabilities to v2 format if the rootid returned to
+ * userspace would be 0 or if the rootid corresponds to root in an
+ * ancestor user namespace.
+ * - Returns EOVERFLOW for a v3 capability whose rootid does not map to a
+ * valid id in current_user_ns() or to root in an ancestor namespace.
+ *
+ * If the xattr is being read or written through an idmapped mount the
+ * idmap of the vfsmount must be passed through @idmap. This function
+ * will then take care to map the rootid according to @idmap.
+ *
+ * Return: On success, return the size of the xattr data. On error,
+ * return < 0.
+ */
+ssize_t vfs_caps_to_user_xattr(struct mnt_idmap *idmap,
+ struct user_namespace *dest_userns,
+ const struct vfs_caps *vfs_caps,
+ void *data, size_t size)
+{
+ struct vfs_ns_cap_data *ns_caps = data;
+ bool is_v3;
+ u32 magic;
+
+ /* Preserve previous behavior of returning EINVAL for v1 caps */
+ if ((vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) == VFS_CAP_REVISION_1)
+ return -EINVAL;
+
+ size = __vfs_caps_to_xattr(idmap, dest_userns, vfs_caps, data, size);
+ if (size < 0)
+ return size;
+
+ magic = vfs_caps->magic_etc &
+ (VFS_CAP_REVISION_MASK | VFS_CAP_FLAGS_EFFECTIVE);
+ ns_caps->magic_etc = cpu_to_le32(magic);
+
+ /*
+ * If this is a v3 capability with a valid, non-zero rootid, return
+ * the v3 capability to userspace. A v3 capability with a rootid of
+ * 0 will be converted to a v2 capability below for compatibility
+ * with old userspace.
+ */
+ is_v3 = (vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) == VFS_CAP_REVISION_3;
+ if (is_v3) {
+ uid_t rootid = le32_to_cpu(ns_caps->rootid);
+ if (rootid != (uid_t)-1 && rootid != (uid_t)0)
+ return size;
+ }
+
+ if (!rootid_owns_currentns(vfs_caps->rootid))
+ return -EOVERFLOW;
+
+ /* This comes from a parent namespace. Return as a v2 capability. */
+ if (is_v3) {
+ magic = VFS_CAP_REVISION_2 |
+ (vfs_caps->magic_etc & VFS_CAP_FLAGS_EFFECTIVE);
+ ns_caps->magic_etc = cpu_to_le32(magic);
+ ns_caps->rootid = cpu_to_le32(0);
+ size = XATTR_CAPS_SZ_2;
+ }
+
+ return size;
+}
+
/**
* get_vfs_caps_from_disk - retrieve vfs caps from disk
*

--
2.43.0


2024-02-21 21:30:42

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 10/25] xattr: use is_fscaps_xattr()

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
fs/xattr.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/xattr.c b/fs/xattr.c
index 09d927603433..06290e4ebc03 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -310,7 +310,7 @@ vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
const void *orig_value = value;
int error;

- if (size && strcmp(name, XATTR_NAME_CAPS) == 0) {
+ if (size && is_fscaps_xattr(name)) {
error = cap_convert_nscap(idmap, dentry, &value, size);
if (error < 0)
return error;

--
2.43.0


2024-02-21 21:30:52

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 08/25] xattr: add is_fscaps_xattr() helper

Add a helper to determine if an xattr time is XATTR_NAME_CAPS instead of
open-coding a string comparision.

Suggested-by: Amir Goldstein <[email protected]>
Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
include/linux/xattr.h | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/include/linux/xattr.h b/include/linux/xattr.h
index d20051865800..cbacfb4d74fa 100644
--- a/include/linux/xattr.h
+++ b/include/linux/xattr.h
@@ -28,6 +28,11 @@ static inline bool is_posix_acl_xattr(const char *name)
(strcmp(name, XATTR_NAME_POSIX_ACL_DEFAULT) == 0);
}

+static inline bool is_fscaps_xattr(const char *name)
+{
+ return strcmp(name, XATTR_NAME_CAPS) == 0;
+}
+
/*
* struct xattr_handler: When @name is set, match attributes with exactly that
* name. When @prefix is set instead, match attributes with that prefix and

--
2.43.0


2024-02-21 21:31:01

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 06/25] capability: provide helpers for converting between xattrs and vfs_caps

To pass around vfs_caps instead of raw xattr data we will need to
convert between the two representations near userspace and disk
boundaries. We already convert xattrs from disks to vfs_caps, so move
that code into a helper, and change get_vfs_caps_from_disk() to use the
helper.

When converting vfs_caps to xattrs we have different considerations
depending on the destination of the xattr data. For xattrs which will be
written to disk we need to reject the xattr if the rootid does not map
into the filesystem's user namespace, whereas xattrs read by userspace
may need to undergo a conversion from v3 to v2 format when the rootid
does not map. So this helper is split into an internal and an external
interface. The internal interface does not return an error if the rootid
has no mapping in the target user namespace and will be used for
conversions targeting userspace. The external interface returns
EOVERFLOW if the rootid has no mapping and will be used for all other
conversions.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
include/linux/capability.h | 10 ++
security/commoncap.c | 228 +++++++++++++++++++++++++++++++++++----------
2 files changed, 187 insertions(+), 51 deletions(-)

diff --git a/include/linux/capability.h b/include/linux/capability.h
index eb46d346bbbc..a0893ac4664b 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -209,6 +209,16 @@ static inline bool checkpoint_restore_ns_capable(struct user_namespace *ns)
ns_capable(ns, CAP_SYS_ADMIN);
}

+/* helpers to convert between xattr and in-kernel representations */
+int vfs_caps_from_xattr(struct mnt_idmap *idmap,
+ struct user_namespace *src_userns,
+ struct vfs_caps *vfs_caps,
+ const void *data, size_t size);
+ssize_t vfs_caps_to_xattr(struct mnt_idmap *idmap,
+ struct user_namespace *dest_userns,
+ const struct vfs_caps *vfs_caps,
+ void *data, size_t size);
+
/* audit system wants to get cap info from files as well */
int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
const struct dentry *dentry,
diff --git a/security/commoncap.c b/security/commoncap.c
index a0b5c9740759..7531c9634997 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -619,54 +619,41 @@ static inline int bprm_caps_from_vfs_caps(struct vfs_caps *caps,
}

/**
- * get_vfs_caps_from_disk - retrieve vfs caps from disk
+ * vfs_caps_from_xattr - convert raw caps xattr data to vfs_caps
*
- * @idmap: idmap of the mount the inode was found from
- * @dentry: dentry from which @inode is retrieved
- * @cpu_caps: vfs capabilities
+ * @idmap: idmap of the mount the inode was found from
+ * @src_userns: user namespace for ids in xattr data
+ * @vfs_caps: destination buffer for vfs_caps data
+ * @data: rax xattr caps data
+ * @size: size of xattr data
*
- * Extract the on-exec-apply capability sets for an executable file.
+ * Converts a raw security.capability xattr into the kernel-internal
+ * capabilities format.
*
- * If the inode has been found through an idmapped mount the idmap of
- * the vfsmount must be passed through @idmap. This function will then
- * take care to map the inode according to @idmap before checking
- * permissions. On non-idmapped mounts or if permission checking is to be
- * performed on the raw inode simply pass @nop_mnt_idmap.
+ * If the xattr is being read or written through an idmapped mount the
+ * idmap of the vfsmount must be passed through @idmap. This function
+ * will then take care to map the rootid according to @idmap.
+ *
+ * Return: On success, return 0; on error, return < 0.
*/
-int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
- const struct dentry *dentry,
- struct vfs_caps *cpu_caps)
+int vfs_caps_from_xattr(struct mnt_idmap *idmap,
+ struct user_namespace *src_userns,
+ struct vfs_caps *vfs_caps,
+ const void *data, size_t size)
{
- struct inode *inode = d_backing_inode(dentry);
__u32 magic_etc;
- int size;
- struct vfs_ns_cap_data data, *nscaps = &data;
- struct vfs_cap_data *caps = (struct vfs_cap_data *) &data;
+ const struct vfs_ns_cap_data *ns_caps = data;
+ struct vfs_cap_data *caps = (struct vfs_cap_data *)ns_caps;
kuid_t rootkuid;
- vfsuid_t rootvfsuid;
- struct user_namespace *fs_ns;
-
- memset(cpu_caps, 0, sizeof(struct vfs_caps));
-
- if (!inode)
- return -ENODATA;

- fs_ns = inode->i_sb->s_user_ns;
- size = __vfs_getxattr((struct dentry *)dentry, inode,
- XATTR_NAME_CAPS, &data, XATTR_CAPS_SZ);
- if (size == -ENODATA || size == -EOPNOTSUPP)
- /* no data, that's ok */
- return -ENODATA;
-
- if (size < 0)
- return size;
+ memset(vfs_caps, 0, sizeof(*vfs_caps));

if (size < sizeof(magic_etc))
return -EINVAL;

- cpu_caps->magic_etc = magic_etc = le32_to_cpu(caps->magic_etc);
+ vfs_caps->magic_etc = magic_etc = le32_to_cpu(caps->magic_etc);

- rootkuid = make_kuid(fs_ns, 0);
+ rootkuid = make_kuid(src_userns, 0);
switch (magic_etc & VFS_CAP_REVISION_MASK) {
case VFS_CAP_REVISION_1:
if (size != XATTR_CAPS_SZ_1)
@@ -679,39 +666,178 @@ int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
case VFS_CAP_REVISION_3:
if (size != XATTR_CAPS_SZ_3)
return -EINVAL;
- rootkuid = make_kuid(fs_ns, le32_to_cpu(nscaps->rootid));
+ rootkuid = make_kuid(src_userns, le32_to_cpu(ns_caps->rootid));
break;

default:
return -EINVAL;
}

- rootvfsuid = make_vfsuid(idmap, fs_ns, rootkuid);
- if (!vfsuid_valid(rootvfsuid))
- return -ENODATA;
+ vfs_caps->rootid = make_vfsuid(idmap, src_userns, rootkuid);
+ if (!vfsuid_valid(vfs_caps->rootid))
+ return -EOVERFLOW;

- /* Limit the caps to the mounter of the filesystem
- * or the more limited uid specified in the xattr.
+ vfs_caps->permitted.val = le32_to_cpu(caps->data[0].permitted);
+ vfs_caps->inheritable.val = le32_to_cpu(caps->data[0].inheritable);
+
+ /*
+ * Rev1 had just a single 32-bit word, later expanded
+ * to a second one for the high bits
*/
- if (!rootid_owns_currentns(rootvfsuid))
- return -ENODATA;
+ if ((magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
+ vfs_caps->permitted.val += (u64)le32_to_cpu(caps->data[1].permitted) << 32;
+ vfs_caps->inheritable.val += (u64)le32_to_cpu(caps->data[1].inheritable) << 32;
+ }
+
+ vfs_caps->permitted.val &= CAP_VALID_MASK;
+ vfs_caps->inheritable.val &= CAP_VALID_MASK;
+
+ return 0;
+}
+
+/*
+ * Inner implementation of vfs_caps_to_xattr() which does not return an
+ * error if the rootid does not map into @dest_userns.
+ */
+static ssize_t __vfs_caps_to_xattr(struct mnt_idmap *idmap,
+ struct user_namespace *dest_userns,
+ const struct vfs_caps *vfs_caps,
+ void *data, size_t size)
+{
+ struct vfs_ns_cap_data *ns_caps = data;
+ struct vfs_cap_data *caps = (struct vfs_cap_data *)ns_caps;
+ kuid_t rootkuid;
+ uid_t rootid;
+
+ memset(ns_caps, 0, size);
+
+ rootid = 0;
+ switch (vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) {
+ case VFS_CAP_REVISION_1:
+ if (size < XATTR_CAPS_SZ_1)
+ return -EINVAL;
+ size = XATTR_CAPS_SZ_1;
+ break;
+ case VFS_CAP_REVISION_2:
+ if (size < XATTR_CAPS_SZ_2)
+ return -EINVAL;
+ size = XATTR_CAPS_SZ_2;
+ break;
+ case VFS_CAP_REVISION_3:
+ if (size < XATTR_CAPS_SZ_3)
+ return -EINVAL;
+ size = XATTR_CAPS_SZ_3;
+ rootkuid = from_vfsuid(idmap, dest_userns, vfs_caps->rootid);
+ rootid = from_kuid(dest_userns, rootkuid);
+ ns_caps->rootid = cpu_to_le32(rootid);
+ break;

- cpu_caps->permitted.val = le32_to_cpu(caps->data[0].permitted);
- cpu_caps->inheritable.val = le32_to_cpu(caps->data[0].inheritable);
+ default:
+ return -EINVAL;
+ }
+
+ caps->magic_etc = cpu_to_le32(vfs_caps->magic_etc);
+
+ caps->data[0].permitted = cpu_to_le32(lower_32_bits(vfs_caps->permitted.val));
+ caps->data[0].inheritable = cpu_to_le32(lower_32_bits(vfs_caps->inheritable.val));

/*
* Rev1 had just a single 32-bit word, later expanded
* to a second one for the high bits
*/
- if ((magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
- cpu_caps->permitted.val += (u64)le32_to_cpu(caps->data[1].permitted) << 32;
- cpu_caps->inheritable.val += (u64)le32_to_cpu(caps->data[1].inheritable) << 32;
+ if ((vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
+ caps->data[1].permitted =
+ cpu_to_le32(upper_32_bits(vfs_caps->permitted.val));
+ caps->data[1].inheritable =
+ cpu_to_le32(upper_32_bits(vfs_caps->inheritable.val));
}

- cpu_caps->permitted.val &= CAP_VALID_MASK;
- cpu_caps->inheritable.val &= CAP_VALID_MASK;
+ return size;
+}
+
+
+/**
+ * vfs_caps_to_xattr - convert vfs_caps to raw caps xattr data
+ *
+ * @idmap: idmap of the mount the inode was found from
+ * @dest_userns: user namespace for ids in xattr data
+ * @vfs_caps: source vfs_caps data
+ * @data: destination buffer for rax xattr caps data
+ * @size: size of the @data buffer
+ *
+ * Converts a kernel-internal capability into the raw security.capability
+ * xattr format.
+ *
+ * If the xattr is being read or written through an idmapped mount the
+ * idmap of the vfsmount must be passed through @idmap. This function
+ * will then take care to map the rootid according to @idmap.
+ *
+ * Return: On success, return the size of the xattr data. On error,
+ * return < 0.
+ */
+ssize_t vfs_caps_to_xattr(struct mnt_idmap *idmap,
+ struct user_namespace *dest_userns,
+ const struct vfs_caps *vfs_caps,
+ void *data, size_t size)
+{
+ struct vfs_ns_cap_data *caps = data;
+ int ret;
+
+ ret = __vfs_caps_to_xattr(idmap, dest_userns, vfs_caps, data, size);
+ if (ret > 0 &&
+ (vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) == VFS_CAP_REVISION_3 &&
+ le32_to_cpu(caps->rootid) == (uid_t)-1)
+ return -EOVERFLOW;
+ return ret;
+}
+
+/**
+ * get_vfs_caps_from_disk - retrieve vfs caps from disk
+ *
+ * @idmap: idmap of the mount the inode was found from
+ * @dentry: dentry from which @inode is retrieved
+ * @cpu_caps: vfs capabilities
+ *
+ * Extract the on-exec-apply capability sets for an executable file.
+ *
+ * If the inode has been found through an idmapped mount the idmap of
+ * the vfsmount must be passed through @idmap. This function will then
+ * take care to map the inode according to @idmap before checking
+ * permissions. On non-idmapped mounts or if permission checking is to be
+ * performed on the raw inode simply pass @nop_mnt_idmap.
+ */
+int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
+ const struct dentry *dentry,
+ struct vfs_caps *cpu_caps)
+{
+ struct inode *inode = d_backing_inode(dentry);
+ int size, ret;
+ struct vfs_ns_cap_data data, *nscaps = &data;
+
+ if (!inode)
+ return -ENODATA;

- cpu_caps->rootid = rootvfsuid;
+ size = __vfs_getxattr((struct dentry *)dentry, inode,
+ XATTR_NAME_CAPS, &data, XATTR_CAPS_SZ);
+ if (size == -ENODATA || size == -EOPNOTSUPP)
+ /* no data, that's ok */
+ return -ENODATA;
+
+ if (size < 0)
+ return size;
+
+ ret = vfs_caps_from_xattr(idmap, inode->i_sb->s_user_ns,
+ cpu_caps, nscaps, size);
+ if (ret == -EOVERFLOW)
+ return -ENODATA;
+ if (ret)
+ return ret;
+
+ /* Limit the caps to the mounter of the filesystem
+ * or the more limited uid specified in the xattr.
+ */
+ if (!rootid_owns_currentns(cpu_caps->rootid))
+ return -ENODATA;

return 0;
}

--
2.43.0


2024-02-21 21:31:11

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 11/25] security: add hooks for set/get/remove of fscaps

In preparation for moving fscaps out of the xattr code paths, add new
security hooks. These hooks are largely needed because common kernel
code will pass around struct vfs_caps pointers, which EVM will need to
convert to raw xattr data for verification and updates of its hashes.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
include/linux/lsm_hook_defs.h | 7 +++++
include/linux/security.h | 33 +++++++++++++++++++++
security/security.c | 69 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 109 insertions(+)

diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 76458b6d53da..7b3c23f9e4a5 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -152,6 +152,13 @@ LSM_HOOK(int, 0, inode_get_acl, struct mnt_idmap *idmap,
struct dentry *dentry, const char *acl_name)
LSM_HOOK(int, 0, inode_remove_acl, struct mnt_idmap *idmap,
struct dentry *dentry, const char *acl_name)
+LSM_HOOK(int, 0, inode_set_fscaps, struct mnt_idmap *idmap,
+ struct dentry *dentry, const struct vfs_caps *caps, int flags);
+LSM_HOOK(void, LSM_RET_VOID, inode_post_set_fscaps, struct mnt_idmap *idmap,
+ struct dentry *dentry, const struct vfs_caps *caps, int flags);
+LSM_HOOK(int, 0, inode_get_fscaps, struct mnt_idmap *idmap, struct dentry *dentry);
+LSM_HOOK(int, 0, inode_remove_fscaps, struct mnt_idmap *idmap,
+ struct dentry *dentry);
LSM_HOOK(int, 0, inode_need_killpriv, struct dentry *dentry)
LSM_HOOK(int, 0, inode_killpriv, struct mnt_idmap *idmap,
struct dentry *dentry)
diff --git a/include/linux/security.h b/include/linux/security.h
index d0eb20f90b26..40be548e5e12 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -378,6 +378,13 @@ int security_inode_getxattr(struct dentry *dentry, const char *name);
int security_inode_listxattr(struct dentry *dentry);
int security_inode_removexattr(struct mnt_idmap *idmap,
struct dentry *dentry, const char *name);
+int security_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ const struct vfs_caps *caps, int flags);
+void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps, int flags);
+int security_inode_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry);
+int security_inode_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry);
int security_inode_need_killpriv(struct dentry *dentry);
int security_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry);
int security_inode_getsecurity(struct mnt_idmap *idmap,
@@ -935,6 +942,32 @@ static inline int security_inode_removexattr(struct mnt_idmap *idmap,
return cap_inode_removexattr(idmap, dentry, name);
}

+static inline int security_inode_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps,
+ int flags)
+{
+ return 0;
+}
+static void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps,
+ int flags)
+{
+}
+
+static int security_inode_get_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry)
+{
+ return 0;
+}
+
+static int security_inode_remove_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry)
+{
+ return 0;
+}
+
static inline int security_inode_need_killpriv(struct dentry *dentry)
{
return cap_inode_need_killpriv(dentry);
diff --git a/security/security.c b/security/security.c
index 3aaad75c9ce8..0d210da9862c 100644
--- a/security/security.c
+++ b/security/security.c
@@ -2351,6 +2351,75 @@ int security_inode_remove_acl(struct mnt_idmap *idmap,
return evm_inode_remove_acl(idmap, dentry, acl_name);
}

+/**
+ * security_inode_set_fscaps() - Check if setting fscaps is allowed
+ * @idmap: idmap of the mount
+ * @dentry: file
+ * @caps: fscaps to be written
+ * @flags: flags for setxattr
+ *
+ * Check permission before setting the file capabilities given in @vfs_caps.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ const struct vfs_caps *caps, int flags)
+{
+ if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+ return 0;
+ return call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
+}
+
+/**
+ * security_inode_post_set_fscaps() - Update the inode after setting fscaps
+ * @idmap: idmap of the mount
+ * @dentry: file
+ * @caps: fscaps to be written
+ * @flags: flags for setxattr
+ *
+ * Update inode security field after successfully setting fscaps.
+ *
+ */
+void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps, int flags)
+{
+ if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+ return;
+ call_void_hook(inode_post_set_fscaps, idmap, dentry, caps, flags);
+}
+
+/**
+ * security_inode_get_fscaps() - Check if reading fscaps is allowed
+ * @dentry: file
+ *
+ * Check permission before getting fscaps.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_inode_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
+{
+ if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+ return 0;
+ return call_int_hook(inode_get_fscaps, 0, idmap, dentry);
+}
+
+/**
+ * security_inode_remove_fscaps() - Check if removing fscaps is allowed
+ * @idmap: idmap of the mount
+ * @dentry: file
+ *
+ * Check permission before removing fscaps.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_inode_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
+{
+ if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+ return 0;
+ return call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
+}
+
/**
* security_inode_post_setxattr() - Update the inode after a setxattr operation
* @dentry: file

--
2.43.0


2024-02-21 21:31:22

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 14/25] evm: add support for fscaps security hooks

Support the new fscaps security hooks by converting the vfs_caps to raw
xattr data and then handling them the same as other xattrs.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
include/linux/evm.h | 39 +++++++++++++++++++++++++
security/integrity/evm/evm_main.c | 60 +++++++++++++++++++++++++++++++++++++++
2 files changed, 99 insertions(+)

diff --git a/include/linux/evm.h b/include/linux/evm.h
index 36ec884320d9..aeb9ff52ad22 100644
--- a/include/linux/evm.h
+++ b/include/linux/evm.h
@@ -57,6 +57,20 @@ static inline void evm_inode_post_set_acl(struct dentry *dentry,
{
return evm_inode_post_setxattr(dentry, acl_name, NULL, 0);
}
+extern int evm_inode_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps, int flags);
+static inline int evm_inode_remove_fscaps(struct dentry *dentry)
+{
+ return evm_inode_set_fscaps(&nop_mnt_idmap, dentry, NULL, XATTR_REPLACE);
+}
+extern void evm_inode_post_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps, int flags);
+static inline void evm_inode_post_remove_fscaps(struct dentry *dentry)
+{
+ return evm_inode_post_set_fscaps(&nop_mnt_idmap, dentry, NULL, 0);
+}

int evm_inode_init_security(struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs,
@@ -164,6 +178,31 @@ static inline void evm_inode_post_set_acl(struct dentry *dentry,
return;
}

+static inline int evm_inode_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps, int flags)
+{
+ return 0;
+}
+
+static inline int evm_inode_remove_fscaps(struct dentry *dentry)
+{
+ return 0;
+}
+
+static inline void evm_inode_post_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps,
+ int flags)
+{
+ return;
+}
+
+static inline void evm_inode_post_remove_fscaps(struct dentry *dentry)
+{
+ return;
+}
+
static inline int evm_inode_init_security(struct inode *inode, struct inode *dir,
const struct qstr *qstr,
struct xattr *xattrs,
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index cc7956d7878b..ecf4634a921a 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -805,6 +805,66 @@ void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name)
evm_update_evmxattr(dentry, xattr_name, NULL, 0);
}

+int evm_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ const struct vfs_caps *caps, int flags)
+{
+ struct inode *inode = d_inode(dentry);
+ struct vfs_ns_cap_data nscaps;
+ const void *xattr_data = NULL;
+ int size = 0;
+
+ /* Policy permits modification of the protected xattrs even though
+ * there's no HMAC key loaded
+ */
+ if (evm_initialized & EVM_ALLOW_METADATA_WRITES)
+ return 0;
+
+ if (caps) {
+ size = vfs_caps_to_xattr(idmap, i_user_ns(inode), caps, &nscaps,
+ sizeof(nscaps));
+ if (size < 0)
+ return size;
+ xattr_data = &nscaps;
+ }
+
+ return evm_protect_xattr(idmap, dentry, XATTR_NAME_CAPS, xattr_data, size);
+}
+
+void evm_inode_post_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ const struct vfs_caps *caps, int flags)
+{
+ struct inode *inode = d_inode(dentry);
+ struct vfs_ns_cap_data nscaps;
+ const void *xattr_data = NULL;
+ int size = 0;
+
+ if (!evm_revalidate_status(XATTR_NAME_CAPS))
+ return;
+
+ evm_reset_status(dentry->d_inode);
+
+ if (!(evm_initialized & EVM_INIT_HMAC))
+ return;
+
+ if (is_unsupported_fs(dentry))
+ return;
+
+ if (caps) {
+ size = vfs_caps_to_xattr(idmap, i_user_ns(inode), caps, &nscaps,
+ sizeof(nscaps));
+ /*
+ * The fscaps here should have been converted to an xattr by
+ * evm_inode_set_fscaps() already, so a failure to convert
+ * here is a bug.
+ */
+ if (WARN_ON_ONCE(size < 0))
+ return;
+ xattr_data = &nscaps;
+ }
+
+ evm_update_evmxattr(dentry, XATTR_NAME_CAPS, xattr_data, size);
+}
+
static int evm_attr_change(struct mnt_idmap *idmap,
struct dentry *dentry, struct iattr *attr)
{

--
2.43.0


2024-02-21 21:33:04

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 18/25] fs: add vfs_set_fscaps()

Provide a type-safe interface for setting filesystem capabilities and a
generic implementation suitable for most filesystems.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
fs/xattr.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/fs.h | 2 ++
2 files changed, 81 insertions(+)

diff --git a/fs/xattr.c b/fs/xattr.c
index 10d1b1f78fc2..96de43928a51 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -245,6 +245,85 @@ int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
}
EXPORT_SYMBOL(vfs_get_fscaps);

+static int generic_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ const struct vfs_caps *caps, int setxattr_flags)
+{
+ struct inode *inode = d_inode(dentry);
+ struct vfs_ns_cap_data nscaps;
+ int size;
+
+ size = vfs_caps_to_xattr(idmap, i_user_ns(inode), caps,
+ &nscaps, sizeof(nscaps));
+ if (size < 0)
+ return size;
+
+ return __vfs_setxattr_noperm(idmap, dentry, XATTR_NAME_CAPS,
+ &nscaps, size, setxattr_flags);
+}
+
+/**
+ * vfs_set_fscaps - set filesystem capabilities
+ * @idmap: idmap of the mount the inode was found from
+ * @dentry: the dentry on which to set filesystem capabilities
+ * @caps: the filesystem capabilities to be written
+ * @setxattr_flags: setxattr flags to use when writing the capabilities xattr
+ *
+ * This function writes the supplied filesystem capabilities to the dentry.
+ *
+ * Return: 0 on success, a negative errno on error.
+ */
+int vfs_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ const struct vfs_caps *caps, int setxattr_flags)
+{
+ struct inode *inode = d_inode(dentry);
+ struct inode *delegated_inode = NULL;
+ int error;
+
+retry_deleg:
+ inode_lock(inode);
+
+ error = xattr_permission(idmap, inode, XATTR_NAME_CAPS, MAY_WRITE);
+ if (error)
+ goto out_inode_unlock;
+ error = security_inode_set_fscaps(idmap, dentry, caps, setxattr_flags);
+ if (error)
+ goto out_inode_unlock;
+
+ error = try_break_deleg(inode, &delegated_inode);
+ if (error)
+ goto out_inode_unlock;
+
+ if (inode->i_opflags & IOP_XATTR) {
+ if (inode->i_op->set_fscaps)
+ error = inode->i_op->set_fscaps(idmap, dentry, caps,
+ setxattr_flags);
+ else
+ error = generic_set_fscaps(idmap, dentry, caps,
+ setxattr_flags);
+ if (!error) {
+ fsnotify_xattr(dentry);
+ security_inode_post_set_fscaps(idmap, dentry, caps,
+ setxattr_flags);
+ }
+ } else if (unlikely(is_bad_inode(inode))) {
+ error = -EIO;
+ } else {
+ error = -EOPNOTSUPP;
+ }
+
+out_inode_unlock:
+ inode_unlock(inode);
+
+ if (delegated_inode) {
+ error = break_deleg_wait(&delegated_inode);
+ if (!error)
+ goto retry_deleg;
+ }
+
+ return error;
+}
+EXPORT_SYMBOL(vfs_set_fscaps);
+
int
__vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct inode *inode, const char *name, const void *value,
diff --git a/include/linux/fs.h b/include/linux/fs.h
index d7cd2467e1ea..4f5d7ed44644 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2120,6 +2120,8 @@ extern int vfs_get_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry,
struct vfs_caps *caps);
extern int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
struct vfs_caps *caps);
+extern int vfs_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ const struct vfs_caps *caps, int setxattr_flags);

/**
* enum freeze_holder - holder of the freeze

--
2.43.0


2024-02-21 21:33:07

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 20/25] ovl: add fscaps handlers

Add handlers which read fs caps from the lower or upper filesystem and
write/remove fs caps to the upper filesystem, performing copy-up as
necessary.

While fscaps only really make sense on regular files, the general policy
is to allow most xattr namespaces on all different inode types, so
fscaps handlers are installed in the inode operations for all types of
inodes.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
fs/overlayfs/dir.c | 2 ++
fs/overlayfs/inode.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++
fs/overlayfs/overlayfs.h | 5 ++++
3 files changed, 79 insertions(+)

diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 0f8b4a719237..4ff360fe10c9 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -1307,6 +1307,8 @@ const struct inode_operations ovl_dir_inode_operations = {
.get_inode_acl = ovl_get_inode_acl,
.get_acl = ovl_get_acl,
.set_acl = ovl_set_acl,
+ .get_fscaps = ovl_get_fscaps,
+ .set_fscaps = ovl_set_fscaps,
.update_time = ovl_update_time,
.fileattr_get = ovl_fileattr_get,
.fileattr_set = ovl_fileattr_set,
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index c63b31a460be..7a8978ea6fe1 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -568,6 +568,72 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
}
#endif

+int ovl_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct vfs_caps *caps)
+{
+ int err;
+ const struct cred *old_cred;
+ struct path realpath;
+
+ ovl_path_real(dentry, &realpath);
+ old_cred = ovl_override_creds(dentry->d_sb);
+ err = vfs_get_fscaps(mnt_idmap(realpath.mnt), realpath.dentry, caps);
+ revert_creds(old_cred);
+ return err;
+}
+
+int ovl_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ const struct vfs_caps *caps, int setxattr_flags)
+{
+ int err;
+ struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
+ struct dentry *upperdentry = ovl_dentry_upper(dentry);
+ struct dentry *realdentry = upperdentry ?: ovl_dentry_lower(dentry);
+ const struct cred *old_cred;
+
+ /*
+ * If the fscaps are to be remove from a lower file, check that they
+ * exist before copying up.
+ */
+ if (!caps && !upperdentry) {
+ struct path realpath;
+ struct vfs_caps lower_caps;
+
+ ovl_path_lower(dentry, &realpath);
+ old_cred = ovl_override_creds(dentry->d_sb);
+ err = vfs_get_fscaps(mnt_idmap(realpath.mnt), realdentry,
+ &lower_caps);
+ revert_creds(old_cred);
+ if (err)
+ goto out;
+ }
+
+ err = ovl_want_write(dentry);
+ if (err)
+ goto out;
+
+ err = ovl_copy_up(dentry);
+ if (err)
+ goto out_drop_write;
+ upperdentry = ovl_dentry_upper(dentry);
+
+ old_cred = ovl_override_creds(dentry->d_sb);
+ if (!caps)
+ err = vfs_remove_fscaps(ovl_upper_mnt_idmap(ofs), upperdentry);
+ else
+ err = vfs_set_fscaps(ovl_upper_mnt_idmap(ofs), upperdentry,
+ caps, setxattr_flags);
+ revert_creds(old_cred);
+
+ /* copy c/mtime */
+ ovl_copyattr(d_inode(dentry));
+
+out_drop_write:
+ ovl_drop_write(dentry);
+out:
+ return err;
+}
+
int ovl_update_time(struct inode *inode, int flags)
{
if (flags & S_ATIME) {
@@ -747,6 +813,8 @@ static const struct inode_operations ovl_file_inode_operations = {
.get_inode_acl = ovl_get_inode_acl,
.get_acl = ovl_get_acl,
.set_acl = ovl_set_acl,
+ .get_fscaps = ovl_get_fscaps,
+ .set_fscaps = ovl_set_fscaps,
.update_time = ovl_update_time,
.fiemap = ovl_fiemap,
.fileattr_get = ovl_fileattr_get,
@@ -758,6 +826,8 @@ static const struct inode_operations ovl_symlink_inode_operations = {
.get_link = ovl_get_link,
.getattr = ovl_getattr,
.listxattr = ovl_listxattr,
+ .get_fscaps = ovl_get_fscaps,
+ .set_fscaps = ovl_set_fscaps,
.update_time = ovl_update_time,
};

@@ -769,6 +839,8 @@ static const struct inode_operations ovl_special_inode_operations = {
.get_inode_acl = ovl_get_inode_acl,
.get_acl = ovl_get_acl,
.set_acl = ovl_set_acl,
+ .get_fscaps = ovl_get_fscaps,
+ .set_fscaps = ovl_set_fscaps,
.update_time = ovl_update_time,
};

diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index ee949f3e7c77..4f948749ee02 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -781,6 +781,11 @@ static inline struct posix_acl *ovl_get_acl_path(const struct path *path,
}
#endif

+int ovl_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct vfs_caps *caps);
+int ovl_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ const struct vfs_caps *caps, int setxattr_flags);
+
int ovl_update_time(struct inode *inode, int flags);
bool ovl_is_private_xattr(struct super_block *sb, const char *name);


--
2.43.0


2024-02-21 21:33:27

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 21/25] ovl: use vfs_{get,set}_fscaps() for copy-up

Using vfs_{get,set}xattr() for fscaps will be blocked in a future
commit, so convert ovl to use the new interfaces. Also remove the now
unused ovl_getxattr_value().

Reviewed-by: Amir Goldstein <[email protected]>
Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
fs/overlayfs/copy_up.c | 72 ++++++++++++++++++++++++++------------------------
1 file changed, 37 insertions(+), 35 deletions(-)

diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index b8e25ca51016..d7c8d76e2394 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -73,6 +73,23 @@ static int ovl_copy_acl(struct ovl_fs *ofs, const struct path *path,
return err;
}

+static int ovl_copy_fscaps(struct ovl_fs *ofs, const struct path *oldpath,
+ struct dentry *new)
+{
+ struct vfs_caps capability;
+ int err;
+
+ err = vfs_get_fscaps(mnt_idmap(oldpath->mnt), oldpath->dentry,
+ &capability);
+ if (err) {
+ if (err == -ENODATA || err == -EOPNOTSUPP)
+ return 0;
+ return err;
+ }
+
+ return vfs_set_fscaps(ovl_upper_mnt_idmap(ofs), new, &capability, 0);
+}
+
int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct dentry *new)
{
struct dentry *old = oldpath->dentry;
@@ -130,6 +147,14 @@ int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct de
break;
}

+ if (is_fscaps_xattr(name)) {
+ error = ovl_copy_fscaps(OVL_FS(sb), oldpath, new);
+ if (!error)
+ continue;
+ /* fs capabilities must be copied */
+ break;
+ }
+
retry:
size = ovl_do_getxattr(oldpath, name, value, value_size);
if (size == -ERANGE)
@@ -1039,61 +1064,40 @@ static bool ovl_need_meta_copy_up(struct dentry *dentry, umode_t mode,
return true;
}

-static ssize_t ovl_getxattr_value(const struct path *path, char *name, char **value)
-{
- ssize_t res;
- char *buf;
-
- res = ovl_do_getxattr(path, name, NULL, 0);
- if (res == -ENODATA || res == -EOPNOTSUPP)
- res = 0;
-
- if (res > 0) {
- buf = kzalloc(res, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- res = ovl_do_getxattr(path, name, buf, res);
- if (res < 0)
- kfree(buf);
- else
- *value = buf;
- }
- return res;
-}
-
/* Copy up data of an inode which was copied up metadata only in the past. */
static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c)
{
struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb);
struct path upperpath;
int err;
- char *capability = NULL;
- ssize_t cap_size;
+ struct vfs_caps capability;
+ bool has_capability = false;

ovl_path_upper(c->dentry, &upperpath);
if (WARN_ON(upperpath.dentry == NULL))
return -EIO;

if (c->stat.size) {
- err = cap_size = ovl_getxattr_value(&upperpath, XATTR_NAME_CAPS,
- &capability);
- if (cap_size < 0)
+ err = vfs_get_fscaps(mnt_idmap(upperpath.mnt), upperpath.dentry,
+ &capability);
+ if (!err)
+ has_capability = 1;
+ else if (err != -ENODATA && err != EOPNOTSUPP)
goto out;
}

err = ovl_copy_up_data(c, &upperpath);
if (err)
- goto out_free;
+ goto out;

/*
* Writing to upper file will clear security.capability xattr. We
* don't want that to happen for normal copy-up operation.
*/
ovl_start_write(c->dentry);
- if (capability) {
- err = ovl_do_setxattr(ofs, upperpath.dentry, XATTR_NAME_CAPS,
- capability, cap_size, 0);
+ if (has_capability) {
+ err = vfs_set_fscaps(mnt_idmap(upperpath.mnt), upperpath.dentry,
+ &capability, 0);
}
if (!err) {
err = ovl_removexattr(ofs, upperpath.dentry,
@@ -1101,13 +1105,11 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c)
}
ovl_end_write(c->dentry);
if (err)
- goto out_free;
+ goto out;

ovl_clear_flag(OVL_HAS_DIGEST, d_inode(c->dentry));
ovl_clear_flag(OVL_VERIFIED_DIGEST, d_inode(c->dentry));
ovl_set_upperdata(d_inode(c->dentry));
-out_free:
- kfree(capability);
out:
return err;
}

--
2.43.0


2024-02-21 21:33:27

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 15/25] security: call evm fscaps hooks from generic security hooks

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
security/security.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/security/security.c b/security/security.c
index 0d210da9862c..f515d8430318 100644
--- a/security/security.c
+++ b/security/security.c
@@ -2365,9 +2365,14 @@ int security_inode_remove_acl(struct mnt_idmap *idmap,
int security_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
const struct vfs_caps *caps, int flags)
{
+ int ret;
+
if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
return 0;
- return call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
+ ret = call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
+ if (ret)
+ return ret;
+ return evm_inode_set_fscaps(idmap, dentry, caps, flags);
}

/**
@@ -2387,6 +2392,7 @@ void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
return;
call_void_hook(inode_post_set_fscaps, idmap, dentry, caps, flags);
+ evm_inode_post_set_fscaps(idmap, dentry, caps, flags);
}

/**
@@ -2415,9 +2421,14 @@ int security_inode_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
*/
int security_inode_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
{
+ int ret;
+
if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
return 0;
- return call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
+ ret = call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
+ if (ret)
+ return ret;
+ return evm_inode_remove_fscaps(dentry);
}

/**

--
2.43.0


2024-02-21 21:38:05

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 12/25] selinux: add hooks for fscaps operations

Add hooks for set/get/remove fscaps operations which perform the same
checks as the xattr hooks would have done for XATTR_NAME_CAPS.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
security/selinux/hooks.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index a6bf90ace84c..da129a387b34 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3367,6 +3367,29 @@ static int selinux_inode_removexattr(struct mnt_idmap *idmap,
return -EACCES;
}

+static int selinux_inode_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps, int flags)
+{
+ return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
+}
+
+static int selinux_inode_get_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry)
+{
+ return dentry_has_perm(current_cred(), dentry, FILE__GETATTR);
+}
+
+static int selinux_inode_remove_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry)
+{
+ int rc = cap_inode_removexattr(idmap, dentry, XATTR_NAME_CAPS);
+ if (rc)
+ return rc;
+
+ return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
+}
+
static int selinux_path_notify(const struct path *path, u64 mask,
unsigned int obj_type)
{
@@ -7165,6 +7188,9 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
LSM_HOOK_INIT(inode_set_acl, selinux_inode_set_acl),
LSM_HOOK_INIT(inode_get_acl, selinux_inode_get_acl),
LSM_HOOK_INIT(inode_remove_acl, selinux_inode_remove_acl),
+ LSM_HOOK_INIT(inode_set_fscaps, selinux_inode_set_fscaps),
+ LSM_HOOK_INIT(inode_get_fscaps, selinux_inode_get_fscaps),
+ LSM_HOOK_INIT(inode_remove_fscaps, selinux_inode_remove_fscaps),
LSM_HOOK_INIT(inode_getsecurity, selinux_inode_getsecurity),
LSM_HOOK_INIT(inode_setsecurity, selinux_inode_setsecurity),
LSM_HOOK_INIT(inode_listsecurity, selinux_inode_listsecurity),

--
2.43.0


2024-02-21 21:38:21

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 09/25] commoncap: use is_fscaps_xattr()

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
security/commoncap.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/security/commoncap.c b/security/commoncap.c
index 289530e58c37..19affcfa3126 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -1205,7 +1205,7 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name,
* For XATTR_NAME_CAPS the check will be done in
* cap_convert_nscap(), called by setxattr()
*/
- if (strcmp(name, XATTR_NAME_CAPS) == 0)
+ if (is_fscaps_xattr(name))
return 0;

if (!ns_capable(user_ns, CAP_SYS_ADMIN))
@@ -1242,7 +1242,7 @@ int cap_inode_removexattr(struct mnt_idmap *idmap,
XATTR_SECURITY_PREFIX_LEN) != 0)
return 0;

- if (strcmp(name, XATTR_NAME_CAPS) == 0) {
+ if (is_fscaps_xattr(name)) {
/* security.capability gets namespaced */
struct inode *inode = d_backing_inode(dentry);
if (!inode)

--
2.43.0


2024-02-21 21:38:52

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 13/25] smack: add hooks for fscaps operations

Add hooks for set/get/remove fscaps operations which perform the same
checks as the xattr hooks would have done for XATTR_NAME_CAPS.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
security/smack/smack_lsm.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 71 insertions(+)

diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 0fdbf04cc258..1eaa89dede6b 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1530,6 +1530,74 @@ static int smack_inode_remove_acl(struct mnt_idmap *idmap,
return rc;
}

+/**
+ * smack_inode_set_fscaps - Smack check for setting file capabilities
+ * @mnt_userns: the userns attached to the source mnt for this request
+ * @detry: the object
+ * @caps: the file capabilities
+ * @flags: unused
+ *
+ * Returns 0 if the access is permitted, or an error code otherwise.
+ */
+static int smack_inode_set_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry,
+ const struct vfs_caps *caps, int flags)
+{
+ struct smk_audit_info ad;
+ int rc;
+
+ smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
+ smk_ad_setfield_u_fs_path_dentry(&ad, dentry);
+ rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad);
+ rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc);
+ return rc;
+}
+
+/**
+ * smack_inode_get_fscaps - Smack check for getting file capabilities
+ * @dentry: the object
+ *
+ * Returns 0 if access is permitted, an error code otherwise
+ */
+static int smack_inode_get_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry)
+{
+ struct smk_audit_info ad;
+ int rc;
+
+ smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
+ smk_ad_setfield_u_fs_path_dentry(&ad, dentry);
+
+ rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_READ, &ad);
+ rc = smk_bu_inode(d_backing_inode(dentry), MAY_READ, rc);
+ return rc;
+}
+
+/**
+ * smack_inode_remove_acl - Smack check for removing file capabilities
+ * @idmap: idmap of the mnt this request came from
+ * @dentry: the object
+ *
+ * Returns 0 if access is permitted, an error code otherwise
+ */
+static int smack_inode_remove_fscaps(struct mnt_idmap *idmap,
+ struct dentry *dentry)
+{
+ struct smk_audit_info ad;
+ int rc;
+
+ rc = cap_inode_removexattr(idmap, dentry, XATTR_NAME_CAPS);
+ if (rc != 0)
+ return rc;
+
+ smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
+ smk_ad_setfield_u_fs_path_dentry(&ad, dentry);
+
+ rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad);
+ rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc);
+ return rc;
+}
+
/**
* smack_inode_getsecurity - get smack xattrs
* @idmap: idmap of the mount
@@ -5045,6 +5113,9 @@ static struct security_hook_list smack_hooks[] __ro_after_init = {
LSM_HOOK_INIT(inode_set_acl, smack_inode_set_acl),
LSM_HOOK_INIT(inode_get_acl, smack_inode_get_acl),
LSM_HOOK_INIT(inode_remove_acl, smack_inode_remove_acl),
+ LSM_HOOK_INIT(inode_set_fscaps, smack_inode_set_fscaps),
+ LSM_HOOK_INIT(inode_get_fscaps, smack_inode_get_fscaps),
+ LSM_HOOK_INIT(inode_remove_fscaps, smack_inode_remove_fscaps),
LSM_HOOK_INIT(inode_getsecurity, smack_inode_getsecurity),
LSM_HOOK_INIT(inode_setsecurity, smack_inode_setsecurity),
LSM_HOOK_INIT(inode_listsecurity, smack_inode_listsecurity),

--
2.43.0


2024-02-21 21:40:29

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 19/25] fs: add vfs_remove_fscaps()

Provide a type-safe interface for removing filesystem capabilities and a
generic implementation suitable for most filesystems. Also add an
internal interface, vfs_remove_fscaps_nosec(), which is called with the
inode lock held and skips security checks for later use from the
capability code.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
fs/xattr.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/fs.h | 2 ++
2 files changed, 83 insertions(+)

diff --git a/fs/xattr.c b/fs/xattr.c
index 96de43928a51..8b0f7384cbc9 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -324,6 +324,87 @@ int vfs_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
}
EXPORT_SYMBOL(vfs_set_fscaps);

+static int generic_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
+{
+ return __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
+}
+
+/**
+ * vfs_remove_fscaps_nosec - remove filesystem capabilities without
+ * security checks
+ * @idmap: idmap of the mount the inode was found from
+ * @dentry: the dentry from which to remove filesystem capabilities
+ *
+ * This function removes any filesystem capabilities from the specified
+ * dentry. Does not perform any security checks, and callers must hold the
+ * inode lock.
+ *
+ * Return: 0 on success, a negative errno on error.
+ */
+int vfs_remove_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry)
+{
+ struct inode *inode = dentry->d_inode;
+ int error;
+
+ if (inode->i_op->set_fscaps)
+ error = inode->i_op->set_fscaps(idmap, dentry, NULL,
+ XATTR_REPLACE);
+ else
+ error = generic_remove_fscaps(idmap, dentry);
+
+ return error;
+}
+
+/**
+ * vfs_remove_fscaps - remove filesystem capabilities
+ * @idmap: idmap of the mount the inode was found from
+ * @dentry: the dentry from which to remove filesystem capabilities
+ *
+ * This function removes any filesystem capabilities from the specified
+ * dentry.
+ *
+ * Return: 0 on success, a negative errno on error.
+ */
+int vfs_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
+{
+ struct inode *inode = dentry->d_inode;
+ struct inode *delegated_inode = NULL;
+ int error;
+
+retry_deleg:
+ inode_lock(inode);
+
+ error = xattr_permission(idmap, inode, XATTR_NAME_CAPS, MAY_WRITE);
+ if (error)
+ goto out_inode_unlock;
+
+ error = security_inode_remove_fscaps(idmap, dentry);
+ if (error)
+ goto out_inode_unlock;
+
+ error = try_break_deleg(inode, &delegated_inode);
+ if (error)
+ goto out_inode_unlock;
+
+ error = vfs_remove_fscaps_nosec(idmap, dentry);
+ if (!error) {
+ fsnotify_xattr(dentry);
+ evm_inode_post_remove_fscaps(dentry);
+ }
+
+out_inode_unlock:
+ inode_unlock(inode);
+
+ if (delegated_inode) {
+ error = break_deleg_wait(&delegated_inode);
+ if (!error)
+ goto retry_deleg;
+ }
+
+ return error;
+}
+EXPORT_SYMBOL(vfs_remove_fscaps);
+
int
__vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct inode *inode, const char *name, const void *value,
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 4f5d7ed44644..c07427d2fc71 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2122,6 +2122,8 @@ extern int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
struct vfs_caps *caps);
extern int vfs_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
const struct vfs_caps *caps, int setxattr_flags);
+extern int vfs_remove_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry);
+extern int vfs_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry);

/**
* enum freeze_holder - holder of the freeze

--
2.43.0


2024-02-21 21:40:36

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 25/25] vfs: return -EOPNOTSUPP for fscaps from vfs_*xattr()

Now that the new vfs-level interfaces are fully supported and all code
has been converted to use them, stop permitting use of the top-level vfs
xattr interfaces for capabilities xattrs. Unlike with ACLs we still need
to be able to work with fscaps xattrs using lower-level interfaces in a
handful of places, so only use of the top-level xattr interfaces is
restricted.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
fs/xattr.c | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/fs/xattr.c b/fs/xattr.c
index 30eff6bc4f6d..2b8214c9534f 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -534,6 +534,9 @@ vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
const void *orig_value = value;
int error;

+ if (WARN_ON_ONCE(is_fscaps_xattr(name)))
+ return -EOPNOTSUPP;
+
retry_deleg:
inode_lock(inode);
error = __vfs_setxattr_locked(idmap, dentry, name, value, size,
@@ -649,6 +652,9 @@ vfs_getxattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct inode *inode = dentry->d_inode;
int error;

+ if (WARN_ON_ONCE(is_fscaps_xattr(name)))
+ return -EOPNOTSUPP;
+
error = xattr_permission(idmap, inode, name, MAY_READ);
if (error)
return error;
@@ -788,6 +794,9 @@ vfs_removexattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct inode *delegated_inode = NULL;
int error;

+ if (WARN_ON_ONCE(is_fscaps_xattr(name)))
+ return -EOPNOTSUPP;
+
retry_deleg:
inode_lock(inode);
error = __vfs_removexattr_locked(idmap, dentry,

--
2.43.0


2024-02-21 21:40:47

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 17/25] fs: add vfs_get_fscaps()

Provide a type-safe interface for retrieving filesystem capabilities and
a generic implementation suitable for most filesystems. Also add an
internal interface, vfs_get_fscaps_nosec(), which skips security checks
for later use from the capability code.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
fs/xattr.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/fs.h | 4 ++++
2 files changed, 68 insertions(+)

diff --git a/fs/xattr.c b/fs/xattr.c
index 06290e4ebc03..10d1b1f78fc2 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -181,6 +181,70 @@ xattr_supports_user_prefix(struct inode *inode)
}
EXPORT_SYMBOL(xattr_supports_user_prefix);

+static int generic_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct vfs_caps *caps)
+{
+ struct inode *inode = d_inode(dentry);
+ struct vfs_ns_cap_data nscaps;
+ int ret;
+
+ ret = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, &nscaps, sizeof(nscaps));
+
+ if (ret >= 0)
+ ret = vfs_caps_from_xattr(idmap, i_user_ns(inode), caps, &nscaps, ret);
+
+ return ret;
+}
+
+/**
+ * vfs_get_fscaps_nosec - get filesystem capabilities without security checks
+ * @idmap: idmap of the mount the inode was found from
+ * @dentry: the dentry from which to get filesystem capabilities
+ * @caps: storage in which to return the filesystem capabilities
+ *
+ * This function gets the filesystem capabilities for the dentry and returns
+ * them in @caps. It does not perform security checks.
+ *
+ * Return: 0 on success, a negative errno on error.
+ */
+int vfs_get_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct vfs_caps *caps)
+{
+ struct inode *inode = d_inode(dentry);
+
+ if (inode->i_op->get_fscaps)
+ return inode->i_op->get_fscaps(idmap, dentry, caps);
+ return generic_get_fscaps(idmap, dentry, caps);
+}
+
+/**
+ * vfs_get_fscaps - get filesystem capabilities
+ * @idmap: idmap of the mount the inode was found from
+ * @dentry: the dentry from which to get filesystem capabilities
+ * @caps: storage in which to return the filesystem capabilities
+ *
+ * This function gets the filesystem capabilities for the dentry and returns
+ * them in @caps.
+ *
+ * Return: 0 on success, a negative errno on error.
+ */
+int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct vfs_caps *caps)
+{
+ int error;
+
+ /*
+ * The VFS has no restrictions on reading security.* xattrs, so
+ * xattr_permission() isn't needed. Only LSMs get a say.
+ */
+ error = security_inode_get_fscaps(idmap, dentry);
+ if (error)
+ return error;
+
+ return vfs_get_fscaps_nosec(idmap, dentry, caps);
+}
+EXPORT_SYMBOL(vfs_get_fscaps);
+
int
__vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct inode *inode, const char *name, const void *value,
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 89163e0f7aad..d7cd2467e1ea 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2116,6 +2116,10 @@ extern int vfs_dedupe_file_range(struct file *file,
extern loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,
struct file *dst_file, loff_t dst_pos,
loff_t len, unsigned int remap_flags);
+extern int vfs_get_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct vfs_caps *caps);
+extern int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct vfs_caps *caps);

/**
* enum freeze_holder - holder of the freeze

--
2.43.0


2024-02-21 21:42:17

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 23/25] commoncap: remove cap_inode_getsecurity()

Reading of fscaps xattrs is now done via vfs_get_fscaps(), so there is
no longer any need to do it from security_inode_getsecurity(). Remove
cap_inode_getsecurity() and its associated helpers which are now unused.

We don't allow reading capabilities xattrs this way anyomre, so remove
the handler and associated helpers.

Acked-by: Paul Moore <[email protected]>
Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
include/linux/security.h | 5 +-
security/commoncap.c | 132 -----------------------------------------------
2 files changed, 1 insertion(+), 136 deletions(-)

diff --git a/include/linux/security.h b/include/linux/security.h
index 40be548e5e12..599d665eac71 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -162,9 +162,6 @@ int cap_inode_removexattr(struct mnt_idmap *idmap,
struct dentry *dentry, const char *name);
int cap_inode_need_killpriv(struct dentry *dentry);
int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry);
-int cap_inode_getsecurity(struct mnt_idmap *idmap,
- struct inode *inode, const char *name, void **buffer,
- bool alloc);
extern int cap_mmap_addr(unsigned long addr);
extern int cap_mmap_file(struct file *file, unsigned long reqprot,
unsigned long prot, unsigned long flags);
@@ -984,7 +981,7 @@ static inline int security_inode_getsecurity(struct mnt_idmap *idmap,
const char *name, void **buffer,
bool alloc)
{
- return cap_inode_getsecurity(idmap, inode, name, buffer, alloc);
+ return -EOPNOTSUPP;
}

static inline int security_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags)
diff --git a/security/commoncap.c b/security/commoncap.c
index 4254e5e46024..a0ff7e6092e0 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -353,137 +353,6 @@ static __u32 sansflags(__u32 m)
return m & ~VFS_CAP_FLAGS_EFFECTIVE;
}

-static bool is_v2header(int size, const struct vfs_cap_data *cap)
-{
- if (size != XATTR_CAPS_SZ_2)
- return false;
- return sansflags(le32_to_cpu(cap->magic_etc)) == VFS_CAP_REVISION_2;
-}
-
-static bool is_v3header(int size, const struct vfs_cap_data *cap)
-{
- if (size != XATTR_CAPS_SZ_3)
- return false;
- return sansflags(le32_to_cpu(cap->magic_etc)) == VFS_CAP_REVISION_3;
-}
-
-/*
- * getsecurity: We are called for security.* before any attempt to read the
- * xattr from the inode itself.
- *
- * This gives us a chance to read the on-disk value and convert it. If we
- * return -EOPNOTSUPP, then vfs_getxattr() will call the i_op handler.
- *
- * Note we are not called by vfs_getxattr_alloc(), but that is only called
- * by the integrity subsystem, which really wants the unconverted values -
- * so that's good.
- */
-int cap_inode_getsecurity(struct mnt_idmap *idmap,
- struct inode *inode, const char *name, void **buffer,
- bool alloc)
-{
- int size;
- kuid_t kroot;
- vfsuid_t vfsroot;
- u32 nsmagic, magic;
- uid_t root, mappedroot;
- char *tmpbuf = NULL;
- struct vfs_cap_data *cap;
- struct vfs_ns_cap_data *nscap = NULL;
- struct dentry *dentry;
- struct user_namespace *fs_ns;
-
- if (strcmp(name, "capability") != 0)
- return -EOPNOTSUPP;
-
- dentry = d_find_any_alias(inode);
- if (!dentry)
- return -EINVAL;
- size = vfs_getxattr_alloc(idmap, dentry, XATTR_NAME_CAPS, &tmpbuf,
- sizeof(struct vfs_ns_cap_data), GFP_NOFS);
- dput(dentry);
- /* gcc11 complains if we don't check for !tmpbuf */
- if (size < 0 || !tmpbuf)
- goto out_free;
-
- fs_ns = inode->i_sb->s_user_ns;
- cap = (struct vfs_cap_data *) tmpbuf;
- if (is_v2header(size, cap)) {
- root = 0;
- } else if (is_v3header(size, cap)) {
- nscap = (struct vfs_ns_cap_data *) tmpbuf;
- root = le32_to_cpu(nscap->rootid);
- } else {
- size = -EINVAL;
- goto out_free;
- }
-
- kroot = make_kuid(fs_ns, root);
-
- /* If this is an idmapped mount shift the kuid. */
- vfsroot = make_vfsuid(idmap, fs_ns, kroot);
-
- /* If the root kuid maps to a valid uid in current ns, then return
- * this as a nscap. */
- mappedroot = from_kuid(current_user_ns(), vfsuid_into_kuid(vfsroot));
- if (mappedroot != (uid_t)-1 && mappedroot != (uid_t)0) {
- size = sizeof(struct vfs_ns_cap_data);
- if (alloc) {
- if (!nscap) {
- /* v2 -> v3 conversion */
- nscap = kzalloc(size, GFP_ATOMIC);
- if (!nscap) {
- size = -ENOMEM;
- goto out_free;
- }
- nsmagic = VFS_CAP_REVISION_3;
- magic = le32_to_cpu(cap->magic_etc);
- if (magic & VFS_CAP_FLAGS_EFFECTIVE)
- nsmagic |= VFS_CAP_FLAGS_EFFECTIVE;
- memcpy(&nscap->data, &cap->data, sizeof(__le32) * 2 * VFS_CAP_U32);
- nscap->magic_etc = cpu_to_le32(nsmagic);
- } else {
- /* use allocated v3 buffer */
- tmpbuf = NULL;
- }
- nscap->rootid = cpu_to_le32(mappedroot);
- *buffer = nscap;
- }
- goto out_free;
- }
-
- if (!rootid_owns_currentns(vfsroot)) {
- size = -EOVERFLOW;
- goto out_free;
- }
-
- /* This comes from a parent namespace. Return as a v2 capability */
- size = sizeof(struct vfs_cap_data);
- if (alloc) {
- if (nscap) {
- /* v3 -> v2 conversion */
- cap = kzalloc(size, GFP_ATOMIC);
- if (!cap) {
- size = -ENOMEM;
- goto out_free;
- }
- magic = VFS_CAP_REVISION_2;
- nsmagic = le32_to_cpu(nscap->magic_etc);
- if (nsmagic & VFS_CAP_FLAGS_EFFECTIVE)
- magic |= VFS_CAP_FLAGS_EFFECTIVE;
- memcpy(&cap->data, &nscap->data, sizeof(__le32) * 2 * VFS_CAP_U32);
- cap->magic_etc = cpu_to_le32(magic);
- } else {
- /* use unconverted v2 */
- tmpbuf = NULL;
- }
- *buffer = cap;
- }
-out_free:
- kfree(tmpbuf);
- return size;
-}
-
/**
* rootid_from_vfs_caps - translate root uid of vfs caps
*
@@ -1633,7 +1502,6 @@ static struct security_hook_list capability_hooks[] __ro_after_init = {
LSM_HOOK_INIT(bprm_creds_from_file, cap_bprm_creds_from_file),
LSM_HOOK_INIT(inode_need_killpriv, cap_inode_need_killpriv),
LSM_HOOK_INIT(inode_killpriv, cap_inode_killpriv),
- LSM_HOOK_INIT(inode_getsecurity, cap_inode_getsecurity),
LSM_HOOK_INIT(mmap_addr, cap_mmap_addr),
LSM_HOOK_INIT(mmap_file, cap_mmap_file),
LSM_HOOK_INIT(task_fix_setuid, cap_task_fix_setuid),

--
2.43.0


2024-02-21 21:42:36

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 16/25] fs: add inode operations to get/set/remove fscaps

Add inode operations for getting, setting and removing filesystem
capabilities rather than passing around raw xattr data. This provides
better type safety for ids contained within xattrs.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
Documentation/filesystems/locking.rst | 4 ++++
Documentation/filesystems/vfs.rst | 17 +++++++++++++++++
include/linux/fs.h | 4 ++++
3 files changed, 25 insertions(+)

diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst
index d5bf4b6b7509..d208dd9f75ae 100644
--- a/Documentation/filesystems/locking.rst
+++ b/Documentation/filesystems/locking.rst
@@ -81,6 +81,8 @@ prototypes::
umode_t create_mode);
int (*tmpfile) (struct mnt_idmap *, struct inode *,
struct file *, umode_t);
+ int (*get_fscaps)(struct mnt_idmap *, struct dentry *, struct vfs_caps *);
+ int (*set_fscaps)(struct mnt_idmap *, struct dentry *, const struct vfs_caps *, int setxattr_flags);
int (*fileattr_set)(struct mnt_idmap *idmap,
struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
@@ -114,6 +116,8 @@ fiemap: no
update_time: no
atomic_open: shared (exclusive if O_CREAT is set in open flags)
tmpfile: no
+get_fscaps: no
+set_fscaps: exclusive
fileattr_get: no or exclusive
fileattr_set: exclusive
get_offset_ctx no
diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
index eebcc0f9e2bc..ed1cb03f271e 100644
--- a/Documentation/filesystems/vfs.rst
+++ b/Documentation/filesystems/vfs.rst
@@ -514,6 +514,8 @@ As of kernel 2.6.22, the following members are defined:
int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t);
struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int);
int (*set_acl)(struct mnt_idmap *, struct dentry *, struct posix_acl *, int);
+ int (*get_fscaps)(struct mnt_idmap *, struct dentry *, struct vfs_caps *);
+ int (*set_fscaps)(struct mnt_idmap *, struct dentry *, const struct vfs_caps *, int setxattr_flags);
int (*fileattr_set)(struct mnt_idmap *idmap,
struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
@@ -667,6 +669,21 @@ otherwise noted.
open; this can be done by calling finish_open_simple() right at
the end.

+``get_fscaps``
+
+ called to get filesystem capabilites of an inode. If unset,
+ xattr handlers will be used to get the raw xattr data. Most
+ filesystems can rely on the generic handler.
+
+``set_fscaps``
+
+ called to set filesystem capabilites of an inode. If unset,
+ xattr handlers will be used to set the raw xattr data. Most
+ filesystems can rely on the generic handler.
+
+ If the new fscaps value is NULL the filesystem must remove any
+ fscaps from the inode.
+
``fileattr_get``
called on ioctl(FS_IOC_GETFLAGS) and ioctl(FS_IOC_FSGETXATTR) to
retrieve miscellaneous file flags and attributes. Also called
diff --git a/include/linux/fs.h b/include/linux/fs.h
index ed5966a70495..89163e0f7aad 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2067,6 +2067,10 @@ struct inode_operations {
int);
int (*set_acl)(struct mnt_idmap *, struct dentry *,
struct posix_acl *, int);
+ int (*get_fscaps)(struct mnt_idmap *, struct dentry *,
+ struct vfs_caps *);
+ int (*set_fscaps)(struct mnt_idmap *, struct dentry *,
+ const struct vfs_caps *, int setxattr_flags);
int (*fileattr_set)(struct mnt_idmap *idmap,
struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);

--
2.43.0


2024-02-21 21:42:41

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

Use the vfs interfaces for fetching file capabilities for killpriv
checks and from get_vfs_caps_from_disk(). While there, update the
kerneldoc for get_vfs_caps_from_disk() to explain how it is different
from vfs_get_fscaps_nosec().

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
security/commoncap.c | 30 +++++++++++++-----------------
1 file changed, 13 insertions(+), 17 deletions(-)

diff --git a/security/commoncap.c b/security/commoncap.c
index a0ff7e6092e0..751bb26a06a6 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
*/
int cap_inode_need_killpriv(struct dentry *dentry)
{
- struct inode *inode = d_backing_inode(dentry);
+ struct vfs_caps caps;
int error;

- error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
- return error > 0;
+ /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
+ error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
+ return error == 0;
}

/**
@@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
{
int error;

- error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
+ error = vfs_remove_fscaps_nosec(idmap, dentry);
if (error == -EOPNOTSUPP)
error = 0;
return error;
@@ -719,6 +720,10 @@ ssize_t vfs_caps_to_user_xattr(struct mnt_idmap *idmap,
* @cpu_caps: vfs capabilities
*
* Extract the on-exec-apply capability sets for an executable file.
+ * For version 3 capabilities xattrs, returns the capabilities only if
+ * they are applicable to current_user_ns() (i.e. that the rootid
+ * corresponds to an ID which maps to ID 0 in current_user_ns() or an
+ * ancestor), and returns -ENODATA otherwise.
*
* If the inode has been found through an idmapped mount the idmap of
* the vfsmount must be passed through @idmap. This function will then
@@ -731,25 +736,16 @@ int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
struct vfs_caps *cpu_caps)
{
struct inode *inode = d_backing_inode(dentry);
- int size, ret;
- struct vfs_ns_cap_data data, *nscaps = &data;
+ int ret;

if (!inode)
return -ENODATA;

- size = __vfs_getxattr((struct dentry *)dentry, inode,
- XATTR_NAME_CAPS, &data, XATTR_CAPS_SZ);
- if (size == -ENODATA || size == -EOPNOTSUPP)
+ ret = vfs_get_fscaps_nosec(idmap, (struct dentry *)dentry, cpu_caps);
+ if (ret == -EOPNOTSUPP || ret == -EOVERFLOW)
/* no data, that's ok */
- return -ENODATA;
+ ret = -ENODATA;

- if (size < 0)
- return size;
-
- ret = vfs_caps_from_xattr(idmap, inode->i_sb->s_user_ns,
- cpu_caps, nscaps, size);
- if (ret == -EOVERFLOW)
- return -ENODATA;
if (ret)
return ret;


--
2.43.0


2024-02-21 21:42:44

by Seth Forshee

[permalink] [raw]
Subject: [PATCH v2 22/25] fs: use vfs interfaces for capabilities xattrs

Now that all the plumbing is in place, switch over to using the new
inode operations to get/set fs caps. This pushes all mapping of ids into
the caller's user ns to above the vfs_*() level, making this consistent
with other vfs_*() interfaces.

cap_convert_nscap() is updated to return vfs_caps and moved to be called
from the new code path for setting fscaps. This means that use of
vfs_setxattr() will no longer remap ids in fscap xattrs, but all code
which used vfs_setxattr() for fscaps xattrs has been converted to the
new interfaces.

Removing the mapping of fscaps rootids from vfs_getxattr() is more
invovled and will be addressed in a later commit.

Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
---
fs/xattr.c | 49 ++++++++++++++++++++++++----
include/linux/capability.h | 2 +-
security/commoncap.c | 79 +++++++++++++++-------------------------------
3 files changed, 69 insertions(+), 61 deletions(-)

diff --git a/fs/xattr.c b/fs/xattr.c
index 8b0f7384cbc9..30eff6bc4f6d 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -534,13 +534,6 @@ vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
const void *orig_value = value;
int error;

- if (size && is_fscaps_xattr(name)) {
- error = cap_convert_nscap(idmap, dentry, &value, size);
- if (error < 0)
- return error;
- size = error;
- }
-
retry_deleg:
inode_lock(inode);
error = __vfs_setxattr_locked(idmap, dentry, name, value, size,
@@ -851,6 +844,24 @@ int do_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
return do_set_acl(idmap, dentry, ctx->kname->name,
ctx->kvalue, ctx->size);

+ if (is_fscaps_xattr(ctx->kname->name)) {
+ struct vfs_caps caps;
+ int ret;
+
+ /*
+ * rootid is already in the mount idmap, so pass nop_mnt_idmap
+ * so that it won't be mapped.
+ */
+ ret = vfs_caps_from_xattr(&nop_mnt_idmap, current_user_ns(),
+ &caps, ctx->kvalue, ctx->size);
+ if (ret)
+ return ret;
+ ret = cap_convert_nscap(idmap, dentry, &caps);
+ if (ret)
+ return ret;
+ return vfs_set_fscaps(idmap, dentry, &caps, ctx->flags);
+ }
+
return vfs_setxattr(idmap, dentry, ctx->kname->name,
ctx->kvalue, ctx->size, ctx->flags);
}
@@ -949,6 +960,27 @@ do_getxattr(struct mnt_idmap *idmap, struct dentry *d,
ssize_t error;
char *kname = ctx->kname->name;

+ if (is_fscaps_xattr(kname)) {
+ struct vfs_caps caps;
+ struct vfs_ns_cap_data data;
+ int ret;
+
+ ret = vfs_get_fscaps(idmap, d, &caps);
+ if (ret)
+ return ret;
+ /*
+ * rootid is already in the mount idmap, so pass nop_mnt_idmap
+ * so that it won't be mapped.
+ */
+ ret = vfs_caps_to_user_xattr(&nop_mnt_idmap, current_user_ns(),
+ &caps, &data, ctx->size);
+ if (ret < 0)
+ return ret;
+ if (ctx->size && copy_to_user(ctx->value, &data, ret))
+ return -EFAULT;
+ return ret;
+ }
+
if (ctx->size) {
if (ctx->size > XATTR_SIZE_MAX)
ctx->size = XATTR_SIZE_MAX;
@@ -1139,6 +1171,9 @@ removexattr(struct mnt_idmap *idmap, struct dentry *d,
if (is_posix_acl_xattr(kname))
return vfs_remove_acl(idmap, d, kname);

+ if (is_fscaps_xattr(kname))
+ return vfs_remove_fscaps(idmap, d);
+
return vfs_removexattr(idmap, d, kname);
}

diff --git a/include/linux/capability.h b/include/linux/capability.h
index eb06d7c6224b..5e7cbf07e3a7 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -229,6 +229,6 @@ int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
struct vfs_caps *cpu_caps);

int cap_convert_nscap(struct mnt_idmap *idmap, struct dentry *dentry,
- const void **ivalue, size_t size);
+ struct vfs_caps *caps);

#endif /* !_LINUX_CAPABILITY_H */
diff --git a/security/commoncap.c b/security/commoncap.c
index 19affcfa3126..4254e5e46024 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -485,27 +485,21 @@ int cap_inode_getsecurity(struct mnt_idmap *idmap,
}

/**
- * rootid_from_xattr - translate root uid of vfs caps
+ * rootid_from_vfs_caps - translate root uid of vfs caps
*
- * @value: vfs caps value which may be modified by this function
- * @size: size of @ivalue
+ * @caps: vfs caps value which may be modified by this function
* @task_ns: user namespace of the caller
+ *
+ * Return the rootid from a v3 fs cap, or the id of root in the task's user
+ * namespace for v1 and v2 fs caps.
*/
-static vfsuid_t rootid_from_xattr(const void *value, size_t size,
- struct user_namespace *task_ns)
+static vfsuid_t rootid_from_vfs_caps(const struct vfs_caps *caps,
+ struct user_namespace *task_ns)
{
- const struct vfs_ns_cap_data *nscap = value;
- uid_t rootid = 0;
-
- if (size == XATTR_CAPS_SZ_3)
- rootid = le32_to_cpu(nscap->rootid);
-
- return VFSUIDT_INIT(make_kuid(task_ns, rootid));
-}
+ if ((caps->magic_etc & VFS_CAP_REVISION_MASK) == VFS_CAP_REVISION_3)
+ return caps->rootid;

-static bool validheader(size_t size, const struct vfs_cap_data *cap)
-{
- return is_v2header(size, cap) || is_v3header(size, cap);
+ return VFSUIDT_INIT(make_kuid(task_ns, 0));
}

/**
@@ -513,11 +507,10 @@ static bool validheader(size_t size, const struct vfs_cap_data *cap)
*
* @idmap: idmap of the mount the inode was found from
* @dentry: used to retrieve inode to check permissions on
- * @ivalue: vfs caps value which may be modified by this function
- * @size: size of @ivalue
+ * @caps: vfs caps which may be modified by this function
*
- * User requested a write of security.capability. If needed, update the
- * xattr to change from v2 to v3, or to fixup the v3 rootid.
+ * User requested a write of security.capability. Check permissions, and if
+ * needed, update the xattr to change from v2 to v3.
*
* If the inode has been found through an idmapped mount the idmap of
* the vfsmount must be passed through @idmap. This function will then
@@ -525,59 +518,39 @@ static bool validheader(size_t size, const struct vfs_cap_data *cap)
* permissions. On non-idmapped mounts or if permission checking is to be
* performed on the raw inode simply pass @nop_mnt_idmap.
*
- * Return: On success, return the new size; on error, return < 0.
+ * Return: On success, return 0; on error, return < 0.
*/
int cap_convert_nscap(struct mnt_idmap *idmap, struct dentry *dentry,
- const void **ivalue, size_t size)
+ struct vfs_caps *caps)
{
- struct vfs_ns_cap_data *nscap;
- uid_t nsrootid;
- const struct vfs_cap_data *cap = *ivalue;
- __u32 magic, nsmagic;
struct inode *inode = d_backing_inode(dentry);
struct user_namespace *task_ns = current_user_ns(),
*fs_ns = inode->i_sb->s_user_ns;
- kuid_t rootid;
vfsuid_t vfsrootid;
- size_t newsize;
+ __u32 revision;

- if (!*ivalue)
- return -EINVAL;
- if (!validheader(size, cap))
+ revision = sansflags(caps->magic_etc);
+ if (revision != VFS_CAP_REVISION_2 && revision != VFS_CAP_REVISION_3)
return -EINVAL;
if (!capable_wrt_inode_uidgid(idmap, inode, CAP_SETFCAP))
return -EPERM;
- if (size == XATTR_CAPS_SZ_2 && (idmap == &nop_mnt_idmap))
+ if (revision == VFS_CAP_REVISION_2 && (idmap == &nop_mnt_idmap))
if (ns_capable(inode->i_sb->s_user_ns, CAP_SETFCAP))
/* user is privileged, just write the v2 */
- return size;
+ return 0;

- vfsrootid = rootid_from_xattr(*ivalue, size, task_ns);
+ vfsrootid = rootid_from_vfs_caps(caps, task_ns);
if (!vfsuid_valid(vfsrootid))
return -EINVAL;

- rootid = from_vfsuid(idmap, fs_ns, vfsrootid);
- if (!uid_valid(rootid))
+ if (!vfsuid_has_fsmapping(idmap, fs_ns, vfsrootid))
return -EINVAL;

- nsrootid = from_kuid(fs_ns, rootid);
- if (nsrootid == -1)
- return -EINVAL;
+ caps->rootid = vfsrootid;
+ caps->magic_etc = VFS_CAP_REVISION_3 |
+ (caps->magic_etc & VFS_CAP_FLAGS_EFFECTIVE);

- newsize = sizeof(struct vfs_ns_cap_data);
- nscap = kmalloc(newsize, GFP_ATOMIC);
- if (!nscap)
- return -ENOMEM;
- nscap->rootid = cpu_to_le32(nsrootid);
- nsmagic = VFS_CAP_REVISION_3;
- magic = le32_to_cpu(cap->magic_etc);
- if (magic & VFS_CAP_FLAGS_EFFECTIVE)
- nsmagic |= VFS_CAP_FLAGS_EFFECTIVE;
- nscap->magic_etc = cpu_to_le32(nsmagic);
- memcpy(&nscap->data, &cap->data, sizeof(__le32) * 2 * VFS_CAP_U32);
-
- *ivalue = nscap;
- return newsize;
+ return 0;
}

/*

--
2.43.0


2024-02-21 23:32:09

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH v2 11/25] security: add hooks for set/get/remove of fscaps

On Wed, Feb 21, 2024 at 4:26 PM Seth Forshee (DigitalOcean)
<[email protected]> wrote:
>
> In preparation for moving fscaps out of the xattr code paths, add new
> security hooks. These hooks are largely needed because common kernel
> code will pass around struct vfs_caps pointers, which EVM will need to
> convert to raw xattr data for verification and updates of its hashes.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> include/linux/lsm_hook_defs.h | 7 +++++
> include/linux/security.h | 33 +++++++++++++++++++++
> security/security.c | 69 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 109 insertions(+)

One minor problem below, but assuming you fix that, this looks okay to me.

Acked-by: Paul Moore <[email protected]>

> diff --git a/security/security.c b/security/security.c
> index 3aaad75c9ce8..0d210da9862c 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -2351,6 +2351,75 @@ int security_inode_remove_acl(struct mnt_idmap *idmap,

..

> +/**
> + * security_inode_get_fscaps() - Check if reading fscaps is allowed
> + * @dentry: file

You are missing an entry for the @idmap parameter.

> + * Check permission before getting fscaps.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_inode_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> +{
> + if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> + return 0;
> + return call_int_hook(inode_get_fscaps, 0, idmap, dentry);
> +}

--
paul-moore.com

2024-02-21 23:39:29

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH v2 12/25] selinux: add hooks for fscaps operations

On Wed, Feb 21, 2024 at 4:25 PM Seth Forshee (DigitalOcean)
<[email protected]> wrote:
>
> Add hooks for set/get/remove fscaps operations which perform the same
> checks as the xattr hooks would have done for XATTR_NAME_CAPS.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> security/selinux/hooks.c | 26 ++++++++++++++++++++++++++
> 1 file changed, 26 insertions(+)
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index a6bf90ace84c..da129a387b34 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -3367,6 +3367,29 @@ static int selinux_inode_removexattr(struct mnt_idmap *idmap,
> return -EACCES;
> }
>
> +static int selinux_inode_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
> +}

The selinux_inode_setxattr() code also has a cap_inode_setxattr()
check which is missing here. Unless you are handling this somewhere
else, I would expect the function above to look similar to
selinux_inode_remove_fscaps(), but obviously tweaked for setting the
fscaps and not removing them.

> +static int selinux_inode_get_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry)
> +{
> + return dentry_has_perm(current_cred(), dentry, FILE__GETATTR);
> +}
> +
> +static int selinux_inode_remove_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry)
> +{
> + int rc = cap_inode_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> + if (rc)
> + return rc;
> +
> + return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
> +}
> +
> static int selinux_path_notify(const struct path *path, u64 mask,
> unsigned int obj_type)
> {
> @@ -7165,6 +7188,9 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
> LSM_HOOK_INIT(inode_set_acl, selinux_inode_set_acl),
> LSM_HOOK_INIT(inode_get_acl, selinux_inode_get_acl),
> LSM_HOOK_INIT(inode_remove_acl, selinux_inode_remove_acl),
> + LSM_HOOK_INIT(inode_set_fscaps, selinux_inode_set_fscaps),
> + LSM_HOOK_INIT(inode_get_fscaps, selinux_inode_get_fscaps),
> + LSM_HOOK_INIT(inode_remove_fscaps, selinux_inode_remove_fscaps),
> LSM_HOOK_INIT(inode_getsecurity, selinux_inode_getsecurity),
> LSM_HOOK_INIT(inode_setsecurity, selinux_inode_setsecurity),
> LSM_HOOK_INIT(inode_listsecurity, selinux_inode_listsecurity),
>
> --
> 2.43.0

--
paul-moore.com

2024-02-21 23:44:10

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH v2 15/25] security: call evm fscaps hooks from generic security hooks

On Wed, Feb 21, 2024 at 4:25 PM Seth Forshee (DigitalOcean)
<[email protected]> wrote:
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> security/security.c | 15 +++++++++++++--
> 1 file changed, 13 insertions(+), 2 deletions(-)

First off, you've got to write *something* for the commit description,
even if it is just a single sentence.

> diff --git a/security/security.c b/security/security.c
> index 0d210da9862c..f515d8430318 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -2365,9 +2365,14 @@ int security_inode_remove_acl(struct mnt_idmap *idmap,
> int security_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> const struct vfs_caps *caps, int flags)
> {
> + int ret;
> +
> if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> return 0;
> - return call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
> + ret = call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
> + if (ret)
> + return ret;
> + return evm_inode_set_fscaps(idmap, dentry, caps, flags);
> }
>
> /**
> @@ -2387,6 +2392,7 @@ void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
> if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> return;
> call_void_hook(inode_post_set_fscaps, idmap, dentry, caps, flags);
> + evm_inode_post_set_fscaps(idmap, dentry, caps, flags);
> }
>
> /**
> @@ -2415,9 +2421,14 @@ int security_inode_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> */
> int security_inode_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> {
> + int ret;
> +
> if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> return 0;
> - return call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
> + ret = call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
> + if (ret)
> + return ret;
> + return evm_inode_remove_fscaps(dentry);
> }

If you take a look at linux-next or the LSM tree's dev branch you'll
see that we've gotten rid of the dedicated IMA and EVM hooks,
promoting both IMA and EVM to "proper" LSMs that leverage the existing
LSM hook infrastructure. In this patchset, and moving forward, please
don't add dedicated IMA/EVM hooks like this, instead register them as
LSM hook implementations with LSM_HOOK_INIT().

--
paul-moore.com

2024-02-22 00:08:17

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 11/25] security: add hooks for set/get/remove of fscaps

On Wed, Feb 21, 2024 at 06:31:42PM -0500, Paul Moore wrote:
> On Wed, Feb 21, 2024 at 4:26 PM Seth Forshee (DigitalOcean)
> <[email protected]> wrote:
> >
> > In preparation for moving fscaps out of the xattr code paths, add new
> > security hooks. These hooks are largely needed because common kernel
> > code will pass around struct vfs_caps pointers, which EVM will need to
> > convert to raw xattr data for verification and updates of its hashes.
> >
> > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > ---
> > include/linux/lsm_hook_defs.h | 7 +++++
> > include/linux/security.h | 33 +++++++++++++++++++++
> > security/security.c | 69 +++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 109 insertions(+)
>
> One minor problem below, but assuming you fix that, this looks okay to me.
>
> Acked-by: Paul Moore <[email protected]>
>
> > diff --git a/security/security.c b/security/security.c
> > index 3aaad75c9ce8..0d210da9862c 100644
> > --- a/security/security.c
> > +++ b/security/security.c
> > @@ -2351,6 +2351,75 @@ int security_inode_remove_acl(struct mnt_idmap *idmap,
>
> ...
>
> > +/**
> > + * security_inode_get_fscaps() - Check if reading fscaps is allowed
> > + * @dentry: file
>
> You are missing an entry for the @idmap parameter.

Fixed, thanks!

2024-02-22 00:10:02

by Casey Schaufler

[permalink] [raw]
Subject: Re: [PATCH v2 13/25] smack: add hooks for fscaps operations

On 2/21/2024 1:24 PM, Seth Forshee (DigitalOcean) wrote:
> Add hooks for set/get/remove fscaps operations which perform the same
> checks as the xattr hooks would have done for XATTR_NAME_CAPS.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> security/smack/smack_lsm.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 71 insertions(+)
>
> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index 0fdbf04cc258..1eaa89dede6b 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -1530,6 +1530,74 @@ static int smack_inode_remove_acl(struct mnt_idmap *idmap,
> return rc;
> }
>
> +/**
> + * smack_inode_set_fscaps - Smack check for setting file capabilities
> + * @mnt_userns: the userns attached to the source mnt for this request
> + * @detry: the object
> + * @caps: the file capabilities
> + * @flags: unused
> + *
> + * Returns 0 if the access is permitted, or an error code otherwise.
> + */
> +static int smack_inode_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + struct smk_audit_info ad;
> + int rc;
> +
> + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
> + smk_ad_setfield_u_fs_path_dentry(&ad, dentry);
> + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad);
> + rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc);
> + return rc;
> +}
> +
> +/**
> + * smack_inode_get_fscaps - Smack check for getting file capabilities
> + * @dentry: the object
> + *
> + * Returns 0 if access is permitted, an error code otherwise
> + */
> +static int smack_inode_get_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry)
> +{
> + struct smk_audit_info ad;
> + int rc;
> +
> + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
> + smk_ad_setfield_u_fs_path_dentry(&ad, dentry);
> +
> + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_READ, &ad);
> + rc = smk_bu_inode(d_backing_inode(dentry), MAY_READ, rc);
> + return rc;
> +}
> +
> +/**
> + * smack_inode_remove_acl - Smack check for removing file capabilities

s/smack_inode_remove_acl/smack_inode_remove_fscaps/

> + * @idmap: idmap of the mnt this request came from
> + * @dentry: the object
> + *
> + * Returns 0 if access is permitted, an error code otherwise
> + */
> +static int smack_inode_remove_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry)
> +{
> + struct smk_audit_info ad;
> + int rc;
> +
> + rc = cap_inode_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> + if (rc != 0)
> + return rc;
> +
> + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
> + smk_ad_setfield_u_fs_path_dentry(&ad, dentry);
> +
> + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad);
> + rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc);
> + return rc;
> +}
> +
> /**
> * smack_inode_getsecurity - get smack xattrs
> * @idmap: idmap of the mount
> @@ -5045,6 +5113,9 @@ static struct security_hook_list smack_hooks[] __ro_after_init = {
> LSM_HOOK_INIT(inode_set_acl, smack_inode_set_acl),
> LSM_HOOK_INIT(inode_get_acl, smack_inode_get_acl),
> LSM_HOOK_INIT(inode_remove_acl, smack_inode_remove_acl),
> + LSM_HOOK_INIT(inode_set_fscaps, smack_inode_set_fscaps),
> + LSM_HOOK_INIT(inode_get_fscaps, smack_inode_get_fscaps),
> + LSM_HOOK_INIT(inode_remove_fscaps, smack_inode_remove_fscaps),
> LSM_HOOK_INIT(inode_getsecurity, smack_inode_getsecurity),
> LSM_HOOK_INIT(inode_setsecurity, smack_inode_setsecurity),
> LSM_HOOK_INIT(inode_listsecurity, smack_inode_listsecurity),
>

2024-02-22 00:11:08

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 12/25] selinux: add hooks for fscaps operations

On Wed, Feb 21, 2024 at 06:38:33PM -0500, Paul Moore wrote:
> On Wed, Feb 21, 2024 at 4:25 PM Seth Forshee (DigitalOcean)
> <[email protected]> wrote:
> >
> > Add hooks for set/get/remove fscaps operations which perform the same
> > checks as the xattr hooks would have done for XATTR_NAME_CAPS.
> >
> > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > ---
> > security/selinux/hooks.c | 26 ++++++++++++++++++++++++++
> > 1 file changed, 26 insertions(+)
> >
> > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> > index a6bf90ace84c..da129a387b34 100644
> > --- a/security/selinux/hooks.c
> > +++ b/security/selinux/hooks.c
> > @@ -3367,6 +3367,29 @@ static int selinux_inode_removexattr(struct mnt_idmap *idmap,
> > return -EACCES;
> > }
> >
> > +static int selinux_inode_set_fscaps(struct mnt_idmap *idmap,
> > + struct dentry *dentry,
> > + const struct vfs_caps *caps, int flags)
> > +{
> > + return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
> > +}
>
> The selinux_inode_setxattr() code also has a cap_inode_setxattr()
> check which is missing here. Unless you are handling this somewhere
> else, I would expect the function above to look similar to
> selinux_inode_remove_fscaps(), but obviously tweaked for setting the
> fscaps and not removing them.

Right, but cap_inode_setxattr() doesn't do anything for fscaps, so I
omitted the call. Unless you think the call should be included in case
cap_inode_setxattr() changes in the future, which is a reasonable
position.

Thanks,
Seth

2024-02-22 00:11:49

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 13/25] smack: add hooks for fscaps operations

On Wed, Feb 21, 2024 at 02:52:50PM -0800, Casey Schaufler wrote:
> > +/**
> > + * smack_inode_remove_acl - Smack check for removing file capabilities
>
> s/smack_inode_remove_acl/smack_inode_remove_fscaps/

Fixed, thanks! I guess you can see where I copied my work from :-)

2024-02-22 00:19:38

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH v2 12/25] selinux: add hooks for fscaps operations

On Wed, Feb 21, 2024 at 7:10 PM Seth Forshee (DigitalOcean)
<[email protected]> wrote:
> On Wed, Feb 21, 2024 at 06:38:33PM -0500, Paul Moore wrote:
> > On Wed, Feb 21, 2024 at 4:25 PM Seth Forshee (DigitalOcean)
> > <[email protected]> wrote:
> > >
> > > Add hooks for set/get/remove fscaps operations which perform the same
> > > checks as the xattr hooks would have done for XATTR_NAME_CAPS.
> > >
> > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > ---
> > > security/selinux/hooks.c | 26 ++++++++++++++++++++++++++
> > > 1 file changed, 26 insertions(+)
> > >
> > > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> > > index a6bf90ace84c..da129a387b34 100644
> > > --- a/security/selinux/hooks.c
> > > +++ b/security/selinux/hooks.c
> > > @@ -3367,6 +3367,29 @@ static int selinux_inode_removexattr(struct mnt_idmap *idmap,
> > > return -EACCES;
> > > }
> > >
> > > +static int selinux_inode_set_fscaps(struct mnt_idmap *idmap,
> > > + struct dentry *dentry,
> > > + const struct vfs_caps *caps, int flags)
> > > +{
> > > + return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
> > > +}
> >
> > The selinux_inode_setxattr() code also has a cap_inode_setxattr()
> > check which is missing here. Unless you are handling this somewhere
> > else, I would expect the function above to look similar to
> > selinux_inode_remove_fscaps(), but obviously tweaked for setting the
> > fscaps and not removing them.
>
> Right, but cap_inode_setxattr() doesn't do anything for fscaps, so I
> omitted the call. Unless you think the call should be included in case
> cap_inode_setxattr() changes in the future, which is a reasonable
> position.

Fair enough, but I'd be a lot happier if you included the call in case
something changes in the future. I worry that omitting the call would
make it easier for us to forget about this if/when things change and
suddenly we have a security issue. If you are morally opposed to
that, at the very least put a comment in selinux_inode_set_fscaps()
about this so we know who to yell at in the future ;)

--
paul-moore.com

2024-02-22 00:20:29

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 15/25] security: call evm fscaps hooks from generic security hooks

On Wed, Feb 21, 2024 at 06:43:43PM -0500, Paul Moore wrote:
> On Wed, Feb 21, 2024 at 4:25 PM Seth Forshee (DigitalOcean)
> <[email protected]> wrote:
> >
> > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > ---
> > security/security.c | 15 +++++++++++++--
> > 1 file changed, 13 insertions(+), 2 deletions(-)
>
> First off, you've got to write *something* for the commit description,
> even if it is just a single sentence.
>
> > diff --git a/security/security.c b/security/security.c
> > index 0d210da9862c..f515d8430318 100644
> > --- a/security/security.c
> > +++ b/security/security.c
> > @@ -2365,9 +2365,14 @@ int security_inode_remove_acl(struct mnt_idmap *idmap,
> > int security_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> > const struct vfs_caps *caps, int flags)
> > {
> > + int ret;
> > +
> > if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> > return 0;
> > - return call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
> > + ret = call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
> > + if (ret)
> > + return ret;
> > + return evm_inode_set_fscaps(idmap, dentry, caps, flags);
> > }
> >
> > /**
> > @@ -2387,6 +2392,7 @@ void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
> > if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> > return;
> > call_void_hook(inode_post_set_fscaps, idmap, dentry, caps, flags);
> > + evm_inode_post_set_fscaps(idmap, dentry, caps, flags);
> > }
> >
> > /**
> > @@ -2415,9 +2421,14 @@ int security_inode_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> > */
> > int security_inode_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> > {
> > + int ret;
> > +
> > if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> > return 0;
> > - return call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
> > + ret = call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
> > + if (ret)
> > + return ret;
> > + return evm_inode_remove_fscaps(dentry);
> > }
>
> If you take a look at linux-next or the LSM tree's dev branch you'll
> see that we've gotten rid of the dedicated IMA and EVM hooks,
> promoting both IMA and EVM to "proper" LSMs that leverage the existing
> LSM hook infrastructure. In this patchset, and moving forward, please
> don't add dedicated IMA/EVM hooks like this, instead register them as
> LSM hook implementations with LSM_HOOK_INIT().

Yeah, I'm aware that work was going on and got applied recently. I've
been assuming this change will go in through the vfs tree though, and I
wasn't sure how you and Al/Christian would want to handle that
dependency between your trees, so I held off on updating based off the
LSM tree. I'm happy to update this for the next round though.

Thanks,
Seth

2024-02-22 00:28:53

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 12/25] selinux: add hooks for fscaps operations

On Wed, Feb 21, 2024 at 07:19:07PM -0500, Paul Moore wrote:
> On Wed, Feb 21, 2024 at 7:10 PM Seth Forshee (DigitalOcean)
> <[email protected]> wrote:
> > On Wed, Feb 21, 2024 at 06:38:33PM -0500, Paul Moore wrote:
> > > On Wed, Feb 21, 2024 at 4:25 PM Seth Forshee (DigitalOcean)
> > > <[email protected]> wrote:
> > > >
> > > > Add hooks for set/get/remove fscaps operations which perform the same
> > > > checks as the xattr hooks would have done for XATTR_NAME_CAPS.
> > > >
> > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > ---
> > > > security/selinux/hooks.c | 26 ++++++++++++++++++++++++++
> > > > 1 file changed, 26 insertions(+)
> > > >
> > > > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> > > > index a6bf90ace84c..da129a387b34 100644
> > > > --- a/security/selinux/hooks.c
> > > > +++ b/security/selinux/hooks.c
> > > > @@ -3367,6 +3367,29 @@ static int selinux_inode_removexattr(struct mnt_idmap *idmap,
> > > > return -EACCES;
> > > > }
> > > >
> > > > +static int selinux_inode_set_fscaps(struct mnt_idmap *idmap,
> > > > + struct dentry *dentry,
> > > > + const struct vfs_caps *caps, int flags)
> > > > +{
> > > > + return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
> > > > +}
> > >
> > > The selinux_inode_setxattr() code also has a cap_inode_setxattr()
> > > check which is missing here. Unless you are handling this somewhere
> > > else, I would expect the function above to look similar to
> > > selinux_inode_remove_fscaps(), but obviously tweaked for setting the
> > > fscaps and not removing them.
> >
> > Right, but cap_inode_setxattr() doesn't do anything for fscaps, so I
> > omitted the call. Unless you think the call should be included in case
> > cap_inode_setxattr() changes in the future, which is a reasonable
> > position.
>
> Fair enough, but I'd be a lot happier if you included the call in case
> something changes in the future. I worry that omitting the call would
> make it easier for us to forget about this if/when things change and
> suddenly we have a security issue. If you are morally opposed to
> that, at the very least put a comment in selinux_inode_set_fscaps()
> about this so we know who to yell at in the future ;)

Makes sense, no objection from me. I'll add it in for v3.

2024-02-22 00:37:34

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH v2 15/25] security: call evm fscaps hooks from generic security hooks

On Wed, Feb 21, 2024 at 7:20 PM Seth Forshee (DigitalOcean)
<[email protected]> wrote:
> On Wed, Feb 21, 2024 at 06:43:43PM -0500, Paul Moore wrote:
> > On Wed, Feb 21, 2024 at 4:25 PM Seth Forshee (DigitalOcean)
> > <[email protected]> wrote:
> > >
> > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > ---
> > > security/security.c | 15 +++++++++++++--
> > > 1 file changed, 13 insertions(+), 2 deletions(-)
> >
> > First off, you've got to write *something* for the commit description,
> > even if it is just a single sentence.
> >
> > > diff --git a/security/security.c b/security/security.c
> > > index 0d210da9862c..f515d8430318 100644
> > > --- a/security/security.c
> > > +++ b/security/security.c
> > > @@ -2365,9 +2365,14 @@ int security_inode_remove_acl(struct mnt_idmap *idmap,
> > > int security_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> > > const struct vfs_caps *caps, int flags)
> > > {
> > > + int ret;
> > > +
> > > if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> > > return 0;
> > > - return call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
> > > + ret = call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
> > > + if (ret)
> > > + return ret;
> > > + return evm_inode_set_fscaps(idmap, dentry, caps, flags);
> > > }
> > >
> > > /**
> > > @@ -2387,6 +2392,7 @@ void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
> > > if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> > > return;
> > > call_void_hook(inode_post_set_fscaps, idmap, dentry, caps, flags);
> > > + evm_inode_post_set_fscaps(idmap, dentry, caps, flags);
> > > }
> > >
> > > /**
> > > @@ -2415,9 +2421,14 @@ int security_inode_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> > > */
> > > int security_inode_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> > > {
> > > + int ret;
> > > +
> > > if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> > > return 0;
> > > - return call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
> > > + ret = call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
> > > + if (ret)
> > > + return ret;
> > > + return evm_inode_remove_fscaps(dentry);
> > > }
> >
> > If you take a look at linux-next or the LSM tree's dev branch you'll
> > see that we've gotten rid of the dedicated IMA and EVM hooks,
> > promoting both IMA and EVM to "proper" LSMs that leverage the existing
> > LSM hook infrastructure. In this patchset, and moving forward, please
> > don't add dedicated IMA/EVM hooks like this, instead register them as
> > LSM hook implementations with LSM_HOOK_INIT().
>
> Yeah, I'm aware that work was going on and got applied recently. I've
> been assuming this change will go in through the vfs tree though, and I
> wasn't sure how you and Al/Christian would want to handle that
> dependency between your trees, so I held off on updating based off the
> LSM tree. I'm happy to update this for the next round though.

Okay, good, I just wanted to make sure you were aware of the changes.
Since the merge window is only a couple of weeks away I'm guessing
this isn't something we'll need to worry about in Linus' tree as the
LSM/IMA/EVM changes are slated to go up during the next merge window
and I'm guessing this will likely go in after that, targeting the
following merge window at the earliest.

--
paul-moore.com

2024-02-22 15:25:37

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 07/25] capability: provide a helper for converting vfs_caps to xattr for userspace

On Wed, Feb 21, 2024 at 03:24:38PM -0600, Seth Forshee (DigitalOcean) wrote:
> cap_inode_getsecurity() implements a handful of policies for capability
> xattrs read by userspace:
>
> - It returns EINVAL if the on-disk capability is in v1 format.
>
> - It masks off all bits in magic_etc except for the version and
> VFS_CAP_FLAGS_EFFECTIVE.
>
> - v3 capabilities are converted to v2 format if the rootid returned to
> userspace would be 0 or if the rootid corresponds to root in an
> ancestor user namespace.
>
> - It returns EOVERFLOW for a v3 capability whose rootid does not map to
> a valid id in current_user_ns() or to root in an ancestor namespace.
>
> These policies must be maintained when converting vfs_caps to an xattr
> for userspace. Provide a vfs_caps_to_user_xattr() helper which will
> enforce these policies.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---

Looks good,
Reviewed-by: Christian Brauner <[email protected]>

2024-02-22 15:28:11

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 00/25] fs: use type-safe uid representation for filesystem capabilities

On Wed, Feb 21, 2024 at 03:24:31PM -0600, Seth Forshee (DigitalOcean) wrote:
> This series converts filesystem capabilities from passing around raw
> xattr data to using a kernel-internal representation with type safe
> uids, similar to the conversion done previously for posix ACLs.
> Currently fscaps representations in the kernel have two different
> instances of unclear or confused types:
>
> - fscaps are generally passed around in the raw xattr form, with the
> rootid sometimes containing the user uid value and at other times
> containing the filesystem value.
> - The existing kernel-internal representation of fscaps,
> cpu_vfs_cap_data, uses the kuid_t type, but the value stored is
> actually a vfsuid.
>
> This series eliminates this confusion by converting the xattr data to
> the kernel representation near the userspace and filesystem boundaries,
> using the kernel representation within the vfs and commoncap code. The
> internal representation is renamed to vfs_caps to reflect this broader
> use, and the rootid is changed to a vfsuid_t to correctly identify the
> type of uid which it contains.
>
> New vfs interfaces are added to allow for getting and setting fscaps
> using the kernel representation. This requires the addition of new inode
> operations to allow overlayfs to handle fscaps properly; all other
> filesystems fall back to a generic implementation. The top-level vfs
> xattr interfaces will now reject fscaps xattrs, though the lower-level
> interfaces continue to accept them for reading and writing the raw xattr
> data.
>
> Based on previous feedback, new security hooks are added for fscaps
> operations. These are really only needed for EVM, and the selinux and
> smack implementations just peform the same operations that the
> equivalent xattr hooks would have done. Note too that this has not yet
> been updated based on the changes to make EVM into an LSM.
>
> The remainder of the changes are preparatory work, addition of helpers
> for converting between the xattr and kernel fscaps representation, and
> various updates to use the kernel representation and new interfaces.

I still think that the generic_{get,set,remove}_fscaps() helpers falling
back to plain *vfs_*xattr() calls is a hackish. So ideally I'd like to
see this killed in a follow-up series and make all fses that support
them use the inode operation.

>
> I have tested this code with xfstests, ltp, libcap2, and libcap-ng with
> no regressions found.

+1

2024-02-22 15:31:45

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 06/25] capability: provide helpers for converting between xattrs and vfs_caps

On Wed, Feb 21, 2024 at 03:24:37PM -0600, Seth Forshee (DigitalOcean) wrote:
> To pass around vfs_caps instead of raw xattr data we will need to
> convert between the two representations near userspace and disk
> boundaries. We already convert xattrs from disks to vfs_caps, so move
> that code into a helper, and change get_vfs_caps_from_disk() to use the
> helper.
>
> When converting vfs_caps to xattrs we have different considerations
> depending on the destination of the xattr data. For xattrs which will be
> written to disk we need to reject the xattr if the rootid does not map
> into the filesystem's user namespace, whereas xattrs read by userspace
> may need to undergo a conversion from v3 to v2 format when the rootid
> does not map. So this helper is split into an internal and an external
> interface. The internal interface does not return an error if the rootid
> has no mapping in the target user namespace and will be used for
> conversions targeting userspace. The external interface returns
> EOVERFLOW if the rootid has no mapping and will be used for all other
> conversions.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> include/linux/capability.h | 10 ++
> security/commoncap.c | 228 +++++++++++++++++++++++++++++++++++----------
> 2 files changed, 187 insertions(+), 51 deletions(-)
>
> diff --git a/include/linux/capability.h b/include/linux/capability.h
> index eb46d346bbbc..a0893ac4664b 100644
> --- a/include/linux/capability.h
> +++ b/include/linux/capability.h
> @@ -209,6 +209,16 @@ static inline bool checkpoint_restore_ns_capable(struct user_namespace *ns)
> ns_capable(ns, CAP_SYS_ADMIN);
> }
>
> +/* helpers to convert between xattr and in-kernel representations */
> +int vfs_caps_from_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *src_userns,
> + struct vfs_caps *vfs_caps,
> + const void *data, size_t size);
> +ssize_t vfs_caps_to_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *dest_userns,
> + const struct vfs_caps *vfs_caps,
> + void *data, size_t size);
> +
> /* audit system wants to get cap info from files as well */
> int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
> const struct dentry *dentry,
> diff --git a/security/commoncap.c b/security/commoncap.c
> index a0b5c9740759..7531c9634997 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
> @@ -619,54 +619,41 @@ static inline int bprm_caps_from_vfs_caps(struct vfs_caps *caps,
> }
>
> /**
> - * get_vfs_caps_from_disk - retrieve vfs caps from disk
> + * vfs_caps_from_xattr - convert raw caps xattr data to vfs_caps
> *
> - * @idmap: idmap of the mount the inode was found from
> - * @dentry: dentry from which @inode is retrieved
> - * @cpu_caps: vfs capabilities
> + * @idmap: idmap of the mount the inode was found from
> + * @src_userns: user namespace for ids in xattr data
> + * @vfs_caps: destination buffer for vfs_caps data
> + * @data: rax xattr caps data
> + * @size: size of xattr data
> *
> - * Extract the on-exec-apply capability sets for an executable file.
> + * Converts a raw security.capability xattr into the kernel-internal
> + * capabilities format.
> *
> - * If the inode has been found through an idmapped mount the idmap of
> - * the vfsmount must be passed through @idmap. This function will then
> - * take care to map the inode according to @idmap before checking
> - * permissions. On non-idmapped mounts or if permission checking is to be
> - * performed on the raw inode simply pass @nop_mnt_idmap.
> + * If the xattr is being read or written through an idmapped mount the
> + * idmap of the vfsmount must be passed through @idmap. This function
> + * will then take care to map the rootid according to @idmap.
> + *
> + * Return: On success, return 0; on error, return < 0.
> */
> -int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
> - const struct dentry *dentry,
> - struct vfs_caps *cpu_caps)
> +int vfs_caps_from_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *src_userns,
> + struct vfs_caps *vfs_caps,
> + const void *data, size_t size)
> {
> - struct inode *inode = d_backing_inode(dentry);
> __u32 magic_etc;
> - int size;
> - struct vfs_ns_cap_data data, *nscaps = &data;
> - struct vfs_cap_data *caps = (struct vfs_cap_data *) &data;
> + const struct vfs_ns_cap_data *ns_caps = data;
> + struct vfs_cap_data *caps = (struct vfs_cap_data *)ns_caps;
> kuid_t rootkuid;
> - vfsuid_t rootvfsuid;
> - struct user_namespace *fs_ns;
> -
> - memset(cpu_caps, 0, sizeof(struct vfs_caps));
> -
> - if (!inode)
> - return -ENODATA;
>
> - fs_ns = inode->i_sb->s_user_ns;
> - size = __vfs_getxattr((struct dentry *)dentry, inode,
> - XATTR_NAME_CAPS, &data, XATTR_CAPS_SZ);
> - if (size == -ENODATA || size == -EOPNOTSUPP)
> - /* no data, that's ok */
> - return -ENODATA;
> -
> - if (size < 0)
> - return size;
> + memset(vfs_caps, 0, sizeof(*vfs_caps));
>
> if (size < sizeof(magic_etc))
> return -EINVAL;
>
> - cpu_caps->magic_etc = magic_etc = le32_to_cpu(caps->magic_etc);
> + vfs_caps->magic_etc = magic_etc = le32_to_cpu(caps->magic_etc);
>
> - rootkuid = make_kuid(fs_ns, 0);
> + rootkuid = make_kuid(src_userns, 0);
> switch (magic_etc & VFS_CAP_REVISION_MASK) {
> case VFS_CAP_REVISION_1:
> if (size != XATTR_CAPS_SZ_1)
> @@ -679,39 +666,178 @@ int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
> case VFS_CAP_REVISION_3:
> if (size != XATTR_CAPS_SZ_3)
> return -EINVAL;
> - rootkuid = make_kuid(fs_ns, le32_to_cpu(nscaps->rootid));
> + rootkuid = make_kuid(src_userns, le32_to_cpu(ns_caps->rootid));
> break;
>
> default:
> return -EINVAL;
> }
>
> - rootvfsuid = make_vfsuid(idmap, fs_ns, rootkuid);
> - if (!vfsuid_valid(rootvfsuid))
> - return -ENODATA;
> + vfs_caps->rootid = make_vfsuid(idmap, src_userns, rootkuid);
> + if (!vfsuid_valid(vfs_caps->rootid))
> + return -EOVERFLOW;
>
> - /* Limit the caps to the mounter of the filesystem
> - * or the more limited uid specified in the xattr.
> + vfs_caps->permitted.val = le32_to_cpu(caps->data[0].permitted);
> + vfs_caps->inheritable.val = le32_to_cpu(caps->data[0].inheritable);
> +
> + /*
> + * Rev1 had just a single 32-bit word, later expanded
> + * to a second one for the high bits
> */
> - if (!rootid_owns_currentns(rootvfsuid))
> - return -ENODATA;
> + if ((magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
> + vfs_caps->permitted.val += (u64)le32_to_cpu(caps->data[1].permitted) << 32;
> + vfs_caps->inheritable.val += (u64)le32_to_cpu(caps->data[1].inheritable) << 32;

That + makes this even more difficult to read. This should be rewritten.

> + }
> +
> + vfs_caps->permitted.val &= CAP_VALID_MASK;
> + vfs_caps->inheritable.val &= CAP_VALID_MASK;
> +
> + return 0;
> +}
> +
> +/*
> + * Inner implementation of vfs_caps_to_xattr() which does not return an
> + * error if the rootid does not map into @dest_userns.
> + */
> +static ssize_t __vfs_caps_to_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *dest_userns,
> + const struct vfs_caps *vfs_caps,
> + void *data, size_t size)
> +{
> + struct vfs_ns_cap_data *ns_caps = data;
> + struct vfs_cap_data *caps = (struct vfs_cap_data *)ns_caps;
> + kuid_t rootkuid;
> + uid_t rootid;
> +
> + memset(ns_caps, 0, size);
> +
> + rootid = 0;
> + switch (vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) {
> + case VFS_CAP_REVISION_1:
> + if (size < XATTR_CAPS_SZ_1)
> + return -EINVAL;
> + size = XATTR_CAPS_SZ_1;
> + break;
> + case VFS_CAP_REVISION_2:
> + if (size < XATTR_CAPS_SZ_2)
> + return -EINVAL;
> + size = XATTR_CAPS_SZ_2;
> + break;
> + case VFS_CAP_REVISION_3:
> + if (size < XATTR_CAPS_SZ_3)
> + return -EINVAL;
> + size = XATTR_CAPS_SZ_3;
> + rootkuid = from_vfsuid(idmap, dest_userns, vfs_caps->rootid);
> + rootid = from_kuid(dest_userns, rootkuid);
> + ns_caps->rootid = cpu_to_le32(rootid);
> + break;
>
> - cpu_caps->permitted.val = le32_to_cpu(caps->data[0].permitted);
> - cpu_caps->inheritable.val = le32_to_cpu(caps->data[0].inheritable);
> + default:
> + return -EINVAL;
> + }
> +
> + caps->magic_etc = cpu_to_le32(vfs_caps->magic_etc);
> +
> + caps->data[0].permitted = cpu_to_le32(lower_32_bits(vfs_caps->permitted.val));
> + caps->data[0].inheritable = cpu_to_le32(lower_32_bits(vfs_caps->inheritable.val));
>
> /*
> * Rev1 had just a single 32-bit word, later expanded
> * to a second one for the high bits
> */
> - if ((magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
> - cpu_caps->permitted.val += (u64)le32_to_cpu(caps->data[1].permitted) << 32;
> - cpu_caps->inheritable.val += (u64)le32_to_cpu(caps->data[1].inheritable) << 32;
> + if ((vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
> + caps->data[1].permitted =
> + cpu_to_le32(upper_32_bits(vfs_caps->permitted.val));
> + caps->data[1].inheritable =
> + cpu_to_le32(upper_32_bits(vfs_caps->inheritable.val));
> }
>
> - cpu_caps->permitted.val &= CAP_VALID_MASK;
> - cpu_caps->inheritable.val &= CAP_VALID_MASK;
> + return size;
> +}
> +
> +
> +/**
> + * vfs_caps_to_xattr - convert vfs_caps to raw caps xattr data
> + *
> + * @idmap: idmap of the mount the inode was found from
> + * @dest_userns: user namespace for ids in xattr data
> + * @vfs_caps: source vfs_caps data
> + * @data: destination buffer for rax xattr caps data
> + * @size: size of the @data buffer
> + *
> + * Converts a kernel-internal capability into the raw security.capability
> + * xattr format.
> + *
> + * If the xattr is being read or written through an idmapped mount the
> + * idmap of the vfsmount must be passed through @idmap. This function
> + * will then take care to map the rootid according to @idmap.
> + *
> + * Return: On success, return the size of the xattr data. On error,
> + * return < 0.
> + */
> +ssize_t vfs_caps_to_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *dest_userns,
> + const struct vfs_caps *vfs_caps,
> + void *data, size_t size)
> +{
> + struct vfs_ns_cap_data *caps = data;
> + int ret;

This should very likely be ssize_t ret.

> +
> + ret = __vfs_caps_to_xattr(idmap, dest_userns, vfs_caps, data, size);
> + if (ret > 0 &&
> + (vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) == VFS_CAP_REVISION_3 &&
> + le32_to_cpu(caps->rootid) == (uid_t)-1)
> + return -EOVERFLOW;
> + return ret;
> +}
> +
> +/**
> + * get_vfs_caps_from_disk - retrieve vfs caps from disk
> + *
> + * @idmap: idmap of the mount the inode was found from
> + * @dentry: dentry from which @inode is retrieved
> + * @cpu_caps: vfs capabilities
> + *
> + * Extract the on-exec-apply capability sets for an executable file.
> + *
> + * If the inode has been found through an idmapped mount the idmap of
> + * the vfsmount must be passed through @idmap. This function will then
> + * take care to map the inode according to @idmap before checking
> + * permissions. On non-idmapped mounts or if permission checking is to be
> + * performed on the raw inode simply pass @nop_mnt_idmap.
> + */
> +int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
> + const struct dentry *dentry,
> + struct vfs_caps *cpu_caps)
> +{
> + struct inode *inode = d_backing_inode(dentry);
> + int size, ret;
> + struct vfs_ns_cap_data data, *nscaps = &data;
> +
> + if (!inode)
> + return -ENODATA;
>
> - cpu_caps->rootid = rootvfsuid;
> + size = __vfs_getxattr((struct dentry *)dentry, inode,
> + XATTR_NAME_CAPS, &data, XATTR_CAPS_SZ);
> + if (size == -ENODATA || size == -EOPNOTSUPP)
> + /* no data, that's ok */
> + return -ENODATA;
> +
> + if (size < 0)
> + return size;
> +
> + ret = vfs_caps_from_xattr(idmap, inode->i_sb->s_user_ns,
> + cpu_caps, nscaps, size);
> + if (ret == -EOVERFLOW)
> + return -ENODATA;
> + if (ret)
> + return ret;
> +
> + /* Limit the caps to the mounter of the filesystem
> + * or the more limited uid specified in the xattr.
> + */
> + if (!rootid_owns_currentns(cpu_caps->rootid))
> + return -ENODATA;
>
> return 0;
> }
>
> --
> 2.43.0
>

2024-02-22 15:38:23

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 06/25] capability: provide helpers for converting between xattrs and vfs_caps

On Thu, Feb 22, 2024 at 04:20:08PM +0100, Christian Brauner wrote:
> > + if ((magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
> > + vfs_caps->permitted.val += (u64)le32_to_cpu(caps->data[1].permitted) << 32;
> > + vfs_caps->inheritable.val += (u64)le32_to_cpu(caps->data[1].inheritable) << 32;
>
> That + makes this even more difficult to read. This should be rewritten.

Do you meant that you would prefer |= to +=, or do you have something
else in mind?

Note though that this is code that I didn't change, just moved.
Generally I tried to avoid changing code if it wasn't necessary for the
aims of this series.

> > +ssize_t vfs_caps_to_xattr(struct mnt_idmap *idmap,
> > + struct user_namespace *dest_userns,
> > + const struct vfs_caps *vfs_caps,
> > + void *data, size_t size)
> > +{
> > + struct vfs_ns_cap_data *caps = data;
> > + int ret;
>
> This should very likely be ssize_t ret.

Indeed, I'll fix that.

2024-02-22 16:51:19

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 00/25] fs: use type-safe uid representation for filesystem capabilities

On Thu, Feb 22, 2024 at 04:27:50PM +0100, Christian Brauner wrote:
> I still think that the generic_{get,set,remove}_fscaps() helpers falling
> back to plain *vfs_*xattr() calls is a hackish. So ideally I'd like to
> see this killed in a follow-up series and make all fses that support
> them use the inode operation.

Right, you brought this up last time, and I probably should have
mentioned it in the cover letter. I haven't seriously looked at doing
this yet, in large part because I got interrupted from working on this
and felt like v2 patches were long overdue (and you said you were fine
with doing it as a follow-up series anyway). But I will have a look to
see if it makes sense to change that in the next version of this series.

2024-02-23 08:08:31

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 06/25] capability: provide helpers for converting between xattrs and vfs_caps

On Thu, Feb 22, 2024 at 09:38:04AM -0600, Seth Forshee (DigitalOcean) wrote:
> On Thu, Feb 22, 2024 at 04:20:08PM +0100, Christian Brauner wrote:
> > > + if ((magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
> > > + vfs_caps->permitted.val += (u64)le32_to_cpu(caps->data[1].permitted) << 32;
> > > + vfs_caps->inheritable.val += (u64)le32_to_cpu(caps->data[1].inheritable) << 32;
> >
> > That + makes this even more difficult to read. This should be rewritten.
>
> Do you meant that you would prefer |= to +=, or do you have something

Yes.

2024-02-23 08:10:46

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 08/25] xattr: add is_fscaps_xattr() helper

On Wed, Feb 21, 2024 at 03:24:39PM -0600, Seth Forshee (DigitalOcean) wrote:
> Add a helper to determine if an xattr time is XATTR_NAME_CAPS instead of
> open-coding a string comparision.
>
> Suggested-by: Amir Goldstein <[email protected]>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---

Looks good,
Reviewed-by: Christian Brauner <[email protected]>

2024-02-23 08:11:12

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 09/25] commoncap: use is_fscaps_xattr()

On Wed, Feb 21, 2024 at 03:24:40PM -0600, Seth Forshee (DigitalOcean) wrote:
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---

Looks good,
Reviewed-by: Christian Brauner <[email protected]>

2024-02-23 08:11:12

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 10/25] xattr: use is_fscaps_xattr()

On Wed, Feb 21, 2024 at 03:24:41PM -0600, Seth Forshee (DigitalOcean) wrote:
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---

Looks good,
Reviewed-by: Christian Brauner <[email protected]>

2024-02-23 08:24:33

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 11/25] security: add hooks for set/get/remove of fscaps

On Wed, Feb 21, 2024 at 03:24:42PM -0600, Seth Forshee (DigitalOcean) wrote:
> In preparation for moving fscaps out of the xattr code paths, add new
> security hooks. These hooks are largely needed because common kernel
> code will pass around struct vfs_caps pointers, which EVM will need to
> convert to raw xattr data for verification and updates of its hashes.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---

Looks good,
Reviewed-by: Christian Brauner <[email protected]>

2024-02-23 08:32:35

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 16/25] fs: add inode operations to get/set/remove fscaps

On Wed, Feb 21, 2024 at 03:24:47PM -0600, Seth Forshee (DigitalOcean) wrote:
> Add inode operations for getting, setting and removing filesystem
> capabilities rather than passing around raw xattr data. This provides
> better type safety for ids contained within xattrs.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---

Looks good,
Reviewed-by: Christian Brauner <[email protected]>

2024-02-23 08:34:01

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 17/25] fs: add vfs_get_fscaps()

On Wed, Feb 21, 2024 at 03:24:48PM -0600, Seth Forshee (DigitalOcean) wrote:
> Provide a type-safe interface for retrieving filesystem capabilities and
> a generic implementation suitable for most filesystems. Also add an
> internal interface, vfs_get_fscaps_nosec(), which skips security checks
> for later use from the capability code.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> fs/xattr.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/fs.h | 4 ++++
> 2 files changed, 68 insertions(+)
>
> diff --git a/fs/xattr.c b/fs/xattr.c
> index 06290e4ebc03..10d1b1f78fc2 100644
> --- a/fs/xattr.c
> +++ b/fs/xattr.c
> @@ -181,6 +181,70 @@ xattr_supports_user_prefix(struct inode *inode)
> }
> EXPORT_SYMBOL(xattr_supports_user_prefix);
>
> +static int generic_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + struct vfs_caps *caps)
> +{
> + struct inode *inode = d_inode(dentry);
> + struct vfs_ns_cap_data nscaps;
> + int ret;
> +
> + ret = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, &nscaps, sizeof(nscaps));
> +
> + if (ret >= 0)
> + ret = vfs_caps_from_xattr(idmap, i_user_ns(inode), caps, &nscaps, ret);
> +
> + return ret;
> +}
> +
> +/**
> + * vfs_get_fscaps_nosec - get filesystem capabilities without security checks
> + * @idmap: idmap of the mount the inode was found from
> + * @dentry: the dentry from which to get filesystem capabilities
> + * @caps: storage in which to return the filesystem capabilities
> + *
> + * This function gets the filesystem capabilities for the dentry and returns
> + * them in @caps. It does not perform security checks.
> + *
> + * Return: 0 on success, a negative errno on error.
> + */
> +int vfs_get_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry,
> + struct vfs_caps *caps)
> +{
> + struct inode *inode = d_inode(dentry);
> +
> + if (inode->i_op->get_fscaps)
> + return inode->i_op->get_fscaps(idmap, dentry, caps);
> + return generic_get_fscaps(idmap, dentry, caps);
> +}
> +
> +/**
> + * vfs_get_fscaps - get filesystem capabilities
> + * @idmap: idmap of the mount the inode was found from
> + * @dentry: the dentry from which to get filesystem capabilities
> + * @caps: storage in which to return the filesystem capabilities
> + *
> + * This function gets the filesystem capabilities for the dentry and returns
> + * them in @caps.
> + *
> + * Return: 0 on success, a negative errno on error.
> + */
> +int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + struct vfs_caps *caps)
> +{
> + int error;
> +
> + /*
> + * The VFS has no restrictions on reading security.* xattrs, so
> + * xattr_permission() isn't needed. Only LSMs get a say.
> + */
> + error = security_inode_get_fscaps(idmap, dentry);
> + if (error)
> + return error;
> +
> + return vfs_get_fscaps_nosec(idmap, dentry, caps);
> +}
> +EXPORT_SYMBOL(vfs_get_fscaps);
> +
> int
> __vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
> struct inode *inode, const char *name, const void *value,
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 89163e0f7aad..d7cd2467e1ea 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -2116,6 +2116,10 @@ extern int vfs_dedupe_file_range(struct file *file,
> extern loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,
> struct file *dst_file, loff_t dst_pos,
> loff_t len, unsigned int remap_flags);
> +extern int vfs_get_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry,
> + struct vfs_caps *caps);
> +extern int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + struct vfs_caps *caps);

Please drop the externs. Other than my usual complaing about this
falling back to the legacy vfs_*xattr() interfaces,
Reviewed-by: Christian Brauner <[email protected]>

2024-02-23 08:39:24

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 18/25] fs: add vfs_set_fscaps()

On Wed, Feb 21, 2024 at 03:24:49PM -0600, Seth Forshee (DigitalOcean) wrote:
> Provide a type-safe interface for setting filesystem capabilities and a
> generic implementation suitable for most filesystems.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> fs/xattr.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/fs.h | 2 ++
> 2 files changed, 81 insertions(+)
>
> diff --git a/fs/xattr.c b/fs/xattr.c
> index 10d1b1f78fc2..96de43928a51 100644
> --- a/fs/xattr.c
> +++ b/fs/xattr.c
> @@ -245,6 +245,85 @@ int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> }
> EXPORT_SYMBOL(vfs_get_fscaps);
>
> +static int generic_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int setxattr_flags)
> +{
> + struct inode *inode = d_inode(dentry);
> + struct vfs_ns_cap_data nscaps;
> + int size;

ssize_t, I believe.

> +
> + size = vfs_caps_to_xattr(idmap, i_user_ns(inode), caps,
> + &nscaps, sizeof(nscaps));
> + if (size < 0)
> + return size;
> +
> + return __vfs_setxattr_noperm(idmap, dentry, XATTR_NAME_CAPS,
> + &nscaps, size, setxattr_flags);
> +}
> +
> +/**
> + * vfs_set_fscaps - set filesystem capabilities
> + * @idmap: idmap of the mount the inode was found from
> + * @dentry: the dentry on which to set filesystem capabilities
> + * @caps: the filesystem capabilities to be written
> + * @setxattr_flags: setxattr flags to use when writing the capabilities xattr
> + *
> + * This function writes the supplied filesystem capabilities to the dentry.
> + *
> + * Return: 0 on success, a negative errno on error.
> + */
> +int vfs_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int setxattr_flags)
> +{
> + struct inode *inode = d_inode(dentry);
> + struct inode *delegated_inode = NULL;
> + int error;
> +
> +retry_deleg:
> + inode_lock(inode);
> +
> + error = xattr_permission(idmap, inode, XATTR_NAME_CAPS, MAY_WRITE);
> + if (error)
> + goto out_inode_unlock;

I think this should be

/*
* We only care about restrictions the inode struct itself places upon
* us otherwise fscaps aren't subject to any VFS restrictions.
*/
error = may_write_xattr(idmap, inode);
if (error)
goto out_inode_unlock;

which is a 1:1 copy of what POSIX ACLs do?

> + error = security_inode_set_fscaps(idmap, dentry, caps, setxattr_flags);
> + if (error)
> + goto out_inode_unlock;
> +
> + error = try_break_deleg(inode, &delegated_inode);
> + if (error)
> + goto out_inode_unlock;
> +
> + if (inode->i_opflags & IOP_XATTR) {

Fwiw, I think that if we move fscaps off of xattr handlers completely
this can go away and we can simply rely on ->{g,s}et_fscaps() being
implemented. But again, that can be in a follow-up series.

> + if (inode->i_op->set_fscaps)
> + error = inode->i_op->set_fscaps(idmap, dentry, caps,
> + setxattr_flags);
> + else
> + error = generic_set_fscaps(idmap, dentry, caps,
> + setxattr_flags);
> + if (!error) {
> + fsnotify_xattr(dentry);
> + security_inode_post_set_fscaps(idmap, dentry, caps,
> + setxattr_flags);
> + }
> + } else if (unlikely(is_bad_inode(inode))) {
> + error = -EIO;
> + } else {
> + error = -EOPNOTSUPP;
> + }
> +
> +out_inode_unlock:
> + inode_unlock(inode);
> +
> + if (delegated_inode) {
> + error = break_deleg_wait(&delegated_inode);
> + if (!error)
> + goto retry_deleg;
> + }
> +
> + return error;
> +}
> +EXPORT_SYMBOL(vfs_set_fscaps);
> +
> int
> __vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
> struct inode *inode, const char *name, const void *value,
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index d7cd2467e1ea..4f5d7ed44644 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -2120,6 +2120,8 @@ extern int vfs_get_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry,
> struct vfs_caps *caps);
> extern int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> struct vfs_caps *caps);
> +extern int vfs_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int setxattr_flags);

Please drop the extern.

2024-02-23 08:40:27

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 19/25] fs: add vfs_remove_fscaps()

On Wed, Feb 21, 2024 at 03:24:50PM -0600, Seth Forshee (DigitalOcean) wrote:
> Provide a type-safe interface for removing filesystem capabilities and a
> generic implementation suitable for most filesystems. Also add an
> internal interface, vfs_remove_fscaps_nosec(), which is called with the
> inode lock held and skips security checks for later use from the
> capability code.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> fs/xattr.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/fs.h | 2 ++
> 2 files changed, 83 insertions(+)
>
> diff --git a/fs/xattr.c b/fs/xattr.c
> index 96de43928a51..8b0f7384cbc9 100644
> --- a/fs/xattr.c
> +++ b/fs/xattr.c
> @@ -324,6 +324,87 @@ int vfs_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> }
> EXPORT_SYMBOL(vfs_set_fscaps);
>
> +static int generic_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> +{
> + return __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> +}
> +
> +/**
> + * vfs_remove_fscaps_nosec - remove filesystem capabilities without
> + * security checks
> + * @idmap: idmap of the mount the inode was found from
> + * @dentry: the dentry from which to remove filesystem capabilities
> + *
> + * This function removes any filesystem capabilities from the specified
> + * dentry. Does not perform any security checks, and callers must hold the
> + * inode lock.
> + *
> + * Return: 0 on success, a negative errno on error.
> + */
> +int vfs_remove_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry)
> +{
> + struct inode *inode = dentry->d_inode;
> + int error;
> +
> + if (inode->i_op->set_fscaps)
> + error = inode->i_op->set_fscaps(idmap, dentry, NULL,
> + XATTR_REPLACE);
> + else
> + error = generic_remove_fscaps(idmap, dentry);
> +
> + return error;
> +}
> +
> +/**
> + * vfs_remove_fscaps - remove filesystem capabilities
> + * @idmap: idmap of the mount the inode was found from
> + * @dentry: the dentry from which to remove filesystem capabilities
> + *
> + * This function removes any filesystem capabilities from the specified
> + * dentry.
> + *
> + * Return: 0 on success, a negative errno on error.
> + */
> +int vfs_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> +{
> + struct inode *inode = dentry->d_inode;
> + struct inode *delegated_inode = NULL;
> + int error;
> +
> +retry_deleg:
> + inode_lock(inode);
> +
> + error = xattr_permission(idmap, inode, XATTR_NAME_CAPS, MAY_WRITE);
> + if (error)
> + goto out_inode_unlock;

Should also use may_write_xattr() instead of xattr_permission() if
possible.

> +
> + error = security_inode_remove_fscaps(idmap, dentry);
> + if (error)
> + goto out_inode_unlock;
> +
> + error = try_break_deleg(inode, &delegated_inode);
> + if (error)
> + goto out_inode_unlock;
> +
> + error = vfs_remove_fscaps_nosec(idmap, dentry);
> + if (!error) {
> + fsnotify_xattr(dentry);
> + evm_inode_post_remove_fscaps(dentry);
> + }
> +
> +out_inode_unlock:
> + inode_unlock(inode);
> +
> + if (delegated_inode) {
> + error = break_deleg_wait(&delegated_inode);
> + if (!error)
> + goto retry_deleg;
> + }
> +
> + return error;
> +}
> +EXPORT_SYMBOL(vfs_remove_fscaps);
> +
> int
> __vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
> struct inode *inode, const char *name, const void *value,
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 4f5d7ed44644..c07427d2fc71 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -2122,6 +2122,8 @@ extern int vfs_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> struct vfs_caps *caps);
> extern int vfs_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> const struct vfs_caps *caps, int setxattr_flags);
> +extern int vfs_remove_fscaps_nosec(struct mnt_idmap *idmap, struct dentry *dentry);
> +extern int vfs_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry);

Please drop the extern.

2024-02-23 09:10:23

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 20/25] ovl: add fscaps handlers

On Wed, Feb 21, 2024 at 03:24:51PM -0600, Seth Forshee (DigitalOcean) wrote:
> Add handlers which read fs caps from the lower or upper filesystem and
> write/remove fs caps to the upper filesystem, performing copy-up as
> necessary.
>
> While fscaps only really make sense on regular files, the general policy
> is to allow most xattr namespaces on all different inode types, so
> fscaps handlers are installed in the inode operations for all types of
> inodes.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> fs/overlayfs/dir.c | 2 ++
> fs/overlayfs/inode.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++
> fs/overlayfs/overlayfs.h | 5 ++++
> 3 files changed, 79 insertions(+)
>
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index 0f8b4a719237..4ff360fe10c9 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -1307,6 +1307,8 @@ const struct inode_operations ovl_dir_inode_operations = {
> .get_inode_acl = ovl_get_inode_acl,
> .get_acl = ovl_get_acl,
> .set_acl = ovl_set_acl,
> + .get_fscaps = ovl_get_fscaps,
> + .set_fscaps = ovl_set_fscaps,
> .update_time = ovl_update_time,
> .fileattr_get = ovl_fileattr_get,
> .fileattr_set = ovl_fileattr_set,
> diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
> index c63b31a460be..7a8978ea6fe1 100644
> --- a/fs/overlayfs/inode.c
> +++ b/fs/overlayfs/inode.c
> @@ -568,6 +568,72 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
> }
> #endif
>
> +int ovl_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + struct vfs_caps *caps)
> +{
> + int err;
> + const struct cred *old_cred;
> + struct path realpath;
> +
> + ovl_path_real(dentry, &realpath);
> + old_cred = ovl_override_creds(dentry->d_sb);
> + err = vfs_get_fscaps(mnt_idmap(realpath.mnt), realpath.dentry, caps);

Right, vfs_get_fscaps() returns a struct vfs_caps which contains a
vfs{g,u}id and has the lower/upper layer's idmap taken into account.

That confused me at first because vfs_get_acl() returns a struct
posix_acl which contains k{g,u}id.

Reading through this made me realize that we need a few more words about
the translations. The reason is that we do distinct things for POSIX
ACLs and for fscaps. For POSIX ACLs when we call vfs_get_acl() what we
get is a struct posix_acl which contains k{g,u}id_t types. Because
struct posix_acl is cached filesytems wide and thus shared among
concurrent retrievers from different mounts with different idmappings.
Which means that we can't put vfs{g,u}id_t types in there. Instead we
perform translations on the fly. We do that in the VFS during path
lookup and we do that for overlayfs when it retrieves POSIX ACLs.

However, for fscaps we seem to do it differently because they're not
cached which is ok because they don't matter during path lookup as POSIX
ACLs do. So performance here doesn't matter too much. But that means
overall that the translations are quite distinct. And that gets
confusing when we have a stacking filesystem in the mix where we have to
take into account the privileges of the mounter of the overlayfs
instance and the idmap of the lower/upper layer.

I only skimmed my old commit but I think that commit 0c5fd887d2bb ("acl: move
idmapped mount fixup into vfs_{g,s}etxattr()") contains a detailed explanation
of this as I see:

> For POSIX ACLs we need to do something similar. However, in contrast to fscaps
> we cannot apply the fix directly to the kernel internal posix acl data
> structure as this would alter the cached values and would also require a rework
> of how we currently deal with POSIX ACLs in general which almost never take the
> filesystem idmapping into account (the noteable exception being FUSE but even
> there the implementation is special) and instead retrieve the raw values based
> on the initial idmapping.

Could you please add a diagram/explanation illustrating the translations for
fscaps in the general case and for stacking filesystems? It doesn't really
matter too much where you put it. Either add a section to
Documentation/filesystems/porting.rst or add a section to
Documentation/filesystems/idmapping.rst.

> + revert_creds(old_cred);
> + return err;
> +}
> +
> +int ovl_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int setxattr_flags)
> +{
> + int err;
> + struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
> + struct dentry *upperdentry = ovl_dentry_upper(dentry);
> + struct dentry *realdentry = upperdentry ?: ovl_dentry_lower(dentry);
> + const struct cred *old_cred;
> +
> + /*
> + * If the fscaps are to be remove from a lower file, check that they
> + * exist before copying up.
> + */
> + if (!caps && !upperdentry) {
> + struct path realpath;
> + struct vfs_caps lower_caps;
> +
> + ovl_path_lower(dentry, &realpath);
> + old_cred = ovl_override_creds(dentry->d_sb);
> + err = vfs_get_fscaps(mnt_idmap(realpath.mnt), realdentry,
> + &lower_caps);
> + revert_creds(old_cred);
> + if (err)
> + goto out;
> + }
> +
> + err = ovl_want_write(dentry);
> + if (err)
> + goto out;
> +
> + err = ovl_copy_up(dentry);
> + if (err)
> + goto out_drop_write;
> + upperdentry = ovl_dentry_upper(dentry);
> +
> + old_cred = ovl_override_creds(dentry->d_sb);
> + if (!caps)
> + err = vfs_remove_fscaps(ovl_upper_mnt_idmap(ofs), upperdentry);
> + else
> + err = vfs_set_fscaps(ovl_upper_mnt_idmap(ofs), upperdentry,
> + caps, setxattr_flags);
> + revert_creds(old_cred);
> +
> + /* copy c/mtime */
> + ovl_copyattr(d_inode(dentry));
> +
> +out_drop_write:
> + ovl_drop_write(dentry);
> +out:
> + return err;
> +}
> +
> int ovl_update_time(struct inode *inode, int flags)
> {
> if (flags & S_ATIME) {
> @@ -747,6 +813,8 @@ static const struct inode_operations ovl_file_inode_operations = {
> .get_inode_acl = ovl_get_inode_acl,
> .get_acl = ovl_get_acl,
> .set_acl = ovl_set_acl,
> + .get_fscaps = ovl_get_fscaps,
> + .set_fscaps = ovl_set_fscaps,
> .update_time = ovl_update_time,
> .fiemap = ovl_fiemap,
> .fileattr_get = ovl_fileattr_get,
> @@ -758,6 +826,8 @@ static const struct inode_operations ovl_symlink_inode_operations = {
> .get_link = ovl_get_link,
> .getattr = ovl_getattr,
> .listxattr = ovl_listxattr,
> + .get_fscaps = ovl_get_fscaps,
> + .set_fscaps = ovl_set_fscaps,
> .update_time = ovl_update_time,
> };
>
> @@ -769,6 +839,8 @@ static const struct inode_operations ovl_special_inode_operations = {
> .get_inode_acl = ovl_get_inode_acl,
> .get_acl = ovl_get_acl,
> .set_acl = ovl_set_acl,
> + .get_fscaps = ovl_get_fscaps,
> + .set_fscaps = ovl_set_fscaps,
> .update_time = ovl_update_time,
> };
>
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index ee949f3e7c77..4f948749ee02 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -781,6 +781,11 @@ static inline struct posix_acl *ovl_get_acl_path(const struct path *path,
> }
> #endif
>
> +int ovl_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + struct vfs_caps *caps);
> +int ovl_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int setxattr_flags);
> +
> int ovl_update_time(struct inode *inode, int flags);
> bool ovl_is_private_xattr(struct super_block *sb, const char *name);
>
>
> --
> 2.43.0
>

2024-02-27 13:30:00

by Amir Goldstein

[permalink] [raw]
Subject: Re: [PATCH v2 20/25] ovl: add fscaps handlers

On Wed, Feb 21, 2024 at 11:25 PM Seth Forshee (DigitalOcean)
<[email protected]> wrote:
>
> Add handlers which read fs caps from the lower or upper filesystem and
> write/remove fs caps to the upper filesystem, performing copy-up as
> necessary.
>
> While fscaps only really make sense on regular files, the general policy
> is to allow most xattr namespaces on all different inode types, so
> fscaps handlers are installed in the inode operations for all types of
> inodes.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> fs/overlayfs/dir.c | 2 ++
> fs/overlayfs/inode.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++
> fs/overlayfs/overlayfs.h | 5 ++++
> 3 files changed, 79 insertions(+)
>
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index 0f8b4a719237..4ff360fe10c9 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -1307,6 +1307,8 @@ const struct inode_operations ovl_dir_inode_operations = {
> .get_inode_acl = ovl_get_inode_acl,
> .get_acl = ovl_get_acl,
> .set_acl = ovl_set_acl,
> + .get_fscaps = ovl_get_fscaps,
> + .set_fscaps = ovl_set_fscaps,
> .update_time = ovl_update_time,
> .fileattr_get = ovl_fileattr_get,
> .fileattr_set = ovl_fileattr_set,
> diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
> index c63b31a460be..7a8978ea6fe1 100644
> --- a/fs/overlayfs/inode.c
> +++ b/fs/overlayfs/inode.c
> @@ -568,6 +568,72 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
> }
> #endif
>
> +int ovl_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + struct vfs_caps *caps)
> +{
> + int err;
> + const struct cred *old_cred;
> + struct path realpath;
> +
> + ovl_path_real(dentry, &realpath);
> + old_cred = ovl_override_creds(dentry->d_sb);
> + err = vfs_get_fscaps(mnt_idmap(realpath.mnt), realpath.dentry, caps);
> + revert_creds(old_cred);
> + return err;
> +}
> +
> +int ovl_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int setxattr_flags)
> +{
> + int err;
> + struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
> + struct dentry *upperdentry = ovl_dentry_upper(dentry);
> + struct dentry *realdentry = upperdentry ?: ovl_dentry_lower(dentry);
> + const struct cred *old_cred;
> +
> + /*
> + * If the fscaps are to be remove from a lower file, check that they
> + * exist before copying up.
> + */

Don't you need to convert -ENODATA to 0 return value in this case?

> + if (!caps && !upperdentry) {
> + struct path realpath;
> + struct vfs_caps lower_caps;
> +
> + ovl_path_lower(dentry, &realpath);
> + old_cred = ovl_override_creds(dentry->d_sb);
> + err = vfs_get_fscaps(mnt_idmap(realpath.mnt), realdentry,
> + &lower_caps);
> + revert_creds(old_cred);
> + if (err)
> + goto out;
> + }
> +
> + err = ovl_want_write(dentry);
> + if (err)
> + goto out;
> +

ovl_want_write() should after ovl_copy_up(), see:
162d06444070 ("ovl: reorder ovl_want_write() after ovl_inode_lock()")


> + err = ovl_copy_up(dentry);
> + if (err)
> + goto out_drop_write;
> + upperdentry = ovl_dentry_upper(dentry);
> +
> + old_cred = ovl_override_creds(dentry->d_sb);
> + if (!caps)
> + err = vfs_remove_fscaps(ovl_upper_mnt_idmap(ofs), upperdentry);
> + else
> + err = vfs_set_fscaps(ovl_upper_mnt_idmap(ofs), upperdentry,
> + caps, setxattr_flags);
> + revert_creds(old_cred);
> +
> + /* copy c/mtime */
> + ovl_copyattr(d_inode(dentry));
> +
> +out_drop_write:
> + ovl_drop_write(dentry);
> +out:
> + return err;
> +}
> +
> int ovl_update_time(struct inode *inode, int flags)
> {
> if (flags & S_ATIME) {
> @@ -747,6 +813,8 @@ static const struct inode_operations ovl_file_inode_operations = {
> .get_inode_acl = ovl_get_inode_acl,
> .get_acl = ovl_get_acl,
> .set_acl = ovl_set_acl,
> + .get_fscaps = ovl_get_fscaps,
> + .set_fscaps = ovl_set_fscaps,
> .update_time = ovl_update_time,
> .fiemap = ovl_fiemap,
> .fileattr_get = ovl_fileattr_get,
> @@ -758,6 +826,8 @@ static const struct inode_operations ovl_symlink_inode_operations = {
> .get_link = ovl_get_link,
> .getattr = ovl_getattr,
> .listxattr = ovl_listxattr,
> + .get_fscaps = ovl_get_fscaps,
> + .set_fscaps = ovl_set_fscaps,
> .update_time = ovl_update_time,
> };
>
> @@ -769,6 +839,8 @@ static const struct inode_operations ovl_special_inode_operations = {
> .get_inode_acl = ovl_get_inode_acl,
> .get_acl = ovl_get_acl,
> .set_acl = ovl_set_acl,
> + .get_fscaps = ovl_get_fscaps,
> + .set_fscaps = ovl_set_fscaps,
> .update_time = ovl_update_time,
> };
>


Sorry, I did not understand the explanation why fscaps ops are needed
for non regular files. It does not look right to me.

Thanks,
Amir.

2024-02-27 15:03:33

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 20/25] ovl: add fscaps handlers

On Tue, Feb 27, 2024 at 03:28:18PM +0200, Amir Goldstein wrote:
> > +int ovl_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> > + const struct vfs_caps *caps, int setxattr_flags)
> > +{
> > + int err;
> > + struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
> > + struct dentry *upperdentry = ovl_dentry_upper(dentry);
> > + struct dentry *realdentry = upperdentry ?: ovl_dentry_lower(dentry);
> > + const struct cred *old_cred;
> > +
> > + /*
> > + * If the fscaps are to be remove from a lower file, check that they
> > + * exist before copying up.
> > + */
>
> Don't you need to convert -ENODATA to 0 return value in this case?

Do you mean that trying to remove an xattr that does not exist should
return 0? Standard behavior is to return -ENODATA in this situation.

>
> > + if (!caps && !upperdentry) {
> > + struct path realpath;
> > + struct vfs_caps lower_caps;
> > +
> > + ovl_path_lower(dentry, &realpath);
> > + old_cred = ovl_override_creds(dentry->d_sb);
> > + err = vfs_get_fscaps(mnt_idmap(realpath.mnt), realdentry,
> > + &lower_caps);
> > + revert_creds(old_cred);
> > + if (err)
> > + goto out;
> > + }
> > +
> > + err = ovl_want_write(dentry);
> > + if (err)
> > + goto out;
> > +
>
> ovl_want_write() should after ovl_copy_up(), see:
> 162d06444070 ("ovl: reorder ovl_want_write() after ovl_inode_lock()")

Fixed.

> > @@ -758,6 +826,8 @@ static const struct inode_operations ovl_symlink_inode_operations = {
> > .get_link = ovl_get_link,
> > .getattr = ovl_getattr,
> > .listxattr = ovl_listxattr,
> > + .get_fscaps = ovl_get_fscaps,
> > + .set_fscaps = ovl_set_fscaps,
> > .update_time = ovl_update_time,
> > };
> >
> > @@ -769,6 +839,8 @@ static const struct inode_operations ovl_special_inode_operations = {
> > .get_inode_acl = ovl_get_inode_acl,
> > .get_acl = ovl_get_acl,
> > .set_acl = ovl_set_acl,
> > + .get_fscaps = ovl_get_fscaps,
> > + .set_fscaps = ovl_set_fscaps,
> > .update_time = ovl_update_time,
> > };
> >
>
>
> Sorry, I did not understand the explanation why fscaps ops are needed
> for non regular files. It does not look right to me.

The kernel does not forbid XATTR_NAME_CAPS for non-regular files and
will internally even try to read them from non-regular files during
killpriv checks. If we do not add handlers then we will end up using the
normal ovl xattr handlers, which call vfs_*xattr(). These will return an
error for fscaps xattrs after this series, which would be a change in
behavior for overlayfs and make it behave differently from other
filesystems.

2024-03-01 09:20:06

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 14/25] evm: add support for fscaps security hooks

On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> Support the new fscaps security hooks by converting the vfs_caps to raw
> xattr data and then handling them the same as other xattrs.

Hi Seth

I started looking at this patch set.

The first question I have is if you are also going to update libcap
(and also tar, I guess), since both deal with the raw xattr.

From IMA/EVM perspective (Mimi will add on that), I guess it is
important that files with a signature/HMAC continue to be accessible
after applying this patch set.

Looking at the code, it seems the case (if I understood correctly,
vfs_getxattr_alloc() is still allowed).

To be sure that everything works, it would be really nice if you could
also extend our test suite:

https://github.com/mimizohar/ima-evm-utils/blob/next-testing/tests/portable_signatures.test

and

https://github.com/mimizohar/ima-evm-utils/blob/next-testing/tests/evm_hmactest


The first test we would need to extend is check_cp_preserve_xattrs,
which basically does a cp -a. We would need to set fscaps in the
origin, copy to the destination, and see if the latter is accessible.

I would also extend:

check_tar_extract_xattrs_different_owner
check_tar_extract_xattrs_same_owner
check_metadata_change
check_evm_revalidate
check_evm_portable_sig_ima_appraisal
check_evm_portable_sig_ima_measurement_list

It should not be too complicated. The purpose would be to exercise your
code below.


Regarding the second test, we would need to extend just check_evm_hmac.


Just realized, before extending the tests, it would be necessary to
modify also evmctl.c, to retrieve fscaps through the new interfaces,
and to let users provide custom fscaps the HMAC or portable signature
is calculated on.


You can run the tests locally (even with UML linux), or make a PR in
Github for both linux and ima-evm-utils, and me and Mimi will help to
run them. For Github, for now please use:

https://github.com/linux-integrity/linux
https://github.com/mimizohar/ima-evm-utils/

Thanks

Roberto

> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> include/linux/evm.h | 39 +++++++++++++++++++++++++
> security/integrity/evm/evm_main.c | 60 +++++++++++++++++++++++++++++++++++++++
> 2 files changed, 99 insertions(+)
>
> diff --git a/include/linux/evm.h b/include/linux/evm.h
> index 36ec884320d9..aeb9ff52ad22 100644
> --- a/include/linux/evm.h
> +++ b/include/linux/evm.h
> @@ -57,6 +57,20 @@ static inline void evm_inode_post_set_acl(struct dentry *dentry,
> {
> return evm_inode_post_setxattr(dentry, acl_name, NULL, 0);
> }
> +extern int evm_inode_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags);
> +static inline int evm_inode_remove_fscaps(struct dentry *dentry)
> +{
> + return evm_inode_set_fscaps(&nop_mnt_idmap, dentry, NULL, XATTR_REPLACE);
> +}
> +extern void evm_inode_post_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags);
> +static inline void evm_inode_post_remove_fscaps(struct dentry *dentry)
> +{
> + return evm_inode_post_set_fscaps(&nop_mnt_idmap, dentry, NULL, 0);
> +}
>
> int evm_inode_init_security(struct inode *inode, struct inode *dir,
> const struct qstr *qstr, struct xattr *xattrs,
> @@ -164,6 +178,31 @@ static inline void evm_inode_post_set_acl(struct dentry *dentry,
> return;
> }
>
> +static inline int evm_inode_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + return 0;
> +}
> +
> +static inline int evm_inode_remove_fscaps(struct dentry *dentry)
> +{
> + return 0;
> +}
> +
> +static inline void evm_inode_post_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps,
> + int flags)
> +{
> + return;
> +}
> +
> +static inline void evm_inode_post_remove_fscaps(struct dentry *dentry)
> +{
> + return;
> +}
> +
> static inline int evm_inode_init_security(struct inode *inode, struct inode *dir,
> const struct qstr *qstr,
> struct xattr *xattrs,
> diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
> index cc7956d7878b..ecf4634a921a 100644
> --- a/security/integrity/evm/evm_main.c
> +++ b/security/integrity/evm/evm_main.c
> @@ -805,6 +805,66 @@ void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name)
> evm_update_evmxattr(dentry, xattr_name, NULL, 0);
> }
>
> +int evm_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + struct inode *inode = d_inode(dentry);
> + struct vfs_ns_cap_data nscaps;
> + const void *xattr_data = NULL;
> + int size = 0;
> +
> + /* Policy permits modification of the protected xattrs even though
> + * there's no HMAC key loaded
> + */
> + if (evm_initialized & EVM_ALLOW_METADATA_WRITES)
> + return 0;
> +
> + if (caps) {
> + size = vfs_caps_to_xattr(idmap, i_user_ns(inode), caps, &nscaps,
> + sizeof(nscaps));
> + if (size < 0)
> + return size;
> + xattr_data = &nscaps;
> + }
> +
> + return evm_protect_xattr(idmap, dentry, XATTR_NAME_CAPS, xattr_data, size);
> +}
> +
> +void evm_inode_post_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + struct inode *inode = d_inode(dentry);
> + struct vfs_ns_cap_data nscaps;
> + const void *xattr_data = NULL;
> + int size = 0;
> +
> + if (!evm_revalidate_status(XATTR_NAME_CAPS))
> + return;
> +
> + evm_reset_status(dentry->d_inode);
> +
> + if (!(evm_initialized & EVM_INIT_HMAC))
> + return;
> +
> + if (is_unsupported_fs(dentry))
> + return;
> +
> + if (caps) {
> + size = vfs_caps_to_xattr(idmap, i_user_ns(inode), caps, &nscaps,
> + sizeof(nscaps));
> + /*
> + * The fscaps here should have been converted to an xattr by
> + * evm_inode_set_fscaps() already, so a failure to convert
> + * here is a bug.
> + */
> + if (WARN_ON_ONCE(size < 0))
> + return;
> + xattr_data = &nscaps;
> + }
> +
> + evm_update_evmxattr(dentry, XATTR_NAME_CAPS, xattr_data, size);
> +}
> +
> static int evm_attr_change(struct mnt_idmap *idmap,
> struct dentry *dentry, struct iattr *attr)
> {
>


2024-03-01 12:54:39

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 14/25] evm: add support for fscaps security hooks

On Fri, Mar 01, 2024 at 10:19:13AM +0100, Roberto Sassu wrote:
> On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > Support the new fscaps security hooks by converting the vfs_caps to raw
> > xattr data and then handling them the same as other xattrs.
>
> Hi Seth
>
> I started looking at this patch set.
>
> The first question I have is if you are also going to update libcap
> (and also tar, I guess), since both deal with the raw xattr.
>
> From IMA/EVM perspective (Mimi will add on that), I guess it is
> important that files with a signature/HMAC continue to be accessible
> after applying this patch set.
>
> Looking at the code, it seems the case (if I understood correctly,
> vfs_getxattr_alloc() is still allowed).
>
> To be sure that everything works, it would be really nice if you could
> also extend our test suite:
>
> https://github.com/mimizohar/ima-evm-utils/blob/next-testing/tests/portable_signatures.test
>
> and
>
> https://github.com/mimizohar/ima-evm-utils/blob/next-testing/tests/evm_hmac.test
>
>
> The first test we would need to extend is check_cp_preserve_xattrs,
> which basically does a cp -a. We would need to set fscaps in the
> origin, copy to the destination, and see if the latter is accessible.
>
> I would also extend:
>
> check_tar_extract_xattrs_different_owner
> check_tar_extract_xattrs_same_owner
> check_metadata_change
> check_evm_revalidate
> check_evm_portable_sig_ima_appraisal
> check_evm_portable_sig_ima_measurement_list
>
> It should not be too complicated. The purpose would be to exercise your
> code below.
>
>
> Regarding the second test, we would need to extend just check_evm_hmac.
>
>
> Just realized, before extending the tests, it would be necessary to
> modify also evmctl.c, to retrieve fscaps through the new interfaces,
> and to let users provide custom fscaps the HMAC or portable signature
> is calculated on.

While request for tests are obviously fine they should be added by the
respective experts for IMA/EVM in this case. I don't think it's
appropriate to expect Seth to do that especially because you seem to
imply that you currently don't have any tests for fscaps at all. We're
always happy to test things and if that'd be adding new IMA/EVM specific
features than it would be something to discuss but really we're
refactoring so the fact that you don't have tests we can run is not the
fault of this patchset and IMA/EVM is just a small portion of it.

2024-03-01 13:20:34

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 14/25] evm: add support for fscaps security hooks

On Fri, 2024-03-01 at 13:54 +0100, Christian Brauner wrote:
> On Fri, Mar 01, 2024 at 10:19:13AM +0100, Roberto Sassu wrote:
> > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > Support the new fscaps security hooks by converting the vfs_caps to raw
> > > xattr data and then handling them the same as other xattrs.
> >
> > Hi Seth
> >
> > I started looking at this patch set.
> >
> > The first question I have is if you are also going to update libcap
> > (and also tar, I guess), since both deal with the raw xattr.
> >
> > From IMA/EVM perspective (Mimi will add on that), I guess it is
> > important that files with a signature/HMAC continue to be accessible
> > after applying this patch set.
> >
> > Looking at the code, it seems the case (if I understood correctly,
> > vfs_getxattr_alloc() is still allowed).
> >
> > To be sure that everything works, it would be really nice if you could
> > also extend our test suite:
> >
> > https://github.com/mimizohar/ima-evm-utils/blob/next-testing/tests/portable_signatures.test
> >
> > and
> >
> > https://github.com/mimizohar/ima-evm-utils/blob/next-testing/tests/evm_hmac.test
> >
> >
> > The first test we would need to extend is check_cp_preserve_xattrs,
> > which basically does a cp -a. We would need to set fscaps in the
> > origin, copy to the destination, and see if the latter is accessible.
> >
> > I would also extend:
> >
> > check_tar_extract_xattrs_different_owner
> > check_tar_extract_xattrs_same_owner
> > check_metadata_change
> > check_evm_revalidate
> > check_evm_portable_sig_ima_appraisal
> > check_evm_portable_sig_ima_measurement_list
> >
> > It should not be too complicated. The purpose would be to exercise your
> > code below.
> >
> >
> > Regarding the second test, we would need to extend just check_evm_hmac.
> >
> >
> > Just realized, before extending the tests, it would be necessary to
> > modify also evmctl.c, to retrieve fscaps through the new interfaces,
> > and to let users provide custom fscaps the HMAC or portable signature
> > is calculated on.
>
> While request for tests are obviously fine they should be added by the
> respective experts for IMA/EVM in this case. I don't think it's
> appropriate to expect Seth to do that especially because you seem to
> imply that you currently don't have any tests for fscaps at all. We're
> always happy to test things and if that'd be adding new IMA/EVM specific
> features than it would be something to discuss but really we're
> refactoring so the fact that you don't have tests we can run is not the
> fault of this patchset and IMA/EVM is just a small portion of it.

Hi Christian

I have seen this policy of adding tests in other subsystems (eBPF),
which in my opinion makes sense, since you want anyway to check that
you didn't break existing code.

And yes, I agree that we should have better tests, and a better
workflow (we are working on improving it).

In this particular case, I was not asking to write a test from scratch,
that should not be difficult per se, but adding additional commands.

If I got it correctly, even if current tests for fscaps would have
existed, they would not work anyway, since they would have been based
on getting/setting the raw xattrs (as far as I know, at least for tar).

Happy to try adding the tests, would appreciate your help to review if
the tests are done in the correct way.

Thanks

Roberto


2024-03-01 13:41:14

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 14/25] evm: add support for fscaps security hooks

> I have seen this policy of adding tests in other subsystems (eBPF),

It makes sense if the drive of the patchset would be IMA/EVM features
not refactoring of existing code.

> Happy to try adding the tests, would appreciate your help to review if

Cool, happy to help review them.

2024-03-01 14:40:32

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 14/25] evm: add support for fscaps security hooks

On Fri, Mar 01, 2024 at 10:19:13AM +0100, Roberto Sassu wrote:
> On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > Support the new fscaps security hooks by converting the vfs_caps to raw
> > xattr data and then handling them the same as other xattrs.
>
> Hi Seth
>
> I started looking at this patch set.
>
> The first question I have is if you are also going to update libcap
> (and also tar, I guess), since both deal with the raw xattr.

There are no changes needed for userspace; it will still deal with raw
xattrs. As I mentioned in the cover letter, capabilities tests from
libcap2, libcap-ng, ltp, and xfstests all pass against this sereies.
That's with no modifications to userspace.

> From IMA/EVM perspective (Mimi will add on that), I guess it is
> important that files with a signature/HMAC continue to be accessible
> after applying this patch set.
>
> Looking at the code, it seems the case (if I understood correctly,
> vfs_getxattr_alloc() is still allowed).

So this is something that would change based on Christian's request to
stop using the xattr handlers entirely for fscaps as was done for acls.
I see how this would impact EVM, but we should be able to deal with it.

I am a little curious now about this code in evm_calc_hmac_or_hash():

size = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, xattr->name,
&xattr_value, xattr_size, GFP_NOFS);
if (size == -ENOMEM) {
error = -ENOMEM;
goto out;
}
if (size < 0)
continue;

user_space_size = vfs_getxattr(&nop_mnt_idmap, dentry,
xattr->name, NULL, 0);
if (user_space_size != size)
pr_debug("file %s: xattr %s size mismatch (kernel: %d, user: %d)\n",
dentry->d_name.name, xattr->name, size,
user_space_size);

Because with the current fscaps code you actually could end up getting
different sizes from these two interfaces, as vfs_getxattr_alloc() reads
the xattr directly from disk but vfs_getxattr() goes through
cap_inode_getsecurity(), which may do conversion between v2 and v3
formats which are different sizes.

Thanks,
Seth

2024-03-01 15:05:04

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 14/25] evm: add support for fscaps security hooks

On Fri, 2024-03-01 at 08:39 -0600, Seth Forshee (DigitalOcean) wrote:
> On Fri, Mar 01, 2024 at 10:19:13AM +0100, Roberto Sassu wrote:
> > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > Support the new fscaps security hooks by converting the vfs_caps to raw
> > > xattr data and then handling them the same as other xattrs.
> >
> > Hi Seth
> >
> > I started looking at this patch set.
> >
> > The first question I have is if you are also going to update libcap
> > (and also tar, I guess), since both deal with the raw xattr.
>
> There are no changes needed for userspace; it will still deal with raw
> xattrs. As I mentioned in the cover letter, capabilities tests from
> libcap2, libcap-ng, ltp, and xfstests all pass against this sereies.
> That's with no modifications to userspace.

Yes, figured it out after applying the patch set. Then yes, IMA/EVM
tests should work too.

> > From IMA/EVM perspective (Mimi will add on that), I guess it is
> > important that files with a signature/HMAC continue to be accessible
> > after applying this patch set.
> >
> > Looking at the code, it seems the case (if I understood correctly,
> > vfs_getxattr_alloc() is still allowed).
>
> So this is something that would change based on Christian's request to
> stop using the xattr handlers entirely for fscaps as was done for acls.
> I see how this would impact EVM, but we should be able to deal with it.
>
> I am a little curious now about this code in evm_calc_hmac_or_hash():
>
> size = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, xattr->name,
> &xattr_value, xattr_size, GFP_NOFS);
> if (size == -ENOMEM) {
> error = -ENOMEM;
> goto out;
> }
> if (size < 0)
> continue;
>
> user_space_size = vfs_getxattr(&nop_mnt_idmap, dentry,
> xattr->name, NULL, 0);
> if (user_space_size != size)
> pr_debug("file %s: xattr %s size mismatch (kernel: %d, user: %d)\n",
> dentry->d_name.name, xattr->name, size,
> user_space_size);
>
> Because with the current fscaps code you actually could end up getting
> different sizes from these two interfaces, as vfs_getxattr_alloc() reads
> the xattr directly from disk but vfs_getxattr() goes through
> cap_inode_getsecurity(), which may do conversion between v2 and v3
> formats which are different sizes.

Yes, that was another source of confusion. It happened that
security.selinux in the disk was without '\0', and the one from
vfs_getxattr() had it (of course the HMAC wouldn't match).

So, basically, you set something in user space and you get something
different.

Example:

# setfattr -n security.selinux -v "unconfined_u:object_r:admin_home_t:s0" test-file

SELinux active:
# getfattr -m - -d -e hex test-file
security.selinux=0x756e636f6e66696e65645f753a6f626a6563745f723a61646d696e5f686f6d655f743a733000

Smack active:
# getfattr -m - -d -e hex test-file
security.selinux=0x756e636f6e66696e65645f753a6f626a6563745f723a61646d696e5f686f6d655f743a7330


evmctl (will) allow to provide a hex xattr value for fscaps. That
should be the one to be used (and vfs_getxattr_alloc() does that).
However, I guess if the conversion happens, evmctl cannot correctly
verify anymore the file, unless the same string is specified for
verification (otherwise it reads the xattr through vfs_getxattr(),
which would be different).

Roberto


2024-03-01 16:00:17

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 11/25] security: add hooks for set/get/remove of fscaps

On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> In preparation for moving fscaps out of the xattr code paths, add new
> security hooks. These hooks are largely needed because common kernel
> code will pass around struct vfs_caps pointers, which EVM will need to
> convert to raw xattr data for verification and updates of its hashes.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> include/linux/lsm_hook_defs.h | 7 +++++
> include/linux/security.h | 33 +++++++++++++++++++++
> security/security.c | 69 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 109 insertions(+)
>
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 76458b6d53da..7b3c23f9e4a5 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -152,6 +152,13 @@ LSM_HOOK(int, 0, inode_get_acl, struct mnt_idmap *idmap,
> struct dentry *dentry, const char *acl_name)
> LSM_HOOK(int, 0, inode_remove_acl, struct mnt_idmap *idmap,
> struct dentry *dentry, const char *acl_name)
> +LSM_HOOK(int, 0, inode_set_fscaps, struct mnt_idmap *idmap,
> + struct dentry *dentry, const struct vfs_caps *caps, int flags);
> +LSM_HOOK(void, LSM_RET_VOID, inode_post_set_fscaps, struct mnt_idmap *idmap,
> + struct dentry *dentry, const struct vfs_caps *caps, int flags);
> +LSM_HOOK(int, 0, inode_get_fscaps, struct mnt_idmap *idmap, struct dentry *dentry);
> +LSM_HOOK(int, 0, inode_remove_fscaps, struct mnt_idmap *idmap,
> + struct dentry *dentry);

Uhm, there should not be semicolons here.

Roberto

> LSM_HOOK(int, 0, inode_need_killpriv, struct dentry *dentry)
> LSM_HOOK(int, 0, inode_killpriv, struct mnt_idmap *idmap,
> struct dentry *dentry)
> diff --git a/include/linux/security.h b/include/linux/security.h
> index d0eb20f90b26..40be548e5e12 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -378,6 +378,13 @@ int security_inode_getxattr(struct dentry *dentry, const char *name);
> int security_inode_listxattr(struct dentry *dentry);
> int security_inode_removexattr(struct mnt_idmap *idmap,
> struct dentry *dentry, const char *name);
> +int security_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int flags);
> +void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags);
> +int security_inode_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry);
> +int security_inode_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry);
> int security_inode_need_killpriv(struct dentry *dentry);
> int security_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry);
> int security_inode_getsecurity(struct mnt_idmap *idmap,
> @@ -935,6 +942,32 @@ static inline int security_inode_removexattr(struct mnt_idmap *idmap,
> return cap_inode_removexattr(idmap, dentry, name);
> }
>
> +static inline int security_inode_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps,
> + int flags)
> +{
> + return 0;
> +}
> +static void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps,
> + int flags)
> +{
> +}
> +
> +static int security_inode_get_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry)
> +{
> + return 0;
> +}
> +
> +static int security_inode_remove_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry)
> +{
> + return 0;
> +}
> +
> static inline int security_inode_need_killpriv(struct dentry *dentry)
> {
> return cap_inode_need_killpriv(dentry);
> diff --git a/security/security.c b/security/security.c
> index 3aaad75c9ce8..0d210da9862c 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -2351,6 +2351,75 @@ int security_inode_remove_acl(struct mnt_idmap *idmap,
> return evm_inode_remove_acl(idmap, dentry, acl_name);
> }
>
> +/**
> + * security_inode_set_fscaps() - Check if setting fscaps is allowed
> + * @idmap: idmap of the mount
> + * @dentry: file
> + * @caps: fscaps to be written
> + * @flags: flags for setxattr
> + *
> + * Check permission before setting the file capabilities given in @vfs_caps.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> + return 0;
> + return call_int_hook(inode_set_fscaps, 0, idmap, dentry, caps, flags);
> +}
> +
> +/**
> + * security_inode_post_set_fscaps() - Update the inode after setting fscaps
> + * @idmap: idmap of the mount
> + * @dentry: file
> + * @caps: fscaps to be written
> + * @flags: flags for setxattr
> + *
> + * Update inode security field after successfully setting fscaps.
> + *
> + */
> +void security_inode_post_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> + return;
> + call_void_hook(inode_post_set_fscaps, idmap, dentry, caps, flags);
> +}
> +
> +/**
> + * security_inode_get_fscaps() - Check if reading fscaps is allowed
> + * @dentry: file
> + *
> + * Check permission before getting fscaps.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_inode_get_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> +{
> + if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> + return 0;
> + return call_int_hook(inode_get_fscaps, 0, idmap, dentry);
> +}
> +
> +/**
> + * security_inode_remove_fscaps() - Check if removing fscaps is allowed
> + * @idmap: idmap of the mount
> + * @dentry: file
> + *
> + * Check permission before removing fscaps.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_inode_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
> +{
> + if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
> + return 0;
> + return call_int_hook(inode_remove_fscaps, 0, idmap, dentry);
> +}
> +
> /**
> * security_inode_post_setxattr() - Update the inode after a setxattr operation
> * @dentry: file
>


2024-03-01 16:32:00

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 06/25] capability: provide helpers for converting between xattrs and vfs_caps

On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> To pass around vfs_caps instead of raw xattr data we will need to
> convert between the two representations near userspace and disk
> boundaries. We already convert xattrs from disks to vfs_caps, so move
> that code into a helper, and change get_vfs_caps_from_disk() to use the
> helper.
>
> When converting vfs_caps to xattrs we have different considerations
> depending on the destination of the xattr data. For xattrs which will be
> written to disk we need to reject the xattr if the rootid does not map
> into the filesystem's user namespace, whereas xattrs read by userspace
> may need to undergo a conversion from v3 to v2 format when the rootid
> does not map. So this helper is split into an internal and an external
> interface. The internal interface does not return an error if the rootid
> has no mapping in the target user namespace and will be used for
> conversions targeting userspace. The external interface returns
> EOVERFLOW if the rootid has no mapping and will be used for all other
> conversions.
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> include/linux/capability.h | 10 ++
> security/commoncap.c | 228 +++++++++++++++++++++++++++++++++++----------
> 2 files changed, 187 insertions(+), 51 deletions(-)
>
> diff --git a/include/linux/capability.h b/include/linux/capability.h
> index eb46d346bbbc..a0893ac4664b 100644
> --- a/include/linux/capability.h
> +++ b/include/linux/capability.h
> @@ -209,6 +209,16 @@ static inline bool checkpoint_restore_ns_capable(struct user_namespace *ns)
> ns_capable(ns, CAP_SYS_ADMIN);
> }
>
> +/* helpers to convert between xattr and in-kernel representations */
> +int vfs_caps_from_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *src_userns,
> + struct vfs_caps *vfs_caps,
> + const void *data, size_t size);
> +ssize_t vfs_caps_to_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *dest_userns,
> + const struct vfs_caps *vfs_caps,
> + void *data, size_t size);
> +
> /* audit system wants to get cap info from files as well */
> int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
> const struct dentry *dentry,
> diff --git a/security/commoncap.c b/security/commoncap.c
> index a0b5c9740759..7531c9634997 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
> @@ -619,54 +619,41 @@ static inline int bprm_caps_from_vfs_caps(struct vfs_caps *caps,
> }
>
> /**
> - * get_vfs_caps_from_disk - retrieve vfs caps from disk
> + * vfs_caps_from_xattr - convert raw caps xattr data to vfs_caps
> *
> - * @idmap: idmap of the mount the inode was found from
> - * @dentry: dentry from which @inode is retrieved
> - * @cpu_caps: vfs capabilities
> + * @idmap: idmap of the mount the inode was found from
> + * @src_userns: user namespace for ids in xattr data
> + * @vfs_caps: destination buffer for vfs_caps data
> + * @data: rax xattr caps data
> + * @size: size of xattr data
> *
> - * Extract the on-exec-apply capability sets for an executable file.
> + * Converts a raw security.capability xattr into the kernel-internal
> + * capabilities format.
> *
> - * If the inode has been found through an idmapped mount the idmap of
> - * the vfsmount must be passed through @idmap. This function will then
> - * take care to map the inode according to @idmap before checking
> - * permissions. On non-idmapped mounts or if permission checking is to be
> - * performed on the raw inode simply pass @nop_mnt_idmap.
> + * If the xattr is being read or written through an idmapped mount the
> + * idmap of the vfsmount must be passed through @idmap. This function
> + * will then take care to map the rootid according to @idmap.
> + *
> + * Return: On success, return 0; on error, return < 0.
> */
> -int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
> - const struct dentry *dentry,
> - struct vfs_caps *cpu_caps)
> +int vfs_caps_from_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *src_userns,
> + struct vfs_caps *vfs_caps,
> + const void *data, size_t size)
> {
> - struct inode *inode = d_backing_inode(dentry);
> __u32 magic_etc;
> - int size;
> - struct vfs_ns_cap_data data, *nscaps = &data;
> - struct vfs_cap_data *caps = (struct vfs_cap_data *) &data;
> + const struct vfs_ns_cap_data *ns_caps = data;
> + struct vfs_cap_data *caps = (struct vfs_cap_data *)ns_caps;
> kuid_t rootkuid;
> - vfsuid_t rootvfsuid;
> - struct user_namespace *fs_ns;
> -
> - memset(cpu_caps, 0, sizeof(struct vfs_caps));
> -
> - if (!inode)
> - return -ENODATA;
>
> - fs_ns = inode->i_sb->s_user_ns;
> - size = __vfs_getxattr((struct dentry *)dentry, inode,
> - XATTR_NAME_CAPS, &data, XATTR_CAPS_SZ);
> - if (size == -ENODATA || size == -EOPNOTSUPP)
> - /* no data, that's ok */
> - return -ENODATA;
> -
> - if (size < 0)
> - return size;
> + memset(vfs_caps, 0, sizeof(*vfs_caps));
>
> if (size < sizeof(magic_etc))
> return -EINVAL;
>
> - cpu_caps->magic_etc = magic_etc = le32_to_cpu(caps->magic_etc);
> + vfs_caps->magic_etc = magic_etc = le32_to_cpu(caps->magic_etc);
>
> - rootkuid = make_kuid(fs_ns, 0);
> + rootkuid = make_kuid(src_userns, 0);
> switch (magic_etc & VFS_CAP_REVISION_MASK) {
> case VFS_CAP_REVISION_1:
> if (size != XATTR_CAPS_SZ_1)
> @@ -679,39 +666,178 @@ int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
> case VFS_CAP_REVISION_3:
> if (size != XATTR_CAPS_SZ_3)
> return -EINVAL;
> - rootkuid = make_kuid(fs_ns, le32_to_cpu(nscaps->rootid));
> + rootkuid = make_kuid(src_userns, le32_to_cpu(ns_caps->rootid));
> break;
>
> default:
> return -EINVAL;
> }
>
> - rootvfsuid = make_vfsuid(idmap, fs_ns, rootkuid);
> - if (!vfsuid_valid(rootvfsuid))
> - return -ENODATA;
> + vfs_caps->rootid = make_vfsuid(idmap, src_userns, rootkuid);
> + if (!vfsuid_valid(vfs_caps->rootid))
> + return -EOVERFLOW;
>
> - /* Limit the caps to the mounter of the filesystem
> - * or the more limited uid specified in the xattr.
> + vfs_caps->permitted.val = le32_to_cpu(caps->data[0].permitted);
> + vfs_caps->inheritable.val = le32_to_cpu(caps->data[0].inheritable);
> +
> + /*
> + * Rev1 had just a single 32-bit word, later expanded
> + * to a second one for the high bits
> */
> - if (!rootid_owns_currentns(rootvfsuid))
> - return -ENODATA;
> + if ((magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
> + vfs_caps->permitted.val += (u64)le32_to_cpu(caps->data[1].permitted) << 32;
> + vfs_caps->inheritable.val += (u64)le32_to_cpu(caps->data[1].inheritable) << 32;
> + }
> +
> + vfs_caps->permitted.val &= CAP_VALID_MASK;
> + vfs_caps->inheritable.val &= CAP_VALID_MASK;
> +
> + return 0;
> +}
> +
> +/*
> + * Inner implementation of vfs_caps_to_xattr() which does not return an
> + * error if the rootid does not map into @dest_userns.
> + */
> +static ssize_t __vfs_caps_to_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *dest_userns,
> + const struct vfs_caps *vfs_caps,
> + void *data, size_t size)
> +{
> + struct vfs_ns_cap_data *ns_caps = data;
> + struct vfs_cap_data *caps = (struct vfs_cap_data *)ns_caps;
> + kuid_t rootkuid;
> + uid_t rootid;
> +
> + memset(ns_caps, 0, size);

size -> sizeof(*ns_caps) (or an equivalent change)

I was zeroing more (the size of the buffer passed to vfs_getxattr()).

Roberto

> +
> + rootid = 0;
> + switch (vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) {
> + case VFS_CAP_REVISION_1:
> + if (size < XATTR_CAPS_SZ_1)
> + return -EINVAL;
> + size = XATTR_CAPS_SZ_1;
> + break;
> + case VFS_CAP_REVISION_2:
> + if (size < XATTR_CAPS_SZ_2)
> + return -EINVAL;
> + size = XATTR_CAPS_SZ_2;
> + break;
> + case VFS_CAP_REVISION_3:
> + if (size < XATTR_CAPS_SZ_3)
> + return -EINVAL;
> + size = XATTR_CAPS_SZ_3;
> + rootkuid = from_vfsuid(idmap, dest_userns, vfs_caps->rootid);
> + rootid = from_kuid(dest_userns, rootkuid);
> + ns_caps->rootid = cpu_to_le32(rootid);
> + break;
>
> - cpu_caps->permitted.val = le32_to_cpu(caps->data[0].permitted);
> - cpu_caps->inheritable.val = le32_to_cpu(caps->data[0].inheritable);
> + default:
> + return -EINVAL;
> + }
> +
> + caps->magic_etc = cpu_to_le32(vfs_caps->magic_etc);
> +
> + caps->data[0].permitted = cpu_to_le32(lower_32_bits(vfs_caps->permitted.val));
> + caps->data[0].inheritable = cpu_to_le32(lower_32_bits(vfs_caps->inheritable.val));
>
> /*
> * Rev1 had just a single 32-bit word, later expanded
> * to a second one for the high bits
> */
> - if ((magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
> - cpu_caps->permitted.val += (u64)le32_to_cpu(caps->data[1].permitted) << 32;
> - cpu_caps->inheritable.val += (u64)le32_to_cpu(caps->data[1].inheritable) << 32;
> + if ((vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) != VFS_CAP_REVISION_1) {
> + caps->data[1].permitted =
> + cpu_to_le32(upper_32_bits(vfs_caps->permitted.val));
> + caps->data[1].inheritable =
> + cpu_to_le32(upper_32_bits(vfs_caps->inheritable.val));
> }
>
> - cpu_caps->permitted.val &= CAP_VALID_MASK;
> - cpu_caps->inheritable.val &= CAP_VALID_MASK;
> + return size;
> +}
> +
> +
> +/**
> + * vfs_caps_to_xattr - convert vfs_caps to raw caps xattr data
> + *
> + * @idmap: idmap of the mount the inode was found from
> + * @dest_userns: user namespace for ids in xattr data
> + * @vfs_caps: source vfs_caps data
> + * @data: destination buffer for rax xattr caps data
> + * @size: size of the @data buffer
> + *
> + * Converts a kernel-internal capability into the raw security.capability
> + * xattr format.
> + *
> + * If the xattr is being read or written through an idmapped mount the
> + * idmap of the vfsmount must be passed through @idmap. This function
> + * will then take care to map the rootid according to @idmap.
> + *
> + * Return: On success, return the size of the xattr data. On error,
> + * return < 0.
> + */
> +ssize_t vfs_caps_to_xattr(struct mnt_idmap *idmap,
> + struct user_namespace *dest_userns,
> + const struct vfs_caps *vfs_caps,
> + void *data, size_t size)
> +{
> + struct vfs_ns_cap_data *caps = data;
> + int ret;
> +
> + ret = __vfs_caps_to_xattr(idmap, dest_userns, vfs_caps, data, size);
> + if (ret > 0 &&
> + (vfs_caps->magic_etc & VFS_CAP_REVISION_MASK) == VFS_CAP_REVISION_3 &&
> + le32_to_cpu(caps->rootid) == (uid_t)-1)
> + return -EOVERFLOW;
> + return ret;
> +}
> +
> +/**
> + * get_vfs_caps_from_disk - retrieve vfs caps from disk
> + *
> + * @idmap: idmap of the mount the inode was found from
> + * @dentry: dentry from which @inode is retrieved
> + * @cpu_caps: vfs capabilities
> + *
> + * Extract the on-exec-apply capability sets for an executable file.
> + *
> + * If the inode has been found through an idmapped mount the idmap of
> + * the vfsmount must be passed through @idmap. This function will then
> + * take care to map the inode according to @idmap before checking
> + * permissions. On non-idmapped mounts or if permission checking is to be
> + * performed on the raw inode simply pass @nop_mnt_idmap.
> + */
> +int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
> + const struct dentry *dentry,
> + struct vfs_caps *cpu_caps)
> +{
> + struct inode *inode = d_backing_inode(dentry);
> + int size, ret;
> + struct vfs_ns_cap_data data, *nscaps = &data;
> +
> + if (!inode)
> + return -ENODATA;
>
> - cpu_caps->rootid = rootvfsuid;
> + size = __vfs_getxattr((struct dentry *)dentry, inode,
> + XATTR_NAME_CAPS, &data, XATTR_CAPS_SZ);
> + if (size == -ENODATA || size == -EOPNOTSUPP)
> + /* no data, that's ok */
> + return -ENODATA;
> +
> + if (size < 0)
> + return size;
> +
> + ret = vfs_caps_from_xattr(idmap, inode->i_sb->s_user_ns,
> + cpu_caps, nscaps, size);
> + if (ret == -EOVERFLOW)
> + return -ENODATA;
> + if (ret)
> + return ret;
> +
> + /* Limit the caps to the mounter of the filesystem
> + * or the more limited uid specified in the xattr.
> + */
> + if (!rootid_owns_currentns(cpu_caps->rootid))
> + return -ENODATA;
>
> return 0;
> }
>


2024-03-01 18:52:37

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 11/25] security: add hooks for set/get/remove of fscaps

On Fri, Mar 01, 2024 at 04:59:16PM +0100, Roberto Sassu wrote:
> On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > In preparation for moving fscaps out of the xattr code paths, add new
> > security hooks. These hooks are largely needed because common kernel
> > code will pass around struct vfs_caps pointers, which EVM will need to
> > convert to raw xattr data for verification and updates of its hashes.
> >
> > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > ---
> > include/linux/lsm_hook_defs.h | 7 +++++
> > include/linux/security.h | 33 +++++++++++++++++++++
> > security/security.c | 69 +++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 109 insertions(+)
> >
> > diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> > index 76458b6d53da..7b3c23f9e4a5 100644
> > --- a/include/linux/lsm_hook_defs.h
> > +++ b/include/linux/lsm_hook_defs.h
> > @@ -152,6 +152,13 @@ LSM_HOOK(int, 0, inode_get_acl, struct mnt_idmap *idmap,
> > struct dentry *dentry, const char *acl_name)
> > LSM_HOOK(int, 0, inode_remove_acl, struct mnt_idmap *idmap,
> > struct dentry *dentry, const char *acl_name)
> > +LSM_HOOK(int, 0, inode_set_fscaps, struct mnt_idmap *idmap,
> > + struct dentry *dentry, const struct vfs_caps *caps, int flags);
> > +LSM_HOOK(void, LSM_RET_VOID, inode_post_set_fscaps, struct mnt_idmap *idmap,
> > + struct dentry *dentry, const struct vfs_caps *caps, int flags);
> > +LSM_HOOK(int, 0, inode_get_fscaps, struct mnt_idmap *idmap, struct dentry *dentry);
> > +LSM_HOOK(int, 0, inode_remove_fscaps, struct mnt_idmap *idmap,
> > + struct dentry *dentry);
>
> Uhm, there should not be semicolons here.

Yes, I've fixed this already for the next version.

Thanks,
Seth

2024-03-01 19:03:19

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 06/25] capability: provide helpers for converting between xattrs and vfs_caps

On Fri, Mar 01, 2024 at 05:30:55PM +0100, Roberto Sassu wrote:
> > +/*
> > + * Inner implementation of vfs_caps_to_xattr() which does not return an
> > + * error if the rootid does not map into @dest_userns.
> > + */
> > +static ssize_t __vfs_caps_to_xattr(struct mnt_idmap *idmap,
> > + struct user_namespace *dest_userns,
> > + const struct vfs_caps *vfs_caps,
> > + void *data, size_t size)
> > +{
> > + struct vfs_ns_cap_data *ns_caps = data;
> > + struct vfs_cap_data *caps = (struct vfs_cap_data *)ns_caps;
> > + kuid_t rootkuid;
> > + uid_t rootid;
> > +
> > + memset(ns_caps, 0, size);
>
> size -> sizeof(*ns_caps) (or an equivalent change)

This is zeroing out the passed buffer, so it should use the size passed
for the buffer. sizeof(*ns_caps) could potentially be more than the size
of the buffer.

Maybe it would be clearer if it was memset(data, 0, size)?

> I was zeroing more (the size of the buffer passed to vfs_getxattr()).
>
> Roberto

2024-03-04 09:03:39

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 06/25] capability: provide helpers for converting between xattrs and vfs_caps

On Fri, 2024-03-01 at 13:00 -0600, Seth Forshee (DigitalOcean) wrote:
> On Fri, Mar 01, 2024 at 05:30:55PM +0100, Roberto Sassu wrote:
> > > +/*
> > > + * Inner implementation of vfs_caps_to_xattr() which does not return an
> > > + * error if the rootid does not map into @dest_userns.
> > > + */
> > > +static ssize_t __vfs_caps_to_xattr(struct mnt_idmap *idmap,
> > > + struct user_namespace *dest_userns,
> > > + const struct vfs_caps *vfs_caps,
> > > + void *data, size_t size)
> > > +{
> > > + struct vfs_ns_cap_data *ns_caps = data;
> > > + struct vfs_cap_data *caps = (struct vfs_cap_data *)ns_caps;
> > > + kuid_t rootkuid;
> > > + uid_t rootid;
> > > +
> > > + memset(ns_caps, 0, size);
> >
> > size -> sizeof(*ns_caps) (or an equivalent change)
>
> This is zeroing out the passed buffer, so it should use the size passed
> for the buffer. sizeof(*ns_caps) could potentially be more than the size
> of the buffer.

Uhm, then maybe the problem is that you are passing the wrong argument?

ssize_t
do_getxattr(struct mnt_idmap *idmap, struct dentry *d,
struct xattr_ctx *ctx)
{
ssize_t error;
char *kname = ctx->kname->name;

if (is_fscaps_xattr(kname)) {
struct vfs_caps caps;
struct vfs_ns_cap_data data;
int ret;

ret = vfs_get_fscaps(idmap, d, &caps);
if (ret)
return ret;
/*
* rootid is already in the mount idmap, so pass nop_mnt_idmap
* so that it won't be mapped.
*/
ret = vfs_caps_to_user_xattr(&nop_mnt_idmap, current_user_ns(),
&caps, &data, ctx->size);


ctx->size in my case is 1024 bytes.

Roberto

> Maybe it would be clearer if it was memset(data, 0, size)?
>
> > I was zeroing more (the size of the buffer passed to vfs_getxattr()).
> >
> > Roberto


2024-03-04 10:22:33

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> Use the vfs interfaces for fetching file capabilities for killpriv
> checks and from get_vfs_caps_from_disk(). While there, update the
> kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> from vfs_get_fscaps_nosec().
>
> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> security/commoncap.c | 30 +++++++++++++-----------------
> 1 file changed, 13 insertions(+), 17 deletions(-)
>
> diff --git a/security/commoncap.c b/security/commoncap.c
> index a0ff7e6092e0..751bb26a06a6 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
> @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> */
> int cap_inode_need_killpriv(struct dentry *dentry)
> {
> - struct inode *inode = d_backing_inode(dentry);
> + struct vfs_caps caps;
> int error;
>
> - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> - return error > 0;
> + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> + return error == 0;
> }
>
> /**
> @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> {
> int error;
>
> - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> + error = vfs_remove_fscaps_nosec(idmap, dentry);

Uhm, I see that the change is logically correct... but the original
code was not correct, since the EVM post hook is not called (thus the
HMAC is broken, or an xattr change is allowed on a portable signature
which should be not).

For completeness, the xattr change on a portable signature should not
happen in the first place, so cap_inode_killpriv() would not be called.
However, since EVM allows same value change, we are here.

Here is how I discovered this problem.

Example:

# ls -l test-file
-rw-r-Sr--. 1 3001 3001 5 Mar 4 10:11 test-file

# getfattr -m - -d -e hex test-file
# file: test-file
security.capability=0x0100000202300000023000000000000000000000
security.evm=0x05020498c82b5300663064023052a1aa6200d08b3db60a1c636b97b52658af369ee0bf521cfca6c733671ebf5764b1b122f67030cfc688a111c19a7ed3023039895966cf92217ea55c1405212ced1396c2d830ae55dbdb517c5d199c5a43638f90d430bad48191149dcc7c01f772ac
security.ima=0x0404f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2
security.selinux=0x756e636f6e66696e65645f753a6f626a6563745f723a756e6c6162656c65645f743a733000

# chown 3001 test-file

# ls -l test-file
-rw-r-Sr--. 1 3001 3001 5 Mar 4 10:14 test-file

# getfattr -m - -d -e hex test-file
# file: test-file
security.evm=0x05020498c82b5300673065023100cdd772fa7f9c17aa66e654c7f9c124de1ccfd36abbe5b8100b64a296164da45d0025fd2a2dec2e9580d5c82e5a32bfca02305ea3458b74e53d743408f65e748dc6ee52964e3aedac7367a43080248f4e000c655eb8e1f4338becb81797ea37f0bca6
security.ima=0x0404f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2
security.selinux=0x756e636f6e66696e65645f753a6f626a6563745f723a756e6c6162656c65645f743a733000


which breaks EVM verification.

Roberto

> if (error == -EOPNOTSUPP)
> error = 0;
> return error;
> @@ -719,6 +720,10 @@ ssize_t vfs_caps_to_user_xattr(struct mnt_idmap *idmap,
> * @cpu_caps: vfs capabilities
> *
> * Extract the on-exec-apply capability sets for an executable file.
> + * For version 3 capabilities xattrs, returns the capabilities only if
> + * they are applicable to current_user_ns() (i.e. that the rootid
> + * corresponds to an ID which maps to ID 0 in current_user_ns() or an
> + * ancestor), and returns -ENODATA otherwise.
> *
> * If the inode has been found through an idmapped mount the idmap of
> * the vfsmount must be passed through @idmap. This function will then
> @@ -731,25 +736,16 @@ int get_vfs_caps_from_disk(struct mnt_idmap *idmap,
> struct vfs_caps *cpu_caps)
> {
> struct inode *inode = d_backing_inode(dentry);
> - int size, ret;
> - struct vfs_ns_cap_data data, *nscaps = &data;
> + int ret;
>
> if (!inode)
> return -ENODATA;
>
> - size = __vfs_getxattr((struct dentry *)dentry, inode,
> - XATTR_NAME_CAPS, &data, XATTR_CAPS_SZ);
> - if (size == -ENODATA || size == -EOPNOTSUPP)
> + ret = vfs_get_fscaps_nosec(idmap, (struct dentry *)dentry, cpu_caps);
> + if (ret == -EOPNOTSUPP || ret == -EOVERFLOW)
> /* no data, that's ok */
> - return -ENODATA;
> + ret = -ENODATA;
>
> - if (size < 0)
> - return size;
> -
> - ret = vfs_caps_from_xattr(idmap, inode->i_sb->s_user_ns,
> - cpu_caps, nscaps, size);
> - if (ret == -EOVERFLOW)
> - return -ENODATA;
> if (ret)
> return ret;
>
>


2024-03-04 14:25:25

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 06/25] capability: provide helpers for converting between xattrs and vfs_caps

On Mon, Mar 04, 2024 at 09:33:06AM +0100, Roberto Sassu wrote:
> On Fri, 2024-03-01 at 13:00 -0600, Seth Forshee (DigitalOcean) wrote:
> > On Fri, Mar 01, 2024 at 05:30:55PM +0100, Roberto Sassu wrote:
> > > > +/*
> > > > + * Inner implementation of vfs_caps_to_xattr() which does not return an
> > > > + * error if the rootid does not map into @dest_userns.
> > > > + */
> > > > +static ssize_t __vfs_caps_to_xattr(struct mnt_idmap *idmap,
> > > > + struct user_namespace *dest_userns,
> > > > + const struct vfs_caps *vfs_caps,
> > > > + void *data, size_t size)
> > > > +{
> > > > + struct vfs_ns_cap_data *ns_caps = data;
> > > > + struct vfs_cap_data *caps = (struct vfs_cap_data *)ns_caps;
> > > > + kuid_t rootkuid;
> > > > + uid_t rootid;
> > > > +
> > > > + memset(ns_caps, 0, size);
> > >
> > > size -> sizeof(*ns_caps) (or an equivalent change)
> >
> > This is zeroing out the passed buffer, so it should use the size passed
> > for the buffer. sizeof(*ns_caps) could potentially be more than the size
> > of the buffer.
>
> Uhm, then maybe the problem is that you are passing the wrong argument?
>
> ssize_t
> do_getxattr(struct mnt_idmap *idmap, struct dentry *d,
> struct xattr_ctx *ctx)
> {
> ssize_t error;
> char *kname = ctx->kname->name;
>
> if (is_fscaps_xattr(kname)) {
> struct vfs_caps caps;
> struct vfs_ns_cap_data data;
> int ret;
>
> ret = vfs_get_fscaps(idmap, d, &caps);
> if (ret)
> return ret;
> /*
> * rootid is already in the mount idmap, so pass nop_mnt_idmap
> * so that it won't be mapped.
> */
> ret = vfs_caps_to_user_xattr(&nop_mnt_idmap, current_user_ns(),
> &caps, &data, ctx->size);
>
>
> ctx->size in my case is 1024 bytes.

Ah, yes that definitely isn't correct. I will fix it, thanks for finding
it.

2024-03-04 15:02:31

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 14/25] evm: add support for fscaps security hooks

On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> Support the new fscaps security hooks by converting the vfs_caps to raw
> xattr data and then handling them the same as other xattrs.

I realized that you need to register hooks for IMA too.

This should be the content to add in ima_appraise.c:

int ima_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
const struct vfs_caps *caps, int flags)
{
if (evm_revalidate_status(XATTR_NAME_CAPS))
ima_reset_appraise_flags(d_backing_inode(dentry), false);

return 0;
}

int ima_inode_remove_fscaps(struct mnt_idmap *idmap, struct dentry *dentry)
{
return ima_inode_set_fscaps(idmap, dentry, NULL, XATTR_REPLACE);
}

Thanks

Roberto

> Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> ---
> include/linux/evm.h | 39 +++++++++++++++++++++++++
> security/integrity/evm/evm_main.c | 60 +++++++++++++++++++++++++++++++++++++++
> 2 files changed, 99 insertions(+)
>
> diff --git a/include/linux/evm.h b/include/linux/evm.h
> index 36ec884320d9..aeb9ff52ad22 100644
> --- a/include/linux/evm.h
> +++ b/include/linux/evm.h
> @@ -57,6 +57,20 @@ static inline void evm_inode_post_set_acl(struct dentry *dentry,
> {
> return evm_inode_post_setxattr(dentry, acl_name, NULL, 0);
> }
> +extern int evm_inode_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags);
> +static inline int evm_inode_remove_fscaps(struct dentry *dentry)
> +{
> + return evm_inode_set_fscaps(&nop_mnt_idmap, dentry, NULL, XATTR_REPLACE);
> +}
> +extern void evm_inode_post_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags);
> +static inline void evm_inode_post_remove_fscaps(struct dentry *dentry)
> +{
> + return evm_inode_post_set_fscaps(&nop_mnt_idmap, dentry, NULL, 0);
> +}
>
> int evm_inode_init_security(struct inode *inode, struct inode *dir,
> const struct qstr *qstr, struct xattr *xattrs,
> @@ -164,6 +178,31 @@ static inline void evm_inode_post_set_acl(struct dentry *dentry,
> return;
> }
>
> +static inline int evm_inode_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + return 0;
> +}
> +
> +static inline int evm_inode_remove_fscaps(struct dentry *dentry)
> +{
> + return 0;
> +}
> +
> +static inline void evm_inode_post_set_fscaps(struct mnt_idmap *idmap,
> + struct dentry *dentry,
> + const struct vfs_caps *caps,
> + int flags)
> +{
> + return;
> +}
> +
> +static inline void evm_inode_post_remove_fscaps(struct dentry *dentry)
> +{
> + return;
> +}
> +
> static inline int evm_inode_init_security(struct inode *inode, struct inode *dir,
> const struct qstr *qstr,
> struct xattr *xattrs,
> diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
> index cc7956d7878b..ecf4634a921a 100644
> --- a/security/integrity/evm/evm_main.c
> +++ b/security/integrity/evm/evm_main.c
> @@ -805,6 +805,66 @@ void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name)
> evm_update_evmxattr(dentry, xattr_name, NULL, 0);
> }
>
> +int evm_inode_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + struct inode *inode = d_inode(dentry);
> + struct vfs_ns_cap_data nscaps;
> + const void *xattr_data = NULL;
> + int size = 0;
> +
> + /* Policy permits modification of the protected xattrs even though
> + * there's no HMAC key loaded
> + */
> + if (evm_initialized & EVM_ALLOW_METADATA_WRITES)
> + return 0;
> +
> + if (caps) {
> + size = vfs_caps_to_xattr(idmap, i_user_ns(inode), caps, &nscaps,
> + sizeof(nscaps));
> + if (size < 0)
> + return size;
> + xattr_data = &nscaps;
> + }
> +
> + return evm_protect_xattr(idmap, dentry, XATTR_NAME_CAPS, xattr_data, size);
> +}
> +
> +void evm_inode_post_set_fscaps(struct mnt_idmap *idmap, struct dentry *dentry,
> + const struct vfs_caps *caps, int flags)
> +{
> + struct inode *inode = d_inode(dentry);
> + struct vfs_ns_cap_data nscaps;
> + const void *xattr_data = NULL;
> + int size = 0;
> +
> + if (!evm_revalidate_status(XATTR_NAME_CAPS))
> + return;
> +
> + evm_reset_status(dentry->d_inode);
> +
> + if (!(evm_initialized & EVM_INIT_HMAC))
> + return;
> +
> + if (is_unsupported_fs(dentry))
> + return;
> +
> + if (caps) {
> + size = vfs_caps_to_xattr(idmap, i_user_ns(inode), caps, &nscaps,
> + sizeof(nscaps));
> + /*
> + * The fscaps here should have been converted to an xattr by
> + * evm_inode_set_fscaps() already, so a failure to convert
> + * here is a bug.
> + */
> + if (WARN_ON_ONCE(size < 0))
> + return;
> + xattr_data = &nscaps;
> + }
> +
> + evm_update_evmxattr(dentry, XATTR_NAME_CAPS, xattr_data, size);
> +}
> +
> static int evm_attr_change(struct mnt_idmap *idmap,
> struct dentry *dentry, struct iattr *attr)
> {
>


2024-03-04 15:33:58

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > Use the vfs interfaces for fetching file capabilities for killpriv
> > checks and from get_vfs_caps_from_disk(). While there, update the
> > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > from vfs_get_fscaps_nosec().
> >
> > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > ---
> > security/commoncap.c | 30 +++++++++++++-----------------
> > 1 file changed, 13 insertions(+), 17 deletions(-)
> >
> > diff --git a/security/commoncap.c b/security/commoncap.c
> > index a0ff7e6092e0..751bb26a06a6 100644
> > --- a/security/commoncap.c
> > +++ b/security/commoncap.c
> > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > */
> > int cap_inode_need_killpriv(struct dentry *dentry)
> > {
> > - struct inode *inode = d_backing_inode(dentry);
> > + struct vfs_caps caps;
> > int error;
> >
> > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > - return error > 0;
> > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > + return error == 0;
> > }
> >
> > /**
> > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > {
> > int error;
> >
> > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > + error = vfs_remove_fscaps_nosec(idmap, dentry);
>
> Uhm, I see that the change is logically correct... but the original
> code was not correct, since the EVM post hook is not called (thus the
> HMAC is broken, or an xattr change is allowed on a portable signature
> which should be not).
>
> For completeness, the xattr change on a portable signature should not
> happen in the first place, so cap_inode_killpriv() would not be called.
> However, since EVM allows same value change, we are here.

I really don't understand EVM that well and am pretty hesitant to try an
change any of the logic around it. But I'll hazard a thought: should EVM
have a inode_need_killpriv hook which returns an error in this
situation?

2024-03-04 16:44:41

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > from vfs_get_fscaps_nosec().
> > >
> > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > ---
> > > security/commoncap.c | 30 +++++++++++++-----------------
> > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > >
> > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > index a0ff7e6092e0..751bb26a06a6 100644
> > > --- a/security/commoncap.c
> > > +++ b/security/commoncap.c
> > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > */
> > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > {
> > > - struct inode *inode = d_backing_inode(dentry);
> > > + struct vfs_caps caps;
> > > int error;
> > >
> > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > - return error > 0;
> > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > + return error == 0;
> > > }
> > >
> > > /**
> > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > {
> > > int error;
> > >
> > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> >
> > Uhm, I see that the change is logically correct... but the original
> > code was not correct, since the EVM post hook is not called (thus the
> > HMAC is broken, or an xattr change is allowed on a portable signature
> > which should be not).
> >
> > For completeness, the xattr change on a portable signature should not
> > happen in the first place, so cap_inode_killpriv() would not be called.
> > However, since EVM allows same value change, we are here.
>
> I really don't understand EVM that well and am pretty hesitant to try an
> change any of the logic around it. But I'll hazard a thought: should EVM
> have a inode_need_killpriv hook which returns an error in this
> situation?

Uhm, I think it would not work without modifying
security_inode_need_killpriv() and the hook definition.

Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
not be invoked. We would need to continue the loop and let EVM know
what is the current return value. Then EVM can reject the change.

An alternative way would be to detect that actually we are setting the
same value for inode metadata, and maybe not returning 1 from
cap_inode_need_killpriv().

I would prefer the second, since EVM allows same value change and we
would have an exception if there are fscaps.

This solves only the case of portable signatures. We would need to
change cap_inode_need_killpriv() anyway to update the HMAC for mutable
files.

Roberto


2024-03-04 16:56:35

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > from vfs_get_fscaps_nosec().
> > > >
> > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > ---
> > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > >
> > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > --- a/security/commoncap.c
> > > > +++ b/security/commoncap.c
> > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > */
> > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > {
> > > > - struct inode *inode = d_backing_inode(dentry);
> > > > + struct vfs_caps caps;
> > > > int error;
> > > >
> > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > - return error > 0;
> > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > + return error == 0;
> > > > }
> > > >
> > > > /**
> > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > {
> > > > int error;
> > > >
> > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > >
> > > Uhm, I see that the change is logically correct... but the original
> > > code was not correct, since the EVM post hook is not called (thus the
> > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > which should be not).
> > >
> > > For completeness, the xattr change on a portable signature should not
> > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > However, since EVM allows same value change, we are here.
> >
> > I really don't understand EVM that well and am pretty hesitant to try an
> > change any of the logic around it. But I'll hazard a thought: should EVM
> > have a inode_need_killpriv hook which returns an error in this
> > situation?
>
> Uhm, I think it would not work without modifying
> security_inode_need_killpriv() and the hook definition.
>
> Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> not be invoked. We would need to continue the loop and let EVM know
> what is the current return value. Then EVM can reject the change.
>
> An alternative way would be to detect that actually we are setting the
> same value for inode metadata, and maybe not returning 1 from
> cap_inode_need_killpriv().
>
> I would prefer the second, since EVM allows same value change and we
> would have an exception if there are fscaps.
>
> This solves only the case of portable signatures. We would need to
> change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> files.

I see. In any case this sounds like a matter for a separate patch
series.

2024-03-05 09:12:31

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean) wrote:
> On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > > from vfs_get_fscaps_nosec().
> > > > >
> > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > ---
> > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > >
> > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > --- a/security/commoncap.c
> > > > > +++ b/security/commoncap.c
> > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > */
> > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > {
> > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > + struct vfs_caps caps;
> > > > > int error;
> > > > >
> > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > > - return error > 0;
> > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > > + return error == 0;
> > > > > }
> > > > >
> > > > > /**
> > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > > {
> > > > > int error;
> > > > >
> > > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > >
> > > > Uhm, I see that the change is logically correct... but the original
> > > > code was not correct, since the EVM post hook is not called (thus the
> > > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > > which should be not).
> > > >
> > > > For completeness, the xattr change on a portable signature should not
> > > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > > However, since EVM allows same value change, we are here.
> > >
> > > I really don't understand EVM that well and am pretty hesitant to try an
> > > change any of the logic around it. But I'll hazard a thought: should EVM
> > > have a inode_need_killpriv hook which returns an error in this
> > > situation?
> >
> > Uhm, I think it would not work without modifying
> > security_inode_need_killpriv() and the hook definition.
> >
> > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> > not be invoked. We would need to continue the loop and let EVM know
> > what is the current return value. Then EVM can reject the change.
> >
> > An alternative way would be to detect that actually we are setting the
> > same value for inode metadata, and maybe not returning 1 from
> > cap_inode_need_killpriv().
> >
> > I would prefer the second, since EVM allows same value change and we
> > would have an exception if there are fscaps.
> >
> > This solves only the case of portable signatures. We would need to
> > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > files.
>
> I see. In any case this sounds like a matter for a separate patch
> series.

Agreed.

2024-03-05 12:49:07

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean) wrote:
> > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > > > from vfs_get_fscaps_nosec().
> > > > > >
> > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > > ---
> > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > >
> > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > --- a/security/commoncap.c
> > > > > > +++ b/security/commoncap.c
> > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > */
> > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > {
> > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > + struct vfs_caps caps;
> > > > > > int error;
> > > > > >
> > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > > > - return error > 0;
> > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > > > + return error == 0;
> > > > > > }
> > > > > >
> > > > > > /**
> > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > > > {
> > > > > > int error;
> > > > > >
> > > > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > >
> > > > > Uhm, I see that the change is logically correct... but the original
> > > > > code was not correct, since the EVM post hook is not called (thus the
> > > > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > > > which should be not).
> > > > >
> > > > > For completeness, the xattr change on a portable signature should not
> > > > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > > > However, since EVM allows same value change, we are here.
> > > >
> > > > I really don't understand EVM that well and am pretty hesitant to try an
> > > > change any of the logic around it. But I'll hazard a thought: should EVM
> > > > have a inode_need_killpriv hook which returns an error in this
> > > > situation?
> > >
> > > Uhm, I think it would not work without modifying
> > > security_inode_need_killpriv() and the hook definition.
> > >
> > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> > > not be invoked. We would need to continue the loop and let EVM know
> > > what is the current return value. Then EVM can reject the change.
> > >
> > > An alternative way would be to detect that actually we are setting the
> > > same value for inode metadata, and maybe not returning 1 from
> > > cap_inode_need_killpriv().
> > >
> > > I would prefer the second, since EVM allows same value change and we
> > > would have an exception if there are fscaps.
> > >
> > > This solves only the case of portable signatures. We would need to
> > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > files.
> >
> > I see. In any case this sounds like a matter for a separate patch
> > series.
>
> Agreed.

Christian, how realistic is that we don't kill priv if we are setting
the same owner?

Serge, would we be able to replace __vfs_removexattr() (or now
vfs_get_fscaps_nosec()) with a security-equivalent alternative?

Thanks

Roberto


2024-03-05 16:36:50

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, 2024-03-05 at 17:26 +0100, Christian Brauner wrote:
> On Tue, Mar 05, 2024 at 01:46:56PM +0100, Roberto Sassu wrote:
> > On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean) wrote:
> > > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > > > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > > > > > from vfs_get_fscaps_nosec().
> > > > > > > >
> > > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <sforshee@kernelorg>
> > > > > > > > ---
> > > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > > >
> > > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > > --- a/security/commoncap.c
> > > > > > > > +++ b/security/commoncap.c
> > > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > > */
> > > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > > {
> > > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > > + struct vfs_caps caps;
> > > > > > > > int error;
> > > > > > > >
> > > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > > > > > - return error > 0;
> > > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > > > > > + return error == 0;
> > > > > > > > }
> > > > > > > >
> > > > > > > > /**
> > > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > > > > > {
> > > > > > > > int error;
> > > > > > > >
> > > > > > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > > >
> > > > > > > Uhm, I see that the change is logically correct... but the original
> > > > > > > code was not correct, since the EVM post hook is not called (thus the
> > > > > > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > > > > > which should be not).
> > > > > > >
> > > > > > > For completeness, the xattr change on a portable signature should not
> > > > > > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > > > > > However, since EVM allows same value change, we are here.
> > > > > >
> > > > > > I really don't understand EVM that well and am pretty hesitant to try an
> > > > > > change any of the logic around it. But I'll hazard a thought: should EVM
> > > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > > situation?
> > > > >
> > > > > Uhm, I think it would not work without modifying
> > > > > security_inode_need_killpriv() and the hook definition.
> > > > >
> > > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> > > > > not be invoked. We would need to continue the loop and let EVM know
> > > > > what is the current return value. Then EVM can reject the change.
> > > > >
> > > > > An alternative way would be to detect that actually we are setting the
> > > > > same value for inode metadata, and maybe not returning 1 from
> > > > > cap_inode_need_killpriv().
> > > > >
> > > > > I would prefer the second, since EVM allows same value change and we
> > > > > would have an exception if there are fscaps.
> > > > >
> > > > > This solves only the case of portable signatures. We would need to
> > > > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > > > files.
> > > >
> > > > I see. In any case this sounds like a matter for a separate patch
> > > > series.
> > >
> > > Agreed.
> >
> > Christian, how realistic is that we don't kill priv if we are setting
> > the same owner?
>
> Uhm, I would need to see the wider context of the proposed change. But
> iiuc then you would be comparing current and new fscaps and if they are
> identical you don't kill privs? I think that would work. But again, I
> would need to see the actual context/change to say something meaningful.

Ok, basically a software vendor can ship binaries with a signature over
file metadata, including UID/GID, etc.

A system can verify the signature through the public key of the
software vendor.

The problem is if someone (or even tar), executes chown on that binary,
fscaps are lost. Thus, signature verification will fail from now on.

EVM locks file metadata as soon as signature verification succeeds
(i.e. metadata are the same of those signed by the software vendor).

EVM locking works if someone is trying to set different metadata. But,
if I try to chown to the same owner as the one stored in the inode, EVM
allows it but the capability LSM removes security.capability, thus
invalidating the signature.

At least, it would be desirable that security.capability is not removed
when setting the same owner. If the owner is different, EVM will handle
that.

Roberto


2024-03-05 16:41:28

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, Mar 05, 2024 at 01:46:56PM +0100, Roberto Sassu wrote:
> On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean) wrote:
> > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > > > > from vfs_get_fscaps_nosec().
> > > > > > >
> > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > > > ---
> > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > >
> > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > --- a/security/commoncap.c
> > > > > > > +++ b/security/commoncap.c
> > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > */
> > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > {
> > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > + struct vfs_caps caps;
> > > > > > > int error;
> > > > > > >
> > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > > > > - return error > 0;
> > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > > > > + return error == 0;
> > > > > > > }
> > > > > > >
> > > > > > > /**
> > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > > > > {
> > > > > > > int error;
> > > > > > >
> > > > > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > >
> > > > > > Uhm, I see that the change is logically correct... but the original
> > > > > > code was not correct, since the EVM post hook is not called (thus the
> > > > > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > > > > which should be not).
> > > > > >
> > > > > > For completeness, the xattr change on a portable signature should not
> > > > > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > > > > However, since EVM allows same value change, we are here.
> > > > >
> > > > > I really don't understand EVM that well and am pretty hesitant to try an
> > > > > change any of the logic around it. But I'll hazard a thought: should EVM
> > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > situation?
> > > >
> > > > Uhm, I think it would not work without modifying
> > > > security_inode_need_killpriv() and the hook definition.
> > > >
> > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> > > > not be invoked. We would need to continue the loop and let EVM know
> > > > what is the current return value. Then EVM can reject the change.
> > > >
> > > > An alternative way would be to detect that actually we are setting the
> > > > same value for inode metadata, and maybe not returning 1 from
> > > > cap_inode_need_killpriv().
> > > >
> > > > I would prefer the second, since EVM allows same value change and we
> > > > would have an exception if there are fscaps.
> > > >
> > > > This solves only the case of portable signatures. We would need to
> > > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > > files.
> > >
> > > I see. In any case this sounds like a matter for a separate patch
> > > series.
> >
> > Agreed.
>
> Christian, how realistic is that we don't kill priv if we are setting
> the same owner?

Uhm, I would need to see the wider context of the proposed change. But
iiuc then you would be comparing current and new fscaps and if they are
identical you don't kill privs? I think that would work. But again, I
would need to see the actual context/change to say something meaningful.

2024-03-05 17:10:00

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, 2024-03-05 at 11:03 -0600, Seth Forshee (DigitalOcean) wrote:
> On Tue, Mar 05, 2024 at 05:35:11PM +0100, Roberto Sassu wrote:
> > On Tue, 2024-03-05 at 17:26 +0100, Christian Brauner wrote:
> > > On Tue, Mar 05, 2024 at 01:46:56PM +0100, Roberto Sassu wrote:
> > > > On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > > > > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > > > > > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > > > > > > > from vfs_get_fscaps_nosec().
> > > > > > > > > >
> > > > > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > > > > > > ---
> > > > > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > > > > >
> > > > > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > > > > --- a/security/commoncap.c
> > > > > > > > > > +++ b/security/commoncap.c
> > > > > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > > > > */
> > > > > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > > > > {
> > > > > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > > > > + struct vfs_caps caps;
> > > > > > > > > > int error;
> > > > > > > > > >
> > > > > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > > > > > > > - return error > 0;
> > > > > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > > > > > > > + return error == 0;
> > > > > > > > > > }
> > > > > > > > > >
> > > > > > > > > > /**
> > > > > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > > > > > > > {
> > > > > > > > > > int error;
> > > > > > > > > >
> > > > > > > > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > > > > >
> > > > > > > > > Uhm, I see that the change is logically correct... but the original
> > > > > > > > > code was not correct, since the EVM post hook is not called (thus the
> > > > > > > > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > > > > > > > which should be not).
> > > > > > > > >
> > > > > > > > > For completeness, the xattr change on a portable signature should not
> > > > > > > > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > > > > > > > However, since EVM allows same value change, we are here.
> > > > > > > >
> > > > > > > > I really don't understand EVM that well and am pretty hesitant to try an
> > > > > > > > change any of the logic around it. But I'll hazard a thought: should EVM
> > > > > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > > > > situation?
> > > > > > >
> > > > > > > Uhm, I think it would not work without modifying
> > > > > > > security_inode_need_killpriv() and the hook definition.
> > > > > > >
> > > > > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> > > > > > > not be invoked. We would need to continue the loop and let EVM know
> > > > > > > what is the current return value. Then EVM can reject the change.
> > > > > > >
> > > > > > > An alternative way would be to detect that actually we are setting the
> > > > > > > same value for inode metadata, and maybe not returning 1 from
> > > > > > > cap_inode_need_killpriv().
> > > > > > >
> > > > > > > I would prefer the second, since EVM allows same value change and we
> > > > > > > would have an exception if there are fscaps.
> > > > > > >
> > > > > > > This solves only the case of portable signatures. We would need to
> > > > > > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > > > > > files.
> > > > > >
> > > > > > I see. In any case this sounds like a matter for a separate patch
> > > > > > series.
> > > > >
> > > > > Agreed.
> > > >
> > > > Christian, how realistic is that we don't kill priv if we are setting
> > > > the same owner?
> > >
> > > Uhm, I would need to see the wider context of the proposed change. But
> > > iiuc then you would be comparing current and new fscaps and if they are
> > > identical you don't kill privs? I think that would work. But again, I
> > > would need to see the actual context/change to say something meaningful.
> >
> > Ok, basically a software vendor can ship binaries with a signature over
> > file metadata, including UID/GID, etc.
> >
> > A system can verify the signature through the public key of the
> > software vendor.
> >
> > The problem is if someone (or even tar), executes chown on that binary,
> > fscaps are lost. Thus, signature verification will fail from now on.
> >
> > EVM locks file metadata as soon as signature verification succeeds
> > (i.e. metadata are the same of those signed by the software vendor).
> >
> > EVM locking works if someone is trying to set different metadata. But,
> > if I try to chown to the same owner as the one stored in the inode, EVM
> > allows it but the capability LSM removes security.capability, thus
> > invalidating the signature.
> >
> > At least, it would be desirable that security.capability is not removed
> > when setting the same owner. If the owner is different, EVM will handle
> > that.
>
> When you say EVM "locks" file metadata, does that mean it prevents
> modification to file metadata?

Yes, but only when metadata are in the final state (what the software
vendor signed). Otherwise, modifications are still allowed.

That was needed to let tar set everything (xattrs and attrs).

> What about changes to file data? This will also result in removing
> fscaps xattrs. Does EVM also block changes to file data when signature
> verification succeeds?

That would be the job of IMA. EVM would not be able to detect that. If
the file has an EVM immutable and portable signature, IMA denies
changes to it (assuming that an IMA appraisal policy was loaded, and
that file matches a policy rule).

Roberto


2024-03-05 17:13:22

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, 2024-03-05 at 13:46 +0100, Roberto Sassu wrote:
> On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean) wrote:
> > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > > > > from vfs_get_fscaps_nosec().
> > > > > > >
> > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > > > ---
> > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > >
> > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > --- a/security/commoncap.c
> > > > > > > +++ b/security/commoncap.c
> > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > */
> > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > {
> > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > + struct vfs_caps caps;
> > > > > > > int error;
> > > > > > >
> > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > > > > - return error > 0;
> > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > > > > + return error == 0;
> > > > > > > }
> > > > > > >
> > > > > > > /**
> > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > > > > {
> > > > > > > int error;
> > > > > > >
> > > > > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > >
> > > > > > Uhm, I see that the change is logically correct... but the original
> > > > > > code was not correct, since the EVM post hook is not called (thus the
> > > > > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > > > > which should be not).
> > > > > >
> > > > > > For completeness, the xattr change on a portable signature should not
> > > > > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > > > > However, since EVM allows same value change, we are here.
> > > > >
> > > > > I really don't understand EVM that well and am pretty hesitant to try an
> > > > > change any of the logic around it. But I'll hazard a thought: should EVM
> > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > situation?
> > > >
> > > > Uhm, I think it would not work without modifying
> > > > security_inode_need_killpriv() and the hook definition.
> > > >
> > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> > > > not be invoked. We would need to continue the loop and let EVM know
> > > > what is the current return value. Then EVM can reject the change.
> > > >
> > > > An alternative way would be to detect that actually we are setting the
> > > > same value for inode metadata, and maybe not returning 1 from
> > > > cap_inode_need_killpriv().
> > > >
> > > > I would prefer the second, since EVM allows same value change and we
> > > > would have an exception if there are fscaps.
> > > >
> > > > This solves only the case of portable signatures. We would need to
> > > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > > files.
> > >
> > > I see. In any case this sounds like a matter for a separate patch
> > > series.
> >
> > Agreed.
>
> Christian, how realistic is that we don't kill priv if we are setting
> the same owner?
>
> Serge, would we be able to replace __vfs_removexattr() (or now
> vfs_get_fscaps_nosec()) with a security-equivalent alternative?

It seems it is not necessary.

security.capability removal occurs between evm_inode_setattr() and
evm_inode_post_setattr(), after the HMAC has been verified and before
the new HMAC is recalculated (without security.capability).

So, all good.

Christian, Seth, I pushed the kernel and the updated tests (all patches
are WIP):

https://github.com/robertosassu/linux/commits/evm-fscaps-v2/

https://github.com/robertosassu/ima-evm-utils/commits/evm-fscaps-v2/


The tests are passing:

https://github.com/robertosassu/ima-evm-utils/actions/runs/8159877004/job/22305521359

Roberto


2024-03-05 17:14:41

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, Mar 05, 2024 at 05:35:11PM +0100, Roberto Sassu wrote:
> On Tue, 2024-03-05 at 17:26 +0100, Christian Brauner wrote:
> > On Tue, Mar 05, 2024 at 01:46:56PM +0100, Roberto Sassu wrote:
> > > On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > > > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > > > > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > > > > > > from vfs_get_fscaps_nosec().
> > > > > > > > >
> > > > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > > > > > ---
> > > > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > > > >
> > > > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > > > --- a/security/commoncap.c
> > > > > > > > > +++ b/security/commoncap.c
> > > > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > > > */
> > > > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > > > {
> > > > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > > > + struct vfs_caps caps;
> > > > > > > > > int error;
> > > > > > > > >
> > > > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > > > > > > - return error > 0;
> > > > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > > > > > > + return error == 0;
> > > > > > > > > }
> > > > > > > > >
> > > > > > > > > /**
> > > > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > > > > > > {
> > > > > > > > > int error;
> > > > > > > > >
> > > > > > > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > > > >
> > > > > > > > Uhm, I see that the change is logically correct... but the original
> > > > > > > > code was not correct, since the EVM post hook is not called (thus the
> > > > > > > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > > > > > > which should be not).
> > > > > > > >
> > > > > > > > For completeness, the xattr change on a portable signature should not
> > > > > > > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > > > > > > However, since EVM allows same value change, we are here.
> > > > > > >
> > > > > > > I really don't understand EVM that well and am pretty hesitant to try an
> > > > > > > change any of the logic around it. But I'll hazard a thought: should EVM
> > > > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > > > situation?
> > > > > >
> > > > > > Uhm, I think it would not work without modifying
> > > > > > security_inode_need_killpriv() and the hook definition.
> > > > > >
> > > > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> > > > > > not be invoked. We would need to continue the loop and let EVM know
> > > > > > what is the current return value. Then EVM can reject the change.
> > > > > >
> > > > > > An alternative way would be to detect that actually we are setting the
> > > > > > same value for inode metadata, and maybe not returning 1 from
> > > > > > cap_inode_need_killpriv().
> > > > > >
> > > > > > I would prefer the second, since EVM allows same value change and we
> > > > > > would have an exception if there are fscaps.
> > > > > >
> > > > > > This solves only the case of portable signatures. We would need to
> > > > > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > > > > files.
> > > > >
> > > > > I see. In any case this sounds like a matter for a separate patch
> > > > > series.
> > > >
> > > > Agreed.
> > >
> > > Christian, how realistic is that we don't kill priv if we are setting
> > > the same owner?
> >
> > Uhm, I would need to see the wider context of the proposed change. But
> > iiuc then you would be comparing current and new fscaps and if they are
> > identical you don't kill privs? I think that would work. But again, I
> > would need to see the actual context/change to say something meaningful.
>
> Ok, basically a software vendor can ship binaries with a signature over
> file metadata, including UID/GID, etc.
>
> A system can verify the signature through the public key of the
> software vendor.
>
> The problem is if someone (or even tar), executes chown on that binary,
> fscaps are lost. Thus, signature verification will fail from now on.
>
> EVM locks file metadata as soon as signature verification succeeds
> (i.e. metadata are the same of those signed by the software vendor).
>
> EVM locking works if someone is trying to set different metadata. But,
> if I try to chown to the same owner as the one stored in the inode, EVM
> allows it but the capability LSM removes security.capability, thus
> invalidating the signature.
>
> At least, it would be desirable that security.capability is not removed
> when setting the same owner. If the owner is different, EVM will handle
> that.

When you say EVM "locks" file metadata, does that mean it prevents
modification to file metadata?

What about changes to file data? This will also result in removing
fscaps xattrs. Does EVM also block changes to file data when signature
verification succeeds?

2024-03-05 20:17:43

by Seth Forshee

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, Mar 05, 2024 at 06:11:45PM +0100, Roberto Sassu wrote:
> On Tue, 2024-03-05 at 13:46 +0100, Roberto Sassu wrote:
> > On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean) wrote:
> > > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > > > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > > > > > from vfs_get_fscaps_nosec().
> > > > > > > >
> > > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > > > > ---
> > > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > > >
> > > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > > --- a/security/commoncap.c
> > > > > > > > +++ b/security/commoncap.c
> > > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > > */
> > > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > > {
> > > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > > + struct vfs_caps caps;
> > > > > > > > int error;
> > > > > > > >
> > > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > > > > > - return error > 0;
> > > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > > > > > + return error == 0;
> > > > > > > > }
> > > > > > > >
> > > > > > > > /**
> > > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > > > > > {
> > > > > > > > int error;
> > > > > > > >
> > > > > > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > > >
> > > > > > > Uhm, I see that the change is logically correct... but the original
> > > > > > > code was not correct, since the EVM post hook is not called (thus the
> > > > > > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > > > > > which should be not).
> > > > > > >
> > > > > > > For completeness, the xattr change on a portable signature should not
> > > > > > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > > > > > However, since EVM allows same value change, we are here.
> > > > > >
> > > > > > I really don't understand EVM that well and am pretty hesitant to try an
> > > > > > change any of the logic around it. But I'll hazard a thought: should EVM
> > > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > > situation?
> > > > >
> > > > > Uhm, I think it would not work without modifying
> > > > > security_inode_need_killpriv() and the hook definition.
> > > > >
> > > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> > > > > not be invoked. We would need to continue the loop and let EVM know
> > > > > what is the current return value. Then EVM can reject the change.
> > > > >
> > > > > An alternative way would be to detect that actually we are setting the
> > > > > same value for inode metadata, and maybe not returning 1 from
> > > > > cap_inode_need_killpriv().
> > > > >
> > > > > I would prefer the second, since EVM allows same value change and we
> > > > > would have an exception if there are fscaps.
> > > > >
> > > > > This solves only the case of portable signatures. We would need to
> > > > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > > > files.
> > > >
> > > > I see. In any case this sounds like a matter for a separate patch
> > > > series.
> > >
> > > Agreed.
> >
> > Christian, how realistic is that we don't kill priv if we are setting
> > the same owner?
> >
> > Serge, would we be able to replace __vfs_removexattr() (or now
> > vfs_get_fscaps_nosec()) with a security-equivalent alternative?
>
> It seems it is not necessary.
>
> security.capability removal occurs between evm_inode_setattr() and
> evm_inode_post_setattr(), after the HMAC has been verified and before
> the new HMAC is recalculated (without security.capability).
>
> So, all good.
>
> Christian, Seth, I pushed the kernel and the updated tests (all patches
> are WIP):
>
> https://github.com/robertosassu/linux/commits/evm-fscaps-v2/
>
> https://github.com/robertosassu/ima-evm-utils/commits/evm-fscaps-v2/
>
>
> The tests are passing:
>
> https://github.com/robertosassu/ima-evm-utils/actions/runs/8159877004/job/22305521359

Thanks! I probably won't be able to take them exactly as-is due to other
changes for the next version (rebasing onto the changes to make IMA and
EVM LSMs, forbidding xattr handlers entirely for fscaps), but they will
serve as a good road map for what needs to happen.

2024-03-06 02:24:03

by Mimi Zohar

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, 2024-03-05 at 18:11 +0100, Roberto Sassu wrote:
> On Tue, 2024-03-05 at 13:46 +0100, Roberto Sassu wrote:
> > On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean)
> > > wrote:
> > > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean)
> > > > > > > wrote:
> > > > > > > > Use the vfs interfaces for fetching file capabilities for
> > > > > > > > killpriv
> > > > > > > > checks and from get_vfs_caps_from_disk(). While there, update
> > > > > > > > the
> > > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is
> > > > > > > > different
> > > > > > > > from vfs_get_fscaps_nosec().
> > > > > > > >
> > > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > > > > ---
> > > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > > >
> > > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > > --- a/security/commoncap.c
> > > > > > > > +++ b/security/commoncap.c
> > > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > > */
> > > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > > {
> > > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > > + struct vfs_caps caps;
> > > > > > > > int error;
> > > > > > > >
> > > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS,
> > > > > > > > NULL, 0);
> > > > > > > > - return error > 0;
> > > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is
> > > > > > > > unimportant */
> > > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry,
> > > > > > > > &caps);
> > > > > > > > + return error == 0;
> > > > > > > > }
> > > > > > > >
> > > > > > > > /**
> > > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap
> > > > > > > > *idmap, struct dentry *dentry)
> > > > > > > > {
> > > > > > > > int error;
> > > > > > > >
> > > > > > > > - error = __vfs_removexattr(idmap, dentry,
> > > > > > > > XATTR_NAME_CAPS);
> > > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > > >
> > > > > > > Uhm, I see that the change is logically correct... but the
> > > > > > > original
> > > > > > > code was not correct, since the EVM post hook is not called (thus
> > > > > > > the
> > > > > > > HMAC is broken, or an xattr change is allowed on a portable
> > > > > > > signature
> > > > > > > which should be not).
> > > > > > >
> > > > > > > For completeness, the xattr change on a portable signature should
> > > > > > > not
> > > > > > > happen in the first place, so cap_inode_killpriv() would not be
> > > > > > > called.
> > > > > > > However, since EVM allows same value change, we are here.
> > > > > >
> > > > > > I really don't understand EVM that well and am pretty hesitant to
> > > > > > try an
> > > > > > change any of the logic around it. But I'll hazard a thought: should
> > > > > > EVM
> > > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > > situation?
> > > > >
> > > > > Uhm, I think it would not work without modifying
> > > > > security_inode_need_killpriv() and the hook definition.
> > > > >
> > > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM
> > > > > would
> > > > > not be invoked. We would need to continue the loop and let EVM know
> > > > > what is the current return value. Then EVM can reject the change.
> > > > >
> > > > > An alternative way would be to detect that actually we are setting the
> > > > > same value for inode metadata, and maybe not returning 1 from
> > > > > cap_inode_need_killpriv().
> > > > >
> > > > > I would prefer the second, since EVM allows same value change and we
> > > > > would have an exception if there are fscaps.
> > > > >
> > > > > This solves only the case of portable signatures. We would need to
> > > > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > > > files.
> > > >
> > > > I see. In any case this sounds like a matter for a separate patch
> > > > series.
> > >
> > > Agreed.
> >
> > Christian, how realistic is that we don't kill priv if we are setting
> > the same owner?
> >
> > Serge, would we be able to replace __vfs_removexattr() (or now
> > vfs_get_fscaps_nosec()) with a security-equivalent alternative?
>
> It seems it is not necessary.
>
> security.capability removal occurs between evm_inode_setattr() and
> evm_inode_post_setattr(), after the HMAC has been verified and before
> the new HMAC is recalculated (without security.capability).
>
> So, all good.
>
> Christian, Seth, I pushed the kernel and the updated tests (all patches
> are WIP):
>
> https://github.com/robertosassu/linux/commits/evm-fscaps-v2/

Resetting the IMA status flag is insufficient. The EVM status needs to be reset
as well. Stefan's "ima: re-evaluate file integrity on file metadata change"
patch does something similar for overlay.

Mimi

https://lore.kernel.org/linux-integrity/[email protected]/

>
> https://github.com/robertosassu/ima-evm-utils/commits/evm-fscaps-v2/
>
>
> The tests are passing:
>
> https://github.com/robertosassu/ima-evm-utils/actions/runs/8159877004/job/22305521359
>
> Roberto
>
>


2024-03-06 08:26:33

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, 2024-03-05 at 21:17 -0500, Mimi Zohar wrote:
> On Tue, 2024-03-05 at 18:11 +0100, Roberto Sassu wrote:
> > On Tue, 2024-03-05 at 13:46 +0100, Roberto Sassu wrote:
> > > On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > > > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean)
> > > > wrote:
> > > > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean)
> > > > > > > > wrote:
> > > > > > > > > Use the vfs interfaces for fetching file capabilities for
> > > > > > > > > killpriv
> > > > > > > > > checks and from get_vfs_caps_from_disk(). While there, update
> > > > > > > > > the
> > > > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is
> > > > > > > > > different
> > > > > > > > > from vfs_get_fscaps_nosec().
> > > > > > > > >
> > > > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > > > > > ---
> > > > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > > > >
> > > > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > > > --- a/security/commoncap.c
> > > > > > > > > +++ b/security/commoncap.c
> > > > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > > > */
> > > > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > > > {
> > > > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > > > + struct vfs_caps caps;
> > > > > > > > > int error;
> > > > > > > > >
> > > > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS,
> > > > > > > > > NULL, 0);
> > > > > > > > > - return error > 0;
> > > > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is
> > > > > > > > > unimportant */
> > > > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry,
> > > > > > > > > &caps);
> > > > > > > > > + return error == 0;
> > > > > > > > > }
> > > > > > > > >
> > > > > > > > > /**
> > > > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap
> > > > > > > > > *idmap, struct dentry *dentry)
> > > > > > > > > {
> > > > > > > > > int error;
> > > > > > > > >
> > > > > > > > > - error = __vfs_removexattr(idmap, dentry,
> > > > > > > > > XATTR_NAME_CAPS);
> > > > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > > > >
> > > > > > > > Uhm, I see that the change is logically correct... but the
> > > > > > > > original
> > > > > > > > code was not correct, since the EVM post hook is not called (thus
> > > > > > > > the
> > > > > > > > HMAC is broken, or an xattr change is allowed on a portable
> > > > > > > > signature
> > > > > > > > which should be not).
> > > > > > > >
> > > > > > > > For completeness, the xattr change on a portable signature should
> > > > > > > > not
> > > > > > > > happen in the first place, so cap_inode_killpriv() would not be
> > > > > > > > called.
> > > > > > > > However, since EVM allows same value change, we are here.
> > > > > > >
> > > > > > > I really don't understand EVM that well and am pretty hesitant to
> > > > > > > try an
> > > > > > > change any of the logic around it. But I'll hazard a thought: should
> > > > > > > EVM
> > > > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > > > situation?
> > > > > >
> > > > > > Uhm, I think it would not work without modifying
> > > > > > security_inode_need_killpriv() and the hook definition.
> > > > > >
> > > > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM
> > > > > > would
> > > > > > not be invoked. We would need to continue the loop and let EVM know
> > > > > > what is the current return value. Then EVM can reject the change.
> > > > > >
> > > > > > An alternative way would be to detect that actually we are setting the
> > > > > > same value for inode metadata, and maybe not returning 1 from
> > > > > > cap_inode_need_killpriv().
> > > > > >
> > > > > > I would prefer the second, since EVM allows same value change and we
> > > > > > would have an exception if there are fscaps.
> > > > > >
> > > > > > This solves only the case of portable signatures. We would need to
> > > > > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > > > > files.
> > > > >
> > > > > I see. In any case this sounds like a matter for a separate patch
> > > > > series.
> > > >
> > > > Agreed.
> > >
> > > Christian, how realistic is that we don't kill priv if we are setting
> > > the same owner?
> > >
> > > Serge, would we be able to replace __vfs_removexattr() (or now
> > > vfs_get_fscaps_nosec()) with a security-equivalent alternative?
> >
> > It seems it is not necessary.
> >
> > security.capability removal occurs between evm_inode_setattr() and
> > evm_inode_post_setattr(), after the HMAC has been verified and before
> > the new HMAC is recalculated (without security.capability).
> >
> > So, all good.
> >
> > Christian, Seth, I pushed the kernel and the updated tests (all patches
> > are WIP):
> >
> > https://github.com/robertosassu/linux/commits/evm-fscaps-v2/
>
> Resetting the IMA status flag is insufficient. The EVM status needs to be reset
> as well. Stefan's "ima: re-evaluate file integrity on file metadata change"
> patch does something similar for overlay.

Both the IMA and EVM status are reset. The IMA one is reset based on
the evm_revalidate_status() call, similarly to ACLs.

Roberto

> Mimi
>
> https://lore.kernel.org/linux-integrity/[email protected]/
>
> >
> > https://github.com/robertosassu/ima-evm-utils/commits/evm-fscaps-v2/
> >
> >
> > The tests are passing:
> >
> > https://github.com/robertosassu/ima-evm-utils/actions/runs/8159877004/job/22305521359
> >
> > Roberto
> >
> >
>


2024-03-06 08:51:14

by Roberto Sassu

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Tue, 2024-03-05 at 14:17 -0600, Seth Forshee (DigitalOcean) wrote:
> On Tue, Mar 05, 2024 at 06:11:45PM +0100, Roberto Sassu wrote:
> > On Tue, 2024-03-05 at 13:46 +0100, Roberto Sassu wrote:
> > > On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > > > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean) wrote:
> > > > > > > > > Use the vfs interfaces for fetching file capabilities for killpriv
> > > > > > > > > checks and from get_vfs_caps_from_disk(). While there, update the
> > > > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is different
> > > > > > > > > from vfs_get_fscaps_nosec().
> > > > > > > > >
> > > > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <[email protected]>
> > > > > > > > > ---
> > > > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > > > >
> > > > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > > > --- a/security/commoncap.c
> > > > > > > > > +++ b/security/commoncap.c
> > > > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > > > */
> > > > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > > > {
> > > > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > > > + struct vfs_caps caps;
> > > > > > > > > int error;
> > > > > > > > >
> > > > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS, NULL, 0);
> > > > > > > > > - return error > 0;
> > > > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is unimportant */
> > > > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry, &caps);
> > > > > > > > > + return error == 0;
> > > > > > > > > }
> > > > > > > > >
> > > > > > > > > /**
> > > > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry)
> > > > > > > > > {
> > > > > > > > > int error;
> > > > > > > > >
> > > > > > > > > - error = __vfs_removexattr(idmap, dentry, XATTR_NAME_CAPS);
> > > > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > > > >
> > > > > > > > Uhm, I see that the change is logically correct... but the original
> > > > > > > > code was not correct, since the EVM post hook is not called (thus the
> > > > > > > > HMAC is broken, or an xattr change is allowed on a portable signature
> > > > > > > > which should be not).
> > > > > > > >
> > > > > > > > For completeness, the xattr change on a portable signature should not
> > > > > > > > happen in the first place, so cap_inode_killpriv() would not be called.
> > > > > > > > However, since EVM allows same value change, we are here.
> > > > > > >
> > > > > > > I really don't understand EVM that well and am pretty hesitant to try an
> > > > > > > change any of the logic around it. But I'll hazard a thought: should EVM
> > > > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > > > situation?
> > > > > >
> > > > > > Uhm, I think it would not work without modifying
> > > > > > security_inode_need_killpriv() and the hook definition.
> > > > > >
> > > > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM would
> > > > > > not be invoked. We would need to continue the loop and let EVM know
> > > > > > what is the current return value. Then EVM can reject the change.
> > > > > >
> > > > > > An alternative way would be to detect that actually we are setting the
> > > > > > same value for inode metadata, and maybe not returning 1 from
> > > > > > cap_inode_need_killpriv().
> > > > > >
> > > > > > I would prefer the second, since EVM allows same value change and we
> > > > > > would have an exception if there are fscaps.
> > > > > >
> > > > > > This solves only the case of portable signatures. We would need to
> > > > > > change cap_inode_need_killpriv() anyway to update the HMAC for mutable
> > > > > > files.
> > > > >
> > > > > I see. In any case this sounds like a matter for a separate patch
> > > > > series.
> > > >
> > > > Agreed.
> > >
> > > Christian, how realistic is that we don't kill priv if we are setting
> > > the same owner?
> > >
> > > Serge, would we be able to replace __vfs_removexattr() (or now
> > > vfs_get_fscaps_nosec()) with a security-equivalent alternative?
> >
> > It seems it is not necessary.
> >
> > security.capability removal occurs between evm_inode_setattr() and
> > evm_inode_post_setattr(), after the HMAC has been verified and before
> > the new HMAC is recalculated (without security.capability).
> >
> > So, all good.
> >
> > Christian, Seth, I pushed the kernel and the updated tests (all patches
> > are WIP):
> >
> > https://github.com/robertosassu/linux/commits/evm-fscaps-v2/
> >
> > https://github.com/robertosassu/ima-evm-utils/commits/evm-fscaps-v2/
> >
> >
> > The tests are passing:
> >
> > https://github.com/robertosassu/ima-evm-utils/actions/runs/8159877004/job/22305521359
>
> Thanks! I probably won't be able to take them exactly as-is due to other
> changes for the next version (rebasing onto the changes to make IMA and
> EVM LSMs, forbidding xattr handlers entirely for fscaps), but they will
> serve as a good road map for what needs to happen.

Welcome. Yes, both ima_inode_set_fscaps() and ima_inode_remove_fscaps()
will be registered as LSM hooks in ima_appraise.c. It will be probably
straightforward for you to make those changes, but if you have any
question, let me know.

Roberto


2024-03-06 13:02:25

by Mimi Zohar

[permalink] [raw]
Subject: Re: [PATCH v2 24/25] commoncap: use vfs fscaps interfaces

On Wed, 2024-03-06 at 09:25 +0100, Roberto Sassu wrote:
> On Tue, 2024-03-05 at 21:17 -0500, Mimi Zohar wrote:
> > On Tue, 2024-03-05 at 18:11 +0100, Roberto Sassu wrote:
> > > On Tue, 2024-03-05 at 13:46 +0100, Roberto Sassu wrote:
> > > > On Tue, 2024-03-05 at 10:12 +0100, Christian Brauner wrote:
> > > > > On Mon, Mar 04, 2024 at 10:56:17AM -0600, Seth Forshee (DigitalOcean)
> > > > > wrote:
> > > > > > On Mon, Mar 04, 2024 at 05:17:57PM +0100, Roberto Sassu wrote:
> > > > > > > On Mon, 2024-03-04 at 09:31 -0600, Seth Forshee (DigitalOcean)
> > > > > > > wrote:
> > > > > > > > On Mon, Mar 04, 2024 at 11:19:54AM +0100, Roberto Sassu wrote:
> > > > > > > > > On Wed, 2024-02-21 at 15:24 -0600, Seth Forshee (DigitalOcean)
> > > > > > > > > wrote:
> > > > > > > > > > Use the vfs interfaces for fetching file capabilities for
> > > > > > > > > > killpriv
> > > > > > > > > > checks and from get_vfs_caps_from_disk(). While there,
> > > > > > > > > > update
> > > > > > > > > > the
> > > > > > > > > > kerneldoc for get_vfs_caps_from_disk() to explain how it is
> > > > > > > > > > different
> > > > > > > > > > from vfs_get_fscaps_nosec().
> > > > > > > > > >
> > > > > > > > > > Signed-off-by: Seth Forshee (DigitalOcean) <
> > > > > > > > > > [email protected]>
> > > > > > > > > > ---
> > > > > > > > > > security/commoncap.c | 30 +++++++++++++-----------------
> > > > > > > > > > 1 file changed, 13 insertions(+), 17 deletions(-)
> > > > > > > > > >
> > > > > > > > > > diff --git a/security/commoncap.c b/security/commoncap.c
> > > > > > > > > > index a0ff7e6092e0..751bb26a06a6 100644
> > > > > > > > > > --- a/security/commoncap.c
> > > > > > > > > > +++ b/security/commoncap.c
> > > > > > > > > > @@ -296,11 +296,12 @@ int cap_capset(struct cred *new,
> > > > > > > > > > */
> > > > > > > > > > int cap_inode_need_killpriv(struct dentry *dentry)
> > > > > > > > > > {
> > > > > > > > > > - struct inode *inode = d_backing_inode(dentry);
> > > > > > > > > > + struct vfs_caps caps;
> > > > > > > > > > int error;
> > > > > > > > > >
> > > > > > > > > > - error = __vfs_getxattr(dentry, inode, XATTR_NAME_CAPS,
> > > > > > > > > > NULL, 0);
> > > > > > > > > > - return error > 0;
> > > > > > > > > > + /* Use nop_mnt_idmap for no mapping here as mapping is
> > > > > > > > > > unimportant */
> > > > > > > > > > + error = vfs_get_fscaps_nosec(&nop_mnt_idmap, dentry,
> > > > > > > > > > &caps);
> > > > > > > > > > + return error == 0;
> > > > > > > > > > }
> > > > > > > > > >
> > > > > > > > > > /**
> > > > > > > > > > @@ -323,7 +324,7 @@ int cap_inode_killpriv(struct mnt_idmap
> > > > > > > > > > *idmap, struct dentry *dentry)
> > > > > > > > > > {
> > > > > > > > > > int error;
> > > > > > > > > >
> > > > > > > > > > - error = __vfs_removexattr(idmap, dentry,
> > > > > > > > > > XATTR_NAME_CAPS);
> > > > > > > > > > + error = vfs_remove_fscaps_nosec(idmap, dentry);
> > > > > > > > >
> > > > > > > > > Uhm, I see that the change is logically correct... but the
> > > > > > > > > original
> > > > > > > > > code was not correct, since the EVM post hook is not called
> > > > > > > > > (thus
> > > > > > > > > the
> > > > > > > > > HMAC is broken, or an xattr change is allowed on a portable
> > > > > > > > > signature
> > > > > > > > > which should be not).
> > > > > > > > >
> > > > > > > > > For completeness, the xattr change on a portable signature
> > > > > > > > > should
> > > > > > > > > not
> > > > > > > > > happen in the first place, so cap_inode_killpriv() would not
> > > > > > > > > be
> > > > > > > > > called.
> > > > > > > > > However, since EVM allows same value change, we are here.
> > > > > > > >
> > > > > > > > I really don't understand EVM that well and am pretty hesitant
> > > > > > > > to
> > > > > > > > try an
> > > > > > > > change any of the logic around it. But I'll hazard a thought:
> > > > > > > > should
> > > > > > > > EVM
> > > > > > > > have a inode_need_killpriv hook which returns an error in this
> > > > > > > > situation?
> > > > > > >
> > > > > > > Uhm, I think it would not work without modifying
> > > > > > > security_inode_need_killpriv() and the hook definition.
> > > > > > >
> > > > > > > Since cap_inode_need_killpriv() returns 1, the loop stops and EVM
> > > > > > > would
> > > > > > > not be invoked. We would need to continue the loop and let EVM
> > > > > > > know
> > > > > > > what is the current return value. Then EVM can reject the change.
> > > > > > >
> > > > > > > An alternative way would be to detect that actually we are setting
> > > > > > > the
> > > > > > > same value for inode metadata, and maybe not returning 1 from
> > > > > > > cap_inode_need_killpriv().
> > > > > > >
> > > > > > > I would prefer the second, since EVM allows same value change and
> > > > > > > we
> > > > > > > would have an exception if there are fscaps.
> > > > > > >
> > > > > > > This solves only the case of portable signatures. We would need to
> > > > > > > change cap_inode_need_killpriv() anyway to update the HMAC for
> > > > > > > mutable
> > > > > > > files.
> > > > > >
> > > > > > I see. In any case this sounds like a matter for a separate patch
> > > > > > series.
> > > > >
> > > > > Agreed.
> > > >
> > > > Christian, how realistic is that we don't kill priv if we are setting
> > > > the same owner?
> > > >
> > > > Serge, would we be able to replace __vfs_removexattr() (or now
> > > > vfs_get_fscaps_nosec()) with a security-equivalent alternative?
> > >
> > > It seems it is not necessary.
> > >
> > > security.capability removal occurs between evm_inode_setattr() and
> > > evm_inode_post_setattr(), after the HMAC has been verified and before
> > > the new HMAC is recalculated (without security.capability).
> > >
> > > So, all good.
> > >
> > > Christian, Seth, I pushed the kernel and the updated tests (all patches
> > > are WIP):
> > >
> > > https://github.com/robertosassu/linux/commits/evm-fscaps-v2/
> >
> > Resetting the IMA status flag is insufficient. The EVM status needs to be
> > reset
> > as well. Stefan's "ima: re-evaluate file integrity on file metadata change"
> > patch does something similar for overlay.
>
> Both the IMA and EVM status are reset. The IMA one is reset based on
> the evm_revalidate_status() call, similarly to ACLs.

Agreed. Oh, evm_status is being reset in evm_inode_post_set_fscaps().

>
>
> > https://lore.kernel.org/linux-integrity/[email protected]/
> >
> > > https://github.com/robertosassu/ima-evm-utils/commits/evm-fscaps-v2/
> > >
> > >
> > > The tests are passing:
> > >
> > > https://github.com/robertosassu/ima-evm-utils/actions/runs/8159877004/job/22305521359
> > >
> > > Roberto
> > >
> > >
>
>