[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