emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master cb242bf151: Add support for additional memory checks using Addres


From: Vibhav Pant
Subject: master cb242bf151: Add support for additional memory checks using AddressSanitizer.
Date: Mon, 19 Dec 2022 07:48:28 -0500 (EST)

branch: master
commit cb242bf1514ade34ab93b1db1ea7550093ae5839
Author: Vibhav Pant <vibhavp@gmail.com>
Commit: Vibhav Pant <vibhavp@gmail.com>

    Add support for additional memory checks using AddressSanitizer.
    
    When Emacs is compiled with AddressSanitizer support, enable
    poisoning/unpoisoning freed/unused Lisp objects and other internal
    memory management structures. If enabled, this will mark freed bytes
    that have been put on free lists for future use and initially
    allocated memory blocks/chunks as "poisoned", triggering an ASan error
    if they are accessed improperly. Structures are unpoisoned when they
    have been taken off their respective free lists. Additionally, add
    optional macros for performing unaligned loads, which when enabled by
    defining USE_SANITIZER_UNALIGNED_LOAD will use ASan provided functions
    for loading from unaligned addresses, which may help catch bugs that
    AddressSanitizer might otherwise miss.
    
    * configure.ac: Check for the existence of address and common
    sanitizer API headers.
    
    * src/lisp.h (UNALIGNED_LOAD_SIZE): New macro. If enabled, and the
    necessary sanitizer API is available, define it to
    __sanitizer_unaligned_load(64|32) depending on the word size of the
    architecture.
    
    * src/fns.c [HAVE_FAST_UNALIGNED_ACCESS] (Fstring_lessp): Use
    'UNALIGNED_LOAD_SIZE' to perform unaligned loads from the two strings.
    
    * src/alloc.c (ASAN_POISON_ABLOCK, ASAN_UNPOISON_ABLOCK)
    (ASAN_POISON_INTERVAL_BLOCK, ASAN_UNPOISON_INTERVAL_BLOCK)
    (ASAN_POISON_INTERVAL, ASAN_UNPOISON_INTERVAL)
    (ASAN_PREPARE_DEAD_SDATA, ASAN_PREPARE_LIVE_SDATA)
    (ASAN_POISON_SBLOCK_DATA, ASAN_POISON_STRING_BLOCK)
    (ASAN_UNPOISON_STRING_BLOCK, ASAN_POISON_STRING)
    (ASAN_UNPOISON_STRING, ASAN_POISON_FLOAT_BLOCK)
    (ASAN_UNPOISON_FLOAT_BLOCK, ASAN_POISON_FLOAT)
    (ASAN_UNPOISON_FLOAT, ASAN_POISON_CONS_BLOCK)
    (ASAN_POISON_CONS, ASAN_UNPOISON_CONS)
    (ASAN_POISON_VECTOR_CONTENTS, ASAN_UNPOISON_VECTOR_CONTENTS)
    (ASAN_UNPOISON_VECTOR_BLOCK, ASAN_POISON_SYMBOL_BLOCK)
    (ASAN_UNPOISON_SYMBOL_BLOCK, ASAN_POISON_SYMBOL)
    (ASAN_UNPOISON_SYMBOL) [ADDRESS_SANITIZER]: New macros. When
    address sanitization is enabled, define them to poison/unpoison
    objects.
    
    (lisp_align_malloc): Poison newly allocated blocks on `free_ablock',
    unpoison ablocks taken from it respectively.
    (lisp_align_free): Poison individual ablocks when they are put on the
    free list, unpoison them when an entire `ablocks' chunk is being
    freed.
    
    (make_interval): Poison interval blocks on initial allocation,
    unpoison individual intervals on allocation and removal from
    `interval_free_list'.
    (sweep_intervals): Unpoison interval blocks before sweeping, poison
    dead/unmarked intervals.
    
    (allocate_string): Poison string blocks on initial allocation,
    unpoison Lisp_Strings on removal from the free list.
    (allocate_string_data): Poison `sblock' data on initial allocation,
    unpoison individual `sdata' contents on allocation or removal from the
    free list. Call `ASAN_PREPARE_LIVE_SDATA' on the new `sdata' struct.
    (sweep_strings): Unpoison string blocks before sweeping them,
    poisoning dead strings and their sdata afterwards.
    (compact_small_strings): Call `ASAN_PREPARE_LIVE_DATA' on the `sdata'
    to where compacted strings to moved to.
    (pin_string): Call `ASAN_PREPARE_DEAD_SDATA' on `old_sdata'.
    
    (make_float): Poison float blocks on allocation, unpoisoning
    individual Lisp_Floats on allocation or removal from
    `float_free_list'.
    (sweep_floats): Unpoison float blocks before sweeping, poison
    dead/unmarked floats.
    
    (free_cons): Poison `ptr'.
    (Fcons): Poison cons blocks on allocation, unpoisoning individual
    Lisp_Cons on allocation or removal from `cons_free_list'.
    (sweep_conses): Poison dead/unmarked conses.
    
    (setup_free_list): Poison vectors put on `vector_free_lists'.
    (allocate_vector_from_block): Unpoison vectors taken from the free
    list, poison excess vector bytes when vectors allocated from the free
    list are larger than requested.
    (sweep_vectors): Unpoison vector blocks before sweeping them.
    
    (Fmake_symbol): Poison symbol blocks on initial allocation,
    unpoisoning individual Lisp_Symbols on allocation or removal from
    `symbol_free_list'.
    (sweep_symbols): Unpoison symbol blocks before sweeping, poisoning
    dead/unmarked symbols.
    
    (live_string_holding, live_cons_holding, live_symbol_holding)
    (live_float_holding): When compiling with address sanitization and GC
    poisoning enabled, return NULL if the passed address is poisoned, or
    if the Lisp object it resides in is poisoned, avoiding a
    use-after-poison trigger if these functions are called on a pointer
    that might be referring to a now dead/swept object.
    
    * etc/DEBUG: Add information about enabling ASan memory poisoning.
---
 configure.ac |   4 +-
 etc/DEBUG    |  22 +++++
 src/alloc.c  | 271 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 src/fns.c    |   9 +-
 src/lisp.h   |  20 +++++
 5 files changed, 315 insertions(+), 11 deletions(-)

diff --git a/configure.ac b/configure.ac
index 67c393e335..5bd6645a25 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1841,7 +1841,9 @@ AC_CHECK_HEADERS_ONCE(
   coff.h pty.h
   sys/resource.h
   sys/utsname.h pwd.h utmp.h util.h
-  sanitizer/lsan_interface.h])
+  sanitizer/lsan_interface.h
+  sanitizer/asan_interface.h
+  sanitizer/common_interface_defs.h])
 
 AC_CACHE_CHECK([for ADDR_NO_RANDOMIZE],
   [emacs_cv_personality_addr_no_randomize],
diff --git a/etc/DEBUG b/etc/DEBUG
index ef9160a209..01c75f8da7 100644
--- a/etc/DEBUG
+++ b/etc/DEBUG
@@ -1002,6 +1002,28 @@ Address sanitization is incompatible with 
undefined-behavior
 sanitization, unfortunately.  Address sanitization is also
 incompatible with the --with-dumping=unexec option of 'configure'.
 
+*** Address poisoning/unpoisoning
+
+When compiled with address sanitization, Emacs will also try to mark
+dead/free lisp objects as poisoned, forbidding them from being
+accessed without being unpoisoned first.  This adds an extra layer
+of checking with objects in internal free lists, which may otherwise
+evade traditional use-after-free checks. To disable this, add
+'allow_user_poisoning=0' to ASAN_OPTIONS, or build Emacs with
+'-DGC_ASAN_POISON_OBJECTS=0' in CFLAGS.
+
+While using GDB, memory addresses can be inspected by using helper
+functions additionally provided by the ASan library:
+
+  (gdb) call __asan_describe_address(ptr)
+
+To check whether an address range is poisoned or not, use:
+
+  (gdb) call __asan_region_is_poisoned(ptr, 8)
+
+Additional functions can be found in the header
+'sanitizer/asan_interface.h' in your compiler's headers directory.
+
 ** Running Emacs under Valgrind
 
 Valgrind <https://valgrind.org/> is free software that can be useful
diff --git a/src/alloc.c b/src/alloc.c
index 82e8901cf4..2975754124 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -80,6 +80,37 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include <valgrind/memcheck.h>
 #endif
 
+/* AddressSanitizer exposes additional functions for manually marking
+   memory as poisoned/unpoisoned.  When ASan is enabled and the needed
+   header is available, memory is poisoned when:
+
+   * An ablock is freed (lisp_align_free), or ablocks are initially
+   allocated (lisp_align_malloc).
+   * An interval_block is initially allocated (make_interval).
+   * A dead INTERVAL is put on the interval free list
+   (sweep_intervals).
+   * A sdata is marked as dead (sweep_strings, pin_string).
+   * An sblock is initially allocated (allocate_string_data).
+   * A string_block is initially allocated (allocate_string).
+   * A dead string is put on string_free_list (sweep_strings).
+   * A float_block is initially allocated (make_float).
+   * A dead float is put on float_free_list.
+   * A cons_block is initially allocated (Fcons).
+   * A dead cons is put on cons_free_list (sweep_cons).
+   * A dead vector is put on vector_free_list (setup_on_free_list),
+   or a new vector block is allocated (allocate_vector_from_block).
+   Accordingly, objects reused from the free list are unpoisoned.
+
+   This feature can be disabled wtih the run-time flag
+   `allow_user_poisoning' set to zero.  */
+#if ADDRESS_SANITIZER && defined HAVE_SANITIZER_ASAN_INTERFACE_H \
+  && !defined GC_ASAN_POISON_OBJECTS
+# define GC_ASAN_POISON_OBJECTS 1
+# include <sanitizer/asan_interface.h>
+#else
+# define GC_ASAN_POISON_OBJECTS 0
+#endif
+
 /* GC_CHECK_MARKED_OBJECTS means do sanity checks on allocated objects.
    We turn that on by default when ENABLE_CHECKING is defined;
    define GC_CHECK_MARKED_OBJECTS to zero to disable.  */
