Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .github/workflows/cmov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ jobs:
- run: cargo build --target ${{ matrix.target }} --all-features
- run: cargo build --target ${{ matrix.target }} --all-features --release

checkct:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: Ledger-Donjon/cargo-checkct@85271e5ce9eab9ba895c62c8b5d4129810a635a0
with:
directory: cmov
timeout: 120

minimal-versions:
uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
with:
Expand Down
19 changes: 19 additions & 0 deletions cmov/checkct/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[build]
target = [
"riscv32imac-unknown-none-elf",
"thumbv6m-none-eabi",
"thumbv7em-none-eabihf",
"x86_64-unknown-linux-gnu",
]

[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C", "linker=rust-lld",
"-C", "linker-flavor=ld.lld",
"-C", "relocation-model=static",
]

[unstable]
unstable-options = true
build-std = ["core", "panic_abort"]
build-std-features = ["compiler-builtins-mem"]
80 changes: 80 additions & 0 deletions cmov/checkct/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions cmov/checkct/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cargo-features = ["panic-immediate-abort"]

[workspace]
members = ["driver"]
resolver = "2"

[profile.release]
debug = true
panic = "immediate-abort"
14 changes: 14 additions & 0 deletions cmov/checkct/checkct_macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#----- AUTOGENERATED BY CARGO-CHECKCT: DO NOT MODIFY -----
[package]
name = "checkct_macros"
version = "0.1.0"
publish = false
edition = "2021"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0.86"
quote = "1.0.37"
syn = { version = "2.0.77", features = ["full"] }
91 changes: 91 additions & 0 deletions cmov/checkct/checkct_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use proc_macro::TokenStream;
use proc_macro2::{Literal, Span};
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
parse_macro_input, Error, Ident, ItemFn, LitStr, Result, Token,
};

struct AttributeArgs {
descriptor_link_section: Option<String>,
}

impl Parse for AttributeArgs {
fn parse(input: ParseStream) -> Result<Self> {
if input.is_empty() {
return Ok(Self {
descriptor_link_section: None,
});
}

let arg_name: Ident = input.parse()?;
if arg_name != "descriptor_link_section" {
return Err(Error::new(
arg_name.span(),
"expected `descriptor_link_section`",
));
}
input.parse::<Token![=]>()?;
let descriptor_link_section: LitStr = input.parse()?;

Ok(Self {
descriptor_link_section: Some(descriptor_link_section.value()),
})
}
}

#[proc_macro_attribute]
pub fn checkct(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as AttributeArgs);
// By default the descriptor will be stored in the .note.checkct section
let descriptor_link_section = Literal::string(
args.descriptor_link_section
.as_deref()
.unwrap_or(".note.checkct"),
);

let item_fn = parse_macro_input!(input as ItemFn);
let name = &item_fn.sig.ident;

let entrypoint_descriptor_name = Ident::new(
&format!("__checkct_entrypoint_descriptor__{name}"),
Span::call_site(),
);
let entrypoint_descriptor_type = Ident::new(
&format!("__CheckctEntrypointDescriptor__{name}"),
Span::call_site(),
);

quote! {
// The descriptor is laid out as an ELF note (which binsec parses), since it is stored
// in a note section by default. It ends with the address of the entrypoint.
#[doc(hidden)]
#[allow(non_camel_case_types, dead_code)]
#[repr(C, packed(4))]
pub struct #entrypoint_descriptor_type {
namesz: u32,
descsz: u32,
kind: u32,
name: [u8; 8],
entrypoint: fn(),
}

// This variable is used to prevent the compiler from removing the code of the entrypoint
// function if it is unused. Moreover this variable name has a special format which allows
// cargo-checkct to find it.
// Make sure that the descriptor link section is kept by the linker.
#[unsafe(link_section = #descriptor_link_section)]
#[used]
pub static #entrypoint_descriptor_name: #entrypoint_descriptor_type =
#entrypoint_descriptor_type {
namesz: 8,
descsz: ::core::mem::size_of::<fn()>() as u32,
kind: 1,
name: *b"checkct\0",
entrypoint: #name,
};

#item_fn
}
.into()
}
13 changes: 13 additions & 0 deletions cmov/checkct/driver/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "driver"
version = "0.0.0"
publish = false
edition = "2024"

[dependencies]
checkct_macros = { path = "../checkct_macros" }
cmov = { path = "../.." }
# RNG traits supplied by cargo-checkct's generated rng.rs.
rand_core = "0.10"
rand_core_09 = { package = "rand_core", version = "0.9" }
rand_core_06 = { package = "rand_core", version = "0.6" }
71 changes: 71 additions & 0 deletions cmov/checkct/driver/src/driver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! Initial constant-time checks for the u32 and u64 primitives.
//!
//! Operands, conditions, and initial outputs are secret.
//! Keep the result live without asserting a predicate on a secret result.
use crate::rng::{PrivateRng, PublicRng};
use checkct_macros::checkct;
use cmov::{Cmov, CmovEq};
use core::hint::black_box;

#[inline]
fn private_byte() -> u8 {
// The generated BINSEC script requires both RNG symbols, even though these
// checks only need secret inputs. Keep the public hook linked as well.
black_box(PublicRng.next_u32());
let mut bytes = [0u8; 1];
PrivateRng.fill_bytes(&mut bytes);
bytes[0]
}

macro_rules! private_integer {
($ty:ty) => {{
let mut bytes = [0u8; size_of::<$ty>()];
PrivateRng.fill_bytes(&mut bytes);
<$ty>::from_le_bytes(bytes)
}};
}

macro_rules! scalar_checks {
($module:ident, $ty:ty) => {
mod $module {
use super::*;

#[checkct]
pub fn cmovnz() {
let mut a = private_integer!($ty);
let b = private_integer!($ty);
a.cmovnz(&b, private_byte());
black_box(a);
}

#[checkct]
pub fn cmovz() {
let mut a = private_integer!($ty);
let b = private_integer!($ty);
a.cmovz(&b, private_byte());
black_box(a);
}

#[checkct]
pub fn cmoveq() {
let a = private_integer!($ty);
let b = private_integer!($ty);
let mut output = private_byte();
a.cmoveq(&b, private_byte(), &mut output);
black_box(output);
}

#[checkct]
pub fn cmovne() {
let a = private_integer!($ty);
let b = private_integer!($ty);
let mut output = private_byte();
a.cmovne(&b, private_byte(), &mut output);
black_box(output);
}
}
};
}

scalar_checks!(u32_ops, u32);
scalar_checks!(u64_ops, u64);
24 changes: 24 additions & 0 deletions cmov/checkct/driver/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//----- AUTOGENERATED BY CARGO-CHECKCT: DO NOT MODIFY -----
//
#![no_std]
#![no_main]

mod driver;
mod rng;

#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
panic!()
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
__checkct_panic()
}

/// The analysis of a path stops when it reaches this function.
#[unsafe(no_mangle)]
#[inline(never)]
pub extern "C" fn __checkct_panic() -> ! {
loop {}
}
Loading
Loading