[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