diff options
Diffstat (limited to 'internal/sanitizers.h')
-rw-r--r-- | internal/sanitizers.h | 159 |
1 files changed, 135 insertions, 24 deletions
diff --git a/internal/sanitizers.h b/internal/sanitizers.h index 7b7d166c74..b0eb1fc851 100644 --- a/internal/sanitizers.h +++ b/internal/sanitizers.h @@ -16,11 +16,15 @@ #endif #ifdef HAVE_SANITIZER_ASAN_INTERFACE_H -# include <sanitizer/asan_interface.h> +# if __has_feature(address_sanitizer) +# define RUBY_ASAN_ENABLED +# include <sanitizer/asan_interface.h> +# endif #endif #ifdef HAVE_SANITIZER_MSAN_INTERFACE_H # if __has_feature(memory_sanitizer) +# define RUBY_MSAN_ENABLED # include <sanitizer/msan_interface.h> # endif #endif @@ -29,10 +33,10 @@ #include "ruby/ruby.h" /* for VALUE */ #if 0 -#elif __has_feature(memory_sanitizer) && __has_feature(address_sanitizer) +#elif defined(RUBY_ASAN_ENABLED) && defined(RUBY_MSAN_ENABLED) # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \ __attribute__((__no_sanitize__("memory, address"), __noinline__)) x -#elif __has_feature(address_sanitizer) +#elif defined(RUBY_ASAN_ENABLED) # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \ __attribute__((__no_sanitize__("address"), __noinline__)) x #elif defined(NO_SANITIZE_ADDRESS) @@ -60,13 +64,15 @@ # define NO_SANITIZE(x, y) y #endif -#if !__has_feature(address_sanitizer) +#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 -#if !__has_feature(memory_sanitizer) +#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)) @@ -89,7 +95,7 @@ # 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 @@ -99,8 +105,8 @@ * 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. + * @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) @@ -109,10 +115,10 @@ asan_poison_memory_region(const volatile void *ptr, size_t 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. + * @param[in] obj target object. */ static inline void asan_poison_object(VALUE obj) @@ -121,20 +127,20 @@ asan_poison_object(VALUE obj) asan_poison_memory_region(ptr, SIZEOF_VALUE); } -#if !__has_feature(address_sanitizer) -#define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj)) -#else +#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. + * @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) @@ -143,7 +149,7 @@ asan_poisoned_object_p(VALUE 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, @@ -154,9 +160,9 @@ asan_poisoned_object_p(VALUE obj) * 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. + * @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) @@ -170,11 +176,11 @@ asan_unpoison_memory_region(const volatile void *ptr, size_t size, bool malloc_p } } -/*! +/** * 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. + * @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) @@ -183,4 +189,109 @@ asan_unpoison_object(VALUE obj, bool newobj_p) 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 */ |