[PATCH RFC v3 13/90] fs: never store an id that was lent to a cell

From: Josef Bacik

Date: Thu Nov 05 2026 - 10:05:20 EST


A block of ids is lent to a cell for as long as the cell exists, and is
later lent to another cell. Anything that is still owned by one of those
ids at that point would belong to the new cell.

Today a task in a user namespace can create a file on a host filesystem
that is not idmapped. fsuidgid_has_mapping() only asks whether the user
namespace of the superblock can represent the kuid, and the initial user
namespace can represent every kuid. The raw kuid ends up on disk.

Refuse to represent an id of the window in a filesystem that the cell does
not own. Do this in the helpers that decide whether an id can be
represented: kuid_has_fsmapping(), kgid_has_fsmapping(),
vfsuid_has_fsmapping() and vfsgid_has_fsmapping(). That covers creating a
file, chown, ACL entries, copy up in overlayfs, and the uid and gid mount
options of tmpfs, fuse and bpffs.

A mount that is idmapped for the cell keeps working. There the id has
already been translated to the on-disk id before it reaches these helpers.

Some details:

- The vfsuid and vfsgid helpers do not call the kuid and kgid helpers. On
a mount that is not idmapped they do not check for a mapping in the
user namespace of the filesystem today, and this patch does not change
that.
- The check looks at the value of the id, not at the caller. overlayfs
creates files in its upper layer with the credentials of the mounter
and the fsuid of the caller.
- The check is not in from_vfsuid(). That would give the inodes of the
kernel internal shmem mount an invalid owner, and notify_change()
would then fail every ftruncate() of a memfd.

With CONFIG_CELLS enabled, the host can no longer chown a file to an id of
2147483648 or above.

Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
include/linux/cell_ids.h | 38 ++++++++++++++++++++++++++++++++++++++
include/linux/mnt_idmapping.h | 13 +++++++++++--
2 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/include/linux/cell_ids.h b/include/linux/cell_ids.h
index dcb0a559d0e9..9bdd98e73a9d 100644
--- a/include/linux/cell_ids.h
+++ b/include/linux/cell_ids.h
@@ -62,4 +62,42 @@ static inline bool cell_range_in_window(u32 first, u32 count) { return false; }
static inline bool userns_is_cell(const struct user_namespace *ns) { return false; }

#endif
+
+/**
+ * kuid_storable - may a filesystem store a kuid?
+ * @fs_userns: the user namespace of the filesystem, sb->s_user_ns
+ * @kuid: the kuid, after the idmapping of the mount has been applied
+ *
+ * An id that was lent to a cell must not outlive the loan, so it never reaches
+ * a filesystem that the cell does not own. Through a mount that is idmapped for
+ * the cell the id has become the on-disk one long before it gets here.
+ *
+ * That @kuid is an id of this cell and not of another is for kuid_has_mapping()
+ * to say. Nobody but kuid_has_fsmapping() and vfsuid_has_fsmapping() has to ask.
+ *
+ * Return: true if @kuid is outside of the window, or @fs_userns is the user
+ * namespace of a cell.
+ */
+static inline bool kuid_storable(const struct user_namespace *fs_userns, kuid_t kuid)
+{
+ return likely(!cell_id_in_window(__kuid_val(kuid))) ||
+ userns_is_cell(fs_userns);
+}
+
+/**
+ * kgid_storable - may a filesystem store a kgid?
+ * @fs_userns: the user namespace of the filesystem, sb->s_user_ns
+ * @kgid: the kgid, after the idmapping of the mount has been applied
+ *
+ * See kuid_storable().
+ *
+ * Return: true if @kgid is outside of the window, or @fs_userns is the user
+ * namespace of a cell.
+ */
+static inline bool kgid_storable(const struct user_namespace *fs_userns, kgid_t kgid)
+{
+ return likely(!cell_id_in_window(__kgid_val(kgid))) ||
+ userns_is_cell(fs_userns);
+}
+
#endif /* _LINUX_CELL_IDS_H */
diff --git a/include/linux/mnt_idmapping.h b/include/linux/mnt_idmapping.h
index b5e0772aa190..dfe35b570317 100644
--- a/include/linux/mnt_idmapping.h
+++ b/include/linux/mnt_idmapping.h
@@ -4,6 +4,7 @@

#include <linux/types.h>
#include <linux/uidgid.h>
+#include <linux/cell_ids.h>

struct mnt_idmap;
struct user_namespace;
@@ -149,6 +150,8 @@ kgid_t from_vfsgid(const struct mnt_idmap *idmap,
static inline bool kuid_has_fsmapping(struct user_namespace *fs_userns,
kuid_t kuid)
{
+ if (!kuid_storable(fs_userns, kuid))
+ return false;
return kuid_has_mapping(fs_userns, kuid);
}

@@ -164,6 +167,8 @@ static inline bool kuid_has_fsmapping(struct user_namespace *fs_userns,
static inline bool kgid_has_fsmapping(struct user_namespace *fs_userns,
kgid_t kgid)
{
+ if (!kgid_storable(fs_userns, kgid))
+ return false;
return kgid_has_mapping(fs_userns, kgid);
}

@@ -183,7 +188,9 @@ static inline bool vfsuid_has_fsmapping(const struct mnt_idmap *idmap,
struct user_namespace *fs_userns,
vfsuid_t vfsuid)
{
- return uid_valid(from_vfsuid(idmap, fs_userns, vfsuid));
+ kuid_t kuid = from_vfsuid(idmap, fs_userns, vfsuid);
+
+ return uid_valid(kuid) && kuid_storable(fs_userns, kuid);
}

static inline bool vfsuid_has_mapping(struct user_namespace *userns,
@@ -221,7 +228,9 @@ static inline bool vfsgid_has_fsmapping(const struct mnt_idmap *idmap,
struct user_namespace *fs_userns,
vfsgid_t vfsgid)
{
- return gid_valid(from_vfsgid(idmap, fs_userns, vfsgid));
+ kgid_t kgid = from_vfsgid(idmap, fs_userns, vfsgid);
+
+ return gid_valid(kgid) && kgid_storable(fs_userns, kgid);
}

static inline bool vfsgid_has_mapping(struct user_namespace *userns,

--
2.55.0