tacet 0.4.2

Detect timing side channels in cryptographic code
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
//! System-level preflight checks.
//!
//! Platform-specific checks to ensure the system is configured
//! optimally for timing measurements.
//!
//! **Severity**: Informational
//!
//! System configuration issues add variance to measurements but don't
//! invalidate the statistical analysis. The Bayesian model's assumptions
//! are still valid; you just need more samples to reach the same confidence.

use serde::{Deserialize, Serialize};

use tacet_core::result::{PreflightCategory, PreflightSeverity, PreflightWarningInfo};

/// Warning from system checks.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SystemWarning {
    /// CPU frequency scaling is not set to performance mode.
    ///
    /// **Severity**: Informational
    CpuGovernorNotPerformance {
        /// Current governor setting.
        current: String,
        /// Recommended governor.
        recommended: String,
    },

    /// Could not read CPU governor (permission or path issue).
    ///
    /// **Severity**: Informational
    CpuGovernorUnreadable {
        /// Error message.
        reason: String,
    },

    /// Turbo boost is enabled (can cause timing variability).
    ///
    /// **Severity**: Informational
    TurboBoostEnabled,

    /// Hyperthreading detected (can affect timing measurements).
    ///
    /// **Severity**: Informational
    HyperthreadingEnabled,

    /// Running in a virtual machine.
    ///
    /// **Severity**: Informational
    VirtualMachineDetected {
        /// Type of VM if known.
        vm_type: Option<String>,
    },

    /// High system load detected.
    ///
    /// **Severity**: Informational
    HighSystemLoad {
        /// Current load average.
        load_average: f64,
        /// Threshold exceeded.
        threshold: f64,
    },
}

impl SystemWarning {
    /// Check if this warning undermines result confidence.
    ///
    /// System warnings are always informational - they add variance but
    /// don't invalidate results.
    pub fn is_result_undermining(&self) -> bool {
        false
    }

    /// Check if this warning indicates a critical issue.
    ///
    /// Deprecated: Use `is_result_undermining()` instead.
    #[deprecated(note = "Use is_result_undermining() instead")]
    pub fn is_critical(&self) -> bool {
        false
    }

    /// Get the severity of this warning.
    pub fn severity(&self) -> PreflightSeverity {
        // All system warnings are informational
        PreflightSeverity::Informational
    }

    /// Get a human-readable description of the warning.
    pub fn description(&self) -> String {
        match self {
            SystemWarning::CpuGovernorNotPerformance {
                current,
                recommended,
            } => {
                format!(
                    "CPU governor is '{}', recommend '{}'.",
                    current, recommended
                )
            }
            SystemWarning::CpuGovernorUnreadable { reason } => {
                format!("Could not check CPU governor: {}.", reason)
            }
            SystemWarning::TurboBoostEnabled => {
                "Turbo boost enabled - can cause timing variability.".to_string()
            }
            SystemWarning::HyperthreadingEnabled => {
                "Hyperthreading detected - consider pinning to physical cores.".to_string()
            }
            SystemWarning::VirtualMachineDetected { vm_type } => {
                let vm_info = vm_type
                    .as_ref()
                    .map(|t| format!(" ({})", t))
                    .unwrap_or_default();
                format!(
                    "Running in a virtual machine{}. Timing measurements may be less reliable.",
                    vm_info
                )
            }
            SystemWarning::HighSystemLoad {
                load_average,
                threshold,
            } => {
                format!(
                    "High system load: {:.1} (threshold: {:.1}).",
                    load_average, threshold
                )
            }
        }
    }

    /// Get guidance for addressing this warning.
    pub fn guidance(&self) -> Option<String> {
        match self {
            SystemWarning::CpuGovernorNotPerformance { .. } => {
                Some("Set with: sudo cpufreq-set -g performance".to_string())
            }
            SystemWarning::CpuGovernorUnreadable { .. } => None,
            SystemWarning::TurboBoostEnabled => {
                Some("Consider disabling for more stable measurements.".to_string())
            }
            SystemWarning::HyperthreadingEnabled => {
                Some("Pin to physical cores for more stable timing.".to_string())
            }
            SystemWarning::VirtualMachineDetected { .. } => {
                Some("Consider running on bare metal for more reliable measurements.".to_string())
            }
            SystemWarning::HighSystemLoad { .. } => {
                Some("Reduce background processes for more stable measurements.".to_string())
            }
        }
    }

