[前][次][番号順一覧][スレッド一覧]

ruby-changes:53357

From: shyouhei <ko1@a...>
Date: Tue, 6 Nov 2018 19:06:14 +0900 (JST)
Subject: [ruby-changes:53357] shyouhei:r65573 (trunk): adopt sanitizer API

shyouhei	2018-11-06 19:06:07 +0900 (Tue, 06 Nov 2018)

  New Revision: 65573

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=65573

  Log:
    adopt sanitizer API
    
    These APIs are much like <valgrind/memcheck.h>. Use them to
    fine-grain annotate the usage of our memory.

  Modified files:
    trunk/configure.ac
    trunk/gc.c
    trunk/internal.h
    trunk/string.c
    trunk/transient_heap.c
Index: string.c
===================================================================
--- string.c	(revision 65572)
+++ string.c	(revision 65573)
@@ -803,6 +803,11 @@ VALUE https://github.com/ruby/ruby/blob/trunk/string.c#L803
 rb_str_new_cstr(const char *ptr)
 {
     must_not_null(ptr);
+    /* rb_str_new_cstr() can take pointer from non-malloc-generated
+     * memory regions, and that cannot be detected by the MSAN.  Just
+     * trust the programmer that the argument passed here is a sane C
+     * string. */
+    __msan_unpoison_string(ptr);
     return rb_str_new(ptr, strlen(ptr));
 }
 
Index: transient_heap.c
===================================================================
--- transient_heap.c	(revision 65572)
+++ transient_heap.c	(revision 65573)
@@ -230,11 +230,13 @@ transient_heap_get(void) https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L230
 static void
 reset_block(struct transient_heap_block *block)
 {
+    __msan_allocated_memory(block, sizeof block);
     block->info.size = TRANSIENT_HEAP_BLOCK_SIZE - sizeof(struct transient_heap_block_header);
     block->info.index = 0;
     block->info.objects = 0;
     block->info.last_marked_index = TRANSIENT_HEAP_ALLOC_MARKING_LAST;
     block->info.next_block = NULL;
+    __asan_poison_memory_region(&block->buff, sizeof block->buff);
 }
 
 static void
