[PATCH v3 06/11] malloc, elf: Add malloc_sanitize to libc_malloc_debug.so

Sung-hun Kim sfoon.kim@samsung.com
Tue Sep 30 06:46:55 GMT 2025


A new hook, malloc_sanitize, has been introduced to provide address sanitization
extension for glibc malloc.
As a proof of concept, I have implemented sampling-asan as the back-end method of
malloc_sanitize.

This feature can be enabled by setting the MALLOC_SANITIZE_ tunable as follows:

 $ LD_PRELOAD=/path/to/libc_malloc_debug.so MALLOC_SANITIZE_=sampling-asan <program>

fore configuring runtime options of sampling-asan, please refer the README in the
sampling-asan directory.

To pass tst-rtld-list-tunables in elf, the new tunable is added in the
tst-rtld-list-unables.exp file.

Signed-off-by: Sung-hun Kim <sfoon.kim@samsung.com>
---
 Rules                          |  20 +++++
 elf/dl-tunables.list           |   6 ++
 elf/tst-rtld-list-tunables.exp |   1 +
 malloc/Makefile                |   8 ++
 malloc/malloc-debug.c          |  18 ++++
 malloc/malloc-sanitize.c       | 147 +++++++++++++++++++++++++++++++++
 malloc/tst-malloc-sanitize.c   |  78 +++++++++++++++++
 7 files changed, 278 insertions(+)
 create mode 100644 malloc/malloc-sanitize.c
 create mode 100644 malloc/tst-malloc-sanitize.c

diff --git a/Rules b/Rules
index 44c041c491..995773616a 100644
--- a/Rules
+++ b/Rules
@@ -167,6 +167,7 @@ tests: $(tests:%=$(objpfx)%.out) $(tests-internal:%=$(objpfx)%.out) \
        $(tests-malloc-hugetlb1:%=$(objpfx)%-malloc-hugetlb1.out) \
        $(tests-malloc-hugetlb2:%=$(objpfx)%-malloc-hugetlb2.out) \
        $(tests-malloc-largetcache:%=$(objpfx)%-malloc-largetcache.out) \
+       $(tests-malloc-sanitize:%=$(objpfx)%-malloc-sanitize.out) \
        $(tests-special) $(tests-printers-out)
 xtests: tests $(xtests:%=$(objpfx)%.out) $(xtests-special)
 endif # $(run-built-tests) != no
@@ -182,6 +183,7 @@ tests-expected = $(tests) $(tests-internal) $(tests-printers) \
 	$(tests-malloc-hugetlb1:%=%-malloc-hugetlb1) \
 	$(tests-malloc-hugetlb2:%=%-malloc-hugetlb2) \
 	$(tests-malloc-largetcache:%=%-malloc-largetcache) \
+	$(tests-malloc-sanitize:%=%-malloc-sanitize) \
 	$(tests-mcheck:%=%-mcheck)
 xtests-expected = $(xtests)
 endif # $(run-built-tests) != no
@@ -215,6 +217,7 @@ binaries-malloc-check-tests = $(tests-malloc-check:%=%-malloc-check)
 binaries-malloc-hugetlb1-tests = $(tests-malloc-hugetlb1:%=%-malloc-hugetlb1)
 binaries-malloc-hugetlb2-tests = $(tests-malloc-hugetlb2:%=%-malloc-hugetlb2)
 binaries-malloc-largetcache-tests = $(tests-malloc-largetcache:%=%-malloc-largetcache)
+binaries-malloc-sanitize-tests = $(tests-malloc-sanitize:%=%-malloc-sanitize)
 else
 binaries-all-notests =
 binaries-all-tests = $(tests) $(tests-internal) $(xtests) $(test-srcs)
@@ -229,6 +232,7 @@ binaries-malloc-check-tests =
 binaries-malloc-hugetlb1-tests =
 binaries-malloc-hugetlb2-tests =
 binaries-malloc-largetcache-tests =
+binaries-malloc-sanitize-tests =
 endif
 
 binaries-pie = $(binaries-pie-tests) $(binaries-pie-notests)
