Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sancov guard draft #7

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Documentation/dev-tools/kcov.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,54 @@ mmaps coverage buffer, and then forks child processes in a loop. The child
processes only need to enable coverage (it gets disabled automatically when
a thread exits).

Unique coverage collection
---------------------------

Instead of collecting raw PCs, KCOV can deduplicate them on the fly.
This mode is enabled by the ``KCOV_UNIQUE_ENABLE`` ioctl (only available if
``CONFIG_KCOV_ENABLE_GUARDS`` is on).

.. code-block:: c
/* Same includes and defines as above. */
#define KCOV_UNIQUE_ENABLE _IOW('c', 103, unsigned long)
#define BITMAP_SIZE (4<<10)
/* Instead of KCOV_ENABLE, enable unique coverage collection. */
if (ioctl(fd, KCOV_UNIQUE_ENABLE, BITMAP_SIZE))
perror("ioctl"), exit(1);
/* Reset the coverage from the tail of the ioctl() call. */
__atomic_store_n(&cover[BITMAP_SIZE], 0, __ATOMIC_RELAXED);
memset(cover, 0, BITMAP_SIZE * sizeof(unsigned long));
/* Call the target syscall call. */
/* ... */
/* Read the number of collected PCs. */
n = __atomic_load_n(&cover[BITMAP_SIZE], __ATOMIC_RELAXED);
/* Disable the coverage collection. */
if (ioctl(fd, KCOV_DISABLE, 0))
perror("ioctl"), exit(1);
Calling ``ioctl(fd, KCOV_UNIQUE_ENABLE, bitmap_size)`` carves out ``bitmap_size``
words from those allocated by ``KCOV_INIT_TRACE`` to keep an opaque bitmap that
prevents the kernel from storing the same PC twice. The remaining part of the
trace is used to collect PCs, like in other modes.

If ``bitmap_size`` is equal to the trace size, kcov only records the bits, but
not the actual PCs.

If ``bitmap_size`` is zero, kcov treats the whole trace as a sparse array where
each PC occurs only once, but there can be holes between PCs.

The mapping between a PC and its position in the bitmap is persistent during the
kernel lifetime, so it is possible for the callers to directly use the bitmap
contents as a coverage signal (like when fuzzing userspace with AFL).

In order to reset the coverage between the runs, the user needs to rewind the
trace (by writing 0 into the first word past ``bitmap_size``) and wipe the whole
bitmap.

Comparison operands collection
------------------------------

Expand Down
1 change: 1 addition & 0 deletions arch/x86/kernel/vmlinux.lds.S
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ SECTIONS
. = ALIGN(PAGE_SIZE);
__bss_stop = .;
}
SANCOV_GUARDS_BSS

/*
* The memory occupied from _text to here, __end_of_kernel_reserve, is
Expand Down
13 changes: 12 additions & 1 deletion include/asm-generic/vmlinux.lds.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
* sections to be brought in with rodata.
*/
#if defined(CONFIG_LD_DEAD_CODE_DATA_ELIMINATION) || defined(CONFIG_LTO_CLANG) || \
defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG) || defined(CONFIG_KCOV_ENABLE_GUARDS)
#define TEXT_MAIN .text .text.[0-9a-zA-Z_]*
#else
#define TEXT_MAIN .text
Expand All @@ -121,6 +121,17 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
#define SBSS_MAIN .sbss
#endif

#if defined(CONFIG_KCOV_ENABLE_GUARDS)
#define SANCOV_GUARDS_BSS \
__sancov_guards(NOLOAD) : { \
__sancov_guards_start = .; \
*(__sancov_guards); \
__sancov_guards_end = .; \
}
#else
#define SANCOV_GUARDS_BSS
#endif

/*
* GCC 4.5 and later have a 32 bytes section alignment for structures.
* Except GCC 4.9, that feels the need to align on 64 bytes.
Expand Down
46 changes: 46 additions & 0 deletions include/linux/kcov-state.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_KCOV_STATE_H
#define _LINUX_KCOV_STATE_H

#ifdef CONFIG_KCOV
struct kcov_state {
/* See kernel/kcov.c for more details. */
/*
* Coverage collection mode enabled for this task (0 if disabled).
* This field is used for synchronization, so it is kept outside of
* the below struct.
*/
unsigned int mode;

struct {
/* Size of the area (in long's). */
unsigned int size;
/*
* Pointer to user-provided memory used by kcov. This memory may
* contain multiple buffers.
*/
void *area;

/* Size of the trace (in long's). */
unsigned int trace_size;
/* Buffer for coverage collection, shared with the userspace. */
unsigned long *trace;

/* Size of the bitmap (in bits). */
unsigned int bitmap_size;
/*
* Bitmap for coverage deduplication, shared with the
* userspace.
*/
unsigned long *bitmap;

/*
* KCOV sequence number: incremented each time kcov is
* reenabled, used by kcov_remote_stop(), see the comment there.
*/
int sequence;
} s;
};
#endif /* CONFIG_KCOV */

#endif /* _LINUX_KCOV_STATE_H */
59 changes: 40 additions & 19 deletions include/linux/kcov.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#ifndef _LINUX_KCOV_H
#define _LINUX_KCOV_H

