diff options
| author | Alexander Potapenko <glider@google.com> | 2025-07-30 10:44:47 +0200 |
|---|---|---|
| committer | Alexander Potapenko <glider@google.com> | 2025-07-31 12:27:19 +0000 |
| commit | 9a518853aaea13e0a60411b7be7d3ff1f05962de (patch) | |
| tree | 646ae2c9dc6f44b86ddf90fe2c90a89b7236fe06 /executor | |
| parent | dc769bad4c765a3c7b54150be90664e7a01caf40 (diff) | |
pkg/flatrpc, pkg/vminfo, executor: introduce readonly coverage
Add a new vminfo feature, FeatureKcovResetIoctl, that is true if the
kernel supports ioctl(KCOV_RESET_TRACE) making it possible to reset the
coverage buffer on the kernel side. This, in turn, allows us to map the
coverage buffer read-only, which will prevent all sorts of
userspace-generated corruptions at a cost of an extra syscall per program
execution.
The corresponding exec env flag, ExecEnv::ReadOnlyCoverage, turns on
read-only coverage in the executor. It is enabled by default
if FeatureKcovResetIoctl is on.
Diffstat (limited to 'executor')
| -rw-r--r-- | executor/executor.cc | 2 | ||||
| -rw-r--r-- | executor/executor_linux.h | 51 |
2 files changed, 48 insertions, 5 deletions
diff --git a/executor/executor.cc b/executor/executor.cc index a262bff83..25fba22e7 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -257,6 +257,7 @@ static uint64 start_time_ms = 0; static bool flag_debug; static bool flag_snapshot; static bool flag_coverage; +static bool flag_read_only_coverage; static bool flag_sandbox_none; static bool flag_sandbox_setuid; static bool flag_sandbox_namespace; @@ -777,6 +778,7 @@ void parse_handshake(const handshake_req& req) slowdown_scale = req.slowdown_scale; flag_debug = (bool)(req.flags & rpc::ExecEnv::Debug); flag_coverage = (bool)(req.flags & rpc::ExecEnv::Signal); + flag_read_only_coverage = (bool)(req.flags & rpc::ExecEnv::ReadOnlyCoverage); flag_sandbox_none = (bool)(req.flags & rpc::ExecEnv::SandboxNone); flag_sandbox_setuid = (bool)(req.flags & rpc::ExecEnv::SandboxSetuid); flag_sandbox_namespace = (bool)(req.flags & rpc::ExecEnv::SandboxNamespace); diff --git a/executor/executor_linux.h b/executor/executor_linux.h index 0ae1155bb..f882b9c40 100644 --- a/executor/executor_linux.h +++ b/executor/executor_linux.h @@ -37,6 +37,7 @@ struct kcov_remote_arg { #define KCOV_ENABLE _IO('c', 100) #define KCOV_DISABLE _IO('c', 101) #define KCOV_REMOTE_ENABLE _IOW('c', 102, kcov_remote_arg<0>) +#define KCOV_RESET_TRACE _IO('c', 104) #define KCOV_SUBSYSTEM_COMMON (0x00ull << 56) #define KCOV_SUBSYSTEM_USB (0x01ull << 56) @@ -142,17 +143,27 @@ static void cover_mmap(cover_t* cov) if (mapped == 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_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, cov->fd, 0); + prot, MAP_SHARED | MAP_FIXED, cov->fd, 0); if (cov->data == MAP_FAILED) exitf("cover mmap failed"); - if (pkeys_enabled && pkey_mprotect(cov->data, cov->mmap_alloc_size, PROT_READ | PROT_WRITE, RESERVED_PKEY)) + if (pkeys_enabled && pkey_mprotect(cov->data, cov->mmap_alloc_size, prot, RESERVED_PKEY)) exitf("failed to pkey_mprotect kcov buffer"); cov->data_end = cov->data + cov->mmap_alloc_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) + 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)) + fail("cover_munmap failed"); + cov->data = NULL; +} + static void cover_enable(cover_t* cov, bool collect_comps, bool extra) { unsigned int kcov_mode = collect_comps ? KCOV_TRACE_CMP : KCOV_TRACE_PC; @@ -186,9 +197,14 @@ static void cover_reset(cover_t* cov) fail("cover_reset: current_thread == 0"); cov = ¤t_thread->cov; } - cover_unprotect(cov); - *(uint64*)cov->data = 0; - cover_protect(cov); + if (flag_read_only_coverage) { + if (ioctl(cov->fd, KCOV_RESET_TRACE, 0)) + fail("KCOV_RESET_TRACE failed"); + } else { + cover_unprotect(cov); + *(uint64*)cov->data = 0; + cover_protect(cov); + } cov->overflow = false; } @@ -307,9 +323,34 @@ static const char* setup_delay_kcov() return error; } +static const char* setup_kcov_reset_ioctl() +{ + int fd = open("/sys/kernel/debug/kcov", O_RDWR); + if (fd == -1) + return "open of /sys/kernel/debug/kcov failed"; + cover_t cov = {}; + cov.fd = kCoverFd; + cover_open(&cov, false); + cover_mmap(&cov); + const char* error = NULL; + cover_enable(&cov, false, false); + int ret; + if ((ret = ioctl(cov.fd, KCOV_RESET_TRACE, 0))) { + if (errno != ENOTTY) { + fprintf(stderr, "ret: %d, errno: %d\n", ret, errno); + fail("ioctl(KCOV_RESET_TRACE) failed"); + } + error = "kernel does not support ioctl(KCOV_RESET_TRACE)"; + } + cover_munmap(&cov); + close(cov.fd); + return error; +} + #define SYZ_HAVE_FEATURES 1 static feature_t features[] = { {rpc::Feature::DelayKcovMmap, setup_delay_kcov}, + {rpc::Feature::KcovResetIoctl, setup_kcov_reset_ioctl}, {rpc::Feature::Fault, setup_fault}, {rpc::Feature::Leak, setup_leak}, {rpc::Feature::KCSAN, setup_kcsan}, |
