[RFC PATCH v2 21/41] fs: add chown_r(), lchown_r(), fchown_r() and fchownat_r()

Jan Koester jan.koester at tuxist.de
Wed Oct 21 03:15:41 UTC 2026


The chown family with __kernel_uid_r_t owner and group, so files can be
given UUID and GUID owners on filesystems that support them
(SB_I_UID_R); others refuse with -EOVERFLOW. NULL pointers, UID_R_NONE
and the classic -1 leave the owner or group unchanged.

The classic calls and the new ones share the lookup and setattr code,
which now works on a struct chown_ids. The classic calls still report a
malformed id only once the file is found, as before.

Assisted-by: Claude:claude-opus-5-5

Signed-off-by: Jan Koester <jan.koester at tuxist.de>
---
 fs/open.c                | 144 +++++++++++++++++++++++++++++++++++----
 include/linux/syscalls.h |  13 ++++
 2 files changed, 145 insertions(+), 12 deletions(-)

diff --git a/fs/open.c b/fs/open.c
index 6b1c14e684..26175b5aa8 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -785,7 +785,46 @@ static inline bool setattr_vfsgid(struct iattr *attr, kgid_t kgid)
 	return true;
 }
 
-int chown_common(const struct path *path, uid_t user, gid_t group)
+/*
+ * The new owner and group of a chown. Invalid ids stand for "unchanged";
+ * @err is an error of their conversion, reported once the path is found,
+ * which keeps the classic calls' order of errors.
+ */
+struct chown_ids {
+	kuid_t uid;
+	kgid_t gid;
+	int err;
+};
+
+static struct chown_ids chown_ids_classic(uid_t user, gid_t group)
+{
+	struct user_namespace *ns = current_user_ns();
+	struct chown_ids ids = {
+		.uid = make_kuid(ns, user),
+		.gid = make_kgid(ns, group),
+	};
+
+	if ((user != (uid_t)-1 && !uid_valid(ids.uid)) ||
+	    (group != (gid_t)-1 && !gid_valid(ids.gid)))
+		ids.err = -EINVAL;
+	return ids;
+}
+
+static int chown_ids_r(const struct __kernel_uid_r __user *user,
+		       const struct __kernel_uid_r __user *group,
+		       struct chown_ids *ids)
+{
+	struct user_namespace *ns = current_user_ns();
+	int err;
+
+	ids->err = 0;
+	err = get_kuid_r(ns, user, &ids->uid);
+	if (!err)
+		err = get_kgid_r(ns, group, &ids->gid);
+	return err;
+}
+
+static int chown_path(const struct path *path, const struct chown_ids *ids)
 {
 	struct mnt_idmap *idmap;
 	struct user_namespace *fs_userns;
@@ -793,11 +832,9 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
 	struct delegated_inode delegated_inode = { };
 	int error;
 	struct iattr newattrs;
-	kuid_t uid;
-	kgid_t gid;
 
-	uid = make_kuid(current_user_ns(), user);
-	gid = make_kgid(current_user_ns(), group);
+	if (ids->err)
+		return ids->err;
 
 	idmap = mnt_idmap(path->mnt);
 	fs_userns = i_user_ns(inode);
@@ -806,9 +843,9 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
 	newattrs.ia_vfsuid = INVALID_VFSUID;
 	newattrs.ia_vfsgid = INVALID_VFSGID;
 	newattrs.ia_valid =  ATTR_CTIME;
-	if ((user != (uid_t)-1) && !setattr_vfsuid(&newattrs, uid))
+	if (uid_valid(ids->uid) && !setattr_vfsuid(&newattrs, ids->uid))
 		return -EINVAL;
-	if ((group != (gid_t)-1) && !setattr_vfsgid(&newattrs, gid))
+	if (gid_valid(ids->gid) && !setattr_vfsgid(&newattrs, ids->gid))
 		return -EINVAL;
 	error = inode_lock_killable(inode);
 	if (error)
@@ -833,8 +870,15 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
 	return error;
 }
 
-int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
-		int flag)
+int chown_common(const struct path *path, uid_t user, gid_t group)
+{
+	struct chown_ids ids = chown_ids_classic(user, group);
+
+	return chown_path(path, &ids);
+}
+
+static int fchownat_ids(int dfd, const char __user *filename,
+			const struct chown_ids *ids, int flag)
 {
 	struct path path;
 	int error;
@@ -850,7 +894,7 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
 	if (!error) {
 		error = mnt_want_write(path.mnt);
 		if (!error) {
-			error = chown_common(&path, user, group);
+			error = chown_path(&path, ids);
 			mnt_drop_write(path.mnt);
 		}
 		path_put(&path);
@@ -862,6 +906,14 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
 	return error;
 }
 
+int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
+		int flag)
+{
+	struct chown_ids ids = chown_ids_classic(user, group);
+
+	return fchownat_ids(dfd, filename, &ids, flag);
+}
+
 SYSCALL_DEFINE5(fchownat, int, dfd, const char __user *, filename, uid_t, user,
 		gid_t, group, int, flag)
 {
@@ -879,7 +931,7 @@ SYSCALL_DEFINE3(lchown, const char __user *, filename, uid_t, user, gid_t, group
 			   AT_SYMLINK_NOFOLLOW);
 }
 