@@ -1157,6 +1188,16 @@ struct ablocks
   (1 & (intptr_t) ABLOCKS_BUSY (abase) ? abase : ((void **) (abase))[-1])
 #endif
 
+#if GC_ASAN_POISON_OBJECTS
+# define ASAN_POISON_ABLOCK(b) \
+  __asan_poison_memory_region (&(b)->x, sizeof ((b)->x))
+# define ASAN_UNPOISON_ABLOCK(b) \
+  __asan_unpoison_memory_region (&(b)->x, sizeof ((b)->x))
+#else
+# define ASAN_POISON_ABLOCK(b) ((void) 0)
+# define ASAN_UNPOISON_ABLOCK(b) ((void) 0)
+#endif
+
 /* The list of free ablock.   */
 static struct ablock *free_ablock;
 
@@ -1235,6 +1276,7 @@ lisp_align_malloc (size_t nbytes, enum mem_type type)
        {
          abase->blocks[i].abase = abase;
          abase->blocks[i].x.next_free = free_ablock;
+         ASAN_POISON_ABLOCK (&abase->blocks[i]);
          free_ablock = &abase->blocks[i];
        }
       intptr_t ialigned = aligned;
@@ -1247,6 +1289,7 @@ lisp_align_malloc (size_t nbytes, enum mem_type type)
       eassert ((intptr_t) ABLOCKS_BUSY (abase) == aligned);
     }
 
