summaryrefslogtreecommitdiff
path: root/internal/sanitizers.h
diff options
context:
space:
mode:
Diffstat (limited to 'internal/sanitizers.h')
-rw-r--r--internal/sanitizers.h162
1 files changed, 134 insertions, 28 deletions
diff --git a/internal/sanitizers.h b/internal/sanitizers.h
index b4f5bdeda1..b0eb1fc851 100644
--- a/internal/sanitizers.h
+++ b/internal/sanitizers.h
@@ -1,7 +1,6 @@
#ifndef INTERNAL_SANITIZERS_H /*-*-C-*-vi:se ft=c:*/
#define INTERNAL_SANITIZERS_H
/**
- * @file
* @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
@@ -17,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
@@ -30,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)
@@ -61,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))
@@ -90,9 +95,7 @@
# 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
@@ -102,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)
@@ -112,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)
@@ -124,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)
@@ -146,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,
@@ -157,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)
@@ -173,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)
@@ -186,6 +189,109 @@ asan_unpoison_object(VALUE obj, bool newobj_p)
asan_unpoison_memory_region(ptr, SIZEOF_VALUE, newobj_p);
}
-#endif /* MJIT_HEADER */
+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 */