From 77ff614fa0319f7b4e99df29822d0590128bf00c Mon Sep 17 00:00:00 2001 From: Alexander Potapenko Date: Fri, 1 Aug 2025 12:12:42 +0200 Subject: executor: decouple kcov memory allocation from the trace On different platforms and in different coverage collection modes the pointer to the beginning of kcov buffer may or may not differ from the pointer to the region that mmap() returned. Decouple these two pointers, so that the memory is always allocated and deallocated with cov->mmap_alloc_ptr and cov->mmap_alloc_size, and the buffer is accessed via cov->data and cov->data_size. I tried my best to not break Darwin and BSD, but I did not test them. --- executor/executor.cc | 8 ++++++++ executor/executor_bsd.h | 22 ++++++++++++---------- executor/executor_darwin.h | 3 ++- executor/executor_linux.h | 38 ++++++++++++++++++++------------------ executor/executor_test.h | 18 ++++++++++-------- 5 files changed, 52 insertions(+), 37 deletions(-) diff --git a/executor/executor.cc b/executor/executor.cc index fb73efc5c..704a284a9 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -348,8 +348,16 @@ struct call_t { struct cover_t { int fd; uint32 size; + // mmap_alloc_ptr is the internal pointer to KCOV mapping, possibly with guard pages. + // It is only used to allocate/deallocate the buffer of mmap_alloc_size. + char* mmap_alloc_ptr; uint32 mmap_alloc_size; + // data is the pointer to the kcov buffer containing the recorded PCs. + // data may differ from mmap_alloc_ptr. char* data; + // data_size is set by cover_open(). This is the requested kcov buffer size. + uint32 data_size; + // data_end is simply data + data_size. char* data_end; // Currently collecting comparisons. bool collect_comps; diff --git a/executor/executor_bsd.h b/executor/executor_bsd.h index b7cbdd4f5..88e1f46cb 100644 --- a/executor/executor_bsd.h +++ b/executor/executor_bsd.h @@ -71,7 +71,7 @@ static void cover_open(cover_t* cov, bool extra) #if GOOS_freebsd if (ioctl(cov->fd, KIOSETBUFSIZE, kCoverSize)) fail("ioctl init trace write failed"); - cov->mmap_alloc_size = kCoverSize * KCOV_ENTRY_SIZE; + cov->data_size = kCoverSize * KCOV_ENTRY_SIZE; #elif GOOS_openbsd unsigned long cover_size = kCoverSize; if (ioctl(cov->fd, KIOSETBUFSIZE, &cover_size)) @@ -83,7 +83,7 @@ static void cover_open(cover_t* cov, bool extra) if (ioctl(cov->fd, KIOREMOTEATTACH, &args)) fail("ioctl remote attach failed"); } - cov->mmap_alloc_size = kCoverSize * (is_kernel_64_bit ? 8 : 4); + cov->data_size = kCoverSize * (is_kernel_64_bit ? 8 : 4); #elif GOOS_netbsd uint64_t cover_size; if (extra) { @@ -100,18 +100,20 @@ static void cover_open(cover_t* cov, bool extra) if (ioctl(cov->fd, KCOV_IOC_SETBUFSIZE, &cover_size)) fail("ioctl init trace write failed"); } - cov->mmap_alloc_size = cover_size * KCOV_ENTRY_SIZE; + cov->data_size = cover_size * KCOV_ENTRY_SIZE; #endif } static void cover_mmap(cover_t* cov) { - if (cov->data != NULL) + if (cov->mmap_alloc_ptr != NULL) fail("cover_mmap invoked on an already mmapped cover_t object"); + cov->mmap_alloc_size = cov->data_size; void* mmap_ptr = mmap(NULL, cov->mmap_alloc_size, PROT_READ | PROT_WRITE, MAP_SHARED, cov->fd, 0); if (mmap_ptr == MAP_FAILED) fail("cover mmap failed"); + cov->mmap_alloc_ptr = (char*)mmap_ptr; cov->data = (char*)mmap_ptr; cov->data_end = cov->data + cov->mmap_alloc_size; cov->data_offset = is_kernel_64_bit ? sizeof(uint64_t) : sizeof(uint32_t); @@ -120,13 +122,13 @@ static void cover_mmap(cover_t* cov) static void cover_protect(cover_t* cov) { - if (cov->data == NULL) + if (cov->mmap_alloc_ptr == NULL) fail("cover_protect invoked on an unmapped cover_t object"); #if GOOS_freebsd size_t mmap_alloc_size = kCoverSize * KCOV_ENTRY_SIZE; long page_size = sysconf(_SC_PAGESIZE); if (page_size > 0) - mprotect(cov->data + page_size, mmap_alloc_size - page_size, + mprotect(cov->mmap_alloc_ptr + page_size, mmap_alloc_size - page_size, PROT_READ); #elif GOOS_openbsd int mib[2], page_size; @@ -135,20 +137,20 @@ static void cover_protect(cover_t* cov) mib[1] = HW_PAGESIZE; size_t len = sizeof(page_size); if (sysctl(mib, ARRAY_SIZE(mib), &page_size, &len, NULL, 0) != -1) - mprotect(cov->data + page_size, mmap_alloc_size - page_size, PROT_READ); + mprotect(cov->mmap_alloc_ptr + page_size, mmap_alloc_size - page_size, PROT_READ); #endif } static void cover_unprotect(cover_t* cov) { - if (cov->data == NULL) + if (cov->mmap_alloc_ptr == NULL) fail("cover_unprotect invoked on an unmapped cover_t object"); #if GOOS_freebsd size_t mmap_alloc_size = kCoverSize * KCOV_ENTRY_SIZE; - mprotect(cov->data, mmap_alloc_size, PROT_READ | PROT_WRITE); + mprotect(cov->mmap_alloc_ptr, mmap_alloc_size, PROT_READ | PROT_WRITE); #elif GOOS_openbsd size_t mmap_alloc_size = kCoverSize * sizeof(uintptr_t); - mprotect(cov->data, mmap_alloc_size, PROT_READ | PROT_WRITE); + mprotect(cov->mmap_alloc_ptr, mmap_alloc_size, PROT_READ | PROT_WRITE); #endif } diff --git a/executor/executor_darwin.h b/executor/executor_darwin.h index bb7956c20..d555eeb02 100644 --- a/executor/executor_darwin.h +++ b/executor/executor_darwin.h @@ -73,7 +73,7 @@ static void cover_open(cover_t* cov, bool extra) static void cover_mmap(cover_t* cov) { - if (cov->data != NULL) + if (cov->mmap_alloc_ptr != NULL) fail("cover_mmap invoked on an already mmapped cover_t object"); uintptr_t mmap_ptr = 0; if (ksancov_map(cov->fd, &mmap_ptr, &cov->mmap_alloc_size)) @@ -84,6 +84,7 @@ static void cover_mmap(cover_t* cov) if (cov->mmap_alloc_size > kCoverSize) fail("mmap allocation size larger than anticipated"); + cov->mmap_alloc_ptr = (char*)mmap_ptr; cov->data = (char*)mmap_ptr; cov->data_end = cov->data + cov->mmap_alloc_size; } diff --git a/executor/executor_linux.h b/executor/executor_linux.h index f882b9c40..a5a073ba0 100644 --- a/executor/executor_linux.h +++ b/executor/executor_linux.h @@ -114,7 +114,7 @@ static void cover_open(cover_t* cov, bool extra) : kCoverSize; if (ioctl(cov->fd, kcov_init_trace, cover_size)) fail("cover init trace write failed"); - cov->mmap_alloc_size = cover_size * (is_kernel_64_bit ? 8 : 4); + cov->data_size = cover_size * (is_kernel_64_bit ? 8 : 4); if (pkeys_enabled) debug("pkey protection enabled\n"); } @@ -133,35 +133,37 @@ static void cover_unprotect(cover_t* cov) static void cover_mmap(cover_t* cov) { - if (cov->data != NULL) + if (cov->mmap_alloc_ptr != NULL) fail("cover_mmap invoked on an already mmapped cover_t object"); - if (cov->mmap_alloc_size == 0) + if (cov->data_size == 0) fail("cover_t structure is corrupted"); // Allocate kcov buffer plus two guard pages surrounding it. - char* mapped = (char*)mmap(NULL, cov->mmap_alloc_size + 2 * SYZ_PAGE_SIZE, - PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (mapped == MAP_FAILED) + cov->mmap_alloc_size = cov->data_size + 2 * SYZ_PAGE_SIZE; + cov->mmap_alloc_ptr = (char*)mmap(NULL, cov->mmap_alloc_size, + PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (cov->mmap_alloc_ptr == MAP_FAILED) exitf("failed to preallocate kcov buffer"); // Now map the kcov buffer to the file, overwriting the existing mapping above. int prot = flag_read_only_coverage ? PROT_READ : (PROT_READ | PROT_WRITE); - cov->data = (char*)mmap(mapped + SYZ_PAGE_SIZE, cov->mmap_alloc_size, - prot, MAP_SHARED | MAP_FIXED, cov->fd, 0); - if (cov->data == MAP_FAILED) + void* data_buf = (char*)mmap(cov->mmap_alloc_ptr + SYZ_PAGE_SIZE, cov->data_size, + prot, MAP_SHARED | MAP_FIXED, cov->fd, 0); + if (data_buf == MAP_FAILED) exitf("cover mmap failed"); - if (pkeys_enabled && pkey_mprotect(cov->data, cov->mmap_alloc_size, prot, RESERVED_PKEY)) + if (pkeys_enabled && pkey_mprotect(data_buf, cov->data_size, prot, RESERVED_PKEY)) exitf("failed to pkey_mprotect kcov buffer"); - cov->data_end = cov->data + cov->mmap_alloc_size; + cov->data = (char*)data_buf; + cov->data_end = cov->data + cov->data_size; cov->data_offset = is_kernel_64_bit ? sizeof(uint64_t) : sizeof(uint32_t); cov->pc_offset = 0; } static void cover_munmap(cover_t* cov) { - if (cov->data == NULL) + if (cov->mmap_alloc_ptr == NULL) fail("cover_munmap invoked on a non-mmapped cover_t object"); - if (munmap(cov->data - SYZ_PAGE_SIZE, cov->mmap_alloc_size + 2 * SYZ_PAGE_SIZE)) + if (munmap(cov->mmap_alloc_ptr, cov->mmap_alloc_size)) fail("cover_munmap failed"); - cov->data = NULL; + cov->mmap_alloc_ptr = NULL; } static void cover_enable(cover_t* cov, bool collect_comps, bool extra) @@ -303,8 +305,8 @@ static const char* setup_delay_kcov() cov.fd = kCoverFd; cover_open(&cov, false); cover_mmap(&cov); - char* first = cov.data; - cov.data = nullptr; + char* first = cov.mmap_alloc_ptr; + cov.mmap_alloc_ptr = nullptr; cover_mmap(&cov); // If delayed kcov mmap is not supported by the kernel, // accesses to the second mapping will crash. @@ -316,9 +318,9 @@ static const char* setup_delay_kcov() fail("clock_gettime failed"); error = "kernel commit b3d7fe86fbd0 is not present"; } else { - munmap(cov.data - SYZ_PAGE_SIZE, cov.mmap_alloc_size + 2 * SYZ_PAGE_SIZE); + munmap(cov.mmap_alloc_ptr, cov.mmap_alloc_size); } - munmap(first - SYZ_PAGE_SIZE, cov.mmap_alloc_size + 2 * SYZ_PAGE_SIZE); + munmap(first, cov.mmap_alloc_size); close(cov.fd); return error; } diff --git a/executor/executor_test.h b/executor/executor_test.h index e2a2002bb..d8471e6b5 100644 --- a/executor/executor_test.h +++ b/executor/executor_test.h @@ -75,7 +75,7 @@ static intptr_t execute_syscall(const call_t* c, intptr_t a[kMaxArgs]) static void cover_open(cover_t* cov, bool extra) { - cov->mmap_alloc_size = kCoverSize * sizeof(unsigned long); + cov->data_size = kCoverSize * sizeof(unsigned long); } static void cover_enable(cover_t* cov, bool collect_comps, bool extra) @@ -102,14 +102,16 @@ static void cover_protect(cover_t* cov) static void cover_mmap(cover_t* cov) { - if (cov->data != NULL) + if (cov->mmap_alloc_ptr != NULL) fail("cover_mmap invoked on an already mmapped cover_t object"); - if (cov->mmap_alloc_size == 0) + if (cov->data_size == 0) fail("cover_t structure is corrupted"); - cov->data = (char*)mmap(NULL, cov->mmap_alloc_size, - PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (cov->data == MAP_FAILED) + cov->mmap_alloc_size = cov->data_size; + cov->mmap_alloc_ptr = (char*)mmap(NULL, cov->mmap_alloc_size, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (cov->mmap_alloc_ptr == MAP_FAILED) exitf("cover mmap failed"); + cov->data = cov->mmap_alloc_ptr; cov->data_end = cov->data + cov->mmap_alloc_size; cov->data_offset = is_kernel_64_bit ? sizeof(uint64_t) : sizeof(uint32_t); // We don't care about the specific PC values for now. @@ -125,9 +127,9 @@ static long inject_cover(cover_t* cov, long a, long b) { if (cov->data == nullptr) return ENOENT; - uint32 size = std::min((uint32)b, cov->mmap_alloc_size); + uint32 size = std::min((uint32)b, cov->data_size); memcpy(cov->data, (void*)a, size); - memset(cov->data + size, 0xcd, std::min(100, cov->mmap_alloc_size - size)); + memset(cov->data + size, 0xcd, std::min(100, cov->data_size - size)); return 0; } -- cgit mrf-deployment