+  ASAN_UNPOISON_ABLOCK (free_ablock);
   abase = ABLOCK_ABASE (free_ablock);
   ABLOCKS_BUSY (abase)
     = (struct ablocks *) (2 + (intptr_t) ABLOCKS_BUSY (abase));
@@ -1278,6 +1321,7 @@ lisp_align_free (void *block)
 #endif
   /* Put on free list.  */
   ablock->x.next_free = free_ablock;
+  ASAN_POISON_ABLOCK (ablock);
   free_ablock = ablock;
   /* Update busy count.  */
   intptr_t busy = (intptr_t) ABLOCKS_BUSY (abase) - 2;
@@ -1290,9 +1334,12 @@ lisp_align_free (void *block)
       bool aligned = busy;
       struct ablock **tem = &free_ablock;
       struct ablock *atop = &abase->blocks[aligned ? ABLOCKS_SIZE : 
ABLOCKS_SIZE - 1];
-
       while (*tem)
        {
+#if GC_ASAN_POISON_OBJECTS
+         __asan_unpoison_memory_region (&(*tem)->x,
+                                        sizeof ((*tem)->x));
+#endif
          if (*tem >= (struct ablock *) abase && *tem < atop)
            {
              i++;
@@ -1421,6 +1468,24 @@ static int interval_block_index = INTERVAL_BLOCK_SIZE;
 
 static INTERVAL interval_free_list;
 
+#if GC_ASAN_POISON_OBJECTS
+# define ASAN_POISON_INTERVAL_BLOCK(b)         \
+  __asan_poison_memory_region ((b)->intervals, \
+                              sizeof ((b)->intervals))
+# define ASAN_UNPOISON_INTERVAL_BLOCK(b)         \
+  __asan_unpoison_memory_region ((b)->intervals, \
+                                sizeof ((b)->intervals))
+# define ASAN_POISON_INTERVAL(i) \
+  __asan_poison_memory_region ((i), sizeof (*(i)))
+# define ASAN_UNPOISON_INTERVAL(i) \
+  __asan_unpoison_memory_region ((i), sizeof (*(i)))
+#else
+# define ASAN_POISON_INTERVAL_BLOCK(b) ((void) 0)
+# define ASAN_UNPOISON_INTERVAL_BLOCK(b) ((void) 0)
+# define ASAN_POISON_INTERVAL(i) ((void) 0)
+# define ASAN_UNPOISON_INTERVAL(i) ((void) 0)
+#endif
+
 /* Return a new interval.  */
 
 INTERVAL
@@ -1433,6 +1498,7 @@ make_interval (void)
   if (interval_free_list)
     {
       val = interval_free_list;
+      ASAN_UNPOISON_INTERVAL (val);
       interval_free_list = INTERVAL_PARENT (interval_free_list);
     }
   else
@@ -1443,10 +1509,12 @@ make_interval (void)
            = lisp_malloc (sizeof *newi, false, MEM_TYPE_NON_LISP);
 
          newi->next = interval_block;
+         ASAN_POISON_INTERVAL_BLOCK (newi);
          interval_block = newi;
          interval_block_index = 0;
        }
       val = &interval_block->intervals[interval_block_index++];
+      ASAN_UNPOISON_INTERVAL (val);
     }
 
   MALLOC_UNBLOCK_INPUT;
@@ -1687,6 +1755,41 @@ init_strings (void)
   staticpro (&empty_multibyte_string);
 }
 
