Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

kernel: make groups_sort calling a responsibility group_info allocators

In testing, we found that nfsd threads may call set_groups in parallel
for the same entry cached in auth.unix.gid, racing in the call of
groups_sort, corrupting the groups for that entry and leading to
permission denials for the client.

This patch:
- Make groups_sort globally visible.
- Move the call to groups_sort to the modifiers of group_info
- Remove the call to groups_sort from set_groups

Link: http://lkml.kernel.org/r/20171211151420.18655-1-thiago.becker@gmail.com
Signed-off-by: Thiago Rafael Becker <thiago.becker@gmail.com>
Reviewed-by: Matthew Wilcox <mawilcox@microsoft.com>
Reviewed-by: NeilBrown <neilb@suse.com>
Acked-by: "J. Bruce Fields" <bfields@fieldses.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Martin Schwidefsky <schwidefsky@de.ibm.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Thiago Rafael Becker and committed by
Linus Torvalds
bdcf0a42 1f704fd0

+13 -2
+1
arch/s390/kernel/compat_linux.c
··· 263 263 return retval; 264 264 } 265 265 266 + groups_sort(group_info); 266 267 retval = set_current_groups(group_info); 267 268 put_group_info(group_info); 268 269
+3
fs/nfsd/auth.c
··· 60 60 gi->gid[i] = exp->ex_anon_gid; 61 61 else 62 62 gi->gid[i] = rqgi->gid[i]; 63 + 64 + /* Each thread allocates its own gi, no race */ 65 + groups_sort(gi); 63 66 } 64 67 } else { 65 68 gi = get_group_info(rqgi);
+1
include/linux/cred.h
··· 83 83 extern void set_groups(struct cred *, struct group_info *); 84 84 extern int groups_search(const struct group_info *, kgid_t); 85 85 extern bool may_setgroups(void); 86 + extern void groups_sort(struct group_info *); 86 87 87 88 /* 88 89 * The security context of a task
+3 -2
kernel/groups.c
··· 86 86 return gid_gt(a, b) - gid_lt(a, b); 87 87 } 88 88 89 - static void groups_sort(struct group_info *group_info) 89 + void groups_sort(struct group_info *group_info) 90 90 { 91 91 sort(group_info->gid, group_info->ngroups, sizeof(*group_info->gid), 92 92 gid_cmp, NULL); 93 93 } 94 + EXPORT_SYMBOL(groups_sort); 94 95 95 96 /* a simple bsearch */ 96 97 int groups_search(const struct group_info *group_info, kgid_t grp) ··· 123 122 void set_groups(struct cred *new, struct group_info *group_info) 124 123 { 125 124 put_group_info(new->group_info); 126 - groups_sort(group_info); 127 125 get_group_info(group_info); 128 126 new->group_info = group_info; 129 127 } ··· 206 206 return retval; 207 207 } 208 208 209 + groups_sort(group_info); 209 210 retval = set_current_groups(group_info); 210 211 put_group_info(group_info); 211 212
+1
kernel/uid16.c
··· 192 192 return retval; 193 193 } 194 194 195 + groups_sort(group_info); 195 196 retval = set_current_groups(group_info); 196 197 put_group_info(group_info); 197 198
+1
net/sunrpc/auth_gss/gss_rpc_xdr.c
··· 231 231 goto out_free_groups; 232 232 creds->cr_group_info->gid[i] = kgid; 233 233 } 234 + groups_sort(creds->cr_group_info); 234 235 235 236 return 0; 236 237 out_free_groups:
+1
net/sunrpc/auth_gss/svcauth_gss.c
··· 481 481 goto out; 482 482 rsci.cred.cr_group_info->gid[i] = kgid; 483 483 } 484 + groups_sort(rsci.cred.cr_group_info); 484 485 485 486 /* mech name */ 486 487 len = qword_get(&mesg, buf, mlen);
+2
net/sunrpc/svcauth_unix.c
··· 520 520 ug.gi->gid[i] = kgid; 521 521 } 522 522 523 + groups_sort(ug.gi); 523 524 ugp = unix_gid_lookup(cd, uid); 524 525 if (ugp) { 525 526 struct cache_head *ch; ··· 820 819 kgid_t kgid = make_kgid(&init_user_ns, svc_getnl(argv)); 821 820 cred->cr_group_info->gid[i] = kgid; 822 821 } 822 + groups_sort(cred->cr_group_info); 823 823 if (svc_getu32(argv) != htonl(RPC_AUTH_NULL) || svc_getu32(argv) != 0) { 824 824 *authp = rpc_autherr_badverf; 825 825 return SVC_DENIED;