[RFC PATCH v2 20/41] groups: add getgroups_r() and setgroups_r()
Jan Koester
jan.koester at tuxist.de
Wed Oct 21 03:15:40 UTC 2026
Supplementary groups as arrays of __kernel_gid_r_t, so they can include
UUID and GUID groups. Unlike the set*id_r() calls, "no id" is not
accepted in the list. getgroups() reports such groups as the overflow
gid.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester at tuxist.de>
---
include/linux/syscalls.h | 4 ++
kernel/groups.c | 79 +++++++++++++++++++++++++++++++++++++++-
kernel/sys_ni.c | 2 +
3 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 09fa1093a9..b4b9d5d76f 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -994,6 +994,10 @@ asmlinkage long sys_setfsuid_r(const struct __kernel_uid_r __user *uid,
struct __kernel_uid_r __user *old_uid);
asmlinkage long sys_setfsgid_r(const struct __kernel_uid_r __user *gid,
struct __kernel_uid_r __user *old_gid);
+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_open_tree(int dfd, const char __user *path, unsigned flags);
asmlinkage long sys_open_tree_attr(int dfd, const char __user *path,
unsigned flags,
diff --git a/kernel/groups.c b/kernel/groups.c
index 3aa0651d7a..8eb8260fc7 100644
--- a/kernel/groups.c
+++ b/kernel/groups.c
@@ -8,6 +8,7 @@
#include <linux/security.h>
#include <linux/sort.h>
#include <linux/syscalls.h>
+#include <linux/uidgid_r.h>
#include <linux/user_namespace.h>
#include <linux/vmalloc.h>
#include <linux/uaccess.h>
@@ -194,7 +195,9 @@ bool may_setgroups(void)
* without another task interfering.
*/
-SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
+static long do_setgroups(int gidsetsize, const void __user *grouplist,
+ int (*from_user)(struct group_info *,
+ const void __user *))
{
struct group_info *group_info;
int retval;
@@ -207,7 +210,7 @@ SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
group_info = groups_alloc(gidsetsize);
if (!group_info)
return -ENOMEM;
- retval = groups_from_user(group_info, grouplist);
+ retval = from_user(group_info, grouplist);
if (retval) {
put_group_info(group_info);
return retval;
@@ -220,6 +223,78 @@ SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
return retval;
}
+static int groups_from_user_classic(struct group_info *group_info,
+ const void __user *grouplist)
+{
+ return groups_from_user(group_info, (gid_t __user *)grouplist);
+}
+
+SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
+{
+ return do_setgroups(gidsetsize, grouplist, groups_from_user_classic);
+}
+
+/* group_info to a user-space array of __kernel_gid_r_t */
+static int groups_to_user_r(struct __kernel_uid_r __user *grouplist,
+ const struct group_info *group_info)
+{
+ struct user_namespace *user_ns = current_user_ns();
+ unsigned int i;
+
+ for (i = 0; i < group_info->ngroups; i++) {
+ if (put_kgid_r(user_ns, group_info->gid[i], grouplist + i))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+/* group_info from a user-space array of __kernel_gid_r_t; no "no id" here */
+static int groups_from_user_r(struct group_info *group_info,
+ const void __user *grouplist)
+{
+ const struct __kernel_uid_r __user *list = grouplist;
+ struct user_namespace *user_ns = current_user_ns();
+ struct __kernel_uid_r id;
+ unsigned int i;
+
+ for (i = 0; i < group_info->ngroups; i++) {
+ kgid_t kgid;
+
+ if (copy_from_user(&id, list + i, sizeof(id)))
+ return -EFAULT;
+ kgid = make_kgid_r(user_ns, &id);
+ if (!gid_valid(kgid))
+ return -EINVAL;
+ group_info->gid[i] = kgid;
+ }
+ return 0;
+}
+
+SYSCALL_DEFINE2(getgroups_r, int, gidsetsize,
+ struct __kernel_uid_r __user *, grouplist)
+{
+ const struct cred *cred = current_cred();
+ int i;
+
+ if (gidsetsize < 0)
+ return -EINVAL;
+
+ i = cred->group_info->ngroups;
+ if (gidsetsize) {
+ if (i > gidsetsize)
+ return -EINVAL;
+ if (groups_to_user_r(grouplist, cred->group_info))
+ return -EFAULT;
+ }
+ return i;
+}
+
+SYSCALL_DEFINE2(setgroups_r, int, gidsetsize,
+ const struct __kernel_uid_r __user *, grouplist)
+{
+ return do_setgroups(gidsetsize, grouplist, groups_from_user_r);
+}
+
/*
* Check whether we're fsgid/egid or in the supplemental group..
*/
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 01f14d7a4d..aa0b867925 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -113,6 +113,8 @@ COND_SYSCALL(setresgid_r);
COND_SYSCALL(getresgid_r);
COND_SYSCALL(setfsuid_r);
COND_SYSCALL(setfsgid_r);
+COND_SYSCALL(setgroups_r);
+COND_SYSCALL(getgroups_r);
COND_SYSCALL(mq_open);
COND_SYSCALL_COMPAT(mq_open);
COND_SYSCALL(mq_unlink);
--
2.56.0
More information about the Linux-security-module-archive
mailing list