[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