use std::time::Duration;
use tacet::helpers::InputPair;
use tacet::{AttackerModel, Outcome, TimingOracle};
use tokio::runtime::Runtime;
use tokio::time::sleep;
fn single_thread_runtime() -> Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.expect("failed to create single-thread runtime")
}
fn multi_thread_runtime() -> Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_time()
.build()
.expect("failed to create multi-thread runtime")
}
fn rand_bytes() -> [u8; 32] {
let mut arr = [0u8; 32];
for byte in &mut arr {
*byte = rand::random();
}
arr
}
#[test]
fn async_executor_overhead_no_false_positive() {
let rt = single_thread_runtime();
let fixed_input: [u8; 32] = [
0x4e, 0x5a, 0xb4, 0x34, 0x9d, 0x4c, 0x14, 0x82, 0x1b, 0xc8, 0x5b, 0x26, 0x8f, 0x0a, 0x33,
0x9c, 0x7f, 0x4b, 0x2e, 0x8e, 0x1d, 0x6a, 0x3c, 0x5f, 0x9a, 0x2d, 0x7e, 0x4c, 0x8b, 0x3a,
0x6d, 0x5e,
];
let inputs = InputPair::new(|| fixed_input, rand_bytes);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.15)
.fail_threshold(0.99)
.time_budget(Duration::from_secs(30))
.test(inputs, |data| {
rt.block_on(async {
std::hint::black_box(data);
})
});
eprintln!("\n[async_executor_overhead_no_false_positive]");
eprintln!("{}", outcome);
match outcome {
Outcome::Pass {
leak_probability, ..
} => {
assert!(
leak_probability < 0.3,
"Leak probability too high: {:.1}%",
leak_probability * 100.0
);
}
Outcome::Fail {
leak_probability, ..
} => {
panic!(
"Unexpected failure for async executor overhead: P={:.1}%",
leak_probability * 100.0
);
}
Outcome::Inconclusive {
leak_probability, ..
} => {
assert!(
leak_probability < 0.5,
"Leak probability too high for inconclusive: {:.1}%",
leak_probability * 100.0
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: unmeasurable - {}", recommendation);
}
Outcome::Research(_) => {}
}
}
#[test]
fn async_block_on_overhead_symmetric() {
let rt = single_thread_runtime();
let inputs = InputPair::new(|| (), || ());
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.15)
.fail_threshold(0.99)
.time_budget(Duration::from_secs(30))
.test(inputs, |_| {
rt.block_on(async {
std::hint::black_box(42);
})
});
eprintln!("\n[async_block_on_overhead_symmetric]");
eprintln!("{}", outcome);
match outcome {
Outcome::Pass {
leak_probability, ..
} => {
assert!(
leak_probability < 0.3,
"Leak probability too high: {:.1}%",
leak_probability * 100.0
);
}
Outcome::Fail {
leak_probability, ..
} => {
panic!(
"Unexpected failure for symmetric block_on: P={:.1}%",
leak_probability * 100.0
);
}
Outcome::Inconclusive {
leak_probability, ..
} => {
assert!(
leak_probability < 0.5,
"Leak probability too high for inconclusive: {:.1}%",
leak_probability * 100.0
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: unmeasurable - {}", recommendation);
}
Outcome::Research(_) => {}
}
}
#[test]
#[ignore]
fn detects_conditional_await_timing() {
let rt = single_thread_runtime();
let inputs = InputPair::new(|| true, || false);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(inputs, |secret| {
rt.block_on(async {
if *secret {
sleep(Duration::from_nanos(100)).await;
}
sleep(Duration::from_micros(5)).await;
std::hint::black_box(42);
})
});
eprintln!("\n[detects_conditional_await_timing]");
eprintln!("{}", outcome);
match outcome {
Outcome::Fail {
leak_probability, ..
} => {
assert!(
leak_probability > 0.7,
"Expected high leak probability, got {:.1}%",
leak_probability * 100.0
);
}
Outcome::Pass {
leak_probability, ..
} => {
panic!(
"Expected to detect conditional await leak, but passed: P={:.1}%",
leak_probability * 100.0
);
}
Outcome::Inconclusive {
leak_probability, ..
} => {
assert!(
leak_probability > 0.5,
"Expected at least ambiguous leak probability, got {:.1}%",
leak_probability * 100.0
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: unmeasurable - {}", recommendation);
}
Outcome::Research(_) => {}
}
}
#[test]
fn detects_early_exit_async() {
let rt = single_thread_runtime();
let secret = [0xABu8; 32];
let inputs = InputPair::new(|| [0xABu8; 32], || [0xCDu8; 32]);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(30))
.test(inputs, |comparison_input| {
rt.block_on(async {
for i in 0..32 {
if secret[i] != comparison_input[i] {
return;
}
tokio::task::yield_now().await;
}
})
});
eprintln!("\n[detects_early_exit_async]");
eprintln!("{}", outcome);
let leak_probability = outcome.leak_probability().unwrap_or(0.0);
assert!(
leak_probability > 0.8 || outcome.failed(),
"Expected to detect early-exit async timing leak (got P={:.1}%)",
leak_probability * 100.0
);
}
#[test]
#[ignore = "slow test - run with --ignored"]
fn detects_secret_dependent_sleep() {
let rt = single_thread_runtime();
let inputs = InputPair::new(|| 10u8, || 1u8);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(60))
.test(inputs, |byte_value| {
rt.block_on(async {
let delay_micros = *byte_value as u64 * 10;
sleep(Duration::from_micros(delay_micros)).await;
std::hint::black_box(42);
})
});
eprintln!("\n[detects_secret_dependent_sleep]");
eprintln!("{}", outcome);
let leak_probability = outcome.leak_probability().unwrap_or(0.0);
assert!(
leak_probability > 0.95 || outcome.failed(),
"Expected very high confidence for large sleep difference (got P={:.1}%)",
leak_probability * 100.0
);
}
#[test]
fn concurrent_tasks_no_crosstalk() {
let rt = multi_thread_runtime();
let fixed_input: [u8; 32] = [
0x4e, 0x5a, 0xb4, 0x34, 0x9d, 0x4c, 0x14, 0x82, 0x1b, 0xc8, 0x5b, 0x26, 0x8f, 0x0a, 0x33,
0x9c, 0x7f, 0x4b, 0x2e, 0x8e, 0x1d, 0x6a, 0x3c, 0x5f, 0x9a, 0x2d, 0x7e, 0x4c, 0x8b, 0x3a,
0x6d, 0x5e,
];
let inputs = InputPair::new(|| fixed_input, rand_bytes);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.15)
.fail_threshold(0.99)
.time_budget(Duration::from_secs(30))
.test(inputs, |data| {
rt.block_on(async {
for _ in 0..10 {
tokio::spawn(async {
sleep(Duration::from_micros(100)).await;
});
}
std::hint::black_box(data);
})
});
eprintln!("\n[concurrent_tasks_no_crosstalk]");
eprintln!("{}", outcome);
match &outcome {
Outcome::Pass {
leak_probability, ..
} => {
eprintln!(
"Good: no significant timing difference (P={:.1}%)",
leak_probability * 100.0
);
}
Outcome::Fail {
leak_probability,
effect,
exploitability,
..
} => {
if effect.max_effect_ns > 10_000.0 {
panic!(
"Catastrophic timing difference with background tasks: {:.1}μs ({:?})",
effect.max_effect_ns / 1000.0,
exploitability
);
}
eprintln!(
"Note: Detected timing difference (P={:.1}%, {:.1}ns) - expected in concurrent tests",
leak_probability * 100.0, effect.max_effect_ns
);
}
Outcome::Inconclusive {
leak_probability, ..
} => {
eprintln!(
"Note: Inconclusive result ({:.1}%) - expected in concurrent tests",
leak_probability * 100.0
);
}
Outcome::Unmeasurable { recommendation, .. } => {
eprintln!("Skipping: unmeasurable - {}", recommendation);
}
Outcome::Research(_) => {}
}
}
#[test]
#[ignore = "slow test - run with --ignored"]
fn detects_task_spawn_timing_leak() {
let rt = multi_thread_runtime();
let inputs = InputPair::new(|| 10usize, || rand::random::<u32>() as usize % 20);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01)
.fail_threshold(0.85)
.time_budget(Duration::from_secs(60))
.test(inputs, |count| {
rt.block_on(async {
for _ in 0..*count {
tokio::spawn(async {
sleep(Duration::from_nanos(10)).await;
});
}
std::hint::black_box(42);
})
});
eprintln!("\n[detects_task_spawn_timing_leak]");
eprintln!("{}", outcome);
let leak_probability = outcome.leak_probability().unwrap_or(0.0);
assert!(
leak_probability > 0.6 || outcome.failed(),
"Expected to detect task spawn count timing leak (got P={:.1}%)",
leak_probability * 100.0
);
}
#[test]
#[ignore = "slow comparative test - run with --ignored"]
fn tokio_single_vs_multi_thread_stability() {
let rt_single = single_thread_runtime();
let inputs_single = InputPair::new(|| [0xABu8; 32], rand_bytes);
let outcome_single = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.15)
.fail_threshold(0.99)
.time_budget(Duration::from_secs(30))
.test(inputs_single, |data| {
rt_single.block_on(async {
std::hint::black_box(data);
})
});
let rt_multi = multi_thread_runtime();
let inputs_multi = InputPair::new(|| [0xABu8; 32], rand_bytes);
let outcome_multi = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.15)
.fail_threshold(0.99)
.time_budget(Duration::from_secs(30))
.test(inputs_multi, |data| {
rt_multi.block_on(async {
std::hint::black_box(data);
})
});
eprintln!("\n[tokio_single_vs_multi_thread_stability]");
eprintln!("--- Single-thread ---");
eprintln!("{}", outcome_single);
eprintln!("--- Multi-thread ---");
eprintln!("{}", outcome_multi);
let single_prob = outcome_single.leak_probability().unwrap_or(0.0);
let multi_prob = outcome_multi.leak_probability().unwrap_or(0.0);
eprintln!(
"Leak probability comparison: single={:.1}%, multi={:.1}%",
single_prob * 100.0,
multi_prob * 100.0
);
}
#[test]
#[ignore = "informational test - run with --ignored"]
fn async_workload_flag_effectiveness() {
let rt = single_thread_runtime();
let inputs = InputPair::new(|| (), || ());
let outcome_without_flag = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.15)
.fail_threshold(0.99)
.time_budget(Duration::from_secs(30))
.test(inputs, |_| {
rt.block_on(async {
for _ in 0..5 {
tokio::task::yield_now().await;
}
std::hint::black_box(42);
})
});
eprintln!("\n[async_workload_flag_effectiveness]");
eprintln!("{}", outcome_without_flag);
let leak_probability = outcome_without_flag.leak_probability().unwrap_or(0.0);
assert!((0.0..=1.0).contains(&leak_probability));
}