summaryrefslogtreecommitdiff
path: root/deps/jemalloc/include/jemalloc/internal/san.h
blob: 8813d6bbe7dee77d39af8e397aa1e89148271204 (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
#ifndef JEMALLOC_INTERNAL_GUARD_H
#define JEMALLOC_INTERNAL_GUARD_H

#include "jemalloc/internal/ehooks.h"
#include "jemalloc/internal/emap.h"

#define SAN_PAGE_GUARD PAGE
#define SAN_PAGE_GUARDS_SIZE (SAN_PAGE_GUARD * 2)

#define SAN_GUARD_LARGE_EVERY_N_EXTENTS_DEFAULT 0
#define SAN_GUARD_SMALL_EVERY_N_EXTENTS_DEFAULT 0

#define SAN_LG_UAF_ALIGN_DEFAULT (-1)
#define SAN_CACHE_BIN_NONFAST_MASK_DEFAULT (uintptr_t)(-1)

static const uintptr_t uaf_detect_junk = (uintptr_t)0x5b5b5b5b5b5b5b5bULL;

/* 0 means disabled, i.e. never guarded. */
extern size_t opt_san_guard_large;
extern size_t opt_san_guard_small;
/* -1 means disabled, i.e. never check for use-after-free. */
extern ssize_t opt_lg_san_uaf_align;

void san_guard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
    emap_t *emap, bool left, bool right, bool remap);
void san_unguard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
    emap_t *emap, bool left, bool right);
/*
 * Unguard the extent, but don't modify emap boundaries. Must be called on an
 * extent that has been erased from emap and shouldn't be placed back.
 */
void san_unguard_pages_pre_destroy(tsdn_t *tsdn, ehooks_t *ehooks,
    edata_t *edata, emap_t *emap);
void san_check_stashed_ptrs(void **ptrs, size_t nstashed, size_t usize);

void tsd_san_init(tsd_t *tsd);
void san_init(ssize_t lg_san_uaf_align);

static inline void
san_guard_pages_two_sided(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
    emap_t *emap, bool remap) {
	san_guard_pages(tsdn, ehooks, edata, emap, true, true, remap);
}

static inline void
san_unguard_pages_two_sided(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
    emap_t *emap) {
	san_unguard_pages(tsdn, ehooks, edata, emap, true, true);
}

static inline size_t
san_two_side_unguarded_sz(size_t size) {
	assert(size % PAGE == 0);
	assert(size >= SAN_PAGE_GUARDS_SIZE);
	return size - SAN_PAGE_GUARDS_SIZE;
}

static inline size_t
san_two_side_guarded_sz(size_t size) {
	assert(size % PAGE == 0);
	return size + SAN_PAGE_GUARDS_SIZE;
}

static inline size_t
san_one_side_unguarded_sz(size_t size) {
	assert(size % PAGE == 0);
	assert(size >= SAN_PAGE_GUARD);
	return size - SAN_PAGE_GUARD;
}

static inline size_t
san_one_side_guarded_sz(size_t size) {
	assert(size % PAGE == 0);
	return size + SAN_PAGE_GUARD;
}

static inline bool
san_guard_enabled(void) {
	return (opt_san_guard_large != 0 || opt_san_guard_small != 0);
}

static inline bool
san_large_extent_decide_guard(tsdn_t *tsdn, ehooks_t *ehooks, size_t size,
    size_t alignment) {
	if (opt_san_guard_large == 0 || ehooks_guard_will_fail(ehooks) ||
	    tsdn_null(tsdn)) {
		return false;
	}

	tsd_t *tsd = tsdn_tsd(tsdn);
	uint64_t n = tsd_san_extents_until_guard_large_get(tsd);
	assert(n >= 1);
	if (n > 1) {
		/*
		 * Subtract conditionally because the guard may not happen due
		 * to alignment or size restriction below.
		 */
		*tsd_san_extents_until_guard_largep_get(tsd) = n - 1;
	}

	if (n == 1 && (alignment <= PAGE) &&
	    (san_two_side_guarded_sz(size) <= SC_LARGE_MAXCLASS)) {
		*tsd_san_extents_until_guard_largep_get(tsd) =
		    opt_san_guard_large;
		return true;
	} else {
		assert(tsd_san_extents_until_guard_large_get(tsd) >= 1);
		return false;
	}
}

static inline bool
san_slab_extent_decide_guard(tsdn_t *tsdn, ehooks_t *ehooks) {
	if (opt_san_guard_small == 0 || ehooks_guard_will_fail(ehooks) ||
	    tsdn_null(tsdn)) {
		return false;
	}

	tsd_t *tsd = tsdn_tsd(tsdn);
	uint64_t n = tsd_san_extents_until_guard_small_get(tsd);
	assert(n >= 1);
	if (n == 1) {
		*tsd_san_extents_until_guard_smallp_get(tsd) =
		    opt_san_guard_small;
		return true;
	} else {
		*tsd_san_extents_until_guard_smallp_get(tsd) = n - 1;
		assert(tsd_san_extents_until_guard_small_get(tsd) >= 1);
		return false;
	}
}

static inline void
san_junk_ptr_locations(void *ptr, size_t usize, void **first, void **mid,
    void **last) {
	size_t ptr_sz = sizeof(void *);

	*first = ptr;

	*mid = (void *)((uintptr_t)ptr + ((usize >> 1) & ~(ptr_sz - 1)));
	assert(*first != *mid || usize == ptr_sz);
	assert((uintptr_t)*first <= (uintptr_t)*mid);

	/*
	 * When usize > 32K, the gap between requested_size and usize might be
	 * greater than 4K -- this means the last write may access an
	 * likely-untouched page (default settings w/ 4K pages).  However by
	 * default the tcache only goes up to the 32K size class, and is usually
	 * tuned lower instead of higher, which makes it less of a concern.
	 */
	*last = (void *)((uintptr_t)ptr + usize - sizeof(uaf_detect_junk));
	assert(*first != *last || usize == ptr_sz);
	assert(*mid != *last || usize <= ptr_sz * 2);
	assert((uintptr_t)*mid <= (uintptr_t)*last);
}

static inline bool
san_junk_ptr_should_slow(void) {
	/*
	 * The latter condition (pointer size greater than the min size class)
	 * is not expected -- fall back to the slow path for simplicity.
	 */
	return config_debug || (LG_SIZEOF_PTR > SC_LG_TINY_MIN);
}

static inline void
san_junk_ptr(void *ptr, size_t usize) {
	if (san_junk_ptr_should_slow()) {
		memset(ptr, (char)uaf_detect_junk, usize);
		return;
	}

	void *first, *mid, *last;
	san_junk_ptr_locations(ptr, usize, &first, &mid, &last);
	*(uintptr_t *)first = uaf_detect_junk;
	*(uintptr_t *)mid = uaf_detect_junk;
	*(uintptr_t *)last = uaf_detect_junk;
}

static inline bool
san_uaf_detection_enabled(void) {
	bool ret = config_uaf_detection && (opt_lg_san_uaf_align != -1);
	if (config_uaf_detection && ret) {
		assert(san_cache_bin_nonfast_mask == ((uintptr_t)1 <<
		    opt_lg_san_uaf_align) - 1);
	}

	return ret;
}

#endif /* JEMALLOC_INTERNAL_GUARD_H */