about summary refs log tree commit diff
path: root/nptl
diff options
context:
space:
mode:
Diffstat (limited to 'nptl')
-rw-r--r--nptl/ChangeLog17
-rw-r--r--nptl/Makefile11
-rw-r--r--nptl/allocatestack.c14
-rw-r--r--nptl/descr.h2
-rw-r--r--nptl/pthreadP.h2
-rw-r--r--nptl/sysdeps/pthread/pthread-functions.h4
-rw-r--r--nptl/sysdeps/pthread/setxid.h64
-rw-r--r--nptl/tst-setuid1-static.c1
-rw-r--r--nptl/tst-setuid1.c1085
9 files changed, 1191 insertions, 9 deletions
diff --git a/nptl/ChangeLog b/nptl/ChangeLog
index 0f8d4a0055..a30b5b877a 100644
--- a/nptl/ChangeLog
+++ b/nptl/ChangeLog
@@ -1,5 +1,22 @@
 2004-11-10  Jakub Jelinek  <jakub@redhat.com>
 
+	* sysdeps/pthread/setxid.h: New file.
+	* sysdeps/pthread/pthread-functions.h (HAVE_PTR__NPTL_SETXID): Remove.
+	(struct xid_command): Add forward decl.
+	(struct pthread_functions): Change return type of __nptl_setxid hook
+	to int.
+	* pthreadP.h (__nptl_setxid): Change return type to int.
+	* allocatestack.c (__nptl_setxid): Call INTERNAL_SYSCALL_NCS in the
+	calling thread, return its return value and set errno on failure.
+	* descr.h (struct xid_command): Change id type to long array.
+
+	* Makefile: Add rules to build and test tst-setuid1 and
+	tst-setuid1-static.
+	* tst-setuid1.c: New test.
+	* tst-setuid1-static.c: New test.
+
+2004-11-10  Jakub Jelinek  <jakub@redhat.com>
+
 	* Makefile (tests): Add tst-exit3.
 	* tst-exit3.c: New test.
 
diff --git a/nptl/Makefile b/nptl/Makefile
index 087b748bb4..7b1759a08f 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -242,6 +242,7 @@ tests = tst-attr1 tst-attr2 tst-attr3 \
 	tst-backtrace1 \
 	tst-oddstacklimit \
 	tst-vfork1 tst-vfork2 tst-vfork1x tst-vfork2x
+xtests = tst-setuid1 tst-setuid1-static
 
 # Files which must not be linked with libpthread.
 tests-nolibpthread = tst-unload
@@ -342,6 +343,7 @@ link-libc-static := $(common-objpfx)libc.a $(static-gnulib) \
 
 ifeq ($(build-static),yes)
 tests-static += tst-locale1 tst-locale2
+xtests-static += tst-setuid1-static
 endif
 # These tests are linked with libc before libpthread
 tests-reverse += tst-cancel5 tst-cancel23 tst-vfork1x tst-vfork2x
@@ -499,9 +501,10 @@ $(objpfx)libpthread.so: $(common-objpfx)libc.so \
 # Make sure we link with the thread library.
 ifeq ($(build-shared),yes)
 $(addprefix $(objpfx), \
-  $(filter-out $(tests-static) $(tests-reverse) $(tests-nolibpthread), \
-    $(tests) $(test-srcs))): $(objpfx)libpthread.so \
-			     $(objpfx)libpthread_nonshared.a
+  $(filter-out $(tests-static) $(xtests-static) $(tests-reverse) \
+    $(tests-nolibpthread), \
+    $(tests) $(xtests) $(test-srcs))): $(objpfx)libpthread.so \
+				       $(objpfx)libpthread_nonshared.a
 $(objpfx)tst-unload: $(common-objpfx)dlfcn/libdl.so
 # $(objpfx)../libc.so is used instead of $(common-objpfx)libc.so,
 # since otherwise libpthread.so comes before libc.so when linking.
@@ -509,7 +512,7 @@ $(addprefix $(objpfx), $(tests-reverse)): \
   $(objpfx)../libc.so $(objpfx)libpthread.so \
   $(objpfx)libpthread_nonshared.a
 $(objpfx)../libc.so: $(common-objpfx)libc.so ;
