[PATCH RFC v3 55/90] fs: let a filesystem hold ids that go with the cell

From: Josef Bacik

Date: Thu Nov 05 2026 - 10:27:42 EST


A filesystem that a cell does not own cannot hold an id that was lent to
the cell, because the id would outlive the cell. That is the right
default. It is wrong for a filesystem whose objects do not outlive the
cell either, because the kernel removes them together with the cell.

Add the superblock flag SB_I_LENT_IDS, with which a filesystem declares
that this is the case. The filesystem promises two things: whatever a cell
owns there is removed with the cell, and the filesystem itself controls
where a task of a cell can create anything. In return, a task of a cell
can create objects there and can chown them to another id of its cell.

Nobody else can. sb_takes_lent_kuid() and sb_takes_lent_kgid() require
that the caller is in a cell and that the id belongs to that cell. So root
on the host still cannot name such an id, and neither can a task of
another cell. The helpers are called where a superblock is at hand: in
fsuidgid_has_mapping() for a create, and in notify_change() for a chown.

No filesystem sets the flag yet.

Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
fs/attr.c | 8 ++++++--
include/linux/fs.h | 40 ++++++++++++++++++++++++++++++++++++++++
include/linux/fs/super_types.h | 1 +
3 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/fs/attr.c b/fs/attr.c
index 9706d370f34a..10cf1b3827c6 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -520,11 +520,15 @@ int notify_change(const struct mnt_idmap *idmap, struct dentry *dentry,
*/
if (ia_valid & ATTR_UID &&
!vfsuid_has_fsmapping(idmap, inode->i_sb->s_user_ns,
- attr->ia_vfsuid))
+ attr->ia_vfsuid) &&
+ !sb_takes_lent_kuid(inode->i_sb,
+ from_vfsuid(idmap, i_user_ns(inode), attr->ia_vfsuid)))
return -EOVERFLOW;
if (ia_valid & ATTR_GID &&
!vfsgid_has_fsmapping(idmap, inode->i_sb->s_user_ns,
- attr->ia_vfsgid))
+ attr->ia_vfsgid) &&
+ !sb_takes_lent_kgid(inode->i_sb,
+ from_vfsgid(idmap, i_user_ns(inode), attr->ia_vfsgid)))
return -EOVERFLOW;

/* Don't allow modifications of files with invalid uids or
diff --git a/include/linux/fs.h b/include/linux/fs.h
index ea9b9d5a667d..e1933649e885 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1565,6 +1565,44 @@ static inline void inode_fsgid_set(struct inode *inode,
inode->i_gid = mapped_fsgid(idmap, i_user_ns(inode));
}

+/**
+ * sb_takes_lent_kuid - may the caller have a filesystem hold a kuid of its cell?
+ * @sb: the superblock
+ * @kuid: the kuid, after the idmapping of the mount has been applied
+ *
+ * The ids of a cell are only lent to it, so a filesystem that the cell does not
+ * own does not hold them, see kuid_storable(). One with SB_I_LENT_IDS sees to it
+ * that what a cell owns there goes with the cell, and to where a task of a cell
+ * makes anything. So a task of a cell can have it hold the ids of that cell.
+ * Nobody else can: whoever is not in the cell is not held to any place.
+ *
+ * Return: true if it may.
+ */
+static inline bool sb_takes_lent_kuid(const struct super_block *sb, kuid_t kuid)
+{
+ return (sb->s_iflags & SB_I_LENT_IDS) &&
+ userns_is_cell(current_user_ns()) &&
+ kuid_has_mapping(current_user_ns(), kuid) &&
+ kuid_has_mapping(sb->s_user_ns, kuid);
+}
+
+/**
+ * sb_takes_lent_kgid - may the caller have a filesystem hold a kgid of its cell?
+ * @sb: the superblock
+ * @kgid: the kgid, after the idmapping of the mount has been applied
+ *
+ * See sb_takes_lent_kuid().
+ *
+ * Return: true if it may.
+ */
+static inline bool sb_takes_lent_kgid(const struct super_block *sb, kgid_t kgid)
+{
+ return (sb->s_iflags & SB_I_LENT_IDS) &&
+ userns_is_cell(current_user_ns()) &&
+ kgid_has_mapping(current_user_ns(), kgid) &&
+ kgid_has_mapping(sb->s_user_ns, kgid);
+}
+
/**
* fsuidgid_has_mapping() - check whether caller's fsuid/fsgid is mapped
* @sb: the superblock we want a mapping in
@@ -1587,6 +1625,8 @@ static inline bool fsuidgid_has_mapping(struct super_block *sb,
if (!uid_valid(kuid))
return false;
kgid = mapped_fsgid(idmap, fs_userns);
+ if (sb_takes_lent_kuid(sb, kuid) && sb_takes_lent_kgid(sb, kgid))
+ return true;
if (!gid_valid(kgid))
return false;
return kuid_has_fsmapping(fs_userns, kuid) &&
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index 72f4288e16c5..727a3c0a72dd 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -359,5 +359,6 @@ struct super_block {
#define SB_I_NOIDMAP 0x00002000 /* No idmapped mounts on this superblock */
#define SB_I_ALLOW_HSM 0x00004000 /* Allow HSM events on this superblock */
#define SB_I_NO_DATA_INTEGRITY 0x00008000 /* fs cannot guarantee data persistence on sync */
+#define SB_I_LENT_IDS 0x00010000 /* What a cell owns here goes with the cell */

#endif /* _LINUX_FS_SUPER_TYPES_H */

--
2.55.0