    /// Convert to a PreflightWarningInfo.
    pub fn to_warning_info(&self) -> PreflightWarningInfo {
        match self.guidance() {
            Some(guidance) => PreflightWarningInfo::with_guidance(
                PreflightCategory::System,
                self.severity(),
                self.description(),
                guidance,
            ),
            None => PreflightWarningInfo::new(
                PreflightCategory::System,
                self.severity(),
                self.description(),
            ),
        }
    }
}

/// Perform all system checks.
///
/// Returns a vector of warnings for any issues detected.
/// On unsupported platforms, returns an empty vector.
pub fn system_check() -> Vec<SystemWarning> {
    #[allow(unused_mut)]
    let mut warnings = Vec::new();

    // Run platform-specific checks
    #[cfg(target_os = "linux")]
    {
        if let Some(warning) = check_cpu_governor_linux() {
            warnings.push(warning);
        }
        if let Some(warning) = check_turbo_boost_linux() {
            warnings.push(warning);
        }
        if let Some(warning) = check_hyperthreading_linux() {
            warnings.push(warning);
        }
        if let Some(warning) = check_vm_detection_linux() {
            warnings.push(warning);
        }
        if let Some(warning) = check_load_linux() {
            warnings.push(warning);
        }
    }

    #[cfg(target_os = "macos")]
    {
        // macOS-specific checks could go here
        let _ = check_macos_power_settings();
    }

    #[cfg(target_os = "windows")]
    {
        // Windows-specific checks could go here
        let _ = check_windows_power_settings();
    }

    warnings
}

/// Check CPU frequency governor on Linux.
///
/// Tries multiple sysfs paths to handle different kernel configurations:
/// - Standard per-CPU path: `/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor`
/// - Policy-based path: `/sys/devices/system/cpu/cpufreq/policy0/scaling_governor`
///
/// On systems without cpufreq (common on ARM64 cloud instances where firmware
/// handles frequency scaling), silently returns None.
#[cfg(target_os = "linux")]
fn check_cpu_governor_linux() -> Option<SystemWarning> {
    // Try multiple possible paths for the governor file
    // Different kernel versions and configurations use different paths
    let governor_paths = [
        // Standard per-CPU path (most common)
        "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor",
        // Policy-based path (some newer kernels, especially with intel_pstate)
        "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor",
    ];

    // Try each path in order
    for path in &governor_paths {
        match std::fs::read_to_string(path) {
            Ok(governor) => {
                let governor = governor.trim().to_lowercase();
                if governor != "performance" {
                    return Some(SystemWarning::CpuGovernorNotPerformance {
                        current: governor,
                        recommended: "performance".to_string(),
                    });
                } else {
                    return None; // Governor is "performance", all good
                }
            }
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                // Path doesn't exist, try next one
                continue;
            }
            Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
                // Permission denied - worth warning about
                return Some(SystemWarning::CpuGovernorUnreadable {
                    reason: format!("Permission denied reading {}", path),
                });
            }
            Err(_) => {
                // Other error, try next path
                continue;
            }
        }
    }

    // No paths worked - cpufreq likely not available on this system
    // This is common on:
    // - ARM64 cloud instances (AWS Graviton, Ampere, etc.) where firmware handles scaling
    // - Virtual machines without cpufreq passthrough
    // - Embedded systems without cpufreq support
    // Silently skip - nothing actionable for the user
    None
}

/// Placeholder for macOS power settings check.
#[cfg(target_os = "macos")]
fn check_macos_power_settings() -> Option<SystemWarning> {
    // TODO: Check macOS power settings using pmset or similar
    None
}

/// Placeholder for Windows power settings check.
#[cfg(target_os = "windows")]
fn check_windows_power_settings() -> Option<SystemWarning> {
    // TODO: Check Windows power plan using powercfg or WMI
    None
}

