[PATCH v2 6/9] sampling-asan: Add a continue-on-crash option
Sung-hun Kim
sfoon.kim@samsung.com
Thu Sep 25 08:56:04 GMT 2025
To enhance usability in production environments, I have introduced a continue-on-crash
option for sampling-asan. This feature allows the application to restore the running
context after encountering bypassable memory bugs, such as use-after-free and invalid-
write bugs.
By enabling this option, developers can ensure that applications keep their running
contexts even they meet memory bugs while developers collect bug reports.
This addition makes sampling-asan more practical for production environments.
Signed-off-by: Sung-hun Kim <sfoon.kim@samsung.com>
---
sampling-asan/Makefile | 2 +-
sampling-asan/README.md | 6 ++
sampling-asan/samasan_allocate.c | 41 +++++++++
sampling-asan/samasan_allocate.h | 5 +-
sampling-asan/samasan_error.h | 5 ++
sampling-asan/samasan_fault_handler.c | 21 +++--
sampling-asan/samasan_init.c | 19 ++++
sampling-asan/samasan_init.h | 1 +
sampling-asan/samasan_variable_init.def | 1 +
sampling-asan/tst-bypass-block.c | 68 ++++++++++++++
sampling-asan/tst-continue-on-crash.c | 113 ++++++++++++++++++++++++
11 files changed, 271 insertions(+), 11 deletions(-)
create mode 100644 sampling-asan/tst-bypass-block.c
create mode 100644 sampling-asan/tst-continue-on-crash.c
diff --git a/sampling-asan/Makefile b/sampling-asan/Makefile
index b0d408af30..b040a5ede9 100644
--- a/sampling-asan/Makefile
+++ b/sampling-asan/Makefile
@@ -30,7 +30,7 @@ tests := tst-allocate tst-sampling tst-threaded-allocation tst-init \
tst-block-sizes tst-partition-sizes tst-invalid-access \
tst-invalid-access2 tst-out-of-memory-pool \
tst-invalid-free tst-invalid-free2 tst-pause-on-fork \
- tst-chunk-pick
+ tst-chunk-pick tst-continue-on-crash tst-bypass-block
$(objpfx)tst-threaded-allocation: $(shared-thread-library)
diff --git a/sampling-asan/README.md b/sampling-asan/README.md
index 7f0c08bef1..6ec217c2ad 100644
--- a/sampling-asan/README.md
+++ b/sampling-asan/README.md
@@ -3,6 +3,7 @@
Author: Sung-hun Kim (sfoon.kim@samsung.com, sebuns@gmail.com)
Date: 2025.02.24
+Updated: 2025.09.12
## Overview
@@ -61,6 +62,11 @@ if this variable is true.
- **SAMASAN_CHUNK_PICK=CENTER**: It indicates the picking tendency when allocating a memory chunk from a
memory block. Sampling-asan supports LEFT, RIGHT, and CENTER picking
tendencies.
+- **SAMASAN_CONTINUE_ON_CRASH=true**: It indicates to restore the running context from the memory bug catched by
+sampling-asan. So, the application keeps run while it gets a bug report.
+
+When the SAMASAN_CONTINUE_ON_CRASH option is enabled, the application continues running even after a memory bug is detected. However, the preallocated memory pool can be exhausted if bugs are reported continuously.
+In such cases, additional memory bugs won't be able to be reported anymore while the application continues running.
## How to use sampling-asan
diff --git a/sampling-asan/samasan_allocate.c b/sampling-asan/samasan_allocate.c
index c021523e56..75857f922d 100644
--- a/sampling-asan/samasan_allocate.c
+++ b/sampling-asan/samasan_allocate.c
@@ -150,6 +150,22 @@ 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)
@@ -219,6 +235,31 @@ unprotect_range_in_block (void *start, size_t 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.
diff --git a/sampling-asan/samasan_allocate.h b/sampling-asan/samasan_allocate.h
index ce611cbeba..4f5195a58f 100644
--- a/sampling-asan/samasan_allocate.h
+++ b/sampling-asan/samasan_allocate.h
@@ -32,13 +32,13 @@ struct memory_pool_trace {
uint64_t tid;
};
-// TODO: change the type of chunk_size
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:31;
+ uint32_t chunk_size:30;
+ bool is_bypassed:1;
bool is_free:1;
};
@@ -55,6 +55,7 @@ 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);
diff --git a/sampling-asan/samasan_error.h b/sampling-asan/samasan_error.h
index 270b244f10..dcf5af728d 100644
--- a/sampling-asan/samasan_error.h
+++ b/sampling-asan/samasan_error.h
@@ -45,4 +45,9 @@ extern void raise_fault_with_address (samasan_error_t error,
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
index 1e88e9b07f..4e30eae707 100644
--- a/sampling-asan/samasan_fault_handler.c
+++ b/sampling-asan/samasan_fault_handler.c
@@ -22,6 +22,7 @@
#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;
@@ -54,6 +55,7 @@ segfault_handler (int sig, siginfo_t *info, void *context)
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,
@@ -61,21 +63,23 @@ segfault_handler (int sig, siginfo_t *info, void *context)
/* 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))
{
signal (SIGSEGV, SIG_DFL);
raise (SIGSEGV);
}
+ }
else if (default_handler.sa_handler == SIG_IGN)
{
/* This error is not reported by sampling-asan */
- if (error == OUT_OF_MEMORY_POOL || error == UNKNOWN_ERROR)
- {
- /* Sampling-asan is intended to be used in production system.
- So, we don't need to abort the execution of the program.
- XXX: is this meaningful? or can be removed? */
- signal (SIGSEGV, SIG_IGN);
- raise (SIGSEGV);
- }
+ signal (SIGSEGV, SIG_IGN);
+ raise (SIGSEGV);
}
else
{
@@ -96,6 +100,7 @@ install_signal_handler (void)
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);
diff --git a/sampling-asan/samasan_init.c b/sampling-asan/samasan_init.c
index 8cf1df849b..c703674544 100644
--- a/sampling-asan/samasan_init.c
+++ b/sampling-asan/samasan_init.c
@@ -39,6 +39,7 @@ typedef enum samasan_option {
SAMASAN_PARTITION_SIZE,
SAMASAN_PAUSE_ON_FORK,
SAMASAN_CHUNK_PICK,
+ SAMASAN_CONTINUE_ON_CRASH,
SAMASAN_OPTIONS,
} samasan_option_t;
@@ -51,6 +52,7 @@ static const char *options_string[SAMASAN_OPTIONS + 1] = {
"SAMASAN_PARTITION_SIZE",
"SAMASAN_PAUSE_ON_FORK",
"SAMASAN_CHUNK_PICK",
+ "SAMASAN_CONTINUE_ON_CRASH",
"SAMASAN_OPTIONS",
};
@@ -72,6 +74,8 @@ struct samasan_configurable {
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)
@@ -133,6 +137,8 @@ samasan_set_variable_range (samasan_option_t option, uint32_t max,
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)
@@ -217,6 +223,15 @@ import_samasan_variable (int type, const char *val)
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;
@@ -277,6 +292,8 @@ samasan_variable_init (void)
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
@@ -324,6 +341,8 @@ samasan_init (void)
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;
diff --git a/sampling-asan/samasan_init.h b/sampling-asan/samasan_init.h
index 607131cf25..971590eeca 100644
--- a/sampling-asan/samasan_init.h
+++ b/sampling-asan/samasan_init.h
@@ -22,6 +22,7 @@
#include <stdbool.h>
extern bool samasan_enabled;
+extern bool samasan_continue_on_crash;
static __inline __attribute__ ((always_inline)) bool
is_samasan_enabled (void)
diff --git a/sampling-asan/samasan_variable_init.def b/sampling-asan/samasan_variable_init.def
index 3a5441bc15..b92feb1701 100644
--- a/sampling-asan/samasan_variable_init.def
+++ b/sampling-asan/samasan_variable_init.def
@@ -24,6 +24,7 @@
#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"
diff --git a/sampling-asan/tst-bypass-block.c b/sampling-asan/tst-bypass-block.c
new file mode 100644
index 0000000000..67d2e8ae82
--- /dev/null
+++ b/sampling-asan/tst-bypass-block.c
@@ -0,0 +1,68 @@
+/* Test and verify a double-free case.
+ 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <support/support.h>
+#include <support/check.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "samasan.h"
+#include "samasan_init.h" /* for samasan_deinit */
+
+static void
+do_use_after_free_iteration (size_t iterations)
+{
+ size_t allocation_size = 512;
+ int *ptr;
+
+ for (size_t i = 0; i < iterations; i++)
+ {
+ ptr = (int *) samasan_allocate (allocation_size);
+ *(ptr) = 1;
+ samasan_free (ptr);
+ *(ptr) = 0; /* raise use-after-free */
+ printf ("%ld\n", i);
+ }
+
+ ptr = (int *) samasan_allocate (allocation_size);
+ TEST_VERIFY (ptr == NULL); /* memory pool exhausted */
+}
+
+static int
+do_test (void)
+{
+ const char *report_path = "/dev/null";
+
+ setenv ("SAMASAN_ENABLE", "true", 1/* replace */);
+ setenv ("SAMASAN_MAX_ALLOC_SIZE", "4096", 1/* replace */);
+ setenv ("SAMASAN_MAX_ON_GOING_ALLOCATIONS", "10", 1/* replace */);
+ setenv ("SAMASAN_SAMPLING_RATE", "1.0", 1/* replace */);
+ setenv ("SAMASAN_OUTPUT_PATH", report_path, 1/* replace */);
+ setenv ("SAMASAN_CONTINUE_ON_CRASH", "true", 1/* replace */);
+
+ /* glibc already initialized sampling-asan.
+ So, deinitialize sampling-asan before starting the test */
+ samasan_deinit ();
+ samasan_init ();
+
+ do_use_after_free_iteration (10);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sampling-asan/tst-continue-on-crash.c b/sampling-asan/tst-continue-on-crash.c
new file mode 100644
index 0000000000..28926d2e83
--- /dev/null
+++ b/sampling-asan/tst-continue-on-crash.c
@@ -0,0 +1,113 @@
+/* Test and verify an invalid-access case.
+ 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <support/support.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "samasan.h"
+#include "samasan_init.h" /* for samasan_deinit */
+
+static bool continue_after_crash = false;
+
+static bool
+check_report (const char *keyword, const char *report_path)
+{
+ printf ("%s\n", report_path);
+ if (access (report_path, F_OK) == 0)
+ {
+ printf ("access success\n");
+ bool check = false;
+ FILE *report = fopen (report_path, "r");
+ if (report != NULL)
+ {
+ char buf[255], *substr;
+ while (fscanf (report, "%s", buf) != EOF)
+ {
+ substr = strstr (buf, keyword);
+ if (substr != NULL && !strncmp (substr, keyword, strlen (keyword)))
+ {
+ check = true;
+ break;
+ }
+ memset (buf, 0, sizeof (buf));
+ }
+ fclose (report);
+ }
+ return (check == true);
+ }
+ printf ("access failed\n");
+ return false;
+}
+
+static void
+clean_up_report (const char *report_path)
+{
+ if (access (report_path, F_OK) == 0)
+ unlink (report_path);
+}
+
+static void
+test_use_after_free_continue (void)
+{
+ const size_t allocation_size = 512;
+ int *ptr;
+
+ ptr = (int *) samasan_allocate (allocation_size);
+ *(ptr) = 1;
+ samasan_free (ptr);
+ *(ptr) = 0; /* raise use-after-free */
+
+ continue_after_crash = true;
+}
+
+static int
+do_test (void)
+{
+ const char *report_path = "/tmp/continue-on-crash-report";
+
+ setenv ("SAMASAN_ENABLE", "true", 1 /* replace */);
+ setenv ("SAMASAN_MAX_ALLOC_SIZE", "4096", 1 /* replace */);
+ setenv ("SAMASAN_MAX_ON_GOING_ALLOCATIONS", "10", 1 /* replace */);
+ setenv ("SAMASAN_SAMPLING_RATE", "1.0", 1 /* replace */);
+ setenv ("SAMASAN_OUTPUT_PATH", report_path, 1 /* replace */);
+ setenv ("SAMASAN_CONTINUE_ON_CRASH", "on", 1 /* replace */);
+
+ /* glibc already initialized sampling-asan.
+ So, deinitialize sampling-asan before starting the test */
+ samasan_deinit ();
+ samasan_init ();
+
+ test_use_after_free_continue ();
+
+ TEST_VERIFY (check_report ("USE_AFTER_FREE", report_path) == true);
+ clean_up_report (report_path);
+
+ /* We set SAMASAN_CONTINUE_ON_CRASH to "on", so the thread should
+ run even after a crash occurred. */
+ TEST_VERIFY (continue_after_crash == true);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.25.1
More information about the Libc-alpha
mailing list