|
15 | 15 | * limitations under the License. |
16 | 16 | */ |
17 | 17 |
|
| 18 | +use std::fs::File; |
| 19 | +use std::io::Write; |
| 20 | +use std::path::Path; |
18 | 21 | use std::process::Command; |
19 | 22 |
|
20 | 23 | use tss_esapi::handles::AuthHandle; |
21 | 24 | use tss_esapi::interface_types::session_handles::AuthSession; |
22 | 25 |
|
23 | 26 | use crate::{CarbideClientError, attestation as attest}; |
24 | 27 |
|
| 28 | +pub(crate) const TPM_RECOVERY_ATTEMPTED_PATH: &str = "/tmp/tpm_recovery_reboot_attempted"; |
| 29 | + |
25 | 30 | // From https://superuser.com/questions/1404738/tpm-2-0-hardware-error-da-lockout-mode |
26 | 31 | pub(crate) fn set_tpm_max_auth_fail() -> Result<(), CarbideClientError> { |
27 | 32 | let output = Command::new("tpm2_dictionarylockout") |
@@ -81,3 +86,63 @@ pub(crate) fn clear_tpm(tpm_path: &str) -> Result<(), CarbideClientError> { |
81 | 86 | tracing::info!("TPM lockout hierarchy clear completed"); |
82 | 87 | Ok(()) |
83 | 88 | } |
| 89 | + |
| 90 | +pub(crate) fn is_recoverable_tpm_client_error(error: &CarbideClientError) -> bool { |
| 91 | + match error { |
| 92 | + CarbideClientError::TpmError(message) => { |
| 93 | + message.contains("Could not create AttestKeyInfo") |
| 94 | + || message.contains("Could not create context") |
| 95 | + || message.contains("TPM2_Clear") |
| 96 | + } |
| 97 | + _ => false, |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +/// Clears the TPM and reboots the host once per boot cycle to recover from missing TPM material. |
| 102 | +pub(crate) fn recover_tpm_and_reboot(tpm_path: &str) -> Result<(), CarbideClientError> { |
| 103 | + if Path::new(TPM_RECOVERY_ATTEMPTED_PATH).exists() { |
| 104 | + return Err(CarbideClientError::TpmError( |
| 105 | + "TPM recovery was already attempted this boot cycle; refusing to loop".to_string(), |
| 106 | + )); |
| 107 | + } |
| 108 | + |
| 109 | + tracing::warn!("Attempting automated TPM clear and reboot to recover attestation state"); |
| 110 | + clear_tpm(tpm_path)?; |
| 111 | + |
| 112 | + let mut marker = |
| 113 | + File::create(TPM_RECOVERY_ATTEMPTED_PATH).map_err(CarbideClientError::StdIo)?; |
| 114 | + marker |
| 115 | + .write_all(b"tpm recovery reboot requested\n") |
| 116 | + .map_err(CarbideClientError::StdIo)?; |
| 117 | + |
| 118 | + let output = Command::new("systemctl") |
| 119 | + .arg("reboot") |
| 120 | + .output() |
| 121 | + .map_err(CarbideClientError::StdIo)?; |
| 122 | + if !output.status.success() { |
| 123 | + return Err(CarbideClientError::GenericError(format!( |
| 124 | + "systemctl reboot failed with status {:?}: {}", |
| 125 | + output.status.code(), |
| 126 | + String::from_utf8_lossy(&output.stderr) |
| 127 | + ))); |
| 128 | + } |
| 129 | + |
| 130 | + Ok(()) |
| 131 | +} |
| 132 | + |
| 133 | +#[cfg(test)] |
| 134 | +mod tests { |
| 135 | + use super::*; |
| 136 | + |
| 137 | + #[test] |
| 138 | + fn recoverable_tpm_errors_include_attest_key_info_failures() { |
| 139 | + let err = CarbideClientError::TpmError("Could not create AttestKeyInfo: test".to_string()); |
| 140 | + assert!(is_recoverable_tpm_client_error(&err)); |
| 141 | + } |
| 142 | + |
| 143 | + #[test] |
| 144 | + fn non_tpm_client_errors_are_not_recoverable() { |
| 145 | + let err = CarbideClientError::GenericError("transport failed".to_string()); |
| 146 | + assert!(!is_recoverable_tpm_client_error(&err)); |
| 147 | + } |
| 148 | +} |
0 commit comments