+#if GC_ASAN_POISON_OBJECTS
+/* Prepare s for denoting a free sdata struct, i.e, poison all bytes
+   in the flexible array member, except the first SDATA_OFFSET bytes.
+   This is only effective for strings of size n where n > sdata_size(n).
+ */
+# define ASAN_PREPARE_DEAD_SDATA(s, size)                          \
+  do {                                                             \
+    __asan_poison_memory_region ((s), sdata_size ((size)));        \
+    __asan_unpoison_memory_region (&(((s))->string),                 \
+                                  sizeof (struct Lisp_String *)); \
+    __asan_unpoison_memory_region (&SDATA_NBYTES ((s)),            \
+                                  sizeof (SDATA_NBYTES ((s))));   \
+   } while (false)
+/* Prepare s for storing string data for NBYTES bytes.  */
+# define ASAN_PREPARE_LIVE_SDATA(s, nbytes) \
+  __asan_unpoison_memory_region ((s), sdata_size ((nbytes)))
+# define ASAN_POISON_SBLOCK_DATA(b, size) \
+  __asan_poison_memory_region ((b)->data, (size))
+# define ASAN_POISON_STRING_BLOCK(b) \
+  __asan_poison_memory_region ((b)->strings, STRING_BLOCK_SIZE)
+# define ASAN_UNPOISON_STRING_BLOCK(b) \
+  __asan_unpoison_memory_region ((b)->strings, STRING_BLOCK_SIZE)
+# define ASAN_POISON_STRING(s) \
+  __asan_poison_memory_region ((s), sizeof (*(s)))
+# define ASAN_UNPOISON_STRING(s) \
+  __asan_unpoison_memory_region ((s), sizeof (*(s)))
+#else
+# define ASAN_PREPARE_DEAD_SDATA(s, size) ((void) 0)
+# define ASAN_PREPARE_LIVE_SDATA(s, nbytes) ((void) 0)
+# define ASAN_POISON_SBLOCK_DATA(b, size) ((void) 0)
+# define ASAN_POISON_STRING_BLOCK(b) ((void) 0)
+# define ASAN_UNPOISON_STRING_BLOCK(b) ((void) 0)
+# define ASAN_POISON_STRING(s) ((void) 0)
+# define ASAN_UNPOISON_STRING(s) ((void) 0)
+#endif
 
 #ifdef GC_CHECK_STRING_BYTES
 
@@ -1805,12 +1908,14 @@ allocate_string (void)
          NEXT_FREE_LISP_STRING (s) = string_free_list;
          string_free_list = s;
        }
+      ASAN_POISON_STRING_BLOCK (b);
     }
 
   check_string_free_list ();
 
   /* Pop a Lisp_String off the free-list.  */
   s = string_free_list;
+  ASAN_UNPOISON_STRING (s);
   string_free_list = NEXT_FREE_LISP_STRING (s);
 
   MALLOC_UNBLOCK_INPUT;
@@ -1870,6 +1975,7 @@ allocate_string_data (struct Lisp_String *s,
 #endif
 
       b = lisp_malloc (size + GC_STRING_EXTRA, clearit, MEM_TYPE_NON_LISP);
+      ASAN_POISON_SBLOCK_DATA (b, size);
 
 #ifdef DOUG_LEA_MALLOC
       if (!mmap_lisp_allowed_p ())
@@ -1891,6 +1997,8 @@ allocate_string_data (struct Lisp_String *s,
        {
          /* Not enough room in the current sblock.  */
          b = lisp_malloc (SBLOCK_SIZE, false, MEM_TYPE_NON_LISP);
+         ASAN_POISON_SBLOCK_DATA (b, SBLOCK_SIZE);
+
          data = b->data;
          b->next = NULL;
          b->next_free = data;
@@ -1903,10 +2011,19 @@ allocate_string_data (struct Lisp_String *s,
        }
 
       data = b->next_free;
+
       if (clearit)
-       memset (SDATA_DATA (data), 0, nbytes);
+       {
+#if GC_ASAN_POISON_OBJECTS
+         /* We are accessing SDATA_DATA (data) before it gets
+          * normally unpoisoned, so do it manually.  */
+         __asan_unpoison_memory_region (SDATA_DATA (data), nbytes);
+#endif
+         memset (SDATA_DATA (data), 0, nbytes);
+       }
     }
 
+  ASAN_PREPARE_LIVE_SDATA (data, nbytes);
   data->string = s;
   b->next_free = (sdata *) ((char *) data + needed + GC_STRING_EXTRA);
   eassert ((uintptr_t) b->next_free % alignof (sdata) == 0);
@@ -1998,12 +2115,16 @@ sweep_strings (void)
       int i, nfree = 0;
       struct Lisp_String *free_list_before = string_free_list;
 
+      ASAN_UNPOISON_STRING_BLOCK (b);
+
       next = b->next;
 
       for (i = 0; i < STRING_BLOCK_SIZE; ++i)
        {
          struct Lisp_String *s = b->strings + i;
 
+         ASAN_UNPOISON_STRING (s);
+
          if (s->u.s.data)
            {
              /* String was not on free-list before.  */
@@ -2040,6 +2161,8 @@ sweep_strings (void)
 
                  /* Put the string on the free-list.  */
                  NEXT_FREE_LISP_STRING (s) = string_free_list;
+                 ASAN_POISON_STRING (s);
+                 ASAN_PREPARE_DEAD_SDATA (data, SDATA_NBYTES (data));
                  string_free_list = s;
                  ++nfree;
                }
@@ -2048,6 +2171,8 @@ sweep_strings (void)
            {
              /* S was on the free-list before.  Put it there again.  */
              NEXT_FREE_LISP_STRING (s) = string_free_list;
+             ASAN_POISON_STRING (s);
+
              string_free_list = s;
              ++nfree;
            }
@@ -2174,6 +2299,7 @@ compact_small_strings (void)
                  if (from != to)
                    {
                      eassert (tb != b || to < from);
+                     ASAN_PREPARE_LIVE_SDATA (to, nbytes);
                      memmove (to, from, size + GC_STRING_EXTRA);
                      to->string->u.s.data = SDATA_DATA (to);
                    }
@@ -2525,6 +2651,7 @@ pin_string (Lisp_Object string)
       memcpy (s->u.s.data, data, size);
       old_sdata->string = NULL;
       SDATA_NBYTES (old_sdata) = size;
+      ASAN_PREPARE_DEAD_SDATA (old_sdata, size);
     }
   s->u.s.size_byte = -3;
 }
@@ -2582,6 +2709,24 @@ struct float_block
 #define XFLOAT_UNMARK(fptr) \
   UNSETMARKBIT (FLOAT_BLOCK (fptr), FLOAT_INDEX ((fptr)))
 
+#if GC_ASAN_POISON_OBJECTS
+# define ASAN_POISON_FLOAT_BLOCK(fblk)         \
+  __asan_poison_memory_region ((fblk)->floats, \
+                              sizeof ((fblk)->floats))
+# define ASAN_UNPOISON_FLOAT_BLOCK(fblk)         \
+  __asan_unpoison_memory_region ((fblk)->floats, \
+                                sizeof ((fblk)->floats))
+# define ASAN_POISON_FLOAT(p) \
+  __asan_poison_memory_region ((p), sizeof (struct Lisp_Float))
+# define ASAN_UNPOISON_FLOAT(p) \
+  __asan_unpoison_memory_region ((p), sizeof (struct Lisp_Float))
+#else
+# define ASAN_POISON_FLOAT_BLOCK(fblk) ((void) 0)
+# define ASAN_UNPOISON_FLOAT_BLOCK(fblk) ((void) 0)
+# define ASAN_POISON_FLOAT(p) ((void) 0)
+# define ASAN_UNPOISON_FLOAT(p) ((void) 0)
+#endif
+
 /* Current float_block.  */
 
 static struct float_block *float_block;
@@ -2606,6 +2751,7 @@ make_float (double float_value)
   if (float_free_list)
     {
       XSETFLOAT (val, float_free_list);
+      ASAN_UNPOISON_FLOAT (float_free_list);
       float_free_list = float_free_list->u.chain;
     }
   else
@@ -2616,9 +2762,11 @@ make_float (double float_value)
            = lisp_align_malloc (sizeof *new, MEM_TYPE_FLOAT);
          new->next = float_block;
          memset (new->gcmarkbits, 0, sizeof new->gcmarkbits);
+         ASAN_POISON_FLOAT_BLOCK (new);
          float_block = new;
          float_block_index = 0;
        }
