Skip to content

Commit 03bb718

Browse files
committed
performance fixes to sanity debug paths
1 parent 4f9dabd commit 03bb718

6 files changed

Lines changed: 100 additions & 21 deletions

File tree

include/conf.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@
4242
#endif
4343

4444
/* If you're using the UAF_PTR_PAGE functionality and
45-
* want to change the frequency it is triggered or the
46-
* magic value that is written */
45+
* want to change the frequency it is triggered */
4746
#if UAF_PTR_PAGE
48-
#define UAF_PTR_PAGE_ODDS 1000000
47+
#define UAF_PTR_PAGE_ODDS 250000
4948
#endif
5049

5150
/* Zones can be retired after a certain number of

include/iso_alloc_internal.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,9 @@ INTERNAL_HIDDEN ASSUME_ALIGNED void *_iso_big_alloc(size_t size);
418418
INTERNAL_HIDDEN ASSUME_ALIGNED void *_iso_alloc(iso_alloc_zone_t *zone, size_t size);
419419
INTERNAL_HIDDEN INLINE ASSUME_ALIGNED void *_iso_alloc_bitslot_from_zone(bit_slot_t bitslot, iso_alloc_zone_t *zone);
420420
INTERNAL_HIDDEN ASSUME_ALIGNED void *_iso_calloc(size_t nmemb, size_t size);
421-
INTERNAL_HIDDEN void *_iso_alloc_ptr_search(void *n, bool poison);
421+
#if UAF_PTR_PAGE
422+
INTERNAL_HIDDEN void *_iso_alloc_ptr_search(void *n);
423+
#endif
422424
INTERNAL_HIDDEN INLINE uint64_t us_rand_uint64(uint64_t *seed);
423425
INTERNAL_HIDDEN INLINE uint64_t rand_uint64(void);
424426
INTERNAL_HIDDEN uint8_t _iso_alloc_get_mem_tag(void *p, iso_alloc_zone_t *zone);

src/iso_alloc.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1727,7 +1727,7 @@ INTERNAL_HIDDEN iso_alloc_zone_t *_iso_free_internal_unlocked(void *p, bool perm
17271727

17281728
#if UAF_PTR_PAGE
17291729
if(UNLIKELY((us_rand_uint64(&_root->seed) % UAF_PTR_PAGE_ODDS) == 1)) {
1730-
_iso_alloc_ptr_search(p, true);
1730+
_iso_alloc_ptr_search(p);
17311731
}
17321732
#endif
17331733

src/iso_alloc_sanity.c

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,18 @@ INTERNAL_HIDDEN void *_page_fault_thread_handler(void *unused) {
285285

286286
INTERNAL_HIDDEN INLINE void write_sanity_canary(void *p) {
287287
const uint64_t canary = (_sanity_canary & SANITY_CANARY_VALIDATE_MASK);
288+
int32_t qwords = (int32_t) (g_page_size / sizeof(uint64_t));
288289

289-
for(int32_t i = 0; i < (g_page_size / sizeof(uint64_t)); i++) {
290+
#if USE_NEON
291+
const uint64x2_t cv = vdupq_n_u64(canary);
292+
while(qwords >= 2) {
293+
vst1q_u64((uint64_t *) p, cv);
294+
p += sizeof(uint64x2_t);
295+
qwords -= 2;
296+
}
297+
#endif
298+
299+
while(qwords--) {
290300
*(uint64_t *) p = canary;
291301
p += sizeof(uint64_t);
292302
}
@@ -305,16 +315,53 @@ INTERNAL_HIDDEN INLINE void check_sanity_canary(_sane_allocation_t *sane_alloc)
305315
start = sane_alloc->address + sane_alloc->orig_size;
306316
}
307317

308-
while(start < end) {
318+
const uint64_t canary = (_sanity_canary & SANITY_CANARY_VALIDATE_MASK);
319+
320+
/* orig_size is unaligned in general, so [start, end) may begin or
321+
* end mid-qword. The expected byte at offset (p & 7) is that byte
322+
* within the canary qword, since write_sanity_canary stored qwords
323+
* starting at the page base. Walk the partial bytes at the head
324+
* until start is 8-aligned. */
325+
while(start < end && ((uintptr_t) start & 7)) {
326+
uint8_t expected = (uint8_t) (canary >> (((uintptr_t) start & 7) << 3));
327+
if(UNLIKELY(*(uint8_t *) start != expected)) {
328+
LOG_AND_ABORT("Sanity canary byte at 0x%p has been corrupted! Value: 0x%x Expected: 0x%x", start, *(uint8_t *) start, expected);
329+
}
330+
start++;
331+
}
332+
333+
#if USE_NEON
334+
/* Compare two qwords at a time and reduce. On any mismatch, fall
335+
* through to the scalar loop which will pinpoint and abort. */
336+
const uint64x2_t cv = vdupq_n_u64(canary);
337+
while((start + sizeof(uint64x2_t)) <= end) {
338+
uint64x2_t v = vld1q_u64((const uint64_t *) start);
339+
if(UNLIKELY(vmaxvq_u32(vreinterpretq_u32_u64(veorq_u64(v, cv))) != 0)) {
340+
break;
341+
}
342+
start += sizeof(uint64x2_t);
343+
}
344+
#endif
345+
346+
while((start + sizeof(uint64_t)) <= end) {
309347
uint64_t v = *((uint64_t *) start);
310-
uint64_t canary = (_sanity_canary & SANITY_CANARY_VALIDATE_MASK);
311348

312349
if(UNLIKELY(v != canary)) {
313350
LOG_AND_ABORT("Sanity canary at 0x%p has been corrupted! Value: 0x%x Expected: 0x%x", start, v, canary);
314351
}
315352

316353
start += sizeof(uint64_t);
317354
}
355+
356+
/* Tail: 0–7 partial bytes when end isn't 8-aligned (right-aligned
357+
* sample with unaligned orig_size). */
358+
while(start < end) {
359+
uint8_t expected = (uint8_t) (canary >> (((uintptr_t) start & 7) << 3));
360+
if(UNLIKELY(*(uint8_t *) start != expected)) {
361+
LOG_AND_ABORT("Sanity canary byte at 0x%p has been corrupted! Value: 0x%x Expected: 0x%x", start, *(uint8_t *) start, expected);
362+
}
363+
start++;
364+
}
318365
}
319366

