summaryrefslogtreecommitdiff
path: root/internal/sanitizers.h
blob: b0eb1fc851b415abda224f71c76484263c735049 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#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)
#  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

#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(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
#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);
}

/**
 * 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);
}

#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.
 */
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);
}

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 */