[PATCH v3 07/11] sampling-asan: Add a continue-on-crash option

Sung-hun Kim sfoon.kim@samsung.com
Tue Sep 30 06:46:56 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