Skip to content

Interpreting Results

When a timing test completes, you receive an Outcome that tells you whether a timing leak was detected, how confident the result is, and details about the effect size. This page explains how to interpret these results.

Every test returns one of four outcomes:

OutcomeMeaningProbability Range
PassNo timing leak detected above your thresholdP(leak) < 5%
FailTiming leak confirmed above your thresholdP(leak) > 95%
InconclusiveCannot reach a confident decision5% ≤ P(leak) ≤ 95%
UnmeasurableOperation too fast to measure reliablyN/A
use tacet::{TimingOracle, AttackerModel, Outcome, helpers::InputPair};
let inputs = InputPair::new(|| [0u8; 32], || rand::random());
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.test(inputs, |data| my_function(&data));
match outcome {
Outcome::Pass { leak_probability, quality, .. } => {
println!("No leak detected (P={:.1}%)", leak_probability * 100.0);
println!("Measurement quality: {:?}", quality);
}
Outcome::Fail { leak_probability, effect, exploitability, .. } => {
println!("Timing leak detected (P={:.1}%)", leak_probability * 100.0);
println!("Effect: {:.1}ns shift, {:.1}ns tail", effect.shift_ns, effect.tail_ns);
println!("Exploitability: {:?}", exploitability);
}
Outcome::Inconclusive { reason, leak_probability, .. } => {
println!("Inconclusive: {:?}", reason);
println!("Current estimate: P={:.1}%", leak_probability * 100.0);
}
Outcome::Unmeasurable { recommendation, .. } => {
println!("Cannot measure: {}", recommendation);
}
}

The leak_probability is a Bayesian posterior probability: given the timing data collected, what’s the probability that the maximum effect exceeds your threshold?

P(leak) = P(max|Δ| > θ | data)

This is different from a p-value:

Bayesian posteriorFrequentist p-value
”72% probability of a leak""If there were no leak, we’d see this data 28% of the time”
Directly interpretableRequires careful interpretation
Converges with more dataCan produce more false positives with more data
RangeInterpretationOutcome
P < 5%Confident there’s no exploitable leakPass
5% ≤ P ≤ 50%Probably no leak, but uncertainInconclusive
50% < P < 95%Probably a leak, but uncertainInconclusive
P > 95%Confident there’s an exploitable leakFail

You can adjust these thresholds for stricter or more lenient testing:

TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.pass_threshold(0.01) // Require P < 1% for pass (stricter)
.fail_threshold(0.99) // Require P > 99% for fail (stricter)

When a leak is detected, the effect field breaks down the timing difference into interpretable components:

  • Shift (μ): A uniform timing difference affecting all measurements equally. Typical cause: the code takes a different branch.

  • Tail (τ): Upper quantiles (slower measurements) are affected more than lower quantiles. Typical cause: cache misses that only occur for certain inputs.

Outcome::Fail { effect, .. } => {
println!("Shift: {:.1}ns", effect.shift_ns);
println!("Tail: {:.1}ns", effect.tail_ns);
println!("95% CI: [{:.1}, {:.1}]ns",
effect.credible_interval_ns.0,
effect.credible_interval_ns.1);
}
PatternWhat it meansTypical cause
UniformShiftAll quantiles shifted equallyBranch on secret bit, different code path
TailEffectUpper quantiles shifted moreCache misses, memory access patterns
MixedBoth shift and tail are significantMultiple leak sources interacting
ComplexDoesn’t fit standard patternsAsymmetric or multi-modal effects

For Fail outcomes, the exploitability field estimates how difficult it would be to actually exploit the leak:

LevelEffect SizeAttack scenario
SharedHardwareOnly< 10ns~1k queries with shared physical core (SGX, containers)
Http2Multiplexing10-100ns~100k concurrent HTTP/2 requests from the internet
StandardRemote100ns-10μs~1k-10k queries with network timing
ObviousLeak> 10μs< 100 queries, trivially exploitable

This is based on research by Crosby et al. (2009) on the relationship between timing precision and query complexity for remote timing attacks.

An Inconclusive outcome means the test couldn’t reach a confident decision. The reason field tells you why:

ReasonMeaningWhat to do
DataTooNoisyMeasurement noise is too highReduce system noise, use TimerSpec::CyclePrecision, or increase time budget
NotLearningPosterior stopped updatingCheck for measurement setup issues
TimeBudgetExceededRan out of time before reaching conclusionIncrease time_budget
SampleBudgetExceededHit sample limitIncrease max_samples
WouldTakeTooLongProjected time to conclusion exceeds budgetAccept uncertainty or increase budget
Outcome::Inconclusive { reason, leak_probability, .. } => {
match reason {
InconclusiveReason::DataTooNoisy => {
eprintln!("Environment too noisy for reliable measurement");
}
InconclusiveReason::TimeBudgetExceeded => {
eprintln!("Need more time; current estimate: P={:.0}%", leak_probability * 100.0);
}
_ => {
eprintln!("Inconclusive: {:?}", reason);
}
}
}

An Unmeasurable outcome means the operation completes faster than the timer can reliably measure:

Outcome::Unmeasurable { operation_ns, threshold_ns, recommendation, .. } => {
println!("Operation takes ~{:.0}ns, need >{:.0}ns to measure",
operation_ns, threshold_ns);
println!("{}", recommendation);
}

Common solutions:

  • Use TimerSpec::CyclePrecision for finer resolution (see Measurement Precision)
  • Test a larger input size or more iterations
  • Accept that ultra-fast operations may not be measurable on your platform

The quality field indicates overall measurement reliability:

QualityMDEMeaning
Excellent< 5nsCycle-level precision
Good5-20nsSuitable for most testing
Poor20-100nsMay miss small leaks
TooNoisy> 100nsResults unreliable

MDE (Minimum Detectable Effect) is the smallest timing difference that could be reliably detected given the noise level.

The diagnostics include information about threshold elevation:

match outcome {
Outcome::Pass { diagnostics, .. } | Outcome::Fail { diagnostics, .. } => {
if diagnostics.theta_eff > diagnostics.theta_user * 1.1 {
println!("Note: Threshold elevated from {:.1}ns to {:.1}ns",
diagnostics.theta_user, diagnostics.theta_eff);
println!("See: /core-concepts/measurement-precision");
}
}
_ => {}
}
FieldMeaning
theta_userThe threshold you requested (via attacker model)
theta_floorThe minimum detectable effect for this setup
theta_effThe threshold actually used: max(theta_user, theta_floor)

When theta_eff > theta_user, your results answer “is there a leak above theta_eff?” rather than “is there a leak above theta_user?”. See Measurement Precision for details.

  • Pass (P < 5%): No leak detected above your threshold
  • Fail (P > 95%): Leak confirmed, with effect size and exploitability assessment
  • Inconclusive: Check the reason; often fixable by adjusting budget or environment
  • Unmeasurable: Operation too fast; use TimerSpec::CyclePrecision or test larger workload
  • Leak probability is Bayesian (converges with more data, unlike p-values)
  • Effect decomposition tells you how the leak manifests (shift vs tail)
  • Check theta_eff vs theta_user to understand if threshold was elevated