blob: a2d14259620c73d87c9f81a2f05ad9747d944810 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::path::Path;
use log::error;
use log::info;
use crate::context::Context;
use crate::error::HwsecError;
use crate::gsc::gsc_get_name;
use crate::gsc::gsctool_cmd_successful;
use crate::gsc::run_gsctool_cmd;
use crate::gsc::GSCTOOL_CMD_NAME;
const MAX_RETRIES: u32 = 3;
const SLEEP_DURATION: u64 = 70;
pub fn gsc_update(ctx: &mut impl Context, root: &Path) -> Result<(), HwsecError> {
info!("Starting");
let options: Vec<&str>;
// determine the best way to communicate with the GSC.
if gsctool_cmd_successful(ctx, vec!["--fwver", "--systemdev"]) {
info!("Will use /dev/tpm0");
options = vec!["--systemdev"];
} else if gsctool_cmd_successful(ctx, vec!["--fwver", "--trunks_send"]) {
info!("Will use trunks_send");
options = vec!["--trunks_send"];
} else {
error!("Could not communicate with GSC");
return Err(HwsecError::GsctoolError(1));
}
let gsc_image = root.join(gsc_get_name(ctx, &options[..])?);
let gsc_image_str = gsc_image.to_str().ok_or_else(|| {
error!("Cannot convert {} to string.", gsc_image.display());
HwsecError::GsctoolResponseBadFormatError
})?;
if !ctx.path_exists(gsc_image_str) {
info!("{} not found, quitting.", gsc_image.display());
return Err(HwsecError::GsctoolError(1));
}
let mut exit_status: i32 = 0;
for retries in 0..MAX_RETRIES {
if retries != 0 {
// Need to sleep for at least a minute to get around GSC update throttling:
// it rejects repeat update attempts happening sooner than 60 seconds after
// the previous one.
ctx.sleep(SLEEP_DURATION);
}
let exe_result =
run_gsctool_cmd(ctx, [&options[..], &["--upstart", gsc_image_str]].concat())?;
exit_status = exe_result.status.code().unwrap();
// Exit status values 2 or below indicate successful update, nonzero
// values mean that reboot is required for the new version to kick in.
if exit_status <= 2 {
// Callers of this script do not care about the details and consider any
// non-zero value an error.
info!("success ({})", exit_status);
exit_status = 0;
break;
}
info!(
"{} (options: {:?}) attempt {} error {}",
GSCTOOL_CMD_NAME,
options,
retries + 1,
exit_status
);
let output = format!(
"{}{}",
std::str::from_utf8(&exe_result.stdout)
.map_err(|_| HwsecError::GsctoolResponseBadFormatError)?,
std::str::from_utf8(&exe_result.stderr)
.map_err(|_| HwsecError::GsctoolResponseBadFormatError)?
);
// Log output text one line at a time, otherwise they are all concatenated
// into a single long entry with messed up line breaks.
for line in output.lines() {
info!("{}", line);
}
}
if exit_status == 0 {
Ok(())
} else {
Err(HwsecError::GsctoolError(exit_status))
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use crate::context::mock::MockContext;
use crate::context::Context;
use crate::error::HwsecError;
use crate::gsc::gsc_update;
use crate::gsc::update::MAX_RETRIES;
use crate::gsc::GSC_IMAGE_BASE_NAME;
#[test]
fn test_gsc_update_could_not_communicate_with_cr50() {
let mut mock_ctx = MockContext::new();
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--systemdev"], 1, "", "");
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--trunks_send"], 1, "", "");
let result = gsc_update(&mut mock_ctx, Path::new("/"));
assert_eq!(result, Err(HwsecError::GsctoolError(1)));
}
#[test]
fn test_gsc_update_image_not_exist() {
let mut mock_ctx = MockContext::new();
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--systemdev"], 0, "", "");
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--board_id"],
0,
"Board ID space: 43425559:bcbdaaa6:00007f10",
"",
);
let result = gsc_update(&mut mock_ctx, Path::new("/"));
assert_eq!(result, Err(HwsecError::GsctoolError(1)));
}
#[test]
fn test_gsc_update_ok() {
let mut mock_ctx = MockContext::new();
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--systemdev"], 0, "", "");
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--board_id"],
0,
"Board ID space: 43425559:bcbdaaa6:00007f10",
"",
);
let path = "/".to_owned() + &String::from(GSC_IMAGE_BASE_NAME) + ".prepvt";
mock_ctx.create_path(path.as_str());
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--upstart", path.as_str()],
0,
"",
"",
);
let result = gsc_update(&mut mock_ctx, Path::new("/"));
assert_eq!(result, Ok(()));
}
#[test]
fn test_gsc_update_failed_exceed_max_retries() {
let mut mock_ctx = MockContext::new();
mock_ctx
.cmd_runner()
.add_gsctool_interaction(vec!["--fwver", "--systemdev"], 0, "", "");
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--board_id"],
0,
"Board ID space: 43425559:bcbdaaa6:00007f10",
"",
);
let path = "/".to_owned() + &String::from(GSC_IMAGE_BASE_NAME) + ".prepvt";
mock_ctx.create_path(path.as_str());
for _ in 0..MAX_RETRIES {
mock_ctx.cmd_runner().add_gsctool_interaction(
vec!["--systemdev", "--upstart", path.as_str()],
3,
"",
"",
);
}
let result = gsc_update(&mut mock_ctx, Path::new("/"));
assert_eq!(result, Err(HwsecError::GsctoolError(3)));
}
}