+      ASAN_UNPOISON_FLOAT (&float_block->floats[float_block_index]);
       XSETFLOAT (val, &float_block->floats[float_block_index]);
       float_block_index++;
     }
@@ -2690,6 +2838,19 @@ static int cons_block_index = CONS_BLOCK_SIZE;
 
 static struct Lisp_Cons *cons_free_list;
 
+#if GC_ASAN_POISON_OBJECTS
+# define ASAN_POISON_CONS_BLOCK(b) \
+  __asan_poison_memory_region ((b)->conses, sizeof ((b)->conses))
+# define ASAN_POISON_CONS(p) \
+  __asan_poison_memory_region ((p), sizeof (struct Lisp_Cons))
+# define ASAN_UNPOISON_CONS(p) \
+  __asan_unpoison_memory_region ((p), sizeof (struct Lisp_Cons))
+#else
+# define ASAN_POISON_CONS_BLOCK(b) ((void) 0)
+# define ASAN_POISON_CONS(p) ((void) 0)
+# define ASAN_UNPOISON_CONS(p) ((void) 0)
+#endif
+
 /* Explicitly free a cons cell by putting it on the free-list.  */
 
 void
@@ -2700,6 +2861,7 @@ free_cons (struct Lisp_Cons *ptr)
   cons_free_list = ptr;
   ptrdiff_t nbytes = sizeof *ptr;
   tally_consing (-nbytes);
