Skip to content

Commit 43ff5ed

Browse files
committed
[bug/5614743] fix: coderabbit fixes
1 parent 2d06c9b commit 43ff5ed

3 files changed

Lines changed: 90 additions & 52 deletions

File tree

crates/scout/src/platform.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,23 @@
1717

1818
use smbioslib::{SMBiosSystemInformation, table_load_from_device};
1919

20+
pub(crate) fn is_host_from_product_names<'a>(
21+
product_names: impl IntoIterator<Item = &'a str>,
22+
) -> bool {
23+
!product_names
24+
.into_iter()
25+
.any(|name| name.to_ascii_lowercase().contains("bluefield"))
26+
}
27+
2028
/// Returns `true` when scout is running on a managed host (as opposed to a DPU).
2129
pub(crate) fn is_host() -> bool {
2230
match table_load_from_device() {
23-
Ok(data) => data.any(|sys_info: SMBiosSystemInformation| {
24-
!sys_info
25-
.product_name()
26-
.to_string()
27-
.to_lowercase()
28-
.contains("bluefield")
29-
}),
31+
Ok(data) => {
32+
let product_names = data
33+
.map(|sys_info: SMBiosSystemInformation| sys_info.product_name().to_string())
34+
.collect::<Vec<_>>();
35+
is_host_from_product_names(product_names.iter().map(String::as_str))
36+
}
3037
Err(_err) => true,
3138
}
3239
}
@@ -35,6 +42,26 @@ pub(crate) fn is_host() -> bool {
3542
mod tests {
3643
use super::*;
3744

45+
#[test]
46+
fn is_host_from_product_names_cases() {
47+
let cases: &[(&[&str], bool)] = &[
48+
(&["DGX H100"], true),
49+
(&["BlueField-3 DPU"], false),
50+
(&["NVIDIA Bluefield 2"], false),
51+
(&[""], true),
52+
(&["DGX H100", "BlueField-3 DPU"], false),
53+
(&["DGX H100", "Other Platform"], true),
54+
];
55+
56+
for (product_names, want_host) in cases {
57+
assert_eq!(
58+
is_host_from_product_names(product_names.iter().copied()),
59+
*want_host,
60+
"product_names={product_names:?}"
61+
);
62+
}
63+
}
64+
3865
#[test]
3966
fn is_host_returns_bool_without_panicking() {
4067
let _ = is_host();

crates/scout/src/register.rs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,29 +57,21 @@ pub async fn run(
5757
crate::tpm::set_tpm_max_auth_fail()?;
5858

5959
// create tss context
60-
let mut tss_ctx = match attest::create_context_from_path(tpm_path) {
61-
Ok(ctx) => ctx,
62-
Err(e) => {
63-
let err = CarbideClientError::TpmError(format!("Could not create context: {e}"));
64-
if tpm::is_recoverable_tpm_client_error(&err) {
65-
tpm::recover_tpm_and_reboot(tpm_path)?;
66-
}
67-
return Err(err);
68-
}
69-
};
60+
let mut tss_ctx = attest::create_context_from_path(tpm_path)
61+
.map_err(|e| CarbideClientError::TpmError(format!("Could not create context: {e}")))?;
7062

7163
// CHANGETO - supply context externally
7264
hardware_info.tpm_description = attest::get_tpm_description(&mut tss_ctx);
7365

7466
let result = match attest::create_attest_key_info(&mut tss_ctx) {
7567
Ok(result) => result,
7668
Err(e) => {
77-
let err =
78-
CarbideClientError::TpmError(format!("Could not create AttestKeyInfo: {e}"));
79-
if tpm::is_recoverable_tpm_client_error(&err) {
69+
if tpm::should_attempt_tpm_recovery_for_attest_key_failure(&*e) {
8070
tpm::recover_tpm_and_reboot(tpm_path)?;
8171
}
82-
return Err(err);
72+
return Err(CarbideClientError::TpmError(format!(
73+
"Could not create AttestKeyInfo: {e}"
74+
)));
8375
}
8476
};
8577

crates/scout/src/tpm.rs

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
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};
2020
use std::path::Path;
2121
use std::process::Command;
2222

@@ -25,7 +25,7 @@ use tss_esapi::interface_types::session_handles::AuthSession;
2525

2626
use 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
3131
pub(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.
102125
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-
}
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

Comments
 (0)