@@ -379,10 +381,17 @@ rb_transient_heap_alloc(VALUE obj, size_ https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L381
         if (header) {
             void *ptr;
 
+            /* header is poisoned to prevent buffer overflow, should
+             * unpoison first... */
+            unpoison_memory_region(header, sizeof *header, true);
+
             header->size = size;
             header->magic = TRANSIENT_HEAP_ALLOC_MAGIC;
             header->next_marked_index = TRANSIENT_HEAP_ALLOC_MARKING_FREE;
             header->obj = obj; /* TODO: can we eliminate it? */
+
+            /* header is fixed; shall poison again */
+            poison_memory_region(header, sizeof *header);
             ptr = header + 1;
 
             theap->total_objects++; /* statistics */
@@ -395,6 +404,9 @@ rb_transient_heap_alloc(VALUE obj, size_ https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L404
             if (TRANSIENT_HEAP_DEBUG >= 3) fprintf(stderr, "rb_transient_heap_alloc: header:%p ptr:%p size:%d obj:%s\n", (void *)header, ptr, (int)size, rb_obj_info(obj));
 
             RB_DEBUG_COUNTER_INC(theap_alloc);
+
+            /* ptr is set up; OK to unpoison. */
+            unpoison_memory_region(ptr, size, true);
             return ptr;
         }
         else {
@@ -509,6 +521,7 @@ void https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L521
 rb_transient_heap_mark(VALUE obj, const void *ptr)
 {
     struct transient_alloc_header *header = ptr_to_alloc_header(ptr);
+    unpoison_memory_region(header, sizeof *header, false);
     if (header->magic != TRANSIENT_HEAP_ALLOC_MAGIC) rb_bug("rb_transient_heap_mark: wrong header, %s (%p)", rb_obj_info(obj), ptr);
     if (TRANSIENT_HEAP_DEBUG >= 3) fprintf(stderr, "rb_transient_heap_mark: %s (%p)\n", rb_obj_info(obj), ptr);
 
@@ -537,6 +550,7 @@ rb_transient_heap_mark(VALUE obj, const https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L550
     else {
         struct transient_heap* theap = transient_heap_get();
         struct transient_heap_block *block = alloc_header_to_block(theap, header);
+        __asan_unpoison_memory_region(&block->info, sizeof block->info);
         header->next_marked_index = block->info.last_marked_index;
         block->info.last_marked_index = (int)((char *)header - block->buff);
         theap->total_marked_objects++;
Index: internal.h
===================================================================
--- internal.h	(revision 65572)
+++ internal.h	(revision 65573)
@@ -69,7 +69,7 @@ extern "C" { https://github.com/ruby/ruby/blob/trunk/internal.h#L69
 #endif
 
 #ifndef NO_SANITIZE
-#define NO_SANITIZE(x, y) y
+# define NO_SANITIZE(x, y) y
 #endif
 
 #ifdef HAVE_VALGRIND_MEMCHECK_H
@@ -95,6 +95,60 @@ extern "C" { https://github.com/ruby/ruby/blob/trunk/internal.h#L95
 # define __has_extension __has_feature
 #endif
 
+#ifdef HAVE_SANITIZER_ASAN_INTERFACE_H
+# include <sanitizer/asan_interface.h>
+#endif
+
+#if !__has_feature(address_sanitizer)
+# define __asan_poison_memory_region(x, y)
+# define __asan_unpoison_memory_region(x, y)
+# define __asan_region_is_poisoned(x, y) 0
+#endif
+
+#ifdef HAVE_SANITIZER_MSAN_INTERFACE_H
+# include <sanitizer/msan_interface.h>
+#endif
+
+#if !__has_feature(memory_sanitizer)
+# define __msan_allocated_memory(x, y)
+# define __msan_poison(x, y)
+# define __msan_unpoison(x, y)
+# define __msan_unpoison_string(x)
+#endif
+
+static inline void
+poison_memory_region(const volatile void *ptr, size_t size)
+{
+    __msan_poison(ptr, size);
+    __asan_poison_memory_region(ptr, size);
+}
+
+static inline void
+poison_object(VALUE obj)
+{
+    struct RVALUE *ptr = (void *)obj;
+    poison_memory_region(ptr, SIZEOF_VALUE);
+}
+
+static inline void
+unpoison_memory_region(const volatile void *ptr, size_t size, bool malloc_p)
+{
+    __asan_unpoison_memory_region(ptr, size);
+    if (malloc_p) {
+        __msan_allocated_memory(ptr, size);
+    }
+    else {
+        __msan_unpoison(ptr, size);
+    }
+}
+
+static inline void
+unpoison_object(VALUE obj, bool newobj_p)
+{
+    struct RVALUE *ptr = (void *)obj;
+    unpoison_memory_region(ptr, SIZEOF_VALUE, newobj_p);
+}
+
 /* Prevent compiler from reordering access */
 #define ACCESS_ONCE(type,x) (*((volatile type *)&(x)))
 
Index: configure.ac
===================================================================
--- configure.ac	(revision 65572)
+++ configure.ac	(revision 65573)
@@ -1023,6 +1023,8 @@ AC_CHECK_HEADERS(malloc_np.h) https://github.com/ruby/ruby/blob/trunk/configure.ac#L1023
 AC_CHECK_HEADERS(net/socket.h)
 AC_CHECK_HEADERS(process.h)
 AC_CHECK_HEADERS(pwd.h)
+AC_CHECK_HEADERS(sanitizer/asan_interface.h)
+AC_CHECK_HEADERS(sanitizer/msan_interface.h)
 AC_CHECK_HEADERS(setjmpex.h)
 AC_CHECK_HEADERS(stdalign.h)
 AC_CHECK_HEADERS(sys/attr.h)
Index: gc.c
===================================================================
--- gc.c	(revision 65572)
+++ gc.c	(revision 65573)
@@ -1434,6 +1434,7 @@ heap_page_add_freeobj(rb_objspace_t *obj https://github.com/ruby/ruby/blob/trunk/gc.c#L1434
     if (RGENGC_CHECK_MODE && !is_pointer_to_heap(objspace, p)) {
 	rb_bug("heap_page_add_freeobj: %p is not rvalue.", (void *)p);
     }
+    poison_object(obj);
 
     gc_report(3, objspace, "heap_page_add_freeobj: add %p to freelist\n", (void *)obj);
 }
@@ -1758,6 +1759,7 @@ heap_get_freeobj_from_next_freepage(rb_o https://github.com/ruby/ruby/blob/trunk/gc.c#L1759
     p = page->freelist;
     page->freelist = NULL;
     page->free_slots = 0;
+    unpoison_object((VALUE)p, true);
     return p;
 }
 
@@ -1768,6 +1770,7 @@ heap_get_freeobj_head(rb_objspace_t *obj https://github.com/ruby/ruby/blob/trunk/gc.c#L1770
     if (LIKELY(p != NULL)) {
 	heap->freelist = p->as.free.next;
     }
+    unpoison_object((VALUE)p, true);
     return (VALUE)p;
 }
 
@@ -1778,6 +1781,7 @@ heap_get_freeobj(rb_objspace_t *objspace https://github.com/ruby/ruby/blob/trunk/gc.c#L1781
 
     while (1) {
 	if (LIKELY(p != NULL)) {
+            unpoison_object((VALUE)p, true);
 	    heap->freelist = p->as.free.next;
 	    return (VALUE)p;
 	}
@@ -2612,8 +2616,11 @@ static int https://github.com/ruby/ruby/blob/trunk/gc.c#L2616
 internal_object_p(VALUE obj)
 {
     RVALUE *p = (RVALUE *)obj;
+    void *ptr = __asan_region_is_poisoned(p, SIZEOF_VALUE);
+    bool used_p = p->as.basic.flags;
+    unpoison_object(obj, false);
 
-    if (p->as.basic.flags) {
+    if (used_p) {
 	switch (BUILTIN_TYPE(p)) {
 	  case T_NODE:
 	    UNEXPECTED_NODE(internal_object_p);
@@ -2634,6 +2641,9 @@ internal_object_p(VALUE obj) https://github.com/ruby/ruby/blob/trunk/gc.c#L2641
 	    return 0;
 	}
     }
+    if (ptr || ! used_p) {
+        poison_object(obj);
+    }
     return 1;
 }
 
@@ -2924,8 +2934,11 @@ static void https://github.com/ruby/ruby/blob/trunk/gc.c#L2934
 finalize_list(rb_objspace_t *objspace, VALUE zombie)
 {
     while (zombie) {
-	VALUE next_zombie = RZOMBIE(zombie)->next;
-	struct heap_page *page = GET_HEAP_PAGE(zombie);
+        VALUE next_zombie;
+        struct heap_page *page;
+        unpoison_object(zombie, false);
+        next_zombie = RZOMBIE(zombie)->next;
+        page = GET_HEAP_PAGE(zombie);
 
 	run_final(objspace, zombie);
 
@@ -3044,6 +3057,7 @@ rb_objspace_call_finalizer(rb_objspace_t https://github.com/ruby/ruby/blob/trunk/gc.c#L3057
     for (i = 0; i < heap_allocated_pages; i++) {
 	p = heap_pages_sorted[i]->start; pend = p + heap_pages_sorted[i]->total_slots;
 	while (p < pend) {
+            unpoison_object((VALUE)p, false);
 	    switch (BUILTIN_TYPE(p)) {
 	      case T_DATA:
 		if (!DATA_PTR(p) || !RANY(p)->as.data.dfree) break;
@@ -3067,6 +3081,7 @@ rb_objspace_call_finalizer(rb_objspace_t https://github.com/ruby/ruby/blob/trunk/gc.c#L3081
 		}
 		break;
 	    }
+            poison_object((VALUE)p);
 	    p++;
 	}
     }
@@ -3610,6 +3625,7 @@ gc_page_sweep(rb_objspace_t *objspace, r https://github.com/ruby/ruby/blob/trunk/gc.c#L3625
 	if (bitset) {
 	    p = offset  + i * BITS_BITLENGTH;
 	    do {
+                unpoison_object((VALUE)p, false);
 		if (bitset & 1) {
 		    switch (BUILTIN_TYPE(p)) {
 		      default: { /* majority case */
@@ -3628,6 +3644,7 @@ gc_page_sweep(rb_objspace_t *objspace, r https://github.com/ruby/ruby/blob/trunk/gc.c#L3644
 			      heap_page_add_freeobj(objspace, sweep_page, (VALUE)p);
 			      gc_report(3, objspace, "page_sweep: %s is added to freelist\n", obj_info((VALUE)p));
 			      freed_slots++;
+                              poison_object((VALUE)p);
 			  }
 			  break;
 		      }
@@ -4419,10 +4436,17 @@ gc_mark_maybe(rb_objspace_t *objspace, V https://github.com/ruby/ruby/blob/trunk/gc.c#L4436
 {
     (void)VALGRIND_MAKE_MEM_DEFINED(&obj, sizeof(obj));
     if (is_pointer_to_heap(objspace, (void *)obj)) {
-	int type = BUILTIN_TYPE(obj);
+        int type;
+        void *ptr = __asan_region_is_poisoned((void *)obj, SIZEOF_VALUE);
+
+        unpoison_object(obj, false);
+        type = BUILTIN_TYPE(obj);
 	if (type != T_ZOMBIE && type != T_NONE) {
 	    gc_mark_ptr(objspace, obj);
 	}
+        if (ptr) {
+            poison_object(obj);
+        }
     }
 }
 

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]