-$(addprefix $(objpfx),$(tests-static)): $(objpfx)libpthread.a
+$(addprefix $(objpfx),$(tests-static) $(xtests-static)): $(objpfx)libpthread.a
 
 $(objpfx)tst-atfork2.out: $(objpfx)tst-atfork2mod.so
 else
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index d4f3188f53..6c2367cc89 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -816,10 +816,11 @@ __find_thread_by_id (pid_t tid)
 }
 #endif
 
-void
+int
 attribute_hidden
 __nptl_setxid (struct xid_command *cmdp)
 {
+  int result;
   lll_lock (stack_cache_lock);
 
   __xidcmd = cmdp;
@@ -891,7 +892,18 @@ __nptl_setxid (struct xid_command *cmdp)
       cur = cmdp->cntr;
     }
 
+  /* This must be last, otherwise the current thread might not have
+     permissions to send SIGSETXID syscall to the other threads.  */
+  result = INTERNAL_SYSCALL_NCS (cmdp->syscall_no, err, 3,
+				 cmdp->id[0], cmdp->id[1], cmdp->id[2]);
+  if (INTERNAL_SYSCALL_ERROR_P (result, err))
+    {
+      __set_errno (INTERNAL_SYSCALL_ERRNO (result, err));
+      result = -1;
+    }
+
   lll_unlock (stack_cache_lock);
+  return result;
 }
 
 static inline void __attribute__((always_inline))
diff --git a/nptl/descr.h b/nptl/descr.h
index d81392921c..454bb2a547 100644
--- a/nptl/descr.h
+++ b/nptl/descr.h
@@ -97,7 +97,7 @@ struct pthread_unwind_buf
 struct xid_command
 {
   int syscall_no;
-  id_t id[3];
+  long id[3];
   volatile int cntr;
 };
 
diff --git a/nptl/pthreadP.h b/nptl/pthreadP.h
index 1fedce5f3a..f73c817f60 100644
--- a/nptl/pthreadP.h
+++ b/nptl/pthreadP.h
@@ -448,6 +448,6 @@ extern void _pthread_cleanup_pop_restore (struct _pthread_cleanup_buffer *buffer
 
 extern void __nptl_deallocate_tsd (void) attribute_hidden;
 
-extern void __nptl_setxid (struct xid_command *cmdp) attribute_hidden;
+extern int __nptl_setxid (struct xid_command *cmdp) attribute_hidden;
 
 #endif	/* pthreadP.h */
diff --git a/nptl/sysdeps/pthread/pthread-functions.h b/nptl/sysdeps/pthread/pthread-functions.h
index b1e0fcb26d..2845346128 100644
--- a/nptl/sysdeps/pthread/pthread-functions.h
+++ b/nptl/sysdeps/pthread/pthread-functions.h
@@ -24,6 +24,7 @@
 #include <setjmp.h>
 #include <internaltypes.h>
 
+struct xid_command;
 
 /* Data type shared with libc.  The libc uses it to pass on calls to
    the thread functions.  */
@@ -93,8 +94,7 @@ struct pthread_functions
   void (*ptr___pthread_unwind) (__pthread_unwind_buf_t *)
        __attribute ((noreturn)) __cleanup_fct_attribute;
   void (*ptr__nptl_deallocate_tsd) (void);
-#define HAVE_PTR__NPTL_SETXID
-  void (*ptr__nptl_setxid) (struct xid_command *);
+  int (*ptr__nptl_setxid) (struct xid_command *);
 };
 
 /* Variable in libc.so.  */
