syd 3.57.0

rock-solid application kernel
Documentation
/*
 * src/kcov/stub.c: KCOV sanitizer coverage hooks
 *
 * Copyright (c) 2026 Ali Polatel <alip@chesswob.org>
 * SPDX-License-Identifier: GPL-3.0
 */

/*
 * # Safety
 *
 * This file is compiled WITHOUT instrumentation to avoid infinite recursion.
 */

#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

// KCOV context
struct kcov_ctx {
	// Shared mapping base address
	uintptr_t base;
	// Buffer size in words
	uint64_t words;
	// Tracee word width (4 or 8)
	uint8_t wordsize;
	// 0=PC, 1=CMP
	int mode;
};

// External rust functions called via FFI.
extern bool syd_kcov_get_ctx(struct kcov_ctx *out_ctx) __attribute__((weak));

// Atomic recursion guard (not instrumented).
static _Thread_local atomic_bool tls_rec = false;

/*
 * Per-thread syscall number (C mirror of Rust's TLS_SYS).
 *
 * Syd processes all syscalls through the same internal code paths,
 * producing identical PCs regardless of the syscall type. Mixing the
 * active syscall number into each PC makes the same Syd function
 * produce different signal for different syscalls.
 */
static _Thread_local long tls_sys = -1;

/* Called from Rust before entering the syscall handler. */
__attribute__((no_sanitize("coverage"))) void syd_kcov_set_syscall(long nr)
{
	tls_sys = nr;
}

/* Mix syscall number into a relative PC. */
static inline uint64_t mix_syscall(uint64_t pc)
{
	if (tls_sys > 0) {
		pc ^= (uint64_t)tls_sys * 0x517cc1b727220a95ULL;
	}
	return pc;
}

/*
 * Binary base address for ASLR-independent PCs.
 *
 * Syd may be a static-pie binary; __builtin_return_address(0) yields
 * ASLR-randomised absolute addresses that differ between runs.
 * Subtracting the load base turns each PC into a fixed offset within
 * the binary, producing the same canonical PCs across runs.
 */
extern char __executable_start[] __attribute__((weak));
static uint64_t base_addr;
static atomic_bool base_init = false;

static inline uint64_t get_base_addr(void)
{
	if (__builtin_expect(!atomic_load_explicit(&base_init, memory_order_acquire),
	                     0)) {
		base_addr = __executable_start ? (uint64_t)__executable_start : 0;
		atomic_store_explicit(&base_init, true, memory_order_release);
	}
	return base_addr;
}

/* Return an ASLR-independent PC from a raw return address. */
static inline uint64_t pc_rel(uint64_t raw_pc)
{
	return raw_pc - get_base_addr();
}

// Convert to canonical PC format within the kernel text range that
// Syzkaller's signal filter expects:
// [0xFFFFFFFF80000000, 0xFFFFFFFFFF000000).
static uint64_t to_canon_pc(uint64_t v)
{
	v &= ~0xFULL; // 16-byte alignment
#if __SIZEOF_POINTER__ == 8
	// Base: 0xFFFFFFFF_80000000 Mask: 0x3FFF_FFF0 (~1 GiB, 16B aligned)
	return 0xFFFFFFFF80000000ULL | (v & 0x3FFFFFF0ULL);
#else
	uint32_t x = ((uint32_t)v) & 0x0FFFFFF0U;
	return (uint64_t)(0x80000000U | x);
#endif
}

// Record PC coverage.
static void record_pc_impl(uint64_t pc)
{
	// Fast atomic guard to prevent recursion.
	bool expected = false;
	if (!atomic_compare_exchange_strong(&tls_rec, &expected, true)) {
		// Already recording.
		return;
	}

	// Check if FFI functions are available (weak symbols may be NULL).
	if (!syd_kcov_get_ctx) {
		atomic_store(&tls_rec, false);
		return;
	}

	// Get context from Rust.
	struct kcov_ctx ctx;
	if (!syd_kcov_get_ctx(&ctx)) {
		atomic_store(&tls_rec, false);
		return;
	}

	if (ctx.mode != 0 || ctx.words <= 1) {
		atomic_store(&tls_rec, false);
		return;
	}

	// PC mode with valid context.
	uint64_t cap = ctx.words - 1;
	uint64_t cov = to_canon_pc(mix_syscall(pc));

	if (ctx.wordsize == 4) {
		uint32_t *area = (uint32_t *)ctx.base;
		for (;;) {
			uint32_t cnt = __atomic_load_n(&area[0], __ATOMIC_ACQUIRE);
			if (cnt >= cap)
				break;
			__atomic_store_n(&area[1 + cnt], (uint32_t)cov,
			                 __ATOMIC_RELAXED);
			if (__atomic_compare_exchange_n(&area[0], &cnt, cnt + 1,
			                                true, __ATOMIC_ACQ_REL,
			                                __ATOMIC_ACQUIRE))
				break;
		}
	} else {
		uint64_t *area = (uint64_t *)ctx.base;
		for (;;) {
			uint64_t cnt = __atomic_load_n(&area[0], __ATOMIC_ACQUIRE);
			if (cnt >= cap)
				break;
			__atomic_store_n(&area[1 + cnt], cov, __ATOMIC_RELAXED);
			if (__atomic_compare_exchange_n(&area[0], &cnt, cnt + 1,
			                                true, __ATOMIC_ACQ_REL,
			                                __ATOMIC_ACQUIRE))
				break;
		}
	}

	atomic_store(&tls_rec, false);
}

