xref: /freebsd/contrib/jemalloc/src/san.c (revision c43cad87172039ccf38172129c79755ea79e6102)
1*c43cad87SWarner Losh #include "jemalloc/internal/jemalloc_preamble.h"
2*c43cad87SWarner Losh #include "jemalloc/internal/jemalloc_internal_includes.h"
3*c43cad87SWarner Losh 
4*c43cad87SWarner Losh #include "jemalloc/internal/assert.h"
5*c43cad87SWarner Losh #include "jemalloc/internal/ehooks.h"
6*c43cad87SWarner Losh #include "jemalloc/internal/san.h"
7*c43cad87SWarner Losh #include "jemalloc/internal/tsd.h"
8*c43cad87SWarner Losh 
9*c43cad87SWarner Losh /* The sanitizer options. */
10*c43cad87SWarner Losh size_t opt_san_guard_large = SAN_GUARD_LARGE_EVERY_N_EXTENTS_DEFAULT;
11*c43cad87SWarner Losh size_t opt_san_guard_small = SAN_GUARD_SMALL_EVERY_N_EXTENTS_DEFAULT;
12*c43cad87SWarner Losh 
13*c43cad87SWarner Losh /* Aligned (-1 is off) ptrs will be junked & stashed on dealloc. */
14*c43cad87SWarner Losh ssize_t opt_lg_san_uaf_align = SAN_LG_UAF_ALIGN_DEFAULT;
15*c43cad87SWarner Losh 
16*c43cad87SWarner Losh /*
17*c43cad87SWarner Losh  *  Initialized in san_init().  When disabled, the mask is set to (uintptr_t)-1
18*c43cad87SWarner Losh  *  to always fail the nonfast_align check.
19*c43cad87SWarner Losh  */
20*c43cad87SWarner Losh uintptr_t san_cache_bin_nonfast_mask = SAN_CACHE_BIN_NONFAST_MASK_DEFAULT;
21*c43cad87SWarner Losh 
22*c43cad87SWarner Losh static inline void
23*c43cad87SWarner Losh san_find_guarded_addr(edata_t *edata, uintptr_t *guard1, uintptr_t *guard2,
24*c43cad87SWarner Losh     uintptr_t *addr, size_t size, bool left, bool right) {
25*c43cad87SWarner Losh 	assert(!edata_guarded_get(edata));
26*c43cad87SWarner Losh 	assert(size % PAGE == 0);
27*c43cad87SWarner Losh 	*addr = (uintptr_t)edata_base_get(edata);
28*c43cad87SWarner Losh 	if (left) {
29*c43cad87SWarner Losh 		*guard1 = *addr;
30*c43cad87SWarner Losh 		*addr += SAN_PAGE_GUARD;
31*c43cad87SWarner Losh 	} else {
32*c43cad87SWarner Losh 		*guard1 = 0;
33*c43cad87SWarner Losh 	}
34*c43cad87SWarner Losh 
35*c43cad87SWarner Losh 	if (right) {
36*c43cad87SWarner Losh 		*guard2 = *addr + size;
37*c43cad87SWarner Losh 	} else {
38*c43cad87SWarner Losh 		*guard2 = 0;
39*c43cad87SWarner Losh 	}
40*c43cad87SWarner Losh }
41*c43cad87SWarner Losh 
42*c43cad87SWarner Losh static inline void
43*c43cad87SWarner Losh san_find_unguarded_addr(edata_t *edata, uintptr_t *guard1, uintptr_t *guard2,
44*c43cad87SWarner Losh     uintptr_t *addr, size_t size, bool left, bool right) {
45*c43cad87SWarner Losh 	assert(edata_guarded_get(edata));
46*c43cad87SWarner Losh 	assert(size % PAGE == 0);
47*c43cad87SWarner Losh 	*addr = (uintptr_t)edata_base_get(edata);
48*c43cad87SWarner Losh 	if (right) {
49*c43cad87SWarner Losh 		*guard2 = *addr + size;
50*c43cad87SWarner Losh 	} else {
51*c43cad87SWarner Losh 		*guard2 = 0;
52*c43cad87SWarner Losh 	}
53*c43cad87SWarner Losh 
54*c43cad87SWarner Losh 	if (left) {
55*c43cad87SWarner Losh 		*guard1 = *addr - SAN_PAGE_GUARD;
56*c43cad87SWarner Losh 		assert(*guard1 != 0);
57*c43cad87SWarner Losh 		*addr = *guard1;
58*c43cad87SWarner Losh 	} else {
59*c43cad87SWarner Losh 		*guard1 = 0;
60*c43cad87SWarner Losh 	}
61*c43cad87SWarner Losh }
62*c43cad87SWarner Losh 
63*c43cad87SWarner Losh void
64*c43cad87SWarner Losh san_guard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap,
65*c43cad87SWarner Losh     bool left, bool right, bool remap) {
66*c43cad87SWarner Losh 	assert(left || right);
67*c43cad87SWarner Losh 	if (remap) {
68*c43cad87SWarner Losh 		emap_deregister_boundary(tsdn, emap, edata);
69*c43cad87SWarner Losh 	}
70*c43cad87SWarner Losh 
71*c43cad87SWarner Losh 	size_t size_with_guards = edata_size_get(edata);
72*c43cad87SWarner Losh 	size_t usize = (left && right)
73*c43cad87SWarner Losh 	    ? san_two_side_unguarded_sz(size_with_guards)
74*c43cad87SWarner Losh 	    : san_one_side_unguarded_sz(size_with_guards);
75*c43cad87SWarner Losh 
76*c43cad87SWarner Losh 	uintptr_t guard1, guard2, addr;
77*c43cad87SWarner Losh 	san_find_guarded_addr(edata, &guard1, &guard2, &addr, usize, left,
78*c43cad87SWarner Losh 	    right);
79*c43cad87SWarner Losh 
80*c43cad87SWarner Losh 	assert(edata_state_get(edata) == extent_state_active);
81*c43cad87SWarner Losh 	ehooks_guard(tsdn, ehooks, (void *)guard1, (void *)guard2);
82*c43cad87SWarner Losh 
83*c43cad87SWarner Losh 	/* Update the guarded addr and usable size of the edata. */
84*c43cad87SWarner Losh 	edata_size_set(edata, usize);
85*c43cad87SWarner Losh 	edata_addr_set(edata, (void *)addr);
86*c43cad87SWarner Losh 	edata_guarded_set(edata, true);
87*c43cad87SWarner Losh 
88*c43cad87SWarner Losh 	if (remap) {
89*c43cad87SWarner Losh 		emap_register_boundary(tsdn, emap, edata, SC_NSIZES,
90*c43cad87SWarner Losh 		    /* slab */ false);
91*c43cad87SWarner Losh 	}
92*c43cad87SWarner Losh }
93*c43cad87SWarner Losh 
94*c43cad87SWarner Losh static void
95*c43cad87SWarner Losh san_unguard_pages_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
96*c43cad87SWarner Losh     emap_t *emap, bool left, bool right, bool remap) {
97*c43cad87SWarner Losh 	assert(left || right);
98*c43cad87SWarner Losh 	/* Remove the inner boundary which no longer exists. */
99*c43cad87SWarner Losh 	if (remap) {
100*c43cad87SWarner Losh 		assert(edata_state_get(edata) == extent_state_active);
101*c43cad87SWarner Losh 		emap_deregister_boundary(tsdn, emap, edata);
102*c43cad87SWarner Losh 	} else {
103*c43cad87SWarner Losh 		assert(edata_state_get(edata) == extent_state_retained);
104*c43cad87SWarner Losh 	}
105*c43cad87SWarner Losh 
106*c43cad87SWarner Losh 	size_t size = edata_size_get(edata);
107*c43cad87SWarner Losh 	size_t size_with_guards = (left && right)
108*c43cad87SWarner Losh 	    ? san_two_side_guarded_sz(size)
109*c43cad87SWarner Losh 	    : san_one_side_guarded_sz(size);
110*c43cad87SWarner Losh 
111*c43cad87SWarner Losh 	uintptr_t guard1, guard2, addr;
112*c43cad87SWarner Losh 	san_find_unguarded_addr(edata, &guard1, &guard2, &addr, size, left,
113*c43cad87SWarner Losh 	    right);
114*c43cad87SWarner Losh 
115*c43cad87SWarner Losh 	ehooks_unguard(tsdn, ehooks, (void *)guard1, (void *)guard2);
116*c43cad87SWarner Losh 
117*c43cad87SWarner Losh 	/* Update the true addr and usable size of the edata. */
118*c43cad87SWarner Losh 	edata_size_set(edata, size_with_guards);
119*c43cad87SWarner Losh 	edata_addr_set(edata, (void *)addr);
120*c43cad87SWarner Losh 	edata_guarded_set(edata, false);
121*c43cad87SWarner Losh 
122*c43cad87SWarner Losh 	/*
123*c43cad87SWarner Losh 	 * Then re-register the outer boundary including the guards, if
124*c43cad87SWarner Losh 	 * requested.
125*c43cad87SWarner Losh 	 */
126*c43cad87SWarner Losh 	if (remap) {
127*c43cad87SWarner Losh 		emap_register_boundary(tsdn, emap, edata, SC_NSIZES,
128*c43cad87SWarner Losh 		    /* slab */ false);
129*c43cad87SWarner Losh 	}
130*c43cad87SWarner Losh }
131*c43cad87SWarner Losh 
132*c43cad87SWarner Losh void
133*c43cad87SWarner Losh san_unguard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
134*c43cad87SWarner Losh     emap_t *emap, bool left, bool right) {
135*c43cad87SWarner Losh 	san_unguard_pages_impl(tsdn, ehooks, edata, emap, left, right,
136*c43cad87SWarner Losh 	    /* remap */ true);
137*c43cad87SWarner Losh }
138*c43cad87SWarner Losh 
139*c43cad87SWarner Losh void
140*c43cad87SWarner Losh san_unguard_pages_pre_destroy(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
141*c43cad87SWarner Losh     emap_t *emap) {
142*c43cad87SWarner Losh 	emap_assert_not_mapped(tsdn, emap, edata);
143*c43cad87SWarner Losh 	/*
144*c43cad87SWarner Losh 	 * We don't want to touch the emap of about to be destroyed extents, as
145*c43cad87SWarner Losh 	 * they have been unmapped upon eviction from the retained ecache. Also,
146*c43cad87SWarner Losh 	 * we unguard the extents to the right, because retained extents only
147*c43cad87SWarner Losh 	 * own their right guard page per san_bump_alloc's logic.
148*c43cad87SWarner Losh 	 */
149*c43cad87SWarner Losh 	 san_unguard_pages_impl(tsdn, ehooks, edata, emap, /* left */ false,
150*c43cad87SWarner Losh 	    /* right */ true, /* remap */ false);
151*c43cad87SWarner Losh }
152*c43cad87SWarner Losh 
153*c43cad87SWarner Losh static bool
154*c43cad87SWarner Losh san_stashed_corrupted(void *ptr, size_t size) {
155*c43cad87SWarner Losh 	if (san_junk_ptr_should_slow()) {
156*c43cad87SWarner Losh 		for (size_t i = 0; i < size; i++) {
157*c43cad87SWarner Losh 			if (((char *)ptr)[i] != (char)uaf_detect_junk) {
158*c43cad87SWarner Losh 				return true;
159*c43cad87SWarner Losh 			}
160*c43cad87SWarner Losh 		}
161*c43cad87SWarner Losh 		return false;
162*c43cad87SWarner Losh 	}
163*c43cad87SWarner Losh 
164*c43cad87SWarner Losh 	void *first, *mid, *last;
165*c43cad87SWarner Losh 	san_junk_ptr_locations(ptr, size, &first, &mid, &last);
166*c43cad87SWarner Losh 	if (*(uintptr_t *)first != uaf_detect_junk ||
167*c43cad87SWarner Losh 	    *(uintptr_t *)mid != uaf_detect_junk ||
168*c43cad87SWarner Losh 	    *(uintptr_t *)last != uaf_detect_junk) {
169*c43cad87SWarner Losh 		return true;
170*c43cad87SWarner Losh 	}
171*c43cad87SWarner Losh 
172*c43cad87SWarner Losh 	return false;
173*c43cad87SWarner Losh }
174*c43cad87SWarner Losh 
175*c43cad87SWarner Losh void
176*c43cad87SWarner Losh san_check_stashed_ptrs(void **ptrs, size_t nstashed, size_t usize) {
177*c43cad87SWarner Losh 	/*
178*c43cad87SWarner Losh 	 * Verify that the junked-filled & stashed pointers remain unchanged, to
179*c43cad87SWarner Losh 	 * detect write-after-free.
180*c43cad87SWarner Losh 	 */
181*c43cad87SWarner Losh 	for (size_t n = 0; n < nstashed; n++) {
182*c43cad87SWarner Losh 		void *stashed = ptrs[n];
183*c43cad87SWarner Losh 		assert(stashed != NULL);
184*c43cad87SWarner Losh 		assert(cache_bin_nonfast_aligned(stashed));
185*c43cad87SWarner Losh 		if (unlikely(san_stashed_corrupted(stashed, usize))) {
186*c43cad87SWarner Losh 			safety_check_fail("<jemalloc>: Write-after-free "
187*c43cad87SWarner Losh 			    "detected on deallocated pointer %p (size %zu).\n",
188*c43cad87SWarner Losh 			    stashed, usize);
189*c43cad87SWarner Losh 		}
190*c43cad87SWarner Losh 	}
191*c43cad87SWarner Losh }
192*c43cad87SWarner Losh 
193*c43cad87SWarner Losh void
194*c43cad87SWarner Losh tsd_san_init(tsd_t *tsd) {
195*c43cad87SWarner Losh 	*tsd_san_extents_until_guard_smallp_get(tsd) = opt_san_guard_small;
196*c43cad87SWarner Losh 	*tsd_san_extents_until_guard_largep_get(tsd) = opt_san_guard_large;
197*c43cad87SWarner Losh }
198*c43cad87SWarner Losh 
199*c43cad87SWarner Losh void
200*c43cad87SWarner Losh san_init(ssize_t lg_san_uaf_align) {
201*c43cad87SWarner Losh 	assert(lg_san_uaf_align == -1 || lg_san_uaf_align >= LG_PAGE);
202*c43cad87SWarner Losh 	if (lg_san_uaf_align == -1) {
203*c43cad87SWarner Losh 		san_cache_bin_nonfast_mask = (uintptr_t)-1;
204*c43cad87SWarner Losh 		return;
205*c43cad87SWarner Losh 	}
206*c43cad87SWarner Losh 
207*c43cad87SWarner Losh 	san_cache_bin_nonfast_mask = ((uintptr_t)1 << lg_san_uaf_align) - 1;
208*c43cad87SWarner Losh }
209