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 --- 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