-int vfs_fchown(struct file *file, uid_t user, gid_t group)
+static int fchown_ids(struct file *file, const struct chown_ids *ids)
 {
 	int error;
 
@@ -887,11 +939,18 @@ int vfs_fchown(struct file *file, uid_t user, gid_t group)
 	if (error)
 		return error;
 	audit_file(file);
-	error = chown_common(&file->f_path, user, group);
+	error = chown_path(&file->f_path, ids);
 	mnt_drop_write_file(file);
 	return error;
 }
 
+int vfs_fchown(struct file *file, uid_t user, gid_t group)
+{
+	struct chown_ids ids = chown_ids_classic(user, group);
+
+	return fchown_ids(file, &ids);
+}
+
 int ksys_fchown(unsigned int fd, uid_t user, gid_t group)
 {
 	CLASS(fd, f)(fd);
@@ -907,6 +966,67 @@ SYSCALL_DEFINE3(fchown, unsigned int, fd, uid_t, user, gid_t, group)
 	return ksys_fchown(fd, user, group);
 }
 
+/*
+ * The *_r variants take __kernel_uid_r_t ids, which can also be UUIDs or
+ * GUIDs. A NULL pointer, UID_R_NONE or a classic -1 leave the owner or
+ * group unchanged.
+ */
+SYSCALL_DEFINE5(fchownat_r, int, dfd, const char __user *, filename,
+		const struct __kernel_uid_r __user *, user,
+		const struct __kernel_uid_r __user *, group, int, flag)
+{
+	struct chown_ids ids;
+	int error;
+
+	error = chown_ids_r(user, group, &ids);
+	if (error)
+		return error;
+	return fchownat_ids(dfd, filename, &ids, flag);
+}
+
+SYSCALL_DEFINE3(chown_r, const char __user *, filename,
+		const struct __kernel_uid_r __user *, user,
+		const struct __kernel_uid_r __user *, group)
+{
+	struct chown_ids ids;
+	int error;
+
+	error = chown_ids_r(user, group, &ids);
+	if (error)
+		return error;
+	return fchownat_ids(AT_FDCWD, filename, &ids, 0);
+}
+
+SYSCALL_DEFINE3(lchown_r, const char __user *, filename,
+		const struct __kernel_uid_r __user *, user,
+		const struct __kernel_uid_r __user *, group)
+{
+	struct chown_ids ids;
+	int error;
+
+	error = chown_ids_r(user, group, &ids);
+	if (error)
+		return error;
+	return fchownat_ids(AT_FDCWD, filename, &ids, AT_SYMLINK_NOFOLLOW);
+}
+
+SYSCALL_DEFINE3(fchown_r, unsigned int, fd,
+		const struct __kernel_uid_r __user *, user,
+		const struct __kernel_uid_r __user *, group)
+{
+	struct chown_ids ids;
+	int error;
+
+	error = chown_ids_r(user, group, &ids);
+	if (error)
+		return error;
+
+	CLASS(fd, f)(fd);
+	if (fd_empty(f))
+		return -EBADF;
+	return fchown_ids(fd_file(f), &ids);
+}
+
 static inline int file_get_write_access(struct file *f)
 {
 	int error;
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index b4b9d5d76f..4a18b59631 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -998,6 +998,19 @@ asmlinkage long sys_getgroups_r(int gidsetsize,
 				struct __kernel_uid_r __user *grouplist);
 asmlinkage long sys_setgroups_r(int gidsetsize,
 				const struct __kernel_uid_r __user *grouplist);
+asmlinkage long sys_chown_r(const char __user *filename,
+			    const struct __kernel_uid_r __user *user,
+			    const struct __kernel_uid_r __user *group);
+asmlinkage long sys_lchown_r(const char __user *filename,
+			     const struct __kernel_uid_r __user *user,
+			     const struct __kernel_uid_r __user *group);
+asmlinkage long sys_fchown_r(unsigned int fd,
+			     const struct __kernel_uid_r __user *user,
+			     const struct __kernel_uid_r __user *group);
+asmlinkage long sys_fchownat_r(int dfd, const char __user *filename,
+			       const struct __kernel_uid_r __user *user,
+			       const struct __kernel_uid_r __user *group,
+			       int flag);
 asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags);
 asmlinkage long sys_open_tree_attr(int dfd, const char __user *path,
 				   unsigned flags,
-- 
2.56.0




More information about the Linux-security-module-archive mailing list