+  ASAN_POISON_CONS (ptr);
 }
 
 DEFUN ("cons", Fcons, Scons, 2, 2, 0,
@@ -2712,6 +2874,7 @@ DEFUN ("cons", Fcons, Scons, 2, 2, 0,
 
   if (cons_free_list)
     {
+      ASAN_UNPOISON_CONS (cons_free_list);
       XSETCONS (val, cons_free_list);
       cons_free_list = cons_free_list->u.s.u.chain;
     }
@@ -2722,10 +2885,12 @@ DEFUN ("cons", Fcons, Scons, 2, 2, 0,
          struct cons_block *new
            = lisp_align_malloc (sizeof *new, MEM_TYPE_CONS);
          memset (new->gcmarkbits, 0, sizeof new->gcmarkbits);
+         ASAN_POISON_CONS_BLOCK (new);
          new->next = cons_block;
          cons_block = new;
          cons_block_index = 0;
        }
+      ASAN_UNPOISON_CONS (&cons_block->conses[cons_block_index]);
       XSETCONS (val, &cons_block->conses[cons_block_index]);
       cons_block_index++;
     }
@@ -2980,6 +3145,19 @@ static struct large_vector *large_vectors;
 
 Lisp_Object zero_vector;
 
+#if GC_ASAN_POISON_OBJECTS
+# define ASAN_POISON_VECTOR_CONTENTS(v, bytes) \
+  __asan_poison_memory_region ((v)->contents, (bytes))
+# define ASAN_UNPOISON_VECTOR_CONTENTS(v, bytes) \
+  __asan_unpoison_memory_region ((v)->contents, (bytes))
+# define ASAN_UNPOISON_VECTOR_BLOCK(b) \
+  __asan_unpoison_memory_region ((b)->data, sizeof ((b)->data))
+#else
+# define ASAN_POISON_VECTOR_CONTENTS(v, bytes) ((void) 0)
+# define ASAN_UNPOISON_VECTOR_CONTENTS(v, bytes) ((void) 0)
+# define ASAN_UNPOISON_VECTOR_BLOCK(b) ((void) 0)
+#endif
+
 /* Common shortcut to setup vector on a free list.  */
 
 static void
@@ -2992,6 +3170,7 @@ setup_on_free_list (struct Lisp_Vector *v, ptrdiff_t 
nbytes)
   ptrdiff_t vindex = VINDEX (nbytes);
   eassert (vindex < VECTOR_MAX_FREE_LIST_INDEX);
   set_next_vector (v, vector_free_lists[vindex]);
+  ASAN_POISON_VECTOR_CONTENTS (v, nbytes - header_size);
   vector_free_lists[vindex] = v;
 }
 
@@ -3039,6 +3218,7 @@ allocate_vector_from_block (ptrdiff_t nbytes)
   if (vector_free_lists[index])
     {
       vector = vector_free_lists[index];
+      ASAN_UNPOISON_VECTOR_CONTENTS (vector, nbytes - header_size);
       vector_free_lists[index] = next_vector (vector);
       return vector;
     }
@@ -3052,12 +3232,18 @@ allocate_vector_from_block (ptrdiff_t nbytes)
       {
        /* This vector is larger than requested.  */
        vector = vector_free_lists[index];
+       ASAN_UNPOISON_VECTOR_CONTENTS (vector, nbytes - header_size);
        vector_free_lists[index] = next_vector (vector);
 
        /* Excess bytes are used for the smaller vector,
           which should be set on an appropriate free list.  */
        restbytes = index * roundup_size + VBLOCK_BYTES_MIN - nbytes;
        eassert (restbytes % roundup_size == 0);
+#if GC_ASAN_POISON_OBJECTS
+       /* Ensure that accessing excess bytes does not trigger ASan.  */
+       __asan_unpoison_memory_region (ADVANCE (vector, nbytes),
+                                      restbytes);
+#endif
        setup_on_free_list (ADVANCE (vector, nbytes), restbytes);
        return vector;
       }
@@ -3233,6 +3419,7 @@ sweep_vectors (void)
       for (vector = (struct Lisp_Vector *) block->data;
           VECTOR_IN_BLOCK (vector, block); vector = next)
        {
+         ASAN_UNPOISON_VECTOR_BLOCK (block);
          if (XVECTOR_MARKED_P (vector))
            {
              XUNMARK_VECTOR (vector);
@@ -3608,6 +3795,23 @@ struct symbol_block
   struct symbol_block *next;
 };
 
+#if GC_ASAN_POISON_OBJECTS
+# define ASAN_POISON_SYMBOL_BLOCK(s) \
+  __asan_poison_memory_region ((s)->symbols, sizeof ((s)->symbols))
+# define ASAN_UNPOISON_SYMBOL_BLOCK(s) \
+  __asan_unpoison_memory_region ((s)->symbols, sizeof ((s)->symbols))
+# define ASAN_POISON_SYMBOL(sym) \
+  __asan_poison_memory_region ((sym), sizeof (*(sym)))
+# define ASAN_UNPOISON_SYMBOL(sym) \
+  __asan_unpoison_memory_region ((sym), sizeof (*(sym)))
+
+#else
+# define ASAN_POISON_SYMBOL_BLOCK(s) ((void) 0)
+# define ASAN_UNPOISON_SYMBOL_BLOCK(s) ((void) 0)
+# define ASAN_POISON_SYMBOL(sym) ((void) 0)
+# define ASAN_UNPOISON_SYMBOL(sym) ((void) 0)
+#endif
+
 /* Current symbol block and index of first unused Lisp_Symbol
    structure in it.  */
 
@@ -3661,6 +3865,7 @@ Its value is void, and its function definition and 
property list are nil.  */)
 
   if (symbol_free_list)
     {
+      ASAN_UNPOISON_SYMBOL (symbol_free_list);
       XSETSYMBOL (val, symbol_free_list);
       symbol_free_list = symbol_free_list->u.s.next;
     }
@@ -3670,10 +3875,13 @@ Its value is void, and its function definition and 
property list are nil.  */)
        {
          struct symbol_block *new
            = lisp_malloc (sizeof *new, false, MEM_TYPE_SYMBOL);
+         ASAN_POISON_SYMBOL_BLOCK (new);
          new->next = symbol_block;
          symbol_block = new;
          symbol_block_index = 0;
        }
