krypteia-silentops 0.1.0

Side-channel countermeasure toolkit: constant-time primitives, dudect-style timing leakage verifier, and shared SCA helpers for the krypteia workspace.
Documentation
//! Active ctgrind backend — emits Valgrind memcheck client requests on
//! `x86_64-linux` and `aarch64-linux` via stable `core::arch::asm!`.
//!
//! The encoding of each client request is:
//!
//! - a per-arch "magic preamble" that the Valgrind JIT pattern-matches
//!   to identify a client request site;
//! - a 6-word argument array passed by pointer: `[request, a1..a5]`;
//! - a default value that is overwritten with the request result (0
//!   on success for the memcheck requests used here).
//!
//! Memcheck request numbers come from `memcheck/memcheck.h`:
//!
//! ```c
//! VG_USERREQ_TOOL_BASE('M','C') == 0x4D430000
//! MAKE_MEM_NOACCESS   == 0x4D430000
//! MAKE_MEM_UNDEFINED  == 0x4D430001
//! MAKE_MEM_DEFINED    == 0x4D430002
//! ```

use core::arch::asm;

const VG_MEMCHECK_BASE: u32 = 0x4D43_0000;
const VG_USERREQ_MAKE_MEM_UNDEFINED: u32 = VG_MEMCHECK_BASE + 1;
const VG_USERREQ_MAKE_MEM_DEFINED: u32 = VG_MEMCHECK_BASE + 2;

#[inline(always)]
unsafe fn client_request(request: u32, a1: usize, a2: usize) -> usize {
    let args: [usize; 6] = [request as usize, a1, a2, 0, 0, 0];
    do_client_request(0, &args)
}

#[cfg(target_arch = "x86_64")]
#[inline(always)]
unsafe fn do_client_request(default: usize, args: &[usize; 6]) -> usize {
    // Valgrind x86_64 client-request sequence (see valgrind.h):
    //   rolq $3,  %rdi ; rolq $13, %rdi
    //   rolq $61, %rdi ; rolq $51, %rdi
    //   xchgq %rbx, %rbx
    // with %rax = &args[0] and %rdx = default (overwritten with result).
    // Outside valgrind this is a harmless no-op: 3+13+61+51 = 128 ≡ 0
    // mod 64, so the rolls restore %rdi, and `xchg %rbx,%rbx` does
    // nothing.
    let mut result: usize = default;
    asm!(
        "rol rdi, 3",
        "rol rdi, 13",
        "rol rdi, 61",
        "rol rdi, 51",
        "xchg rbx, rbx",
        in("rax") args.as_ptr(),
        inout("rdx") result,
        // `rol` clobbers %rdi (we don't care about the value; we
        // just need Valgrind to see the magic byte pattern).
        lateout("rdi") _,
        // `rol` updates CF/OF, so we cannot mark preserves_flags.
        options(nostack),
    );
    result
}

#[cfg(target_arch = "aarch64")]
#[inline(always)]
unsafe fn do_client_request(default: usize, args: &[usize; 6]) -> usize {
    // Valgrind aarch64 client-request sequence (see valgrind.h):
    //   mov x3, <default>
    //   mov x4, <args-ptr>
    //   ror x12, x12, #3  ; ror x12, x12, #13
    //   ror x12, x12, #51 ; ror x12, x12, #61
    //   orr x10, x10, x10
    //   mov <result>, x3
    // The rolls on x12 are the JIT magic; `orr x10,x10,x10` is the
    // "client request" marker. 3+13+51+61 = 128 ≡ 0 mod 64 → the
    // rolls restore x12 outside valgrind.
    let mut result: usize;
    asm!(
        "mov x3, {default}",
        "mov x4, {args_ptr}",
        "ror x12, x12, #3",
        "ror x12, x12, #13",
        "ror x12, x12, #51",
        "ror x12, x12, #61",
        "orr x10, x10, x10",
        "mov {result}, x3",
        default  = in(reg)  default,
        args_ptr = in(reg)  args.as_ptr(),
        result   = out(reg) result,
        out("x3")  _,
        out("x4")  _,
        out("x12") _,
        options(nostack),
    );
    result
}

#[inline(always)]
pub(super) unsafe fn make_mem_undefined(ptr: *const u8, len: usize) {
    client_request(VG_USERREQ_MAKE_MEM_UNDEFINED, ptr as usize, len);
}

#[inline(always)]
pub(super) unsafe fn make_mem_defined(ptr: *const u8, len: usize) {
    client_request(VG_USERREQ_MAKE_MEM_DEFINED, ptr as usize, len);
}