#include <linux/sched.h>
#include <linux/kcov-state.h>
#include <uapi/linux/kcov.h>

struct task_struct;
Expand All @@ -23,22 +23,23 @@ enum kcov_mode {
KCOV_MODE_TRACE_CMP = 3,
/* The process owns a KCOV remote reference. */
KCOV_MODE_REMOTE = 4,
KCOV_MODE_TRACE_UNIQUE_PC = 5,
};

#define KCOV_IN_CTXSW (1 << 30)
#define KCOV_IN_CTXSW (1 << 30)

void kcov_task_init(struct task_struct *t);
void kcov_task_exit(struct task_struct *t);

#define kcov_prepare_switch(t) \
do { \
(t)->kcov_mode |= KCOV_IN_CTXSW; \
} while (0)
#define kcov_prepare_switch(t) \
do { \
(t)->kcov_state.mode |= KCOV_IN_CTXSW; \
} while (0)

#define kcov_finish_switch(t) \
do { \
(t)->kcov_mode &= ~KCOV_IN_CTXSW; \
} while (0)
#define kcov_finish_switch(t) \
do { \
(t)->kcov_state.mode &= ~KCOV_IN_CTXSW; \
} while (0)

/* See Documentation/dev-tools/kcov.rst for usage details. */
void kcov_remote_start(u64 handle);
Expand Down Expand Up @@ -107,6 +108,8 @@ typedef unsigned long long kcov_u64;
#endif

void __sanitizer_cov_trace_pc(void);
void __sanitizer_cov_trace_pc_guard(u32 *guard);
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop);
void __sanitizer_cov_trace_cmp1(u8 arg1, u8 arg2);
void __sanitizer_cov_trace_cmp2(u16 arg1, u16 arg2);
void __sanitizer_cov_trace_cmp4(u32 arg1, u32 arg2);
Expand All @@ -119,23 +122,41 @@ void __sanitizer_cov_trace_switch(kcov_u64 val, void *cases);

#else

static inline void kcov_task_init(struct task_struct *t) {}
static inline void kcov_task_exit(struct task_struct *t) {}
static inline void kcov_prepare_switch(struct task_struct *t) {}
static inline void kcov_finish_switch(struct task_struct *t) {}
static inline void kcov_remote_start(u64 handle) {}
static inline void kcov_remote_stop(void) {}
static inline void kcov_task_init(struct task_struct *t)
{
}
static inline void kcov_task_exit(struct task_struct *t)
{
}
static inline void kcov_prepare_switch(struct task_struct *t)
{
}
static inline void kcov_finish_switch(struct task_struct *t)
{
}
static inline void kcov_remote_start(u64 handle)
{
}
static inline void kcov_remote_stop(void)
{
}
static inline u64 kcov_common_handle(void)
{
return 0;
}
static inline void kcov_remote_start_common(u64 id) {}
static inline void kcov_remote_start_usb(u64 id) {}
static inline void kcov_remote_start_common(u64 id)
{
}
static inline void kcov_remote_start_usb(u64 id)
{
}
static inline unsigned long kcov_remote_start_usb_softirq(u64 id)
{
return 0;
}
static inline void kcov_remote_stop_softirq(unsigned long flags) {}
static inline void kcov_remote_stop_softirq(unsigned long flags)
{
}

#endif /* CONFIG_KCOV */
#endif /* _LINUX_KCOV_H */
16 changes: 2 additions & 14 deletions include/linux/sched.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include <linux/restart_block.h>
#include <uapi/linux/rseq.h>
#include <linux/seqlock_types.h>
#include <linux/kcov-state.h>
#include <linux/kcsan.h>
#include <linux/rv.h>
#include <linux/livepatch_sched.h>
Expand Down Expand Up @@ -1485,26 +1486,13 @@ struct task_struct {
#endif /* CONFIG_TRACING */

#ifdef CONFIG_KCOV
/* See kernel/kcov.c for more details. */

/* Coverage collection mode enabled for this task (0 if disabled): */
unsigned int kcov_mode;

/* Size of the kcov_area: */
unsigned int kcov_size;

/* Buffer for coverage collection: */
void *kcov_area;

struct kcov_state kcov_state;
/* KCOV descriptor wired with this task or NULL: */
struct kcov *kcov;

/* KCOV common handle for remote coverage collection: */
u64 kcov_handle;

/* KCOV sequence number: */
int kcov_sequence;

/* Collect coverage from softirq context: */
unsigned int kcov_softirq;
#endif
Expand Down
3 changes: 3 additions & 0 deletions include/uapi/linux/kcov.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct kcov_remote_arg {
#define KCOV_ENABLE _IO('c', 100)
#define KCOV_DISABLE _IO('c', 101)
#define KCOV_REMOTE_ENABLE _IOW('c', 102, struct kcov_remote_arg)
#define KCOV_UNIQUE_ENABLE _IOR('c', 103, unsigned long)

enum {
/*
Expand All @@ -35,6 +36,8 @@ enum {
KCOV_TRACE_PC = 0,
/* Collecting comparison operands mode. */
KCOV_TRACE_CMP = 1,
/* Deduplicate collected PCs. */
KCOV_TRACE_UNIQUE_PC = 2,
};

/*
Expand Down
Loading