1use crate::Error;
7use crate::FilePathArg;
8use crate::KeyPathArg;
9use crate::OpenMode;
10use crate::storage_backend::VmgsStorageBackend;
11use crate::vmgs_file_open;
12use crate::vmgs_json;
13use anyhow::Result;
14use clap::Args;
15use clap::Subcommand;
16use fs_err::File;
17use guid::Guid;
18use hcl_compat_uefi_nvram_storage::HclCompatNvram;
19use std::io::Write;
20use std::ops::Deref;
21use std::path::Path;
22use std::path::PathBuf;
23use std::str::FromStr;
24use ucs2::Ucs2LeVec;
25use uefi_nvram_specvars::ParsedNvramEntry;
26use uefi_nvram_specvars::boot_order;
27use uefi_nvram_specvars::parse_nvram_entry;
28use uefi_nvram_specvars::signature_list::SignatureList;
29use uefi_nvram_storage::NvramStorage;
30use uefi_specs::uefi::nvram::vars::EFI_GLOBAL_VARIABLE;
31use uefi_specs::uefi::time::EFI_TIME;
32use vmgs::Vmgs;
33
34#[derive(Args)]
35pub(crate) struct OutputArgs {
36 #[clap(short = 'o', long, alias = "outpath")]
38 output_path: Option<PathBuf>,
39 #[clap(short = 't', long)]
41 truncate: bool,
42}
43
44#[derive(Subcommand)]
45pub(crate) enum UefiNvramOperation {
46 Dump {
48 #[command(flatten)]
49 file_path: FilePathArg,
50 #[command(flatten)]
51 key_path: KeyPathArg,
52 #[command(flatten)]
53 output: OutputArgs,
54 },
55 DumpFromJson {
58 #[clap(short = 'f', long, alias = "filepath")]
60 file_path: PathBuf,
61 #[command(flatten)]
62 output: OutputArgs,
63 },
64 RemoveBootEntries {
66 #[command(flatten)]
67 file_path: FilePathArg,
68 #[command(flatten)]
69 key_path: KeyPathArg,
70 #[clap(short = 'n', long)]
72 dry_run: bool,
73 },
74 RemoveEntry {
76 #[command(flatten)]
77 file_path: FilePathArg,
78 #[command(flatten)]
79 key_path: KeyPathArg,
80 #[clap(short = 'n', long)]
82 name: String,
83 #[clap(short = 'v', long)]
85 vendor: String,
86 },
87}
88
89pub(crate) async fn do_command(operation: UefiNvramOperation) -> Result<(), Error> {
90 match operation {
91 UefiNvramOperation::Dump {
92 file_path,
93 key_path,
94 output,
95 } => {
96 vmgs_file_dump_nvram(
97 file_path.file_path,
98 output.output_path,
99 key_path.key_path,
100 output.truncate,
101 )
102 .await
103 }
104 UefiNvramOperation::DumpFromJson { file_path, output } => {
105 dump_nvram_from_json(file_path, output.output_path, output.truncate)
106 }
107 UefiNvramOperation::RemoveBootEntries {
108 file_path,
109 key_path,
110 dry_run,
111 } => vmgs_file_remove_boot_entries(file_path.file_path, key_path.key_path, dry_run).await,
112 UefiNvramOperation::RemoveEntry {
113 file_path,
114 key_path,
115 name,
116 vendor,
117 } => {
118 vmgs_file_remove_nvram_entry(file_path.file_path, key_path.key_path, name, vendor).await
119 }
120 }
121}
122
123async fn vmgs_file_dump_nvram(
125 file_path: impl AsRef<Path>,
126 output_path: Option<impl AsRef<Path>>,
127 key_path: Option<impl AsRef<Path>>,
128 truncate: bool,
129) -> Result<(), Error> {
130 let mut nvram_storage =
131 vmgs_file_open_nvram(file_path, key_path, OpenMode::ReadOnlyWarn).await?;
132
133 let mut out: Box<dyn Write + Send> = if let Some(path) = output_path {
134 Box::new(File::create(path.as_ref()).map_err(Error::DataFile)?)
135 } else {
136 Box::new(std::io::stdout())
137 };
138
139 dump_nvram(&mut nvram_storage, &mut out, truncate).await
140}
141
142async fn dump_nvram(
143 nvram_storage: &mut HclCompatNvram<VmgsStorageBackend>,
144 out: &mut impl Write,
145 truncate: bool,
146) -> Result<(), Error> {
147 let mut count = 0;
148 for entry in nvram_storage.iter().await? {
149 let meta = NvramEntryMetadata {
150 vendor: entry.vendor.to_string(),
151 name: entry.name.to_string(),
152 timestamp: Some(entry.timestamp),
153 attr: entry.attr,
154 size: entry.data.len(),
155 };
156 let entry = parse_nvram_entry(&meta.name, entry.data)?;
157 print_nvram_entry(out, &meta, &entry, truncate).map_err(Error::DataFile)?;
158 count += 1;
159 }
160
161 tracing::info!("Retrieved {count} NVRAM entries");
162 Ok(())
163}
164
165fn dump_nvram_from_json(
167 file_path: impl AsRef<Path>,
168 output_path: Option<impl AsRef<Path>>,
169 truncate: bool,
170) -> Result<(), Error> {
171 tracing::info!("Opening JSON file: {}", file_path.as_ref().display());
172 let file = File::open(file_path.as_ref()).map_err(Error::VmgsFile)?;
173
174 let runtime_state: vmgs_json::RuntimeState = serde_json::from_reader(file)?;
175
176 let nvram_state = runtime_state
177 .devices
178 .get(vmgs_json::BIOS_LOADER_DEVICE_ID)
179 .ok_or(Error::Json("Missing BIOS_LOADER_DEVICE_ID".to_string()))?
180 .states
181 .get("Nvram")
182 .ok_or(Error::Json("Missing Nvram".to_string()))?;
183
184 let vendors = match nvram_state {
185 vmgs_json::State::Nvram { vendors, .. } => vendors,
186 _ => return Err(Error::Json("Nvram state invalid".to_string())),
187 };
188
189 let mut out: Box<dyn Write> = if let Some(path) = output_path {
190 Box::new(File::create(path.as_ref()).map_err(Error::DataFile)?)
191 } else {
192 Box::new(std::io::stdout())
193 };
194
195 let mut count = 0;
196 for (vendor, val) in vendors.iter() {
197 for (name, var) in val.variables.iter() {
198 let meta = NvramEntryMetadata {
199 vendor: vendor.clone(),
200 name: name.clone(),
201 timestamp: None,
202 attr: var.attributes,
203 size: var.data.len(),
204 };
205 let entry = parse_nvram_entry(&meta.name, &var.data)?;
206 print_nvram_entry(&mut out, &meta, &entry, truncate).map_err(Error::DataFile)?;
207 count += 1;
208 }
209 }
210
211 tracing::info!("Retrieved {count} NVRAM entries");
212 Ok(())
213}
214
215struct NvramEntryMetadata {
218 pub vendor: String,
219 pub name: String,
220 pub timestamp: Option<EFI_TIME>,
221 pub attr: u32,
222 pub size: usize,
223}
224
225fn print_nvram_entry(
226 out: &mut impl Write,
227 meta: &NvramEntryMetadata,
228 entry: &ParsedNvramEntry<'_>,
229 truncate: bool,
230) -> std::io::Result<()> {
231 const LINE_WIDTH: usize = 80;
232
233 write!(
234 out,
235 "Vendor: {:?}\nName: {:?}\nAttributes: {:#x}\nSize: {:#x}\n",
236 meta.vendor, meta.name, meta.attr, meta.size,
237 )?;
238
239 if let Some(timestamp) = meta.timestamp {
240 writeln!(out, "Timestamp: {}", timestamp)?;
241 }
242
243 match entry {
244 ParsedNvramEntry::BootOrder(boot_order) => {
245 write!(out, "Boot Order:")?;
246 for x in boot_order {
247 write!(out, " {}", x)?;
248 }
249 writeln!(out)?;
250 }
251 ParsedNvramEntry::Boot(load_option) => {
252 writeln!(
253 out,
254 "Load Option: attributes: {:x}, description: {}",
255 load_option.attributes, load_option.description
256 )?;
257 for path in &load_option.device_paths {
258 writeln!(out, " - {:x?}", path)?;
259 }
260 if let Some(opt) = load_option.opt {
261 let prefix = " - opt: ";
262 write!(out, "{}", prefix)?;
263 print_hex_compact(out, opt, truncate.then(|| LINE_WIDTH - prefix.len()))?;
264 writeln!(out)?;
265 }
266 }
267 ParsedNvramEntry::SignatureList(sig_lists) => {
268 writeln!(out, "Signature Lists:")?;
269 for sig in sig_lists {
270 match sig {
271 SignatureList::Sha256(list) => {
272 writeln!(out, " - [Sha256]")?;
273 for sig in list {
274 let prefix = format!(
275 " - Signature Owner: {} Data: ",
276 sig.header.signature_owner
277 );
278 write!(out, "{}", &prefix)?;
279 print_hex_compact(
280 out,
281 sig.data.0.deref(),
282 truncate.then(|| LINE_WIDTH - prefix.len()),
283 )?;
284 writeln!(out)?;
285 }
286 }
287 SignatureList::X509(sig) => {
288 let prefix = format!(
289 " - [X509] Signature Owner: {} Data: ",
290 sig.header.signature_owner
291 );
292 write!(out, "{}", &prefix)?;
293 print_hex_compact(
294 out,
295 sig.data.0.deref(),
296 truncate.then(|| LINE_WIDTH - prefix.len()),
297 )?;
298 writeln!(out)?;
299 }
300 }
301 }
302 }
303 ParsedNvramEntry::Unknown(data) => {
304 let prefix = "data: ";
305 write!(out, "{}", prefix)?;
306 print_hex_compact(out, data, truncate.then(|| LINE_WIDTH - prefix.len()))?;
307 writeln!(out)?;
308 }
309 }
310
311 writeln!(out)?;
312
313 Ok(())
314}
315
316fn print_hex_compact(
317 out: &mut impl Write,
318 data: &[u8],
319 truncate: Option<usize>,
320) -> std::io::Result<()> {
321 if let Some(truncate) = truncate {
322 let ellipsis = "...";
323 let num_bytes = (truncate - ellipsis.len()) / 2;
324 for byte in data.iter().take(num_bytes) {
325 write!(out, "{:02x}", byte)?;
326 }
327 if data.len() > num_bytes {
328 write!(out, "{}", ellipsis)?;
329 }
330 } else {
331 for byte in data {
332 write!(out, "{:02x}", byte)?;
333 }
334 }
335
336 Ok(())
337}
338
339async fn vmgs_file_open_nvram(
340 file_path: impl AsRef<Path>,
341 key_path: Option<impl AsRef<Path>>,
342 open_mode: OpenMode,
343) -> Result<HclCompatNvram<VmgsStorageBackend>, Error> {
344 let vmgs = vmgs_file_open(file_path, key_path, open_mode).await?;
345 let encrypted = vmgs.encrypted();
346
347 open_nvram(vmgs, encrypted)
348}
349
350fn open_nvram(vmgs: Vmgs, encrypted: bool) -> Result<HclCompatNvram<VmgsStorageBackend>, Error> {
351 let nvram_storage = HclCompatNvram::new(
352 VmgsStorageBackend::new(vmgs, vmgs::FileId::BIOS_NVRAM, encrypted)
353 .map_err(Error::VmgsStorageBackend)?,
354 None,
355 );
356
357 Ok(nvram_storage)
358}
359
360async fn vmgs_file_remove_boot_entries(
363 file_path: impl AsRef<Path>,
364 key_path: Option<impl AsRef<Path>>,
365 dry_run: bool,
366) -> Result<(), Error> {
367 let mut nvram_storage =
368 vmgs_file_open_nvram(file_path, key_path, OpenMode::ReadWriteRequire).await?;
369
370 if dry_run {
371 tracing::info!("Printing Boot Entries (Dry-run)");
372 } else {
373 tracing::info!("Deleting Boot Entries");
374 }
375
376 let name = Ucs2LeVec::from("BootOrder".to_string());
377 let (_, boot_order_bytes, _) = nvram_storage
378 .get_variable(&name, EFI_GLOBAL_VARIABLE)
379 .await?
380 .ok_or(Error::MissingNvramEntry(name.clone()))?;
381 let boot_order = boot_order::parse_boot_order(&boot_order_bytes)
382 .map_err(uefi_nvram_specvars::ParseError::BootOrder)?;
383
384 if !dry_run {
385 if !nvram_storage
386 .remove_variable(&name, EFI_GLOBAL_VARIABLE)
387 .await?
388 {
389 return Err(Error::MissingNvramEntry(name));
390 }
391 }
392
393 for (i, boot_option_num) in boot_order.enumerate() {
394 let name = Ucs2LeVec::from(format!("Boot{:04x}", boot_option_num));
395 let (_, boot_option_bytes, _) = nvram_storage
396 .get_variable(&name, EFI_GLOBAL_VARIABLE)
397 .await?
398 .ok_or(Error::MissingNvramEntry(name.clone()))?;
399 let boot_option = boot_order::EfiLoadOption::parse(&boot_option_bytes)
400 .map_err(uefi_nvram_specvars::ParseError::BootOrder)?;
401
402 println!("{i}: {}: {:x?}", &name, boot_option);
403
404 if !dry_run {
405 if !nvram_storage
406 .remove_variable(&name, EFI_GLOBAL_VARIABLE)
407 .await?
408 {
409 return Err(Error::MissingNvramEntry(name));
410 }
411 }
412 }
413
414 Ok(())
415}
416
417async fn vmgs_file_remove_nvram_entry(
419 file_path: impl AsRef<Path>,
420 key_path: Option<impl AsRef<Path>>,
421 name: String,
422 vendor: String,
423) -> Result<(), Error> {
424 let mut nvram_storage =
425 vmgs_file_open_nvram(file_path, key_path, OpenMode::ReadWriteRequire).await?;
426
427 tracing::info!("Removing variable with name {name} and vendor {vendor}");
428
429 let name = Ucs2LeVec::from(name);
430 let vendor = Guid::from_str(&vendor)?;
431
432 if !nvram_storage.remove_variable(&name, vendor).await? {
433 return Err(Error::MissingNvramEntry(name));
434 }
435
436 Ok(())
437}