summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog34
-rw-r--r--include/libc-symbols.h15
-rw-r--r--malloc/Makefile27
-rw-r--r--malloc/tst-interpose-aux-nothread.c20
-rw-r--r--malloc/tst-interpose-aux-thread.c20
-rw-r--r--malloc/tst-interpose-aux.c270
-rw-r--r--malloc/tst-interpose-aux.h30
-rw-r--r--malloc/tst-interpose-nothread.c20
-rw-r--r--malloc/tst-interpose-skeleton.c204
-rw-r--r--malloc/tst-interpose-static-nothread.c19
-rw-r--r--malloc/tst-interpose-static-thread.c19
-rw-r--r--malloc/tst-interpose-thread.c20
-rw-r--r--nptl/tst-tls3-malloc.c156
-rw-r--r--sysdeps/mach/hurd/fork.c6
-rw-r--r--sysdeps/nptl/fork.c6
15 files changed, 706 insertions, 160 deletions
diff --git a/ChangeLog b/ChangeLog
index 995b440c12..bf76d97258 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,37 @@
+2016-08-26  Florian Weimer  <fweimer@redhat.com>
+
+	[BZ #20432]
+	Avoid strong references to malloc-internal symbols when linking
+	statically, to support statically interposed mallocs.
+	* include/libc-symbols.h (call_function_static_weak): New macro.
+	* malloc/Makefile (tests): Add tst-interpose-nothread,
+	tst-interpose-thread, tst-interpose-static-nothread,
+	tst-interpose-static-thread.
+	(tests-static): Add tst-interpose-static-nothread,
+	tst-interpose-static-thread.
+	(extra-tests-objs): Add tst-interpose-aux-nothread.o,
+	tst-interpose-aux-thread.o.
+	(test-extras): Add tst-interpose-aux-nothread,
+	tst-interpose-aux-thread.
+	(tst-interpose-nothread, tst-interpose-static-nothread): Link with
+	tst-interpose-aux-nothread.o.
+	(tst-interpose-thread, tst-interpose-static-thread): Link with
+	tst-interpose-aux-thread.o and libthread.
+	* malloc/tst-interpose-aux-nothread.c: New file.
+	* malloc/tst-interpose-aux-thread.c: Likewise.
+	* malloc/tst-interpose-aux.c: Likewise.
+	* malloc/tst-interpose-aux.h: Likewise.
+	* malloc/tst-interpose-nothread.c: Likewise.
+	* malloc/tst-interpose-skeleton.c: Likewise.
+	* malloc/tst-interpose-static-nothread.c: Likewise.
+	* malloc/tst-interpose-static-thread.c: Likewise.
+	* malloc/tst-interpose-thread.c: Likewise.
+	* nptl/tst-tls3-malloc.c: Use new interposed malloc.
+	* sysdeps/mach/hurd/fork.c (__fork): Only call
+	__malloc_fork_lock_parent, __malloc_fork_unlock_parent,
+	__malloc_fork_unlock_child if defined.
+	* sysdeps/nptl/fork.c (__libc_fork): Likewise.
+
 2016-08-26  Adhemerval Zanella  <adhemerval.zanella@linaro.org>
 
 	* test-skeleton.c (delayed_exit_thread): Add initializer on struct
diff --git a/include/libc-symbols.h b/include/libc-symbols.h
index c2b499ae36..e362d42095 100644
--- a/include/libc-symbols.h
+++ b/include/libc-symbols.h
@@ -121,6 +121,21 @@
 # define weak_extern(symbol) _weak_extern (weak symbol)
 # define _weak_extern(expr) _Pragma (#expr)
 
+/* In shared builds, the expression call_function_static_weak
+   (FUNCTION-SYMBOL, ARGUMENTS) invokes FUNCTION-SYMBOL (an
+   identifier) unconditionally, with the (potentially empty) argument
+   list ARGUMENTS.  In static builds, if FUNCTION-SYMBOL has a
+   definition, the function is invoked as before; if FUNCTION-SYMBOL
+   is NULL, no call is performed.  */
+# ifdef SHARED
+#  define call_function_static_weak(func, ...) func (__VA_ARGS__)
+# else	/* !SHARED */
+#  define call_function_static_weak(func, ...)		\
+  ({							\
+    extern __typeof__ (func) func weak_function;	\
+    (func != NULL ? func (__VA_ARGS__) : (void)0);	\
+  })
+# endif
 
 #else /* __ASSEMBLER__ */
 
diff --git a/malloc/Makefile b/malloc/Makefile
index 4d5c81dce6..037e830f31 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -30,7 +30,16 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \
 	 tst-pvalloc tst-memalign tst-mallopt tst-scratch_buffer \
 	 tst-malloc-backtrace tst-malloc-thread-exit \
 	 tst-malloc-thread-fail tst-malloc-fork-deadlock \
-	 tst-mallocfork2
+	 tst-mallocfork2 \
+	 tst-interpose-nothread \
+	 tst-interpose-thread \
+	 tst-interpose-static-nothread \
+	 tst-interpose-static-thread \
+
+tests-static := \
+	 tst-interpose-static-nothread \
+	 tst-interpose-static-thread \
+
 test-srcs = tst-mtrace
 
 routines = malloc morecore mcheck mtrace obstack \
@@ -44,6 +53,15 @@ non-lib.a := libmcheck.a
 extra-libs = libmemusage
 extra-libs-others = $(extra-libs)
 
+# Helper objects for some tests.
+extra-tests-objs += \
+  tst-interpose-aux-nothread.o \
+  tst-interpose-aux-thread.o \
+
+test-extras = \
+  tst-interpose-aux-nothread \
+  tst-interpose-aux-thread \
+
 libmemusage-routines = memusage
 libmemusage-inhibit-o = $(filter-out .os,$(object-suffixes))
 
@@ -170,3 +188,10 @@ $(foreach o,$(all-object-suffixes),$(objpfx)malloc$(o)): arena.c hooks.c
 # Compile the tests with a flag which suppresses the mallopt call in
 # the test skeleton.
 $(tests:%=$(objpfx)%.o): CPPFLAGS += -DTEST_NO_MALLOPT
+
+$(objpfx)tst-interpose-nothread: $(objpfx)tst-interpose-aux-nothread.o
+$(objpfx)tst-interpose-thread: \
+  $(objpfx)tst-interpose-aux-thread.o $(shared-thread-library)
+$(objpfx)tst-interpose-static-nothread: $(objpfx)tst-interpose-aux-nothread.o
+$(objpfx)tst-interpose-static-thread: \
+  $(objpfx)tst-interpose-aux-thread.o $(static-thread-library)
diff --git a/malloc/tst-interpose-aux-nothread.c b/malloc/tst-interpose-aux-nothread.c
new file mode 100644
index 0000000000..0eae66fa6c
--- /dev/null
+++ b/malloc/tst-interpose-aux-nothread.c
@@ -0,0 +1,20 @@
+/* Interposed malloc, version without threading support.
+   Copyright (C) 2016 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; see the file COPYING.LIB.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#define INTERPOSE_THREADS 0
+#include "tst-interpose-aux.c"
diff --git a/malloc/tst-interpose-aux-thread.c b/malloc/tst-interpose-aux-thread.c
new file mode 100644
index 0000000000..354e4d8ed1
--- /dev/null
+++ b/malloc/tst-interpose-aux-thread.c
@@ -0,0 +1,20 @@
+/* Interposed malloc, version with threading support.
+   Copyright (C) 2016 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; see the file COPYING.LIB.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#define INTERPOSE_THREADS 1
+#include "tst-interpose-aux.c"
diff --git a/malloc/tst-interpose-aux.c b/malloc/tst-interpose-aux.c
new file mode 100644
index 0000000000..77866b2e5d
--- /dev/null
+++ b/malloc/tst-interpose-aux.c
@@ -0,0 +1,270 @@
+/* Minimal malloc implementation for interposition tests.
+   Copyright (C) 2016 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; see the file COPYING.LIB.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#include "tst-interpose-aux.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#if INTERPOSE_THREADS
+#include <pthread.h>
+#endif
+
+/* Print the error message and terminate the process with status 1.  */
+__attribute__ ((noreturn))
+__attribute__ ((format (printf, 1, 2)))
+static void *
+fail (const char *format, ...)
+{
+  /* This assumes that vsnprintf will not call malloc.  It does not do
+     so for the format strings we use.  */
+  char message[4096];
+  va_list ap;
+  va_start (ap, format);
+  vsnprintf (message, sizeof (message), format, ap);
+  va_end (ap);
+
+  enum { count = 3 };
+  struct iovec iov[count];
+
+  iov[0].iov_base = (char *) "error: ";
+  iov[1].iov_base = (char *) message;
+  iov[2].iov_base = (char *) "\n";
+
+  for (int i = 0; i < count; ++i)
+    iov[i].iov_len = strlen (iov[i].iov_base);
+
+  int unused __attribute__ ((unused));
+  unused = writev (STDOUT_FILENO, iov, count);
+  _exit (1);
+}
+
+#if INTERPOSE_THREADS
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+static void
+lock (void)
+{
+#if INTERPOSE_THREADS
+  int ret = pthread_mutex_lock (&mutex);
+  if (ret != 0)
+    {
+      errno = ret;
+      fail ("pthread_mutex_lock: %m");
+    }
+#endif
+}
+
+static void
+unlock (void)
+{
+#if INTERPOSE_THREADS
+  int ret = pthread_mutex_unlock (&mutex);
+  if (ret != 0)
+    {
+      errno = ret;
+      fail ("pthread_mutex_unlock: %m");
+    }
+#endif
+}
+
+struct __attribute__ ((aligned (__alignof__ (max_align_t)))) allocation_header
+{
+  size_t allocation_index;
+  size_t allocation_size;
+};
+
+/* Array of known allocations, to track invalid frees.  */
+enum { max_allocations = 65536 };
+static struct allocation_header *allocations[max_allocations];
+static size_t allocation_index;
+static size_t deallocation_count;
+
+/* Sanity check for successful malloc interposition.  */
+__attribute__ ((destructor))
+static void
+check_for_allocations (void)
+{
+  if (allocation_index == 0)
+    {
+      /* Make sure that malloc is called at least once from libc.  */
+      void *volatile ptr = strdup ("ptr");
+      free (ptr);
+      /* Compiler barrier.  The strdup function calls malloc, which
+         updates allocation_index, but strdup is marked __THROW, so
+         the compiler could optimize away the reload.  */
+      __asm__ volatile ("" ::: "memory");
+      /* If the allocation count is still zero, it means we did not
+         interpose malloc successfully.  */
+      if (allocation_index == 0)
+        fail ("malloc does not seem to have been interposed");
+    }
+}
+
+static struct allocation_header *get_header (const char *op, void *ptr)
+{
+  struct allocation_header *header = ((struct allocation_header *) ptr) - 1;
+  if (header->allocation_index >= allocation_index)
+    fail ("%s: %p: invalid allocation index: %zu (not less than %zu)",
+          op, ptr, header->allocation_index, allocation_index);
+  if (allocations[header->allocation_index] != header)
+    fail ("%s: %p: allocation pointer does not point to header, but %p",
+          op, ptr, allocations[header->allocation_index]);
+  return header;
+}
+
+/* Internal helper functions.  Those must be called while the lock is
+   acquired.  */
+
+static void *
+malloc_internal (size_t size)
+{
+  if (allocation_index == max_allocations)
+    {
+      errno = ENOMEM;
+      return NULL;
+    }
+  size_t allocation_size = size + sizeof (struct allocation_header);
+  if (allocation_size < size)
+    {
+      errno = ENOMEM;
+      return NULL;
+    }
+
+  size_t index = allocation_index++;
+  void *result = mmap (NULL, allocation_size, PROT_READ | PROT_WRITE,
+                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (result == MAP_FAILED)
+    return NULL;
+  allocations[index] = result;
+  *allocations[index] = (struct allocation_header)
+    {
+      .allocation_index = index,
+      .allocation_size = allocation_size
+    };
+  return allocations[index] + 1;
+}
+
+static void
+free_internal (const char *op, struct allocation_header *header)
+{
+  size_t index = header->allocation_index;
+  int result = mprotect (header, header->allocation_size, PROT_NONE);
+  if (result != 0)
+    fail ("%s: mprotect (%p, %zu): %m", op, header, header->allocation_size);
+  /* Catch double-free issues.  */
+  allocations[index] = NULL;
+  ++deallocation_count;
+}
+
+static void *
+realloc_internal (void *ptr, size_t new_size)
+{
+  struct allocation_header *header = get_header ("realloc", ptr);
+  size_t old_size = header->allocation_size - sizeof (struct allocation_header);
+  if (old_size >= new_size)
+    return ptr;
+
+  void *newptr = malloc_internal (new_size);
+  if (newptr == NULL)
+    return NULL;
+  memcpy (newptr, ptr, old_size);
+  free_internal ("realloc", header);
+  return newptr;
+}
+
+/* Public interfaces.  These functions must perform locking.  */
+
+size_t
+malloc_allocation_count (void)
+{
+  lock ();
+  size_t count = allocation_index;
+  unlock ();
+  return count;
+}
+
+size_t
+malloc_deallocation_count (void)
+{
+  lock ();
+  size_t count = deallocation_count;
+  unlock ();
+  return count;
+}
+void *
+malloc (size_t size)
+{
+  lock ();
+  void *result = malloc_internal (size);
+  unlock ();
+  return result;
+}
+
+void
+free (void *ptr)
+{
+  if (ptr == NULL)
+    return;
+  lock ();
+  struct allocation_header *header = get_header ("free", ptr);
+  free_internal ("free", header);
+  unlock ();
+}
+
+void *
+calloc (size_t a, size_t b)
+{
+  if (b > 0 && a > SIZE_MAX / b)
+    {
+      errno = ENOMEM;
+      return NULL;
+    }
+  lock ();
+  /* malloc_internal uses mmap, so the memory is zeroed.  */
+  void *result = malloc_internal (a * b);
+  unlock ();
+  return result;
+}
+
+void *
+realloc (void *ptr, size_t n)
+{
+  if (n ==0)
+    {
+      free (ptr);
+      return NULL;
+    }
+  else if (ptr == NULL)
+    return malloc (n);
+  else
+    {
+      lock ();
+      void *result = realloc_internal (ptr, n);
+      unlock ();
+      return result;
+    }
+}
diff --git a/malloc/tst-interpose-aux.h b/malloc/tst-interpose-aux.h
new file mode 100644
index 0000000000..2fb22d312a
--- /dev/null
+++ b/malloc/tst-interpose-aux.h
@@ -0,0 +1,30 @@
+/* Statistics interface for the minimal malloc implementation.
+   Copyright (C) 2016 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; see the file COPYING.LIB.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef TST_INTERPOSE_AUX_H
+#define TST_INTERPOSE_AUX_H
+
+#include <stddef.h>
+
+/* Return the number of allocations performed.  */
+size_t malloc_allocation_count (void);
+
+/* Return the number of deallocations performed.  */
+size_t malloc_deallocation_count (void);
+
+#endif /* TST_INTERPOSE_AUX_H */
diff --git a/malloc/tst-interpose-nothread.c b/malloc/tst-interpose-nothread.c
new file mode 100644
index 0000000000..9acb572098
--- /dev/null
+++ b/malloc/tst-interpose-nothread.c
@@ -0,0 +1,20 @@
+/* Malloc interposition test, dynamically-linked version without threads.
+   Copyright (C) 2016 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; see the file COPYING.LIB.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#define INTERPOSE_THREADS 0
+#include "tst-interpose-skeleton.c"
diff --git a/malloc/tst-interpose-skeleton.c b/malloc/tst-interpose-skeleton.c
new file mode 100644
index 0000000000..d1ebc9ae2d
--- /dev/null
+++ b/malloc/tst-interpose-skeleton.c
@@ -0,0 +1,204 @@
+/* Test driver for malloc interposition tests.
+   Copyright (C) 2016 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; see the file COPYING.LIB.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#if INTERPOSE_THREADS
+#include <pthread.h>
+#endif
+
+static int do_test (void);
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
+
+/* Fills BUFFER with a test string.  */
+static void
+line_string (int number, char *buffer, size_t length)
+{
+  for (size_t i = 0; i < length - 2; ++i)
+    buffer[i] = 'A' + ((number + i) % 26);
+  buffer[length - 2] = '\n';
+  buffer[length - 1] = '\0';
+}
+
+/* Perform the tests.  */
+static void *
+run_tests (void *closure)
+{
+  char *temp_file_path;
+  int fd = create_temp_file ("tst-malloc-interpose", &temp_file_path);
+  if (fd < 0)
+    _exit (1);
+
+  /* Line lengths excluding the line terminator.  */
+  static const int line_lengths[] = { 0, 45, 80, 2, 8201, 0, 17, -1 };
+
+  /* Fill the test file with data.  */
+  {
+    FILE *fp = fdopen (fd, "w");
+    for (int lineno = 0; line_lengths[lineno] >= 0; ++lineno)
+      {
+        char buffer[line_lengths[lineno] + 2];
+        line_string (lineno, buffer, sizeof (buffer));
+        fprintf (fp, "%s", buffer);
+      }
+
+    if (ferror (fp))
+      {
+        printf ("error: fprintf: %m\n");
+        _exit (1);
+      }
+    if (fclose (fp) != 0)
+      {
+        printf ("error: fclose: %m\n");
+        _exit (1);
+      }
+  }
+
+  /* Read the test file.  This tests libc-internal allocation with
+     realloc.  */
+  {
+    FILE *fp = fopen (temp_file_path, "r");
+
+    char *actual = NULL;
+    size_t actual_size = 0;
+    for (int lineno = 0; ; ++lineno)
+      {
+        errno = 0;
+        ssize_t result = getline (&actual, &actual_size, fp);
+        if (result == 0)
+          {
+            printf ("error: invalid return value 0 from getline\n");
+            _exit (1);
+          }
+        if (result < 0 && errno != 0)
+          {
+            printf ("error: getline: %m\n");
+            _exit (1);
+          }
+        if (result < 0 && line_lengths[lineno] >= 0)
+          {
+            printf ("error: unexpected end of file after line %d\n", lineno);
+            _exit (1);
+          }
+        if (result > 0 && line_lengths[lineno] < 0)
+          {
+            printf ("error: no end of file after line %d\n", lineno);
+            _exit (1);
+          }
+        if (result == -1 && line_lengths[lineno] == -1)
+          /* End of file reached as expected.  */
+          break;
+
+        if (result != line_lengths[lineno] + 1)
+          {
+            printf ("error: line length mismatch: expected %d, got %zd\n",
+                    line_lengths[lineno], result);
+            _exit (1);
+          }
+
+        char expected[line_lengths[lineno] + 2];
+        line_string (lineno, expected, sizeof (expected));
+        if (strcmp (actual, expected) != 0)
+          {
+            printf ("error: line mismatch\n");
+            printf ("error:   expected: [[%s]]\n", expected);
+            printf ("error:   actual:   [[%s]]\n", actual);
+            _exit (1);
+          }
+      }
+
+    if (fclose (fp) != 0)
+      {
+        printf ("error: fclose (after reading): %m\n");
+        _exit (1);
+      }
+  }
+
+  free (temp_file_path);
+
+  /* Make sure that fork is working.  */
+  pid_t pid = fork ();
+  if (pid == -1)
+    {
+      printf ("error: fork: %m\n");
+      _exit (1);
+    }
+  enum { exit_code = 55 };
+  if (pid == 0)
+    _exit (exit_code);
+  int status;
+  int ret = waitpid (pid, &status, 0);
+  if (ret < 0)
+    {
+      printf ("error: waitpid: %m\n");
+      _exit (1);
+    }
+  if (!WIFEXITED (status) || WEXITSTATUS (status) != exit_code)
+    {
+      printf ("error: unexpected exit status from child process: %d\n",
+              status);
+      _exit (1);
+    }
+
+  return NULL;
+}
+
+/* This is used to detect if malloc has not been successfully
+   interposed.  The interposed malloc does not use brk/sbrk.  */
+static void *initial_brk;
+__attribute__ ((constructor))
+static void
+set_initial_brk (void)
+{
+  initial_brk = sbrk (0);
+}
+
+/* Terminate the process if the break value has been changed.  */
+__attribute__ ((destructor))
+static void
+check_brk (void)
+{
+  void *current = sbrk (0);
+  if (current != initial_brk)
+    {
+      printf ("error: brk changed from %p to %p; no interposition?\n",
+              initial_brk, current);
+      _exit (1);
+    }
+}
+
+static int
+do_test (void)
+{
+  check_brk ();
+
+#if INTERPOSE_THREADS
+  pthread_t thr = xpthread_create (NULL, run_tests, NULL);
+  xpthread_join (thr);
+#else
+  run_tests (NULL);
+#endif
+
+  check_brk ();
+
+  return 0;
+}
diff --git a/malloc/tst-interpose-static-nothread.c b/malloc/tst-interpose-static-nothread.c
new file mode 100644
index 0000000000..3fb2dd8777
--- /dev/null
+++ b/malloc/tst-interpose-static-nothread.c
@@ -0,0 +1,19 @@
+/* Malloc interposition test, static version without threads.
+   Copyright (C) 2016 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; see the file COPYING.LIB.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#include "tst-interpose-nothread.c"
diff --git a/malloc/tst-interpose-static-thread.c b/malloc/tst-interpose-static-thread.c
new file mode 100644
index 0000000000..c78ebc70ba
--- /dev/null
+++ b/malloc/tst-interpose-static-thread.c
@@ -0,0 +1,19 @@
+/* Malloc interposition test, static version with threads.
+   Copyright (C) 2016 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; see the file COPYING.LIB.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#include "tst-interpose-nothread.c"
diff --git a/malloc/tst-interpose-thread.c b/malloc/tst-interpose-thread.c
new file mode 100644
index 0000000000..d3e20c7457
--- /dev/null
+++ b/malloc/tst-interpose-thread.c
@@ -0,0 +1,20 @@
+/* Malloc interposition test, dynamically-linked version with threads.
+   Copyright (C) 2016 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; see the file COPYING.LIB.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#define INTERPOSE_THREADS 1
+#include "tst-interpose-skeleton.c"
diff --git a/nptl/tst-tls3-malloc.c b/nptl/tst-tls3-malloc.c
index 8a580fa789..719ab28cf0 100644
--- a/nptl/tst-tls3-malloc.c
+++ b/nptl/tst-tls3-malloc.c
@@ -17,6 +17,7 @@
    <http://www.gnu.org/licenses/>.  */
 
 /* Reuse the test.  */
+#define STACK_SIZE_MB 5
 #include "tst-tls3.c"
 
 /* Increase the thread stack size to 10 MiB, so that some thread
@@ -26,156 +27,5 @@ static long stack_size_in_mb = 10;
 
 #include <sys/mman.h>
 
-/* Interpose a minimal malloc implementation.  This implementation
-   deliberately interposes just a restricted set of symbols, to detect
-   if the TLS code bypasses the interposed malloc.  */
-
-/* Lock to guard malloc internals.  */
-static pthread_mutex_t malloc_lock = PTHREAD_MUTEX_INITIALIZER;
-
-/* Information about an allocation chunk.  */
-struct malloc_chunk
-{
-  /* Start of the allocation.  */
-  void *start;
-  /* Size of the allocation.  */
-  size_t size;
-};
-
-enum { malloc_chunk_count = 1000 };
-static struct malloc_chunk chunks[malloc_chunk_count];
-
-/* Lock the malloc lock.  */
-static void
-xlock (void)
-{
-  int ret = pthread_mutex_lock (&malloc_lock);
-  if (ret != 0)
-    {
-      errno = ret;
-      printf ("error: pthread_mutex_lock: %m\n");
-      _exit (1);
-    }
-}
-
-/* Unlock the malloc lock.  */
-static void
-xunlock (void)
-{
-  int ret = pthread_mutex_unlock (&malloc_lock);
-  if (ret != 0)
-    {
-      errno = ret;
-      printf ("error: pthread_mutex_unlock: %m\n");
-      _exit (1);
-    }
-}
-
-/* Internal malloc without locking and registration.  */
-static void *
-malloc_internal (size_t size)
-{
-  void *result = mmap (NULL, size, PROT_READ | PROT_WRITE,
-                       MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
-  if (result == MAP_FAILED)
-    {
-      printf ("error: mmap: %m\n");
-      _exit (1);
-    }
-  return result;
-}
-
-void *
-malloc (size_t size)
-{
-  if (size == 0)
-    size = 1;
-  xlock ();
-  void *result = malloc_internal (size);
-  for (int i = 0; i < malloc_chunk_count; ++i)
-    if (chunks[i].start == NULL)
-      {
-        chunks[i].start = result;
-        chunks[i].size = size;
-        xunlock ();
-        return result;
-      }
-  xunlock ();
-  printf ("error: no place to store chunk pointer\n");
-  _exit (1);
-}
-
-void *
-calloc (size_t a, size_t b)
-{
-  if (b != 0 && a > SIZE_MAX / b)
-    return NULL;
-  /* malloc uses mmap, which provides zeroed memory.  */
-  return malloc (a * b);
-}
-
-static void
-xunmap (void *ptr, size_t size)
-{
-  int ret = munmap (ptr, size);
-  if (ret < 0)
-    {
-      printf ("error: munmap (%p, %zu) failed: %m\n", ptr, size);
-      _exit (1);
-    }
-}
-
-void
-free (void *ptr)
-{
-  if (ptr == NULL)
-    return;
-
-  xlock ();
-  for (int i = 0; i < malloc_chunk_count; ++i)
-    if (chunks[i].start == ptr)
-      {
-        xunmap (ptr, chunks[i].size);
-        chunks[i] = (struct malloc_chunk) {};
-        xunlock ();
-        return;
-      }
-  xunlock ();
-  printf ("error: tried to free non-allocated pointer %p\n", ptr);
-  _exit (1);
-}
-
-void *
-realloc (void *old, size_t size)
-{
-  if (old != NULL)
-    {
-      xlock ();
-      for (int i = 0; i < malloc_chunk_count; ++i)
-        if (chunks[i].start == old)
-          {
-            size_t old_size = chunks[i].size;
-            void *result;
-            if (old_size < size)
-              {
-                result = malloc_internal (size);
-                /* Reuse the slot for the new allocation.  */
-                memcpy (result, old, old_size);
-                xunmap (old, old_size);
-                chunks[i].start = result;
-                chunks[i].size = size;
-              }
-            else
-              /* Old size is not smaller, so reuse the old
-                 allocation.  */
-              result = old;
-            xunlock ();
-            return result;
-          }
-      xunlock ();
-      printf ("error: tried to realloc non-allocated pointer %p\n", old);
-      _exit (1);
-    }
-  else
-    return malloc (size);
-}
+#define INTERPOSE_THREADS 1
+#include "../malloc/tst-interpose-aux.c"
diff --git a/sysdeps/mach/hurd/fork.c b/sysdeps/mach/hurd/fork.c
index 2e8b59e7c0..9973b1bb9e 100644
--- a/sysdeps/mach/hurd/fork.c
+++ b/sysdeps/mach/hurd/fork.c
@@ -112,7 +112,7 @@ __fork (void)
 	 handlers may use malloc, and the libio list lock has an
 	 indirect malloc dependency as well (via the getdelim
 	 function).  */
-      __malloc_fork_lock_parent ();
+      call_function_static_weak (__malloc_fork_lock_parent);
 
       /* Lock things that want to be locked before we fork.  */
       {
@@ -612,7 +612,7 @@ __fork (void)
 	}
 
       /* Release malloc locks.  */
-      __malloc_fork_unlock_parent ();
+      call_function_static_weak (__malloc_fork_unlock_parent);
 
       /* Run things that want to run in the parent to restore it to
 	 normality.  Usually prepare hooks and parent hooks are
@@ -666,7 +666,7 @@ __fork (void)
       __sigemptyset (&_hurdsig_traced);
 
       /* Release malloc locks.  */
-      __malloc_fork_unlock_child ();
+      call_function_static_weak (__malloc_fork_unlock_child);
 
       /* Run things that want to run in the child task to set up.  */
       RUN_HOOK (_hurd_fork_child_hook, ());
diff --git a/sysdeps/nptl/fork.c b/sysdeps/nptl/fork.c
index 616d897a36..ea135f8e18 100644
--- a/sysdeps/nptl/fork.c
+++ b/sysdeps/nptl/fork.c
@@ -128,7 +128,7 @@ __libc_fork (void)
 	 handlers may use malloc, and the libio list lock has an
 	 indirect malloc dependency as well (via the getdelim
 	 function).  */
-      __malloc_fork_lock_parent ();
+      call_function_static_weak (__malloc_fork_lock_parent);
     }
 
 #ifndef NDEBUG
@@ -192,7 +192,7 @@ __libc_fork (void)
       if (multiple_threads)
 	{
 	  /* Release malloc locks.  */
-	  __malloc_fork_unlock_child ();
+	  call_function_static_weak (__malloc_fork_unlock_child);
 
 	  /* Reset the file list.  These are recursive mutexes.  */
 	  fresetlockfiles ();
@@ -240,7 +240,7 @@ __libc_fork (void)
       if (multiple_threads)
 	{
 	  /* Release malloc locks, parent process variant.  */
-	  __malloc_fork_unlock_parent ();
+	  call_function_static_weak (__malloc_fork_unlock_parent);
 
 	  /* We execute this even if the 'fork' call failed.  */
 	  _IO_list_unlock ();