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/ctutils.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ jobs:
- run: cargo build --target ${{ matrix.target }}
- run: cargo build --target ${{ matrix.target }} --features alloc

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

minimal-versions:
uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
with:
Expand Down
19 changes: 19 additions & 0 deletions ctutils/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"]
87 changes: 87 additions & 0 deletions ctutils/checkct/Cargo.lock

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

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

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

[profile.release]
debug = true
panic = "immediate-abort"

[patch.crates-io]
cmov = { path = "../../cmov" }
14 changes: 14 additions & 0 deletions ctutils/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 ctutils/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 ctutils/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" }
ctutils = { 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" }
74 changes: 74 additions & 0 deletions ctutils/checkct/driver/src/driver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::rng::{PrivateRng, PublicRng};
use checkct_macros::checkct;
use core::hint::black_box;
use ctutils::{Choice, CtSelect};

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, $from_lsb:ident, $from_lt:ident, $select:ident) => {
mod $module {
use super::*;

fn inputs() -> ($ty, $ty, $ty) {
// Keep both RNG hooks linked: the BINSEC script uses both.
black_box(PublicRng.next_u32());
(
private_integer!($ty),
private_integer!($ty),
private_integer!($ty),
)
}

#[checkct]
pub fn choice_select() {
let (a, b, condition) = inputs();
black_box(select(a, b, condition));
}

#[inline(never)]
fn select(a: $ty, b: $ty, condition: $ty) -> $ty {
Choice::$from_lsb(condition).$select(a, b)
}

/// A single shift-and-subtract division step, as used in crypto-bigint.
/// Keep the comparison and selection together to test their optimization.
#[checkct]
pub fn choice_select_after_lt() {
let (a, b, _) = inputs();
black_box(subtract_if_ge(a, b));
}

#[inline(never)]
fn subtract_if_ge(a: $ty, b: $ty) -> $ty {
let choice = Choice::$from_lt(a, b);
choice.$select(a.wrapping_sub(b), a)
}

/// Compare the const helper with the cmov-backed trait on the same expression.
#[checkct]
pub fn ct_select_after_lt() {
let (a, b, _) = inputs();
black_box(subtract_if_ge_with_ct_select(a, b));
}

#[inline(never)]
fn subtract_if_ge_with_ct_select(a: $ty, b: $ty) -> $ty {
let choice = Choice::$from_lt(a, b);
a.wrapping_sub(b).ct_select(&a, choice)
}
}
};
}

scalar_checks!(u8_ops, u8, from_u8_lsb, from_u8_lt, select_u8);
scalar_checks!(u16_ops, u16, from_u16_lsb, from_u16_lt, select_u16);
scalar_checks!(u32_ops, u32, from_u32_lsb, from_u32_lt, select_u32);
scalar_checks!(u64_ops, u64, from_u64_lsb, from_u64_lt, select_u64);
// TODO: scalar_checks!(u128_ops, u128, from_u128_lsb, from_u128_lt, select_u128);
24 changes: 24 additions & 0 deletions ctutils/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