aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2014-07-10 17:34:46 +0200
committerFlorian Weimer <fweimer@redhat.com>2014-07-11 12:30:53 +0200
commit771eb1415fde935e377111f69364a5d92a29e67d (patch)
tree1e9dd40b64e87fe715c4bc7ccee7487c6e500a8a
parentbc1da1765e901a9a9f532f91d09f5237655e01fd (diff)
downloadglibc-771eb1415fde935e377111f69364a5d92a29e67d.tar
glibc-771eb1415fde935e377111f69364a5d92a29e67d.tar.gz
glibc-771eb1415fde935e377111f69364a5d92a29e67d.tar.bz2
glibc-771eb1415fde935e377111f69364a5d92a29e67d.zip
nptl: Fix abort in case of set*id failure [BZ #17135]
If a call to the set*id functions fails in a multi-threaded program, the abort introduced in commit 13f7fe35ae2b0ea55dc4b9628763aafdc8bdc30c was triggered. We address by checking that all calls to set*id on all threads give the same result, and only abort if we see success followed by failure (or vice versa).
-rw-r--r--ChangeLog12
-rw-r--r--NEWS2
-rw-r--r--nptl/Makefile1
-rw-r--r--nptl/allocatestack.c27
-rw-r--r--nptl/descr.h1
-rw-r--r--nptl/nptl-init.c6
-rw-r--r--nptl/pthreadP.h2
-rw-r--r--nptl/tst-setuid3.c104
8 files changed, 149 insertions, 6 deletions
diff --git a/ChangeLog b/ChangeLog
index 99b87329d5..7e847697b0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2014-07-10 Florian Weimer <fweimer@redhat.com>
+
+ [BZ #17135]
+ * nptl/pthreadP.h (__nptl_setxid_error): Declare function.
+ * nptl/allocatestack.c (__nptl_setxid_error): New function.
+ (__nptl_setxid): Initialize error member. Call
+ __nptl_setxid_error.
+ * nptl/nptl-init.c (sighandler_setxid): Call __nptl_setxid_error.
+ * nptl/descr.h (struct xid_command): Add error member.
+ * nptl/tst-setuid3.c: New file.
+ * nptl/Makefile (tests): Add it.
+
2014-07-10 Adhemerval Zanella <azanella@linux.vnet.ibm.com>
* sysdeps/unix/sysv/linux/powerpc/lowlevellock.h (__lll_base_trylock):
diff --git a/NEWS b/NEWS
index a6617a145e..697b730ee0 100644
--- a/NEWS
+++ b/NEWS
@@ -22,7 +22,7 @@ Version 2.20
16927, 16928, 16932, 16943, 16958, 16965, 16966, 16967, 16977, 16978,
16984, 16990, 16996, 17009, 17022, 17031, 17042, 17048, 17050, 17058,
17061, 17062, 17069, 17075, 17079, 17084, 17086, 17092, 17097, 17125,
- 17137.
+ 17135, 17137.
* Optimized strchr implementation for AArch64. Contributed by ARM Ltd.
diff --git a/nptl/Makefile b/nptl/Makefile
index 67f7d5bfa9..ab3080e5bb 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -271,6 +271,7 @@ tests = tst-typesizes \
tst-abstime \
tst-vfork1 tst-vfork2 tst-vfork1x tst-vfork2x \
tst-getpid1 tst-getpid2 tst-getpid3 \
+ tst-setuid3 \
tst-initializers1 $(patsubst %,tst-initializers1-%,c89 gnu89 c99 gnu99)
xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
tst-mutexpp1 tst-mutexpp6 tst-mutexpp10
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index 9095ef430d..d95ffe9d36 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -1059,6 +1059,25 @@ setxid_signal_thread (struct xid_command *cmdp, struct pthread *t)
return 0;
}
+/* Check for consistency across set*id system call results. The abort
+ should not happen as long as all privileges changes happen through
+ the glibc wrappers. ERROR must be 0 (no error) or an errno
+ code. */
+void
+attribute_hidden
+__nptl_setxid_error (struct xid_command *cmdp, int error)
+{
+ do
+ {
+ int olderror = cmdp->error;
+ if (olderror == error)
+ break;
+ if (olderror != -1)
+ /* Mismatch between current and previous results. */
+ abort ();
+ }
+ while (atomic_compare_and_exchange_bool_acq (&cmdp->error, error, -1));
+}
int
attribute_hidden
@@ -1070,6 +1089,7 @@ __nptl_setxid (struct xid_command *cmdp)
__xidcmd = cmdp;
cmdp->cntr = 0;
+ cmdp->error = -1;
struct pthread *self = THREAD_SELF;
@@ -1153,11 +1173,14 @@ __nptl_setxid (struct xid_command *cmdp)
INTERNAL_SYSCALL_DECL (err);
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))
+ int error = 0;
+ if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (result, err)))
{
- __set_errno (INTERNAL_SYSCALL_ERRNO (result, err));
+ error = INTERNAL_SYSCALL_ERRNO (result, err);
+ __set_errno (error);
result = -1;
}
+ __nptl_setxid_error (cmdp, error);
lll_unlock (stack_cache_lock, LLL_PRIVATE);
return result;
diff --git a/nptl/descr.h b/nptl/descr.h
index 61d57d5732..6738591585 100644
--- a/nptl/descr.h
+++ b/nptl/descr.h
@@ -100,6 +100,7 @@ struct xid_command
int syscall_no;
long int id[3];
volatile int cntr;
+ volatile int error; /* -1: no call yet, 0: success seen, >0: error seen. */
};
diff --git a/nptl/nptl-init.c b/nptl/nptl-init.c
index 2796dc5182..9f7b20a90e 100644
--- a/nptl/nptl-init.c
+++ b/nptl/nptl-init.c
@@ -248,10 +248,10 @@ sighandler_setxid (int sig, siginfo_t *si, void *ctx)
INTERNAL_SYSCALL_DECL (err);
result = INTERNAL_SYSCALL_NCS (__xidcmd->syscall_no, err, 3, __xidcmd->id[0],
__xidcmd->id[1], __xidcmd->id[2]);
+ int error = 0;
if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (result, err)))
- /* Safety check. This should never happen if the setxid system
- calls are only ever called through their glibc wrappers. */
- abort ();
+ error = INTERNAL_SYSCALL_ERRNO (result, err);
+ __nptl_setxid_error (__xidcmd, error);
/* Reset the SETXID flag. */
struct pthread *self = THREAD_SELF;
diff --git a/nptl/pthreadP.h b/nptl/pthreadP.h
index 197401af89..94e7890fa5 100644
--- a/nptl/pthreadP.h
+++ b/nptl/pthreadP.h
@@ -578,6 +578,8 @@ extern void _pthread_cleanup_pop_restore (struct _pthread_cleanup_buffer *buffer
extern void __nptl_deallocate_tsd (void) attribute_hidden;
+extern void __nptl_setxid_error (struct xid_command *cmdp, int error)
+ attribute_hidden;
extern int __nptl_setxid (struct xid_command *cmdp) attribute_hidden;
#ifndef SHARED
extern void __nptl_set_robust (struct pthread *self);
diff --git a/nptl/tst-setuid3.c b/nptl/tst-setuid3.c
new file mode 100644
index 0000000000..f78f485239
--- /dev/null
+++ b/nptl/tst-setuid3.c
@@ -0,0 +1,104 @@
+/* Copyright (C) 2014 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 <err.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+/* The test must run under a non-privileged user ID. */
+static const uid_t test_uid = 1;
+
+static pthread_barrier_t barrier1;
+static pthread_barrier_t barrier2;
+
+static void *
+thread_func (void *ctx __attribute__ ((unused)))
+{
+ int ret = pthread_barrier_wait (&barrier1);
+ if (ret != PTHREAD_BARRIER_SERIAL_THREAD && ret != 0)
+ errx (1, "pthread_barrier_wait (barrier1) (on thread): %d", ret);
+ ret = pthread_barrier_wait (&barrier2);
+ if (ret != PTHREAD_BARRIER_SERIAL_THREAD && ret != 0)
+ errx (1, "pthread_barrier_wait (barrier2) (on thread): %d", ret);
+ return NULL;
+}
+
+static void
+setuid_failure (int phase)
+{
+ int ret = setuid (0);
+ switch (ret)
+ {
+ case 0:
+ errx (1, "setuid succeeded unexpectedly in phase %d", phase);
+ case -1:
+ if (errno != EPERM)
+ err (1, "setuid phase %d", phase);
+ break;
+ default:
+ errx (1, "invalid setuid return value in phase %d: %d", phase, ret);
+ }
+}
+
+static int
+do_test (void)
+{
+ if (getuid () == 0)
+ if (setuid (test_uid) != 0)
+ err (1, "setuid (%u)", (unsigned) test_uid);
+ if (setuid (getuid ()))
+ err (1, "setuid (getuid ())");
+ setuid_failure (1);
+
+ int ret = pthread_barrier_init (&barrier1, NULL, 2);
+ if (ret != 0)
+ errx (1, "pthread_barrier_init (barrier1): %d", ret);
+ ret = pthread_barrier_init (&barrier2, NULL, 2);
+ if (ret != 0)
+ errx (1, "pthread_barrier_init (barrier2): %d", ret);
+
+ pthread_t thread;
+ ret = pthread_create (&thread, NULL, thread_func, NULL);
+ if (ret != 0)
+ errx (1, "pthread_create: %d", ret);
+
+ /* Ensure that the thread is running properly. */
+ ret = pthread_barrier_wait (&barrier1);
+ if (ret != 0)
+ errx (1, "pthread_barrier_wait (barrier1): %d", ret);
+
+ setuid_failure (2);
+
+ /* Check success case. */
+ if (setuid (getuid ()) != 0)
+ err (1, "setuid (getuid ())");
+
+ /* Shutdown. */
+ ret = pthread_barrier_wait (&barrier2);
+ if (ret != PTHREAD_BARRIER_SERIAL_THREAD && ret != 0)
+ errx (1, "pthread_barrier_wait (barrier2): %d", ret);
+
+ if (ret != PTHREAD_BARRIER_SERIAL_THREAD && ret != 0)
+ errx (1, "pthread_join: %d", ret);
+
+ return 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"