+
+      ASAN_UNPOISON_SYMBOL (&symbol_block->symbols[symbol_block_index]);
       XSETSYMBOL (val, &symbol_block->symbols[symbol_block_index]);
       symbol_block_index++;
     }
@@ -4561,6 +4769,11 @@ static struct Lisp_String *
 live_string_holding (struct mem_node *m, void *p)
 {
   eassert (m->type == MEM_TYPE_STRING);
+#if GC_ASAN_POISON_OBJECTS
+  if (__asan_address_is_poisoned (p))
+    return NULL;
+#endif
+
   struct string_block *b = m->start;
   char *cp = p;
   ptrdiff_t offset = cp - (char *) &b->strings[0];
@@ -4577,6 +4790,10 @@ live_string_holding (struct mem_node *m, void *p)
          || off == offsetof (struct Lisp_String, u.s.data))
        {
          struct Lisp_String *s = p = cp -= off;
+#if GC_ASAN_POISON_OBJECTS
+         if (__asan_region_is_poisoned (s, sizeof (*s)))
+           return NULL;
+#endif
          if (s->u.s.data)
            return s;
        }
@@ -4598,6 +4815,11 @@ static struct Lisp_Cons *
 live_cons_holding (struct mem_node *m, void *p)
 {
   eassert (m->type == MEM_TYPE_CONS);
+#if GC_ASAN_POISON_OBJECTS
+  if (__asan_address_is_poisoned (p))
+    return NULL;
+#endif
+
   struct cons_block *b = m->start;
   char *cp = p;
   ptrdiff_t offset = cp - (char *) &b->conses[0];
@@ -4615,6 +4837,10 @@ live_cons_holding (struct mem_node *m, void *p)
          || off == offsetof (struct Lisp_Cons, u.s.u.cdr))
        {
          struct Lisp_Cons *s = p = cp -= off;
+#if GC_ASAN_POISON_OBJECTS
+         if (__asan_region_is_poisoned (s, sizeof (*s)))
+           return NULL;
+#endif
          if (!deadp (s->u.s.car))
            return s;
        }
@@ -4637,6 +4863,10 @@ static struct Lisp_Symbol *
 live_symbol_holding (struct mem_node *m, void *p)
 {
   eassert (m->type == MEM_TYPE_SYMBOL);
+#if GC_ASAN_POISON_OBJECTS
+  if (__asan_address_is_poisoned (p))
+    return NULL;
+#endif
   struct symbol_block *b = m->start;
   char *cp = p;
   ptrdiff_t offset = cp - (char *) &b->symbols[0];
@@ -4662,6 +4892,10 @@ live_symbol_holding (struct mem_node *m, void *p)
          || off == offsetof (struct Lisp_Symbol, u.s.next))
        {
          struct Lisp_Symbol *s = p = cp -= off;
+#if GC_ASAN_POISON_OBJECTS
+         if (__asan_region_is_poisoned (s, sizeof (*s)))
+           return NULL;
+#endif
          if (!deadp (s->u.s.function))
            return s;
        }
@@ -4684,6 +4918,11 @@ static struct Lisp_Float *
 live_float_holding (struct mem_node *m, void *p)
 {
   eassert (m->type == MEM_TYPE_FLOAT);
+#if GC_ASAN_POISON_OBJECTS
+  if (__asan_address_is_poisoned (p))
+    return NULL;
+#endif
+
   struct float_block *b = m->start;
   char *cp = p;
   ptrdiff_t offset = cp - (char *) &b->floats[0];
@@ -4698,8 +4937,12 @@ live_float_holding (struct mem_node *m, void *p)
          && (b != float_block
              || offset / sizeof b->floats[0] < float_block_index))
        {
-         p = cp - off;
-         return p;
+         struct Lisp_Float *f = (struct Lisp_Float *) (cp - off);
+#if GC_ASAN_POISON_OBJECTS
+         if (__asan_region_is_poisoned (f, sizeof (*f)))
+           return NULL;
+#endif
+         return f;
        }
     }
   return NULL;
