#ifndef INTERNAL_SANITIZERS_H /*-*-C-*-vi:se ft=c:*/ #define INTERNAL_SANITIZERS_H /** * @author Ruby developers * @copyright This file is a part of the programming language Ruby. * Permission is hereby granted, to either redistribute and/or * modify this file, provided that the conditions mentioned in the * file COPYING are met. Consult the file for details. * @brief Internal header for ASAN / MSAN / etc. */ #include "ruby/internal/config.h" #include "internal/compilers.h" /* for __has_feature */ #ifdef HAVE_VALGRIND_MEMCHECK_H # include #endif #ifdef HAVE_SANITIZER_ASAN_INTERFACE_H # if __has_feature(address_sanitizer) # define RUBY_ASAN_ENABLED # include # endif #endif #ifdef HAVE_SANITIZER_MSAN_INTERFACE_H # if __has_feature(memory_sanitizer) # define RUBY_MSAN_ENABLED # include # endif #endif #include "ruby/internal/stdbool.h" /* for bool */ #include "ruby/ruby.h" /* for VALUE */ #if 0 #elif defined(RUBY_ASAN_ENABLED) && defined(RUBY_MSAN_ENABLED) # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \ __attribute__((__no_sanitize__("memory, address"), __noinline__)) x #elif defined(RUBY_ASAN_ENABLED) # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \ __attribute__((__no_sanitize__("address"), __noinline__)) x #elif defined(NO_SANITIZE_ADDRESS) # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \ NO_SANITIZE_ADDRESS(NOINLINE(x)) #elif defined(NO_ADDRESS_SAFETY_ANALYSIS) # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \ NO_ADDRESS_SAFETY_ANALYSIS(NOINLINE(x)) #else # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) x #endif #if defined(NO_SANITIZE) && RBIMPL_COMPILER_IS(GCC) /* GCC warns about unknown sanitizer, which is annoying. */ # include "internal/warnings.h" # undef NO_SANITIZE # define NO_SANITIZE(x, y) \ COMPILER_WARNING_PUSH; \ COMPILER_WARNING_IGNORED(-Wattributes); \ __attribute__((__no_sanitize__(x))) y; \ COMPILER_WARNING_POP #endif #ifndef NO_SANITIZE # define NO_SANITIZE(x, y) y #endif #ifndef RUBY_ASAN_ENABLED # define __asan_poison_memory_region(x, y) # define __asan_unpoison_memory_region(x, y) # define __asan_region_is_poisoned(x, y) 0 # define __asan_get_current_fake_stack() NULL # define __asan_addr_is_in_fake_stack(fake_stack, slot, start, end) NULL #endif #ifndef RUBY_MSAN_ENABLED # define __msan_allocated_memory(x, y) ((void)(x), (void)(y)) # define __msan_poison(x, y) ((void)(x), (void)(y)) # define __msan_unpoison(x, y) ((void)(x), (void)(y)) # define __msan_unpoison_string(x) ((void)(x)) #endif #ifdef VALGRIND_MAKE_READABLE # define VALGRIND_MAKE_MEM_DEFINED(p, n) VALGRIND_MAKE_READABLE((p), (n)) #endif #ifdef VALGRIND_MAKE_WRITABLE # define VALGRIND_MAKE_MEM_UNDEFINED(p, n) VALGRIND_MAKE_WRITABLE((p), (n)) #endif #ifndef VALGRIND_MAKE_MEM_DEFINED # define VALGRIND_MAKE_MEM_DEFINED(p, n) 0 #endif #ifndef VALGRIND_MAKE_MEM_UNDEFINED # define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0 #endif /*! * This function asserts that a (continuous) memory region from ptr to size * being "poisoned". Both read / write access to such memory region are * prohibited until properly unpoisoned. The region must be previously * allocated (do not pass a freed pointer here), but not necessarily be an * entire object that the malloc returns. You can punch hole a part of a * gigantic heap arena. This is handy when you do not free an allocated memory * region to reuse later: poison when you keep it unused, and unpoison when you * reuse. * * \param[in] ptr pointer to the beginning of the memory region to poison. * \param[in] size the length of the memory region to poison. */ static inline void asan_poison_memory_region(const volatile void *ptr, size_t size) { __msan_poison(ptr, size); __asan_poison_memory_region(ptr, size); } /*! * This is a variant of asan_poison_memory_region that takes a VALUE. * * \param[in] obj target object. */ static inline void asan_poison_object(VALUE obj) { MAYBE_UNUSED(struct RVALUE *) ptr = (void *)obj; asan_poison_memory_region(ptr, SIZEOF_VALUE); } #ifdef RUBY_ASAN_ENABLED #define asan_poison_object_if(ptr, obj) do { \ if (ptr) asan_poison_object(obj); \ } while (0) #else #define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj)) #endif /*! * This function predicates if the given object is fully addressable or not. * * \param[in] obj target object. * \retval 0 the given object is fully addressable. * \retval otherwise pointer to first such byte who is poisoned. */ static inline void * asan_poisoned_object_p(VALUE obj) { MAYBE_UNUSED(struct RVALUE *) ptr = (void *)obj; return __asan_region_is_poisoned(ptr, SIZEOF_VALUE); } /*! * This function asserts that a (formally poisoned) memory region from ptr to * size is now addressable. Write access to such memory region gets allowed. * However read access might or might not be possible depending on situations, * because the region can have contents of previous usages. That information * should be passed by the malloc_p flag. If that is true, the contents of the * region is _not_ fully defined (like the return value of malloc behaves). * Reading from there is NG; write something first. If malloc_p is false on * the other hand, that memory region is fully defined and can be read * immediately. * * \param[in] ptr pointer to the beginning of the memory region to unpoison. * \param[in] size the length of the memory region. * \param[in] malloc_p if the memory region is like a malloc's return value or not. */ static inline void asan_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); } } /*! * This is a variant of asan_unpoison_memory_region that takes a VALUE. * * \param[in] obj target object. * \param[in] malloc_p if the memory region is like a malloc's return value or not. */ static inline void asan_unpoison_object(VALUE obj, bool newobj_p) { MAYBE_UNUSED(struct RVALUE *) ptr = (void *)obj; asan_unpoison_memory_region(ptr, SIZEOF_VALUE, newobj_p); } static inline void * asan_unpoison_object_temporary(VALUE obj) { void *ptr = asan_poisoned_object_p(obj); asan_unpoison_object(obj, false); return ptr; } static inline void * asan_poison_object_restore(VALUE obj, void *ptr) { if (ptr) { asan_poison_object(obj); } return NULL; } /*! * Checks if the given pointer is on an ASAN fake stack. If so, it returns the * address this variable has on the real frame; if not, it returns the origin * address unmodified. * * n.b. - _dereferencing_ the returned address is meaningless and should not * be done; even though ASAN reserves space for the variable in both the real and * fake stacks, the _value_ of that variable is only in the fake stack. * * n.b. - this only works for addresses passed in from local variables on the same * thread, because the ASAN fake stacks are threadlocal. * * \param[in] slot the address of some local variable * \retval a pointer to something from that frame on the _real_ machine stack */ static inline void * asan_get_real_stack_addr(void* slot) { VALUE *addr; addr = __asan_addr_is_in_fake_stack(__asan_get_current_fake_stack(), slot, NULL, NULL); return addr ? addr : slot; } /*! * Gets the current thread's fake stack handle, which can be passed into get_fake_stack_extents * * \retval An opaque value which can be passed to asan_get_fake_stack_extents */ static inline void * asan_get_thread_fake_stack_handle(void) { return __asan_get_current_fake_stack(); } /*! * Checks if the given VALUE _actually_ represents a pointer to an ASAN fake stack. * * If the given slot _is_ actually a reference to an ASAN fake stack, and that fake stack * contains the real values for the passed-in range of machine stack addresses, returns true * and the range of the fake stack through the outparams. * * Otherwise, returns false, and sets the outparams to NULL. * * Note that this function expects "start" to be > "end" on downward-growing stack architectures; * * \param[in] thread_fake_stack_handle The asan fake stack reference for the thread we're scanning * \param[in] slot The value on the machine stack we want to inspect * \param[in] machine_stack_start The extents of the real machine stack on which slot lives * \param[in] machine_stack_end The extents of the real machine stack on which slot lives * \param[out] fake_stack_start_out The extents of the fake stack which contains real VALUEs * \param[out] fake_stack_end_out The extents of the fake stack which contains real VALUEs * \return Whether slot is a pointer to a fake stack for the given machine stack range */ static inline bool asan_get_fake_stack_extents(void *thread_fake_stack_handle, VALUE slot, void *machine_stack_start, void *machine_stack_end, void **fake_stack_start_out, void **fake_stack_end_out) { /* the ifdef is needed here to suppress a warning about fake_frame_{start/end} being uninitialized if __asan_addr_is_in_fake_stack is an empty macro */ #ifdef RUBY_ASAN_ENABLED void *fake_frame_start; void *fake_frame_end; void *real_stack_frame = __asan_addr_is_in_fake_stack( thread_fake_stack_handle, (void *)slot, &fake_frame_start, &fake_frame_end ); if (real_stack_frame) { bool in_range; #if STACK_GROW_DIRECTION < 0 in_range = machine_stack_start >= real_stack_frame && real_stack_frame >= machine_stack_end; #else in_range = machine_stack_start <= real_stack_frame && real_stack_frame <= machine_stack_end; #endif if (in_range) { *fake_stack_start_out = fake_frame_start; *fake_stack_end_out = fake_frame_end; return true; } } #endif *fake_stack_start_out = 0; *fake_stack_end_out = 0; return false; } #endif /* INTERNAL_SANITIZERS_H */