@@ -303,6 +307,14 @@ $(addprefix $(objpfx),$(binaries-malloc-largetcache-tests)): %-malloc-largetcach
 	$(+link-tests)
 endif
 
+ifneq "$(strip $(binaries-malloc-sanitize-tests))" ""
+$(addprefix $(objpfx),$(binaries-malloc-sanitize-tests)): %-malloc-sanitize: %.o \
+  $(link-extra-libs-tests) \
+  $(sort $(filter $(common-objpfx)lib%,$(link-libc))) \
+  $(addprefix $(csu-objpfx),start.o) $(+preinit) $(+postinit)
+	$(+link-tests)
+endif
+
 ifneq "$(strip $(binaries-pie-tests))" ""
 $(addprefix $(objpfx),$(binaries-pie-tests)): %: %.o \
   $(link-extra-libs-tests) \
@@ -358,6 +370,14 @@ $(1)-malloc-largetcache-ENV += GLIBC_TUNABLES=glibc.malloc.tcache_max=1048576
 endef
 $(foreach t,$(tests-malloc-largetcache),$(eval $(call malloc-largetcache-ENVS,$(t))))
 
+# All malloc-sanitize tests will be run with MALLOC_SANITIZE_=sampling-asan
+define malloc-sanitize-ENVS
+$(1)-malloc-sanitize-ENV = MALLOC_SANITIZE_=sampling-asan SAMASAN_ENABLE=true \
+      SAMASAN_SAMPLING_RATE=1.0 SAMASAN_MAX_ALLOC_SIZE=4096 \
+      LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
+endef
+$(foreach t,$(tests-malloc-sanitize),$(eval $(call malloc-sanitize-ENVS,$(t))))
+
 # mcheck tests need the debug DSO to support -lmcheck.
 define mcheck-ENVS
 $(1)-mcheck-ENV = LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list
index c03c9967f0..dd4804b77b 100644
--- a/elf/dl-tunables.list
+++ b/elf/dl-tunables.list
@@ -82,6 +82,12 @@ glibc {
       type: SIZE_T
       minval: 0
     }