320367
/* Callers of this function should hold the sanity cache lock */

src/iso_alloc_search.c

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
#include "iso_alloc_internal.h"
55

6-
/* Search all zones for either the first instance of a pointer
7-
* value and return it or overwrite the first potentially
8-
* dangling pointer with the address of an unmapped page */
9-
INTERNAL_HIDDEN void *_iso_alloc_ptr_search(void *n, bool poison) {
6+
#if UAF_PTR_PAGE
7+
/* Search all zones for the first 8-byte sequence equal to n and overwrite
8+
* it with the address of the PROT_NONE uaf_ptr_page so the next deref
9+
* faults at a known address. Sampled from the free path. */
10+
INTERNAL_HIDDEN void *_iso_alloc_ptr_search(void *n) {
1011
uint8_t *search = NULL;
1112
uint8_t *end = NULL;
1213
const size_t zones_used = _root->zones_used;
@@ -17,30 +18,58 @@ INTERNAL_HIDDEN void *_iso_alloc_ptr_search(void *n, bool poison) {
1718
n = (void *) ((uintptr_t) n & TAGGED_PTR_MASK);
1819
#endif
1920

21+
#if USE_NEON
22+
/* Per-call invariants — n is fixed for the entire search, so broadcast
23+
* the two pre-filter bytes once instead of once per zone. */
24+
const uint8x16_t b0 = vdupq_n_u8((uint8_t) (uintptr_t) n);
25+
const uint8x16_t b1 = vdupq_n_u8((uint8_t) ((uintptr_t) n >> 8));
26+
#endif
27+
2028
for(int32_t i = 0; i < zones_used; i++) {
2129
iso_alloc_zone_t *zone = &_root->zones[i];
2230

2331
search = UNMASK_USER_PTR(zone);
2432
end = search + ZONE_USER_SIZE;
2533

26-
while(search <= (uint8_t *) (end - sizeof(uint64_t))) {
27-
if(LIKELY((uint64_t) * (uint64_t *) search != (uint64_t) n)) {
28-
search++;
29-
} else {
30-
if(poison == false) {
31-
return search;
32-
} else {
33-
#if UAF_PTR_PAGE
34+
#if USE_NEON
35+
/* A u64 at byte position k can match n only if bytes[k] == n[0]
36+
* AND bytes[k+1] == n[1]. AND the two shifted byte-equality
37+
* vectors before reducing — collapses false positives 256x and
38+
* keeps the filter useful when chunks are filled with POISON_BYTE
39+
* (which would defeat a single-byte filter when n[0] == 0xde).
40+
* Stop 23 bytes before end so the last candidate u64 read fits. */
41+
uint8_t *neon_end = end - 23;
42+
while(search <= neon_end) {
43+
uint8x16_t eq = vandq_u8(vceqq_u8(vld1q_u8(search), b0),
44+
vceqq_u8(vld1q_u8(search + 1), b1));
45+
if(LIKELY(vmaxvq_u8(eq) == 0)) {
46+
search += 16;
47+
continue;
48+
}
49+
uint8_t *win_end = search + 16;
50+
while(search < win_end) {
51+
if(UNLIKELY(*(uint64_t *) search == (uint64_t) n)) {
3452
*(uint64_t *) search = (uint64_t) (_root->uaf_ptr_page);
3553
return search;
36-
#endif
3754
}
55+
search++;
56+
}
57+
}
58+
#endif
59+
60+
uint8_t *tail_end = end - sizeof(uint64_t);
61+
while(search <= tail_end) {
62+
if(UNLIKELY((uint64_t) * (uint64_t *) search == (uint64_t) n)) {
63+
*(uint64_t *) search = (uint64_t) (_root->uaf_ptr_page);
64+
return search;
3865
}
66+
search++;
3967
}
4068
}
4169

4270
return NULL;
4371
}
72+
#endif
4473

4574
#if EXPERIMENTAL
4675
/* These functions are all experimental and subject to change */

utils/run_tests.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ tests=("tests" "big_tests" "interfaces_test" "thread_tests" "pool_test"
77
"rand_freelist")
88
failure=0
99
succeeded=0
10+
start=$SECONDS
1011

1112
$(ulimit -c 0)
1213

@@ -52,6 +53,7 @@ done
5253

5354
echo "$succeeded Tests passed"
5455
echo "$failure Tests failed"
56+
echo "Total runtime: $((SECONDS - start))s"
5557

5658
unset LD_LIBRARY_PATH
5759
unset LD_PRELOAD

0 commit comments

Comments
 (0)