diff --git a/nptl/sysdeps/pthread/setxid.h b/nptl/sysdeps/pthread/setxid.h
new file mode 100644
index 0000000000..8ec382f406
--- /dev/null
+++ b/nptl/sysdeps/pthread/setxid.h
@@ -0,0 +1,64 @@
+/* Copyright (C) 2004 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, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#include <pthreadP.h>
+#include <sysdep.h>
+
+#define __SETXID_1(cmd, arg1) \
+  cmd.id[0] = arg1
+#define __SETXID_2(cmd, arg1, arg2) \
+  __SETXID_1 (cmd, arg1); cmd.id[1] = arg2
+#define __SETXID_3(cmd, arg1, arg2, arg3) \
+  __SETXID_2 (cmd, arg1, arg2); cmd.id[2] = arg3
+
+#ifdef SINGLE_THREAD
+# define INLINE_SETXID_SYSCALL(name, nr, args...) \
+  INLINE_SYSCALL (name, nr, args)
+#elif defined SHARED
+# define INLINE_SETXID_SYSCALL(name, nr, args...) \
+  ({									\
+    int __result;							\
+    if (__builtin_expect (__libc_pthread_functions.ptr__nptl_setxid	\
+			  != NULL, 0))					\
+      {									\
+	struct xid_command __cmd;					\
+	__cmd.syscall_no = __NR_##name;					\
+	__SETXID_##nr (__cmd, args);					\
+	__result = __libc_pthread_functions.ptr__nptl_setxid (&__cmd);	\
+	}								\
+    else								\
+      __result = INLINE_SYSCALL (name, nr, args);			\
+    __result;								\
+   })
+#else
+# define INLINE_SETXID_SYSCALL(name, nr, args...) \
+  ({									\
+    extern __typeof (__nptl_setxid) __nptl_setxid __attribute__((weak));\
+    int __result;							\
+    if (__builtin_expect (__nptl_setxid	!= NULL, 0))			\
+      {									\
+	struct xid_command __cmd;					\
+	__cmd.syscall_no = __NR_##name;					\
+	__SETXID_##nr (__cmd, args);					\
+	__result =__nptl_setxid (&__cmd);				\
+      }									\
+    else								\
+      __result = INLINE_SYSCALL (name, nr, args);			\
+    __result;								\
+   })
+#endif
diff --git a/nptl/tst-setuid1-static.c b/nptl/tst-setuid1-static.c
new file mode 100644
index 0000000000..46d26f0991
--- /dev/null
+++ b/nptl/tst-setuid1-static.c
@@ -0,0 +1 @@
+#include "tst-setuid1.c"
diff --git a/nptl/tst-setuid1.c b/nptl/tst-setuid1.c
new file mode 100644
index 0000000000..f026c576d6
--- /dev/null
+++ b/nptl/tst-setuid1.c
@@ -0,0 +1,1085 @@
+/* Copyright (C) 2004 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Jakub Jelinek <jaku@redhat.com>, 2004.
+
+   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, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#include <pthread.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+static pthread_barrier_t b3, b4;
+static uid_t prev_ruid, prev_euid, prev_suid, nobody_uid;
+static gid_t prev_rgid, prev_egid, prev_sgid, nobody_gid;
+enum ACTION { PREPARE, SET, CHECK_BEFORE, CHECK_AFTER };
+#define TESTNO(arg) ((long int) (arg) & 0xff)
+#define THREADNO(arg) ((long int) (arg) >> 8)
+
+
+static void
+check_prev_uid (int tno)
+{
+  uid_t ruid, euid, suid;
+  if (getresuid (&ruid, &euid, &suid) < 0)
+    {
+      printf ("getresuid failed: %d %m\n", tno);
+      exit (1);
+    }
+
+  if (ruid != prev_ruid || euid != prev_euid || suid != prev_suid)
+    {
+      printf ("uids before in %d (%d %d %d) != (%d %d %d)\n", tno,
+	      ruid, euid, suid, prev_ruid, prev_euid, prev_suid);
+      exit (1);
+    }
+}
+
+
+static void
+check_prev_gid (int tno)
+{
+  gid_t rgid, egid, sgid;
+  if (getresgid (&rgid, &egid, &sgid) < 0)
+    {
+      printf ("getresgid failed: %d %m\n", tno);
+      exit (1);
+    }
+
+  if (rgid != prev_rgid || egid != prev_egid || sgid != prev_sgid)
+    {
+      printf ("gids before in %d (%d %d %d) != (%d %d %d)\n", tno,
+	      rgid, egid, sgid, prev_rgid, prev_egid, prev_sgid);
+      exit (1);
+    }
+}
+
+
+static void
+test_setuid1 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_uid (tno);
+
+  if (action == SET && setuid (nobody_uid) < 0)
+    {
+       printf ("setuid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      uid_t ruid, euid, suid;
+      if (getresuid (&ruid, &euid, &suid) < 0)
+	{
+	  printf ("getresuid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (ruid != nobody_uid || euid != nobody_uid || suid != nobody_uid)
+	{
+	  printf ("after setuid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  ruid, euid, suid, nobody_uid, nobody_uid, nobody_uid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setuid2 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    {
+      if (setresuid (nobody_uid, nobody_uid, -1) < 0)
+	{
+	  printf ("setresuid failed: %m\n");
+	  exit (1);
+	}
+
+      prev_ruid = nobody_uid;
+      prev_euid = nobody_uid;
+      return;
+    }
+
+  if (action != CHECK_AFTER)
+    check_prev_uid (tno);
+
+  if (action == SET && setuid (prev_suid) < 0)
+    {
+      printf ("setuid failed: %m\n");
+      exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      uid_t ruid, euid, suid;
+      if (getresuid (&ruid, &euid, &suid) < 0)
+	{
+	  printf ("getresuid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (ruid != nobody_uid || euid != prev_suid || suid != prev_suid)
+	{
+	  printf ("after setuid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  ruid, euid, suid, nobody_uid, prev_suid, prev_suid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_seteuid1 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_uid (tno);
+
+  if (action == SET && seteuid (nobody_uid) < 0)
+    {
+       printf ("seteuid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      uid_t ruid, euid, suid;
+      if (getresuid (&ruid, &euid, &suid) < 0)
+	{
+	  printf ("getresuid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (ruid != prev_ruid || euid != nobody_uid || suid != prev_suid)
+	{
+	  printf ("after seteuid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  ruid, euid, suid, prev_ruid, nobody_uid, prev_suid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_seteuid2 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    {
+      if (setresuid (nobody_uid, nobody_uid, -1) < 0)
+	{
+	  printf ("setresuid failed: %m\n");
+	  exit (1);
+	}
+
+      prev_ruid = nobody_uid;
+      prev_euid = nobody_uid;
+      nobody_uid = prev_suid;
+      return;
+    }
+
+  test_seteuid1 (action, tno);
+}
+
+
+static void
+test_setreuid1 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_uid (tno);
+
+  if (action == SET && setreuid (-1, nobody_uid) < 0)
+    {
+       printf ("setreuid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      uid_t ruid, euid, suid, esuid;
+      if (getresuid (&ruid, &euid, &suid) < 0)
+	{
+	  printf ("getresuid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (prev_ruid != nobody_uid)
+	esuid = nobody_uid;
+      else
+	esuid = prev_suid;
+
+      if (ruid != prev_ruid || euid != nobody_uid || suid != esuid)
+	{
+	  printf ("after setreuid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  ruid, euid, suid, prev_ruid, nobody_uid, esuid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setreuid2 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_uid (tno);
+
+  if (action == SET && setreuid (nobody_uid, -1) < 0)
+    {
+       printf ("setreuid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      uid_t ruid, euid, suid;
+      if (getresuid (&ruid, &euid, &suid) < 0)
+	{
+	  printf ("getresuid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (ruid != nobody_uid || euid != prev_euid || suid != prev_euid)
+	{
+	  printf ("after setreuid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  ruid, euid, suid, nobody_uid, prev_euid, prev_euid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setreuid3 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_uid (tno);
+
+  if (action == SET && setreuid (nobody_uid, nobody_uid) < 0)
+    {
+       printf ("setreuid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      uid_t ruid, euid, suid;
+      if (getresuid (&ruid, &euid, &suid) < 0)
+	{
+	  printf ("getresuid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (ruid != nobody_uid || euid != nobody_uid || suid != nobody_uid)
+	{
+	  printf ("after setreuid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  ruid, euid, suid, nobody_uid, nobody_uid, nobody_uid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setreuid4 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    {
+      if (setresuid (nobody_uid, nobody_uid, -1) < 0)
+	{
+	  printf ("setresuid failed: %m\n");
+	  exit (1);
+	}
+
+      prev_ruid = nobody_uid;
+      prev_euid = nobody_uid;
+      nobody_uid = prev_suid;
+      return;
+    }
+
+  test_setreuid1 (action, tno);
+}
+
+
+static void
+test_setresuid1 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_uid (tno);
+
+  if (action == SET && setresuid (-1, nobody_uid, -1) < 0)
+    {
+       printf ("setresuid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      uid_t ruid, euid, suid;
+      if (getresuid (&ruid, &euid, &suid) < 0)
+	{
+	  printf ("getresuid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (ruid != prev_ruid || euid != nobody_uid || suid != prev_suid)
+	{
+	  printf ("after setresuid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  ruid, euid, suid, prev_ruid, nobody_uid, prev_suid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setresuid2 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_uid (tno);
+
+  if (action == SET && setresuid (prev_euid, nobody_uid, nobody_uid) < 0)
+    {
+       printf ("setresuid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      uid_t ruid, euid, suid;
+      if (getresuid (&ruid, &euid, &suid) < 0)
+	{
+	  printf ("getresuid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (ruid != prev_euid || euid != nobody_uid || suid != nobody_uid)
+	{
+	  printf ("after setresuid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  ruid, euid, suid, prev_euid, nobody_uid, nobody_uid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setresuid3 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_uid (tno);
+
+  if (action == SET && setresuid (nobody_uid, nobody_uid, nobody_uid) < 0)
+    {
+       printf ("setresuid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      uid_t ruid, euid, suid;
+      if (getresuid (&ruid, &euid, &suid) < 0)
+	{
+	  printf ("getresuid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (ruid != nobody_uid || euid != nobody_uid || suid != nobody_uid)
+	{
+	  printf ("after setresuid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  ruid, euid, suid, nobody_uid, nobody_uid, nobody_uid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setresuid4 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    {
+      if (setresuid (nobody_uid, nobody_uid, -1) < 0)
+	{
+	  printf ("setresuid failed: %m\n");
+	  exit (1);
+	}
+
+      prev_ruid = nobody_uid;
+      prev_euid = nobody_uid;
+      nobody_uid = prev_suid;
+      return;
+    }
+
+  test_setresuid1 (action, tno);
+}
+
+
+static void
+test_setgid1 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_gid (tno);
+
+  if (action == SET && setgid (nobody_gid) < 0)
+    {
+       printf ("setgid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      gid_t rgid, egid, sgid;
+      if (getresgid (&rgid, &egid, &sgid) < 0)
+	{
+	  printf ("getresgid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (rgid != nobody_gid || egid != nobody_gid || sgid != nobody_gid)
+	{
+	  printf ("after setgid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  rgid, egid, sgid, nobody_gid, nobody_gid, nobody_gid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setgid2 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    {
+      if (setresgid (nobody_gid, nobody_gid, -1) < 0)
+	{
+	  printf ("setresgid failed: %m\n");
+	  exit (1);
+	}
+
+      prev_rgid = nobody_gid;
+      prev_egid = nobody_gid;
+
+      if (setresuid (nobody_uid, nobody_uid, -1) < 0)
+	{
+	  printf ("setresuid failed: %m\n");
+	  exit (1);
+	}
+
+      prev_ruid = nobody_uid;
+      prev_euid = nobody_uid;
+      return;
+    }
+
+  if (action != CHECK_AFTER)
+    check_prev_gid (tno);
+
+  if (action == SET && setgid (prev_sgid) < 0)
+    {
+      printf ("setgid failed: %m\n");
+      exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      gid_t rgid, egid, sgid;
+      if (getresgid (&rgid, &egid, &sgid) < 0)
+	{
+	  printf ("getresgid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (rgid != nobody_gid || egid != prev_sgid || sgid != prev_sgid)
+	{
+	  printf ("after setgid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  rgid, egid, sgid, nobody_gid, prev_sgid, prev_sgid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setegid1 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_gid (tno);
+
+  if (action == SET && setegid (nobody_gid) < 0)
+    {
+       printf ("setegid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      gid_t rgid, egid, sgid;
+      if (getresgid (&rgid, &egid, &sgid) < 0)
+	{
+	  printf ("getresgid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (rgid != prev_rgid || egid != nobody_gid || sgid != prev_sgid)
+	{
+	  printf ("after setegid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  rgid, egid, sgid, prev_rgid, nobody_gid, prev_sgid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setegid2 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    {
+      if (setresgid (nobody_gid, nobody_gid, -1) < 0)
+	{
+	  printf ("setresgid failed: %m\n");
+	  exit (1);
+	}
+
+      prev_rgid = nobody_gid;
+      prev_egid = nobody_gid;
+      nobody_gid = prev_sgid;
+      return;
+    }
+
+  test_setegid1 (action, tno);
+}
+
+
+static void
+test_setregid1 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_gid (tno);
+
+  if (action == SET && setregid (-1, nobody_gid) < 0)
+    {
+       printf ("setregid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      gid_t rgid, egid, sgid, esgid;
+      if (getresgid (&rgid, &egid, &sgid) < 0)
+	{
+	  printf ("getresgid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (prev_rgid != nobody_gid)
+	esgid = nobody_gid;
+      else
+	esgid = prev_sgid;
+
+      if (rgid != prev_rgid || egid != nobody_gid || sgid != esgid)
+	{
+	  printf ("after setregid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  rgid, egid, sgid, prev_rgid, nobody_gid, esgid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setregid2 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_gid (tno);
+
+  if (action == SET && setregid (nobody_gid, -1) < 0)
+    {
+       printf ("setregid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      gid_t rgid, egid, sgid;
+      if (getresgid (&rgid, &egid, &sgid) < 0)
+	{
+	  printf ("getresgid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (rgid != nobody_gid || egid != prev_egid || sgid != prev_egid)
+	{
+	  printf ("after setregid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  rgid, egid, sgid, nobody_gid, prev_egid, prev_egid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setregid3 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_gid (tno);
+
+  if (action == SET && setregid (nobody_gid, nobody_gid) < 0)
+    {
+       printf ("setregid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      gid_t rgid, egid, sgid;
+      if (getresgid (&rgid, &egid, &sgid) < 0)
+	{
+	  printf ("getresgid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (rgid != nobody_gid || egid != nobody_gid || sgid != nobody_gid)
+	{
+	  printf ("after setregid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  rgid, egid, sgid, nobody_gid, nobody_gid, nobody_gid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setregid4 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    {
+      if (setresgid (nobody_gid, nobody_gid, -1) < 0)
+	{
+	  printf ("setresgid failed: %m\n");
+	  exit (1);
+	}
+
+      prev_rgid = nobody_gid;
+      prev_egid = nobody_gid;
+      nobody_gid = prev_sgid;
+      return;
+    }
+
+  test_setregid1 (action, tno);
+}
+
+
+static void
+test_setresgid1 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_gid (tno);
+
+  if (action == SET && setresgid (-1, nobody_gid, -1) < 0)
+    {
+       printf ("setresgid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      gid_t rgid, egid, sgid;
+      if (getresgid (&rgid, &egid, &sgid) < 0)
+	{
+	  printf ("getresgid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (rgid != prev_rgid || egid != nobody_gid || sgid != prev_sgid)
+	{
+	  printf ("after setresgid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  rgid, egid, sgid, prev_rgid, nobody_gid, prev_sgid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setresgid2 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_gid (tno);
+
+  if (action == SET && setresgid (prev_egid, nobody_gid, nobody_gid) < 0)
+    {
+       printf ("setresgid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      gid_t rgid, egid, sgid;
+      if (getresgid (&rgid, &egid, &sgid) < 0)
+	{
+	  printf ("getresgid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (rgid != prev_egid || egid != nobody_gid || sgid != nobody_gid)
+	{
+	  printf ("after setresgid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  rgid, egid, sgid, prev_egid, nobody_gid, nobody_gid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setresgid3 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    return;
+
+  if (action != CHECK_AFTER)
+    check_prev_gid (tno);
+
+  if (action == SET && setresgid (nobody_gid, nobody_gid, nobody_gid) < 0)
+    {
+       printf ("setresgid failed: %m\n");
+       exit (1);
+    }
+
+  if (action != CHECK_BEFORE)
+    {
+      gid_t rgid, egid, sgid;
+      if (getresgid (&rgid, &egid, &sgid) < 0)
+	{
+	  printf ("getresgid failed: %d %m\n", tno);
+	  exit (1);
+	}
+
+      if (rgid != nobody_gid || egid != nobody_gid || sgid != nobody_gid)
+	{
+	  printf ("after setresgid %d (%d %d %d) != (%d %d %d)\n", tno,
+		  rgid, egid, sgid, nobody_gid, nobody_gid, nobody_gid);
+	  exit (1);
+	}
+    }
+}
+
+
+static void
+test_setresgid4 (enum ACTION action, int tno)
+{
+  if (action == PREPARE)
+    {
+      if (setresgid (nobody_gid, nobody_gid, -1) < 0)
+	{
+	  printf ("setresgid failed: %m\n");
+	  exit (1);
+	}
+
+      prev_rgid = nobody_gid;
+      prev_egid = nobody_gid;
+      nobody_gid = prev_sgid;
+      return;
+    }
+
+  test_setresgid1 (action, tno);
+}
+
+
+static struct setuid_test
+{
+  const char *name;
+  void (*test) (enum ACTION, int tno);
+} setuid_tests[] =
+{
+  { "setuid1", test_setuid1 },
+  { "setuid2", test_setuid2 },
+  { "seteuid1", test_seteuid1 },
+  { "seteuid2", test_seteuid2 },
+  { "setreuid1", test_setreuid1 },
+  { "setreuid2", test_setreuid2 },
+  { "setreuid3", test_setreuid3 },
+  { "setreuid4", test_setreuid4 },
+  { "setresuid1", test_setresuid1 },
+  { "setresuid2", test_setresuid2 },
+  { "setresuid3", test_setresuid3 },
+  { "setresuid4", test_setresuid4 },
+  { "setgid1", test_setgid1 },
+  { "setgid2", test_setgid2 },
+  { "setegid1", test_setegid1 },
+  { "setegid2", test_setegid2 },
+  { "setregid1", test_setregid1 },
+  { "setregid2", test_setregid2 },
+  { "setregid3", test_setregid3 },
+  { "setregid4", test_setregid4 },
+  { "setresgid1", test_setresgid1 },
+  { "setresgid2", test_setresgid2 },
+  { "setresgid3", test_setresgid3 },
+  { "setresgid4", test_setresgid4 }
+};
+
+
+static void *
+tf2 (void *arg)
+{
+  int e = pthread_barrier_wait (&b4);
+  if (e != 0 && e != PTHREAD_BARRIER_SERIAL_THREAD)
+    {
+      puts ("barrier_wait failed");
+      exit (1);
+    }
+
+  setuid_tests[TESTNO (arg)].test (CHECK_AFTER, THREADNO (arg));
+  return NULL;
+}
+
+
+static void *
+tf (void *arg)
+{
+  setuid_tests[TESTNO (arg)].test (CHECK_BEFORE, THREADNO (arg));
+
+  int e = pthread_barrier_wait (&b3);
+  if (e != 0 && e != PTHREAD_BARRIER_SERIAL_THREAD)
+    {
+      puts ("barrier_wait failed");
+      exit (1);
+    }
+
+  return tf2 (arg);
+}
+
+
+static int
+do_one_test (long int testno)
+{
+  printf ("%s test\n", setuid_tests[testno].name);
+
+  pid_t pid = fork ();
+  if (pid == 0)
+    {
+      setuid_tests[testno].test (PREPARE, 0);
+      setuid_tests[testno].test (SET, 0);
+      exit (0);
+    }
+
+  if (pid < 0)
+    {
+      printf ("fork failed: %m\n");
+      exit (1);
+    }
+
+  int status;
+  if (waitpid (pid, &status, 0) < 0)
+    {
+      printf ("waitpid failed: %m\n");
+      exit (1);
+    }
+
+  if (!WIFEXITED (status))
+    {
+      puts ("child did not exit");
+      exit (1);
+    }
+
+  if (WEXITSTATUS (status))
+    {
+      printf ("skipping %s test\n", setuid_tests[testno].name);
+      return 0;
+    }
+
+  pid = fork ();
+  if (pid == 0)
+    {
+      setuid_tests[testno].test (PREPARE, 0);
+
+      pthread_t th;
+      int e = pthread_create (&th, NULL, tf, (void *) (testno | 0x100L));
+      if (e != 0)
+	{
+	  printf ("create failed: %m\n");
+	  exit (1);
+	}
+
+      pthread_t th2;
+      e = pthread_create (&th2, NULL, tf, (void *) (testno | 0x200L));
+      if (e != 0)
+	{
+	  printf ("create failed: %m\n");
+	  exit (1);
+	}
+
+      e = pthread_barrier_wait (&b3);
+      if (e != 0 && e != PTHREAD_BARRIER_SERIAL_THREAD)
+	{
+	  puts ("barrier_wait failed");
+	  exit (1);
+	}
+
+      setuid_tests[testno].test (SET, 0);
+
+      pthread_t th3;
+      e = pthread_create (&th3, NULL, tf2, (void *) (testno | 0x300L));
+      if (e != 0)
+	{
+	  printf ("create failed: %m\n");
+	  exit (1);
+	}
+
+      e = pthread_barrier_wait (&b4);
+      if (e != 0 && e != PTHREAD_BARRIER_SERIAL_THREAD)
+	{
+	  puts ("barrier_wait failed");
+	  exit (1);
+	}
+
+      exit (0);
+    }
+
+  if (pid < 0)
+    {
+      printf ("fork failed: %m\n");
+      exit (1);
+    }
+
+  if (waitpid (pid, &status, 0) < 0)
+    {
+      printf ("waitpid failed: %m\n");
+      exit (1);
+    }
+
+  if (!WIFEXITED (status))
+    {
+      puts ("second child did not exit");
+      exit (1);
+    }
+
+  if (WEXITSTATUS (status))
+    exit (WEXITSTATUS (status));
+
+  return 0;
+}
+
+
+static int
+do_test (void)
+{
+  struct passwd *pwd = getpwnam ("nobody");
+  if (pwd == NULL)
+    {
+      puts ("User nobody doesn't exist");
+      return 0;
+    }
+  nobody_uid = pwd->pw_uid;
+  nobody_gid = pwd->pw_gid;
+
+  if (getresuid (&prev_ruid, &prev_euid, &prev_suid) < 0)
+    {
+      printf ("getresuid failed: %m\n");
+      exit (1);
+    }
+
+  if (getresgid (&prev_rgid, &prev_egid, &prev_sgid) < 0)
+    {
+      printf ("getresgid failed: %m\n");
+      exit (1);
+    }
+
+  if (prev_ruid == nobody_uid || prev_euid == nobody_uid
+      || prev_suid == nobody_uid)
+    {
+      puts ("already running as user nobody, skipping tests");
+      exit (0);
+    }
+
+  if (prev_rgid == nobody_gid || prev_egid == nobody_gid
+      || prev_sgid == nobody_gid)
+    {
+      puts ("already running as group nobody, skipping tests");
+      exit (0);
+    }
+
+  if (pthread_barrier_init (&b3, NULL, 3) != 0)
+    {
+      puts ("barrier_init failed");
+      exit (1);
+    }
+
+  if (pthread_barrier_init (&b4, NULL, 4) != 0)
+    {
+      puts ("barrier_init failed");
+      exit (1);
+    }
+
+  for (unsigned long int testno = 0;
+       testno < sizeof (setuid_tests) / sizeof (setuid_tests[0]);
+       ++testno)
+    do_one_test (testno);
+  return 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"