#ifndef INTERNAL_SANITIZERS_H /*-*-C-*-vi:se ft=c:*/ #define INTERNAL_SANITIZERS_H /** * @file * @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/3/config.h" #include "internal/compilers.h" /* for __has_feature */ #ifdef HAVE_VALGRIND_MEMCHECK_H # include #endif #ifdef HAVE_SANITIZER_ASAN_INTERFACE_H # include #endif #ifdef HAVE_SANITIZER_MSAN_INTERFACE_H # if __has_feature(memory_sanitizer) # include # endif #endif #include "ruby/3/stdbool.h" /* for bool */ #include "ruby/ruby.h" /* for VALUE */ #if 0 #elif __has_feature(memory_sanitizer) && __has_feature(address_sanitizer) # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \ __attribute__((__no_sanitize__("memory, address"), __noinline__)) x #elif __has_feature(address_sanitizer) # 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) && RUBY3_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 #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 #if !__has_feature(memory_sanitizer) # 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 #ifndef MJIT_HEADER /*! * 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); } #if !__has_feature(address_sanitizer) #define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj)) #else #define asan_poison_object_if(ptr, obj) do { \ if (ptr) asan_poison_object(obj); \ } while (0) #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); } #endif /* MJIT_HEADER */ #endif /* INTERNAL_SANITIZERS_H */