1 // Copyright 2023 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //! Explains AP RO verification status codes from plain integers.
5 //!
6 //! AP RO verification result integers take two forms:
7 //!
8 //! - A `u32` derived from `ApRoVerificationResult` in capsules.
9 //! This puts a `VerifyError` in the top 8 bits, and stores extra
10 //! detail in the lower 24 bits. This is what is found in the
11 //! Platform.TPM.ExpandedApRoVerificationStatus UMA metric.
12 //!
13 //! - A `u8` derived from `ApRoVerificationTpmvStatus`, in syscalls.
14 //! This is the output of `gsctool -B` and found in the
15 //! Platform.Ti50.ARVStatus metric. It follows a different set of
16 //! statuses from Cr50, but is in the same namespace.
17
18 use ap_ro_errs::{
19 ApRoVerificationResult, ApRoVerificationTpmvStatus, BadValue, CryptoAlgorithmSource,
20 DigestLocation, InternalErrorSource, SignatureLocation, VerifyError, VerifyErrorCode,
21 VersionMismatchSource,
22 };
23 use std::fmt::Display;
24
25 #[derive(Debug)]
26 enum ExplainError {
27 WrongArgCount,
28 InvalidArg,
29 }
30
31 impl ExplainError {
to_str(self) -> &'static str32 fn to_str(self) -> &'static str {
33 match self {
34 ExplainError::WrongArgCount => "Expected a single arg, hex or decimal",
35 ExplainError::InvalidArg => {
36 "Unrecognized argument format, expected a single hex or decimal argument"
37 }
38 }
39 }
40 }
41
parse_arg(mut arg: &str) -> Result<u32, ExplainError>42 fn parse_arg(mut arg: &str) -> Result<u32, ExplainError> {
43 if let Some(stripped) = arg.strip_prefix("0x").or_else(|| arg.strip_prefix('x')) {
44 arg = stripped;
45 } else {
46 // First try to parse as base-10, unless there's a hex prefix.
47 if let Ok(out) = arg.parse() {
48 return Ok(out);
49 } else if let Ok(out) = arg.parse::<i32>() {
50 // Try parse negative numbers, and if successful, cast to u32
51 return Ok(out as u32);
52 }
53 }
54 // If parsing as base-10 failed, try base-16.
55 u32::from_str_radix(arg, 16).map_err(|_| ExplainError::InvalidArg)
56 }
57
get_code_from_args() -> Result<u32, ExplainError>58 fn get_code_from_args() -> Result<u32, ExplainError> {
59 let mut args = std::env::args();
60 let _app_name = args.next();
61 let Some(out) = args.next() else {
62 return Err(ExplainError::WrongArgCount);
63 };
64 if args.next().is_some() {
65 return Err(ExplainError::WrongArgCount);
66 }
67 parse_arg(&out)
68 }
69
expected_less_detail(detail: &[u8], at: &str)70 fn expected_less_detail(detail: &[u8], at: &str) {
71 println!(
72 "This tool expects the expanded status to have no detail in the {at},\n\
73 but instead it has {detail:#02x?}.\n\
74 Consider updating this tool."
75 );
76 }
77
explain_code(code: u32)78 fn explain_code(code: u32) {
79 /// Bit that indicates that AP RO verification has succeeded at least once on device.
80 const LATCH_SUCCESS: u32 = 1 << 31;
81
82 /// Bit that indicates that the AllowUnverifiedRo CCD capability was enabled on device at the
83 /// time that the extended status was queried.
84 const ALLOW_UNVERIFIED_RO_ENABLED: u32 = 1 << 30;
85
86 /// Bit that indicates that the AllowUnverifiedRo CCD capability was set to never at the
87 /// time that the extended status was queried.
88 const ALLOW_UNVERIFIED_RO_NEVER: u32 = 1 << 29;
89
90 println!("Code: 0x{code:08x}");
91 if let Ok(bottom) = u8::try_from(code) {
92 println!("This is likely a status code from TPM");
93 let text = ApRoVerificationTpmvStatus::from_u8(bottom)
94 .map_or("Unknown to this tool", |x| x.to_str());
95 println!("Description: {text}");
96 return;
97 }
98 if code == ApRoVerificationResult::from(Ok(())).into() {
99 println!("It is a SUCCESS status");
100 return;
101 }
102 let result_code =
103 code & !LATCH_SUCCESS & !ALLOW_UNVERIFIED_RO_ENABLED & !ALLOW_UNVERIFIED_RO_NEVER;
104 // Remove latch value before trying to parse the result
105 let result = ApRoVerificationResult::from(result_code);
106 let err = Result::from(result).expect_err("Already checked for success.");
107 if err == VerifyError::Internal(InternalErrorSource::CouldNotDeserialize, 0) {
108 println!("The code is not recognized by this tool");
109 return;
110 }
111 println!("This is likely an expanded error code");
112 if code & LATCH_SUCCESS != 0 {
113 println!("THIS ERROR OCURRED AFTER SUCCESS WAS LATCHED!")
114 }
115 if code & ALLOW_UNVERIFIED_RO_ENABLED != 0 {
116 println!("The system had the `AllowUnverifiedRo` capability enabled at query time")
117 }
118 if code & ALLOW_UNVERIFIED_RO_NEVER != 0 {
119 println!("The system had the `AllowUnverifiedRo` capability set to Never at query time")
120 }
121 let code = err.code();
122 let detail = err.detail();
123
124 // TODO(kupiakos): Instead of displaying `Debug` for codes, display the
125 // contents of the docstrings.
126 // This would be best done with a proc macro on the enums.
127
128 println!("Top level code: {:?} ({})", code, code as u8);
129 match code {
130 VerifyErrorCode::TooBig
131 | VerifyErrorCode::InconsistentGscvd
132 | VerifyErrorCode::InconsistentKeyblock
133 | VerifyErrorCode::MissingGscvd
134 | VerifyErrorCode::SettingNotProvisioned
135 | VerifyErrorCode::OutOfMemory
136 | VerifyErrorCode::InconsistentKey
137 | VerifyErrorCode::SpiRead
138 | VerifyErrorCode::NonZeroGbbFlags
139 | VerifyErrorCode::WrongRootKey => {
140 if let [0, 0, 0] = detail {
141 println!("This expanded status carries no extra detail as expected.");
142 } else {
143 expected_less_detail(&detail, "bottom 24 bits");
144 }
145 }
146
147 VerifyErrorCode::FailedVerification => {
148 let top = detail[0];
149 if top == 0 {
150 println!("Failed due to signature verification failure.");
151 let sig_location = detail[1];
152 if let Some(sig_location) = SignatureLocation::from_u8(sig_location) {
153 println!("Where was the failing signature: {sig_location:?}");
154 } else {
155 println!("Unrecognized signature location ({sig_location})");
156 }
157
158 if detail[2] != 0 {
159 expected_less_detail(&detail[2..], "bottom 8 bits");
160 }
161 return;
162 }
163 println!("Failed due to a digest mismatch with the contents in AP flash.");
164 let num_root_key_hashes = (top >> 4) - 1;
165 println!(
166 "The Ti50 image this came from had {num_root_key_hashes} hardcoded root key hashes."
167 );
168 let digest_location = top & 0x0F;
169 if let Some(digest_location) = DigestLocation::from_u8(digest_location) {
170 println!("Which digest failed: {digest_location:?}");
171 } else {
172 println!("Unrecognized digest location ({digest_location})");
173 }
174 let [_, got, expected] = detail;
175 println!("The first octet of the calculated hash: 0x{got:02x}");
176 println!("The first octet of the expected hash: 0x{expected:02x}");
177 }
178 VerifyErrorCode::VersionMismatch => {
179 let [source, none0, none1] = detail;
180 if none0 != 0 || none1 != 0 {
181 expected_less_detail(&[none0, none1], "bottom 16 bits");
182 }
183 if let Some(known) = VersionMismatchSource::from_u8(source) {
184 println!("Version mismatch source: {:?}", known);
185 } else {
186 println!("Unknown version mismatch source ({})", source);
187 }
188 }
189 VerifyErrorCode::FailedStatusRegister1
190 | VerifyErrorCode::FailedStatusRegister2
191 | VerifyErrorCode::FailedStatusRegister3 => {
192 let register_index = match code {
193 VerifyErrorCode::FailedStatusRegister1 => 1,
194 VerifyErrorCode::FailedStatusRegister2 => 2,
195 VerifyErrorCode::FailedStatusRegister3 => 3,
196 _ => unreachable!(),
197 };
198 let [got, value, mask] = detail;
199 println!("Status Register {register_index} did not have an expected value.");
200 println!("The flash chip on the system returned 0x{got:02x}");
201 println!("The provisioned value is: 0x{value:02x}");
202 println!("The provisioned mask is: 0x{mask:02x}");
203 // Mask of 0 is a special case for an error getting provisioned value
204 if mask == 0 {
205 if let Some(e) = BadValue::from_u8(value) {
206 println!(
207 "Error getting provisioned value due to it being {}",
208 e.to_string()
209 );
210 } else {
211 println!("Unknown error getting provision value: {value}");
212 }
213 } else if got & mask == value & mask {
214 println!("This doesn't make sense, because it should have passed.");
215 } else {
216 println!(
217 "It failed because 0x{got:02x} & 0x{mask:02x} != 0x{value:02x} & 0x{mask:02x}"
218 );
219 }
220 }
221 VerifyErrorCode::UnsupportedCryptoAlgorithm => {
222 let [source, hi, lo] = detail;
223 let unknown_code = u16::from_be_bytes([hi, lo]);
224 println!("An unknown crypto algorithm was encountered: {unknown_code}");
225 if let Some(source) = CryptoAlgorithmSource::from_u8(source) {
226 println!("Location of unknown crypto algorithm: {source:?}");
227 } else {
228 println!("Unknown crypto algorithm location ({source})");
229 }
230 }
231 VerifyErrorCode::Internal => {
232 let [source, hi, lo] = detail;
233 let Some(source) = InternalErrorSource::from_u8(source) else {
234 println!("Unknown internal error source ({source})");
235 return;
236 };
237 let code = u16::from_be_bytes([hi, lo]);
238 match source {
239 InternalErrorSource::Kernel => {
240 println!("Internal error caused by the kernel");
241 let code_str = match code {
242 1 => Some("FAIL"),
243 2 => Some("BUSY"),
244 3 => Some("ALREADY"),
245 4 => Some("OFF"),
246 5 => Some("RESERVE"),
247 6 => Some("INVAL"),
248 7 => Some("SIZE"),
249 8 => Some("CANCEL"),
250 9 => Some("NOMEM"),
251 10 => Some("NOSUPPORT"),
252 11 => Some("NODEVICE"),
253 12 => Some("UNINSTALLED"),
254 13 => Some("NOACK"),
255 _ => None,
256 };
257 if let Some(code_str) = code_str {
258 println!("Kernel error code {code} ({code_str})");
259 } else {
260 println!("Unknown kernel error code {code}");
261 }
262 }
263 InternalErrorSource::Crypto => {
264 println!("Error caused by the crypto engine");
265 let code = code as i16 as i32;
266 println!("Raw code: {code}");
267 println!("TODO: print out friendlier status, requires `enum_as` rework");
268 }
269 InternalErrorSource::VerifyFlashRanges => {
270 println!("Error occurred while verifying flash ranges");
271 }
272 InternalErrorSource::CouldNotDeserialize => {
273 unreachable!("Handled by \"code is not recognized by this tool\"")
274 }
275 }
276 }
277 VerifyErrorCode::BoardIdMismatch => {
278 let [got_byte, got_nibble_expected_nibble, expected_byte] = detail;
279 let got = (got_byte as u32) << 4 | (got_nibble_expected_nibble as u32) >> 4;
280 let expected =
281 ((got_nibble_expected_nibble & 0x0f) as u32) << 8 | (expected_byte as u32);
282
283 struct Display3BoardIdNibbles(u32);
284 impl Display for Display3BoardIdNibbles {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 use core::fmt::Write as _;
287
288 write!(f, "{:03x} (first char: ", self.0)?;
289 for b in std::ascii::escape_default((self.0 >> 4) as u8) {
290 f.write_char(b.into())?;
291 }
292 if self.0 == 0x5a5 {
293 write!(f, ", probably ZZCR")?;
294 }
295 write!(f, ")")
296 }
297 }
298
299 println!("There was a mismatch between the board ID in the GSCVD and on the board.");
300 println!("The detailed code carries the top 12 bits of the ID on AP flash and GSC.");
301 println!("In GSCVD: {}", Display3BoardIdNibbles(got));
302 println!("On GSC: {}", Display3BoardIdNibbles(expected));
303 }
304 }
305 }
306
main()307 fn main() {
308 if let Err(e) = get_code_from_args().map(explain_code) {
309 println!("{}", e.to_str());
310 }
311 }
312