summaryrefslogtreecommitdiff
path: root/internal/sanitizers.h
diff options
context:
space:
mode:
Diffstat (limited to 'internal/sanitizers.h')
-rw-r--r--internal/sanitizers.h346
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 */