blob: 451fb48b5d994e575a896a0e2eefa203b8dd2872 [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 core::time;
use std::fs;
use std::io;
use std::path::Path;
use std::thread;
use log::error;
use super::get_challenge_string;
use super::get_gbb_flags;
use super::get_hwid;
use super::set_gbb_flags;
use crate::command_runner::CommandRunner;
use crate::context::Context;
use crate::cr50::clear_terminal;
use crate::cr50::gsctool_cmd_successful;
use crate::error::HwsecError;
const RMA_SERVER: &str = "https://www.google.com/chromeos/partner/console/cr50reset";
const MAX_RETRIES: i32 = 3;
const RETRY_DELAY: i32 = 10;
const FRECON_PID_FILE: &str = "/run/frecon/pid";
fn gbb_force_dev_mode(ctx: &mut impl Context) -> Result<u32, HwsecError> {
// TODO: call flashrom with library instead of commands
ctx.cmd_runner()
.run(
"flashrom",
vec!["-p", "host", "--wp-disable", "--wp-range", "0,0"],
)
.map_err(|_| {
error!("Failed to run flashrom");
HwsecError::CommandRunnerError
})?;
let flags: u32 = get_gbb_flags(ctx)?;
let new_flags: u32 = flags | 0x8;
set_gbb_flags(ctx, new_flags)?;
Ok(new_flags)
}
pub fn cr50_reset(ctx: &mut impl Context) -> Result<(), HwsecError> {
const WAIT_TO_ENTER_RMA_SECS: u64 = 2;
const SECS_IN_A_DAY: u64 = 86400;
let frecon_pid = fs::read_to_string(FRECON_PID_FILE).map_err(|_| {
error!("Failed to read {}", FRECON_PID_FILE);
HwsecError::FileError
})?;
let chg_str_path = format!("/proc/{}/root", frecon_pid);
if !Path::new(&chg_str_path).exists() {
error!("frecon not running. Can't display qrcode.");
return Err(HwsecError::FileError);
}
let hwid = get_hwid(ctx).map_err(|e| {
error!("Failed to get hwid.");
e
})?;
let challenge_string = get_challenge_string(ctx).map_err(|e| {
error!("Failed to get challenge string.");
e
})?;
if challenge_string.is_empty() {
error!(r"Challenge wasn't generated. CR50 might need updating.");
return Err(HwsecError::GsctoolResponseBadFormatError);
}
clear_terminal();
println!("Challenge:");
println!("{}", challenge_string);
let challenge_string = challenge_string.replace(['\n', ' '], "");
let chstr = format!(
"{}?challenge={}&hwid={}",
RMA_SERVER, challenge_string, hwid
);
println!("\nURL: {}", chstr);
// TODO: replace qrencode command with qrcode library like this
//
// let qrcode = QrCode::new(challenge_string).unwrap();
// let image = qrcode.render::<Luma<u8>>().build();
// image.save(format!("{chg_str_path}/chg.png")).unwrap();
ctx.cmd_runner()
.run(
"qrencode",
vec![
"-s",
"5",
"-o",
&format!("{}/chg.png", chg_str_path),
&chstr,
],
)
.map_err(|_| {
error!("Failed to qrencode.");
HwsecError::QrencodeError
})?;
fs::write("/run/frecon/vt0", "\x0033]image:file=/chg.png\x0033\\").map_err(|_| {
error!("Failed to write to /run/frecon/vt0");
HwsecError::FileError
})?;
for _ in 0..MAX_RETRIES {
print!("\nEnter authorization code: ");
let mut auth_code = String::new();
while io::stdin().read_line(&mut auth_code).is_err() {
println!("Please only enter ASCII characters.");
}
let auth_code = auth_code.to_uppercase();
if gsctool_cmd_successful(ctx, vec!["--trunks_send", "--rma_auth", &auth_code]) {
println!("The system will reboot shortly.");
thread::sleep(time::Duration::from_secs(WAIT_TO_ENTER_RMA_SECS));
gbb_force_dev_mode(ctx).map_err(|e| {
error!("gbb_force_dev_mode failed.");
e
})?;
// TODO: reboot with function call instead
ctx.cmd_runner()
.run("reboot", Vec::<&str>::new())
.map_err(|_| {
error!("Failed to reboot.");
HwsecError::SystemRebootError
})?;
thread::sleep(time::Duration::from_secs(SECS_IN_A_DAY));
}
println!("Invalid authorization code. Please try again.\n");
}
println!("Number of retries exceeded. Another qrcode will generate in 10s.");
for _ in 0..RETRY_DELAY {
print!(".");
thread::sleep(time::Duration::from_secs(1));
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use crate::command_runner::MockCommandInput;
use crate::command_runner::MockCommandOutput;
use crate::context::mock::MockContext;
use crate::context::Context;
use crate::cr50::reset::gbb_force_dev_mode;
use crate::error::HwsecError;
#[test]
fn test_gbb_force_dev_mode_successful() {
const NUM_TEST_CASES: u32 = 100;
let mut mock_ctx = MockContext::new();
for _ in 0..NUM_TEST_CASES {
let old_flag = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.subsec_nanos();
mock_ctx.cmd_runner().add_expectation(
MockCommandInput::new(
"flashrom",
vec!["-p", "host", "--wp-disable", "--wp-range", "0,0"],
),
MockCommandOutput::new(0, "", ""),
);
mock_ctx
.cmd_runner()
.add_successful_get_gbb_flags_interaction(old_flag);
mock_ctx
.cmd_runner()
.add_successful_set_gbb_flags_interaction(old_flag | 0x8);
let new_flag = gbb_force_dev_mode(&mut mock_ctx);
assert_eq!(new_flag, Ok(old_flag | 0x8));
}
}
#[test]
fn test_gbb_force_dev_mode_failed_to_get_flag() {
let mut mock_ctx = MockContext::new();
mock_ctx.cmd_runner().add_expectation(
MockCommandInput::new(
"flashrom",
vec!["-p", "host", "--wp-disable", "--wp-range", "0,0"],
),
MockCommandOutput::new(0, "", ""),
);
mock_ctx.cmd_runner().add_expectation(
MockCommandInput::new("futility", vec!["gbb", "--get", "--flash", "--flags"]),
MockCommandOutput::new(0, "Oops... no flag ><", ""),
);
let new_flag = gbb_force_dev_mode(&mut mock_ctx);
assert_eq!(new_flag, Err(HwsecError::VbootScriptResponseBadFormatError));
}
// TODO (b/249022052): add unit tests for function cr50_reset
}