@@ -7181,11 +7424,13 @@ sweep_conses (void)
                  struct Lisp_Cons *acons = &cblk->conses[pos];
                  if (!XCONS_MARKED_P (acons))
                     {
+                     ASAN_UNPOISON_CONS (&cblk->conses[pos]);
                       this_free++;
                       cblk->conses[pos].u.s.u.chain = cons_free_list;
                       cons_free_list = &cblk->conses[pos];
                       cons_free_list->u.s.car = dead_object ();
-                    }
+                     ASAN_POISON_CONS (&cblk->conses[pos]);
+                   }
                   else
                     {
                       num_used++;
@@ -7203,6 +7448,7 @@ sweep_conses (void)
         {
           *cprev = cblk->next;
           /* Unhook from the free list.  */
+         ASAN_UNPOISON_CONS (&cblk->conses[0]);
           cons_free_list = cblk->conses[0].u.s.u.chain;
           lisp_align_free (cblk);
         }
@@ -7229,6 +7475,7 @@ sweep_floats (void)
   for (struct float_block *fblk; (fblk = *fprev); )
     {
       int this_free = 0;
+      ASAN_UNPOISON_FLOAT_BLOCK (fblk);
       for (int i = 0; i < lim; i++)
        {
          struct Lisp_Float *afloat = &fblk->floats[i];
@@ -7236,6 +7483,7 @@ sweep_floats (void)
            {
              this_free++;
              fblk->floats[i].u.chain = float_free_list;
+             ASAN_POISON_FLOAT (&fblk->floats[i]);
              float_free_list = &fblk->floats[i];
            }
          else
@@ -7252,7 +7500,8 @@ sweep_floats (void)
         {
           *fprev = fblk->next;
           /* Unhook from the free list.  */
-          float_free_list = fblk->floats[0].u.chain;
+         ASAN_UNPOISON_FLOAT (&fblk->floats[0]);
+         float_free_list = fblk->floats[0].u.chain;
           lisp_align_free (fblk);
         }
       else
@@ -7278,13 +7527,14 @@ sweep_intervals (void)
   for (struct interval_block *iblk; (iblk = *iprev); )
     {
       int this_free = 0;
-
+      ASAN_UNPOISON_INTERVAL_BLOCK (iblk);
       for (int i = 0; i < lim; i++)
         {
           if (!iblk->intervals[i].gcmarkbit)
             {
               set_interval_parent (&iblk->intervals[i], interval_free_list);
               interval_free_list = &iblk->intervals[i];
+             ASAN_POISON_INTERVAL (&iblk->intervals[i]);
               this_free++;
             }
           else
@@ -7301,6 +7551,7 @@ sweep_intervals (void)
         {
           *iprev = iblk->next;
           /* Unhook from the free list.  */
+         ASAN_UNPOISON_INTERVAL (&iblk->intervals[0]);
           interval_free_list = INTERVAL_PARENT (&iblk->intervals[0]);
           lisp_free (iblk);
         }
@@ -7330,6 +7581,8 @@ sweep_symbols (void)
 
   for (sblk = symbol_block; sblk; sblk = *sprev)
     {
+      ASAN_UNPOISON_SYMBOL_BLOCK (sblk);
+
       int this_free = 0;
       struct Lisp_Symbol *sym = sblk->symbols;
       struct Lisp_Symbol *end = sym + lim;
@@ -7351,7 +7604,8 @@ sweep_symbols (void)
               sym->u.s.next = symbol_free_list;
               symbol_free_list = sym;
               symbol_free_list->u.s.function = dead_object ();
-              ++this_free;
+             ASAN_POISON_SYMBOL (sym);
+             ++this_free;
             }
           else
             {
@@ -7370,6 +7624,7 @@ sweep_symbols (void)
         {
           *sprev = sblk->next;
           /* Unhook from the free list.  */
+         ASAN_UNPOISON_SYMBOL (&sblk->symbols[0]);
           symbol_free_list = sblk->symbols[0].u.s.next;
           lisp_free (sblk);
         }
diff --git a/src/fns.c b/src/fns.c
index b6e871ad49..eeb65cadf3 100644
--- a/src/fns.c
+++ b/src/fns.c
@@ -497,8 +497,13 @@ Symbols are also allowed; their print names are used 
instead.  */)
          int ws = sizeof (word_t);
          const word_t *w1 = (const word_t *) SDATA (string1);
          const word_t *w2 = (const word_t *) SDATA (string2);
-         while (b < nb - ws + 1 && w1[b / ws] == w2[b / ws])
-           b += ws;
+         while (b < nb - ws + 1)
+           {
+             if (UNALIGNED_LOAD_SIZE (w1, b / ws)
+                 != UNALIGNED_LOAD_SIZE (w2, b / ws))
+               break;
+             b += ws;
+           }
        }
 
       /* Scan forward to the differing byte.  */
diff --git a/src/lisp.h b/src/lisp.h
index 0f70f60d75..be511a0eb9 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -5296,6 +5296,26 @@ __lsan_ignore_object (void const *p)
 }
 #endif
 
+/* If built with USE_SANITIZER_UNALIGNED_LOAD defined, use compiler
+   provided ASan functions to perform unaligned loads, allowing ASan
+   to catch bugs which it might otherwise miss.  */
+#if defined HAVE_SANITIZER_COMMON_INTERFACE_DEFS_H \
+  && defined ADDRESS_SANITIZER                     \
+  && defined USE_SANITIZER_UNALIGNED_LOAD
+# include <sanitizer/common_interface_defs.h>
+# if (SIZE_MAX == UINT64_MAX)
+#  define UNALIGNED_LOAD_SIZE(a, i) \
+   (size_t) __sanitizer_unaligned_load64 ((void *) ((a) + (i)))
+# elif (SIZE_MAX == UINT32_MAX)
+#  define UNALIGNED_LOAD_SIZE(a, i) \
+   (size_t) __sanitizer_unaligned_load32 ((void *) ((a) + (i)))
+# else
+#  define UNALIGNED_LOAD_SIZE(a, i) *((a) + (i))
+# endif
+#else
+# define UNALIGNED_LOAD_SIZE(a, i) *((a) + (i))
+#endif
+
 extern void xputenv (const char *);
 
 extern char *egetenv_internal (const char *, ptrdiff_t);



reply via email to

[Prev in Thread] Current Thread [Next in Thread]