+    malloc_sanitize {
+      type: STRING
+      minval: 7
+      maxval: 13
+      env_alias: MALLOC_SANITIZE_
+    }
   }
 
   elision {
diff --git a/elf/tst-rtld-list-tunables.exp b/elf/tst-rtld-list-tunables.exp
index 8df6f5906e..b948d4a503 100644
--- a/elf/tst-rtld-list-tunables.exp
+++ b/elf/tst-rtld-list-tunables.exp
@@ -2,6 +2,7 @@ glibc.malloc.arena_max: 0x0 (min: 0x1, max: 0x[f]+)
 glibc.malloc.arena_test: 0x0 (min: 0x1, max: 0x[f]+)
 glibc.malloc.check: 0 (min: 0, max: 3)
 glibc.malloc.hugetlb: 0x0 (min: 0x0, max: 0x[f]+)
+glibc.malloc.malloc_sanitize:
 glibc.malloc.mmap_max: 0 (min: 0, max: 2147483647)
 glibc.malloc.mmap_threshold: 0x0 (min: 0x0, max: 0x[f]+)
 glibc.malloc.mxfast: 0x0 (min: 0x0, max: 0x[f]+)
diff --git a/malloc/Makefile b/malloc/Makefile
index cc012e2921..7cadf4b786 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -41,6 +41,7 @@ tests := \
   tst-malloc-check \
   tst-malloc-fork-deadlock \
   tst-malloc-random \
+  tst-malloc-sanitize \
   tst-malloc-stats-cancellation \
   tst-malloc-tcache-leak \
   tst-malloc-thread-exit \
@@ -106,6 +107,7 @@ tests-exclude-malloc-check = \
   tst-compathooks-off \
   tst-compathooks-on \
   tst-malloc-check \
+  tst-malloc-sanitize \
   tst-malloc-tcache-leak \
   tst-malloc-usable \
   tst-mallocfork2 \
@@ -131,6 +133,7 @@ tests-exclude-hugetlb1 = \
   tst-interpose-static-nothread \
   tst-interpose-static-thread \
   tst-interpose-thread \
+  tst-malloc-sanitize \
   tst-malloc-tcache-leak \
   tst-malloc-usable \
   tst-malloc-usable-tunables \
@@ -157,6 +160,7 @@ tests-exclude-largetcache = \
   tst-interpose-static-thread \
   tst-interpose-thread \
   tst-malloc-backtrace \
+  tst-malloc-sanitize \
   tst-malloc-usable \
   tst-malloc-usable-tunables \
   tst-mallocstate \
@@ -177,6 +181,7 @@ tests-exclude-mcheck = \
   tst-compathooks-on \
   tst-malloc-backtrace \
   tst-malloc-fork-deadlock \
+  tst-malloc-sanitize \
   tst-malloc-stats-cancellation \
   tst-malloc-tcache-leak \
   tst-malloc-thread-exit \
@@ -384,6 +389,9 @@ tst-malloc-usable-ENV = MALLOC_CHECK_=3 \
 		       LD_PRELOAD=$(objpfx)/libc_malloc_debug.so
 tst-malloc-usable-tunables-ENV = GLIBC_TUNABLES=glibc.malloc.check=3 \
 				 LD_PRELOAD=$(objpfx)/libc_malloc_debug.so
+tst-malloc-sanitize-ENV = MALLOC_SANITIZE_=sampling-asan SAMASAN_ENABLE=true \
+           SAMASAN_SAMPLING_RATE=1.0 SAMASAN_MAX_ALLOC_SIZE=4096 \
+           LD_PRELOAD=$(objpfx)/libc_malloc_debug.so
 
 tst-mxfast-ENV = GLIBC_TUNABLES=glibc.malloc.tcache_count=0:glibc.malloc.mxfast=0
 
diff --git a/malloc/malloc-debug.c b/malloc/malloc-debug.c
index 0bb57841ee..56622eddfa 100644
--- a/malloc/malloc-debug.c
+++ b/malloc/malloc-debug.c
@@ -52,6 +52,8 @@ enum malloc_debug_hooks
   MALLOC_MCHECK_HOOK = 1 << 0, /* mcheck()  */
   MALLOC_MTRACE_HOOK = 1 << 1, /* mtrace()  */
   MALLOC_CHECK_HOOK = 1 << 2,  /* MALLOC_CHECK_ or glibc.malloc.check.  */
+  MALLOC_SANITIZE_HOOK = 1 << 3, /* MALLOC_SANITIZE_ or
+                                    glibc.malloc.sanitize.  */
 };
 static unsigned __malloc_debugging_hooks;
 
@@ -76,6 +78,7 @@ __malloc_debug_disable (enum malloc_debug_hooks flag)
 #include "mcheck.c"
 #include "mtrace.c"
 #include "malloc-check.c"
+#include "malloc-sanitize.c"
 
 #if SHLIB_COMPAT (libc_malloc_debug, GLIBC_2_0, GLIBC_2_24)
 extern void (*__malloc_initialize_hook) (void);
@@ -118,6 +121,8 @@ generic_hook_ini (void)
        will not try to optimize it away.  */
     __libc_free (__libc_malloc (0));
 
+  initialize_malloc_sanitize ();
+
 #if SHLIB_COMPAT (libc_malloc_debug, GLIBC_2_0, GLIBC_2_24)
   void (*hook) (void) = __malloc_initialize_hook;
   if (hook != NULL)
@@ -158,6 +163,8 @@ __debug_malloc (size_t bytes)
   void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook);
   if (__glibc_unlikely (hook != NULL))
     return (*hook)(bytes, RETURN_ADDRESS (0));
+  if (__is_malloc_debug_enabled (MALLOC_SANITIZE_HOOK))
+    return malloc_sanitize (bytes);
 
   void *victim = NULL;
   size_t orig_bytes = bytes;
@@ -185,6 +192,11 @@ __debug_free (void *mem)
       (*hook)(mem, RETURN_ADDRESS (0));
       return;
     }
