kcov: move thread attach from open to start

The current kcov code attaches a kcov object to the thread which opened
it. This is not desirable, as syzkaller opens /dev/kcov in a parent
process and passes it down to the child.

Move the thread attachment from open() time to start time.
This commit is contained in:
Matheus
2026-10-08 14:41:34 -03:00
parent d999ab7de2
commit 9a9740469f
4 changed files with 46 additions and 53 deletions
+3 -3
View File
@@ -22,7 +22,7 @@
#define KCOV_CMP_FLAGS_64BIT 6 #define KCOV_CMP_FLAGS_64BIT 6
typedef struct { typedef struct {
unsigned int mode; unsigned int mode; // transitions protected by mutex
mutex_t mapping_mutex; mutex_t mapping_mutex;
unsigned int entry_count; // protected by mutex unsigned int entry_count; // protected by mutex
uint64_t *buffer; // pointer protected by mutex uint64_t *buffer; // pointer protected by mutex
@@ -34,8 +34,8 @@ typedef struct {
void kcov_publish_pc(uint64_t pc); void kcov_publish_pc(uint64_t pc);
void kcov_publish_cmp(uint64_t pc, uint64_t v1, uint64_t v2, uint64_t flags); void kcov_publish_cmp(uint64_t pc, uint64_t v1, uint64_t v2, uint64_t flags);
int kcov_set_buffer_size(unsigned int entry_count); int kcov_set_buffer_size(kcov_t *kcov, unsigned int entry_count);
int kcov_set_mode(unsigned int mode); int kcov_set_mode(kcov_t *kcov, unsigned int mode);
void kcov_hold(kcov_t *kcov); void kcov_hold(kcov_t *kcov);
void kcov_release(kcov_t *kcov); void kcov_release(kcov_t *kcov);
void kcov_free_buffer(kcov_t *kcov); void kcov_free_buffer(kcov_t *kcov);
+30 -13
View File
@@ -69,8 +69,7 @@ KCOV_DISABLED static int resize_buffer(kcov_t *kcov, unsigned int entry_count) {
return 0; return 0;
} }
KCOV_DISABLED int kcov_set_mode(unsigned int mode) { KCOV_DISABLED int kcov_set_mode(kcov_t *kcov, unsigned int mode) {
kcov_t *kcov = current_thread()->kcov;
__assert(kcov); __assert(kcov);
if (mode != KCOV_MODE_DISABLED && if (mode != KCOV_MODE_DISABLED &&
@@ -78,26 +77,44 @@ KCOV_DISABLED int kcov_set_mode(unsigned int mode) {
mode != KCOV_MODE_CMP) mode != KCOV_MODE_CMP)
return EINVAL; return EINVAL;
if (mode == KCOV_MODE_DISABLED && kcov->mode == KCOV_MODE_DISABLED) MUTEX_ACQUIRE(&kcov->mapping_mutex);
return EINVAL; int error = 0;
if (mode == KCOV_MODE_DISABLED) {
if (current_thread()->kcov != kcov) {
error = EINVAL;
goto cleanup;
}
if (mode != KCOV_MODE_DISABLED && kcov->mode != KCOV_MODE_DISABLED) current_thread()->kcov = NULL;
return EINVAL; } else {
if (current_thread()->kcov || kcov->mode != KCOV_MODE_DISABLED) {
error = EBUSY;
goto cleanup;
}
if (kcov->buffer == NULL) if (kcov->buffer == NULL) {
return EINVAL; error = EINVAL;
goto cleanup;
}
kcov_hold(kcov);
}
kcov->mode = mode; kcov->mode = mode;
return 0; if (mode != KCOV_MODE_DISABLED)
current_thread()->kcov = kcov;
cleanup:
MUTEX_RELEASE(&kcov->mapping_mutex);
if (error == 0 && mode == KCOV_MODE_DISABLED)
kcov_release(kcov);
return error;
} }
KCOV_DISABLED int kcov_set_buffer_size(unsigned int entry_count) { KCOV_DISABLED int kcov_set_buffer_size(kcov_t *kcov, unsigned int entry_count) {
if (entry_count < 0 || entry_count > MAX_ENTRIES) if (entry_count < 0 || entry_count > MAX_ENTRIES)
return EINVAL; return EINVAL;
kcov_t *kcov = current_thread()->kcov;
__assert(kcov);
MUTEX_ACQUIRE(&kcov->mapping_mutex); MUTEX_ACQUIRE(&kcov->mapping_mutex);
int error = 0; int error = 0;
if (kcov->mappings || kcov->mode != KCOV_MODE_DISABLED) { if (kcov->mappings || kcov->mode != KCOV_MODE_DISABLED) {
+8 -28
View File
@@ -89,10 +89,6 @@ static int create_node_for_kcov(kcov_t *kcov, int minor, vnode_t **vnodep) {
kcov->vnode_handle = vnode; kcov->vnode_handle = vnode;
devfs_remove_anonymous(DEV_MAJOR_KCOV, minor); devfs_remove_anonymous(DEV_MAJOR_KCOV, minor);
// a kcov will have two references initially:
// - one for the thread holding it
// - one for the vnode
kcov_hold(kcov);
*vnodep = vnode; *vnodep = vnode;
return 0; return 0;
} }
@@ -103,18 +99,6 @@ static int open(int oldminor, vnode_t **vnodep, int flags) {
if (oldminor != KCOV_MAX) if (oldminor != KCOV_MAX)
return ENODEV; return ENODEV;
// if the thread already has a kcov attached, return it
if (current_thread()->kcov) {
kcov_t *kcov = current_thread()->kcov;
int error = create_node_for_kcov(kcov, kcov->minor, vnodep);
if (error)
return error;
VOP_RELEASE(old);
return 0;
}
int minor; int minor;
int error = allocate_kcov(&minor); int error = allocate_kcov(&minor);
if (error) if (error)
@@ -127,7 +111,6 @@ static int open(int oldminor, vnode_t **vnodep, int flags) {
return error; return error;
} }
current_thread()->kcov = kcov;
VOP_RELEASE(old); VOP_RELEASE(old);
return 0; return 0;
} }
@@ -137,8 +120,8 @@ static int open(int oldminor, vnode_t **vnodep, int flags) {
#define IOCTL_REQUEST_SET_SIZE 0xF1177200 #define IOCTL_REQUEST_SET_SIZE 0xF1177200
static int ioctl(int minor, unsigned long request, void *arg, int *result, cred_t *cred) { static int ioctl(int minor, unsigned long request, void *arg, int *result, cred_t *cred) {
kcov_t *kcov = current_thread()->kcov; kcov_t *kcov = get_kcov(minor);
if (kcov == NULL || kcov->minor != minor) if (kcov == NULL)
return EINVAL; return EINVAL;
switch (request) { switch (request) {
@@ -148,24 +131,21 @@ static int ioctl(int minor, unsigned long request, void *arg, int *result, cred_
if (error) if (error)
return error; return error;
return kcov_set_buffer_size(size); return kcov_set_buffer_size(kcov, size);
} }
case IOCTL_REQUEST_START: { case IOCTL_REQUEST_START: {
if (kcov->mode != KCOV_MODE_DISABLED)
return EBUSY;
if (kcov->buffer == NULL)
return EINVAL;
int mode; int mode;
int error = USERCOPY_POSSIBLY_FROM_USER(&mode, arg, sizeof(mode)); int error = USERCOPY_POSSIBLY_FROM_USER(&mode, arg, sizeof(mode));
if (error) if (error)
return error; return error;
return kcov_set_mode(mode); if (mode != KCOV_MODE_PC && mode != KCOV_MODE_CMP)
return EINVAL;
return kcov_set_mode(kcov, mode);
} }
case IOCTL_REQUEST_STOP: case IOCTL_REQUEST_STOP:
return kcov_set_mode(KCOV_MODE_DISABLED); return kcov_set_mode(kcov, KCOV_MODE_DISABLED);
default: default:
return ENOTTY; return ENOTTY;
} }
+5 -9
View File
@@ -57,10 +57,6 @@ thread_t *sched_newthread(void *ip, size_t kstacksize, int nice, proc_t *proc, v
} }
void sched_destroythread(thread_t *thread) { void sched_destroythread(thread_t *thread) {
#ifdef KCOV_ENABLED
if (thread->kcov)
kcov_release(thread->kcov);
#endif
mm_unmap(thread->kernelstack, thread->kernelstacksize, 0); mm_unmap(thread->kernelstack, thread->kernelstacksize, 0);
arch_extracontext_free(&thread->extracontext); arch_extracontext_free(&thread->extracontext);
slab_free(thread_cache, thread); slab_free(thread_cache, thread);
@@ -88,6 +84,11 @@ __attribute__((noreturn)) void sched_threadexit() {
thread_t *thread = current_thread(); thread_t *thread = current_thread();
proc_t *proc = thread->proc; proc_t *proc = thread->proc;
#ifdef KCOV_ENABLED
if (thread->kcov)
kcov_set_mode(thread->kcov, KCOV_MODE_DISABLED);
#endif
mm_context_t *oldctx = thread->mmctx; mm_context_t *oldctx = thread->mmctx;
mm_switch_context(&mm_kernel_ctx); mm_switch_context(&mm_kernel_ctx);
@@ -105,11 +106,6 @@ __attribute__((noreturn)) void sched_threadexit() {
PROC_RELEASE(proc); PROC_RELEASE(proc);
} }
#ifdef KCOV_ENABLED
if (current_thread()->kcov)
kcov_set_mode(KCOV_MODE_DISABLED);
#endif
interrupt_set(false); interrupt_set(false);
arch_context_saveandcall(threadexit_internal, current_cpu()->schedulerstack, NULL); arch_context_saveandcall(threadexit_internal, current_cpu()->schedulerstack, NULL);
__builtin_unreachable(); __builtin_unreachable();