Skip to main content

igvmfilegen/
main.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implements a command line utility to generate IGVM files.
5
6#![forbid(unsafe_code)]
7
8#[cfg(not(test))]
9crypto::ensure_single_backend!();
10
11mod file_loader;
12mod identity_mapping;
13mod signed_measurement;
14mod vp_context_builder;
15
16use crate::file_loader::IgvmLoader;
17use crate::file_loader::LoaderIsolationType;
18use anyhow::Context;
19use anyhow::bail;
20use clap::Parser;
21use file_loader::IgvmLoaderRegister;
22use file_loader::IgvmVtlLoader;
23use igvm::IgvmFile;
24use igvm_defs::IGVM_FIXED_HEADER;
25use igvm_defs::SnpPolicy;
26use igvm_defs::TdxPolicy;
27use igvmfilegen_config::Config;
28use igvmfilegen_config::ConfigIsolationType;
29use igvmfilegen_config::Image;
30use igvmfilegen_config::LinuxImage;
31use igvmfilegen_config::ResourceType;
32use igvmfilegen_config::Resources;
33use igvmfilegen_config::SecureAvicType;
34use igvmfilegen_config::SnpInjectionType;
35use igvmfilegen_config::UefiConfigType;
36use loader::importer::Aarch64Register;
37use loader::importer::GuestArch;
38use loader::importer::GuestArchKind;
39use loader::importer::ImageLoad;
40use loader::importer::X86Register;
41use loader::linux::InitrdConfig;
42use loader::paravisor::CommandLineType;
43use loader::paravisor::Vtl0Config;
44use loader::paravisor::Vtl0Linux;
45use std::io::Seek;
46use std::io::Write;
47use std::path::PathBuf;
48use tracing_subscriber::EnvFilter;
49use tracing_subscriber::filter::LevelFilter;
50use zerocopy::FromBytes;
51use zerocopy::IntoBytes;
52
53#[derive(Parser)]
54#[clap(name = "igvmfilegen", about = "Tool to generate IGVM files")]
55enum Options {
56    /// Dumps the contents of an IGVM file in a human-readable format
57    // TODO: Move into its own tool.
58    Dump {
59        /// Dump file path
60        #[clap(short, long = "filepath")]
61        file_path: PathBuf,
62    },
63    /// Build an IGVM file according to a manifest
64    Manifest {
65        /// Config manifest file path
66        #[clap(short, long = "manifest")]
67        manifest: PathBuf,
68        /// Resources file describing binary resources used to build the igvm
69        /// file.
70        #[clap(short, long = "resources")]
71        resources: PathBuf,
72        /// Output file path for the built igvm file
73        #[clap(short = 'o', long)]
74        output: PathBuf,
75        /// Additional debug validation when building IGVM files
76        #[clap(long)]
77        debug_validation: bool,
78        /// Override secure AVIC to disabled for debug SNP guest configs
79        #[clap(long)]
80        disable_secure_avic: bool,
81    },
82}
83
84// TODO: Potential CLI flags:
85//       --report: Dump additional data like what memory ranges were accepted with what values
86
87fn main() -> anyhow::Result<()> {
88    let opts = Options::parse();
89    let filter = if std::env::var(EnvFilter::DEFAULT_ENV).is_ok() {
90        EnvFilter::from_default_env()
91    } else {
92        EnvFilter::default().add_directive(LevelFilter::INFO.into())
93    };
94    tracing_subscriber::fmt()
95        .log_internal_errors(true)
96        .with_writer(std::io::stderr)
97        .with_env_filter(filter)
98        .init();
99
100    match opts {
101        Options::Dump { file_path } => {
102            let image = fs_err::read(file_path).context("reading input file")?;
103            let fixed_header = IGVM_FIXED_HEADER::read_from_prefix(image.as_bytes())
104                .expect("Invalid fixed header")
105                .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
106
107            let igvm_data = IgvmFile::new_from_binary(&image, None).expect("should be valid");
108            println!("Total file size: {} bytes\n", fixed_header.total_file_size);
109            println!("{:#X?}", fixed_header);
110            println!("{}", igvm_data);
111            Ok(())
112        }
113        Options::Manifest {
114            manifest,
115            resources,
116            output,
117            debug_validation,
118            disable_secure_avic,
119        } => {
120            // Read the config from the JSON manifest path.
121            let mut config: Config = serde_json::from_str(
122                &fs_err::read_to_string(manifest).context("reading manifest")?,
123            )
124            .context("parsing manifest")?;
125
126            if disable_secure_avic {
127                for guest_config in &mut config.guest_configs {
128                    if let ConfigIsolationType::Snp { secure_avic, .. } =
129                        &mut guest_config.isolation_type
130                    {
131                        *secure_avic = SecureAvicType::Disabled;
132                    }
133                }
134            }
135
136            // Read resources and validate that it covers the required resources
137            // from the config.
138            let resources: Resources = serde_json::from_str(
139                &fs_err::read_to_string(resources).context("reading resources")?,
140            )
141            .context("parsing resources")?;
142
143            let required_resources = config.required_resources();
144            resources
145                .check_required(&required_resources)
146                .context("required resources not specified")?;
147
148            tracing::info!(
149                ?config,
150                ?resources,
151                "Building igvm file with given config and resources"
152            );
153
154            // Enable debug validation if specified or if running a debug build.
155            match config.guest_arch {
156                igvmfilegen_config::GuestArch::X64 => create_igvm_file::<X86Register>(
157                    config,
158                    resources,
159                    debug_validation || cfg!(debug_assertions),
160                    output,
161                ),
162                igvmfilegen_config::GuestArch::Aarch64 => create_igvm_file::<Aarch64Register>(
163                    config,
164                    resources,
165                    debug_validation || cfg!(debug_assertions),
166                    output,
167                ),
168            }
169        }
170    }
171}
172
173/// Create an IGVM file from the specified config
174fn create_igvm_file<R: IgvmfilegenRegister + GuestArch + 'static>(
175    igvm_config: Config,
176    resources: Resources,
177    debug_validation: bool,
178    output: PathBuf,
179) -> anyhow::Result<()> {
180    tracing::debug!(?igvm_config, "Creating IGVM file",);
181
182    let mut igvm_file: Option<IgvmFile> = None;
183    let mut map_files = Vec::new();
184    let base_path = output.file_stem().unwrap();
185    for config in igvm_config.guest_configs {
186        // Max VTL must be 2 or 0.
187        if config.max_vtl != 2 && config.max_vtl != 0 {
188            bail!("max_vtl must be 2 or 0");
189        }
190
191        let isolation_string = match config.isolation_type {
192            ConfigIsolationType::None => "none",
193            ConfigIsolationType::Vbs { .. } => "vbs",
194            ConfigIsolationType::Snp { .. } => "snp",
195            ConfigIsolationType::Tdx { .. } => "tdx",
196        };
197        let loader_isolation_type = match config.isolation_type {
198            ConfigIsolationType::None => LoaderIsolationType::None,
199            ConfigIsolationType::Vbs { enable_debug } => LoaderIsolationType::Vbs { enable_debug },
200            ConfigIsolationType::Snp {
201                shared_gpa_boundary_bits,
202                policy,
203                enable_debug,
204                injection_type,
205                secure_avic,
206            } => LoaderIsolationType::Snp {
207                shared_gpa_boundary_bits,
208                policy: SnpPolicy::from(policy).with_debug(enable_debug as u8),
209                injection_type: match injection_type {
210                    SnpInjectionType::Normal => vp_context_builder::snp::InjectionType::Normal,
211                    SnpInjectionType::Restricted => {
212                        vp_context_builder::snp::InjectionType::Restricted
213                    }
214                },
215                secure_avic: match secure_avic {
216                    SecureAvicType::Enabled => vp_context_builder::snp::SecureAvic::Enabled,
217                    SecureAvicType::Disabled => vp_context_builder::snp::SecureAvic::Disabled,
218                },
219            },
220            ConfigIsolationType::Tdx {
221                enable_debug,
222                sept_ve_disable,
223            } => LoaderIsolationType::Tdx {
224                policy: TdxPolicy::new()
225                    .with_debug_allowed(enable_debug as u8)
226                    .with_sept_ve_disable(sept_ve_disable as u8),
227            },
228        };
229
230        // Max VTL of 2 implies paravisor.
231        let with_paravisor = config.max_vtl == 2;
232
233        let mut loader = IgvmLoader::<R>::new(with_paravisor, loader_isolation_type);
234
235        load_image(&mut loader.loader(), &config.image, &resources)?;
236
237        let igvm_output = loader
238            .finalize(config.guest_svn)
239            .context("finalizing loader")?;
240
241        // Merge the loaded guest into the overall IGVM file.
242        match &mut igvm_file {
243            Some(file) => file
244                .merge_simple(igvm_output.guest)
245                .context("merging guest into overall igvm file")?,
246            None => igvm_file = Some(igvm_output.guest),
247        }
248
249        map_files.push(igvm_output.map);
250
251        if let Some(doc) = igvm_output.doc {
252            // Write the measurement document to a file with the same name,
253            // but with -[isolation].json extension.
254            let doc_path = {
255                let mut name = base_path.to_os_string();
256                name.push("-");
257                name.push(isolation_string);
258                name.push(".json");
259                output.with_file_name(name)
260            };
261            tracing::info!(
262                path = %doc_path.display(),
263                "Writing document json file",
264            );
265            let mut doc_file = fs_err::OpenOptions::new()
266                .create(true)
267                .write(true)
268                .open(doc_path)
269                .context("creating doc file")?;
270
271            writeln!(
272                doc_file,
273                "{}",
274                serde_json::to_string(&doc).expect("json string")
275            )
276            .context("writing doc file")?;
277        }
278    }
279
280    let mut igvm_binary = Vec::new();
281    let igvm_file = igvm_file.expect("should have an igvm file");
282    igvm_file
283        .serialize(&mut igvm_binary)
284        .context("serializing igvm")?;
285
286    // If enabled, perform additional validation.
287    if debug_validation {
288        debug_validate_igvm_file(&igvm_file, &igvm_binary);
289    }
290
291    // Write the IGVM file to the specified file path in the config.
292    tracing::info!(
293        path = %output.display(),
294        "Writing output IGVM file",
295    );
296    fs_err::File::create(&output)
297        .context("creating igvm file")?
298        .write_all(&igvm_binary)
299        .context("writing igvm file")?;
300
301    // Write the map file display output to a file with the same name, but .map
302    // extension.
303    let map_path = {
304        let mut name = output.file_name().expect("has name").to_owned();
305        name.push(".map");
306        output.with_file_name(name)
307    };
308    tracing::info!(
309        path = %map_path.display(),
310        "Writing output map file",
311    );
312    let mut map_file = fs_err::OpenOptions::new()
313        .create(true)
314        .write(true)
315        .open(map_path)
316        .context("creating map file")?;
317
318    for map in map_files {
319        writeln!(map_file, "{}", map).context("writing map file")?;
320    }
321
322    Ok(())
323}
324
325/// Validate an in-memory IGVM file and the binary repr are equivalent.
326// TODO: should live in the igvm crate
327fn debug_validate_igvm_file(igvm_file: &IgvmFile, binary_file: &[u8]) {
328    use igvm::IgvmDirectiveHeader;
329    tracing::info!("Debug validation of serialized IGVM file.");
330
331    let igvm_reserialized = IgvmFile::new_from_binary(binary_file, None).expect("should be valid");
332
333    for (a, b) in igvm_file
334        .platforms()
335        .iter()
336        .zip(igvm_reserialized.platforms().iter())
337    {
338        assert_eq!(a, b);
339    }
340
341    for (a, b) in igvm_file
342        .initializations()
343        .iter()
344        .zip(igvm_reserialized.initializations().iter())
345    {
346        assert_eq!(a, b);
347    }
348
349    for (a, b) in igvm_file
350        .directives()
351        .iter()
352        .zip(igvm_reserialized.directives().iter())
353    {
354        match (a, b) {
355            (
356                IgvmDirectiveHeader::PageData {
357                    gpa: a_gpa,
358                    flags: a_flags,
359                    data_type: a_data_type,
360                    data: a_data,
361                    compatibility_mask: a_compmask,
362                },
363                IgvmDirectiveHeader::PageData {
364                    gpa: b_gpa,
365                    flags: b_flags,
366                    data_type: b_data_type,
367                    data: b_data,
368                    compatibility_mask: b_compmask,
369                },
370            ) => {
371                assert!(
372                    a_gpa == b_gpa
373                        && a_flags == b_flags
374                        && a_data_type == b_data_type
375                        && a_compmask == b_compmask
376                );
377
378                // data might not be the same length, as it gets padded out.
379                for i in 0..b_data.len() {
380                    if i < a_data.len() {
381                        assert_eq!(a_data[i], b_data[i]);
382                    } else {
383                        assert_eq!(0, b_data[i]);
384                    }
385                }
386            }
387            (
388                IgvmDirectiveHeader::ParameterArea {
389                    number_of_bytes: a_number_of_bytes,
390                    parameter_area_index: a_parameter_area_index,
391                    initial_data: a_initial_data,
392                },
393                IgvmDirectiveHeader::ParameterArea {
394                    number_of_bytes: b_number_of_bytes,
395                    parameter_area_index: b_parameter_area_index,
396                    initial_data: b_initial_data,
397                },
398            ) => {
399                assert!(
400                    a_number_of_bytes == b_number_of_bytes
401                        && a_parameter_area_index == b_parameter_area_index
402                );
403
404                // initial_data might be padded out just like page data.
405                for i in 0..b_initial_data.len() {
406                    if i < a_initial_data.len() {
407                        assert_eq!(a_initial_data[i], b_initial_data[i]);
408                    } else {
409                        assert_eq!(0, b_initial_data[i]);
410                    }
411                }
412            }
413            _ => assert_eq!(a, b),
414        }
415    }
416}
417
418/// A trait to specialize behavior of the file builder based on different
419/// register types for different architectures. Different methods may need to be
420/// called depending on the register type that represents the given architecture.
421trait IgvmfilegenRegister: IgvmLoaderRegister + 'static {
422    fn load_uefi(
423        importer: &mut dyn ImageLoad<Self>,
424        image: &[u8],
425        config: loader::uefi::ConfigType,
426    ) -> Result<loader::uefi::LoadInfo, loader::uefi::Error>;
427
428    fn load_linux_kernel_and_initrd<F>(
429        importer: &mut impl ImageLoad<Self>,
430        kernel_image: &mut F,
431        kernel_minimum_start_address: u64,
432        initrd: Option<InitrdConfig<'_>>,
433        device_tree_blob: Option<&[u8]>,
434    ) -> Result<loader::linux::LoadInfo, loader::linux::Error>
435    where
436        F: std::io::Read + Seek,
437        Self: GuestArch;
438
439    fn load_openhcl<F>(
440        importer: &mut dyn ImageLoad<Self>,
441        kernel_image: &mut F,
442        shim: &mut F,
443        sidecar: Option<&mut F>,
444        command_line: CommandLineType<'_>,
445        initrd: Option<(&mut dyn loader::common::ReadSeek, u64)>,
446        memory_page_base: Option<u64>,
447        memory_page_count: u64,
448        vtl0_config: Vtl0Config<'_>,
449    ) -> Result<(), loader::paravisor::Error>
450    where
451        F: std::io::Read + Seek;
452}
453
454impl IgvmfilegenRegister for X86Register {
455    fn load_uefi(
456        importer: &mut dyn ImageLoad<Self>,
457        image: &[u8],
458        config: loader::uefi::ConfigType,
459    ) -> Result<loader::uefi::LoadInfo, loader::uefi::Error> {
460        loader::uefi::x86_64::load(importer, image, config)
461    }
462
463    fn load_linux_kernel_and_initrd<F>(
464        importer: &mut impl ImageLoad<Self>,
465        kernel_image: &mut F,
466        kernel_minimum_start_address: u64,
467        initrd: Option<InitrdConfig<'_>>,
468        _device_tree_blob: Option<&[u8]>,
469    ) -> Result<loader::linux::LoadInfo, loader::linux::Error>
470    where
471        F: std::io::Read + Seek,
472    {
473        loader::linux::load_kernel_and_initrd_x64(
474            importer,
475            kernel_image,
476            kernel_minimum_start_address,
477            initrd,
478        )
479    }
480
481    fn load_openhcl<F>(
482        importer: &mut dyn ImageLoad<Self>,
483        kernel_image: &mut F,
484        shim: &mut F,
485        sidecar: Option<&mut F>,
486        command_line: CommandLineType<'_>,
487        initrd: Option<(&mut dyn loader::common::ReadSeek, u64)>,
488        memory_page_base: Option<u64>,
489        memory_page_count: u64,
490        vtl0_config: Vtl0Config<'_>,
491    ) -> Result<(), loader::paravisor::Error>
492    where
493        F: std::io::Read + Seek,
494    {
495        loader::paravisor::load_openhcl_x64(
496            importer,
497            kernel_image,
498            shim,
499            sidecar,
500            command_line,
501            initrd,
502            memory_page_base,
503            memory_page_count,
504            vtl0_config,
505        )
506    }
507}
508
509impl IgvmfilegenRegister for Aarch64Register {
510    fn load_uefi(
511        importer: &mut dyn ImageLoad<Self>,
512        image: &[u8],
513        config: loader::uefi::ConfigType,
514    ) -> Result<loader::uefi::LoadInfo, loader::uefi::Error> {
515        loader::uefi::aarch64::load(importer, image, config)
516    }
517
518    fn load_linux_kernel_and_initrd<F>(
519        importer: &mut impl ImageLoad<Self>,
520        kernel_image: &mut F,
521        kernel_minimum_start_address: u64,
522        initrd: Option<InitrdConfig<'_>>,
523        device_tree_blob: Option<&[u8]>,
524    ) -> Result<loader::linux::LoadInfo, loader::linux::Error>
525    where
526        F: std::io::Read + Seek,
527    {
528        loader::linux::load_kernel_and_initrd_arm64(
529            importer,
530            kernel_image,
531            kernel_minimum_start_address,
532            initrd,
533            device_tree_blob,
534        )
535    }
536
537    fn load_openhcl<F>(
538        importer: &mut dyn ImageLoad<Self>,
539        kernel_image: &mut F,
540        shim: &mut F,
541        _sidecar: Option<&mut F>,
542        command_line: CommandLineType<'_>,
543        initrd: Option<(&mut dyn loader::common::ReadSeek, u64)>,
544        memory_page_base: Option<u64>,
545        memory_page_count: u64,
546        vtl0_config: Vtl0Config<'_>,
547    ) -> Result<(), loader::paravisor::Error>
548    where
549        F: std::io::Read + Seek,
550    {
551        loader::paravisor::load_openhcl_arm64(
552            importer,
553            kernel_image,
554            shim,
555            command_line,
556            initrd,
557            memory_page_base,
558            memory_page_count,
559            vtl0_config,
560        )
561    }
562}
563
564/// Load an image.
565fn load_image<'a, R: IgvmfilegenRegister + GuestArch + 'static>(
566    loader: &mut IgvmVtlLoader<'_, R>,
567    config: &'a Image,
568    resources: &'a Resources,
569) -> anyhow::Result<()> {
570    tracing::debug!(?config, "loading into VTL0");
571
572    match *config {
573        Image::None => {
574            // Nothing is loaded.
575        }
576        Image::Uefi { config_type } => {
577            load_uefi(loader, resources, config_type)?;
578        }
579        Image::Linux(ref linux) => {
580            load_linux(loader, linux, resources)?;
581        }
582        Image::Openhcl {
583            ref command_line,
584            static_command_line,
585            memory_page_base,
586            memory_page_count,
587            uefi,
588            ref linux,
589        } => {
590            if uefi && linux.is_some() {
591                anyhow::bail!("cannot include both UEFI and Linux images in OpenHCL image");
592            }
593
594            let kernel_path = resources
595                .get(ResourceType::UnderhillKernel)
596                .expect("validated present");
597            let mut kernel = fs_err::File::open(kernel_path).context(format!(
598                "reading underhill kernel image at {}",
599                kernel_path.display()
600            ))?;
601
602            let mut initrd = {
603                let initrd_path = resources
604                    .get(ResourceType::UnderhillInitrd)
605                    .expect("validated present");
606                Some(fs_err::File::open(initrd_path).context(format!(
607                    "reading underhill initrd at {}",
608                    initrd_path.display()
609                ))?)
610            };
611
612            let shim_path = resources
613                .get(ResourceType::OpenhclBoot)
614                .expect("validated present");
615            let mut shim = fs_err::File::open(shim_path)
616                .context(format!("reading underhill shim at {}", shim_path.display()))?;
617
618            let mut sidecar =
619                if let Some(sidecar_path) = resources.get(ResourceType::UnderhillSidecar) {
620                    Some(fs_err::File::open(sidecar_path).context("reading AP kernel")?)
621                } else {
622                    None
623                };
624
625            let initrd_info = if let Some(ref mut f) = initrd {
626                let size = f.seek(std::io::SeekFrom::End(0))?;
627                f.rewind()?;
628                Some((f as &mut dyn loader::common::ReadSeek, size))
629            } else {
630                None
631            };
632
633            // TODO: While the paravisor supports multiple things that can be
634            // loaded in VTL0, we don't yet have updated file builder config for
635            // that.
636            //
637            // Since the host performs PCAT loading, each image that supports
638            // UEFI also supports PCAT boot. A future file builder config change
639            // will make this more explicit.
640            let vtl0_load_config = if uefi {
641                let mut inner_loader = loader.nested_loader();
642                let load_info = load_uefi(&mut inner_loader, resources, UefiConfigType::None)?;
643                let vp_context = inner_loader.take_vp_context();
644                Vtl0Config {
645                    supports_pcat: loader.loader().arch() == GuestArchKind::X86_64,
646                    supports_uefi: Some((load_info, vp_context)),
647                    supports_linux: None,
648                }
649            } else if let Some(linux) = linux {
650                let load_info = load_linux(&mut loader.nested_loader(), linux, resources)?;
651                Vtl0Config {
652                    supports_pcat: false,
653                    supports_uefi: None,
654                    supports_linux: Some(Vtl0Linux {
655                        command_line: &linux.command_line,
656                        load_info,
657                    }),
658                }
659            } else {
660                Vtl0Config {
661                    supports_pcat: false,
662                    supports_uefi: None,
663                    supports_linux: None,
664                }
665            };
666
667            let command_line = if static_command_line {
668                CommandLineType::Static(command_line)
669            } else {
670                CommandLineType::HostAppendable(command_line)
671            };
672
673            R::load_openhcl(
674                loader,
675                &mut kernel,
676                &mut shim,
677                sidecar.as_mut(),
678                command_line,
679                initrd_info,
680                memory_page_base,
681                memory_page_count,
682                vtl0_load_config,
683            )
684            .context("underhill kernel loader")?;
685        }
686    };
687
688    Ok(())
689}
690
691fn load_uefi<R: IgvmfilegenRegister + GuestArch + 'static>(
692    loader: &mut IgvmVtlLoader<'_, R>,
693    resources: &Resources,
694    config_type: UefiConfigType,
695) -> Result<loader::uefi::LoadInfo, anyhow::Error> {
696    let image_path = resources
697        .get(ResourceType::Uefi)
698        .expect("validated present");
699    let image = fs_err::read(image_path)
700        .context(format!("reading uefi image at {}", image_path.display()))?;
701    let config = match config_type {
702        UefiConfigType::None => loader::uefi::ConfigType::None,
703        UefiConfigType::Igvm => loader::uefi::ConfigType::Igvm,
704    };
705    let load_info = R::load_uefi(loader, &image, config).context("uefi loader")?;
706    Ok(load_info)
707}
708
709fn load_linux<R: IgvmfilegenRegister + GuestArch + 'static>(
710    loader: &mut IgvmVtlLoader<'_, R>,
711    config: &LinuxImage,
712    resources: &Resources,
713) -> Result<loader::linux::LoadInfo, anyhow::Error> {
714    let LinuxImage {
715        use_initrd,
716        command_line: _,
717    } = *config;
718    let kernel_path = resources
719        .get(ResourceType::LinuxKernel)
720        .expect("validated present");
721    let mut kernel = fs_err::File::open(kernel_path).context(format!(
722        "reading vtl0 kernel image at {}",
723        kernel_path.display()
724    ))?;
725    let mut initrd_file = if use_initrd {
726        let initrd_path = resources
727            .get(ResourceType::LinuxInitrd)
728            .expect("validated present");
729        Some(
730            fs_err::File::open(initrd_path)
731                .context(format!("reading vtl0 initrd at {}", initrd_path.display()))?,
732        )
733    } else {
734        None
735    };
736    let initrd = if let Some(ref mut f) = initrd_file {
737        let size = f.seek(std::io::SeekFrom::End(0))?;
738        f.rewind()?;
739        Some(InitrdConfig {
740            initrd_address: loader::linux::InitrdAddressType::AfterKernel,
741            initrd: f,
742            size,
743        })
744    } else {
745        None
746    };
747    let load_info = R::load_linux_kernel_and_initrd(loader, &mut kernel, 0, initrd, None)
748        .context("loading linux kernel and initrd")?;
749    Ok(load_info)
750}