#[cfg(target_os = "linux")]
fn check_turbo_boost_linux() -> Option<SystemWarning> {
    let intel_path = "/sys/devices/system/cpu/intel_pstate/no_turbo";
    if let Ok(value) = std::fs::read_to_string(intel_path) {
        if value.trim() == "0" {
            return Some(SystemWarning::TurboBoostEnabled);
        }
        return None;
    }

    let generic_path = "/sys/devices/system/cpu/cpufreq/boost";
    if let Ok(value) = std::fs::read_to_string(generic_path) {
        if value.trim() == "1" {
            return Some(SystemWarning::TurboBoostEnabled);
        }
    }

    None
}

#[cfg(target_os = "linux")]
fn check_hyperthreading_linux() -> Option<SystemWarning> {
    let path = "/sys/devices/system/cpu/smt/active";
    if let Ok(value) = std::fs::read_to_string(path) {
        if value.trim() == "1" {
            return Some(SystemWarning::HyperthreadingEnabled);
        }
    }
    None
}

#[cfg(target_os = "linux")]
fn check_vm_detection_linux() -> Option<SystemWarning> {
    let cpuinfo = std::fs::read_to_string("/proc/cpuinfo").ok()?;
    if cpuinfo.to_lowercase().contains("hypervisor") {
        return Some(SystemWarning::VirtualMachineDetected { vm_type: None });
    }
    None
}

#[cfg(target_os = "linux")]
fn check_load_linux() -> Option<SystemWarning> {
    let loadavg = std::fs::read_to_string("/proc/loadavg").ok()?;
    let load = loadavg
        .split_whitespace()
        .next()
        .and_then(|val| val.parse::<f64>().ok())?;

    let threshold = 1.0;
    if load > threshold {
        Some(SystemWarning::HighSystemLoad {
            load_average: load,
            threshold,
        })
    } else {
        None
    }
}

#[allow(dead_code)]
fn check_system_load() -> Option<SystemWarning> {
    const LOAD_THRESHOLD: f64 = 1.0;

    // TODO: Read load average
    // Linux: /proc/loadavg
    // macOS: getloadavg() or sysctl
    // Windows: Performance counters

    let _ = LOAD_THRESHOLD;
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_system_check_runs() {
        // Just verify it doesn't panic
        let _warnings = system_check();
    }

    #[test]
    fn test_warning_descriptions() {
        let warning = SystemWarning::CpuGovernorNotPerformance {
            current: "powersave".to_string(),
            recommended: "performance".to_string(),
        };
        let desc = warning.description();
        assert!(desc.contains("powersave"));
        assert!(desc.contains("performance"));

        let warning = SystemWarning::TurboBoostEnabled;
        let desc = warning.description();
        assert!(desc.contains("Turbo boost"));

        let warning = SystemWarning::VirtualMachineDetected {
            vm_type: Some("QEMU".to_string()),
        };
        let desc = warning.description();
        assert!(desc.contains("virtual machine"));
        assert!(desc.contains("QEMU"));

        let warning = SystemWarning::HighSystemLoad {
            load_average: 2.5,
            threshold: 1.0,
        };
        let desc = warning.description();
        assert!(desc.contains("2.5"));
    }

    #[test]
    fn test_warning_is_not_critical() {
        let warnings = vec![
            SystemWarning::CpuGovernorNotPerformance {
                current: "powersave".to_string(),
                recommended: "performance".to_string(),
            },
            SystemWarning::TurboBoostEnabled,
            SystemWarning::HyperthreadingEnabled,
            SystemWarning::VirtualMachineDetected { vm_type: None },
            SystemWarning::HighSystemLoad {
                load_average: 2.0,
                threshold: 1.0,
            },
        ];

        for warning in warnings {
            assert!(
                !warning.is_result_undermining(),
                "System warnings should not undermine results"
            );
            assert_eq!(warning.severity(), PreflightSeverity::Informational);
        }
    }

    #[test]
    fn test_severity() {
        let governor = SystemWarning::CpuGovernorNotPerformance {
            current: "powersave".to_string(),
            recommended: "performance".to_string(),
        };
        assert_eq!(governor.severity(), PreflightSeverity::Informational);
        assert!(!governor.is_result_undermining());
    }
}