Linux: Implement per-thread user and group IDs

Message ID 87mup9okd3.fsf@oldenburg2.str.redhat.com
State New, archived
Headers

Commit Message

Florian Weimer Dec. 13, 2018, 9:18 a.m. UTC
  This functionality is needed by many file servers, including Samba and
nfs-ganesha.

The patch context depends on the earlier patch for per-thread file
system attributes:

  <https://sourceware.org/ml/libc-alpha/2018-11/msg00866.html>

(Although there is no functional dependency.)

The abilist diffs are not included in this patch because maintaining
them causes too much churn.

Thanks,
Florian

-----
This commit adds the functions pthread_attr_setperthreadids_np and
pthread_attr_getperthreadids_np.  Threads created with the new flag
will be exempted from the setxid broadcast.  setuid and related
functions will only update the credentials for the current thread.

Multi-threaded file servers typically need this functionality and
call the system calls directly to implement this.

2018-12-12  Florian Weimer  <fweimer@redhat.com>

	* sysdeps/nptl/pthread.h (pthread_attr_setperthreadids_np)
	(pthread_attr_getperthreadids_np): Declare.
	* nptl/Makefile (routines): Add pthread_attr_setperthreadids_np,
	pthread_attr_getperthreadids_np.
	* nptl/Versions (GLIBC_2.29): Export
	pthread_attr_setperthreadids_np, pthread_attr_getperthreadids_np.
	* nptl/allocatestack.c (thread_excluded_from_setxid_broadcast):
	New function.
	(__nptl_setxid): Use it.
	* nptl/pthreadP.h (nptl_current_thread_has_separate_ids): New
	function.
	* susdeps/nptl/setxid.h (INLINE_SETXID_SYSCALL): Check
	nptl_current_thread_has_separate_ids before setxid broadcast.
	* nptl/pthread_attr_setperthreadids_np.c: New file
	* nptl/pthread_attr_getperthreadids_np.c: Likewise.
	* sysdeps/nptl/internaltypes.h (ATTR_FLAG_PERTHREADIDS)
	(ATTR_FLAGS_IGNORED_ATTR, ATTR_FLAGS_INHERITED): New macros.
	* nptl/pthread_create.c (__pthread_create_2_1): Use
	ATTR_FLAGS_IGNORED_ATTR and ATTR_FLAGS_INHERITED to compute the
	flags for the new thread.
	* nptl/tst-pthread-perthreadids.c: New file.
	* support/Makefile (libsupport-routines): Add xgetresgid,
	xgetresuid.
	* support/xgetresgid.c: New file.
	* support/xsetresgid.c: Likewise.
	* support/xunistd.h (xgetresuid, xgetresgid): Declare.
	* sysdeps/unix/sysv/linux/aarch64/libc.abilist (GLIBC_2.29): Add
	pthread_attr_setperthreadids_np, pthread_attr_getperthreadids_np.
	* sysdeps/unix/sysv/linux/alpha/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/arm/libc.abilist (GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/hppa/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/i386/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/ia64/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/microblaze/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
	(GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
	(GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
	(GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
	(GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/nios2/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
	(GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
	(GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/powerpc/powerpc64/libc-le.abilist
	(GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/powerpc/powerpc64/libc.abilist
	(GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/sh/libc.abilist (GLIBC_2.29): Likewise.
	* sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/x86_64/64/libc.abilist (GLIBC_2.29):
	Likewise.
	* sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist (GLIBC_2.29):
	Likewise.
  

Comments

Joseph Myers Dec. 13, 2018, 5:38 p.m. UTC | #1
It has previously been discussed that it would be desirable for the kernel 
to have whole-process setXid syscalls 
<https://sourceware.org/glibc/wiki/Development_Todo/Master#setXid_issues>.  
Given the per-thread ID feature, would you expect any such future kernel 
feature to provide some way of exempting particular threads from the 
whole-process syscalls, or would you expect glibc to continue to use the 
signal-based mechanism in processes with threads with per-thread IDs?
  
Florian Weimer Dec. 13, 2018, 5:52 p.m. UTC | #2
* Joseph Myers:

> It has previously been discussed that it would be desirable for the kernel 
> to have whole-process setXid syscalls 
> <https://sourceware.org/glibc/wiki/Development_Todo/Master#setXid_issues>.  
> Given the per-thread ID feature, would you expect any such future kernel 
> feature to provide some way of exempting particular threads from the 
> whole-process syscalls, or would you expect glibc to continue to use the 
> signal-based mechanism in processes with threads with per-thread IDs?

It's hard to know without the exact mechanism, but I think the proposed
interface is compatible with a typical implementation: if we get
something to unshare UIDs/GIDs, we would keep one sharing group for the
main thread and any threads that do not use per-thread IDs, and each
thread with per-thread IDs would be put into a singleton group of its
own.  The semantics would then closely match what we have today with
signal delivery and this patch, which makes the per-thread nature of IDs
sticky (for subthreads).

In today's setting, we could reasily make a thread enter the non-shared
group of threads even if created from a thread with per-thread IDs.  But
I think this is too confusing, so I implemented the inheritance of the
per-thread flag, and this also allows us to use the most likely kernel
extension in this area (if it ever materializes).

Unfortunately, this model is different from the CLONE_FS approach from
the other patch, where it is possible to build hierarchies of threads
sharing or not sharing the file system attributes.  But implementing
something like that would be quite hard without kernel support, and we
can't even know that future kernel support will behave in this way.

Thanks,
Florian
  
Florian Weimer Feb. 8, 2019, 12:34 p.m. UTC | #3
* Florian Weimer:

> This functionality is needed by many file servers, including Samba and
> nfs-ganesha.
>
> The patch context depends on the earlier patch for per-thread file
> system attributes:
>
>   <https://sourceware.org/ml/libc-alpha/2018-11/msg00866.html>
>
> (Although there is no functional dependency.)
>
> The abilist diffs are not included in this patch because maintaining
> them causes too much churn.
>
> Thanks,
> Florian
>
> -----
> This commit adds the functions pthread_attr_setperthreadids_np and
> pthread_attr_getperthreadids_np.  Threads created with the new flag
> will be exempted from the setxid broadcast.  setuid and related
> functions will only update the credentials for the current thread.
>
> Multi-threaded file servers typically need this functionality and
> call the system calls directly to implement this.
>
> 2018-12-12  Florian Weimer  <fweimer@redhat.com>
>
> 	* sysdeps/nptl/pthread.h (pthread_attr_setperthreadids_np)
> 	(pthread_attr_getperthreadids_np): Declare.
> 	* nptl/Makefile (routines): Add pthread_attr_setperthreadids_np,
> 	pthread_attr_getperthreadids_np.
> 	* nptl/Versions (GLIBC_2.29): Export

Ping?

Original patch: <https://sourceware.org/ml/libc-alpha/2018-12/msg00461.html>

Do we want this at all?  If yes, I will rebase the patch to current
master and resubmit.

Thanks,
Florian
  

Patch

diff --git a/NEWS b/NEWS
index 190b0ddb57..1d1c193743 100644
--- a/NEWS
+++ b/NEWS
@@ -51,6 +51,12 @@  Major new features:
   pthread_attr_setperthreadfs_np and pthread_attr_getperthreadfs_np have
   been added in support of that.
 
+* On Linux, threads can now be created in such a way that they retain
+  per-thread user and group IDs.  (This has always been the way how the
+  Linux kernel implements threads.)  The functions
+  pthread_attr_setperthreadids_np and pthread_attr_getperthreadids_np have
+  been added in support of that.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * The glibc.tune tunable namespace has been renamed to glibc.cpu and the
diff --git a/nptl/Makefile b/nptl/Makefile
index 8a071af5c7..7e82f0bdbf 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -31,7 +31,8 @@  routines = alloca_cutoff forward libc-lowlevellock libc-cancellation \
 	   libc-cleanup libc_pthread_init libc_multiple_threads \
 	   register-atfork pthread_atfork pthread_self thrd_current \
 	   thrd_equal thrd_sleep thrd_yield \
-	   pthread_attr_setperthreadfs_np pthread_attr_getperthreadfs_np
+	   pthread_attr_setperthreadfs_np pthread_attr_getperthreadfs_np \
+	   pthread_attr_setperthreadids_np pthread_attr_getperthreadids_np
 shared-only-routines = forward
 static-only-routines = pthread_atfork
 
@@ -328,7 +329,8 @@  tests-internal := tst-rwlock19 tst-rwlock20 \
 		  tst-mutexpi8 tst-mutexpi8-static
 
 xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
-	tst-mutexpp1 tst-mutexpp6 tst-mutexpp10
+	tst-mutexpp1 tst-mutexpp6 tst-mutexpp10 \
+	tst-pthread-perthreadids
 test-srcs = tst-oddstacklimit
 
 # Test expected to fail on most targets (except x86_64) due to bug
diff --git a/nptl/Versions b/nptl/Versions
index ba71f85411..95490322d6 100644
--- a/nptl/Versions
+++ b/nptl/Versions
@@ -35,6 +35,8 @@  libc {
   GLIBC_2.29 {
     pthread_attr_setperthreadfs_np;
     pthread_attr_getperthreadfs_np;
+    pthread_attr_setperthreadids_np;
+    pthread_attr_getperthreadids_np;
   }
   GLIBC_PRIVATE {
     __libc_alloca_cutoff;
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index 04e3f08465..d4c25a951f 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -1114,6 +1114,14 @@  __nptl_setxid_error (struct xid_command *cmdp, int error)
   while (atomic_compare_and_exchange_bool_acq (&cmdp->error, error, -1));
 }
 
+/* The current thread and threads with per-thread user and group IDs
+   are not part of the setxid broadcast.  */
+static inline bool
+thread_excluded_from_setxid_broadcast (struct pthread *self, struct pthread *t)
+{
+  return t == self || (t->flags & ATTR_FLAG_PERTHREADIDS);
+}
+
 int
 attribute_hidden
 __nptl_setxid (struct xid_command *cmdp)
@@ -1127,13 +1135,14 @@  __nptl_setxid (struct xid_command *cmdp)
   cmdp->error = -1;
 
   struct pthread *self = THREAD_SELF;
+  assert (!nptl_current_thread_has_separate_ids ());
 
   /* Iterate over the list with system-allocated threads first.  */
   list_t *runp;
   list_for_each (runp, &stack_used)
     {
       struct pthread *t = list_entry (runp, struct pthread, list);
-      if (t == self)
+      if (thread_excluded_from_setxid_broadcast (self, t))
 	continue;
 
       setxid_mark_thread (cmdp, t);
@@ -1143,7 +1152,7 @@  __nptl_setxid (struct xid_command *cmdp)
   list_for_each (runp, &__stack_user)
     {
       struct pthread *t = list_entry (runp, struct pthread, list);
-      if (t == self)
+      if (thread_excluded_from_setxid_broadcast (self, t))
 	continue;
 
       setxid_mark_thread (cmdp, t);
@@ -1159,7 +1168,7 @@  __nptl_setxid (struct xid_command *cmdp)
       list_for_each (runp, &stack_used)
 	{
 	  struct pthread *t = list_entry (runp, struct pthread, list);
-	  if (t == self)
+	  if (thread_excluded_from_setxid_broadcast (self, t))
 	    continue;
 
 	  signalled += setxid_signal_thread (cmdp, t);
@@ -1168,7 +1177,7 @@  __nptl_setxid (struct xid_command *cmdp)
       list_for_each (runp, &__stack_user)
 	{
 	  struct pthread *t = list_entry (runp, struct pthread, list);
-	  if (t == self)
+	  if (thread_excluded_from_setxid_broadcast (self, t))
 	    continue;
 
 	  signalled += setxid_signal_thread (cmdp, t);
@@ -1189,7 +1198,7 @@  __nptl_setxid (struct xid_command *cmdp)
   list_for_each (runp, &stack_used)
     {
       struct pthread *t = list_entry (runp, struct pthread, list);
-      if (t == self)
+      if (thread_excluded_from_setxid_broadcast (self, t))
 	continue;
 
       setxid_unmark_thread (cmdp, t);
@@ -1198,7 +1207,7 @@  __nptl_setxid (struct xid_command *cmdp)
   list_for_each (runp, &__stack_user)
     {
       struct pthread *t = list_entry (runp, struct pthread, list);
-      if (t == self)
+      if (thread_excluded_from_setxid_broadcast (self, t))
 	continue;
 
       setxid_unmark_thread (cmdp, t);
diff --git a/nptl/pthreadP.h b/nptl/pthreadP.h
index 7f16ba9800..7a1f965d9e 100644
--- a/nptl/pthreadP.h
+++ b/nptl/pthreadP.h
@@ -655,6 +655,14 @@  check_stacksize_attr (size_t st)
   return EINVAL;
 }
 
+/* Return true if the current thread has per-thread user and group
+   IDS.  */
+static inline bool
+nptl_current_thread_has_separate_ids (void)
+{
+  return THREAD_SELF->flags & ATTR_FLAG_PERTHREADIDS;
+}
+
 #define ASSERT_TYPE_SIZE(type, size) 					\
   _Static_assert (sizeof (type) == size,				\
 		  "sizeof (" #type ") != " #size)
diff --git a/nptl/pthread_attr_getperthreadids_np.c b/nptl/pthread_attr_getperthreadids_np.c
new file mode 100644
index 0000000000..cfadfb3bed
--- /dev/null
+++ b/nptl/pthread_attr_getperthreadids_np.c
@@ -0,0 +1,27 @@ 
+/* Read the per-thread user/group IDs flag in thread attributes.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <pthread.h>
+#include <internaltypes.h>
+
+int
+pthread_attr_getperthreadids_np (const pthread_attr_t *attr)
+{
+  struct pthread_attr *iattr = (struct pthread_attr *) attr;
+  return (iattr->flags & ATTR_FLAG_PERTHREADIDS) != 0;
+}
diff --git a/nptl/pthread_attr_setperthreadids_np.c b/nptl/pthread_attr_setperthreadids_np.c
new file mode 100644
index 0000000000..84d68e0e4f
--- /dev/null
+++ b/nptl/pthread_attr_setperthreadids_np.c
@@ -0,0 +1,30 @@ 
+/* Change the per-thread user/group IDs flag in thread attributes.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <pthread.h>
+#include <internaltypes.h>
+
+void
+pthread_attr_setperthreadids_np (pthread_attr_t *attr, int enabled)
+{
+  struct pthread_attr *iattr = (struct pthread_attr *) attr;
+  if (enabled)
+    iattr->flags |= ATTR_FLAG_PERTHREADIDS;
+  else
+    iattr->flags &= ~ATTR_FLAG_PERTHREADIDS;
+}
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index fe75d04113..cf9dae201b 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -700,8 +700,8 @@  __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
 
   /* Copy the thread attribute flags.  */
   struct pthread *self = THREAD_SELF;
-  pd->flags = ((iattr->flags & ~(ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET))
-	       | (self->flags & (ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)));
+  pd->flags = ((iattr->flags & ~ATTR_FLAGS_IGNORED_ATTR)
+	       | (self->flags & ATTR_FLAGS_INHERITED));
 
   /* Initialize the field for the ID of the thread which is waiting
      for us.  This is a self-reference in case the thread is created
diff --git a/nptl/tst-pthread-perthreadids.c b/nptl/tst-pthread-perthreadids.c
new file mode 100644
index 0000000000..ad021e1255
--- /dev/null
+++ b/nptl/tst-pthread-perthreadids.c
@@ -0,0 +1,426 @@ 
+/* Test per-thread user and group IDs.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+enum operation
+{
+ OP_NONE,
+ OP_SETUID,
+ OP_SETEUID,
+ OP_SETREUID,
+ OP_SETRESUID,
+ OP_SETGID,
+ OP_SETEGID,
+ OP_SETREGID,
+ OP_SETRESGID,
+ OP_SETGROUPS,
+};
+
+static const char *
+operation_string (enum operation op)
+{
+  switch (op)
+    {
+    case OP_NONE:
+      return "<none>";
+    case OP_SETUID:
+      return "OP_SETUID";
+    case OP_SETEUID:
+      return "OP_SETEUID";
+    case OP_SETREUID:
+      return "OP_SETREUID";
+    case OP_SETRESUID:
+      return "OP_SETRESUID";
+    case OP_SETGID:
+      return "OP_SETGID";
+    case OP_SETEGID:
+      return "OP_SETEGID";
+    case OP_SETREGID:
+      return "OP_SETREGID";
+    case OP_SETRESGID:
+      return "OP_SETRESGID";
+    case OP_SETGROUPS:
+      return "OP_SETGROUPS";
+    }
+
+  FAIL_EXIT1 ("invalid operation: %d", (int) op);
+}
+
+struct test_case
+{
+  enum operation op;
+  int args[3];
+
+  /* Expected UIDs and GIDs are only used if the current thread has
+     made changes.  */
+  uid_t expected_uid[3];
+  gid_t expected_gid[3];
+};
+
+static const struct test_case test_cases[] =
+  {
+   { OP_NONE, { 0, }, { 0, 0, 0}, {0, 0, 0} },
+
+   { OP_SETUID, { 1, }, { 1, 1, 1 }, { 0, } },
+   { OP_SETEUID, { 2, }, { 0, 2, }, { 0, } },
+   { OP_SETREUID, { 3, 4, }, { 3, 4, 4 }, { 0, } },
+   { OP_SETRESUID, { 3, 4, 5 }, { 3, 4, 5 }, { 0, } },
+
+   { OP_SETGID, { 6, }, { 0, }, { 6, 6, 6 } },
+   { OP_SETEGID, { 7, }, { 0, }, { 0, 7, } },
+   { OP_SETREGID, { 8, 9, }, { 0, }, { 8, 9, 9 } },
+   { OP_SETRESGID, { 10, 11, 12 }, { 0, }, { 10, 11, 12 } },
+
+   { OP_SETGROUPS, { -1, }, { 0, }, { 0, } },
+   { OP_SETGROUPS, { 13, -1 }, { 0, }, { 0, } },
+   { OP_SETGROUPS, { 13, 14, -1 }, { 0, }, { 0, } },
+   { OP_SETGROUPS, { 13, 14, 15 }, { 0, }, { 0, } },
+
+   /* Final round of checks.  */
+   { OP_NONE, { 0, }, { 0, }, {0, } },
+  };
+
+static size_t
+supplemetary_count (const struct test_case *test)
+{
+  TEST_COMPARE (test->op, OP_SETGROUPS);
+  size_t count = 0;
+  while (count < array_length (test->args))
+    {
+      if (test->args[count] < 0)
+        break;
+      ++count;
+    }
+  return count;
+}
+
+static void
+test_case_run (const struct test_case *test)
+{
+  int ret = -1;
+  switch (test->op)
+    {
+    case OP_NONE:
+      return;
+
+    case OP_SETUID:
+      ret = setuid (test->args[0]);
+      break;
+    case OP_SETEUID:
+      ret = seteuid (test->args[0]);
+      break;
+    case OP_SETREUID:
+      ret = setreuid (test->args[0], test->args[1]);
+      break;
+    case OP_SETRESUID:
+      ret = setresuid (test->args[0], test->args[1], test->args[2]);
+      break;
+
+    case OP_SETGID:
+      ret = setgid (test->args[0]);
+      break;
+    case OP_SETEGID:
+      ret = setegid (test->args[0]);
+      break;
+    case OP_SETREGID:
+      ret = setregid (test->args[0], test->args[1]);
+      break;
+    case OP_SETRESGID:
+      ret = setresgid (test->args[0], test->args[1], test->args[2]);
+      break;
+
+    case OP_SETGROUPS:
+      {
+        gid_t groups[] = { test->args[0], test->args[1], test->args[2] };
+        ret = setgroups (supplemetary_count (test), groups);
+      }
+    }
+
+  if (ret != 0)
+    FAIL_EXIT1 ("%s (%d, %d, %d): %m (%d)",
+                operation_string (test->op),
+                test->args[0], test->args[1], test->args[2], errno);
+}
+
+/* Used to synchronize between threads changing UIDs/GIDs.  */
+static pthread_barrier_t barrier;
+
+/* Used to avoid interleaving the checking phase between threads.  */
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* Argument to the thread.  */
+struct thread_argument
+{
+  size_t thread_index;
+  /* Do not actually perform the operation, only verify the result.
+     Used for threads participating in the setxid broadcast.  */
+  bool suppress_operation;
+
+  /* Run the actual test in a newly created thread, with default
+     attributes.  */
+  bool indirect;
+};
+
+static struct thread_argument *
+create_thread_argument (size_t index, bool suppress, bool indirect)
+{
+  struct thread_argument *result = xmalloc (sizeof (*result));
+  result->thread_index = index;
+  result->suppress_operation = suppress;
+  return result;
+}
+
+static void *
+threadfunc (void *closure)
+{
+  struct thread_argument *arg = closure;
+
+  if (arg->indirect)
+    {
+      arg->indirect = false;
+      xpthread_join (xpthread_create (NULL, threadfunc, arg));
+      return NULL;
+    }
+
+  const struct test_case *expected = &test_cases[0];
+  gid_t expected_supplementary[3] = { -1, -1, -1 };
+  int expected_supplementary_count = 0;
+
+  xpthread_barrier_wait (&barrier);
+
+  for (size_t test_index = 0; test_index < array_length (test_cases);
+       ++test_index)
+    {
+      if (test_index == arg->thread_index)
+        {
+          if (test_verbose > 0)
+            {
+              if (arg->suppress_operation)
+                printf ("info: thread %zu suppressing operation\n",
+                        test_index);
+              else
+                printf ("info: thread %zu performing operation\n",
+                        test_index);
+            }
+          if (!arg->suppress_operation)
+            test_case_run (&test_cases[test_index]);
+
+          expected = &test_cases[test_index];
+          if (test_cases[test_index].op == OP_SETGROUPS)
+            {
+              expected_supplementary_count
+                = supplemetary_count (&test_cases[test_index]);
+              for (int i = 0; i < expected_supplementary_count; ++i)
+                expected_supplementary[i] = test_cases[test_index].args[i];
+            }
+        }
+
+      xpthread_barrier_wait (&barrier);
+
+      xpthread_mutex_lock (&mutex);
+
+      if (test_verbose > 0)
+        printf ("info: checking phase for thread %zu, test %zu\n",
+                arg->thread_index, test_index);
+      uid_t actual_uid[3];
+      xgetresuid (&actual_uid[0], &actual_uid[1], &actual_uid[2]);
+      TEST_COMPARE (actual_uid[0], expected->expected_uid[0]);
+      TEST_COMPARE (actual_uid[1], expected->expected_uid[1]);
+      TEST_COMPARE (actual_uid[2], expected->expected_uid[2]);
+      gid_t actual_gid[3];
+      xgetresgid (&actual_gid[0], &actual_gid[1], &actual_gid[2]);
+      TEST_COMPARE (actual_gid[0], expected->expected_gid[0]);
+      TEST_COMPARE (actual_gid[1], expected->expected_gid[1]);
+      TEST_COMPARE (actual_gid[2], expected->expected_gid[2]);
+
+      gid_t actual_supplementary[3];
+      gid_t actual_supplementary_count
+        = getgroups (array_length (actual_supplementary),
+                     actual_supplementary);
+      TEST_COMPARE (actual_supplementary_count, expected_supplementary_count);
+      if (actual_supplementary_count > 0)
+        TEST_COMPARE (actual_supplementary[0], expected_supplementary[0]);
+      if (actual_supplementary_count > 1)
+        TEST_COMPARE (actual_supplementary[1], expected_supplementary[1]);
+      if (actual_supplementary_count > 2)
+        TEST_COMPARE (actual_supplementary[2], expected_supplementary[2]);
+
+      xpthread_mutex_unlock (&mutex);
+
+      xpthread_barrier_wait (&barrier);
+    }
+
+  free (arg);
+  return NULL;
+}
+
+/* Used to create threads with per-thread user/group IDs.  */
+static pthread_attr_t attr_perthreadids;
+
+/* Test which verifies that per-thread IDs are thread-specific.  */
+static void
+check_perthread (bool indirect)
+{
+  if (test_verbose > 0)
+    printf ("info: testing per-thread IDs, %s indirection\n",
+            indirect ? "with" : "without");
+  /* Main thread and another shared thread are extras.  */
+  xpthread_barrier_init (&barrier, NULL, array_length (test_cases) + 2);
+
+  pthread_t perthread_threads[array_length (test_cases)];
+  /* Use thread index zero for no-op checking.  */
+  pthread_t shared_thread
+    = xpthread_create (NULL, threadfunc,
+                       create_thread_argument (0, false, false));
+  for (size_t i = 0; i < array_length (test_cases); ++i)
+    {
+      struct thread_argument *arg
+        = create_thread_argument (i, false, indirect);
+      perthread_threads[i] = xpthread_create (&attr_perthreadids, threadfunc,
+                                              arg);
+    }
+  /* Use thread index zero for no-op checking.  */
+  threadfunc (create_thread_argument (0, false, false));
+  for (size_t i = 0; i < array_length (test_cases); ++i)
+    xpthread_join (perthread_threads[i]);
+  xpthread_join (shared_thread);
+
+  xpthread_barrier_destroy (&barrier);
+}
+
+/* Closure arguments to the subprocess.  */
+struct subprocess_argument
+{
+  size_t broadcast_test_index;
+  bool broadcast_from_main;
+  bool indirect;
+};
+
+/* Setting the broadcast UID is destructive, so we need to run it in a
+   subprocess.  */
+static void
+subprocess (void *closure)
+{
+  struct subprocess_argument *arg = closure;
+
+  if (test_verbose > 0)
+    printf ("info: testing broadcasting test case %zu,"
+            " %sbroadcasting from main, %s indirection\n",
+            arg->broadcast_test_index,
+            arg->broadcast_from_main ? "" : "not ",
+            arg->indirect ? "with" : "without");
+
+  /* Main thread and two other shared threads are extras.  One
+     per-thread test is skipped, so there are two extra threads.  */
+  xpthread_barrier_init (&barrier, NULL, array_length (test_cases) + 2);
+
+  pthread_t perthread_threads[array_length (test_cases)];
+  /* Use thread index zero for no-op checking.  */
+  pthread_t shared_threads[2];
+  shared_threads[0]
+    = xpthread_create (NULL, threadfunc,
+                       create_thread_argument (arg->broadcast_test_index,
+                                               arg->broadcast_from_main,
+                                               false));
+  shared_threads[1]
+    = xpthread_create (NULL, threadfunc,
+                       create_thread_argument (arg->broadcast_test_index,
+                                               true, false));
+
+  for (size_t i = 0; i < array_length (test_cases); ++i)
+    /* Skip the test which uses broadcasting.  */
+    if (i != arg->broadcast_test_index)
+      perthread_threads[i]
+        = xpthread_create (&attr_perthreadids, threadfunc,
+                           create_thread_argument (i, false, arg->indirect));
+  /* Use thread index zero for no-op checking.  */
+  threadfunc (create_thread_argument (arg->broadcast_test_index,
+                                      !arg->broadcast_from_main, false));
+  for (size_t i = 0; i < array_length (test_cases); ++i)
+    /* Skip the test which uses broadcasting.  */
+    if (i != arg->broadcast_test_index)
+      xpthread_join (perthread_threads[i]);
+  xpthread_join (shared_threads[0]);
+  xpthread_join (shared_threads[1]);
+
+  xpthread_barrier_destroy (&barrier);
+}
+
+static int
+do_test (void)
+{
+  if (setuid (0) != 0)
+    FAIL_EXIT1 ("setuid (0): %m");
+  if (setgid (0) != 0)
+    FAIL_EXIT1 ("setgid (0): %m");
+  if (setgroups (0, NULL) != 0)
+    FAIL_EXIT1 ("setgroups (0, NULL): %m");
+
+  xpthread_attr_init (&attr_perthreadids);
+  TEST_COMPARE (pthread_attr_getperthreadids_np (&attr_perthreadids), 0);
+  pthread_attr_setperthreadids_np (&attr_perthreadids, true);
+  TEST_COMPARE (pthread_attr_getperthreadids_np (&attr_perthreadids), 1);
+  /* The flag is normalized to 1.  */
+  pthread_attr_setperthreadids_np (&attr_perthreadids, 2);
+  TEST_COMPARE (pthread_attr_getperthreadids_np (&attr_perthreadids), 1);
+  pthread_attr_setperthreadids_np (&attr_perthreadids, 0);
+  TEST_COMPARE (pthread_attr_getperthreadids_np (&attr_perthreadids), 0);
+  pthread_attr_setperthreadids_np (&attr_perthreadids, true);
+  TEST_COMPARE (pthread_attr_getperthreadids_np (&attr_perthreadids), 1);
+
+  for (int indirect = 0; indirect < 2; ++indirect)
+    {
+      check_perthread (indirect);
+
+      for (int broadcast_from_main = 0; broadcast_from_main < 2;
+           ++broadcast_from_main)
+        for (size_t broadcast_test_index = 0;
+             broadcast_test_index < array_length (test_cases);
+             ++broadcast_test_index)
+          {
+            struct subprocess_argument arg =
+              {
+               .broadcast_test_index = broadcast_test_index,
+               .broadcast_from_main = broadcast_from_main,
+               .indirect = indirect,
+              };
+            support_isolate_in_subprocess (subprocess, &arg);
+          }
+    }
+
+  xpthread_attr_destroy (&attr_perthreadids);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/Makefile b/support/Makefile
index 2ef8c019b1..230532d728 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -88,6 +88,8 @@  libsupport-routines = \
   xfopen \
   xfork \
   xftruncate \
+  xgetresgid \
+  xgetresuid \
   xgetsockname \
   xlisten \
   xlseek \
diff --git a/support/xgetresgid.c b/support/xgetresgid.c
new file mode 100644
index 0000000000..2e7b413934
--- /dev/null
+++ b/support/xgetresgid.c
@@ -0,0 +1,27 @@ 
+/* getresgid with error checking.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/check.h>
+#include <support/xunistd.h>
+
+void
+xgetresgid (gid_t *real, gid_t *effective, gid_t *saved)
+{
+  if (getresgid (real, effective, saved) != 0)
+    FAIL_EXIT1 ("getresgid: %m");
+}
diff --git a/support/xgetresuid.c b/support/xgetresuid.c
new file mode 100644
index 0000000000..0081d1fa78
--- /dev/null
+++ b/support/xgetresuid.c
@@ -0,0 +1,27 @@ 
+/* getresuid with error checking.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/check.h>
+#include <support/xunistd.h>
+
+void
+xgetresuid (uid_t *real, uid_t *effective, uid_t *saved)
+{
+  if (getresuid (real, effective, saved) != 0)
+    FAIL_EXIT1 ("getresuid: %m");
+}
diff --git a/support/xunistd.h b/support/xunistd.h
index 13dfa2c0e5..2233aa0386 100644
--- a/support/xunistd.h
+++ b/support/xunistd.h
@@ -46,6 +46,8 @@  long xsysconf (int name);
 long long xlseek (int fd, long long offset, int whence);
 void xftruncate (int fd, long long length);
 void xsymlink (const char *target, const char *linkpath);
+void xgetresuid (uid_t *, uid_t *, uid_t *);
+void xgetresgid (gid_t *, gid_t *, gid_t *);
 
 /* Equivalent of "mkdir -p".  */
 void xmkdirp (const char *, mode_t);
diff --git a/sysdeps/nptl/internaltypes.h b/sysdeps/nptl/internaltypes.h
index 30396cc448..cde8f40503 100644
--- a/sysdeps/nptl/internaltypes.h
+++ b/sysdeps/nptl/internaltypes.h
@@ -49,7 +49,18 @@  struct pthread_attr
 #define ATTR_FLAG_SCHED_SET		0x0020
 #define ATTR_FLAG_POLICY_SET		0x0040
 #define ATTR_FLAG_PERTHREADFS		0x0080
+#define ATTR_FLAG_PERTHREADIDS		0x0100
 
+/* These flags are not copied from the thread attribute at
+   pthread_create time.  */
+#define ATTR_FLAGS_IGNORED_ATTR \
+  (ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)
+
+/* These flags are inherited from the current thread during
+   pthread_create even if they are not specified in the thread
+   attribute.  */
+#define ATTR_FLAGS_INHERITED \
+  (ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET | ATTR_FLAG_PERTHREADIDS)
 
 /* Mutex attribute data structure.  */
 struct pthread_mutexattr
diff --git a/sysdeps/nptl/pthread.h b/sysdeps/nptl/pthread.h
index 692d694bbd..ac65069976 100644
--- a/sysdeps/nptl/pthread.h
+++ b/sysdeps/nptl/pthread.h
@@ -418,6 +418,23 @@  void pthread_attr_setperthreadfs_np (pthread_attr_t *__attr, int __enabled)
 int pthread_attr_getperthreadfs_np (const pthread_attr_t *__attr)
   __THROW __nonnull ((1));
 
+/* Control the flag in ATTR whether the thread has its own user and
+   group IDs.  By default, when the real, effective, or saved user or
+   group ID is changed by a thread, this affects the entire process.
+   If a thread is created with this flag set to true, then changing
+   the IDs within that thread will only affect that thread, and user
+   and group ID changes in other threads (whether they have enabled
+   this flag or not) do not affect it.  If a thread has been created
+   with this flag, threads created by it will also have their own,
+   private user and group IDs.  */
+void pthread_attr_setperthreadids_np (pthread_attr_t *__attr, int __enabled)
+  __THROW __nonnull ((1));
+
+/* Return 1 if ATTR enables per-thread user and group IDs, or 0 if
+   not.  See pthread_attr_setperthreadids_np.  */
+int pthread_attr_getperthreadids_np (const pthread_attr_t *__attr)
+  __THROW __nonnull ((1));
+
 /* Get the default attributes used by pthread_create in this process.  */
 extern int pthread_getattr_default_np (pthread_attr_t *__attr)
      __THROW __nonnull ((1));
diff --git a/sysdeps/nptl/setxid.h b/sysdeps/nptl/setxid.h
index de39efc723..09af829d3f 100644
--- a/sysdeps/nptl/setxid.h
+++ b/sysdeps/nptl/setxid.h
@@ -32,7 +32,8 @@ 
 # define INLINE_SETXID_SYSCALL(name, nr, args...) \
   ({									\
     int __result;							\
-    if (__builtin_expect (__libc_pthread_functions_init, 0))		\
+    if (__libc_pthread_functions_init					\
+	&& !nptl_current_thread_has_separate_ids ())			\
       {									\
 	struct xid_command __cmd;					\
 	__cmd.syscall_no = __NR_##name;					\
@@ -48,7 +49,8 @@ 
   ({									\
     extern __typeof (__nptl_setxid) __nptl_setxid __attribute__((weak));\
     int __result;							\
-    if (__glibc_unlikely (__nptl_setxid	!= NULL))			      \
+    if (__nptl_setxid != NULL						\
+	&& !nptl_current_thread_has_separate_ids ())			\
       {									\
 	struct xid_command __cmd;					\
 	__cmd.syscall_no = __NR_##name;					\