Ruby 3.5.0dev (2025-07-26 revision abafb662ea7daff351a2aba1e145ae5df1664e82)
sanitizers.h
1#ifndef INTERNAL_SANITIZERS_H /*-*-C-*-vi:se ft=c:*/
2#define INTERNAL_SANITIZERS_H
11#include "ruby/internal/config.h"
12#include "internal/compilers.h" /* for __has_feature */
13
14#ifdef HAVE_VALGRIND_MEMCHECK_H
15# include <valgrind/memcheck.h>
16#endif
17
18#ifdef HAVE_SANITIZER_ASAN_INTERFACE_H
19# if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
20# define RUBY_ASAN_ENABLED
21# include <sanitizer/asan_interface.h>
22# endif
23#endif
24
25#ifdef HAVE_SANITIZER_MSAN_INTERFACE_H
26# if __has_feature(memory_sanitizer)
27# define RUBY_MSAN_ENABLED
28# include <sanitizer/msan_interface.h>
29# endif
30#endif
31
32#ifdef HAVE_SANITIZER_TSAN_INTERFACE_H
33# if __has_feature(thread_sanitizer) || defined(__SANITIZE_THREAD__)
34# define RUBY_TSAN_ENABLED
35# include <sanitizer/tsan_interface.h>
36# endif
37#endif
38
39#include "ruby/internal/stdbool.h" /* for bool */
40#include "ruby/ruby.h" /* for VALUE */
41
42#if 0
43#elif defined(RUBY_ASAN_ENABLED) && defined(RUBY_MSAN_ENABLED)
44# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
45 __attribute__((__no_sanitize__("memory, address"), __noinline__)) x
46#elif defined(RUBY_ASAN_ENABLED)
47# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
48 __attribute__((__no_sanitize__("address"), __noinline__)) x
49#elif defined(RUBY_MSAN_ENABLED)
50 # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
51 __attribute__((__no_sanitize__("memory"), __noinline__)) x
52#elif defined(RUBY_TSAN_ENABLED)
53# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
54 __attribute__((__no_sanitize__("thread"), __noinline__)) x
55#elif defined(NO_SANITIZE_ADDRESS)
56# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
57 NO_SANITIZE_ADDRESS(NOINLINE(x))
58#elif defined(NO_ADDRESS_SAFETY_ANALYSIS)
59# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
60 NO_ADDRESS_SAFETY_ANALYSIS(NOINLINE(x))
61#else
62# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) x
63#endif
64
65#if defined(NO_SANITIZE) && RBIMPL_COMPILER_IS(GCC)
66/* GCC warns about unknown sanitizer, which is annoying. */
67# include "internal/warnings.h"
68# undef NO_SANITIZE
69# define NO_SANITIZE(x, y) \
70 COMPILER_WARNING_PUSH \
71 COMPILER_WARNING_IGNORED(-Wattributes) \
72 __attribute__((__no_sanitize__(x))) y; \
73 COMPILER_WARNING_POP \
74 y
75#endif
76
77#ifndef NO_SANITIZE
78# define NO_SANITIZE(x, y) y
79#endif
80
81#ifndef RUBY_ASAN_ENABLED
82# define __asan_poison_memory_region(x, y)
83# define __asan_unpoison_memory_region(x, y)
84# define __asan_region_is_poisoned(x, y) 0
85# define __asan_get_current_fake_stack() NULL
86# define __asan_addr_is_in_fake_stack(fake_stack, slot, start, end) NULL
87#endif
88
89#ifndef RUBY_MSAN_ENABLED
90# define __msan_allocated_memory(x, y) ((void)(x), (void)(y))
91# define __msan_poison(x, y) ((void)(x), (void)(y))
92# define __msan_unpoison(x, y) ((void)(x), (void)(y))
93# define __msan_unpoison_string(x) ((void)(x))
94#endif
95
96#ifdef VALGRIND_MAKE_READABLE
97# define VALGRIND_MAKE_MEM_DEFINED(p, n) VALGRIND_MAKE_READABLE((p), (n))
98#endif
99
100#ifdef VALGRIND_MAKE_WRITABLE
101# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) VALGRIND_MAKE_WRITABLE((p), (n))
102#endif
103
104#ifndef VALGRIND_MAKE_MEM_DEFINED
105# define VALGRIND_MAKE_MEM_DEFINED(p, n) 0
106#endif
107
108#ifndef VALGRIND_MAKE_MEM_UNDEFINED
109# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0
110#endif
111
125static inline void
126asan_poison_memory_region(const volatile void *ptr, size_t size)
127{
128 __msan_poison(ptr, size);
129 __asan_poison_memory_region(ptr, size);
130}
131
132#ifdef RUBY_ASAN_ENABLED
133#define asan_poison_object_if(ptr, obj) do { \
134 if (ptr) rb_asan_poison_object(obj); \
135 } while (0)
136#else
137#define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj))
138#endif
139
140#ifdef RUBY_ASAN_ENABLED
141RUBY_SYMBOL_EXPORT_BEGIN
147void rb_asan_poison_object(VALUE obj);
148
156void *rb_asan_poisoned_object_p(VALUE obj);
157
164void rb_asan_unpoison_object(VALUE obj, bool newobj_p);
165
166RUBY_SYMBOL_EXPORT_END
167#else
168# define rb_asan_poison_object(obj) ((void)obj)
169# define rb_asan_poisoned_object_p(obj) ((void)obj, NULL)
170# define rb_asan_unpoison_object(obj, newobj_p) ((void)obj, (void)newobj_p)
171#endif
172
188static inline void
189asan_unpoison_memory_region(const volatile void *ptr, size_t size, bool malloc_p)
190{
191 __asan_unpoison_memory_region(ptr, size);
192 if (malloc_p) {
193 __msan_allocated_memory(ptr, size);
194 }
195 else {
196 __msan_unpoison(ptr, size);
197 }
198}
199
200static inline void *
201asan_unpoison_object_temporary(VALUE obj)
202{
203 void *ptr = rb_asan_poisoned_object_p(obj);
204 rb_asan_unpoison_object(obj, false);
205 return ptr;
206}
207
208static inline void *
209asan_poison_object_restore(VALUE obj, void *ptr)
210{
211 if (ptr) {
212 rb_asan_poison_object(obj);
213 }
214 return NULL;
215}
216
217#define asan_unpoisoning_object(obj) \
218 for (void *poisoned = asan_unpoison_object_temporary(obj), \
219 *unpoisoning = &poisoned; /* flag to loop just once */ \
220 unpoisoning; \
221 unpoisoning = asan_poison_object_restore(obj, poisoned))
222
223
224static inline void *
225asan_unpoison_memory_region_temporary(void *ptr, size_t len)
226{
227 void *poisoned_ptr = __asan_region_is_poisoned(ptr, len);
228 asan_unpoison_memory_region(ptr, len, false);
229 return poisoned_ptr;
230}
231
232static inline void *
233asan_poison_memory_region_restore(void *ptr, size_t len, void *poisoned_ptr)
234{
235 if (poisoned_ptr) {
236 asan_poison_memory_region(ptr, len);
237 }
238 return NULL;
239}
240
241#define asan_unpoisoning_memory_region(ptr, len) \
242 for (void *poisoned = asan_unpoison_memory_region_temporary(ptr, len), \
243 *unpoisoning = &poisoned; /* flag to loop just once */ \
244 unpoisoning; \
245 unpoisoning = asan_poison_memory_region_restore(ptr, len, poisoned))
246
262static inline void *
263asan_get_real_stack_addr(void* slot)
264{
265 VALUE *addr;
266 addr = __asan_addr_is_in_fake_stack(__asan_get_current_fake_stack(), slot, NULL, NULL);
267 return addr ? addr : slot;
268}
269
275static inline void *
276asan_get_thread_fake_stack_handle(void)
277{
278 return __asan_get_current_fake_stack();
279}
280
301static inline bool
302asan_get_fake_stack_extents(void *thread_fake_stack_handle, VALUE slot,
303 void *machine_stack_start, void *machine_stack_end,
304 void **fake_stack_start_out, void **fake_stack_end_out)
305{
306 /* the ifdef is needed here to suppress a warning about fake_frame_{start/end} being
307 uninitialized if __asan_addr_is_in_fake_stack is an empty macro */
308#ifdef RUBY_ASAN_ENABLED
309 void *fake_frame_start;
310 void *fake_frame_end;
311 void *real_stack_frame = __asan_addr_is_in_fake_stack(
312 thread_fake_stack_handle, (void *)slot, &fake_frame_start, &fake_frame_end
313 );
314 if (real_stack_frame) {
315 bool in_range;
316#if STACK_GROW_DIRECTION < 0
317 in_range = machine_stack_start >= real_stack_frame && real_stack_frame >= machine_stack_end;
318#else
319 in_range = machine_stack_start <= real_stack_frame && real_stack_frame <= machine_stack_end;
320#endif
321 if (in_range) {
322 *fake_stack_start_out = fake_frame_start;
323 *fake_stack_end_out = fake_frame_end;
324 return true;
325 }
326 }
327#endif
328 *fake_stack_start_out = 0;
329 *fake_stack_end_out = 0;
330 return false;
331}
332
333extern const char ruby_asan_default_options[];
334
335#ifdef RUBY_ASAN_ENABLED
336/* Compile in the ASAN options Ruby needs, rather than relying on environment variables, so
337 * that even tests which fork ruby with a clean environment will run ASAN with the right
338 * settings */
339# undef RUBY__ASAN_DEFAULT_OPTIONS
340# define RUBY__ASAN_DEFAULT_OPTIONS \
341 RBIMPL_SYMBOL_EXPORT_BEGIN() \
342 const char * __asan_default_options(void) {return ruby_asan_default_options;} \
343 RBIMPL_SYMBOL_EXPORT_END()
344#endif
345
346#endif /* INTERNAL_SANITIZERS_H */
int len
Length of the buffer.
Definition io.h:8
C99 shim for <stdbool.h>
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40