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/