1515 * limitations under the License.
1616 */
1717
18- use std:: fs:: File ;
19- use std:: io:: Write ;
18+ use std:: fs:: { self , OpenOptions } ;
19+ use std:: io:: { ErrorKind , Write } ;
2020use std:: path:: Path ;
2121use std:: process:: Command ;
2222
@@ -25,7 +25,7 @@ use tss_esapi::interface_types::session_handles::AuthSession;
2525
2626use crate :: { CarbideClientError , attestation as attest} ;
2727
28- pub ( crate ) const TPM_RECOVERY_ATTEMPTED_PATH : & str = "/tmp /tpm_recovery_reboot_attempted" ;
28+ pub ( crate ) const TPM_RECOVERY_ATTEMPTED_PATH : & str = "/run/scout /tpm_recovery_reboot_attempted" ;
2929
3030// From https://superuser.com/questions/1404738/tpm-2-0-hardware-error-da-lockout-mode
3131pub ( crate ) fn set_tpm_max_auth_fail ( ) -> Result < ( ) , CarbideClientError > {
@@ -87,34 +87,47 @@ pub(crate) fn clear_tpm(tpm_path: &str) -> Result<(), CarbideClientError> {
8787 Ok ( ( ) )
8888}
8989
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 ,
90+ /// Returns true when attestation-key setup failed after a TPM context was opened successfully.
91+ ///
92+ /// Recovery is only attempted for this stage: context creation failures (bad path, missing device)
93+ /// are not recoverable via TPM clear.
94+ pub ( crate ) fn should_attempt_tpm_recovery_for_attest_key_failure (
95+ source : & dyn std:: error:: Error ,
96+ ) -> bool {
97+ let message = source. to_string ( ) . to_ascii_lowercase ( ) ;
98+ !message. contains ( "not supported" )
99+ }
100+
101+ fn claim_tpm_recovery_attempt ( ) -> Result < ( ) , CarbideClientError > {
102+ if let Some ( parent) = Path :: new ( TPM_RECOVERY_ATTEMPTED_PATH ) . parent ( ) {
103+ fs:: create_dir_all ( parent) . map_err ( CarbideClientError :: StdIo ) ?;
98104 }
105+
106+ let mut marker = match OpenOptions :: new ( )
107+ . write ( true )
108+ . create_new ( true )
109+ . open ( TPM_RECOVERY_ATTEMPTED_PATH )
110+ {
111+ Ok ( file) => file,
112+ Err ( e) if e. kind ( ) == ErrorKind :: AlreadyExists => {
113+ return Err ( CarbideClientError :: TpmError (
114+ "TPM recovery was already attempted this boot cycle; refusing to loop" . to_string ( ) ,
115+ ) ) ;
116+ }
117+ Err ( e) => return Err ( CarbideClientError :: StdIo ( e) ) ,
118+ } ;
119+ marker
120+ . write_all ( b"tpm recovery reboot requested\n " )
121+ . map_err ( CarbideClientError :: StdIo )
99122}
100123
101124/// Clears the TPM and reboots the host once per boot cycle to recover from missing TPM material.
102125pub ( 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- }
126+ claim_tpm_recovery_attempt ( ) ?;
108127
109128 tracing:: warn!( "Attempting automated TPM clear and reboot to recover attestation state" ) ;
110129 clear_tpm ( tpm_path) ?;
111130
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-
118131 let output = Command :: new ( "systemctl" )
119132 . arg ( "reboot" )
120133 . output ( )
@@ -135,14 +148,20 @@ mod tests {
135148 use super :: * ;
136149
137150 #[ 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) ) ;
151+ fn attest_key_failure_recovery_classification_cases ( ) {
152+ let cases: & [ ( & str , bool ) ] = & [
153+ ( "handle already exists" , true ) ,
154+ ( "tpm corruption detected" , true ) ,
155+ ( "feature not supported on this device" , false ) ,
156+ ] ;
157+
158+ for ( message, want_recovery) in cases {
159+ let err: Box < dyn std:: error:: Error > = Box :: new ( std:: io:: Error :: other ( * message) ) ;
160+ assert_eq ! (
161+ should_attempt_tpm_recovery_for_attest_key_failure( & * err) ,
162+ * want_recovery,
163+ "message={message:?}"
164+ ) ;
165+ }
147166 }
148167}
0 commit comments