| // 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))); |
| } |
| } |