Ruby 3.5.0dev (2025-07-04 revision 38993efb27a35b37ecb938f7791fa7c51fbf4bac)
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#include "ruby/internal/stdbool.h" /* for bool */
33#include "ruby/ruby.h" /* for VALUE */
34
35#if 0
36#elif defined(RUBY_ASAN_ENABLED) && defined(RUBY_MSAN_ENABLED)
37# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
38 __attribute__((__no_sanitize__("memory, address"), __noinline__)) x
39#elif defined(RUBY_ASAN_ENABLED)
40# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
41 __attribute__((__no_sanitize__("address"), __noinline__)) x
42#elif defined(RUBY_MSAN_ENABLED)
43 # define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
44 __attribute__((__no_sanitize__("memory"), __noinline__)) x
45#elif defined(NO_SANITIZE_ADDRESS)
46# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
47 NO_SANITIZE_ADDRESS(NOINLINE(x))
48#elif defined(NO_ADDRESS_SAFETY_ANALYSIS)
49# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) \
50 NO_ADDRESS_SAFETY_ANALYSIS(NOINLINE(x))
51#else
52# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(x) x
53#endif
54
55#if defined(NO_SANITIZE) && RBIMPL_COMPILER_IS(GCC)
56/* GCC warns about unknown sanitizer, which is annoying. */
57# include "internal/warnings.h"
58# undef NO_SANITIZE
59# define NO_SANITIZE(x, y) \
60 COMPILER_WARNING_PUSH \
61 COMPILER_WARNING_IGNORED(-Wattributes) \
62 __attribute__((__no_sanitize__(x))) y; \
63 COMPILER_WARNING_POP \
64 y
65#endif
66
67#ifndef NO_SANITIZE
68# define NO_SANITIZE(x, y) y
69#endif
70
71#ifndef RUBY_ASAN_ENABLED
72# define __asan_poison_memory_region(x, y)
73# define __asan_unpoison_memory_region(x, y)
74# define __asan_region_is_poisoned(x, y) 0
75# define __asan_get_current_fake_stack() NULL
76# define __asan_addr_is_in_fake_stack(fake_stack, slot, start, end) NULL
77#endif
78
79#ifndef RUBY_MSAN_ENABLED
80# define __msan_allocated_memory(x, y) ((void)(x), (void)(y))
81# define __msan_poison(x, y) ((void)(x), (void)(y))
82# define __msan_unpoison(x, y) ((void)(x), (void)(y))
83# define __msan_unpoison_string(x) ((void)(x))
84#endif
85
86#ifdef VALGRIND_MAKE_READABLE
87# define VALGRIND_MAKE_MEM_DEFINED(p, n) VALGRIND_MAKE_READABLE((p), (n))
88#endif
89
90#ifdef VALGRIND_MAKE_WRITABLE
91# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) VALGRIND_MAKE_WRITABLE((p), (n))
92#endif
93
94#ifndef VALGRIND_MAKE_MEM_DEFINED
95# define VALGRIND_MAKE_MEM_DEFINED(p, n) 0
96#endif
97
98#ifndef VALGRIND_MAKE_MEM_UNDEFINED
99# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0
100#endif
101
115static inline void
116asan_poison_memory_region(const volatile void *ptr, size_t size)
117{
118 __msan_poison(ptr, size);
119 __asan_poison_memory_region(ptr, size);
120}
121
122#ifdef RUBY_ASAN_ENABLED
123#define asan_poison_object_if(ptr, obj) do { \
124 if (ptr) rb_asan_poison_object(obj); \
125 } while (0)
126#else
127#define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj))
128#endif
129
130#ifdef RUBY_ASAN_ENABLED
131RUBY_SYMBOL_EXPORT_BEGIN
137void rb_asan_poison_object(VALUE obj);
138
146void *rb_asan_poisoned_object_p(VALUE obj);
147
154void rb_asan_unpoison_object(VALUE obj, bool newobj_p);
155
156RUBY_SYMBOL_EXPORT_END
157#else
158# define rb_asan_poison_object(obj) ((void)obj)
159# define rb_asan_poisoned_object_p(obj) ((void)obj, NULL)
160# define rb_asan_unpoison_object(obj, newobj_p) ((void)obj, (void)newobj_p)
161#endif
162
178static inline void
179asan_unpoison_memory_region(const volatile void *ptr, size_t size, bool malloc_p)
180{
181 __asan_unpoison_memory_region(ptr, size);
182 if (malloc_p) {
183 __msan_allocated_memory(ptr, size);
184 }
185 else {
186 __msan_unpoison(ptr, size);
187 }
188}
189
190static inline void *
191asan_unpoison_object_temporary(VALUE obj)
192{
193 void *ptr = rb_asan_poisoned_object_p(obj);
194 rb_asan_unpoison_object(obj, false);
195 return ptr;
196}
197
198static inline void *
199asan_poison_object_restore(VALUE obj, void *ptr)
200{
201 if (ptr) {
202 rb_asan_poison_object(obj);
203 }
204 return NULL;
205}
206
207#define asan_unpoisoning_object(obj) \
208 for (void *poisoned = asan_unpoison_object_temporary(obj), \
209 *unpoisoning = &poisoned; /* flag to loop just once */ \
210 unpoisoning; \
211 unpoisoning = asan_poison_object_restore(obj, poisoned))
212
213
214static inline void *
215asan_unpoison_memory_region_temporary(void *ptr, size_t len)
216{
217 void *poisoned_ptr = __asan_region_is_poisoned(ptr, len);
218 asan_unpoison_memory_region(ptr, len, false);
219 return poisoned_ptr;
220}
221
222static inline void *
223asan_poison_memory_region_restore(void *ptr, size_t len, void *poisoned_ptr)
224{
225 if (poisoned_ptr) {
226 asan_poison_memory_region(ptr, len);
227 }
228 return NULL;
229}
230
231#define asan_unpoisoning_memory_region(ptr, len) \
232 for (void *poisoned = asan_unpoison_memory_region_temporary(ptr, len), \
233 *unpoisoning = &poisoned; /* flag to loop just once */ \
234 unpoisoning; \
235 unpoisoning = asan_poison_memory_region_restore(ptr, len, poisoned))
236
252static inline void *
253asan_get_real_stack_addr(void* slot)
254{
255 VALUE *addr;
256 addr = __asan_addr_is_in_fake_stack(__asan_get_current_fake_stack(), slot, NULL, NULL);
257 return addr ? addr : slot;
258}
259
265static inline void *
266asan_get_thread_fake_stack_handle(void)
267{
268 return __asan_get_current_fake_stack();
269}
270
291static inline bool
292asan_get_fake_stack_extents(void *thread_fake_stack_handle, VALUE slot,
293 void *machine_stack_start, void *machine_stack_end,
294 void **fake_stack_start_out, void **fake_stack_end_out)
295{
296 /* the ifdef is needed here to suppress a warning about fake_frame_{start/end} being
297 uninitialized if __asan_addr_is_in_fake_stack is an empty macro */
298#ifdef RUBY_ASAN_ENABLED
299 void *fake_frame_start;
300 void *fake_frame_end;
301 void *real_stack_frame = __asan_addr_is_in_fake_stack(
302 thread_fake_stack_handle, (void *)slot, &fake_frame_start, &fake_frame_end
303 );
304 if (real_stack_frame) {
305 bool in_range;
306#if STACK_GROW_DIRECTION < 0
307 in_range = machine_stack_start >= real_stack_frame && real_stack_frame >= machine_stack_end;
308#else
309 in_range = machine_stack_start <= real_stack_frame && real_stack_frame <= machine_stack_end;
310#endif
311 if (in_range) {
312 *fake_stack_start_out = fake_frame_start;
313 *fake_stack_end_out = fake_frame_end;
314 return true;
315 }
316 }
317#endif
318 *fake_stack_start_out = 0;
319 *fake_stack_end_out = 0;
320 return false;
321}
322
323extern const char ruby_asan_default_options[];
324
325#ifdef RUBY_ASAN_ENABLED
326/* Compile in the ASAN options Ruby needs, rather than relying on environment variables, so
327 * that even tests which fork ruby with a clean environment will run ASAN with the right
328 * settings */
329# undef RUBY__ASAN_DEFAULT_OPTIONS
330# define RUBY__ASAN_DEFAULT_OPTIONS \
331 RBIMPL_SYMBOL_EXPORT_BEGIN() \
332 const char * __asan_default_options(void) {return ruby_asan_default_options;} \
333 RBIMPL_SYMBOL_EXPORT_END()
334#endif
335
336#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