[PATCH v4 1/9] sampling-asan: A sampling-based address sanitization for on-line memory bug detection
Sung-hun Kim
sfoon.kim@samsung.com
Thu Nov 6 02:26:08 GMT 2025
This patch introduces sampling-asan, a lightweight implementation of address sanitization (ASan)
designed to reduce performance overhead of ASan.
ASan implementation imposes significant performance overhead, discouraging developers from using
ASan to verify their codes. As a result, developers use ASan only after a memory bug is discovered.
Sampling-asan addresses this limitation by leveraging a sampling-based approach to track memory
allocations and deallocations. This design significantly reduces performance overhead, so making it
easier for developers to apply address sanitization for application in use.
Users can configure sampling conditions through environment variables, which provides flexibility
in balancing bug detection accuracy and performance.
Sampling-asan is highly inspired and influenced by GWP-ASan in LLVM [1] and a published paper
by Google [2]. Especially, a sampling-base approach used in sampling-asan is borrowed from the key
concept of GWP-ASan. You can regard, this series of patches is an effort how to apply GWP-ASan
into glibc malloc implementation. Of course, there are some different features and implementation
from GWP-ASan's.
Please refer a simple report of sampling-asan in Glibc Bugzilla:
url: https://sourceware.org/bugzilla/show_bug.cgi?id=33461
In current version, sampling-asan is served as a shared library, named libsamasan.so.
So, if you want to use sampling-asan, you should compile your code with -lsamasan.
[1] https://llvm.org/docs/GwpAsan.html
[2] Kostya Serebryany et al., GWP-ASan: Sampling-Based Detection of Memory-Safety Bugs in Production
https://arxiv.org/abs/2311.09394
Signed-off-by: Sung-hun Kim <sfoon.kim@samsung.com>
---
include/samasan.h | 18 +
sampling-asan/Makefile | 47 ++
sampling-asan/Versions | 17 +
sampling-asan/samasan.h | 44 ++
sampling-asan/samasan_allocate.c | 685 ++++++++++++++++++++++++
sampling-asan/samasan_allocate.h | 71 +++
sampling-asan/samasan_backtrace.c | 58 ++
sampling-asan/samasan_backtrace.h | 30 ++
sampling-asan/samasan_common.c | 25 +
sampling-asan/samasan_common.h | 110 ++++
sampling-asan/samasan_error.c | 117 ++++
sampling-asan/samasan_error.h | 53 ++
sampling-asan/samasan_fault_handler.c | 132 +++++
sampling-asan/samasan_fault_handler.h | 24 +
sampling-asan/samasan_init.c | 370 +++++++++++++
sampling-asan/samasan_init.h | 35 ++
sampling-asan/samasan_report.c | 264 +++++++++
sampling-asan/samasan_report.h | 35 ++
sampling-asan/samasan_sampling.c | 67 +++
sampling-asan/samasan_sampling.h | 28 +
sampling-asan/samasan_variable_init.def | 38 ++
21 files changed, 2268 insertions(+)
create mode 100644 include/samasan.h
create mode 100644 sampling-asan/Makefile
create mode 100644 sampling-asan/Versions
create mode 100644 sampling-asan/samasan.h
create mode 100644 sampling-asan/samasan_allocate.c
create mode 100644 sampling-asan/samasan_allocate.h
create mode 100644 sampling-asan/samasan_backtrace.c
create mode 100644 sampling-asan/samasan_backtrace.h
create mode 100644 sampling-asan/samasan_common.c
create mode 100644 sampling-asan/samasan_common.h
create mode 100644 sampling-asan/samasan_error.c
create mode 100644 sampling-asan/samasan_error.h
create mode 100644 sampling-asan/samasan_fault_handler.c
create mode 100644 sampling-asan/samasan_fault_handler.h
create mode 100644 sampling-asan/samasan_init.c
create mode 100644 sampling-asan/samasan_init.h
create mode 100644 sampling-asan/samasan_report.c
create mode 100644 sampling-asan/samasan_report.h
create mode 100644 sampling-asan/samasan_sampling.c
create mode 100644 sampling-asan/samasan_sampling.h
create mode 100644 sampling-asan/samasan_variable_init.def
diff --git a/include/samasan.h b/include/samasan.h
new file mode 100644
index 0000000000..0f3b16448e
--- /dev/null
+++ b/include/samasan.h
@@ -0,0 +1,18 @@
+/* Sampling-asan for debugging memory allocation.
+ Copyright (C) 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 <sampling-asan/samasan.h>
diff --git a/sampling-asan/Makefile b/sampling-asan/Makefile
new file mode 100644
index 0000000000..6f27bbed0d
--- /dev/null
+++ b/sampling-asan/Makefile
@@ -0,0 +1,47 @@
+# Copyright (C) 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
+# <http://www.gnu.org/licenses/>.
+
+# Makefile for sampling-asan routines
+
+subdir := sampling-asan
+
+include ../Makeconfig
+
+dist-headers := samasan.h
+headers := $(dist-headers)
+
+extra-libs := libsamasan
+extra-libs-others := $(extra-libs)
+
+libsamasan-routines := \
+ samasan_allocate \
+ samasan_backtrace \
+ samasan_common \
+ samasan_error \
+ samasan_fault_handler \
+ samasan_init \
+ samasan_report \
+ samasan_sampling
+# routines
+
+libsamasan-inhibit-o = $(filter-out .os,$(object-suffixes))
+
+install-lib = libsamasan.so
+install-lib-ldscripts = libsamasan.so
+$(inst_libdir)/libsamasan.so:
+
+include ../Rules
diff --git a/sampling-asan/Versions b/sampling-asan/Versions
new file mode 100644
index 0000000000..8f91be534c
--- /dev/null
+++ b/sampling-asan/Versions
@@ -0,0 +1,17 @@
+libsamasan {
+ GLIBC_PRIVATE {
+ #functions
+ samasan_allocate;
+ samasan_free;
+ samasan_init;
+ samasan_deinit;
+ samasan_sampling_ok;
+ samasan_is_pointer_in_sampling_pool;
+ samasan_get_size;
+ samasan_is_enabled;
+
+ #testing
+ samasan_memory_pool_pause;
+ samasan_memory_pool_resume;
+ }
+}
diff --git a/sampling-asan/samasan.h b/sampling-asan/samasan.h
new file mode 100644
index 0000000000..8a8b58f66a
--- /dev/null
+++ b/sampling-asan/samasan.h
@@ -0,0 +1,44 @@
+/* Prototypes for the sampling-asan interfaces.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SAMASAN_H
+#define _SAMASAN_H
+
+#include <stddef.h> /* for size_t */
+#include <stdbool.h> /* for bool type */
+
+/* a call for initializing samasan */
+extern void samasan_init (void);
+/* a call for sanitized malloc/realloc (see samasan_allocate.c) */
+extern void *samasan_allocate (size_t size);
+/* a call for free of sanitized allocation (see samasan_allocate.c) */
+extern void samasan_free (void *ptr);
+/* a call used to decide the allocation should be sampled or not */
+extern bool samasan_sampling_ok (size_t size);
+/* a call returns the size of the given memory chunk */
+extern size_t samasan_get_size (void *ptr);
+/* a call to determine the given pointer is located in the sampling pool */
+extern bool samasan_is_pointer_in_sampling_pool (void *ptr);
+/* a call to query whether the sampling-asan module is enabled or not */
+extern bool samasan_is_enabled (void);
+/* a call to pause the memory pool */
+extern void samasan_memory_pool_pause (void);
+/* a call to resume the memory pool */
+extern void samasan_memory_pool_resume (void);
+
+#endif /* samasan.h */
diff --git a/sampling-asan/samasan_allocate.c b/sampling-asan/samasan_allocate.c
new file mode 100644
index 0000000000..bf2f930f25
--- /dev/null
+++ b/sampling-asan/samasan_allocate.c
@@ -0,0 +1,685 @@
+/* Definitions for the sanitized memory allocation implementation.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+/* Sampling-asan (in short, samasan) tries to find memory bugs, such as
+ use-after-free, unaligned access, double free, unallocated free, and
+ so on with negligible performance degradation. Samasan uses a sampling
+ -based address sanitization to find memory bugs. That's why this mechanism
+ is called sampling-asan. */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <pthread.h>
+#include <atomic.h>
+
+#include "samasan_init.h"
+#include "samasan_common.h"
+#include "samasan_error.h"
+#include "samasan_sampling.h"
+#include "samasan_allocate.h"
+#include "samasan_backtrace.h"
+
+/* Definitions of some terms used in sampling-asan:
+ - memory_pool: A memory_pool is a reserved memory space to serve sanitized
+ memory allocation. Its size can be configured by environmental variables
+ (please refer samasan_init.c). The size of the memory pool is decided by
+ the below fomula:
+ size = SAMASAN_MAX_ALLOC_SIZE * SAMASAN_MAX_ON_GOING_ALLOCATIONS
+ + (SAMASAN_PARTITION_SIZE + 1) * SAMASAN_MAX_ON_GOING_ALLOCATIONS
+ - memory_pool_block: A memory_pool_block is composed of a number of pages.
+ It serves a sanitized memory allocation to users. The size of a
+ memory_pool_block is decided by SAMASAN_MAX_ALLOC_SIZE.
+ - chunk: A served memory area to users is called a chunk. A chunk should
+ be located in a memory_pool_block and must be less than a memory_pool_block.
+ - memory_pool_partition: A memory_pool_partition is a protected zone which
+ is located in the first and last places of the memory pool and between two
+ memory_pool_blocks. Its size is decided by SAMASAN_PARTITION_SIZE. If an
+ user tries to access a memory_pool_partition, the program will get the
+ segmentation fault.
+ - memory_pool_entry: It means that a pair of a memory_pool_partition and
+ a memory_pool_block. It is only used for the address calculation. */
+
+static size_t memory_pool_size;
+static size_t memory_page_size;
+
+static size_t memory_pool_block_size;
+static size_t memory_pool_partition_size;
+static size_t memory_pool_entry_size;
+
+static void *memory_pool;
+static struct memory_pool_entry_info *memory_pool_metadata;
+static int memory_pool_entry_in_use;
+
+#define DEFAULT_GUARD_PATTERN 0xFF
+const static char guard_pattern = DEFAULT_GUARD_PATTERN;
+
+struct samasan_mutex free_list_mutex;
+struct memory_pool_entry_info *free_list_head;
+struct memory_pool_entry_info *free_list_tail;
+
+static uintptr_t memory_pool_begin;
+static uintptr_t memory_pool_end;
+/* memory_pool_begin is aligned in a page-width. Because of that, if
+ memory_pool_block_size is a multiple of a page, calculating the address
+ of a memory chunk should be offsetted by a relative address of
+ memory_pool_begin in a memory_pool_block_size. */
+static uintptr_t memory_pool_offset;
+
+/* TODO: Recursive calling of samasan functions should be prevented.
+ It can be tested when sampling-asan is applied malloc code base.
+ So, the test code should be written after applying sampling-asan.*/
+SAMASAN_TLS_SPECIFIER bool is_in_samasan = false;
+
+#define get_memory_pool_index_by_entry_info(entry) ((uintptr_t) entry - \
+ (uintptr_t) memory_pool_metadata) / sizeof (struct memory_pool_entry_info)
+#define get_partition_bytes_by_entry_info(entry) \
+ ((get_memory_pool_index_by_entry_info (entry) + 1) \
+ * memory_pool_partition_size)
+#define get_block_bytes_by_entry_info(entry) \
+ (get_memory_pool_index_by_entry_info (entry) * memory_pool_block_size)
+#define get_memory_pool_block_address_by_entry_info(entry) \
+ (void *) (memory_pool_begin \
+ + get_partition_bytes_by_entry_info (entry) \
+ + get_block_bytes_by_entry_info (entry))
+
+#define get_blocks(ptr) ((uintptr_t) ptr - memory_pool_begin) \
+ / memory_pool_entry_size
+#define get_partitions(ptr) get_blocks (ptr)
+#define get_block_bytes_by_pointer(ptr) (get_blocks (ptr) \
+ * memory_pool_block_size)
+#define get_partition_bytes_by_pointer(ptr) (get_partitions (ptr) \
+ * memory_pool_partition_size)
+#define get_memory_pool_block_offset(ptr) (get_block_bytes_by_pointer (ptr) \
+ + get_partition_bytes_by_pointer (ptr))
+#define get_memory_pool_index_by_pointer(ptr) \
+ (get_memory_pool_block_offset (ptr) / memory_pool_entry_size)
+
+bool is_pointer_in_partition (void *ptr)
+{
+ return (uintptr_t) ptr - memory_pool_begin
+ - get_memory_pool_block_offset (ptr) < memory_pool_partition_size;
+}
+
+static inline struct memory_pool_entry_info *
+get_relative_memory_pool_entry_from_pointer (void *ptr, ssize_t relative)
+{
+ ssize_t index = (ssize_t) get_memory_pool_index_by_pointer (ptr) + relative;
+
+ if (SAMASAN_UNLIKELY (index < 0))
+ return NULL;
+ if (SAMASAN_UNLIKELY ((size_t) index >= memory_pool_size))
+ return NULL;
+ return &memory_pool_metadata[index];
+}
+
+struct memory_pool_entry_info *
+get_memory_pool_entry_from_pointer (void *ptr)
+{
+ if (is_pointer_in_partition (ptr))
+ /* invalid access */
+ return NULL;
+ return get_relative_memory_pool_entry_from_pointer (ptr, 0);
+}
+
+struct memory_pool_entry_info *
+get_previous_memory_pool_entry_from_pointer (void *ptr)
+{
+ return get_relative_memory_pool_entry_from_pointer (ptr, 0);
+}
+
+struct memory_pool_entry_info *
+get_next_memory_pool_entry_from_pointer (void *ptr)
+{
+ return get_relative_memory_pool_entry_from_pointer (ptr, 1);
+}
+
+/* A bypassed memory_pool entry should be removed from the free list */
+static inline void
+remove_memory_pool_entry_from_free_list
+(struct memory_pool_entry_info *entry, struct memory_pool_entry_info *prev)
+{
+ if (entry == free_list_head && entry == free_list_tail)
+ free_list_head = free_list_tail = NULL;
+ else
+ {
+ prev->list = entry->list;
+ entry->list = NULL;
+ if (entry == free_list_tail)
+ free_list_tail = prev;
+ }
+}
+
+/* A memory_pool_entry is detached from the free list */
+static inline struct memory_pool_entry_info *
+get_memory_pool_entry_from_free_list (void)
+{
+ struct memory_pool_entry_info *entry;
+
+ samasan_assert (free_list_head != NULL, "free list is emptied!\n");
+ if (free_list_head == free_list_tail)
+ free_list_tail = NULL;
+ entry = free_list_head;
+ free_list_head = entry->list;
+ entry->list = NULL;
+ return entry;
+}
+
+static inline void
+put_memory_pool_entry_in_free_list (struct memory_pool_entry_info *entry)
+{
+ if (free_list_tail)
+ {
+ free_list_tail->list = entry;
+ free_list_tail = entry;
+ }
+ else if (free_list_tail == NULL && free_list_head == NULL)
+ free_list_head = free_list_tail = entry;
+}
+
+static struct memory_pool_entry_info *
+get_free_memory_pool_entry (void)
+{
+ struct memory_pool_entry_info *entry;
+
+ if (SAMASAN_UNLIKELY (samasan_atomic_read (&memory_pool_entry_in_use)
+ >= memory_pool_size))
+ return NULL;
+ samasan_mutex_lock (&free_list_mutex);
+ entry = get_memory_pool_entry_from_free_list ();
+ samasan_mutex_unlock (&free_list_mutex);
+ entry->is_free = false;
+ samasan_atomic_inc (&memory_pool_entry_in_use);
+ return entry;
+}
+
+#define get_pointer_offset(ptr) ((uintptr_t) ptr % memory_page_size)
+#define get_page_address(ptr) ((uintptr_t) ptr - get_pointer_offset (ptr))
+#define get_page_pointer(ptr) (void *) get_page_address (ptr)
+#define round_up(size, bound) ((size + bound - 1) & ~(bound - 1))
+
+static inline size_t
+get_page_size (void *ptr, size_t size)
+{
+ size_t offset = (uintptr_t) ptr - get_page_address (ptr);
+ return round_up (offset + size, memory_page_size);
+}
+
+static bool
+protect_range_in_block (void *start, size_t size)
+{
+ return mprotect (get_page_pointer (start), get_page_size (start, size),
+ PROT_NONE) == 0;
+}
+
+static bool
+unprotect_range_in_block (void *start, size_t size)
+{
+ return mprotect (get_page_pointer (start), get_page_size (start, size),
+ PROT_READ | PROT_WRITE) == 0;
+}
+
+bool
+bypass_block (struct memory_pool_entry_info *entry)
+{
+ bool ret = unprotect_range_in_block (
+ get_memory_pool_block_address_by_entry_info (entry),
+ memory_pool_block_size);
+ if (!ret)
+ return false;
+ entry->is_bypassed = true;
+ if (entry->is_free)
+ {
+ struct memory_pool_entry_info *prev;
+ /* remove from free list */
+ samasan_mutex_lock (&free_list_mutex);
+ prev = free_list_head;
+ while (prev->list != entry && prev != free_list_tail)
+ prev = prev->list;
+ remove_memory_pool_entry_from_free_list (entry, prev);
+ samasan_mutex_unlock (&free_list_mutex);
+ /* decrease the number of useful free blocks */
+ samasan_atomic_inc (&memory_pool_entry_in_use);
+ }
+ return true;
+}
+
+/* Styles for picking a memory chunk in a memory block:
+ Mostly, a memory chunk is smaller than a memory block. So, samasan should
+ pick the address in a selected memory block to provide a memory chunk.
+ The picker can take the leftmost address (left-aligned) or the rightmost
+ address (right-aligned) while the memory chunk does not exceed the boundary
+ of the memory block. Of course, it can pick the center address of the memory
+ block (center-aligned). The picking style can be configured by using a
+ environmental variable (SAMASAN_CHUNK_PICK). For details, please refer
+ samasan_init.c. */
+static memory_chunk_pick_style_t chunk_pick_style;
+
+#define pick_from_left(block, size) (void *) ((uintptr_t) block)
+#define pick_from_right(block, size) (void *) ((uintptr_t) block \
+ + (uintptr_t) ((memory_pool_block_size)) - size)
+#define pick_center(block, size) (void *) ((uintptr_t) block \
+ + (uintptr_t) ((memory_pool_block_size - size) >> 1))
+#define pick_chunk(block, size) \
+ (chunk_pick_style == PICK_FROM_LEFT ? pick_from_left (block, size) : \
+ chunk_pick_style == PICK_FROM_RIGHT ? pick_from_right (block, size) : \
+ pick_center (block, size))
+#define get_aligned(ptr) (void *) ((uintptr_t) ptr & ~(sizeof (size_t) - 1))
+
+static inline void *
+allocate_in_memory_pool_block (void *block, size_t size)
+{
+ void *chunk = get_aligned (pick_chunk (block, size));
+ samasan_assert (unprotect_range_in_block (chunk, size),
+ "mprotect on the allocated range is failed");
+ return chunk;
+}
+
+static void *
+allocate_in_memory_pool (size_t size)
+{
+ struct memory_pool_entry_info *entry;
+ void *block;
+ void *chunk = NULL;
+
+ check_in_samasan ();
+ entry = get_free_memory_pool_entry ();
+ if (!entry)
+ goto no_entry_out;
+
+ block = get_memory_pool_block_address_by_entry_info (entry);
+ chunk = allocate_in_memory_pool_block (block, size);
+ entry->chunk_size = size;
+ entry->address = (uintptr_t) chunk;
+ get_backtrace (&entry->allocation_trace);
+
+no_entry_out:
+ check_out_samasan();
+ return chunk;
+}
+
+/* A guard pattern is a predefined character to detect a modification in
+ invalid memory area. If the illegal memory modification occurrs in the
+ unprotected memory block, the kernel cannot raise the fault due to the
+ page-wise resolution of the mprotect() call. So, samasan fills the rest
+ of the memory block by the guard pattern. If the pattern is modified,
+ samasan regards it as an invalid modification and raises an error. */
+
+static void
+fill_guard_pattern (void *start, size_t size)
+{
+ memset(start, guard_pattern, size);
+}
+
+static void
+check_guard_pattern (void *chunk, size_t size)
+{
+ char *ptr = (char *) get_page_pointer (chunk);
+ size_t chunk_page_size = get_page_size (chunk, size);
+
+ for (size_t i = 0; i < chunk_page_size;)
+ {
+ if (&ptr[i] == chunk)
+ {
+ i += size;
+ continue;
+ }
+ if (ptr[i] != guard_pattern)
+ raise_fault_with_address(INVALID_WRITE, (uintptr_t) (ptr + i));
+ i++;
+ }
+}
+
+#define is_address_in_chunk(address, entry) (address >= entry->address && \
+ address < (entry->address + entry->chunk_size))
+
+bool
+is_out_of_chunk_access (uintptr_t address,
+ struct memory_pool_entry_info *entry)
+{
+ if (!is_address_in_chunk (address, entry))
+ return true;
+ return false;
+}
+
+#define is_address_in_memory_pool(address) ((address >= memory_pool_begin) \
+ && (address < memory_pool_end))
+
+bool
+is_pointer_in_memory_pool (void *ptr)
+{
+ return is_address_in_memory_pool ((uintptr_t) ptr);
+}
+
+static void
+deallocate_memory_chunk (void *ptr)
+{
+ struct memory_pool_entry_info *entry =
+ &memory_pool_metadata[get_memory_pool_index_by_pointer (ptr)];
+ uintptr_t address = (uintptr_t) ptr;
+
+ if (entry->is_free)
+ {
+ /* Double free: A case that the program tries to free
+ on the freed object. */
+ raise_fault_with_address (DOUBLE_FREE, (uintptr_t) ptr);
+ }
+ if (entry->address != address)
+ {
+ /* Invalid free: The pointer does not point the start address
+ of the chunk. */
+ raise_fault_with_address (INVALID_FREE, address);
+ }
+ check_guard_pattern(ptr, entry->chunk_size);
+ fill_guard_pattern(ptr, entry->chunk_size);
+ samasan_assert (protect_range_in_block (ptr, entry->chunk_size) == true,
+ "mprotect on deallocated range is failed");
+ entry->is_free = true;
+ entry->chunk_size = 0;
+ entry->address = 0;
+
+ get_backtrace (&entry->deallocation_trace);
+
+ samasan_mutex_lock (&free_list_mutex);
+ put_memory_pool_entry_in_free_list (entry);
+ samasan_mutex_unlock (&free_list_mutex);
+ samasan_atomic_dec (&memory_pool_entry_in_use);
+}
+
+/* Sampling-asan API - samasan_allocate (size_t):
+ Allocate a memory chunk in the preallocated memory pool. If a given
+ allocation size is larger than memory_pool_block_size (or zero), it
+ does not allow to allocate a memory chunk in the memory pool. */
+
+void *
+samasan_allocate (size_t size)
+{
+ if (SAMASAN_UNLIKELY (!is_samasan_enabled ()))
+ return NULL;
+ if (SAMASAN_UNLIKELY (is_call_in_samasan ()))
+ return NULL;
+ if (SAMASAN_UNLIKELY (size > memory_pool_block_size || size == 0))
+ return NULL;
+ if (SAMASAN_UNLIKELY (samasan_atomic_read (&memory_pool_entry_in_use)
+ >= memory_pool_size))
+ return NULL;
+ return allocate_in_memory_pool (size);
+}
+
+/* Sampling-asan API - samasan_free (void *):
+ Return an allocated memory chunk to the memory pool.
+ Before free, samasan checks the given pointer is a memory chunk
+ located in the memory pool. If not, samasan just returns without
+ free. */
+
+void
+samasan_free (void *ptr)
+{
+ uintptr_t address = (uintptr_t) ptr;
+ if (SAMASAN_UNLIKELY (!is_address_in_memory_pool (address)))
+ return;
+ check_in_samasan ();
+ deallocate_memory_chunk (ptr);
+ check_out_samasan();
+}
+
+/* Sampling-asan API - samasan_sampling_ok (size_t)
+ The purpose of sampling-asan is to provide address sanitization
+ without paying the expensive cost. To this end, this function is given
+ to decide whether this memory allocation is sanitized or not. */
+
+bool
+samasan_sampling_ok (size_t size)
+{
+ if (SAMASAN_LIKELY (!is_samasan_enabled ()))
+ return false;
+ if (SAMASAN_UNLIKELY (is_call_in_samasan ()))
+ return false;
+ if (SAMASAN_UNLIKELY (samasan_atomic_read (&memory_pool_entry_in_use)
+ >= memory_pool_size))
+ return false;
+ if (SAMASAN_UNLIKELY (size > memory_pool_block_size || size == 0))
+ return false;
+ if (SAMASAN_LIKELY (!decide_allocation_sampling ()))
+ return false;
+ return true;
+}
+
+/* Sampling-asan API: samasan_get_size (void *)
+ This function returns the size of the given memory chunk. If the given
+ pointer is not an address in the memory pool or an already freed memory
+ chunk, it returns zero. */
+
+size_t
+samasan_get_size (void *ptr)
+{
+ struct memory_pool_entry_info *entry;
+
+ if (SAMASAN_UNLIKELY (!is_pointer_in_memory_pool (ptr)))
+ return 0;
+ entry = get_memory_pool_entry_from_pointer (ptr);
+ if (SAMASAN_UNLIKELY (!entry))
+ return 0;
+ if (entry->is_free)
+ return 0;
+ return entry->chunk_size;
+}
+
+/* Sampling-asan API: samasan_is_pointer_in_sampling_pool (void *)
+ It returns true if the given pointer is an address in the memory pool
+ even though it is invalid. */
+
+bool
+samasan_is_pointer_in_sampling_pool (void *ptr)
+{
+ /* We do not check the pointer is valid to detect double-free cases. */
+ if (SAMASAN_UNLIKELY (is_pointer_in_memory_pool (ptr)))
+ return true;
+ return false;
+}
+
+/* A memory pool is composed of memory blocks and partitions.
+ Sampling-asan uses memory blocks to serve sanitized memory allocation while
+ partitions are used to detect invalid memory accesses.
+
+ The below figure depicts a case that a memory block is composed of two pages.
+
+ |--<partition>--|----------<memory block>-------------|--<partition>--|--
+ |--<partition>--|--<block page-1>--|--<block page-2>--|--<partition>--|--
+
+ If a partition is accessed, the kernel raises a segmentation fault because
+ it is protected by a mprotect call. Block pages are allowed to be accessed
+ but the check-on-free will detect invalid updates on unallocated area in
+ block pages. */
+
+
+static void *
+memory_pool_allocate (size_t size, size_t block_size)
+{
+ void *pool = mmap (NULL, size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ samasan_assert (pool != MAP_FAILED, "memory pool allocation is failed");
+ for (void *block = pool + memory_pool_partition_size; block < pool + size;
+ block += (block_size + memory_pool_partition_size))
+ fill_guard_pattern (block, block_size);
+ samasan_assert (mprotect (pool, size, PROT_NONE) == 0, "mprotect failed");
+ memory_pool_begin = (uintptr_t) pool;
+ memory_pool_end = (uintptr_t) pool + size;
+ memory_pool_offset = (uintptr_t) pool % memory_pool_block_size;
+ return pool;
+}
+
+static void
+memory_pool_deallocate (void *pool)
+{
+ samasan_assert (munmap (pool, memory_pool_block_size * memory_pool_size
+ + memory_pool_partition_size * (memory_pool_size + 1)) == 0,
+ "memory pool deallocation is failed");
+}
+
+static void *
+memory_pool_metadata_allocate (size_t size)
+{
+ void *metadata = mmap (NULL, size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ samasan_assert (metadata != MAP_FAILED,
+ "memory pool metadata allocation is failed");
+ return metadata;
+}
+
+static void
+memory_pool_metadata_deallocate (void *metadata)
+{
+ samasan_assert (munmap (metadata, sizeof (struct memory_pool_entry_info)
+ * memory_pool_size) == 0,
+ "memory pool metadata deallocation is failed");
+}
+
+static void
+memory_pool_metadata_init (void)
+{
+ memset (memory_pool_metadata, 0, sizeof (struct memory_pool_entry_info)
+ * memory_pool_size);
+ for (size_t i = 0; i < memory_pool_size; i++)
+ {
+ memory_pool_metadata[i].is_free = true;
+ if (SAMASAN_UNLIKELY (!free_list_head))
+ free_list_head = &memory_pool_metadata[i];
+ else
+ {
+ memory_pool_metadata[i].list = free_list_head;
+ free_list_head = &memory_pool_metadata[i];
+ }
+ if (SAMASAN_UNLIKELY (!free_list_tail))
+ free_list_tail = &memory_pool_metadata[i];
+ }
+}
+
+static size_t
+get_page_aligned_alloc_size (uint32_t size)
+{
+ if (size % memory_page_size > 0)
+ return size - (size % memory_page_size) + memory_page_size;
+ return size;
+}
+
+/* Functions for pthread_atfork() */
+static bool fork_handler_installed = false;
+
+/* Sampling-asan API: samasan_memory_pool_pause (void)
+ It stops the memory pool to synchronize with the child. */
+
+void
+samasan_memory_pool_pause (void)
+{
+ samasan_mutex_lock (&free_list_mutex);
+}
+
+/* Sampling-asan API: samasan_memory_pool_resume (void)
+ It restarts the memory pool to synchronize with the child. */
+
+void
+samasan_memory_pool_resume (void)
+{
+ samasan_mutex_unlock (&free_list_mutex);
+}
+
+void
+samasan_install_fork_handler (void)
+{
+ if (! samasan_atomic_compare_and_change (&fork_handler_installed, true, false))
+ {
+ pthread_atfork (samasan_memory_pool_pause,
+ samasan_memory_pool_resume,
+ samasan_memory_pool_resume);
+ }
+}
+
+void
+samasan_uninstall_fork_handler (void)
+{
+ // Do nothing, because there is no unregister function
+ // for pthread_atfork like UNREGISTER_ATFORK outside libc.so.
+}
+
+bool
+samasan_memory_pool_init (uint32_t pool_size,
+ uint32_t alloc_size,
+ uint32_t partition_size,
+ memory_chunk_pick_style_t style)
+{
+ size_t memory_pool_size_in_bytes;
+
+ memory_page_size = get_system_page_size ();
+ memory_pool_size = pool_size;
+ if (alloc_size >= memory_page_size)
+ memory_pool_block_size = get_page_aligned_alloc_size (alloc_size);
+ else
+ memory_pool_block_size = memory_page_size;
+ memory_pool_partition_size = partition_size;
+ memory_pool_entry_size = memory_pool_block_size + memory_pool_partition_size;
+ memory_pool_size_in_bytes = memory_pool_block_size
+ * memory_pool_size
+ + (memory_pool_size + 1)
+ * memory_pool_partition_size;
+
+ memory_pool = memory_pool_allocate (memory_pool_size_in_bytes,
+ memory_pool_block_size);
+ if (SAMASAN_UNLIKELY (!memory_pool))
+ return false;
+ memory_pool_metadata = memory_pool_metadata_allocate (
+ sizeof (struct memory_pool_entry_info)
+ * memory_pool_size);
+ if (SAMASAN_UNLIKELY (!memory_pool_metadata))
+ {
+ memory_pool_deallocate (memory_pool);
+ return false;
+ }
+
+ chunk_pick_style = style;
+ memory_pool_metadata_init ();
+ samasan_mutex_init (&free_list_mutex);
+ samasan_atomic_write (&memory_pool_entry_in_use, 0);
+ return true;
+}
+
+static inline void
+memory_pool_free_list_deinit (void)
+{
+ free_list_head = free_list_tail = NULL;
+}
+
+void
+samasan_memory_pool_deinit (void)
+{
+ if (memory_pool)
+ memory_pool_deallocate (memory_pool);
+ memory_pool = NULL;
+ if (memory_pool_metadata)
+ memory_pool_metadata_deallocate (memory_pool_metadata);
+ memory_pool_metadata = NULL;
+
+ memory_pool_free_list_deinit ();
+
+ memory_pool_block_size = 0;
+ memory_pool_partition_size = 0;
+ memory_pool_entry_size = 0;
+ memory_page_size = 0;
+ memory_pool_size = 0;
+ chunk_pick_style = PICK_END;
+}
diff --git a/sampling-asan/samasan_allocate.h b/sampling-asan/samasan_allocate.h
new file mode 100644
index 0000000000..7e4fae68da
--- /dev/null
+++ b/sampling-asan/samasan_allocate.h
@@ -0,0 +1,71 @@
+/* Prototypes and type definitions for sanitized allocation in the
+ sampling-asan implementation.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SAMASAN_ALLOCATE_H
+#define _SAMASAN_ALLOCATE_H
+
+#include <stdbool.h>
+#include <sys/mman.h> /* for mmap/munmap */
+
+/* For allocation/deallocation traces */
+#define INVALID_TID 0UL
+
+#define ALLOCATION_TRACE_SIZE 256
+struct memory_pool_trace {
+ void *stacktrace[ALLOCATION_TRACE_SIZE];
+ size_t trace_size;
+ uint64_t tid;
+};
+
+struct memory_pool_entry_info {
+ struct memory_pool_trace allocation_trace;
+ struct memory_pool_trace deallocation_trace;
+ struct memory_pool_entry_info *list; /* listed in a free list */
+ uintptr_t address;
+ uint32_t chunk_size:30;
+ bool is_bypassed:1;
+ bool is_free:1;
+};
+
+typedef enum memory_chunk_pick_style {
+ PICK_FROM_LEFT = 0,
+ PICK_CENTER,
+ PICK_FROM_RIGHT,
+ PICK_END,
+} memory_chunk_pick_style_t;
+
+extern struct memory_pool_entry_info
+ *get_memory_pool_entry_from_pointer (void *ptr);
+extern struct memory_pool_entry_info
+ *get_previous_memory_pool_entry_from_pointer (void *ptr);
+extern struct memory_pool_entry_info
+ *get_next_memory_pool_entry_from_pointer (void *ptr);
+extern bool bypass_block (struct memory_pool_entry_info *entry);
+extern bool is_pointer_in_partition (void *ptr);
+extern bool is_out_of_chunk_access (uintptr_t address,
+ struct memory_pool_entry_info *entry);
+extern bool is_pointer_in_memory_pool (void *ptr);
+extern void samasan_install_fork_handler (void);
+extern void samasan_uninstall_fork_handler (void);
+extern bool samasan_memory_pool_init (uint32_t pool_size,
+ uint32_t alloc_size, uint32_t partition_size,
+ memory_chunk_pick_style_t style);
+extern void samasan_memory_pool_deinit (void);
+
+#endif /* samasan_allocate.h */
diff --git a/sampling-asan/samasan_backtrace.c b/sampling-asan/samasan_backtrace.c
new file mode 100644
index 0000000000..c671c3cd92
--- /dev/null
+++ b/sampling-asan/samasan_backtrace.c
@@ -0,0 +1,58 @@
+/* Definitions for the backtracing feature used in sampling-asan.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <execinfo.h>
+#include <unistd.h>
+
+#include "samasan_allocate.h"
+
+/* By default, we use glibc's backtrace to get backtrace. */
+void
+get_backtrace (struct memory_pool_trace *trace)
+{
+ trace->trace_size =
+ backtrace ((void **) trace->stacktrace, ALLOCATION_TRACE_SIZE);
+ trace->tid = gettid ();
+}
+
+void
+print_backtrace (struct memory_pool_trace *trace, int stream,
+ ssize_t (*report_printf) (int, const char *, ...))
+{
+ char **symbols;
+
+ if (trace->trace_size == 0)
+ {
+ report_printf(stream, "samasan error - Cannot track the stack trace\n");
+ return;
+ }
+
+ symbols = backtrace_symbols ((void **) trace->stacktrace, trace->trace_size);
+ for (size_t i = 0; i < trace->trace_size; i++)
+ {
+ if (!symbols)
+ report_printf (stream, " #%zu %p\n", i, trace->stacktrace[i]);
+ else
+ report_printf (stream, " #%zu %s\n", i, symbols[i]);
+ }
+ if (symbols)
+ free (symbols);
+}
diff --git a/sampling-asan/samasan_backtrace.h b/sampling-asan/samasan_backtrace.h
new file mode 100644
index 0000000000..ea32ab283c
--- /dev/null
+++ b/sampling-asan/samasan_backtrace.h
@@ -0,0 +1,30 @@
+/* Prototypes for the backtrace feature used in sampling-asan.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SAMASAN_BACKTRACE_H
+#define _SAMASAN_BACKTRACE_H
+
+#include <stdio.h>
+
+#include "samasan_allocate.h"
+
+extern void get_backtrace (struct memory_pool_trace *trace);
+extern void print_backtrace (struct memory_pool_trace *trace, int stream,
+ ssize_t (report_printf) (int, const char *, ...));
+
+#endif /* samasan_backtrace.h */
diff --git a/sampling-asan/samasan_common.c b/sampling-asan/samasan_common.c
new file mode 100644
index 0000000000..45ae532711
--- /dev/null
+++ b/sampling-asan/samasan_common.c
@@ -0,0 +1,25 @@
+/* Definitions for sampling-asan common functions.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#include "samasan_common.h"
+
+bool
+samasan_mutex_trylock (struct samasan_mutex *mutex)
+{
+ return pthread_mutex_trylock (&mutex->lock);
+}
diff --git a/sampling-asan/samasan_common.h b/sampling-asan/samasan_common.h
new file mode 100644
index 0000000000..9b5c040108
--- /dev/null
+++ b/sampling-asan/samasan_common.h
@@ -0,0 +1,110 @@
+/* Prototypes and definitions for sampling-asan common functions.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SAMASAN_COMMON_H
+#define _SAMASAN_COMMON_H
+
+#include <stdio.h> /* for fprintf */
+#include <stddef.h> /* for size_t */
+#include <stdbool.h> /* for bool type */
+#include <unistd.h> /* for __sysconf */
+#include <libc-lock.h> /* for mutex */
+#include <atomic.h> /* for atomic operations */
+#include <pthread.h>
+
+#define SAMASAN_TLS_SPECIFIER __thread \
+ __attribute__ ((tls_model ("initial-exec")))
+
+#define SAMASAN_UNLIKELY(cond) __builtin_expect (!!(cond), 0)
+#define SAMASAN_LIKELY(cond) __builtin_expect (!!(cond), 1)
+
+#define samasan_atomic_read(var) atomic_load_relaxed (var)
+#define samasan_atomic_write(var, val) atomic_store_relaxed (var, val)
+#define samasan_atomic_inc(var) atomic_fetch_add_release (var, 1)
+#define samasan_atomic_dec(var) atomic_fetch_add_release (var, -1)
+#define samasan_atomic_compare_and_change(var, newval, oldval) \
+ ({ \
+ __atomic_check_size_ls((var)); \
+ __typeof (*(var)) __old = (oldval); \
+ !__atomic_compare_exchange_n ((var), (void *)&(__old), (newval), 0, \
+ __ATOMIC_ACQUIRE, __ATOMIC_RELAXED); \
+ })
+
+/* defined in samasan_allocate.c */
+extern SAMASAN_TLS_SPECIFIER bool is_in_samasan;
+
+#define check_in_samasan() is_in_samasan = true
+#define check_out_samasan() is_in_samasan = false
+#define is_call_in_samasan() is_in_samasan == true
+
+static inline size_t
+get_system_page_size (void)
+{
+ return __sysconf(_SC_PAGESIZE);
+}
+
+static inline void
+__samasan_exit (void)
+{
+ __builtin_trap ();
+}
+
+#define samasan_exit_with_message(...) do \
+{ \
+ fprintf (stderr, __VA_ARGS__); \
+ __samasan_exit (); \
+} while (0); \
+
+static inline void
+__samasan_assert
+(bool condition, const char *msg, const char *file, unsigned long line)
+{
+ if (condition)
+ return;
+ samasan_exit_with_message ("%s: %s: %lu: %s %m\n", file, __func__, line, msg);
+ /* never reached */
+}
+
+#define samasan_assert(condition, msg) \
+ __samasan_assert(condition, msg, __FILE__, __LINE__)
+
+struct samasan_mutex {
+ pthread_mutex_t lock;
+};
+
+static inline void
+samasan_mutex_init (struct samasan_mutex *mutex)
+{
+ pthread_mutex_init (&mutex->lock, NULL);
+}
+
+static inline void
+samasan_mutex_lock (struct samasan_mutex *mutex)
+{
+ pthread_mutex_lock (&mutex->lock);
+}
+
+static inline void
+samasan_mutex_unlock (struct samasan_mutex *mutex)
+{
+ pthread_mutex_unlock (&mutex->lock);
+}
+
+extern bool samasan_mutex_trylock (struct samasan_mutex *mutex);
+
+#endif /* samasan_common.h */
diff --git a/sampling-asan/samasan_error.c b/sampling-asan/samasan_error.c
new file mode 100644
index 0000000000..378238ab74
--- /dev/null
+++ b/sampling-asan/samasan_error.c
@@ -0,0 +1,117 @@
+/* Definitions for the error functions in sampling-asan.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <stdint.h>
+#include <sys/mman.h> /* for mmap/munmap */
+
+#include "samasan_common.h"
+#include "samasan_error.h"
+#include "samasan_report.h"
+#include "samasan_allocate.h"
+
+/* tracking error */
+static samasan_error_t current_error = NO_ERROR;
+static uintptr_t fault_address;
+static void *fault_area;
+
+static const char *error_to_string[] = {
+ "INVALID_FREE",
+ "DOUBLE_FREE",
+ "USE_AFTER_FREE",
+ "INVALID_ACCESS",
+ "INVALID_WRITE",
+ "OUT_OF_MEMORY_POOL",
+ "UNKNOWN_ERROR",
+};
+
+const char *
+get_error_name (samasan_error_t error)
+{
+ return error_to_string[error];
+}
+
+samasan_error_t
+get_current_error (void)
+{
+ return current_error;
+}
+
+uintptr_t
+get_fault_address (void)
+{
+ return fault_address;
+}
+
+void
+raise_fault_with_address (samasan_error_t error, uintptr_t address)
+{
+ volatile char *fault;
+
+ current_error = error;
+ fault_address = address;
+
+ fault = (char *) fault_area;
+ *fault = 0; /* raise segmentation fault */
+
+ /* unreachable */
+ __builtin_trap ();
+}
+
+samasan_error_t
+diagnose_error (void *ptr, struct memory_pool_entry_info *entry)
+{
+ if (!is_pointer_in_memory_pool (ptr))
+ return OUT_OF_MEMORY_POOL;
+ if (!entry)
+ {
+ if (is_pointer_in_partition (ptr))
+ return INVALID_ACCESS;
+ return UNKNOWN_ERROR;
+ }
+ if (entry->is_free)
+ {
+ if (entry->deallocation_trace.tid == INVALID_TID)
+ /* The entry has never been allocated. */
+ return INVALID_ACCESS;
+ return USE_AFTER_FREE;
+ }
+ if (is_out_of_chunk_access ((uintptr_t) ptr, entry))
+ return INVALID_ACCESS;
+ return UNKNOWN_ERROR;
+}
+
+bool
+samasan_error_init (void)
+{
+ fault_area = mmap (NULL, get_system_page_size (), PROT_NONE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ samasan_assert (fault_area != MAP_FAILED,
+ "fault area allocation is failed\n");
+ return true;
+}
+
+void
+samasan_error_deinit (void)
+{
+ if (fault_area)
+ {
+ samasan_assert (munmap (fault_area, get_system_page_size ()) == 0,
+ "failed to unmap fault area");
+ fault_area = NULL;
+ }
+}
diff --git a/sampling-asan/samasan_error.h b/sampling-asan/samasan_error.h
new file mode 100644
index 0000000000..3ee0bd96b9
--- /dev/null
+++ b/sampling-asan/samasan_error.h
@@ -0,0 +1,53 @@
+/* Prototypes for the error functions in sampling-asan.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SAMASAN_ERROR_H
+#define _SAMASAN_ERROR_H
+
+#include <stdint.h> /* for uintptr_t type */
+#include <stdbool.h> /* for bool type */
+
+#include "samasan_allocate.h"
+
+typedef enum samasan_error {
+ NO_ERROR = -1, /* no error occurred */
+ INVALID_FREE = 0, /* try to free an address at out of the allocated chunk */
+ DOUBLE_FREE, /* try to free already freed one */
+ USE_AFTER_FREE, /* try to access the freed memory chunk */
+ INVALID_ACCESS, /* try to access the area out of the allocated chunk */
+ INVALID_WRITE, /* write on invalid range, detected by check-on-free */
+ OUT_OF_MEMORY_POOL, /* a given address is out of the memory pool */
+ UNKNOWN_ERROR, /* an unindentified error */
+} samasan_error_t;
+
+extern const char *get_error_name (samasan_error_t error);
+extern samasan_error_t get_current_error (void);
+extern uintptr_t get_fault_address (void);
+extern samasan_error_t diagnose_error (void *ptr,
+ struct memory_pool_entry_info *entry);
+extern void raise_fault_with_address (samasan_error_t error,
+ uintptr_t address);
+extern bool samasan_error_init (void);
+extern void samasan_error_deinit (void);
+
+#define is_bypassable_error(error) ( \
+ error == USE_AFTER_FREE || \
+ error == INVALID_ACCESS || \
+ error == INVALID_WRITE)
+
+#endif /* samasan_error.h */
diff --git a/sampling-asan/samasan_fault_handler.c b/sampling-asan/samasan_fault_handler.c
new file mode 100644
index 0000000000..3767c7015e
--- /dev/null
+++ b/sampling-asan/samasan_fault_handler.c
@@ -0,0 +1,132 @@
+/* Definitions for the sampling-asan fault handler.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <signal.h>
+
+#include "samasan_allocate.h"
+#include "samasan_error.h"
+#include "samasan_common.h"
+#include "samasan_report.h"
+#include "samasan_init.h"
+#include "samasan_backtrace.h"
+
+static struct sigaction default_handler;
+static bool handler_installed;
+
+static void
+segfault_handler (int sig, siginfo_t *info, void *context)
+{
+ struct memory_pool_entry_info *entry;
+ struct memory_pool_entry_info *entry_at_next = NULL;
+ samasan_error_t error;
+ uintptr_t fault_address;
+ struct memory_pool_trace fault_stack_trace;
+ void *fault_ptr;
+
+ check_in_samasan ();
+
+ fault_address = get_fault_address ();
+ fault_ptr = (void *) fault_address;
+ if (fault_address == 0)
+ {
+ fault_ptr = info->si_addr;
+ fault_address = (uintptr_t) fault_ptr;
+ }
+
+ entry = get_memory_pool_entry_from_pointer (fault_ptr);
+ error = get_current_error ();
+ if (error == NO_ERROR)
+ error = diagnose_error (fault_ptr, entry);
+ if (error == INVALID_ACCESS)
+ {
+ entry = get_previous_memory_pool_entry_from_pointer (fault_ptr);
+ entry_at_next = get_next_memory_pool_entry_from_pointer (fault_ptr);
+ }
+
+ /* get a stack trace of the faulted instruction */
+ get_backtrace (&fault_stack_trace);
+ samasan_report_write (error, fault_address, entry, entry_at_next,
+ &fault_stack_trace);
+
+ /* Crash forwarding */
+ if (default_handler.sa_handler == SIG_DFL)
+ {
+ /* If the error is not reported by sampling-asan, forward the crash
+ to the default handler. */
+ if (samasan_continue_on_crash == false ||
+ !entry ||
+ !is_bypassable_error (error) ||
+ !bypass_block (entry))
+ {
+ __sigaction (SIGSEGV, &default_handler, NULL);
+ raise (SIGSEGV);
+ }
+ }
+ else if (default_handler.sa_handler == SIG_IGN)
+ {
+ /* This error is not reported by sampling-asan */
+ __sigaction (SIGSEGV, &default_handler, NULL);
+ raise (SIGSEGV);
+ }
+ else
+ {
+ if (default_handler.sa_flags & SA_SIGINFO)
+ default_handler.sa_sigaction (sig, info, context);
+ else
+ default_handler.sa_handler (sig);
+ }
+
+ check_out_samasan ();
+}
+
+static void
+install_signal_handler (void)
+{
+ struct sigaction action;
+
+ if (SAMASAN_UNLIKELY (handler_installed))
+ return;
+
+ sigemptyset (&action.sa_mask);
+ action.sa_sigaction = segfault_handler;
+ action.sa_flags = SA_SIGINFO;
+ __sigaction (SIGSEGV, &action, &default_handler);
+ handler_installed = true;
+}
+
+static void
+uninstall_signal_handler (void)
+{
+ if (SAMASAN_UNLIKELY (!handler_installed))
+ return;
+ __sigaction (SIGSEGV, &default_handler, NULL);
+ handler_installed = false;
+}
+
+bool
+samasan_fault_handler_init (void)
+{
+ install_signal_handler ();
+ return true;
+}
+
+void
+samasan_fault_handler_deinit (void)
+{
+ uninstall_signal_handler ();
+}
diff --git a/sampling-asan/samasan_fault_handler.h b/sampling-asan/samasan_fault_handler.h
new file mode 100644
index 0000000000..48deb50d93
--- /dev/null
+++ b/sampling-asan/samasan_fault_handler.h
@@ -0,0 +1,24 @@
+/* Prototypes and definition for a sampling-asan fault handler.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SAMASAN_FAULT_HANDLER_H
+#define _SAMASAN_FAULT_HANDLER_H
+
+extern bool samasan_fault_handler_init (void);
+
+#endif /* samasan_fault_handler.h */
diff --git a/sampling-asan/samasan_init.c b/sampling-asan/samasan_init.c
new file mode 100644
index 0000000000..086618569e
--- /dev/null
+++ b/sampling-asan/samasan_init.c
@@ -0,0 +1,370 @@
+/* Definitions for sampling-asan initialization.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "samasan_common.h"
+#include "samasan_allocate.h"
+#include "samasan_sampling.h"
+#include "samasan_report.h"
+#include "samasan_error.h"
+#include "samasan_fault_handler.h"
+
+typedef enum samasan_option {
+ SAMASAN_ENABLE,
+ SAMASAN_MAX_ALLOC_SIZE,
+ SAMASAN_MAX_ON_GOING_ALLOCATIONS,
+ SAMASAN_SAMPLING_RATE,
+ SAMASAN_OUTPUT_PATH,
+ SAMASAN_PARTITION_SIZE,
+ SAMASAN_PAUSE_ON_FORK,
+ SAMASAN_CHUNK_PICK,
+ SAMASAN_CONTINUE_ON_CRASH,
+ SAMASAN_OPTIONS,
+} samasan_option_t;
+
+static const char *options_string[SAMASAN_OPTIONS + 1] = {
+ "SAMASAN_ENABLE",
+ "SAMASAN_MAX_ALLOC_SIZE",
+ "SAMASAN_MAX_ON_GOING_ALLOCATIONS",
+ "SAMASAN_SAMPLING_RATE",
+ "SAMASAN_OUTPUT_PATH",
+ "SAMASAN_PARTITION_SIZE",
+ "SAMASAN_PAUSE_ON_FORK",
+ "SAMASAN_CHUNK_PICK",
+ "SAMASAN_CONTINUE_ON_CRASH",
+ "SAMASAN_OPTIONS",
+};
+
+union samasan_variable {
+ uint32_t i_value;
+ bool b_value;
+ const char *c_value;
+};
+
+struct samasan_configurable {
+ union samasan_variable value;
+ /* if the value should be located in [min, max), the configurable
+ can define a range. */
+ struct {
+ uint32_t max; /* a value should be smaller than max */
+ uint32_t min; /* a value should be larger (or equal) than (to) min*/
+ } range;
+};
+
+static struct samasan_configurable *samasan_configurables;
+bool samasan_enabled; /* sampling-asan is on-going or not */
+bool samasan_continue_on_crash; /* the program keeps running even after
+ a crash reported by sampling-asan */
+
+bool
+samasan_is_enabled (void)
+{
+ return samasan_enabled;
+}
+
+static inline void
+samasan_set_variable_uint (samasan_option_t option, uint32_t value)
+{
+ samasan_configurables[option].value.i_value = value;
+}
+
+static inline void
+samasan_set_variable_bool (samasan_option_t option, bool value)
+{
+ samasan_configurables[option].value.b_value = value;
+}
+
+static inline void
+samasan_set_variable_char (samasan_option_t option, const char *value)
+{
+ samasan_configurables[option].value.c_value = value;
+}
+
+static inline void
+samasan_set_variable_range (samasan_option_t option, uint32_t max,
+ uint32_t min)
+{
+ samasan_configurables[option].range.max = max;
+ samasan_configurables[option].range.min = min;
+}
+
+#define samasan_set_variable(index, value) \
+ _Generic ((value), \
+ uint32_t: samasan_set_variable_uint, \
+ const char *: samasan_set_variable_char, \
+ char *: samasan_set_variable_char, \
+ bool: samasan_set_variable_bool) (index, value)
+
+#define samasan_get_variable(index) samasan_configurables[index].value
+
+#define samasan_check_variable_range(index, value) \
+ (value >= samasan_configurables[index].range.min && \
+ value <= samasan_configurables[index].range.max)
+
+#define samasan_get_enabled() samasan_get_variable (SAMASAN_ENABLE).b_value
+#define samasan_get_output_path() \
+ samasan_get_variable (SAMASAN_OUTPUT_PATH).c_value
+#define samasan_get_sampling_rate() \
+ samasan_get_variable (SAMASAN_SAMPLING_RATE).i_value
+#define samasan_get_max_on_going_allocations() \
+ samasan_get_variable (SAMASAN_MAX_ON_GOING_ALLOCATIONS).i_value
+#define samasan_get_max_alloc_size() \
+ samasan_get_variable (SAMASAN_MAX_ALLOC_SIZE).i_value
+#define samasan_get_partition_size() \
+ samasan_get_variable (SAMASAN_PARTITION_SIZE).i_value
+#define samasan_get_pause_on_fork() \
+ samasan_get_variable (SAMASAN_PAUSE_ON_FORK).b_value
+#define samasan_get_chunk_pick_style() \
+ samasan_get_variable (SAMASAN_CHUNK_PICK).i_value
+#define samasan_get_continue_on_crash() \
+ samasan_get_variable (SAMASAN_CONTINUE_ON_CRASH).b_value
+
+static bool
+import_samasan_variable (int type, const char *val)
+{
+ switch (type)
+ {
+ case SAMASAN_ENABLE:
+ {
+ bool var = false;
+ if (!strcmp (val, "on") || !strcmp (val, "enable")
+ || !strcmp (val, "yes") || !strcmp (val, "true"))
+ var = true;
+ samasan_set_variable (SAMASAN_ENABLE, var);
+ break;
+ }
+ case SAMASAN_MAX_ALLOC_SIZE:
+ {
+ uint32_t converted = (uint32_t) atoi (val);
+ if (!converted)
+ return false;
+ if (!samasan_check_variable_range (SAMASAN_MAX_ALLOC_SIZE, converted))
+ return false;
+ samasan_set_variable (SAMASAN_MAX_ALLOC_SIZE, converted);
+ break;
+ }
+ case SAMASAN_MAX_ON_GOING_ALLOCATIONS:
+ {
+ uint32_t converted = (uint32_t) atoi (val);
+ if (!converted)
+ return false;
+ if (!samasan_check_variable_range (SAMASAN_MAX_ON_GOING_ALLOCATIONS,
+ converted))
+ return false;
+ samasan_set_variable (SAMASAN_MAX_ON_GOING_ALLOCATIONS, converted);
+ break;
+ }
+ case SAMASAN_SAMPLING_RATE:
+ {
+ uint32_t converted = (uint32_t) (atof (val) * 100000.0);
+ if (!samasan_check_variable_range (SAMASAN_SAMPLING_RATE, converted))
+ return false;
+ samasan_set_variable (SAMASAN_SAMPLING_RATE, converted);
+ break;
+ }
+ case SAMASAN_OUTPUT_PATH:
+ {
+ if (!val)
+ return false;
+ samasan_set_variable (SAMASAN_OUTPUT_PATH, val);
+ break;
+ }
+ case SAMASAN_PARTITION_SIZE:
+ {
+ uint32_t converted = (uint32_t) atoi (val);
+ if (!samasan_check_variable_range (SAMASAN_PARTITION_SIZE,
+ converted))
+ return false;
+ samasan_set_variable (SAMASAN_PARTITION_SIZE, converted);
+ break;
+ }
+ case SAMASAN_PAUSE_ON_FORK:
+ {
+ bool var = false;
+ if (!strcmp (val, "on") || !strcmp (val, "enable")
+ || !strcmp (val, "yes") || !strcmp (val, "true"))
+ var = true;
+ samasan_set_variable (SAMASAN_PAUSE_ON_FORK, var);
+ break;
+ }
+ case SAMASAN_CHUNK_PICK:
+ {
+ uint32_t pick;
+ if (!strcmp (val, "left") || !strcmp (val, "LEFT"))
+ pick = (uint32_t) PICK_FROM_LEFT;
+ else if (!strcmp (val, "right") || !strcmp (val, "RIGHT"))
+ pick = (uint32_t) PICK_FROM_RIGHT;
+ else if (!strcmp (val, "center") || !strcmp (val, "CENTER"))
+ pick = (uint32_t) PICK_CENTER;
+ else
+ return false;
+ samasan_set_variable (SAMASAN_CHUNK_PICK, pick);
+ break;
+ }
+ case SAMASAN_CONTINUE_ON_CRASH:
+ {
+ bool var = false;
+ if (!strcmp (val, "on") || !strcmp (val, "enable")
+ || !strcmp (val, "yes") || !strcmp (val, "true"))
+ var = true;
+ samasan_set_variable (SAMASAN_CONTINUE_ON_CRASH, var);
+ break;
+ }
+ default:
+ break;
+ }
+ return true;
+}
+
+static inline bool import_samasan_variable_range
+(samasan_option_t option, const char *max, const char *min)
+{
+ uint32_t minval, maxval;
+
+ minval = (uint32_t) atoi (min);
+ maxval = (uint32_t) atoi (max);
+ if (maxval < minval)
+ return false;
+
+ samasan_set_variable_range (option, maxval, minval);
+ return true;
+}
+
+#include "samasan_variable_init.def"
+
+static void
+samasan_variable_init (void)
+{
+ if (SAMASAN_UNLIKELY (!samasan_configurables))
+ return;
+
+ /* variable range initialization */
+ import_samasan_variable_range (SAMASAN_SAMPLING_RATE,
+ MAX_SAMASAN_SAMPLING_RATE,
+ MIN_SAMASAN_SAMPLING_RATE);
+ import_samasan_variable_range (SAMASAN_MAX_ALLOC_SIZE,
+ MAX_SAMASAN_MAX_ALLOC_SIZE,
+ MIN_SAMASAN_MAX_ALLOC_SIZE);
+ import_samasan_variable_range (SAMASAN_MAX_ON_GOING_ALLOCATIONS,
+ MAX_SAMASAN_MAX_ON_GOING_ALLOCATIONS,
+ MIN_SAMASAN_MAX_ON_GOING_ALLOCATIONS);
+ import_samasan_variable_range (SAMASAN_PARTITION_SIZE,
+ MAX_SAMASAN_PARTITION_SIZE,
+ MIN_SAMASAN_PARTITION_SIZE);
+
+ /* configurable variables initialization */
+ import_samasan_variable (SAMASAN_ENABLE,
+ DEFAULT_SAMASAN_ENABLED);
+ import_samasan_variable (SAMASAN_SAMPLING_RATE,
+ DEFAULT_SAMASAN_SAMPLING_RATE);
+ import_samasan_variable (SAMASAN_MAX_ALLOC_SIZE,
+ DEFAULT_SAMASAN_MAX_ALLOC_SIZE);
+ import_samasan_variable (SAMASAN_MAX_ON_GOING_ALLOCATIONS,
+ DEFAULT_SAMASAN_MAX_ON_GOING_ALLOCATIONS);
+ import_samasan_variable (SAMASAN_OUTPUT_PATH,
+ DEFAULT_SAMASAN_OUTPUT_PATH);
+ import_samasan_variable (SAMASAN_PARTITION_SIZE,
+ DEFAULT_SAMASAN_PARTITION_SIZE);
+ import_samasan_variable (SAMASAN_PAUSE_ON_FORK,
+ DEFAULT_SAMASAN_PAUSE_ON_FORK);
+ import_samasan_variable (SAMASAN_CHUNK_PICK,
+ DEFAULT_SAMASAN_CHUNK_PICK);
+ import_samasan_variable (SAMASAN_CONTINUE_ON_CRASH,
+ DEFAULT_SAMASAN_CONTINUE_ON_CRASH);
+}
+
+void
+samasan_disable (void)
+{
+ if (samasan_enabled)
+ samasan_enabled = false;
+}
+
+void
+samasan_init (void)
+{
+ struct samasan_configurable configurables[SAMASAN_OPTIONS];
+
+ if (SAMASAN_UNLIKELY (samasan_enabled))
+ return;
+
+ memset (configurables, 0, sizeof (configurables));
+ samasan_configurables = configurables;
+
+ samasan_variable_init ();
+
+ for (int i = 0; i < SAMASAN_OPTIONS; i++)
+ {
+ const char *val = getenv (options_string[i]);
+ if (val)
+ {
+ if (!import_samasan_variable (i, val))
+ /* some variable includes a value out-of-range */
+ goto err_out1;
+ }
+ }
+ if (!samasan_memory_pool_init (samasan_get_max_on_going_allocations (),
+ samasan_get_max_alloc_size (),
+ samasan_get_partition_size (),
+ samasan_get_chunk_pick_style ()))
+ goto err_out1;
+ if (!samasan_report_init (samasan_get_output_path ()))
+ goto err_out2;
+ if (!samasan_sampling_init (samasan_get_sampling_rate ()))
+ goto err_out3;
+ if (!samasan_error_init ())
+ goto err_out3;
+ if (!samasan_fault_handler_init ())
+ goto err_out4;
+ if (samasan_get_pause_on_fork ())
+ samasan_install_fork_handler ();
+ if (samasan_get_continue_on_crash ())
+ samasan_continue_on_crash = true;
+ if (samasan_get_enabled ())
+ samasan_enabled = true;
+ samasan_configurables = NULL;
+ return;
+
+err_out4:
+ samasan_error_deinit ();
+err_out3:
+ samasan_report_deinit ();
+err_out2:
+ samasan_memory_pool_deinit ();
+err_out1:
+ samasan_configurables = NULL;
+}
+
+void
+samasan_deinit (void)
+{
+ if (SAMASAN_LIKELY(samasan_enabled))
+ {
+ samasan_error_deinit ();
+ samasan_memory_pool_deinit ();
+ samasan_report_deinit ();
+ samasan_uninstall_fork_handler();
+ }
+ samasan_enabled = false;
+}
diff --git a/sampling-asan/samasan_init.h b/sampling-asan/samasan_init.h
new file mode 100644
index 0000000000..2bb23dc48e
--- /dev/null
+++ b/sampling-asan/samasan_init.h
@@ -0,0 +1,35 @@
+/* Prototypes and definition for sampling-asan initialization.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SAMASAN_INIT_H
+#define _SAMASAN_INIT_H
+
+#include <stdbool.h>
+
+extern bool samasan_enabled;
+extern bool samasan_continue_on_crash;
+
+static __inline __attribute__ ((always_inline)) bool
+is_samasan_enabled (void)
+{
+ return samasan_enabled;
+}
+
+extern void samasan_deinit (void);
+
+#endif /* samasan_init.h */
diff --git a/sampling-asan/samasan_report.c b/sampling-asan/samasan_report.c
new file mode 100644
index 0000000000..b26f3fce0c
--- /dev/null
+++ b/sampling-asan/samasan_report.c
@@ -0,0 +1,264 @@
+/* Definitions for sampling-asan reporting.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <string.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <libioP.h>
+
+#include "samasan_common.h"
+#include "samasan_error.h"
+#include "samasan_allocate.h"
+#include "samasan_backtrace.h"
+
+static const char *report_path;
+static const char *report_head = "<Sampling-ASAN report begin>";
+static const char *report_foot = "<Sampling-ASAN report end>";
+
+#define NAMELEN 255
+
+static void
+get_command_line (char *str)
+{
+ const char *path = "/proc/self/cmdline";
+ int comm_fd = __open (path, O_RDONLY);
+ size_t pos = 0;
+
+ if (comm_fd < 0)
+ goto comm_error;
+
+ while (1)
+ {
+ size_t ret = __read (comm_fd, &str[pos], NAMELEN - pos);
+ if (ret == 0)
+ break;
+ if (ret < 0)
+ {
+ __close (comm_fd);
+ memset (str, 0, pos);
+ goto comm_error;
+ }
+ pos += ret;
+ if (pos == NAMELEN)
+ {
+ pos--;
+ break;
+ }
+ }
+ __close (comm_fd);
+ str[pos] = '\0';
+
+ return;
+
+comm_error:
+ strcpy (str, "unknown");
+ return;
+}
+
+static char *
+strip_command_line (char *str)
+{
+ char *pos;
+
+ if (!str)
+ return NULL;
+ pos = strrchr (str, '/');
+ if (pos)
+ return pos + 1;
+ return str;
+}
+
+static struct samasan_mutex report_lock;
+
+static ssize_t __report_printf (int fd, const char *format, ...)
+{
+#define BUF_LEN 255
+ char buffer[BUF_LEN];
+ memset (buffer, 0, BUF_LEN);
+ va_list ap;
+
+ samasan_mutex_lock (&report_lock);
+ va_start (ap, format);
+ vsnprintf (buffer, BUF_LEN, format, ap);
+ va_end (ap);
+ samasan_mutex_unlock (&report_lock);
+
+ return write (fd, buffer, sizeof (buffer));
+#undef BUF_LEN
+}
+
+static ssize_t (*report_printf) (int, const char *, ...) =
+ __report_printf;
+
+void
+samasan_report_write (samasan_error_t error, uintptr_t address,
+ struct memory_pool_entry_info *entry,
+ struct memory_pool_entry_info *entry_at_next,
+ struct memory_pool_trace *fault_stack_trace)
+{
+ int stream = STDERR_FILENO;
+ char program_invocation_name[NAMELEN], *process_name;
+ const char *error_name = get_error_name (error);
+
+ if (strcmp (report_path, "stderr"))
+ stream = __open (report_path, O_CREAT | O_WRONLY, 0644);
+
+ if (SAMASAN_UNLIKELY (!stream))
+ return;
+ memset (program_invocation_name, 0, sizeof (program_invocation_name));
+ get_command_line (program_invocation_name);
+ process_name = strip_command_line (program_invocation_name);
+ report_printf (stream, "%s\n", report_head);
+ report_printf (stream, "A crash is occurred in %s\n", process_name);
+ report_printf (stream,
+ "A %s fault raised on 0x%" PRIxPTR "\n", error_name, address);
+ report_printf (stream, "<Stack trace of the faulted instruction>\n");
+ print_backtrace (fault_stack_trace, stream, report_printf);
+
+ if (entry == NULL)
+ {
+ report_printf (stream,
+ "=============================================\n");
+ report_printf (stream, "Cannot find allocation metadata!\n");
+ report_printf (stream, "If an OUT_OF_MEMORY_POOL fault is raised, ");
+ report_printf (stream,
+ "it can be a problem caused by other memory bugs.\n");
+ report_printf (stream, "You can recheck the problem with ");
+ report_printf (stream, "the increased sampling rate by setting ");
+ report_printf (stream, "\"SAMASAN_SAMPLING_RATE=\".\n");
+ report_printf (stream,
+ "============================================\n");
+ goto report_end;
+ }
+
+ if (error != INVALID_ACCESS && !entry->is_free)
+ {
+ report_printf (stream,
+ "=============================================\n");
+ report_printf (stream,
+ "An allocated chunk is located at 0x%" PRIxPTR " ",
+ entry->address);
+ report_printf (stream, "and occupies %" PRIu32 " bytes.\n",
+ entry->chunk_size);
+
+ if (error == INVALID_FREE)
+ {
+ const bool address_after = address > entry->address;
+ size_t diff = address_after ? address - entry->address
+ : entry->address - address;
+ const char *direction = address_after ? "after" : "before";
+
+ report_printf (stream, "You tried to free on 0x%" PRIxPTR " ", address);
+ report_printf (stream, "and it was %zu bytes %s the allocated chunk.\n",
+ diff, direction);
+ }
+ report_printf (stream,
+ "=============================================\n");
+ }
+
+ if (error == INVALID_ACCESS)
+ {
+ if (!entry->is_free)
+ {
+ report_printf (stream,
+ "=============================================\n");
+ report_printf (stream,
+ "The previous allocated chunk is located at 0x%" PRIxPTR " ",
+ entry->address);
+ report_printf (stream, "and occupies %" PRIu32 " bytes.\n",
+ entry->chunk_size);
+ report_printf (stream,
+ "=============================================\n");
+ }
+ report_printf (stream,
+ "<Allocation trace of the previous chunk (tid: %" PRIu64 ")\n",
+ entry->allocation_trace.tid);
+ print_backtrace (&entry->allocation_trace, stream, report_printf);
+ if (entry->is_free)
+ {
+ report_printf (stream,
+ "<Deallocation trace of the previous chunk (tid: %" PRIu64 ")>\n",
+ entry->deallocation_trace.tid);
+ print_backtrace (&entry->deallocation_trace, stream, report_printf);
+ }
+ if (entry_at_next == NULL)
+ goto report_end;
+
+ if (!entry_at_next->is_free)
+ {
+ report_printf (stream, "=============================================\n");
+ report_printf (stream,
+ "The next allocated chunk is located at 0x%" PRIxPTR " ",
+ entry_at_next->address);
+ report_printf (stream, "and occupies %" PRIu32 " bytes.\n",
+ entry_at_next->chunk_size);
+ report_printf (stream, "=============================================\n");
+ }
+ report_printf (stream,
+ "<Allocation trace of the next chunk (tid: %" PRIu64 ")\n",
+ entry_at_next->allocation_trace.tid);
+ print_backtrace (&entry_at_next->allocation_trace, stream,
+ report_printf);
+ if (entry_at_next->is_free)
+ {
+ report_printf (stream,
+ "<Deallocation trace of the next chunk (tid: %" PRIu64 ")>\n",
+ entry_at_next->deallocation_trace.tid);
+ print_backtrace (&entry_at_next->deallocation_trace,
+ stream, report_printf);
+ }
+ }
+ else
+ {
+ report_printf (stream, "<Allocation trace (tid: %" PRIu64 ")>\n",
+ entry->allocation_trace.tid);
+ print_backtrace (&entry->allocation_trace, stream, report_printf);
+ if (entry->is_free)
+ {
+ report_printf (stream, "<Deallocation trace (tid: %" PRIu64 ")>\n",
+ entry->deallocation_trace.tid);
+ print_backtrace (&entry->deallocation_trace, stream, report_printf);
+ }
+ }
+
+report_end:
+ report_printf (stream, "%s\n", report_foot);
+
+ if (stream != STDERR_FILENO)
+ __close (stream);
+}
+
+bool
+samasan_report_init (const char *output_path)
+{
+ samasan_mutex_init (&report_lock);
+ if (!strcmp (output_path, "stderr"))
+ report_path = "stderr";
+ else
+ report_path = output_path;
+ return true;
+}
+
+void
+samasan_report_deinit (void)
+{
+ report_path = NULL;
+}
diff --git a/sampling-asan/samasan_report.h b/sampling-asan/samasan_report.h
new file mode 100644
index 0000000000..df82ce42b3
--- /dev/null
+++ b/sampling-asan/samasan_report.h
@@ -0,0 +1,35 @@
+/* Prototypes for sampling-asan reporting.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SAMASAN_REPORT_H
+#define _SAMASAN_REPORT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "samasan_error.h"
+#include "samasan_allocate.h"
+
+extern void samasan_report_write (samasan_error_t error, uintptr_t address,
+ struct memory_pool_entry_info *entry,
+ struct memory_pool_entry_info *entry_at_next,
+ struct memory_pool_trace *fault_stack_trace);
+extern bool samasan_report_init (const char *output_path);
+extern void samasan_report_deinit (void);
+
+#endif /* samasan_report.h */
diff --git a/sampling-asan/samasan_sampling.c b/sampling-asan/samasan_sampling.c
new file mode 100644
index 0000000000..54be99dd2f
--- /dev/null
+++ b/sampling-asan/samasan_sampling.c
@@ -0,0 +1,67 @@
+/* Definitions for the sampling methods in sampling-asan.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "samasan_common.h"
+
+#define DEFAULT_SAMPLING_INITIAL_VALUE 0x19861216
+#define DEFAULT_SAMPLING_MASK 100 * 1000
+static uint32_t sampling_rate;
+static uint32_t sampling_mask = DEFAULT_SAMPLING_MASK;
+
+static uint32_t *
+get_sampling_variable (void)
+{
+ static SAMASAN_TLS_SPECIFIER uint32_t sampling_variable =
+ DEFAULT_SAMPLING_INITIAL_VALUE;
+ return &sampling_variable;
+}
+
+static inline uint32_t
+get_random_value (uint32_t value)
+{
+ value ^= value << 7;
+ value ^= value << 3;
+ value ^= value << 17;
+ value ^= value << 8;
+ return value;
+}
+
+bool
+decide_allocation_sampling (void)
+{
+ bool ret = false;
+ uint32_t value = get_random_value (*get_sampling_variable ());
+ uint32_t sample = value % sampling_mask;
+
+ if (SAMASAN_UNLIKELY (sample <= sampling_rate))
+ ret = true;
+ *get_sampling_variable () = value;
+ return ret;
+}
+
+bool
+samasan_sampling_init (uint32_t given_sampling_rate)
+{
+ if (given_sampling_rate > DEFAULT_SAMPLING_MASK)
+ return false;
+ sampling_rate = given_sampling_rate;
+ return true;
+}
diff --git a/sampling-asan/samasan_sampling.h b/sampling-asan/samasan_sampling.h
new file mode 100644
index 0000000000..1bd867fc78
--- /dev/null
+++ b/sampling-asan/samasan_sampling.h
@@ -0,0 +1,28 @@
+/* Prototypes for the sampling-asan sampling methods.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SAMASAN_SAMPLING_H
+#define _SAMASAN_SAMPLING_H
+
+#include <stdint.h>
+#include <stdbool.h> /* for bool type */
+
+extern bool decide_allocation_sampling (void);
+extern bool samasan_sampling_init (uint32_t given_sampling_rate);
+
+#endif /* samasan_sampling.h */
diff --git a/sampling-asan/samasan_variable_init.def b/sampling-asan/samasan_variable_init.def
new file mode 100644
index 0000000000..6f43e7d1fd
--- /dev/null
+++ b/sampling-asan/samasan_variable_init.def
@@ -0,0 +1,38 @@
+/* Default values of configurables in sampling-asan.
+ Copyright (C) 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
+ <http://www.gnu.org/licenses/>. */
+
+#define DEFAULT_SAMASAN_ENABLED "false"
+#define DEFAULT_SAMASAN_SAMPLING_RATE "0.005" /* 0.005 */
+#define DEFAULT_SAMASAN_MAX_ALLOC_SIZE "4096"
+#define DEFAULT_SAMASAN_MAX_ON_GOING_ALLOCATIONS "100"
+#define DEFAULT_SAMASAN_OUTPUT_PATH "stderr"
+#define DEFAULT_SAMASAN_PARTITION_SIZE "4096" /* 1 page */
+#define DEFAULT_SAMASAN_PAUSE_ON_FORK "true"
+#define DEFAULT_SAMASAN_CHUNK_PICK "CENTER"
+#define DEFAULT_SAMASAN_CONTINUE_ON_CRASH "false"
+
+/* MAX and MIN define a range of the given configuration. */
+#define MAX_SAMASAN_SAMPLING_RATE "100000"
+#define MAX_SAMASAN_MAX_ALLOC_SIZE "40960"
+#define MAX_SAMASAN_MAX_ON_GOING_ALLOCATIONS "10000"
+#define MAX_SAMASAN_PARTITION_SIZE "40960" /* 10 pages */
+
+#define MIN_SAMASAN_SAMPLING_RATE "0"
+#define MIN_SAMASAN_MAX_ALLOC_SIZE "4096"
+#define MIN_SAMASAN_MAX_ON_GOING_ALLOCATIONS "1"
+#define MIN_SAMASAN_PARTITION_SIZE "0" /* no partition page */
--
2.25.1
More information about the Libc-alpha
mailing list