New paste Repaste Download
// Syd: rock-solid application kernel
// src/kcov/abi.rs — KCOV ABI handlers
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
// SPDX-License-Identifier: GPL-3.0
use std::{
    fmt,
    fs::File,
    marker::PhantomData,
    os::{
        fd::{FromRawFd, IntoRawFd, OwnedFd, RawFd},
        unix::fs::FileExt,
    },
    sync::{OnceLock, RwLock},
};
use libc::pid_t;
use libseccomp::{ScmpArch, ScmpNotifResp};
use memchr::arch::all::is_equal;
use nix::{errno::Errno, fcntl::SealFlag, unistd::Pid};
use serde::{Serialize, Serializer};
use crate::{
    config::SAFE_MFD_FLAGS,
    cookie::safe_ftruncate,
    err::err2no,
    error,
    fs::{create_memfd, seal_memfd, MaybeFd},
    hash::SydHashMap,
    hook::UNotifyEventRequest,
    ioctl::{ioctl_names_get, Ioctl},
    kcov::{Kcov, KcovId, TlsSink, TraceMode, TLS_SINK},
    notice,
    proc::proc_kcov_readid,
};
/// Internal: compute payload capacity (in records) for the given context/mode.
#[inline]
fn payload_cap_records(ctx: &KcovCtx) -> usize {
    match ctx.mode {
        Some(TraceMode::Pc) => ctx.words.saturating_sub(1),
        Some(TraceMode::Cmp) => (ctx.words.saturating_sub(1)) / 4,
        None => 0,
    }
}
/// Internal: read cover[0] (native-endian u64) from the memfd.
#[inline]
fn read_header_ne(ctx: &KcovCtx) -> Result<u64, Errno> {
    let mut hdr = [0u8; 8];
    if ctx.syd_fd.read_at(&mut hdr, 0).is_err() {
        return Err(Errno::EIO);
    }
    Ok(u64::from_ne_bytes(hdr))
}
/// Internal: write cover[0] (native-endian u64) to the memfd.
#[inline]
fn write_header_ne(ctx: &KcovCtx, val: u64) -> Result<(), Errno> {
    let bytes = val.to_ne_bytes();
    if let Err(error) = ctx.syd_fd.write_all_at(&bytes, 0) {
        let errno = err2no(&error);
        error!("ctx" : "kcov",
            "op" : "write_header",
            "id" : ctx.id.0,
            "val" : val,
            "err" : errno as i32,
            "msg" : format!("KCOV:{} header write val:{val} failed: {errno}", ctx.id.0));
        return Err(Errno::EIO);
    }
    Ok(())
}
/// Internal: write a single payload u64 at record index `idx` (0-based).
#[inline]
fn write_payload_word(ctx: &KcovCtx, idx: usize, val: u64) -> Result<(), Errno> {
    // Payload starts at word 1 → byte offset = (1 + idx) * 8.
    let off = ((1 + idx) * 8) as u64;
    let bytes = val.to_ne_bytes();
    if let Err(error) = ctx.syd_fd.write_all_at(&bytes, off) {
        let errno = err2no(&error);
        error!("ctx" : "kcov",
            "op" : "write_payload",
            "id" : ctx.id.0,
            "idx" : idx,
            "val" : val,
            "err" : errno as i32,
            "msg" : format!("KCOV:{} payload write idx:{idx} val:{val} failed: {errno}", ctx.id.0));
        return Err(Errno::EIO);
    }
    Ok(())
}
// Internal: zero the live memfd header+payload strictly within `words`.
#[inline]
fn zero_memfd(ctx: &mut KcovCtx) -> Result<(), Errno> {
    if ctx.words == 0 {
        return Err(Errno::EINVAL);
    }
    let need = ctx.words * 8;
    ensure_len(&mut ctx.scratch, need);
    for b in &mut ctx.scratch[..need] {
        *b = 0;
    }
    ctx.syd_fd
        .write_all_at(&ctx.scratch[..need], 0)
        .or(Err(Errno::EIO))
}
/// Internal: best-effort live memfd update for a PC record with capacity clamp.
///
/// If full, clamps header to capacity and performs no payload write.
#[inline]
fn live_update_pc_clamped(ctx: &mut KcovCtx, pc: u64) {
    // Only when in PC mode with a valid area.
    if ctx.mode != Some(TraceMode::Pc) || ctx.words <= 1 {
        return;
    }
    let cap = payload_cap_records(ctx);
    if cap == 0 {
        return;
    }
    let mut cnt = match read_header_ne(ctx) {
        Ok(n) => n as usize,
        Err(_) => return,
    };
    if cnt >= cap {
        // Clamp header if it drifted past cap; ignore errors.
        if cnt != cap {
            let _ = write_header_ne(ctx, cap as u64);
        }
        return;
    }
    // Write payload at current index and bump header; ignore I/O errors.
    let _ = write_payload_word(ctx, cnt, pc);
    cnt += 1;
    let _ = write_header_ne(ctx, cnt as u64);
}
/// Internal: best-effort live memfd update for a CMP record with capacity clamp.
///
/// If full, clamps header to capacity and performs no payload write.
#[inline]
fn live_update_cmp_clamped(
    ctx: &mut KcovCtx,
    size_bytes: u8,
    is_const: bool,
    a: u64,
    b: u64,
    ip: u64,
) {
    // Only when in CMP mode with a valid area.
    if ctx.mode != Some(TraceMode::Cmp) || ctx.words <= 4 {
        return;
    }
    let payload_words = ctx.words.saturating_sub(1);
    let cap_records = payload_words / 4;
    if cap_records == 0 {
        return;
    }
    // Read current count from memfd header in native-endian.
    let mut cnt = match read_header_ne(ctx) {
        Ok(n) => n as usize,
        Err(_) => return,
    };
    if cnt >= cap_records {
        // Clamp header if it drifted past cap; ignore errors.
        if cnt != cap_records {
            let _ = write_header_ne(ctx, cap_records as u64);
        }
        return;
    }
    // Compute per-record base index (4 words per record).
    let base = cnt.saturating_mul(4);
    // Pack comparison type: size in next byte, const in bit 0.
    let ty = (u64::from(size_bytes) << 8) | u64::from(is_const);
    // Write payload (ty, a, b, ip), then bump header; ignore I/O errors.
    let _ = write_payload_word(ctx, base, ty);
    let _ = write_payload_word(ctx, base + 1, a);
    let _ = write_payload_word(ctx, base + 2, b);
    let _ = write_payload_word(ctx, base + 3, ip);
    cnt += 1;
    let _ = write_header_ne(ctx, cnt as u64);
}
// Read `struct kcov_remote_arg` header (fixed 24 bytes) and optionally N u64 handles.
// Layout is arch-stable due to `__aligned_u64` in uapi.
#[repr(C)]
#[derive(Clone, Copy)]
pub(crate) struct KCovRemoteHdr {
    pub(crate) trace_mode: u32, // 0=PC, 1=CMP
    pub(crate) area_size: u32,  // words
    pub(crate) num_handles: u32,
    pub(crate) _pad: u32, // keep next 8B-aligned
    pub(crate) common_handle: u64,
}
/// Per-TID kcov context.
pub(crate) struct KcovCtx {
    pub(crate) id: KcovId,
    pub(crate) syd_fd: File,
    pub(crate) words: usize,
    pub(crate) mode: Option<TraceMode>,
    pub(crate) scratch: Vec<u8>,
}
//
// Singletons
//
static KCOV_REG: OnceLock<RwLock<SydHashMap<KcovId, KcovCtx>>> = OnceLock::new();
fn reg() -> &'static RwLock<SydHashMap<KcovId, KcovCtx>> {
    KCOV_REG.get_or_init(|| RwLock::new(SydHashMap::default()))
}
/// Map owner (enabling) TID -> KcovId.
static KCOV_OWNER_REG: OnceLock<RwLock<SydHashMap<pid_t, KcovId>>> = OnceLock::new();
fn owner_reg() -> &'static RwLock<SydHashMap<pid_t, KcovId>> {
    KCOV_OWNER_REG.get_or_init(|| RwLock::new(SydHashMap::default()))
}
/// Check that enabling `id` for `owner_tid` would not violate the
/// single-kcov-per-task rule. Does not mutate the map.
#[inline]
fn owner_check(owner_tid: pid_t, id: KcovId) -> Result<(), Errno> {
    let map = owner_reg().read().unwrap_or_else(|e| e.into_inner());
    if matches!(map.get(&owner_tid), Some(cur) if *cur != id) {
        // Task already owns another KCOV instance.
        return Err(Errno::EBUSY);
    }
    Ok(())
}
/// Bind `owner_tid` to `id` after a successful transition to Enabled.
#[inline]
fn owner_bind(owner_tid: pid_t, id: KcovId) {
    let mut map = owner_reg().write().unwrap_or_else(|e| e.into_inner());
    map.insert(owner_tid, id);
}
// Rebind exact owner TID mapping (old -> new) for this KCOV id.
#[inline]
fn owner_rebind_tid(old_tid: pid_t, new_tid: pid_t, id: KcovId) {
    let mut map = owner_reg().write().unwrap_or_else(|e| e.into_inner());
    if let Some(cur) = map.get(&old_tid).copied() {
        if cur == id {
            map.remove(&old_tid);
        }
    }
    // Install new owner tid.
    map.insert(new_tid, id);
}
/*
#[inline]
fn owner_set(owner_tid: pid_t, id: KcovId) {
    let mut map = owner_reg().write().unwrap_or_else(|e| e.into_inner());
    map.insert(owner_tid, id);
}
*/
#[inline]
fn owner_clear(owner_tid: pid_t) {
    let mut map = owner_reg().write().unwrap_or_else(|e| e.into_inner());
    map.remove(&owner_tid);
}
static KCOV_MGR: OnceLock<Kcov> = OnceLock::new();
fn mgr() -> &'static Kcov {
    KCOV_MGR.get_or_init(Kcov::new)
}
//
// Public API
//
/// RAII guard that arms KCOV for the current worker.
pub struct KcovScope {
    prev: Option<TlsSink>,
    _nosend: PhantomData<*mut ()>, // !Send + !Sync
}
impl KcovScope {
    fn new(id: KcovId) -> Self {
        let prev = TLS_SINK.with(|tls| {
            let mut guard = tls.lock().unwrap_or_else(|e| e.into_inner());
            let prev = *guard;
            *guard = Some(TlsSink { id });
            prev
        });
        Self {
            prev,
            _nosend: PhantomData,
        }
    }
}
impl Drop for KcovScope {
    fn drop(&mut self) {
        TLS_SINK.with(|tls| {
            let mut guard = tls.lock().unwrap_or_else(|e| e.into_inner());
            *guard = self.prev;
        });
    }
}
/// Create a named memfd for kcov, register per-TID context.
#[allow(clippy::cognitive_complexity)]
pub(crate) fn kcov_open(tid: Pid) -> Result<MaybeFd, Errno> {
    let tid = tid.as_raw();
    notice!("ctx": "kcov", "op": "open",
        "pid": tid,
        "msg": format!("open /dev/kcov request by tid {tid}"));
    // Allocate ID first so we can encode in memfd name.
    let kcov_id = mgr().open()?;
    // Create memfd with Kcov ID in name.
    let name = format!("syd-kcov:{}", kcov_id.0);
    let mut name_c = name.clone().into_bytes();
    name_c.push(0);
    let memfd = create_memfd(&name_c, *SAFE_MFD_FLAGS)?.into_raw_fd();
    // SAFETY: seccomp addfd creates a duplicate.
    let memfd_own = unsafe { OwnedFd::from_raw_fd(memfd) };
    // Register per-TID context (disabled until KCOV_ENABLE).
    {
        let mut map = reg().write().unwrap_or_else(|e| e.into_inner());
        map.insert(
            kcov_id,
            KcovCtx {
                id: kcov_id,
                syd_fd: memfd_own.into(),
                words: 0,
                mode: None,
                scratch: Vec::new(),
            },
        );
    }
    notice!("ctx": "kcov", "op": "open",
        "pid": tid, "name": &name,
        "msg": format!("open /dev/kcov returned !memfd:{name} to tid {tid}"));
    // Hand the original memfd back to caller.
    Ok(memfd.into())
}
/// Emulate kcov ioctls on our memfd, identified by fd-name.
#[allow(clippy::cognitive_complexity)]
pub(crate) fn kcov_ioctl(request: &UNotifyEventRequest) -> Result<ScmpNotifResp, Errno> {
    let tid = request.scmpreq.pid().as_raw();
    let fd = match RawFd::try_from(request.scmpreq.data.args[0]) {
        Ok(fd) if fd >= 0 => fd,
        _ => return Err(Errno::EBADF),
    };
    // Resolve the KcovId from the memfd name.
    let kcov_id = proc_kcov_readid(tid, fd).ok_or(Errno::ENOTTY)?;
    // Look up per-TID context, fallback to procfs readlink.
    let mut write_map = reg().write().unwrap_or_else(|e| e.into_inner());
    let ctx = write_map.get_mut(&kcov_id).ok_or(Errno::ENOTTY)?;
    // Decode ioctl by NAME (arch-safe).
    let kcov_cmd = request.scmpreq.data.args[1] as Ioctl;
    let kcov_arg = request.scmpreq.data.args[2];
    let kcov_cmd = KcovIoctl::try_from((kcov_cmd, request.scmpreq.data.arch))?;
    notice!("ctx": "kcov", "op": "ioctl",
        "pid": tid, "cmd": kcov_cmd, "fd": fd,
        "msg": format!("ioctl {kcov_cmd} request by tid {tid} for fd {fd}"));
    #[allow(clippy::cast_possible_truncation)]
    match kcov_cmd {
        KcovIoctl::InitTrace => {
            let words = kcov_arg;
            if words < 2 {
                return Err(Errno::EINVAL);
            }
            mgr().init_trace(ctx.id, words)?;
            // Track/resize our memfd view to match `words`.
            ctx.words = words as usize;
            if safe_ftruncate(&ctx.syd_fd, (ctx.words * 8) as i64).is_err() {
                return Err(Errno::EIO);
            }
            if seal_memfd(&ctx.syd_fd, SealFlag::F_SEAL_SHRINK | SealFlag::F_SEAL_GROW).is_err() {
                return Err(Errno::EIO);
            }
            // Zero the file (header+payload) strictly within words.
            zero_memfd(ctx)?;
            Ok(ok0(request))
        }
        KcovIoctl::ResetTrace => {
            // Validate: Area must be initialized,
            // and ioctl arg must be 0.
            if ctx.words == 0 || kcov_arg != 0 {
                return Err(Errno::EINVAL);
            }
            // Header-only reset:
            // Kernel clears cover[0] and leaves payload unspecified. We follow
            // that to avoid O(n) zeroing on large areas. The live writer
            // derives the next index from the header and will overwrite payload
            // entries as it records.
            if write_header_ne(ctx, 0).is_err() {
                return Err(Errno::EIO);
            }
            // TLS remains armed; recording resumes at index 0.
            Ok(ok0(request))
        }
        KcovIoctl::Enable | KcovIoctl::UniqueEnable => {
            let mode = match kcov_arg {
                0 => TraceMode::Pc,
                1 => TraceMode::Cmp,
                _ => return Err(Errno::EINVAL),
            };
            if ctx.words == 0 {
                return Err(Errno::EINVAL);
            }
            let tid = request.scmpreq.pid().as_raw();
            // Enforce single-kcov-per-task:
            owner_check(tid, ctx.id)?;
            // Transition to Enabled (owner recorded in the core).
            mgr().enable(ctx.id, mode, tid)?;
            // Publish the owner mapping only after enable.
            owner_bind(tid, ctx.id);
            ctx.mode = Some(mode);
            // Linux expects userspace to reset cover[0] itself at the tail
            // of ioctl(KCOV_ENABLE). Do not zero header here.
            // (syzkaller does this unconditionally.)
            // Make coverage active immediately after enable.
            let _ = mgr().attach_for_tid(ctx.id, tid);
            // Emit a small heartbeat so cover > 0 immediately.
            // Works in either mode (we send both).
            emit_heartbeats(ctx, mode);
            Ok(ok0(request))
        }
        KcovIoctl::RemoteEnable => {
            // Read & validate remote header; we ignore handles for now.
            let (hdr, _handles_opt) = request.remote_kcov_remote_arg(kcov_arg, false)?;
            let (mode, req_words) = validate_remote_hdr(&hdr, ctx.words)?;
            if ctx.words == 0 {
                // remote_enable without prior init: allow if area_size is sane:
                mgr().init_trace(ctx.id, req_words as u64)?;
                ctx.words = req_words;
                if safe_ftruncate(&ctx.syd_fd, (ctx.words * 8) as i64).is_err() {
                    return Err(Errno::EIO);
                }
                // Zero header+payload for a clean view strictly within words.
                zero_memfd(ctx)?;
                // fallthrough to enable below
            }
            let tid = request.scmpreq.pid().as_raw();
            // Enforce single-kcov-per-task (same as KCOV_ENABLE).
            owner_check(tid, ctx.id)?;
            // Transition to Enabled (owner recorded in the core).
            mgr().enable(ctx.id, mode, tid)?;
            // Publish the owner mapping only after enable; do not clear others.
            owner_bind(tid, ctx.id);
            ctx.mode = Some(mode);
            // Make coverage active immediately after enable.
            let _ = mgr().attach_for_tid(ctx.id, tid);
            // Emit a small heartbeat so cover > 0 immediately.
            // Works in either mode (we send both).
            emit_heartbeats(ctx, mode);
            Ok(ok0(request))
        }
        KcovIoctl::Disable => {
            // Only the owning TID may disable.
            let tid = request.scmpreq.pid().as_raw();
            let ook = {
                let map = owner_reg().read().unwrap_or_else(|e| e.into_inner());
                matches!(map.get(&tid), Some(id) if *id == ctx.id)
            };
            if !ook {
                return Err(Errno::EINVAL);
            }
            // Disable after validation.
            handle_disable(ctx, tid)?;
            Ok(ok0(request))
        }
    }
}
#[inline]
fn ok0(req: &UNotifyEventRequest) -> ScmpNotifResp {
    ScmpNotifResp::new(req.scmpreq.id, 0, 0, 0)
}
#[inline]
fn ensure_len(vec: &mut Vec<u8>, need: usize) {
    if vec.len() < need {
        vec.resize(need, 0);
    }
}
/// Validate a remote KCOV header and compute the effective parameters.
///
/// Returns the selected trace mode and the requested area size in words.
/// Rejects unknown modes, zero area sizes, and attempts to grow beyond an
/// already initialized area.
#[inline]
fn validate_remote_hdr(
    hdr: &KCovRemoteHdr,
    current_words: usize,
) -> Result<(TraceMode, usize), Errno> {
    let mode = match hdr.trace_mode {
        0 => TraceMode::Pc,
        1 => TraceMode::Cmp,
        _ => return Err(Errno::EINVAL),
    };
    if hdr.area_size == 0 {
        return Err(Errno::EINVAL);
    }
    // SAFETY: `area_size` is a u32 from the uapi structure; cast is lossy only
    // if usize is narrower, which does not happen on supported platforms.
    #[allow(clippy::cast_possible_truncation)]
    let req_words = hdr.area_size as usize;
    if current_words != 0 && req_words > current_words {
        return Err(Errno::EINVAL);
    }
    Ok((mode, req_words))
}
/// Attach KCOV for a tracee on syscall dispatch.
#[inline]
pub fn kcov_enter_for(tid: Pid) -> Result<KcovScope, Errno> {
    let tid = tid.as_raw();
    // Ask the manager to attach to any instance that has this tid enabled.
    let id = if let Ok(id) = mgr().attach_for_tid_any(tid) {
        id
    } else {
        // No active coverage for this tid (normal for most syscalls).
        return Err(Errno::ENOENT);
    };
    // If this is the first attach after RESET (cover[0]==0),
    // emit a heartbeat so the probe sees non-zero coverage.
    // Do this *before* the syscall body runs.
    {
        let map = reg().read().unwrap_or_else(|e| e.into_inner());
        if let Some(ctx) = map.get(&id) {
            if let Some(mode) = ctx.mode {
                if let Ok(hdr) = read_header_ne(ctx) {
                    if hdr == 0 {
                        emit_heartbeats(ctx, mode);
                        notice!("ctx":"kcov","op":"attach_heartbeat",
                            "id": id.0, "tid": tid,
                            "msg": format!("emitted post-reset heartbeat for KCOV:{} on attach for tid:{tid}",
                                id.0));
                    }
                }
            }
        }
    }
    notice!("ctx" : "kcov", "op": "attach",
        "id" : id.0, "tid": tid,
        "msg": format!("attached to KCOV:{} for tid:{tid}", id.0));
    // Scope arms and restores TLS.
    Ok(KcovScope::new(id))
}
/// Internal helper: KCOV_DISABLE semantics for a given context.
///
/// Detaches TLS and disables the underlying state; intentionally does
/// **not** publish/snapshot into the memfd.  The live writer already
/// keeps the memfd current during recording.
#[inline]
fn handle_disable(ctx: &mut KcovCtx, owner_tid: pid_t) -> Result<(), Errno> {
    // No TLS changes here; TLS is managed by the RAII scope on the worker.
    // mgr().detach_tls();
    // Per-task disable: Drop only this tid from the enable set.
    mgr().disable_for_tid(ctx.id, owner_tid)?;
    owner_clear(owner_tid);
    Ok(())
}
/// Internal helper: Emit heartbeats, both PC and CMP.
fn emit_heartbeats(ctx: &KcovCtx, mode: TraceMode) {
    // KCOV heartbeat: write one record immediately so cover > 0
    // Write directly to the memfd;
    // do NOT rely on TLS or background attaches.
    match mode {
        TraceMode::Pc => {
            if ctx.words > 1 {
                // payload[0] = marker; header = 1
                let _ = write_payload_word(ctx, 0, 0xDEADC0DE_u64);
                let _ = write_header_ne(ctx, 1);
            }
        }
        TraceMode::Cmp => {
            if ctx.words > 4 {
                // 1 CMP record (ty, a, b, ip), header = 1
                let ty = (8u64 << 8) | 1; // size=8, is_const=1
                let _ = write_payload_word(ctx, 0, ty);
                let _ = write_payload_word(ctx, 1, 0);
                let _ = write_payload_word(ctx, 2, 0);
                let _ = write_payload_word(ctx, 3, 0xDEADC0DE_u64);
                let _ = write_header_ne(ctx, 1);
            }
        }
    }
}
/// KCOV ioctl(2) requests.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum KcovIoctl {
    InitTrace,
    ResetTrace,
    Enable,
    RemoteEnable,
    UniqueEnable,
    Disable,
}
impl TryFrom<(Ioctl, ScmpArch)> for KcovIoctl {
    type Error = Errno;
    /// Convert the given ioctl(2) and arch into a `KcovIoctl`.
    fn try_from(value: (Ioctl, ScmpArch)) -> Result<Self, Errno> {
        let names = ioctl_names_get(value.0, value.1).ok_or(Errno::ENOTTY)?;
        for name in names {
            let name = name.as_bytes();
            if is_equal(name, b"KCOV_INIT_TRACE") {
                return Ok(Self::InitTrace);
            } else if is_equal(name, b"KCOV_RESET_TRACE") {
                return Ok(Self::ResetTrace);
            } else if is_equal(name, b"KCOV_ENABLE") {
                return Ok(Self::Enable);
            } else if is_equal(name, b"KCOV_REMOTE_ENABLE") {
                return Ok(Self::RemoteEnable);
            } else if is_equal(name, b"KCOV_UNIQUE_ENABLE") {
                return Ok(Self::UniqueEnable);
            } else if is_equal(name, b"KCOV_DISABLE") {
                return Ok(Self::Disable);
            }
        }
        Err(Errno::ENOTTY)
    }
}
impl fmt::Display for KcovIoctl {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            Self::InitTrace => "kcov_init_trace",
            Self::ResetTrace => "kcov_reset_trace",
            Self::Enable => "kcov_enable",
            Self::RemoteEnable => "kcov_remote_enable",
            Self::UniqueEnable => "kcov_unique_enable",
            Self::Disable => "kcov_disable",
        };
        write!(f, "{name}")
    }
}
impl Serialize for KcovIoctl {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}
// public: called from main thread on PTRACE_EVENT_EXEC
#[inline]
pub(crate) fn kcov_exec(exec_tid: Pid, old_tid: Pid) {
    // When exec changes TGID/TID ownership,
    // rebind both maps and the core.
    if old_tid == exec_tid {
        return;
    }
    let old = old_tid.as_raw();
    let new = exec_tid.as_raw();
    // If this TID owns a KCOV instance, rebind its TGID/owner to new TGID.
    // Ownership is per-TID (the thread that did KCOV_ENABLE).
    let id = {
        let owners = owner_reg().read().unwrap_or_else(|e| e.into_inner());
        owners.get(&old).copied()
    };
    if let Some(id) = id {
        // Also refresh the *owner TID* mapping to the exec thread.
        owner_rebind_tid(old, new, id);
        if let Err(errno) = mgr().exec_rebind(id, old, new) {
            notice!("ctx" : "kcov", "op" : "exec_rebind_mgr",
                "id": id.0, "tid_old": old, "tid_new": new,
                "err": errno as i32,
                "msg": format!("manager denied KCOV:{} exec rebind for TGID:{new} from TID:{old}: {errno}", id.0));
        }
        notice!("ctx":"kcov","op":"exec_rebind",
            "id": id.0, "tid_old": old, "tid_new": new,
            "msg": format!("KCOV:{} rebound to exec TGID={new} from TID:{old}", id.0));
    }
}
// public: called from main thread on PTRACE_EVENT_EXIT
pub(crate) fn kcov_exit(tid: Pid) {
    let tid = tid.as_raw();
    // If this TID owns a KCOV, auto-disable it.
    if let Some(id) = {
        let mut map = owner_reg().write().unwrap_or_else(|e| e.into_inner());
        map.remove(&tid)
    } {
        // Disable the instance when the enabling task exits and
        // relax ownership so new subprocess can attach immediately.
        let _ = mgr().on_owner_exit(id, tid);
        notice!("ctx" : "kcov", "op" : "owner_exit",
            "id": id.0, "tid": tid,
            "msg": format!("disabled KCOV:{} on owner:{tid} exit", id.0));
    }
    // Drop any TLS arm on this worker.
    mgr().detach_tls();
    // NOTE: Do NOT drop/close/disable KCOV contexts on opener/owner exit.
}
// Recording entry points used by the instrumentation glue.
pub(crate) fn record_pc(pc: u64) -> Result<(), Errno> {
    // Best-effort live memfd update using the TLS sink (tracee_tid).
    // If no sink is installed on this worker, do nothing.
    TLS_SINK.with(|sink| {
        if let Some(s) = *sink.lock().unwrap_or_else(|e| e.into_inner()) {
            let mut map = reg().write().unwrap_or_else(|e| e.into_inner());
            if let Some(ctx) = map.get_mut(&s.id) {
                live_update_pc_clamped(ctx, pc);
            }
        }
    });
    Ok(())
}
pub(crate) fn record_cmp(
    sz: u8,
    is_const: bool,
    a: u64,
    b: u64,
    ip: u64,
) -> Result<(), nix::errno::Errno> {
    // Best-effort live memfd update using the TLS sink (tracee_tid).
    TLS_SINK.with(|sink| {
        if let Some(s) = *sink.lock().unwrap_or_else(|e| e.into_inner()) {
            let mut map = reg().write().unwrap_or_else(|e| e.into_inner());
            if let Some(ctx) = map.get_mut(&s.id) {
                live_update_cmp_clamped(ctx, sz, is_const, a, b, ip);
            }
        }
    });
    Ok(())
}
// Syd: rock-solid application kernel
// src/kcov/api.rs — KCOV API utilities
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
// SPDX-License-Identifier: GPL-3.0
#![forbid(unsafe_code)]
use nix::errno::Errno;
/// const FNV-1a 64-bit; fast, deterministic site IDs.
pub const fn kcov_hash64(s: &str) -> u64 {
    let bytes = s.as_bytes();
    let mut h: u64 = 0xcbf29ce484222325;
    let mut i: usize = 0;
    while i < bytes.len() {
        h ^= bytes[i] as u64;
        h = h.wrapping_mul(0x100000001b3);
        i += 1;
    }
    h
}
/// record a PC edge; no-ops if not enabled (kcov handles TLS/noop)
#[inline(always)]
pub fn record_pc(pc: u64) -> Result<(), Errno> {
    // route to the single kcov manager owned by glue
    crate::kcov::abi::record_pc(pc)
}
/// record a CMP; sz ∈ {1,2,4,8}; ip is a site id; no-ops if not enabled
#[inline(always)]
pub fn record_cmp(sz: u8, is_const: bool, a: u64, b: u64, ip: u64) -> Result<(), Errno> {
    crate::kcov::abi::record_cmp(sz, is_const, a, b, ip)
}
// --- API macros for coverage; gated by `kcov` feature and no-op when disabled ---
/// Emit a lightweight edge at the current callsite using a stable compile-time site ID (no-op when `kcov` is disabled).
#[macro_export]
macro_rules! kcov_edge {
    // auto-site: use file:line:col
    () => {{
        const __KCOV_SITE: u64 = $crate::kcov::api::kcov_hash64(concat!(file!(), ":", line!()));
        let _ = $crate::kcov::api::record_pc(__KCOV_SITE);
    }};
    // user-specified site (any expression → u64)
    ($site:expr) => {{
        let _ = $crate::kcov::api::record_pc(($site) as u64);
    }};
}
/// Emit an edge tagged by a human-readable string hashed at compile time (no-op when `kcov` is disabled).
#[macro_export]
macro_rules! kcov_edge_site {
    // compile-time string -> hashed site
    ($s:literal) => {{
        const __KCOV_SITE: u64 = $crate::kcov::api::kcov_hash64($s);
        let _ = $crate::kcov::api::record_pc(__KCOV_SITE);
    }};
}
/// Record a comparison with automatic site ID so fuzzing can steer value relations (no-op when `kcov` is disabled).
#[macro_export]
macro_rules! kcov_cmp {
    // most convenient form: infer ip from callsite
    ($sz:expr, $isconst:expr, $a:expr, $b:expr) => {{
        const __KCOV_SITE: u64 = $crate::kcov::api::kcov_hash64(concat!(file!(), ":", line!()));
        let _ = $crate::kcov::api::record_cmp(
            ($sz) as u8,
            ($isconst),
            ($a) as u64,
            ($b) as u64,
            __KCOV_SITE,
        );
    }};
    // explicit site id (u64 or anything → u64)
    ($sz:expr, $isconst:expr, $a:expr, $b:expr, $site:expr) => {{
        let _ = $crate::kcov::api::record_cmp(
            ($sz) as u8,
            ($isconst),
            ($a) as u64,
            ($b) as u64,
            ($site) as u64,
        );
    }};
}
/// Record a comparison tagged by a human-readable string hashed at compile time (no-op when `kcov` is disabled).
#[macro_export]
macro_rules! kcov_cmp_site {
    // compile-time string site
    ($sz:expr, $isconst:expr, $a:expr, $b:expr, $s:literal) => {{
        const __KCOV_SITE: u64 = $crate::kcov::api::kcov_hash64($s);
        let _ = $crate::kcov::api::record_cmp(
            ($sz) as u8,
            ($isconst),
            ($a) as u64,
            ($b) as u64,
            __KCOV_SITE,
        );
    }};
}
// Syd: rock-solid application kernel
// src/kcov/mod.rs — KCOV userspace ABI shim for syzkaller
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
// SPDX-License-Identifier: GPL-3.0
use std::{
    fmt,
    sync::{
        atomic::{AtomicU64, Ordering},
        Arc, Mutex, RwLock,
    },
};
use libc::pid_t;
use nix::errno::Errno;
use serde::{Serialize, Serializer};
use crate::{
    hash::{SydHashMap, SydHashSet},
    notice,
};
// KCOV ABI handlers
pub(crate) mod abi;
// KCOV API utilities
pub(crate) mod api;
/// Thread-local sink describing where the live writer should send records.
///
/// We intentionally keep this tiny and policy-free: it captures which KCOV
/// instance is "armed" on this writer thread and which *tracee* tid owns it.
#[derive(Clone, Copy, Debug)]
pub(crate) struct TlsSink {
    pub(crate) id: KcovId,
}
thread_local! {
    static TLS_SINK: Mutex<Option<TlsSink>> = const { Mutex::new(None) };
}
/*
#[inline]
fn set_tls_sink(id: KcovId) {
    TLS_SINK.with(|s| *s.lock().unwrap_or_else(|e| e.into_inner()) = Some(TlsSink { id }));
}
*/
#[inline]
fn clear_tls_sink() {
    TLS_SINK.with(|s| *s.lock().unwrap_or_else(|e| e.into_inner()) = None);
}
//
// Public surface
//
/// KCOV modes (pc/cmp).
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum TraceMode {
    Pc,
    Cmp,
}
// Implement Display manually
impl fmt::Display for TraceMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Pc => write!(f, "pc"),
            Self::Cmp => write!(f, "cmp"),
        }
    }
}
impl Serialize for TraceMode {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Self::Pc => serializer.serialize_str("pc"),
            Self::Cmp => serializer.serialize_str("cmp"),
        }
    }
}
/// /sys/kernel/debug/kcov handle.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct KcovId(u64);
impl KcovId {
    /// Create a new KcovId.
    pub const fn new(id: u64) -> Self {
        Self(id)
    }
}
/// Device manager (open/ioctl/mmap lifecycle).
pub struct Kcov {
    seq: AtomicU64,
    // KcovId -> State
    map: RwLock<SydHashMap<KcovId, Arc<State>>>,
}
impl Kcov {
    /// Detach TLS for the current syd worker thread.
    pub fn detach_tls(&self) {
        TLS_SINK.with(|tls| {
            *tls.lock().unwrap_or_else(|e| e.into_inner()) = None;
        });
    }
    /// new().
    pub fn new() -> Self {
        Self {
            seq: AtomicU64::new(1),
            map: RwLock::new(SydHashMap::default()),
        }
    }
    /// open(): create instance.
    pub fn open(&self) -> Result<KcovId, Errno> {
        let kcov_id = KcovId(self.seq.fetch_add(1, Ordering::Relaxed));
        let state_arc = Arc::new(State::new());
        let mut write_guard = self.map.write().unwrap_or_else(|e| e.into_inner());
        write_guard.insert(kcov_id, state_arc);
        Ok(kcov_id)
    }
    /// KCOV_INIT_TRACE(words).
    pub fn init_trace(&self, kcov_id: KcovId, words: u64) -> Result<(), Errno> {
        self.get(kcov_id)?.init_trace(words)
    }
    /// KCOV_ENABLE: First enable actives this id for syd.
    /// Subsequent enables with the same id increases refcount.
    /// Enabling when other id is active returns EBUSY (single KCOV).
    pub fn enable(&self, id: KcovId, mode: TraceMode, owner_tid: pid_t) -> Result<(), Errno> {
        let st = self.get(id)?;
        st.enable(mode, owner_tid)?;
        Ok(())
    }
    /// KCOV_DISABLE for the calling tid: drop only that tid from the enable set.
    /// When the last tid disables, the instance transitions to Disabled.
    pub fn disable_for_tid(&self, id: KcovId, owner_tid: pid_t) -> Result<(), Errno> {
        let st = self.get(id)?;
        st.disable_for_tid(owner_tid)?;
        // Clear TLS for this worker thread (best-effort).
        clear_tls_sink();
        notice!("ctx" : "kcov", "op" : "disable_tid",
            "id" : id.0, "tid": owner_tid,
            "msg": format!("disabled KCOV:{} for tid:{owner_tid}", id.0));
        Ok(())
    }
    /// Attach TLS to the given KCOV instance only if `owner_tid` matches.
    ///
    /// This avoids the need for a global "active" slot and mirrors the
    /// per-task semantics of the Linux kernel.
    pub fn attach_for_tid(&self, id: KcovId, owner_tid: pid_t) -> Result<(), Errno> {
        let st = self.get(id)?;
        // Maintain lock ordering TLS -> core to avoid deadlock.
        let mut res = Ok(());
        TLS_SINK.with(|tls| {
            let mut guard = tls.lock().unwrap_or_else(|e| e.into_inner());
            // Establish global lock order: TLS -> core (via pre_attach).
            // Do the precondition check while holding TLS to match record paths.
            // Must be Enabled and owned by this tid.
            res = st.pre_attach_for(owner_tid);
            if res.is_ok() {
                *guard = Some(TlsSink { id });
            }
        });
        res
    }
    /// Attach TLS to whichever KCOV instance currently has `owner_tid` enabled.
    ///
    /// This scans all instances and selects the first that accepts `owner_tid`
    /// via `pre_attach_for()`. It establishes TLS before checking the core to
    /// maintain the global lock order (TLS -> core).
    pub fn attach_for_tid_any(&self, owner_tid: pid_t) -> Result<KcovId, Errno> {
        // Snapshot the instances so we don't hold the map lock while probing cores.
        let instances: Vec<(KcovId, Arc<State>)> = {
            let rg = self.map.read().unwrap_or_else(|e| e.into_inner());
            rg.iter().map(|(k, v)| (*k, v.clone())).collect()
        };
        let mut selected: Option<KcovId> = None;
        TLS_SINK.with(|tls| {
            let mut guard = tls.lock().unwrap_or_else(|e| e.into_inner());
            for (id, st) in &instances {
                if st.pre_attach_for(owner_tid).is_ok() {
                    *guard = Some(TlsSink { id: *id });
                    selected = Some(*id);
                    break;
                }
            }
        });
        match selected {
            Some(id) => Ok(id),
            None => Err(Errno::ENOENT),
        }
    }
    /// Update the logical owner TGID after exec() (PTRACE_EVENT_EXEC).
    /// Keeps phase/mode as-is.
    pub fn exec_rebind(
        &self,
        id: KcovId,
        old_owner_tid: pid_t,
        new_owner_tid: pid_t,
    ) -> Result<(), Errno> {
        let st = self.get(id)?;
        let mut core = st.core.lock().unwrap_or_else(|e| e.into_inner());
        if core.phase != Phase::Enabled {
            return Err(Errno::EINVAL);
        }
        // If old tid had enabled coverage,
        // move that enable to the new tid.
        if core.enabled_tids.remove(&old_owner_tid) {
            core.enabled_tids.insert(new_owner_tid);
        }
        Ok(())
    }
    /// The enabling task exited. Keep instance Enabled but clear ownership.
    pub fn on_owner_exit(&self, id: KcovId, exiting_tid: pid_t) -> Result<(), Errno> {
        let st = self.get(id)?;
        let mut core = st.core.lock().unwrap_or_else(|e| e.into_inner());
        if core.phase != Phase::Enabled {
            // Already disabled or not yet enabled.
            return Ok(());
        }
        if !core.enabled_tids.contains(&exiting_tid) {
            // Exiting tid had not enabled coverage.
            return Ok(());
        }
        // Drop the exiting tid from the enable set.
        core.enabled_tids.remove(&exiting_tid);
        core.enabled_refcnt = core.enabled_refcnt.saturating_sub(1);
        let remaining = core.enabled_refcnt;
        if remaining == 0 {
            // No more enabled tids:
            // transition to Disabled (semantics unchanged here).
            core.mode = None;
            core.phase = Phase::Disabled;
        }
        notice!("ctx" : "kcov", "op" : "disable_on_owner_exit",
            "id": id.0, "tid": exiting_tid,
            "msg": format!("dropped exiting tid:{exiting_tid} from KCOV:{} enable set with {remaining} remaining",
                id.0));
        Ok(())
    }
    // -- helpers
    fn get(&self, kcov_id: KcovId) -> Result<Arc<State>, Errno> {
        let read_guard = self.map.read().unwrap_or_else(|e| e.into_inner());
        read_guard.get(&kcov_id).cloned().ok_or(Errno::EBADF)
    }
}
//
// Internals
//
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum Phase {
    Opened,
    Init,
    Enabled,
    Disabled,
    Closed,
}
struct State {
    core: Mutex<Core>,
}
struct Core {
    max_words: usize,
    mode: Option<TraceMode>,
    phase: Phase,
    enabled_tids: SydHashSet<pid_t>,
    enabled_refcnt: usize,
}
impl State {
    fn new() -> Self {
        Self {
            core: Mutex::new(Core {
                max_words: 0,
                mode: None,
                phase: Phase::Opened,
                enabled_tids: SydHashSet::default(),
                enabled_refcnt: 0,
            }),
        }
    }
    // lifecycle
    fn init_trace(&self, words: u64) -> Result<(), Errno> {
        if words < 2 || words > (i32::MAX as u64) / 8 {
            return Err(Errno::EINVAL);
        }
        let mut core = self.core.lock().unwrap_or_else(|e| e.into_inner());
        if core.phase == Phase::Enabled {
            // Not allowed while enabled.
            return Err(Errno::EBUSY);
        }
        if core.phase == Phase::Closed {
            // Treat ops on a closed instance as ENOTTY.
            return Err(Errno::ENOTTY);
        }
        core.max_words = words as usize;
        core.mode = None;
        core.phase = Phase::Init;
        core.enabled_tids.clear();
        core.enabled_refcnt = 0;
        Ok(())
    }
    fn enable(&self, mode: TraceMode, owner_tid: pid_t) -> Result<(), Errno> {
        let mut core = self.core.lock().unwrap_or_else(|e| e.into_inner());
        if core.max_words == 0 {
            return Err(Errno::EINVAL);
        }
        if core.enabled_tids.contains(&owner_tid) {
            // Duplicate enable by the same tid is not allowed.
            return Err(Errno::EBUSY);
        }
        if core.enabled_refcnt > 0 {
            // If already enabled, mode must match the first one.
            if let Some(cur) = core.mode {
                if cur != mode {
                    return Err(Errno::EINVAL);
                }
            }
        }
        // reset per-enable (first enable only)
        if core.enabled_refcnt == 0 {
            core.mode = Some(mode);
            core.phase = Phase::Enabled;
        }
        // add this tid
        core.enabled_tids.insert(owner_tid);
        core.enabled_refcnt = core.enabled_refcnt.saturating_add(1);
        notice!("ctx" : "kcov", "op" : "enable_core",
            "owner_tid" : owner_tid, "mode" : mode,
            "msg" : format!("KCOV state enabled with mode:{mode:?} for tid:{owner_tid} and refcnt:{}",
                core.enabled_refcnt));
        Ok(())
    }
    fn disable_for_tid(&self, owner_tid: pid_t) -> Result<(), Errno> {
        let mut core = self.core.lock().unwrap_or_else(|e| e.into_inner());
        if core.phase != Phase::Enabled {
            return Err(Errno::EINVAL);
        }
        if !core.enabled_tids.remove(&owner_tid) {
            return Err(Errno::EINVAL);
        }
        core.enabled_refcnt = core.enabled_refcnt.saturating_sub(1);
        /*
        let remaining = core.enabled_refcnt;
        if remaining == 0 {
            notice!("ctx" : "kcov", "op" : "disable_core_last",
                "msg" : "last tid disabled; transitioning instance to Disabled");
            core.mode = None;
            core.phase = Phase::Disabled;
        }
        */
        Ok(())
    }
    // Called before TLS attach to ensure the state is usable and owned by `tid`.
    fn pre_attach_for(&self, tid: pid_t) -> Result<(), Errno> {
        let core = self.core.lock().unwrap_or_else(|e| e.into_inner());
        if core.phase != Phase::Enabled {
            return Err(Errno::EINVAL);
        }
        // Allow only tids that previously enabled this instance.
        if core.enabled_tids.contains(&tid) {
            return Ok(());
        }
        notice!("ctx": "kcov", "op": "pre_attach_mismatch",
            "tid": tid, "mode": core.mode,
            "msg" : format!("prevented KCOV attach from tid:{tid}; tid is not enabled"));
        Err(Errno::EPERM)
    }
}
Filename: stdin. Size: 41kb. View raw, , hex, or download this file.

This paste expires on 2025-09-19 21:31:40.634833. Pasted through v1-api.