+  if (__is_malloc_debug_enabled (MALLOC_SANITIZE_HOOK))
+    {
+      free_sanitize (mem);
+      return;
+    }
 
   if (__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK))
     mem = free_mcheck (mem);
@@ -206,6 +218,9 @@ __debug_realloc (void *oldmem, size_t bytes)
   if (__glibc_unlikely (hook != NULL))
     return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));
 
+  if (__is_malloc_debug_enabled (MALLOC_SANITIZE_HOOK))
+    return realloc_sanitize (oldmem, bytes);
+
   size_t orig_bytes = bytes, oldsize = 0;
   void *victim = NULL;
 
@@ -324,6 +339,9 @@ __debug_calloc (size_t nmemb, size_t size)
 {
   size_t bytes;
 
+  if (__is_malloc_debug_enabled (MALLOC_SANITIZE_HOOK))
+    return calloc_sanitize (nmemb, size);
+
   if (__glibc_unlikely (__builtin_mul_overflow (nmemb, size, &bytes)))
     {
       errno = ENOMEM;
diff --git a/malloc/malloc-sanitize.c b/malloc/malloc-sanitize.c
new file mode 100644
index 0000000000..a26fb1d60f
--- /dev/null
+++ b/malloc/malloc-sanitize.c
@@ -0,0 +1,147 @@
+/* glibc.malloc.sanitize implementation.
+   Copyright (C) 2024-2025 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 <https://www.gnu.org/licenses/>.  */
+
+#include <string.h>
+#include <samasan.h>
+#include <libc-mtag.h> // for realloc_sanitize ()
+#include <malloc-size.h> // for realloc_sanitize ()
+
+static void *
+malloc_sanitize (size_t bytes)
+{
+  void *ptr = NULL;
+
+  if (samasan_sampling_ok (bytes))
+    ptr = samasan_allocate (bytes);
+  // Even if samasan_allocate is failed, fall back to the normal
+  // malloc path to guarantee memory allocation.
+  if (__glibc_unlikely (!ptr))
+    ptr = __libc_malloc (bytes);
+  return ptr;
+}
+
+static void
+free_sanitize (void *mem)
+{
+  if (samasan_is_pointer_in_sampling_pool (mem))
+    samasan_free (mem);
+  else
+    __libc_free (mem);
+}
+
+static void *
+realloc_sanitize (void *oldmem, size_t bytes)
+{
+  void *mem;
+  if (oldmem == 0)
+    return malloc_sanitize (bytes);
+  if (samasan_sampling_ok (bytes))
+    {
+      mem = samasan_allocate (bytes);
+      if (mem != 0)
+  {
+    size_t oldsize;
+    memset (mem, 0, bytes);
+    if (samasan_is_pointer_in_sampling_pool (oldmem))
+      oldsize = samasan_get_size (oldmem);
+    else
+      oldsize = memsize (mem2chunk (oldmem));
+    if (oldsize > bytes)
+      memcpy (mem, oldmem, bytes);
+    else
+      memcpy (mem, oldmem, oldsize);
+    free_sanitize (oldmem);
+    return mem;
+  }
+    }
+  if (!samasan_is_pointer_in_sampling_pool (oldmem))
+    return __libc_realloc (oldmem, bytes);
+
+  /* Oldmem points a sampled memory block. Let's mimic __libc_realloc */
+  mem = __libc_malloc (bytes);
+  if (mem != 0)
+    {
+      size_t oldsize = samasan_get_size (oldmem);
+      if (oldsize > bytes)
+  memcpy (mem, oldmem, bytes);
+      else
+  memcpy (mem, oldmem, oldsize);
+      samasan_free (oldmem);
+    }
+  return mem;
+}
+
+static void *
+calloc_sanitize (size_t num, size_t bytes)
+{
+  void *mem;
+  size_t size;
+
+  if (__glibc_unlikely (__builtin_mul_overflow (num, bytes, &size)))
+    {
+      __set_errno (ENOMEM);
+      return NULL;
+    }
+
+  if (samasan_sampling_ok (size))
+    {
+      mem = samasan_allocate (size);
+      if (mem != 0)
+	{
+	  memset (mem, 0, size);
+	  return mem;
+	}
+    }
+  /* Fall through */
+  return __libc_calloc (num, bytes);
+}
+
+#define SAMPLING_ASAN_FULL_LEN 13
+#define SAMPLING_ASAN_FULL_NAME "sampling-asan"
+#define SAMPLING_ASAN_SHORT_LEN 7
+#define SAMPLING_ASAN_SHORT_NAME "samasan"
+
+static inline bool is_tunable_sampling_asan (const char *str, size_t len)
+{
+  if (len == 0)
+    return false;
+  if (len == SAMPLING_ASAN_FULL_LEN &&
+      !strncmp (str, SAMPLING_ASAN_FULL_NAME, len))
+    return true;
+  if (len == SAMPLING_ASAN_SHORT_LEN &&
+      !strncmp (str, SAMPLING_ASAN_SHORT_NAME, len))
+    return true;
+  return false;
+}
+
+static void
+TUNABLE_CALLBACK (set_malloc_sanitize) (tunable_val_t *valp)
+{
+  const char *value = (char *) valp->strval.str;
+  size_t len = valp->strval.len;
+  if (is_tunable_sampling_asan (value, len))
+    __malloc_debug_enable (MALLOC_SANITIZE_HOOK);
+}
+
+static bool
+initialize_malloc_sanitize (void)
+{
+  samasan_init ();
+  TUNABLE_GET (malloc_sanitize, const struct tunable_str_t *, TUNABLE_CALLBACK (set_malloc_sanitize));
+  return __is_malloc_debug_enabled (MALLOC_SANITIZE_HOOK);
+}
diff --git a/malloc/tst-malloc-sanitize.c b/malloc/tst-malloc-sanitize.c
new file mode 100644
index 0000000000..71f619c7fa
--- /dev/null
+++ b/malloc/tst-malloc-sanitize.c
@@ -0,0 +1,78 @@
+/* Copyright (C) 2024-2025 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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <malloc.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libc-diag.h>
+#include <support/check.h>
+#include <samasan.h>
+
+#include "../sampling-asan/samasan_init.h" /* for samasan_deinit () */
+
+static int
+do_test (void)
+{
+  void *ptr;
+  const size_t alloc_size = 512;
+  char *c;
+  int i, ok;
+
+  errno = 0;
+
+  ptr = malloc (alloc_size);
+  TEST_VERIFY (samasan_is_pointer_in_sampling_pool (ptr));
+  free (ptr);
+
+  /* Since a memory_block size is 4096, an allocation request for
+     512 * 10 bytes should be falled back to __libc_malloc. */
+  ptr = malloc (alloc_size * 10);
+  TEST_VERIFY (!samasan_is_pointer_in_sampling_pool (ptr));
+  free (ptr);
+
+  ptr = realloc (NULL, alloc_size);
+  TEST_VERIFY (samasan_is_pointer_in_sampling_pool (ptr));
+  ptr = realloc (ptr, alloc_size * 2);
+  TEST_VERIFY (samasan_is_pointer_in_sampling_pool (ptr));
+  ptr = realloc (ptr, alloc_size * 10);
+  /* Allocation size crosses the limitation of sampling-asan.
+     It should be allocated by the normal malloc path. */
+  TEST_VERIFY (!samasan_is_pointer_in_sampling_pool (ptr));
+  free (ptr);
+
+  ptr = calloc (1, alloc_size);
+  TEST_VERIFY (samasan_is_pointer_in_sampling_pool (ptr));
+  c = (char *) ptr;
+  ok = 0;
+  for (i = 0; i < alloc_size; i++)
+    if (c[i] == 0)
+      ok++;
+  TEST_VERIFY (ok == alloc_size);
+  free (ptr);
+
+  /* Since a memory_block size is 4096, an allocation request for
+     512 * 10 bytes should be falled back to __libc_calloc. */
+  ptr = calloc (10, alloc_size);
+  TEST_VERIFY (!samasan_is_pointer_in_sampling_pool (ptr));
+
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.25.1



More information about the Libc-alpha mailing list