Skip to content

Commit 5119f74

Browse files
zulinx86kalyazin
authored andcommitted
feat(tool): Add fingerprint dump feature
Add a feature to dump a fingerprint consisting of guest CPU config and host information (fingerprint version, kernel version, microcode version, etc.). Signed-off-by: Takahiro Itazuri <[email protected]>
1 parent 969377f commit 5119f74

File tree

5 files changed

+198
-2
lines changed

5 files changed

+198
-2
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cpu-template-helper/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ license = "Apache-2.0"
1010

1111
[dependencies]
1212
clap = { version = "4.2.3", features = ["derive", "string"] }
13+
libc = "0.2.117"
14+
serde = { version = "1.0.136", features = ["derive"] }
1315
serde_json = "1.0.78"
1416
thiserror = "1.0.32"
1517

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::fs::read_to_string;
5+
use std::sync::{Arc, Mutex};
6+
7+
use vmm::Vmm;
8+
9+
use crate::fingerprint::Fingerprint;
10+
11+
#[derive(Debug, thiserror::Error)]
12+
pub enum Error {
13+
/// Failed to dump CPU configuration.
14+
#[error("Failed to dump CPU config: {0}")]
15+
DumpCpuConfig(#[from] crate::template::dump::Error),
16+
/// Failed to read sysfs file.
17+
#[error("Failed to read {0}: {1}")]
18+
ReadSysfsFile(String, std::io::Error),
19+
/// Failed to get kernel version.
20+
#[error("Failed to get kernel version: {0}")]
21+
GetKernelVersion(std::io::Error),
22+
/// Shell command failed.
23+
#[error("`{0}` failed: {1}")]
24+
ShellCommand(String, String),
25+
}
26+
27+
pub fn dump(vmm: Arc<Mutex<Vmm>>) -> Result<Fingerprint, Error> {
28+
Ok(Fingerprint {
29+
firecracker_version: crate::utils::CPU_TEMPLATE_HELPER_VERSION.to_string(),
30+
kernel_version: get_kernel_version()?,
31+
#[cfg(target_arch = "x86_64")]
32+
microcode_version: read_sysfs_file("/sys/devices/system/cpu/cpu0/microcode/version")?,
33+
#[cfg(target_arch = "aarch64")]
34+
microcode_version: read_sysfs_file(
35+
"/sys/devices/system/cpu/cpu0/regs/identification/revidr_el1",
36+
)?,
37+
bios_version: read_sysfs_file("/sys/devices/virtual/dmi/id/bios_version")?,
38+
// TODO: Replace this with `read_sysfs_file("/sys/devices/virtual/dmi/id/bios_release")`
39+
// after the end of kernel 4.14 support.
40+
// https://github.com/firecracker-microvm/firecracker/issues/3677
41+
bios_revision: run_shell_command(
42+
"dmidecode -t bios | grep \"BIOS Revision\" | cut -d':' -f2 | tr -d ' \\n'",
43+
)?,
44+
guest_cpu_config: crate::template::dump::dump(vmm)?,
45+
})
46+
}
47+
48+
fn get_kernel_version() -> Result<String, Error> {
49+
// SAFETY: An all-zeroed value for `libc::utsname` is valid.
50+
let mut name: libc::utsname = unsafe { std::mem::zeroed() };
51+
// SAFETY: The passed arg is a valid mutable reference of `libc::utsname`.
52+
let ret = unsafe { libc::uname(&mut name) };
53+
if ret < 0 {
54+
return Err(Error::GetKernelVersion(std::io::Error::last_os_error()));
55+
}
56+
57+
// SAFETY: The fields of `libc::utsname` are terminated by a null byte ('\0').
58+
// https://man7.org/linux/man-pages/man2/uname.2.html
59+
let c_str = unsafe { std::ffi::CStr::from_ptr(name.release.as_ptr()) };
60+
// SAFETY: The `release` field is an array of `char` in C, in other words, ASCII.
61+
let version = c_str.to_str().unwrap();
62+
Ok(version.to_string())
63+
}
64+
65+
fn read_sysfs_file(path: &str) -> Result<String, Error> {
66+
let s = read_to_string(path).map_err(|err| Error::ReadSysfsFile(path.to_string(), err))?;
67+
Ok(s.trim_end_matches('\n').to_string())
68+
}
69+
70+
fn run_shell_command(cmd: &str) -> Result<String, Error> {
71+
let output = std::process::Command::new("sh")
72+
.args(["-c", cmd])
73+
.output()
74+
.map_err(|err| Error::ShellCommand(cmd.to_string(), err.to_string()))?;
75+
if !output.status.success() {
76+
return Err(Error::ShellCommand(
77+
cmd.to_string(),
78+
format!(
79+
"code: {:?}\nstdout: {}\nstderr: {}",
80+
output.status.code(),
81+
std::str::from_utf8(&output.stdout).unwrap(),
82+
std::str::from_utf8(&output.stderr).unwrap(),
83+
),
84+
));
85+
}
86+
Ok(std::str::from_utf8(&output.stdout).unwrap().to_string())
87+
}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use super::*;
92+
93+
#[test]
94+
fn test_get_kernel_version() {
95+
// `get_kernel_version()` should always succeed.
96+
get_kernel_version().unwrap();
97+
}
98+
99+
#[test]
100+
fn test_read_valid_sysfs_file() {
101+
// The sysfs file for microcode version should exist and be read.
102+
let valid_sysfs_path = "/sys/devices/virtual/dmi/id/bios_version";
103+
read_sysfs_file(valid_sysfs_path).unwrap();
104+
}
105+
106+
#[test]
107+
fn test_read_invalid_sysfs_file() {
108+
let invalid_sysfs_path = "/sys/invalid/path";
109+
if read_sysfs_file(invalid_sysfs_path).is_ok() {
110+
panic!("Should fail with `No such file or directory`");
111+
}
112+
}
113+
114+
#[test]
115+
fn test_run_valid_shell_command() {
116+
let valid_cmd = "ls";
117+
run_shell_command(valid_cmd).unwrap();
118+
}
119+
120+
#[test]
121+
fn test_run_invalid_shell_command() {
122+
let invalid_cmd = "unknown_command";
123+
if run_shell_command(invalid_cmd).is_ok() {
124+
panic!("Should fail with `unknown_command: not found`");
125+
}
126+
}
127+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use serde::{Deserialize, Serialize};
5+
use vmm::cpu_config::templates::CustomCpuTemplate;
6+
7+
pub mod dump;
8+
9+
#[derive(Serialize, Deserialize)]
10+
pub struct Fingerprint {
11+
pub firecracker_version: String,
12+
pub kernel_version: String,
13+
pub microcode_version: String,
14+
pub bios_version: String,
15+
pub bios_revision: String,
16+
pub guest_cpu_config: CustomCpuTemplate,
17+
}

src/cpu-template-helper/src/main.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::path::PathBuf;
77
use clap::{Parser, Subcommand};
88
use vmm::cpu_config::templates::{CustomCpuTemplate, GetCpuTemplate, GetCpuTemplateError};
99

10+
mod fingerprint;
1011
mod template;
1112
mod utils;
1213

@@ -16,6 +17,8 @@ const EXIT_CODE_ERROR: i32 = 1;
1617
enum Error {
1718
#[error("Failed to operate file: {0}")]
1819
FileIo(#[from] std::io::Error),
20+
#[error("{0}")]
21+
FingerprintDump(#[from] fingerprint::dump::Error),
1922
#[error("CPU template is not specified: {0}")]
2023
NoCpuTemplate(#[from] GetCpuTemplateError),
2124
#[error("Failed to serialize/deserialize JSON file: {0}")]
@@ -76,7 +79,17 @@ enum TemplateOperation {
7679
}
7780

7881
#[derive(Subcommand)]
79-
enum FingerprintOperation {}
82+
enum FingerprintOperation {
83+
/// Dump fingerprint consisting of host-related information and guest CPU config.
84+
Dump {
85+
/// Path of fingerprint config file.
86+
#[arg(short, long, value_name = "PATH")]
87+
config: PathBuf,
88+
/// Path of output file.
89+
#[arg(short, long, value_name = "PATH", default_value = "fingerprint.json")]
90+
output: PathBuf,
91+
},
92+
}
8093

8194
fn run(cli: Cli) -> Result<()> {
8295
match cli.command {
@@ -120,7 +133,17 @@ fn run(cli: Cli) -> Result<()> {
120133
template::verify::verify(cpu_template, cpu_config)?;
121134
}
122135
},
123-
Command::Fingerprint(_) => {}
136+
Command::Fingerprint(op) => match op {
137+
FingerprintOperation::Dump { config, output } => {
138+
let config = read_to_string(config)?;
139+
let (vmm, _) = utils::build_microvm_from_config(&config)?;
140+
141+
let fingerprint = fingerprint::dump::dump(vmm)?;
142+
143+
let fingerprint_json = serde_json::to_string_pretty(&fingerprint)?;
144+
write(output, fingerprint_json)?;
145+
}
146+
},
124147
}
125148

126149
Ok(())
@@ -326,4 +349,29 @@ mod tests {
326349

327350
run(cli).unwrap();
328351
}
352+
353+
#[test]
354+
fn test_fingerprint_dump_command() {
355+
let kernel_image_path = kernel_image_path(None);
356+
let rootfs_file = TempFile::new().unwrap();
357+
let config_file = generate_config_file(
358+
&kernel_image_path,
359+
rootfs_file.as_path().to_str().unwrap(),
360+
None,
361+
);
362+
let output_file = TempFile::new().unwrap();
363+
364+
let args = vec![
365+
"cpu-template-helper",
366+
"fingerprint",
367+
"dump",
368+
"--config",
369+
config_file.as_path().to_str().unwrap(),
370+
"--output",
371+
output_file.as_path().to_str().unwrap(),
372+
];
373+
let cli = Cli::parse_from(args);
374+
375+
run(cli).unwrap();
376+
}
329377
}

0 commit comments

Comments
 (0)