// Encode KCOV comparison type.
//
// type bit 0   : KCOV_CMP_CONST
// type bits 1-2: size code (1->0, 2->2, 4->4, 8->6)
static inline uint64_t kcov_cmp_type(uint8_t sz, bool is_const)
{
	uint64_t size_code;

	switch (sz) {
	case 1:
		size_code = 0;
		break;
	case 2:
		size_code = 2;
		break;
	case 4:
		size_code = 4;
		break;
	case 8:
		size_code = 6;
		break;
	default:
		size_code = 6;
		break;
	}

	return size_code | (is_const ? 1 : 0);
}

// Record CMP coverage.
static void record_cmp_impl(uint8_t sz, bool is_const, uint64_t a, uint64_t b,
                            uint64_t ip)
{
	// Fast atomic guard to prevent recursion.
	bool expected = false;
	if (!atomic_compare_exchange_strong(&tls_rec, &expected, true)) {
		// Already recording.
		return;
	}

	// Check if FFI functions are available (weak symbols may be NULL).
	if (!syd_kcov_get_ctx) {
		atomic_store(&tls_rec, false);
		return;
	}

	// Get context from Rust.
	struct kcov_ctx ctx;
	if (syd_kcov_get_ctx(&ctx)) {
		uint64_t slots = ((uint64_t)ctx.words * ctx.wordsize) / 8;
		uint64_t cap = slots > 1 ? (slots - 1) / 4 : 0;
		if (ctx.mode == 1 && cap > 0) { // CMP mode.
			uint64_t *area = (uint64_t *)ctx.base;

			for (;;) {
				uint64_t cnt = __atomic_load_n(&area[0],
				                               __ATOMIC_ACQUIRE);
				if (cnt >= cap)
					break;
				uint64_t rec = 1 + cnt * 4;

				__atomic_store_n(&area[rec + 0],
				                 kcov_cmp_type(sz, is_const),
				                 __ATOMIC_RELAXED);
				__atomic_store_n(&area[rec + 1], a, __ATOMIC_RELAXED);
				__atomic_store_n(&area[rec + 2], b, __ATOMIC_RELAXED);
				__atomic_store_n(&area[rec + 3],
				                 to_canon_pc(mix_syscall(ip)),
				                 __ATOMIC_RELAXED);

				if (__atomic_compare_exchange_n(&area[0], &cnt,
				                                cnt + 1, true,
				                                __ATOMIC_ACQ_REL,
				                                __ATOMIC_ACQUIRE))
					break;
			}
		}
	}

	atomic_store(&tls_rec, false);
}

// Sanitizer hooks which call the recording functions.
void __sanitizer_cov_trace_pc(void)
{
	uint64_t pc = pc_rel((uint64_t)__builtin_return_address(0));
	record_pc_impl(pc);
}

void __sanitizer_cov_trace_cmp1(uint8_t a, uint8_t b)
{
	uint64_t pc = pc_rel((uint64_t)__builtin_return_address(0));
	record_cmp_impl(1, false, a, b, pc);
}

void __sanitizer_cov_trace_cmp2(uint16_t a, uint16_t b)
{
	uint64_t pc = pc_rel((uint64_t)__builtin_return_address(0));
	record_cmp_impl(2, false, a, b, pc);
}

void __sanitizer_cov_trace_cmp4(uint32_t a, uint32_t b)
{
	uint64_t pc = pc_rel((uint64_t)__builtin_return_address(0));
	record_cmp_impl(4, false, a, b, pc);
}

void __sanitizer_cov_trace_cmp8(uint64_t a, uint64_t b)
{
	uint64_t pc = pc_rel((uint64_t)__builtin_return_address(0));
	record_cmp_impl(8, false, a, b, pc);
}

void __sanitizer_cov_trace_const_cmp1(uint8_t a, uint8_t b)
{
	uint64_t pc = pc_rel((uint64_t)__builtin_return_address(0));
	record_cmp_impl(1, true, a, b, pc);
}

void __sanitizer_cov_trace_const_cmp2(uint16_t a, uint16_t b)
{
	uint64_t pc = pc_rel((uint64_t)__builtin_return_address(0));
	record_cmp_impl(2, true, a, b, pc);
}

void __sanitizer_cov_trace_const_cmp4(uint32_t a, uint32_t b)
{
	uint64_t pc = pc_rel((uint64_t)__builtin_return_address(0));
	record_cmp_impl(4, true, a, b, pc);
}

void __sanitizer_cov_trace_const_cmp8(uint64_t a, uint64_t b)
{
	uint64_t pc = pc_rel((uint64_t)__builtin_return_address(0));
	record_cmp_impl(8, true, a, b, pc);
}

void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases)
{
	uint8_t sz;

	switch (cases[1]) {
	case 8:
		sz = 1;
		break;
	case 16:
		sz = 2;
		break;
	case 32:
		sz = 4;
		break;
	case 64:
		sz = 8;
		break;
	default:
		return;
	}

	uint64_t ip = pc_rel((uint64_t)__builtin_return_address(0));
	for (uint64_t i = 0; i < cases[0]; i++)
		record_cmp_impl(sz, true, cases[i + 2], val, ip);
}