@@ -62,6 +62,33 @@ pub(crate) fn set_tpm_max_auth_fail() -> Result<(), CarbideClientError> {
6262 Ok ( ( ) )
6363}
6464
65+ /// Kernel device paths to probe for `tpm_path`. An explicit `/dev/` path (optionally written with a
66+ /// `device` TCTI prefix) resolves to just itself, anything else falls back to the standard nodes.
67+ fn tpm_device_candidates ( tpm_path : & str ) -> Vec < & str > {
68+ let conf = tpm_path. strip_prefix ( "device:" ) . unwrap_or ( tpm_path) ;
69+ if conf. starts_with ( "/dev/" ) {
70+ vec ! [ conf]
71+ } else {
72+ vec ! [ "/dev/tpmrm0" , "/dev/tpm0" ]
73+ }
74+ }
75+
76+ /// True when a kernel TPM device exists for `tpm_path`. Socket TCTIs such as swtpm and mssim are not
77+ /// detected because the lab does not use them.
78+ pub ( crate ) fn tpm_present ( tpm_path : & str ) -> bool {
79+ // try_exists tells a clean absent (Ok(false)) apart from an IO error. On error we assume the
80+ // device is present rather than silently treating the host as having no TPM.
81+ let dev_exists = |path : & str | {
82+ Path :: new ( path) . try_exists ( ) . unwrap_or_else ( |e| {
83+ tracing:: warn!( path = %path, error = %e, "tpm_present: cannot stat TPM device; assuming present" ) ;
84+ true
85+ } )
86+ } ;
87+ tpm_device_candidates ( tpm_path)
88+ . iter ( )
89+ . any ( |& p| dev_exists ( p) )
90+ }
91+
6592/// Clears the TPM storage hierarchies via TPM2_Clear (lockout authorization), after dictionary
6693/// lockout setup.
6794pub ( crate ) fn clear_tpm ( tpm_path : & str ) -> Result < ( ) , CarbideClientError > {
@@ -164,4 +191,35 @@ mod tests {
164191 ) ;
165192 }
166193 }
194+
195+ #[ test]
196+ fn tpm_device_candidates_cases ( ) {
197+ let cases: & [ ( & str , & [ & str ] ) ] = & [
198+ // explicit device file, with and without the device prefix
199+ ( "device:/dev/tpmrm0" , & [ "/dev/tpmrm0" ] ) ,
200+ ( "device:/dev/tpm0" , & [ "/dev/tpm0" ] ) ,
201+ ( "/dev/tpmrm0" , & [ "/dev/tpmrm0" ] ) ,
202+ // socket and default TCTIs fall back to the standard nodes
203+ (
204+ "mssim:host=localhost,port=2321" ,
205+ & [ "/dev/tpmrm0" , "/dev/tpm0" ] ,
206+ ) ,
207+ ( "swtpm:path=/tmp/swtpm-sock" , & [ "/dev/tpmrm0" , "/dev/tpm0" ] ) ,
208+ ( "device:" , & [ "/dev/tpmrm0" , "/dev/tpm0" ] ) ,
209+ ( "" , & [ "/dev/tpmrm0" , "/dev/tpm0" ] ) ,
210+ ] ;
211+ for ( input, want) in cases {
212+ assert_eq ! ( tpm_device_candidates( input) , * want, "input={input:?}" ) ;
213+ }
214+ }
215+
216+ #[ test]
217+ fn tpm_present_probes_explicit_device_path ( ) {
218+ // /dev/null always exists on the Linux hosts scout runs on, so an explicit path pointing at
219+ // it reports present, and a bogus /dev path reports absent.
220+ assert ! ( tpm_present( "device:/dev/null" ) ) ;
221+ assert ! ( tpm_present( "/dev/null" ) ) ;
222+ assert ! ( !tpm_present( "device:/dev/forge_scout_nonexistent_tpm" ) ) ;
223+ assert ! ( !tpm_present( "/dev/forge_scout_nonexistent_tpm" ) ) ;
224+ }
167225}
0 commit comments