diff options
Diffstat (limited to 'internal/sanitizers.h')
| -rw-r--r-- | internal/sanitizers.h | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/internal/sanitizers.h b/internal/sanitizers.h new file mode 100644 index 0000000000..feafb4e616 --- /dev/null +++ b/internal/sanitizers.h @@ -0,0 +1,346 @@ +#ifndef INTERNAL_SANITIZERS_H /*-*-C-*-vi:se ft=c:*/ +#define INTERNAL_SANITIZERS_H +/** + * @author Ruby developers <ruby-core@ruby-lang.org> + * @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 <valgrind/memcheck.h> +#endif + +#ifdef HAVE_SANITIZER_ASAN_INTERFACE_H +# if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +# 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 + +#ifdef HAVE_SANITIZER_TSAN_INTERFACE_H +# if __has_feature(thread_sanitizer) || defined(__SANITIZE_THREAD__) +# define RUBY_TSAN_ENABLED +# include <sanitizer/tsan_interface.h> +# 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(RUBY_MSAN_ENABLED) + # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \ + __attribute__((__no_sanitize__("memory"), __noinline__)) x +#elif defined(RUBY_TSAN_ENABLED) +# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \ + __attribute__((__no_sanitize__("thread"), __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 \ + y +#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); +} + +#ifdef RUBY_ASAN_ENABLED +#define asan_poison_object_if(ptr, obj) do { \ + if (ptr) rb_asan_poison_object(obj); \ + } while (0) +#else +#define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj)) +#endif + +#ifdef RUBY_ASAN_ENABLED +RUBY_SYMBOL_EXPORT_BEGIN +/** + * This is a variant of asan_poison_memory_region that takes a VALUE. + * + * @param[in] obj target object. + */ +void rb_asan_poison_object(VALUE obj); + +/** + * 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. + */ +void *rb_asan_poisoned_object_p(VALUE obj); + +/** + * 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. + */ +void rb_asan_unpoison_object(VALUE obj, bool newobj_p); + +RUBY_SYMBOL_EXPORT_END +#else +# define rb_asan_poison_object(obj) ((void)obj) +# define rb_asan_poisoned_object_p(obj) ((void)obj, NULL) +# define rb_asan_unpoison_object(obj, newobj_p) ((void)obj, (void)newobj_p) +#endif + +/** + * 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); + } +} + +static inline void * +asan_unpoison_object_temporary(VALUE obj) +{ + void *ptr = rb_asan_poisoned_object_p(obj); + rb_asan_unpoison_object(obj, false); + return ptr; +} + +static inline void * +asan_poison_object_restore(VALUE obj, void *ptr) +{ + if (ptr) { + rb_asan_poison_object(obj); + } + return NULL; +} + +#define asan_unpoisoning_object(obj) \ + for (void *poisoned = asan_unpoison_object_temporary(obj), \ + *unpoisoning = &poisoned; /* flag to loop just once */ \ + unpoisoning; \ + unpoisoning = asan_poison_object_restore(obj, poisoned)) + + +static inline void * +asan_unpoison_memory_region_temporary(void *ptr, size_t len) +{ + void *poisoned_ptr = __asan_region_is_poisoned(ptr, len); + asan_unpoison_memory_region(ptr, len, false); + return poisoned_ptr; +} + +static inline void * +asan_poison_memory_region_restore(void *ptr, size_t len, void *poisoned_ptr) +{ + if (poisoned_ptr) { + asan_poison_memory_region(ptr, len); + } + return NULL; +} + +#define asan_unpoisoning_memory_region(ptr, len) \ + for (void *poisoned = asan_unpoison_memory_region_temporary(ptr, len), \ + *unpoisoning = &poisoned; /* flag to loop just once */ \ + unpoisoning; \ + unpoisoning = asan_poison_memory_region_restore(ptr, len, poisoned)) + +/** + * 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; +} + +extern const char ruby_asan_default_options[]; + +#ifdef RUBY_ASAN_ENABLED +/* Compile in the ASAN options Ruby needs, rather than relying on environment variables, so + * that even tests which fork ruby with a clean environment will run ASAN with the right + * settings */ +# undef RUBY__ASAN_DEFAULT_OPTIONS +# define RUBY__ASAN_DEFAULT_OPTIONS \ + RBIMPL_SYMBOL_EXPORT_BEGIN() \ + const char * __asan_default_options(void) {return ruby_asan_default_options;} \ + RBIMPL_SYMBOL_EXPORT_END() +#endif + +#endif /* INTERNAL_SANITIZERS_H */ |
