├── .platform ├── .gitignore ├── src ├── loader │ ├── elf │ │ ├── test_elf.bin │ │ ├── test_bad_align.bin │ │ ├── test_elfnote.bin │ │ ├── test_dummy_note.bin │ │ ├── test_invalid_pvh_note.bin │ │ ├── test_elfnote_8byte_align.bin │ │ └── mod.rs │ ├── pe │ │ ├── test_arm64_image.bin │ │ ├── test_riscv64_image.bin │ │ └── mod.rs │ ├── bzimage │ │ ├── fuzz_invalid_bzimage.bin │ │ └── mod.rs │ └── mod.rs ├── loader_gen │ ├── mod.rs │ └── x86_64 │ │ ├── mod.rs │ │ ├── elf.rs │ │ └── start_info.rs ├── configurator │ ├── x86_64 │ │ ├── mod.rs │ │ ├── linux.rs │ │ └── pvh.rs │ ├── fdt.rs │ └── mod.rs └── lib.rs ├── .gitmodules ├── .cargo ├── config.toml └── audit.toml ├── CODEOWNERS ├── coverage_config_aarch64.json ├── coverage_config_x86_64.json ├── .buildkite ├── bench-tests.json └── download_resources.sh ├── .github ├── workflows │ └── release.yml └── dependabot.yml ├── Cargo.toml ├── benches ├── main.rs ├── fdt.rs └── x86_64 │ └── mod.rs ├── LICENSE-BSD-3-Clause ├── docs ├── elfio_files │ ├── dummy_note.cpp │ ├── invalid_pvh_note_writer.cpp │ ├── ignored_phv.cpp │ ├── ignored_phv_8byte_align.cpp │ ├── basic_elf.cpp │ └── bad_align_writer.cpp └── TESTING.md ├── DESIGN.md ├── README.md ├── CHANGELOG.md └── LICENSE-APACHE /.platform: -------------------------------------------------------------------------------- 1 | x86_64 2 | aarch64 3 | riscv64 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea/ 5 | -------------------------------------------------------------------------------- /src/loader/elf/test_elf.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/linux-loader/HEAD/src/loader/elf/test_elf.bin -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rust-vmm-ci"] 2 | path = rust-vmm-ci 3 | url = https://github.com/rust-vmm/rust-vmm-ci.git 4 | -------------------------------------------------------------------------------- /src/loader/elf/test_bad_align.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/linux-loader/HEAD/src/loader/elf/test_bad_align.bin -------------------------------------------------------------------------------- /src/loader/elf/test_elfnote.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/linux-loader/HEAD/src/loader/elf/test_elfnote.bin -------------------------------------------------------------------------------- /src/loader/elf/test_dummy_note.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/linux-loader/HEAD/src/loader/elf/test_dummy_note.bin -------------------------------------------------------------------------------- /src/loader/pe/test_arm64_image.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/linux-loader/HEAD/src/loader/pe/test_arm64_image.bin -------------------------------------------------------------------------------- /src/loader/pe/test_riscv64_image.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/linux-loader/HEAD/src/loader/pe/test_riscv64_image.bin -------------------------------------------------------------------------------- /src/loader/elf/test_invalid_pvh_note.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/linux-loader/HEAD/src/loader/elf/test_invalid_pvh_note.bin -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-musl] 2 | rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc" ] 3 | 4 | -------------------------------------------------------------------------------- /src/loader/bzimage/fuzz_invalid_bzimage.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/linux-loader/HEAD/src/loader/bzimage/fuzz_invalid_bzimage.bin -------------------------------------------------------------------------------- /src/loader/elf/test_elfnote_8byte_align.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/linux-loader/HEAD/src/loader/elf/test_elfnote_8byte_align.bin -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Add the list of code owners here (using their GitHub username) 2 | * @aghecenco @sameo @rbradford @roypat @andreitraistaru @ShadowCurse 3 | -------------------------------------------------------------------------------- /coverage_config_aarch64.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage_score": 80.7, 3 | "exclude_path": "", 4 | "crate_features": "pe", 5 | "exclude_path": "benches/" 6 | } 7 | -------------------------------------------------------------------------------- /coverage_config_x86_64.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage_score": 92.54, 3 | "exclude_path": "", 4 | "crate_features": "bzimage,elf", 5 | "exclude_path": "loader_gen" 6 | } 7 | -------------------------------------------------------------------------------- /.buildkite/bench-tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "tests": [ 3 | { 4 | "test_name": "bench", 5 | "command": "pytest rust-vmm-ci/integration_tests/test_benchmark.py -s", 6 | "platform": [ 7 | "x86_64", 8 | "aarch64" 9 | ], 10 | "timeout_in_minutes": 15 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish to crates.io 2 | 3 | on: 4 | push: 5 | tags: ['v*'] # Triggers when pushing tags starting with 'v' 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | # environment: release # Optional: for enhanced security 11 | permissions: 12 | id-token: write # Required for OIDC token exchange 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: rust-lang/crates-io-auth-action@v1 16 | id: auth 17 | - run: cargo publish 18 | env: 19 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} 20 | -------------------------------------------------------------------------------- /src/loader_gen/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 2 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // 4 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style license that can be 6 | // found in the LICENSE-BSD-3-Clause file. 7 | // 8 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 9 | 10 | //! Bindgen autogenerated structs for boot parameters. 11 | 12 | #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] 13 | 14 | mod x86_64; 15 | pub use x86_64::*; 16 | -------------------------------------------------------------------------------- /src/configurator/x86_64/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2020, Oracle and/or its affiliates. 2 | // 3 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 4 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // 6 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 7 | // Use of this source code is governed by a BSD-style license that can be 8 | // found in the LICENSE-BSD-3-Clause file. 9 | // 10 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 11 | 12 | //! Traits and structs for configuring and loading boot parameters on `x86_64`. 13 | 14 | #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] 15 | 16 | pub mod linux; 17 | pub mod pvh; 18 | -------------------------------------------------------------------------------- /.cargo/audit.toml: -------------------------------------------------------------------------------- 1 | [advisories] 2 | ignore = [ 3 | # serde_cbor is an unmaintained dependency introduced by criterion. 4 | # We are using criterion only for benchmarks, so we can ignore 5 | # this vulnerability until criterion is fixing this. 6 | # See https://github.com/bheisler/criterion.rs/issues/534. 7 | "RUSTSEC-2021-0127", 8 | # atty is unmaintained (the unsound problem doesn't seem to impact us). 9 | # We are ignoring this advisory because it's only used by criterion, 10 | # and we are using criterion for benchmarks. This is not a problem for 11 | # production use cases. Also, criterion did not update the dependency, 12 | # so there is not much else we can do. 13 | "RUSTSEC-2021-0145" 14 | ] 15 | -------------------------------------------------------------------------------- /.buildkite/download_resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DEB_NAME="linux-image-5.10.0-30-amd64-unsigned_5.10.218-1_amd64.deb" 6 | DEB_URL="http://ftp.us.debian.org/debian/pool/main/l/linux/${DEB_NAME}" 7 | 8 | TMP_PATH="/tmp/linux-loader/" 9 | DEB_PATH="${TMP_PATH}/${DEB_NAME}" 10 | EXTRACT_PATH="${TMP_PATH}/src/bzimage-archive" 11 | BZIMAGE_PATH="${EXTRACT_PATH}/boot/vmlinuz-5.10.0-30-amd64" 12 | SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 13 | DEST_PATH="${SCRIPTPATH}/../src/loader/bzimage/bzimage" 14 | 15 | if [ ! -f ${DEST_PATH} ]; then 16 | mkdir -p ${EXTRACT_PATH} 17 | 18 | curl $DEB_URL -o ${DEB_PATH} 19 | dpkg-deb -x ${DEB_PATH} ${EXTRACT_PATH} 20 | 21 | mv ${BZIMAGE_PATH} ${DEST_PATH} 22 | rm -r ${EXTRACT_PATH} 23 | rm -f ${DEB_PATH} 24 | fi 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # We do not set open-pull-requests-limit, as sometimes 5 | # dependabot thinks the limit is hit without actually having 6 | # any PRs open. In reality, the configs below can only result 7 | # in at most one PR per package ecosyste manyway. 8 | # A weekly update of the rust-vmm-ci submodule 9 | - package-ecosystem: gitsubmodule 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | day: monday 14 | 15 | # A weekly update to rust dependencies. These will be grouped, 16 | # e.g. one PR will contains updates for all dependencies. 17 | - package-ecosystem: cargo 18 | directory: "/" 19 | schedule: 20 | interval: weekly 21 | day: monday 22 | # Make it also update transitive dependencies in Cargo.lock 23 | allow: 24 | - dependency-type: "all" 25 | # Group all available updates into a group called "rust-dependencies" 26 | groups: 27 | rust-dependencies: 28 | patterns: 29 | - "*" 30 | -------------------------------------------------------------------------------- /src/loader_gen/x86_64/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 2 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // 4 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style license that can be 6 | // found in the LICENSE-BSD-3-Clause file. 7 | // 8 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 9 | 10 | //! Bindgen autogenerated structs for `x86_64` boot parameters. 11 | 12 | #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] 13 | #![allow(clippy::all)] 14 | #![allow(dead_code)] 15 | #![allow(missing_docs)] 16 | #![allow(non_camel_case_types)] 17 | #![allow(non_snake_case)] 18 | #![allow(non_upper_case_globals)] 19 | 20 | // Hide the autogenerated documentation for bindgen'ed sources. 21 | #[doc(hidden)] 22 | pub mod bootparam; 23 | 24 | #[cfg(feature = "elf")] 25 | pub mod elf; 26 | 27 | #[cfg(feature = "elf")] 28 | pub mod start_info; 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linux-loader" 3 | version = "0.13.2" 4 | authors = [ 5 | "The rust-vmm maintainers", 6 | "rust-vmm AWS maintainers ", 7 | "Cathy Zhang " 8 | ] 9 | edition = "2021" 10 | license = "Apache-2.0 AND BSD-3-Clause" 11 | description = "A Linux kernel image loading crate." 12 | keywords = ["kernel"] 13 | repository = "https://github.com/rust-vmm/linux-loader" 14 | homepage = "https://github.com/rust-vmm/linux-loader" 15 | readme = "README.md" 16 | autobenches = false 17 | 18 | [package.metadata.docs.rs] 19 | all-features = true 20 | rustdoc-args = ["--cfg", "docsrs"] 21 | 22 | [features] 23 | default = ["pe"] 24 | bzimage = ["elf"] 25 | pe = ["elf"] 26 | elf = [] 27 | 28 | [dependencies] 29 | vm-memory = ">=0.16.0, <=0.17.1" 30 | 31 | [dev-dependencies] 32 | criterion = { version = "0.7.0", features = ["html_reports"] } 33 | vm-memory = { version = ">=0.16.0, <=0.17.1", features = ["backend-mmap"] } 34 | 35 | [[bench]] 36 | name = "main" 37 | harness = false 38 | 39 | [lib] 40 | bench = false # https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options 41 | 42 | [profile.bench] 43 | lto = true 44 | codegen-units = 1 45 | -------------------------------------------------------------------------------- /benches/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style license that can be 4 | // found in the LICENSE-BSD-3-Clause file. 5 | // 6 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 7 | 8 | extern crate criterion; 9 | extern crate linux_loader; 10 | extern crate vm_memory; 11 | 12 | use criterion::{criterion_group, criterion_main, Criterion}; 13 | 14 | #[cfg(all( 15 | any(target_arch = "x86", target_arch = "x86_64"), 16 | any(feature = "elf", feature = "pe", feature = "bzimage") 17 | ))] 18 | mod x86_64; 19 | #[cfg(all( 20 | any(target_arch = "x86", target_arch = "x86_64"), 21 | any(feature = "elf", feature = "pe", feature = "bzimage") 22 | ))] 23 | use x86_64::*; 24 | 25 | #[cfg(all( 26 | any(target_arch = "aarch64", target_arch = "riscv64"), 27 | any(feature = "elf", feature = "pe", feature = "bzimage") 28 | ))] 29 | mod fdt; 30 | #[cfg(all( 31 | any(target_arch = "aarch64", target_arch = "riscv64"), 32 | any(feature = "elf", feature = "pe", feature = "bzimage") 33 | ))] 34 | pub use fdt::*; 35 | 36 | // No-op benchmark when configurator module is not available 37 | #[cfg(not(any(feature = "elf", feature = "pe", feature = "bzimage")))] 38 | fn criterion_benchmark(_c: &mut Criterion) {} 39 | 40 | criterion_group! { 41 | name = benches; 42 | config = Criterion::default().sample_size(500); 43 | targets = criterion_benchmark 44 | } 45 | 46 | criterion_main! { 47 | benches 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE-BSD-3-Clause: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /benches/fdt.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style license that can be 4 | // found in the LICENSE-BSD-3-Clause file. 5 | // 6 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 7 | 8 | #![cfg(any(feature = "elf", feature = "pe", feature = "bzimage"))] 9 | 10 | extern crate criterion; 11 | extern crate linux_loader; 12 | extern crate vm_memory; 13 | 14 | use std::hint::black_box; 15 | 16 | use linux_loader::configurator::fdt::FdtBootConfigurator; 17 | use linux_loader::configurator::{BootConfigurator, BootParams}; 18 | use vm_memory::{ByteValued, GuestAddress, GuestMemoryMmap}; 19 | 20 | use criterion::Criterion; 21 | 22 | const MEM_SIZE: usize = 0x100_0000; 23 | const FDT_MAX_SIZE: usize = 0x20; 24 | 25 | fn create_guest_memory() -> GuestMemoryMmap { 26 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap() 27 | } 28 | 29 | #[derive(Clone, Copy, Default)] 30 | #[allow(dead_code)] 31 | pub struct FdtPlaceholder([u8; FDT_MAX_SIZE]); 32 | 33 | // SAFETY: The layout of the structure is fixed and can be initialized by 34 | // reading its content from byte array. 35 | unsafe impl ByteValued for FdtPlaceholder {} 36 | 37 | fn build_fdt_boot_params() -> BootParams { 38 | let fdt = FdtPlaceholder([0u8; FDT_MAX_SIZE]); 39 | let fdt_addr = GuestAddress((MEM_SIZE - FDT_MAX_SIZE - 1) as u64); 40 | BootParams::new::(&fdt, fdt_addr) 41 | } 42 | 43 | pub fn criterion_benchmark(c: &mut Criterion) { 44 | let guest_mem = create_guest_memory(); 45 | let fdt_boot_params = build_fdt_boot_params(); 46 | c.bench_function("configure_fdt", |b| { 47 | b.iter(|| { 48 | black_box(FdtBootConfigurator::write_bootparams::( 49 | &fdt_boot_params, 50 | &guest_mem, 51 | )) 52 | .unwrap(); 53 | }) 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /docs/elfio_files/dummy_note.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2001-present by Serge Lamikhov-Center 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | using namespace ELFIO; 26 | 27 | int main( void ) 28 | { 29 | elfio writer; 30 | 31 | // You can't proceed without this function call! 32 | writer.create( ELFCLASS64, ELFDATA2LSB ); 33 | 34 | writer.set_os_abi( ELFOSABI_LINUX ); 35 | writer.set_type( ET_EXEC ); 36 | writer.set_machine( EM_X86_64 ); 37 | 38 | // Create a loadable segment 39 | segment* load_seg = writer.segments.add(); 40 | load_seg->set_type( PT_LOAD ); 41 | load_seg->set_virtual_address( 0x400000 ); 42 | load_seg->set_physical_address( 0x0 ); 43 | load_seg->set_flags( PF_R ); 44 | load_seg->set_align( 0x200000 ); 45 | 46 | // Create a note segment 47 | segment* note_seg = writer.segments.add(); 48 | note_seg->set_type( PT_NOTE ); 49 | note_seg->set_virtual_address( 0x04000e0 ); 50 | note_seg->set_physical_address( 0x04000e0 ); 51 | note_seg->set_flags( PF_R ); 52 | note_seg->set_align( 0x4 ); 53 | 54 | // Create a dummy section, and add it to the note segment. 55 | section* xen_note_sec = writer.sections.add( ".note.dummy" ); 56 | xen_note_sec->set_type( SHT_NOTE ); 57 | xen_note_sec->set_addr_align( 0x4 ); 58 | xen_note_sec->set_flags( SHF_ALLOC ); 59 | note_section_accessor xen_note_writer( writer, xen_note_sec ); 60 | 61 | xen_note_writer.add_note( 0x12, "dummy", 0, 0 ); 62 | 63 | note_seg->add_section_index( xen_note_sec->get_index(), 64 | xen_note_sec->get_addr_align() ); 65 | 66 | // Setup entry point. Usually, a linker sets this address on base of 67 | // ‘_start’ label. 68 | writer.set_entry( 0x400104 ); 69 | 70 | // Create ELF file 71 | writer.save( "test_dummy_note.bin" ); 72 | 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /docs/elfio_files/invalid_pvh_note_writer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2001-present by Serge Lamikhov-Center 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | using namespace ELFIO; 26 | 27 | int main( void ) 28 | { 29 | elfio writer; 30 | 31 | // You can't proceed without this function call! 32 | writer.create( ELFCLASS64, ELFDATA2LSB ); 33 | 34 | writer.set_os_abi( ELFOSABI_LINUX ); 35 | writer.set_type( ET_EXEC ); 36 | writer.set_machine( EM_X86_64 ); 37 | 38 | // Create a loadable segment 39 | segment* load_seg = writer.segments.add(); 40 | load_seg->set_type( PT_LOAD ); 41 | load_seg->set_virtual_address( 0x400000 ); 42 | load_seg->set_physical_address( 0x0 ); 43 | load_seg->set_flags( PF_R ); 44 | load_seg->set_align( 0x200000 ); 45 | 46 | // Create a note segment 47 | segment* note_seg = writer.segments.add(); 48 | note_seg->set_type( PT_NOTE ); 49 | note_seg->set_virtual_address( 0x04000e0 ); 50 | note_seg->set_physical_address( 0x04000e0 ); 51 | note_seg->set_flags( PF_R ); 52 | note_seg->set_align( 0x4 ); 53 | 54 | // Create a .note.Xen section, and add it to the note segment. 55 | section* xen_note_sec = writer.sections.add( ".note.Xen" ); 56 | xen_note_sec->set_type( SHT_NOTE ); 57 | xen_note_sec->set_addr_align( 0x4 ); 58 | xen_note_sec->set_flags( SHF_ALLOC ); 59 | note_section_accessor xen_note_writer( writer, xen_note_sec ); 60 | 61 | char descr[2] = { 0x1f, 0x1e }; 62 | xen_note_writer.add_note( 0x12, "Xen", descr, sizeof( descr ) ); 63 | 64 | note_seg->add_section_index( xen_note_sec->get_index(), 65 | xen_note_sec->get_addr_align() ); 66 | 67 | // Setup entry point. Usually, a linker sets this address on base of 68 | // ‘_start’ label. 69 | writer.set_entry( 0x400104 ); 70 | 71 | // Create ELF file 72 | writer.save( "test_invalid_pvh_note.bin" ); 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | # ELF Image parsing and loading 2 | 3 | The boot process is explained from the following two sides. 4 | 5 | ## Loader side 6 | 7 | It follows ELF standard which is specified in elf.rs. 8 | The entry header and program headers will be interpreted, and PT_LOAD segments 9 | will be loaded into guest memory. 10 | 11 | ### Where kernel is loaded 12 | 13 | There are two ways on deciding where the program segments will be loaded. 14 | 15 | - One way is to provide an option and allow vmm to specify where to load the 16 | image, considering its memory layout. 17 | 18 | - The other way is to load image into phdr.p_paddr by default. 19 | 20 | ## VMM side 21 | 22 | ### Construct zero page 23 | 24 | According to the 64-bit boot protocol, the boot parameters (traditionally known 25 | as "zero page") should be setup, including setup_header, e820 table and other 26 | stuff. However, ELF has no setup_header, nothing returned from ELF loader could 27 | be used to fill boot parameters, vmm is totally responsible for the construction. 28 | 29 | ### Configure vCPU 30 | 31 | - RIP, the start offset of guest memory where kernel is loaded, which is 32 | returned from loader 33 | 34 | - 64 bit mode with paging enabled 35 | 36 | - GDT must be configured and loaded 37 | 38 | # bzImage 39 | 40 | The boot process is also explained from the following two sides. 41 | 42 | ## Loader side 43 | 44 | ### What will be returned from loader 45 | 46 | bzImage includes two parts, the setup and the compressed kernel. The compressed 47 | kernel part will be loaded into guest memory, and the following three parts 48 | will be returned to the VMM by the loader. 49 | 50 | - The start address of loaded kernel 51 | 52 | - The offset of memory where kernel is end of loading 53 | 54 | - The setup header begin at the offset 0x01f1 of bzImage, this one is an extra 55 | compared to the return of ELF loader. 56 | 57 | ### Where kernel is loaded 58 | 59 | The same as ELF image loader, there are two ways for deciding where the 60 | compressed kernel will be loaded. 61 | 62 | - VMM specify where to load kernel image. 63 | 64 | - Load into code32_start (Boot load address) by default. 65 | 66 | ### Additional checking 67 | 68 | As what the boot protocol said, the kernel is a bzImage kernel if the 69 | protocol >= 2.00 and the 0x01 bit(LOAD_HIGH) is the loadflags field is set. Add 70 | this checking to validate the bzImage. 71 | 72 | ## VMM side 73 | 74 | ### Construct zero page 75 | 76 | While vmm build "zero page" with e820 table and other stuff, bzImage loader will 77 | return the setup header to fill the boot parameters. Meanwhile, 78 | setup_header.init_size is a must to be filled into zero page, which will be used 79 | during head_64.S boot process. 80 | 81 | ### Configure vCPU 82 | 83 | - RIP, the start address of loaded 64-bit kernel returned from loader + 0x200. 84 | Regarding to the 64-bit boot protocol, kernel is started by jumping to the 85 | 64-bit kernel entry point, which is the start address of loaded 64-bit kernel 86 | plus 0x200. 87 | 88 | - 64 bit mode with paging enabled 89 | 90 | - GDT must be configured and loaded 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing `linux-loader` 2 | 3 | ## Tests 4 | 5 | Our Continuous Integration (CI) pipeline is implemented on top of 6 | [Buildkite](https://buildkite.com/). 7 | For the complete list of tests, check our 8 | [CI pipeline](https://buildkite.com/rust-vmm/rust-vmm-ci). 9 | 10 | Each individual test runs in a container. To reproduce a test locally, you can 11 | use the dev-container on both x86 and arm64. 12 | 13 | ```bash 14 | container_version=11 15 | docker run -it \ 16 | --security-opt seccomp=unconfined \ 17 | --volume $(pwd):/linux-loader \ 18 | rustvmm/dev:v${container_version} 19 | cd linux-loader/ 20 | cargo test --all-features 21 | ``` 22 | 23 | ## Test Images 24 | 25 | The kernel images used in the unit tests fall into 3 categories: 26 | - downloaded from known distro sources using the 27 | [download script](../.buildkite/download_resources.sh). 28 | - fuzzing images; these images were generated by fuzzers such as `libfuzzer` or 29 | `afl` starting from an existing image already used by the unit tests. These 30 | are named with `fuzz_` so that they are easy to identify. 31 | - generated images (either by building the Linux kernel, or by using other 32 | tools such as [ELFIO](https://github.com/serge1/ELFIO)). 33 | 34 | ### Building the `PE` image 35 | 36 | The PE image used by the linux-loader unit tests is built on an `aarch64` 37 | machine using the following commands: 38 | 39 | ```bash 40 | git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 41 | git checkout v4.20 42 | 43 | # To be able to run the following command, you'll need to install 44 | # flex and bison. 45 | make allnoconfig 46 | make Image 47 | head -c 4096 arch/arm64/boot/Image > test_image.bin 48 | ``` 49 | 50 | ### Generating `ELF` images with [ELFIO](https://github.com/serge1/ELFIO) 51 | 52 | All the ELF files used in `linux-loader` are generated using the `ELFIO` tool. 53 | The [`.cpp` source files](elfio_files) are created from the ELFIO `writer` 54 | example, with minimal changes on top. 55 | 56 | The next section includes an example of how to use the source files to generate 57 | the binaries used in the unit tests. 58 | 59 | | Source File | Generated Binary File | 60 | |-------------|-----------------------| 61 | | bad_align_writer.cpp | test_bad_align.bin | 62 | | invalid_pvh_note_writer.cpp | test_invalid_pvh_note.bin | 63 | | dummy_note.cpp | test_dummy_note.bin | 64 | | basic_elf.cpp | test_elf.bin | 65 | | ignored_phv.cpp | test_elfnote.bin | 66 | | ignored_phv_8byte_align.cpp | test_elfnote_8byte_align.bin | 67 | 68 | #### Example for generating `test_bad_align.bin` 69 | 70 | ```bash 71 | git clone git@github.com:serge1/ELFIO.git 72 | 73 | # Copy the bad_align_writer.cpp file from this repo to the ELFIO path. 74 | cp "${LINUX_LOADER_PATH}/docs/elfio_files/bad_align_writer.cpp" "${ELFIO_PATH}" 75 | cd "${ELFIO_PATH}" 76 | # The images in this repo were built from the`57e614a` commit. 77 | git checkout 57e614a 78 | 79 | g++ bad_align_writer.cpp -o bad_align_writer -I. -std=c++11 80 | ./bad_align_writer 81 | cp test_bad_align.bin "${LINUX_LOADER_PATH}/src/loader/elf/" 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/elfio_files/ignored_phv.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2001-present by Serge Lamikhov-Center 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | using namespace ELFIO; 26 | 27 | int main( void ) 28 | { 29 | elfio writer; 30 | 31 | // You can't proceed without this function call! 32 | writer.create( ELFCLASS64, ELFDATA2LSB ); 33 | 34 | writer.set_os_abi( ELFOSABI_LINUX ); 35 | writer.set_type( ET_EXEC ); 36 | writer.set_machine( EM_X86_64 ); 37 | 38 | // Create a loadable segment 39 | segment* load_seg = writer.segments.add(); 40 | load_seg->set_type( PT_LOAD ); 41 | load_seg->set_virtual_address( 0x400000 ); 42 | load_seg->set_physical_address( 0x400000 ); 43 | load_seg->set_flags( PF_R ); 44 | load_seg->set_align( 0x200000 ); 45 | 46 | // Create a note segment 47 | segment* note_seg = writer.segments.add(); 48 | note_seg->set_type( PT_NOTE ); 49 | note_seg->set_virtual_address( 0x4000b0 ); 50 | note_seg->set_physical_address( 0x4000b0 ); 51 | note_seg->set_flags( PF_R ); 52 | note_seg->set_align( 0x4 ); 53 | 54 | // Create a .note.dummy section, and add it to the note segment. 55 | section* dummy_note_sec = writer.sections.add( ".note.dummy" ); 56 | dummy_note_sec->set_type( SHT_NOTE ); 57 | dummy_note_sec->set_addr_align( 0x4 ); 58 | dummy_note_sec->set_flags( SHF_ALLOC ); 59 | note_section_accessor dummy_note_writer( writer, dummy_note_sec ); 60 | 61 | unsigned char dummy_desc[8] = { 0xfe, 0xca, 0xfe, 0xca, 0x00, 0x00 }; 62 | dummy_note_writer.add_note( 0x01, "dummy", dummy_desc, sizeof( dummy_desc ) ); 63 | 64 | note_seg->add_section_index( dummy_note_sec->get_index(), 65 | dummy_note_sec->get_addr_align() ); 66 | 67 | // Create a .note.Xen section, and add it to the note segment. 68 | section* xen_note_sec = writer.sections.add( ".note.Xen" ); 69 | xen_note_sec->set_type( SHT_NOTE ); 70 | xen_note_sec->set_addr_align( 0x4 ); 71 | xen_note_sec->set_flags( SHF_ALLOC ); 72 | note_section_accessor xen_note_writer( writer, xen_note_sec ); 73 | 74 | unsigned char xen_descr[8] = { 0x1f, 0xfe, 0xe1, 0x01 }; 75 | xen_note_writer.add_note( 0x12, "Xen", xen_descr, sizeof( xen_descr ) ); 76 | 77 | note_seg->add_section_index( xen_note_sec->get_index(), 78 | xen_note_sec->get_addr_align() ); 79 | 80 | // Create a .note.gnu.build-id section, and add it to the note segment. 81 | section* gnu_note_sec = writer.sections.add( ".note.gnu.build-id" ); 82 | gnu_note_sec->set_type( SHT_NOTE ); 83 | gnu_note_sec->set_addr_align( 0x4 ); 84 | gnu_note_sec->set_flags( SHF_ALLOC ); 85 | note_section_accessor gnu_note_writer( writer, gnu_note_sec ); 86 | 87 | unsigned char gnu_descr[20] = { 0x28, 0xcc, 0x3d, 0x3d, 0x89, 0xe5, 0xbf, 88 | 0xc6, 0x07, 0xa8, 0xce, 0xe3, 0x29, 0xcc, 89 | 0x70, 0xd0, 0xbf, 0x34, 0x69, 0x2b }; 90 | gnu_note_writer.add_note( 0x03, "GNU", gnu_descr, sizeof( gnu_descr ) ); 91 | 92 | note_seg->add_section_index( gnu_note_sec->get_index(), 93 | gnu_note_sec->get_addr_align() ); 94 | // Setup entry point. Usually, a linker sets this address on base of 95 | // ‘_start’ label. 96 | writer.set_entry( 0x400108 ); 97 | 98 | // Create ELF file 99 | writer.save( "test_elfnote.bin" ); 100 | 101 | return 0; 102 | } 103 | -------------------------------------------------------------------------------- /docs/elfio_files/ignored_phv_8byte_align.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2001-present by Serge Lamikhov-Center 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | using namespace ELFIO; 26 | 27 | int main( void ) 28 | { 29 | elfio writer; 30 | 31 | // You can't proceed without this function call! 32 | writer.create( ELFCLASS64, ELFDATA2LSB ); 33 | 34 | writer.set_os_abi( ELFOSABI_LINUX ); 35 | writer.set_type( ET_EXEC ); 36 | writer.set_machine( EM_X86_64 ); 37 | 38 | // Create a loadable segment 39 | segment* load_seg = writer.segments.add(); 40 | load_seg->set_type( PT_LOAD ); 41 | load_seg->set_virtual_address( 0x400000 ); 42 | load_seg->set_physical_address( 0x400000 ); 43 | load_seg->set_flags( PF_R ); 44 | load_seg->set_align( 0x200000 ); 45 | 46 | // Create a note segment 47 | segment* note_seg = writer.segments.add(); 48 | note_seg->set_type( PT_NOTE ); 49 | note_seg->set_virtual_address( 0x4000b0 ); 50 | note_seg->set_physical_address( 0x4000b0 ); 51 | note_seg->set_flags( PF_R ); 52 | note_seg->set_align( 0x8 ); 53 | 54 | // Create a .note.dummy section, and add it to the note segment. 55 | section* dummy_note_sec = writer.sections.add( ".note.dummy" ); 56 | dummy_note_sec->set_type( SHT_NOTE ); 57 | dummy_note_sec->set_addr_align( 0x4 ); 58 | dummy_note_sec->set_flags( SHF_ALLOC ); 59 | note_section_accessor dummy_note_writer( writer, dummy_note_sec ); 60 | 61 | unsigned char dummy_desc[8] = { 0xfe, 0xca, 0xfe, 0xca, 0x00, 0x00 }; 62 | dummy_note_writer.add_note( 0x01, "dummy", dummy_desc, sizeof( dummy_desc ) ); 63 | 64 | note_seg->add_section_index( dummy_note_sec->get_index(), 65 | dummy_note_sec->get_addr_align() ); 66 | 67 | // Create a .note.Xen section, and add it to the note segment. 68 | section* xen_note_sec = writer.sections.add( ".note.Xen" ); 69 | xen_note_sec->set_type( SHT_NOTE ); 70 | xen_note_sec->set_addr_align( 0x4 ); 71 | xen_note_sec->set_flags( SHF_ALLOC ); 72 | note_section_accessor xen_note_writer( writer, xen_note_sec ); 73 | 74 | unsigned char xen_descr[8] = { 0x1f, 0xfe, 0xe1, 0x01 }; 75 | xen_note_writer.add_note( 0x12, "Xen", xen_descr, sizeof( xen_descr ) ); 76 | 77 | note_seg->add_section_index( xen_note_sec->get_index(), 78 | xen_note_sec->get_addr_align() ); 79 | 80 | // Create a .note.gnu.build-id section, and add it to the note segment. 81 | section* gnu_note_sec = writer.sections.add( ".note.gnu.build-id" ); 82 | gnu_note_sec->set_type( SHT_NOTE ); 83 | gnu_note_sec->set_addr_align( 0x4 ); 84 | gnu_note_sec->set_flags( SHF_ALLOC ); 85 | note_section_accessor gnu_note_writer( writer, gnu_note_sec ); 86 | 87 | unsigned char gnu_descr[20] = { 0x28, 0xcc, 0x3d, 0x3d, 0x89, 0xe5, 0xbf, 88 | 0xc6, 0x07, 0xa8, 0xce, 0xe3, 0x29, 0xcc, 89 | 0x70, 0xd0, 0xbf, 0x34, 0x69, 0x2b }; 90 | gnu_note_writer.add_note( 0x03, "GNU", gnu_descr, sizeof( gnu_descr ) ); 91 | 92 | note_seg->add_section_index( gnu_note_sec->get_index(), 93 | gnu_note_sec->get_addr_align() ); 94 | // Setup entry point. Usually, a linker sets this address on base of 95 | // ‘_start’ label. 96 | writer.set_entry( 0x400108 ); 97 | 98 | // Create ELF file 99 | writer.save( "test_elfnote_8byte_align.bin" ); 100 | 101 | return 0; 102 | } 103 | -------------------------------------------------------------------------------- /docs/elfio_files/basic_elf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2001-present by Serge Lamikhov-Center 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | using namespace ELFIO; 26 | 27 | int main( void ) 28 | { 29 | elfio writer; 30 | 31 | // You can't proceed without this function call! 32 | writer.create( ELFCLASS64, ELFDATA2LSB ); 33 | 34 | writer.set_os_abi( ELFOSABI_LINUX ); 35 | writer.set_type( ET_EXEC ); 36 | writer.set_machine( EM_X86_64 ); 37 | 38 | // Create code section 39 | section* text_sec = writer.sections.add( ".text" ); 40 | text_sec->set_type( SHT_PROGBITS ); 41 | text_sec->set_flags( SHF_ALLOC | SHF_EXECINSTR ); 42 | text_sec->set_addr_align( 0x10 ); 43 | 44 | // Add data into it 45 | char text[] = { 46 | '\xB8', '\x04', '\x00', '\x00', '\x00', // mov eax, 4 47 | '\xBB', '\x01', '\x00', '\x00', '\x00', // mov ebx, 1 48 | '\xB9', '\x20', '\x80', '\x04', '\x08', // mov ecx, msg 49 | '\xBA', '\x0E', '\x00', '\x00', '\x00', // mov edx, 14 50 | '\xCD', '\x80', // int 0x80 51 | '\xB8', '\x01', '\x00', '\x00', '\x00', // mov eax, 1 52 | '\xCD', '\x80' // int 0x80 53 | }; 54 | text_sec->set_data( text, sizeof( text ) ); 55 | 56 | // Create a loadable segment 57 | segment* text_seg = writer.segments.add(); 58 | text_seg->set_type( PT_LOAD ); 59 | text_seg->set_virtual_address( 0x400 ); 60 | text_seg->set_physical_address( 0x0 ); 61 | text_seg->set_flags( PF_X | PF_R ); 62 | text_seg->set_align( 0x1 ); 63 | 64 | // Add code section into program segment 65 | text_seg->add_section_index( text_sec->get_index(), 66 | text_sec->get_addr_align() ); 67 | 68 | // Create data section 69 | section* data_sec = writer.sections.add( ".data" ); 70 | data_sec->set_type( SHT_PROGBITS ); 71 | data_sec->set_flags( SHF_ALLOC | SHF_WRITE ); 72 | data_sec->set_addr_align( 0x4 ); 73 | 74 | char data[] = { 75 | '\x48', '\x65', '\x6C', '\x6C', '\x6F', // msg: db 'Hello, World!', 10 76 | '\x2C', '\x20', '\x57', '\x6F', '\x72', 77 | '\x6C', '\x64', '\x21', '\x0A' }; 78 | data_sec->set_data( data, sizeof( data ) ); 79 | 80 | // Create a read/write segment 81 | segment* data_seg = writer.segments.add(); 82 | data_seg->set_type( PT_LOAD ); 83 | data_seg->set_virtual_address( 0x0420 ); 84 | data_seg->set_physical_address( 0x0420 ); 85 | data_seg->set_flags( PF_W | PF_R ); 86 | data_seg->set_align( 0x10 ); 87 | 88 | // Add code section into program segment 89 | data_seg->add_section_index( data_sec->get_index(), 90 | data_sec->get_addr_align() ); 91 | 92 | // Add optional signature for the file producer 93 | section* note_sec = writer.sections.add( ".note" ); 94 | note_sec->set_type( SHT_NOTE ); 95 | note_sec->set_addr_align( 1 ); 96 | note_section_accessor note_writer( writer, note_sec ); 97 | note_writer.add_note( 0x01, "Created by ELFIO", 0, 0 ); 98 | char descr[6] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; 99 | note_writer.add_note( 0x01, "Never easier!", descr, sizeof( descr ) ); 100 | 101 | // Setup entry point. Usually, a linker sets this address on base of 102 | // ‘_start’ label. 103 | // In this example, the code starts at the first address of the 104 | // 'text_seg' segment. Therefore, the start address is set 105 | // to be equal to the segment location 106 | writer.set_entry( 0x400 ); 107 | 108 | // Create ELF file 109 | writer.save( "test_elf.bin" ); 110 | 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /benches/x86_64/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style license that can be 4 | // found in the LICENSE-BSD-3-Clause file. 5 | // 6 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 7 | 8 | #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] 9 | #![cfg(any(feature = "elf", feature = "pe", feature = "bzimage"))] 10 | #![allow(unused_imports)] 11 | 12 | extern crate linux_loader; 13 | extern crate vm_memory; 14 | 15 | use std::fs::File; 16 | use std::hint::black_box; 17 | use std::io::{Cursor, Read}; 18 | 19 | use linux_loader::configurator::pvh::PvhBootConfigurator; 20 | use linux_loader::configurator::{BootConfigurator, BootParams}; 21 | #[cfg(feature = "bzimage")] 22 | use linux_loader::loader::bzimage::BzImage; 23 | use linux_loader::loader::elf::start_info::{hvm_memmap_table_entry, hvm_start_info}; 24 | use linux_loader::loader::elf::Elf; 25 | use linux_loader::loader::KernelLoader; 26 | use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; 27 | 28 | use criterion::Criterion; 29 | 30 | const MEM_SIZE: usize = 0x100_0000; 31 | const E820_RAM: u32 = 1; 32 | const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578; 33 | 34 | fn create_guest_memory() -> GuestMemoryMmap { 35 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap() 36 | } 37 | 38 | fn create_elf_pvh_image() -> Vec { 39 | include_bytes!(concat!( 40 | env!("CARGO_MANIFEST_DIR"), 41 | "/src/loader/elf/test_elfnote.bin" 42 | )) 43 | .to_vec() 44 | } 45 | 46 | fn build_boot_params() -> (hvm_start_info, Vec) { 47 | let mut start_info = hvm_start_info::default(); 48 | let memmap_entry = hvm_memmap_table_entry { 49 | addr: 0x7000, 50 | size: 0, 51 | type_: E820_RAM, 52 | reserved: 0, 53 | }; 54 | start_info.magic = XEN_HVM_START_MAGIC_VALUE; 55 | start_info.version = 1; 56 | start_info.nr_modules = 0; 57 | start_info.memmap_entries = 0; 58 | (start_info, vec![memmap_entry]) 59 | } 60 | 61 | fn build_pvh_boot_params() -> BootParams { 62 | let (mut start_info, memmap_entries) = build_boot_params(); 63 | // Address in guest memory where the `start_info` struct will be written. 64 | let start_info_addr = GuestAddress(0x6000); 65 | // Address in guest memory where the memory map will be written. 66 | let memmap_addr = GuestAddress(0x7000); 67 | start_info.memmap_paddr = memmap_addr.raw_value(); 68 | // Write boot parameters in guest memory. 69 | let mut boot_params = BootParams::new::(&start_info, start_info_addr); 70 | boot_params.set_sections::(&memmap_entries, memmap_addr); 71 | boot_params 72 | } 73 | 74 | #[cfg(feature = "bzimage")] 75 | fn download_resources() { 76 | use std::process::Command; 77 | 78 | let command = "./.buildkite/download_resources.sh"; 79 | let status = Command::new(command).status().unwrap(); 80 | if !status.success() { 81 | panic!("Cannot run build script"); 82 | } 83 | } 84 | 85 | #[cfg(feature = "bzimage")] 86 | fn create_bzimage() -> Vec { 87 | download_resources(); 88 | let mut v = Vec::new(); 89 | let path = concat!(env!("CARGO_MANIFEST_DIR"), "/src/loader/bzimage/bzimage"); 90 | let mut f = File::open(path).unwrap(); 91 | f.read_to_end(&mut v).unwrap(); 92 | 93 | v 94 | } 95 | 96 | pub fn criterion_benchmark(c: &mut Criterion) { 97 | let guest_mem = create_guest_memory(); 98 | 99 | let elf_pvh_image = create_elf_pvh_image(); 100 | let pvh_boot_params = build_pvh_boot_params(); 101 | 102 | c.bench_function("load_elf_pvh", |b| { 103 | b.iter(|| { 104 | black_box(Elf::load( 105 | &guest_mem, 106 | None, 107 | &mut Cursor::new(&elf_pvh_image), 108 | None, 109 | )) 110 | .unwrap(); 111 | }) 112 | }); 113 | 114 | c.bench_function("configure_pvh", |b| { 115 | b.iter(|| { 116 | black_box(PvhBootConfigurator::write_bootparams::( 117 | &pvh_boot_params, 118 | &guest_mem, 119 | )) 120 | .unwrap(); 121 | }) 122 | }); 123 | 124 | #[cfg(feature = "bzimage")] 125 | let bzimage = create_bzimage(); 126 | 127 | #[cfg(feature = "bzimage")] 128 | c.bench_function("load_bzimage", |b| { 129 | b.iter(|| { 130 | black_box(BzImage::load( 131 | &guest_mem, 132 | None, 133 | &mut Cursor::new(&bzimage), 134 | None, 135 | )) 136 | .unwrap(); 137 | }) 138 | }); 139 | } 140 | -------------------------------------------------------------------------------- /docs/elfio_files/bad_align_writer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2001-present by Serge Lamikhov-Center 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | using namespace ELFIO; 26 | 27 | int main( void ) 28 | { 29 | elfio writer; 30 | 31 | // You can't proceed without this function call! 32 | writer.create( ELFCLASS64, ELFDATA2LSB ); 33 | 34 | writer.set_os_abi( ELFOSABI_LINUX ); 35 | writer.set_type( ET_EXEC ); 36 | writer.set_machine( EM_X86_64 ); 37 | 38 | // Create code section 39 | section* text_sec = writer.sections.add( ".text" ); 40 | text_sec->set_type( SHT_PROGBITS ); 41 | text_sec->set_flags( SHF_ALLOC | SHF_EXECINSTR ); 42 | text_sec->set_addr_align( 0x10 ); 43 | 44 | // Add data into it 45 | char text[] = { 46 | '\xB8', '\x04', '\x00', '\x00', '\x00', // mov eax, 4 47 | '\xBB', '\x01', '\x00', '\x00', '\x00', // mov ebx, 1 48 | '\xB9', '\x20', '\x80', '\x04', '\x08', // mov ecx, msg 49 | '\xBA', '\x0E', '\x00', '\x00', '\x00', // mov edx, 14 50 | '\xCD', '\x80', // int 0x80 51 | '\xB8', '\x01', '\x00', '\x00', '\x00', // mov eax, 1 52 | '\xCD', '\x80' // int 0x80 53 | }; 54 | text_sec->set_data( text, sizeof( text ) ); 55 | 56 | // Create a loadable segment 57 | segment* text_seg = writer.segments.add(); 58 | text_seg->set_type( PT_LOAD ); 59 | text_seg->set_virtual_address( 0x400 ); 60 | text_seg->set_physical_address( 0x400 ); 61 | text_seg->set_flags( PF_X | PF_R ); 62 | text_seg->set_align( 0x100 ); 63 | 64 | // Add code section into program segment 65 | text_seg->add_section_index( text_sec->get_index(), 66 | text_sec->get_addr_align() ); 67 | 68 | // Create data section 69 | section* data_sec = writer.sections.add( ".data" ); 70 | data_sec->set_type( SHT_PROGBITS ); 71 | data_sec->set_flags( SHF_ALLOC | SHF_WRITE ); 72 | data_sec->set_addr_align( 0x4 ); 73 | 74 | char data[] = { 75 | '\x48', '\x65', '\x6C', '\x6C', '\x6F', // msg: db 'Hello, World!', 10 76 | '\x2C', '\x20', '\x57', '\x6F', '\x72', 77 | '\x6C', '\x64', '\x21', '\x0A' }; 78 | data_sec->set_data( data, sizeof( data ) ); 79 | 80 | // Create a read/write segment 81 | segment* data_seg = writer.segments.add(); 82 | data_seg->set_type( PT_NOTE ); 83 | data_seg->set_virtual_address( 0x8888048020 ); 84 | data_seg->set_physical_address( 0x8888048020 ); 85 | data_seg->set_flags( PF_W | PF_R ); 86 | data_seg->set_align( 13 ); 87 | 88 | // Add code section into program segment 89 | data_seg->add_section_index( data_sec->get_index(), 90 | data_sec->get_addr_align() ); 91 | 92 | // Add optional signature for the file producer 93 | section* note_sec = writer.sections.add( ".note" ); 94 | note_sec->set_type( SHT_NOTE ); 95 | note_sec->set_addr_align( 1 ); 96 | note_section_accessor note_writer( writer, note_sec ); 97 | note_writer.add_note( 0x01, "Created by ELFIO", 0, 0 ); 98 | char descr[6] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; 99 | note_writer.add_note( 0x01, "Never easier!", descr, sizeof( descr ) ); 100 | 101 | // Setup entry point. Usually, a linker sets this address on base of 102 | // ‘_start’ label. 103 | // In this example, the code starts at the first address of the 104 | // 'text_seg' segment. Therefore, the start address is set 105 | // to be equal to the segment location 106 | writer.set_entry( 0x400 ); 107 | 108 | // Create ELF file 109 | writer.save( "test_bad_align.bin" ); 110 | 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linux-loader 2 | 3 | [![crates.io](https://img.shields.io/crates/v/linux-loader)](https://crates.io/crates/linux-loader) 4 | [![docs.rs](https://img.shields.io/docsrs/linux-loader)](https://docs.rs/linux-loader/) 5 | 6 | The `linux-loader` crate offers support for loading raw ELF (`vmlinux`) and 7 | compressed big zImage (`bzImage`) format kernel images on `x86_64` and PE 8 | (`Image`) kernel images on `aarch64` and `riscv64`. ELF support includes the 9 | [Linux](https://www.kernel.org/doc/Documentation/x86/boot.txt) and 10 | [PVH](https://xenbits.xen.org/docs/unstable/misc/pvh.html) boot protocols. 11 | 12 | The `linux-loader` crate is not yet fully independent and self-sufficient, and 13 | much of the boot process remains the VMM's responsibility. See [Usage] for details. 14 | 15 | ## Supported features 16 | 17 | - Parsing and loading kernel images into guest memory. 18 | - `x86_64`: `vmlinux` (raw ELF image), `bzImage` 19 | - `aarch64`: `Image` 20 | - `riscv64`: `Image` 21 | - Parsing and building the kernel command line. 22 | - Loading device tree blobs (`aarch64` and `riscv64`). 23 | - Configuring boot parameters using the exported primitives. 24 | - `x86_64` Linux boot: 25 | - [`setup_header`](https://elixir.bootlin.com/linux/latest/source/arch/x86/include/uapi/asm/bootparam.h#L65) 26 | - [`boot_params`](https://elixir.bootlin.com/linux/latest/source/arch/x86/include/uapi/asm/bootparam.h#L175) 27 | - `x86_64` PVH boot: 28 | - [`hvm_start_info`](https://elixir.bootlin.com/linux/latest/source/include/xen/interface/hvm/start_info.h#L125) 29 | - [`hvm_modlist_entry`](https://elixir.bootlin.com/linux/latest/source/include/xen/interface/hvm/start_info.h#L145) 30 | - [`hvm_memmap_table_entry`](https://elixir.bootlin.com/linux/latest/source/include/xen/interface/hvm/start_info.h#L152) 31 | - `aarch64` boot: 32 | - [`arm64_image_header`](https://elixir.bootlin.com/linux/latest/source/arch/arm64/include/asm/image.h#L44) 33 | - `riscv64` boot: 34 | - [`riscv64_image_header`](https://elixir.bootlin.com/linux/latest/source/arch/riscv/include/asm/image.h#L51) 35 | 36 | ## Usage 37 | 38 | Booting a guest using the `linux-loader` crate involves several steps, 39 | depending on the boot protocol used. A simplified overview follows. 40 | 41 | Consider an `x86_64` VMM that: 42 | - interfaces with `linux-loader`; 43 | - uses `GuestMemoryMmap` for its guest memory backend; 44 | - loads an ELF kernel image from a `File`. 45 | 46 | ### Loading the kernel 47 | 48 | One of the first steps in starting the guest is to load the kernel from a 49 | [`Read`er](https://doc.rust-lang.org/std/io/trait.Read.html) into guest memory. 50 | For this step, the VMM is required to have configured its guest memory. 51 | 52 | In this example, the VMM specifies both the kernel starting address and the 53 | starting address of high memory. 54 | 55 | ```rust 56 | use linux_loader::loader::elf::Elf as Loader; 57 | use vm_memory::GuestMemoryMmap; 58 | 59 | use std::fs::File; 60 | use std::result::Result; 61 | 62 | impl MyVMM { 63 | fn start_vm(&mut self) { 64 | let guest_memory = self.create_guest_memory(); 65 | let kernel_file = self.open_kernel_file(); 66 | 67 | let load_result = Loader::load::( 68 | &guest_memory, 69 | Some(self.kernel_start_addr()), 70 | &mut kernel_file, 71 | Some(self.himem_start_addr()), 72 | ) 73 | .expect("Failed to load kernel"); 74 | } 75 | } 76 | ``` 77 | 78 | ### Configuring the devices and kernel command line 79 | 80 | After the guest memory has been created and the kernel parsed and loaded, the 81 | VMM will optionally configure devices and the kernel command line. The latter 82 | can then be loaded in guest memory. 83 | 84 | ```rust 85 | impl MyVMM { 86 | fn start_vm(&mut self) { 87 | ... 88 | let cmdline_size = self.kernel_cmdline().as_str().len() + 1; 89 | linux_loader::loader::load_cmdline::( 90 | &guest_memory, 91 | self.cmdline_start_addr(), 92 | &CString::new(kernel_cmdline).expect("Failed to parse cmdline") 93 | ).expect("Failed to load cmdline"); 94 | } 95 | ``` 96 | 97 | ### Configuring boot parameters 98 | 99 | The VMM sets up initial registry values in this phase, without using 100 | `linux-loader`. It can also configure additional boot parameters, using the 101 | structs exported by `linux-loader`. 102 | 103 | ```rust 104 | use linux_loader::configurator::linux::LinuxBootConfigurator; 105 | use linux_loader::configurator::{BootConfigurator, BootParams}; 106 | 107 | impl MyVMM { 108 | fn start_vm(&mut self) { 109 | ... 110 | let mut bootparams = boot_params::default(); 111 | self.configure_bootparams(&mut bootparams); 112 | LinuxBootConfigurator::write_bootparams( 113 | BootParams::new(¶ms, self.zeropage_addr()), 114 | &guest_memory, 115 | ).expect("Failed to write boot params in guest memory"); 116 | } 117 | ``` 118 | 119 | Done! 120 | 121 | ## Testing 122 | 123 | See [`docs/TESTING.md`](docs/TESTING.md). 124 | 125 | ## License 126 | 127 | This project is licensed under either of: 128 | - [Apache License](LICENSE-APACHE), Version 2.0 129 | - [BSD-3-Clause License](LICENSE-BSD-3-Clause) 130 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 2 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // 4 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style license that can be 6 | // found in the LICENSE-BSD-3-Clause file. 7 | // 8 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 9 | 10 | #![deny(missing_docs)] 11 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 12 | 13 | //! A Linux kernel image loading crate. 14 | //! 15 | //! This crate offers support for loading raw ELF (vmlinux), compressed 16 | //! big zImage (bzImage) and PE (Image) kernel images. 17 | //! ELF support includes the Linux and PVH boot protocols. 18 | //! Support for any other kernel image format can be added by implementing 19 | //! the [`KernelLoader`] and [`BootConfigurator`]. 20 | //! 21 | //! # Platform support 22 | //! 23 | //! - `x86_64` 24 | //! - `ARM64` 25 | //! - `RISC-V64` 26 | //! 27 | //! # Example - load an ELF kernel and configure boot params with the PVH protocol 28 | //! 29 | //! This example shows how to prepare a VM for booting with an ELF kernel, following the PVH 30 | //! boot protocol. 31 | //! 32 | //! ```rust 33 | //! # extern crate linux_loader; 34 | //! # extern crate vm_memory; 35 | //! # use std::{io::{Cursor, Read}, fs::File}; 36 | //! 37 | //! # #[cfg(all( 38 | //! # any(target_arch = "x86", target_arch = "x86_64"), 39 | //! # any(feature = "elf", feature = "pe", feature = "bzimage") 40 | //! # ))] 41 | //! # mod imports { 42 | //! # pub use linux_loader::configurator::{BootConfigurator, BootParams}; 43 | //! # pub use linux_loader::configurator::pvh::PvhBootConfigurator; 44 | //! # pub use linux_loader::loader::elf::start_info::{hvm_memmap_table_entry, hvm_start_info}; 45 | //! # pub use linux_loader::loader::elf::Elf; 46 | //! # } 47 | //! 48 | //! # #[cfg(all( 49 | //! # any(target_arch = "x86", target_arch = "x86_64"), 50 | //! # any(feature = "elf", feature = "pe", feature = "bzimage") 51 | //! # ))] 52 | //! # pub use imports::*; 53 | //! 54 | //! # use linux_loader::loader::KernelLoader; 55 | //! # use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; 56 | //! # const E820_RAM: u32 = 1; 57 | //! # const MEM_SIZE: usize = 0x100_0000; 58 | //! # const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578; 59 | //! 60 | //! # #[cfg(all( 61 | //! # any(target_arch = "x86", target_arch = "x86_64"), 62 | //! # any(feature = "elf", feature = "pe", feature = "bzimage") 63 | //! # ))] 64 | //! fn build_boot_params() -> (hvm_start_info, Vec) { 65 | //! let mut start_info = hvm_start_info::default(); 66 | //! let memmap_entry = hvm_memmap_table_entry { 67 | //! addr: 0x7000, 68 | //! size: 0, 69 | //! type_: E820_RAM, 70 | //! reserved: 0, 71 | //! }; 72 | //! start_info.magic = XEN_HVM_START_MAGIC_VALUE; 73 | //! start_info.version = 1; 74 | //! start_info.nr_modules = 0; 75 | //! start_info.memmap_entries = 0; 76 | //! (start_info, vec![memmap_entry]) 77 | //! } 78 | //! 79 | //! # #[cfg(all( 80 | //! # any(target_arch = "x86", target_arch = "x86_64"), 81 | //! # any(feature = "elf", feature = "pe", feature = "bzimage") 82 | //! # ))] 83 | //! fn main() { 84 | //! let guest_mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap(); 85 | //! 86 | //! let mut elf_pvh_image = Vec::new(); 87 | //! let path = concat!( 88 | //! env!("CARGO_MANIFEST_DIR"), 89 | //! "/src/loader/elf/test_elfnote.bin" 90 | //! ); 91 | //! let mut file = File::open(path).unwrap(); 92 | //! file.read_to_end(&mut elf_pvh_image).unwrap(); 93 | //! 94 | //! // Load the kernel image. 95 | //! let loader_result = 96 | //! Elf::load(&guest_mem, None, &mut Cursor::new(&elf_pvh_image), None).unwrap(); 97 | //! 98 | //! // Build boot parameters. 99 | //! let (mut start_info, memmap_entries) = build_boot_params(); 100 | //! // Address in guest memory where the `start_info` struct will be written. 101 | //! let start_info_addr = GuestAddress(0x6000); 102 | //! // Address in guest memory where the memory map will be written. 103 | //! let memmap_addr = GuestAddress(0x7000); 104 | //! start_info.memmap_paddr = memmap_addr.raw_value(); 105 | //! 106 | //! // Write boot parameters in guest memory. 107 | //! let mut boot_params = BootParams::new::(&start_info, start_info_addr); 108 | //! boot_params.set_sections::(&memmap_entries, memmap_addr); 109 | //! PvhBootConfigurator::write_bootparams::(&boot_params, &guest_mem).unwrap(); 110 | //! } 111 | //! 112 | //! # #[cfg(all( 113 | //! # any(target_arch = "aarch64", target_arch = "riscv64"), 114 | //! # any(feature = "elf", feature = "pe", feature = "bzimage") 115 | //! # ))] 116 | //! # fn main() {} 117 | //! 118 | //! # #[cfg(not(any(feature = "elf", feature = "pe", feature = "bzimage")))] 119 | //! # fn main() {} 120 | //! ``` 121 | //! 122 | //! [`BootConfigurator`]: trait.BootConfigurator.html 123 | //! [`KernelLoader`]: trait.KernelLoader.html 124 | 125 | pub mod cmdline; 126 | pub mod configurator; 127 | pub mod loader; 128 | 129 | #[allow(clippy::undocumented_unsafe_blocks)] 130 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 131 | mod loader_gen; 132 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 133 | pub use loader_gen::*; 134 | 135 | extern crate vm_memory; 136 | -------------------------------------------------------------------------------- /src/configurator/fdt.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 4 | 5 | //! Traits and structs for loading the device tree. 6 | 7 | use vm_memory::{Bytes, GuestMemory}; 8 | 9 | use std::fmt; 10 | 11 | use crate::configurator::{BootConfigurator, BootParams, Error as BootConfiguratorError, Result}; 12 | 13 | /// Errors specific to the device tree boot protocol configuration. 14 | #[derive(Debug, PartialEq, Eq)] 15 | pub enum Error { 16 | /// FDT does not fit in guest memory. 17 | FDTPastRamEnd, 18 | /// Error writing FDT in memory. 19 | WriteFDTToMemory, 20 | } 21 | 22 | impl fmt::Display for Error { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | use Error::*; 25 | let desc = match self { 26 | FDTPastRamEnd => "FDT does not fit in guest memory.", 27 | WriteFDTToMemory => "error writing FDT in guest memory.", 28 | }; 29 | 30 | write!(f, "Device Tree Boot Configurator: {}", desc) 31 | } 32 | } 33 | 34 | impl std::error::Error for Error {} 35 | 36 | impl From for BootConfiguratorError { 37 | fn from(err: Error) -> Self { 38 | BootConfiguratorError::Fdt(err) 39 | } 40 | } 41 | 42 | /// Boot configurator for device tree. 43 | pub struct FdtBootConfigurator {} 44 | 45 | impl BootConfigurator for FdtBootConfigurator { 46 | /// Writes the boot parameters (configured elsewhere) into guest memory. 47 | /// 48 | /// # Arguments 49 | /// 50 | /// * `params` - boot parameters containing the FDT. 51 | /// * `guest_memory` - guest's physical memory. 52 | /// 53 | /// # Examples 54 | /// 55 | /// ```rust 56 | /// # extern crate vm_memory; 57 | /// # use linux_loader::configurator::{BootConfigurator, BootParams}; 58 | /// # use linux_loader::configurator::fdt::FdtBootConfigurator; 59 | /// # use vm_memory::{Address, ByteValued, GuestMemory, GuestMemoryMmap, GuestAddress}; 60 | /// # #[derive(Clone, Copy, Default)] 61 | /// # struct FdtPlaceholder([u8; 0x20]); 62 | /// # unsafe impl ByteValued for FdtPlaceholder {} 63 | /// # fn create_guest_memory() -> GuestMemoryMmap { 64 | /// # GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (0x100_0000 as usize))]).unwrap() 65 | /// # } 66 | /// # fn create_fdt(guest_memory: &GuestMemoryMmap) -> (FdtPlaceholder, GuestAddress) { 67 | /// # let last_addr = guest_memory.last_addr().raw_value(); 68 | /// # (FdtPlaceholder([0u8; 0x20]), GuestAddress(last_addr - 0x20u64)) 69 | /// # } 70 | /// # fn main() { 71 | /// let guest_memory = create_guest_memory(); 72 | /// let (fdt, fdt_addr) = create_fdt(&guest_memory); 73 | /// FdtBootConfigurator::write_bootparams::( 74 | /// &BootParams::new::(&fdt, fdt_addr), 75 | /// &guest_memory, 76 | /// ) 77 | /// .unwrap(); 78 | /// # } 79 | /// ``` 80 | fn write_bootparams(params: &BootParams, guest_memory: &M) -> Result<()> 81 | where 82 | M: GuestMemory, 83 | { 84 | guest_memory 85 | .checked_offset(params.header_start, params.header.len()) 86 | .ok_or(Error::FDTPastRamEnd)?; 87 | 88 | // The VMM has filled an FDT and passed it as a `ByteValued` object. 89 | guest_memory 90 | .write_slice(params.header.as_slice(), params.header_start) 91 | .map_err(|_| Error::WriteFDTToMemory.into()) 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | #![allow(clippy::undocumented_unsafe_blocks)] 98 | use super::*; 99 | use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryMmap}; 100 | 101 | const FDT_MAX_SIZE: usize = 0x20; 102 | const MEM_SIZE: u64 = 0x100_0000; 103 | 104 | fn create_guest_mem() -> GuestMemoryMmap { 105 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() 106 | } 107 | 108 | #[derive(Clone, Copy, Default)] 109 | #[allow(dead_code)] // rustc thinks the field is never read, but it is via `ByteValued` impl 110 | struct FdtPlaceholder([u8; FDT_MAX_SIZE]); 111 | unsafe impl ByteValued for FdtPlaceholder {} 112 | 113 | #[test] 114 | fn test_configure_fdt_boot() { 115 | let fdt = FdtPlaceholder([0u8; FDT_MAX_SIZE]); 116 | let guest_memory = create_guest_mem(); 117 | 118 | // Error case: FDT doesn't fit in guest memory. 119 | let fdt_addr = GuestAddress(guest_memory.last_addr().raw_value() - FDT_MAX_SIZE as u64 + 1); 120 | assert_eq!( 121 | FdtBootConfigurator::write_bootparams::( 122 | &BootParams::new::(&fdt, fdt_addr), 123 | &guest_memory, 124 | ) 125 | .err(), 126 | Some(Error::FDTPastRamEnd.into()) 127 | ); 128 | 129 | let fdt_addr = GuestAddress(guest_memory.last_addr().raw_value() - FDT_MAX_SIZE as u64); 130 | assert!(FdtBootConfigurator::write_bootparams::( 131 | &BootParams::new::(&fdt, fdt_addr), 132 | &guest_memory, 133 | ) 134 | .is_ok()); 135 | } 136 | 137 | #[test] 138 | fn test_error_messages() { 139 | assert_eq!( 140 | format!("{}", Error::FDTPastRamEnd), 141 | "Device Tree Boot Configurator: FDT does not fit in guest memory." 142 | ); 143 | assert_eq!( 144 | format!("{}", Error::WriteFDTToMemory), 145 | "Device Tree Boot Configurator: error writing FDT in guest memory." 146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Upcoming Release 2 | 3 | # [v0.13.2] 4 | 5 | ## Changed 6 | 7 | - [[#214](https://github.com/rust-vmm/linux-loader/pull/214)] Updated vm-memory to 0.17.1. 8 | 9 | # [v0.13.1] 10 | 11 | ## Changed 12 | - [[#148](https://github.com/rust-vmm/linux-loader/pull/148)] Fixing kernel commandline parameter validation 13 | This change allows parameter values containing spaces in the middle, provided they are enclosed in quotes. However, 14 | strings with quotes in the middle will no longer be accepted as valid parameter values. 15 | - [[#205](https://github.com/rust-vmm/linux-loader/pull/205)] Make image header for ARM and RISC-V public 16 | Kernel headers for ARM and RISC-V are made public to be used in a fw_cfg 17 | implementation that reads the kernel header. 18 | 19 | # [v0.13.0] 20 | 21 | ## Added 22 | 23 | - [[#190](https://github.com/rust-vmm/linux-loader/pull/190)] Introduce RISC-V64 24 | architecture support. 25 | 26 | ## Changed 27 | 28 | - [[#194](https://github.com/rust-vmm/linux-loader/pull/194)] Updated vm-memory to 0.16.0. 29 | - [[#197](https://github.com/rust-vmm/linux-loader/pull/197)] Re-organize 30 | `loader`, `configurator` and `benches` module layout, leaving original interface 31 | intact. 32 | 33 | # [v0.12.0] 34 | 35 | ## Changed 36 | 37 | - [[#187](https://github.com/rust-vmm/linux-loader/pull/187)] Updated vm-memory to 0.15.0. 38 | - [[#179](https://github.com/rust-vmm/linux-loader/pull/179)] Load hvm_modlist_entry into guest memory when requested. 39 | - [[#177](https://github.com/rust-vmm/linux-loader/pull/176)] Added loading 40 | of PVH module blobs into guest memory. This enables booting with `initrd` 41 | via PVH boot. 42 | 43 | # [v0.11.0] 44 | 45 | ## Changed 46 | 47 | - [[#173](https://github.com/rust-vmm/linux-loader/pull/173)] Updated vm-memory to 0.14.0. 48 | - [[#170](https://github.com/rust-vmm/linux-loader/pull/170)] Added all features to the generated docs.rs documentation. 49 | 50 | # [v0.10.0] 51 | 52 | ## Changed 53 | 54 | - [[#162](https://github.com/rust-vmm/linux-loader/pull/162)] Updated vm-memory to 0.13.0. 55 | This introduces a `ReadVolatile` bound on `KernelLoader::load`. 56 | 57 | # [v0.9.1] 58 | 59 | ## Fixed 60 | - [[#130]](https://github.com/rust-vmm/linux-loader/issues/130) Generate bindings 61 | to fix unaligned references in unit tests. 62 | - [[#160]](https://github.com/rust-vmm/linux-loader/pulls/160) Update vm-memory to 0.12.2 63 | 64 | # [v0.9.0] 65 | 66 | ## Fixed 67 | - [[#71]](https://github.com/rust-vmm/linux-loader/issues/71) Fix incorrect 68 | alignment for ELF notes, starting address of name field and descriptor 69 | field have a 4-byte alignment. 70 | 71 | # [v0.8.1] 72 | 73 | ## Fixed 74 | 75 | - [[#125]](https://github.com/rust-vmm/linux-loader/pull/125) The ELF 76 | header contains offsets that the loader uses to find other 77 | structures. If those offsets are beyond the end of the file (or would go 78 | past the end of the file) it is essential to error out when attempting 79 | to read those. 80 | 81 | ## Added 82 | - Add a new criterion advisory to ignore list [`2580d4`](https://github.com/rust-vmm/linux-loader/commit/2580d45f741988468e9b086adbcadae7cc7433a5) 83 | 84 | # [v0.8.0] 85 | 86 | ## Changed 87 | 88 | - Updated vm-memory from 0.9.0 to 0.10.0 89 | 90 | # [v0.7.0] 91 | 92 | ## Added 93 | - Added `insert_init_args` method allowing insertion of init arguments into `Cmdline`. 94 | 95 | ## Changed 96 | - Removed `InvalidDevice` error type (it wasn't used anywhere). 97 | - Replaced `From` with `TryFrom` for `Vec` to be able 98 | to propagate errors returned by `as_cstring` when converting a `Cmdline` to `Vec`. 99 | - Support added for both boot and init arguments in `try_from`. 100 | - Changed `new` to return `Result` for invalid command line capacity handling. 101 | 102 | # [v0.6.0] 103 | 104 | ## Changed 105 | - Crate is now using edition 2021. 106 | 107 | ## Added 108 | - Derived `Eq` for `Error` types and the `PvhBootCapability` enum. 109 | 110 | ## Fixed 111 | - Fixed a bug in `load_cmdline` due to which the command line was not null 112 | terminated. This resulted in a change in the `Cmdline` API where instead of 113 | returning the cmdline as a String, we're now returning it as a `CString` as 114 | the latter has support for converting it to a null terminated bytes array. 115 | - Fixed an off-by-one error in load_cmdline, where we were doing validations 116 | on the first address after the command line memory region, instead of the 117 | last inclusive one of it. 118 | 119 | # [v0.5.0] 120 | 121 | ## Fixed 122 | - [[#104]](https://github.com/rust-vmm/linux-loader/issues/104) Fixed 123 | the `--no-default-features` not working. 124 | 125 | ## Changed 126 | - [[#111]](https://github.com/rust-vmm/linux-loader/pull/111) Use 127 | caret requirements for dependencies. 128 | 129 | ## Added 130 | - [[#99]](https://github.com/rust-vmm/linux-loader/pull/99) Implement 131 | `Debug` and `PartialEq` for `CmdLine`. 132 | - [[#100]](https://github.com/rust-vmm/linux-loader/pull/100) Added 133 | `Clone` derive for `CmdLine`. 134 | 135 | # [v0.4.0] 136 | 137 | ## Fixed 138 | 139 | - [[#66]](https://github.com/rust-vmm/linux-loader/issues/66) Fixed potential 140 | overflow in calls to `align_up`. 141 | 142 | ## Changed 143 | 144 | - [[#62]](https://github.com/rust-vmm/linux-loader/issues/62) The 145 | `load_cmdline` function now takes as a parameter the crate defined 146 | `Cmdline` object instead of `Cstr`. This means that customers don't need to 147 | convert the object before calling into `load_cmdline`. 148 | - [[#83]](https://github.com/rust-vmm/linux-loader/issues/83) Updated the 149 | vm-memory dependency requirement to the latest version (0.6.0). 150 | 151 | ## Added 152 | 153 | - [[#79]](https://github.com/rust-vmm/linux-loader/pull/79) Implemented 154 | `From` for `Vec`. This replaces the obsolete `Into` 155 | implementation. 156 | 157 | # [v0.3.0] 158 | 159 | ## Fixed 160 | 161 | - Replaced panic condition in `align_up` with returning an Error. 162 | - Fixed potential hang condition in Elf::load caused by arithmetic overflow. 163 | - Disallow overflow when computing the kernel load address when loading ELF. 164 | - Fix unchecked arithmetic in BzImage::load that could lead to undefined 165 | behavior. 166 | 167 | 168 | ## Added 169 | 170 | - Added functions for specifying virtio MMIO devices when building the kernel 171 | command line. 172 | - Added a function to specify multiple values in `key=values` pairs when 173 | building the kernel command line. 174 | 175 | # [v0.2.0] 176 | 177 | ## Added 178 | 179 | - Added traits and structs for loading ELF (`vmlinux`), big zImage (`bzImage`) 180 | and PE (`Image`) kernels into guest memory. 181 | - Added traits and structs for writing boot parameters to guest memory. 182 | -------------------------------------------------------------------------------- /src/configurator/x86_64/linux.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2020, Oracle and/or its affiliates. 2 | // 3 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 4 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // 6 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 7 | // Use of this source code is governed by a BSD-style license that can be 8 | // found in the LICENSE-BSD-3-Clause file. 9 | // 10 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 11 | 12 | //! Traits and structs for configuring and loading boot parameters on `x86_64` using the Linux 13 | //! boot protocol. 14 | 15 | use vm_memory::{Bytes, GuestMemory}; 16 | 17 | use crate::configurator::{BootConfigurator, BootParams, Error as BootConfiguratorError, Result}; 18 | 19 | use std::fmt; 20 | 21 | /// Boot configurator for the Linux boot protocol. 22 | pub struct LinuxBootConfigurator {} 23 | 24 | /// Errors specific to the Linux boot protocol configuration. 25 | #[derive(Debug, PartialEq, Eq)] 26 | pub enum Error { 27 | /// The zero page extends past the end of guest memory. 28 | ZeroPagePastRamEnd, 29 | /// Error writing to the zero page of guest memory. 30 | ZeroPageSetup, 31 | } 32 | 33 | impl fmt::Display for Error { 34 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 35 | use Error::*; 36 | let desc = match self { 37 | ZeroPagePastRamEnd => "the zero page extends past the end of guest memory.", 38 | ZeroPageSetup => "error writing to the zero page of guest memory.", 39 | }; 40 | 41 | write!(f, "Linux Boot Configurator: {}", desc,) 42 | } 43 | } 44 | 45 | impl std::error::Error for Error {} 46 | 47 | impl From for BootConfiguratorError { 48 | fn from(err: Error) -> Self { 49 | BootConfiguratorError::Linux(err) 50 | } 51 | } 52 | 53 | impl BootConfigurator for LinuxBootConfigurator { 54 | /// Writes the boot parameters (configured elsewhere) into guest memory. 55 | /// 56 | /// # Arguments 57 | /// 58 | /// * `params` - boot parameters. The header contains a [`boot_params`] struct. The `sections` 59 | /// and `modules` are unused. 60 | /// * `guest_memory` - guest's physical memory. 61 | /// 62 | /// # Examples 63 | /// 64 | /// ```rust 65 | /// # extern crate vm_memory; 66 | /// # use linux_loader::configurator::{BootConfigurator, BootParams}; 67 | /// # use linux_loader::configurator::linux::LinuxBootConfigurator; 68 | /// # use linux_loader::loader::bootparam::boot_params; 69 | /// # use vm_memory::{Address, ByteValued, GuestMemory, GuestMemoryMmap, GuestAddress}; 70 | /// # const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; 71 | /// # const KERNEL_HDR_MAGIC: u32 = 0x53726448; 72 | /// # const KERNEL_LOADER_OTHER: u8 = 0xff; 73 | /// # const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; 74 | /// # const MEM_SIZE: u64 = 0x100_0000; 75 | /// # fn create_guest_memory() -> GuestMemoryMmap { 76 | /// # GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() 77 | /// # } 78 | /// fn build_bootparams() -> boot_params { 79 | /// let mut params = boot_params::default(); 80 | /// params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC; 81 | /// params.hdr.header = KERNEL_HDR_MAGIC; 82 | /// params.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES; 83 | /// params.hdr.type_of_loader = KERNEL_LOADER_OTHER; 84 | /// params 85 | /// } 86 | /// 87 | /// fn main() { 88 | /// # let zero_page_addr = GuestAddress(0x30000); 89 | /// let guest_memory = create_guest_memory(); 90 | /// let params = build_bootparams(); 91 | /// let mut bootparams = BootParams::new::(¶ms, zero_page_addr); 92 | /// LinuxBootConfigurator::write_bootparams::(&bootparams, &guest_memory) 93 | /// .unwrap(); 94 | /// } 95 | /// ``` 96 | /// 97 | /// [`boot_params`]: ../loader/bootparam/struct.boot_params.html 98 | fn write_bootparams(params: &BootParams, guest_memory: &M) -> Result<()> 99 | where 100 | M: GuestMemory, 101 | { 102 | // The VMM has filled a `boot_params` struct and its e820 map. 103 | // This will be written in guest memory at the zero page. 104 | guest_memory 105 | .checked_offset(params.header_start, params.header.len()) 106 | .ok_or(Error::ZeroPagePastRamEnd)?; 107 | guest_memory 108 | .write_slice(params.header.as_slice(), params.header_start) 109 | .map_err(|_| Error::ZeroPageSetup)?; 110 | 111 | Ok(()) 112 | } 113 | } 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | use super::*; 118 | use crate::loader_gen::bootparam::boot_params; 119 | use std::mem; 120 | use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; 121 | 122 | const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; 123 | const KERNEL_HDR_MAGIC: u32 = 0x53726448; 124 | const KERNEL_LOADER_OTHER: u8 = 0xff; 125 | const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; 126 | const MEM_SIZE: u64 = 0x100_0000; 127 | 128 | fn create_guest_mem() -> GuestMemoryMmap { 129 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() 130 | } 131 | 132 | fn build_bootparams_common() -> boot_params { 133 | let mut params = boot_params::default(); 134 | params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC; 135 | params.hdr.header = KERNEL_HDR_MAGIC; 136 | params.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES; 137 | params.hdr.type_of_loader = KERNEL_LOADER_OTHER; 138 | params 139 | } 140 | 141 | #[test] 142 | fn test_configure_linux_boot() { 143 | let zero_page_addr = GuestAddress(0x30000); 144 | 145 | let params = build_bootparams_common(); 146 | // This is where we'd append e820 entries, cmdline, PCI, ACPI etc. 147 | 148 | let guest_memory = create_guest_mem(); 149 | 150 | // Error case: boot params don't fit in guest memory (zero page address too close to end). 151 | let bad_zeropg_addr = GuestAddress( 152 | guest_memory.last_addr().raw_value() - mem::size_of::() as u64 + 1, 153 | ); 154 | let mut bootparams = BootParams::new::(¶ms, bad_zeropg_addr); 155 | assert_eq!( 156 | LinuxBootConfigurator::write_bootparams::(&bootparams, &guest_memory,) 157 | .err(), 158 | Some(Error::ZeroPagePastRamEnd.into()), 159 | ); 160 | 161 | // Success case. 162 | bootparams.header_start = zero_page_addr; 163 | assert!(LinuxBootConfigurator::write_bootparams::( 164 | &bootparams, 165 | &guest_memory, 166 | ) 167 | .is_ok()); 168 | } 169 | 170 | #[test] 171 | fn test_error_messages() { 172 | assert_eq!( 173 | format!("{}", Error::ZeroPagePastRamEnd), 174 | "Linux Boot Configurator: the zero page extends past the end of guest memory." 175 | ); 176 | assert_eq!( 177 | format!("{}", Error::ZeroPageSetup), 178 | "Linux Boot Configurator: error writing to the zero page of guest memory." 179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/loader/pe/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 © Institute of Software, CAS. All rights reserved. 2 | // Copyright © 2020, Oracle and/or its affiliates. 3 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 4 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // 6 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 7 | // Use of this source code is governed by a BSD-style license that can be 8 | // found in the LICENSE-BSD-3-Clause file. 9 | // 10 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 11 | 12 | //! Traits and structs for loading pe image kernels into guest memory. 13 | 14 | use std::fmt; 15 | use std::io::{Read, Seek, SeekFrom}; 16 | 17 | use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize, ReadVolatile}; 18 | 19 | use crate::loader::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; 20 | 21 | /// ARM64 and RISC-V64 Image (PE) format support 22 | pub struct PE; 23 | 24 | #[derive(Debug, PartialEq, Eq)] 25 | /// PE kernel loader errors. 26 | pub enum Error { 27 | /// Unable to seek to Image end. 28 | SeekImageEnd, 29 | /// Unable to seek to Image header. 30 | SeekImageHeader, 31 | /// Unable to seek to DTB start. 32 | SeekDtbStart, 33 | /// Unable to seek to DTB end. 34 | SeekDtbEnd, 35 | /// Device tree binary too big. 36 | DtbTooBig, 37 | /// Unable to read kernel image. 38 | ReadKernelImage, 39 | /// Unable to read Image header. 40 | ReadImageHeader, 41 | /// Unable to read DTB image 42 | ReadDtbImage, 43 | /// Invalid Image binary. 44 | InvalidImage, 45 | /// Invalid Image magic number. 46 | InvalidImageMagicNumber, 47 | /// Invalid base address alignment 48 | InvalidBaseAddrAlignment, 49 | } 50 | 51 | impl fmt::Display for Error { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | let desc = match self { 54 | Error::SeekImageEnd => "unable to seek Image end", 55 | Error::SeekImageHeader => "unable to seek Image header", 56 | Error::ReadImageHeader => "unable to read Image header", 57 | Error::ReadDtbImage => "unable to read DTB image", 58 | Error::SeekDtbStart => "unable to seek DTB start", 59 | Error::SeekDtbEnd => "unable to seek DTB end", 60 | Error::InvalidImage => "invalid Image", 61 | Error::InvalidImageMagicNumber => "invalid Image magic number", 62 | Error::DtbTooBig => "device tree image too big", 63 | Error::ReadKernelImage => "unable to read kernel image", 64 | Error::InvalidBaseAddrAlignment => "base address not aligned to 2 MB", 65 | }; 66 | 67 | write!(f, "PE Kernel Loader: {}", desc) 68 | } 69 | } 70 | 71 | impl std::error::Error for Error {} 72 | 73 | #[cfg(target_arch = "aarch64")] 74 | #[repr(C)] 75 | #[derive(Debug, Copy, Clone, Default)] 76 | #[allow(missing_docs)] 77 | // See kernel doc Documentation/arm64/booting.txt for more information. 78 | // All these fields should be little endian. 79 | pub struct arm64_image_header { 80 | pub code0: u32, 81 | pub code1: u32, 82 | pub text_offset: u64, 83 | pub image_size: u64, 84 | pub flags: u64, 85 | pub res2: u64, 86 | pub res3: u64, 87 | pub res4: u64, 88 | pub magic: u32, 89 | pub res5: u32, 90 | } 91 | 92 | #[cfg(target_arch = "aarch64")] 93 | // SAFETY: The layout of the structure is fixed and can be initialized by 94 | // reading its content from byte array. 95 | unsafe impl ByteValued for arm64_image_header {} 96 | 97 | #[cfg(target_arch = "riscv64")] 98 | #[repr(C)] 99 | #[derive(Debug, Copy, Clone, Default)] 100 | #[allow(missing_docs)] 101 | // See kernel doc Documentation/arch/riscv/boot-image-header.rst 102 | // All these fields should be little endian. 103 | pub struct riscv64_image_header { 104 | pub code0: u32, 105 | pub code1: u32, 106 | pub text_offset: u64, 107 | pub image_size: u64, 108 | pub flags: u64, 109 | pub version: u32, 110 | pub res1: u32, 111 | pub res2: u64, 112 | pub magic: u64, 113 | pub magic2: u32, 114 | pub res3: u32, 115 | } 116 | 117 | #[cfg(target_arch = "riscv64")] 118 | // SAFETY: The layout of the structure is fixed and can be initialized by 119 | // reading its content from byte array. 120 | unsafe impl ByteValued for riscv64_image_header {} 121 | 122 | impl KernelLoader for PE { 123 | /// Loads a PE Image into guest memory. 124 | /// 125 | /// # Arguments 126 | /// 127 | /// * `guest_mem` - The guest memory where the kernel image is loaded. 128 | /// * `kernel_offset` - 2MB-aligned base addres in guest memory at which to load the kernel. 129 | /// * `kernel_image` - Input Image format kernel image. 130 | /// * `highmem_start_address` - ignored on ARM64 and RISC-V64. 131 | /// 132 | /// # Returns 133 | /// * KernelLoaderResult 134 | fn load( 135 | guest_mem: &M, 136 | kernel_offset: Option, 137 | kernel_image: &mut F, 138 | _highmem_start_address: Option, 139 | ) -> Result 140 | where 141 | F: ReadVolatile + Read + Seek, 142 | { 143 | let kernel_size = kernel_image 144 | .seek(SeekFrom::End(0)) 145 | .map_err(|_| Error::SeekImageEnd)? as usize; 146 | 147 | #[cfg(target_arch = "aarch64")] 148 | let mut image_header: arm64_image_header = Default::default(); 149 | #[cfg(target_arch = "riscv64")] 150 | let mut image_header: riscv64_image_header = Default::default(); 151 | 152 | kernel_image.rewind().map_err(|_| Error::SeekImageHeader)?; 153 | 154 | kernel_image 155 | .read_exact(image_header.as_mut_slice()) 156 | .map_err(|_| Error::ReadImageHeader)?; 157 | 158 | #[cfg(target_arch = "aarch64")] 159 | if u32::from_le(image_header.magic) != 0x644d_5241 { 160 | return Err(Error::InvalidImageMagicNumber.into()); 161 | } 162 | #[cfg(target_arch = "riscv64")] 163 | if u32::from_le(image_header.magic2) != 0x0543_5352 { 164 | return Err(Error::InvalidImageMagicNumber.into()); 165 | } 166 | 167 | #[cfg(target_arch = "aarch64")] 168 | let text_offset = if u64::from_le(image_header.image_size) == 0 { 169 | 0x80000 170 | } else { 171 | u64::from_le(image_header.text_offset) 172 | }; 173 | #[cfg(target_arch = "riscv64")] 174 | let text_offset = u64::from_le(image_header.text_offset); 175 | 176 | // Validate that kernel_offset is 2 MB aligned, as required by the 177 | // arm64 and riscv64 boot protocol 178 | if let Some(kernel_offset) = kernel_offset { 179 | if kernel_offset.raw_value() % 0x0020_0000 != 0 { 180 | return Err(Error::InvalidBaseAddrAlignment.into()); 181 | } 182 | } 183 | 184 | let mem_offset = kernel_offset 185 | .unwrap_or(GuestAddress(0)) 186 | .checked_add(text_offset) 187 | .ok_or(Error::InvalidImage)?; 188 | 189 | let mut loader_result = KernelLoaderResult { 190 | kernel_load: mem_offset, 191 | ..Default::default() 192 | }; 193 | 194 | kernel_image.rewind().map_err(|_| Error::SeekImageHeader)?; 195 | guest_mem 196 | .read_exact_volatile_from(mem_offset, kernel_image, kernel_size) 197 | .map_err(|_| Error::ReadKernelImage)?; 198 | 199 | loader_result.kernel_end = mem_offset 200 | .raw_value() 201 | .checked_add(kernel_size as GuestUsize) 202 | .ok_or(KernelLoaderError::MemoryOverflow)?; 203 | 204 | Ok(loader_result) 205 | } 206 | } 207 | 208 | /// Writes the device tree to the given memory slice. 209 | /// 210 | /// # Arguments 211 | /// 212 | /// * `guest_mem` - A u8 slice that will be partially overwritten by the device tree blob. 213 | /// * `guest_addr` - The address in `guest_mem` at which to load the device tree blob. 214 | /// * `dtb_image` - The device tree blob. 215 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 216 | pub fn load_dtb( 217 | guest_mem: &M, 218 | guest_addr: GuestAddress, 219 | dtb_image: &mut F, 220 | ) -> Result<()> 221 | where 222 | F: ReadVolatile + Read + Seek, 223 | { 224 | let dtb_size = dtb_image 225 | .seek(SeekFrom::End(0)) 226 | .map_err(|_| Error::SeekDtbEnd)? as usize; 227 | if dtb_size > 0x200000 { 228 | return Err(Error::DtbTooBig.into()); 229 | } 230 | dtb_image.rewind().map_err(|_| Error::SeekDtbStart)?; 231 | guest_mem 232 | .read_exact_volatile_from(guest_addr, dtb_image, dtb_size) 233 | .map_err(|_| Error::ReadDtbImage.into()) 234 | } 235 | 236 | #[cfg(test)] 237 | mod tests { 238 | use super::*; 239 | use std::io::Cursor; 240 | use vm_memory::{Address, GuestAddress}; 241 | type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>; 242 | 243 | const MEM_SIZE: u64 = 0x100_0000; 244 | 245 | fn create_guest_mem() -> GuestMemoryMmap { 246 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() 247 | } 248 | 249 | fn make_image_bin() -> Vec { 250 | let mut v = Vec::new(); 251 | #[cfg(target_arch = "aarch64")] 252 | v.extend_from_slice(include_bytes!("test_arm64_image.bin")); 253 | #[cfg(target_arch = "riscv64")] 254 | v.extend_from_slice(include_bytes!("test_riscv64_image.bin")); 255 | v 256 | } 257 | 258 | #[cfg(target_arch = "aarch64")] 259 | #[test] 260 | fn load_arm64_image() { 261 | let gm = create_guest_mem(); 262 | let mut image = make_image_bin(); 263 | let kernel_addr = GuestAddress(0x200000); 264 | 265 | let loader_result = 266 | PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None).unwrap(); 267 | assert_eq!(loader_result.kernel_load.raw_value(), 0x280000); 268 | assert_eq!(loader_result.kernel_end, 0x281000); 269 | 270 | // Attempt to load the kernel at an address that is not aligned to 2MB boundary 271 | let kernel_offset = GuestAddress(0x0030_0000); 272 | let loader_result = PE::load(&gm, Some(kernel_offset), &mut Cursor::new(&image), None); 273 | assert_eq!( 274 | loader_result, 275 | Err(KernelLoaderError::Pe(Error::InvalidBaseAddrAlignment)) 276 | ); 277 | 278 | image[0x39] = 0x0; 279 | let loader_result = PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None); 280 | assert_eq!( 281 | loader_result, 282 | Err(KernelLoaderError::Pe(Error::InvalidImageMagicNumber)) 283 | ); 284 | } 285 | 286 | #[cfg(target_arch = "riscv64")] 287 | #[test] 288 | fn load_riscv64_image() { 289 | let gm = create_guest_mem(); 290 | let mut image = make_image_bin(); 291 | let kernel_addr = GuestAddress(0x400000); 292 | 293 | let loader_result = 294 | PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None).unwrap(); 295 | assert_eq!(loader_result.kernel_load.raw_value(), 0x600000); 296 | assert_eq!(loader_result.kernel_end, 0x601000); 297 | 298 | // Attempt to load the kernel at an address that is not aligned to 2MB boundary 299 | let kernel_offset = GuestAddress(0x0030_0000); 300 | let loader_result = PE::load(&gm, Some(kernel_offset), &mut Cursor::new(&image), None); 301 | assert_eq!( 302 | loader_result, 303 | Err(KernelLoaderError::Pe(Error::InvalidBaseAddrAlignment)) 304 | ); 305 | 306 | image[0x38] = 0x0; 307 | let loader_result = PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None); 308 | assert_eq!( 309 | loader_result, 310 | Err(KernelLoaderError::Pe(Error::InvalidImageMagicNumber)) 311 | ); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/loader/bzimage/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 2 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // 4 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style license that can be 6 | // found in the LICENSE-BSD-3-Clause file. 7 | // 8 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 9 | 10 | //! Traits and structs for loading bzimage kernels into guest memory. 11 | 12 | #![cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] 13 | 14 | use std::fmt; 15 | use std::io::{Seek, SeekFrom}; 16 | 17 | use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize, ReadVolatile}; 18 | 19 | use crate::loader::{ 20 | bootparam, Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result, 21 | }; 22 | 23 | #[derive(Debug, PartialEq, Eq)] 24 | /// Bzimage kernel loader errors. 25 | pub enum Error { 26 | /// Invalid bzImage binary. 27 | InvalidBzImage, 28 | /// Overflow occurred during an arithmetic operation. 29 | Overflow, 30 | /// Unable to read bzImage header. 31 | ReadBzImageHeader, 32 | /// Unable to read bzImage compressed image. 33 | ReadBzImageCompressedKernel, 34 | /// Unable to seek to bzImage end. 35 | SeekBzImageEnd, 36 | /// Unable to seek to bzImage header. 37 | SeekBzImageHeader, 38 | /// Unable to seek to bzImage compressed kernel. 39 | SeekBzImageCompressedKernel, 40 | /// Underflow occurred during an arithmetic operation. 41 | Underflow, 42 | } 43 | 44 | impl fmt::Display for Error { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | let desc = match self { 47 | Error::InvalidBzImage => "Invalid bzImage", 48 | Error::Overflow => "Overflow occurred during an arithmetic operation", 49 | Error::ReadBzImageHeader => "Unable to read bzImage header", 50 | Error::ReadBzImageCompressedKernel => "Unable to read bzImage compressed kernel", 51 | Error::SeekBzImageEnd => "Unable to seek bzImage end", 52 | Error::SeekBzImageHeader => "Unable to seek bzImage header", 53 | Error::SeekBzImageCompressedKernel => "Unable to seek bzImage compressed kernel", 54 | Error::Underflow => "Underflow occurred during an arithmetic operation", 55 | }; 56 | 57 | write!(f, "Kernel Loader: {}", desc) 58 | } 59 | } 60 | 61 | impl std::error::Error for Error {} 62 | 63 | /// Big zImage (bzImage) kernel image support. 64 | pub struct BzImage; 65 | 66 | impl KernelLoader for BzImage { 67 | /// Loads a kernel from a bzImage to guest memory. 68 | /// 69 | /// The kernel is loaded at `code32_start`, the default load address stored in the bzImage 70 | /// setup header. 71 | /// 72 | /// # Arguments 73 | /// 74 | /// * `guest_mem`: [`GuestMemory`] to load the kernel in. 75 | /// * `kernel_offset`: Address in guest memory where the kernel is loaded. 76 | /// * `kernel_image` - Input bzImage image. 77 | /// * `highmem_start_address`: Address where high memory starts. 78 | /// 79 | /// # Examples 80 | /// 81 | /// ```rust 82 | /// # extern crate vm_memory; 83 | /// # use std::io::Cursor; 84 | /// # use linux_loader::loader::*; 85 | /// # use vm_memory::{Address, GuestAddress}; 86 | /// # type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>; 87 | /// let mem_size: usize = 0x1000000; 88 | /// let himem_start = GuestAddress(0x0); 89 | /// let kernel_addr = GuestAddress(0x200000); 90 | /// let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), mem_size)]).unwrap(); 91 | /// let mut kernel_image = vec![]; 92 | /// kernel_image.extend_from_slice(include_bytes!("bzimage")); 93 | /// bzimage::BzImage::load( 94 | /// &gm, 95 | /// Some(kernel_addr), 96 | /// &mut Cursor::new(&kernel_image), 97 | /// Some(himem_start), 98 | /// ) 99 | /// .unwrap(); 100 | /// ``` 101 | /// 102 | /// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html 103 | fn load( 104 | guest_mem: &M, 105 | kernel_offset: Option, 106 | kernel_image: &mut F, 107 | highmem_start_address: Option, 108 | ) -> Result 109 | where 110 | F: ReadVolatile + Seek, 111 | { 112 | let mut kernel_size = kernel_image 113 | .seek(SeekFrom::End(0)) 114 | .map_err(|_| Error::SeekBzImageEnd)? as usize; 115 | kernel_image 116 | .seek(SeekFrom::Start(0x1F1)) 117 | .map_err(|_| Error::SeekBzImageHeader)?; 118 | 119 | let mut boot_header = bootparam::setup_header::default(); 120 | kernel_image 121 | .read_volatile(&mut boot_header.as_bytes()) 122 | .map_err(|_| Error::ReadBzImageHeader)?; 123 | 124 | // If the `HdrS` magic number is not found at offset 0x202, the boot protocol version is 125 | // "old", the image type is assumed as zImage, not bzImage. 126 | if boot_header.header != 0x5372_6448 { 127 | return Err(Error::InvalidBzImage.into()); 128 | } 129 | 130 | // Follow the section related to loading the rest of the kernel in the linux boot protocol. 131 | if (boot_header.version < 0x0200) || ((boot_header.loadflags & 0x1) == 0x0) { 132 | return Err(Error::InvalidBzImage.into()); 133 | } 134 | 135 | let mut setup_size = boot_header.setup_sects as usize; 136 | if setup_size == 0 { 137 | setup_size = 4; 138 | } 139 | setup_size = setup_size 140 | .checked_add(1) 141 | .and_then(|setup_size| setup_size.checked_mul(512)) 142 | .ok_or(Error::Overflow)?; 143 | kernel_size = kernel_size 144 | .checked_sub(setup_size) 145 | .ok_or(Error::Underflow)?; 146 | 147 | // Check that `code32_start`, the default address of the kernel, is not lower than high 148 | // memory. 149 | if (highmem_start_address.is_some()) 150 | && (u64::from(boot_header.code32_start) < highmem_start_address.unwrap().raw_value()) 151 | { 152 | return Err(KernelLoaderError::InvalidKernelStartAddress); 153 | } 154 | 155 | let mem_offset = match kernel_offset { 156 | Some(start) => start, 157 | None => GuestAddress(u64::from(boot_header.code32_start)), 158 | }; 159 | 160 | boot_header.code32_start = mem_offset.raw_value() as u32; 161 | 162 | let mut loader_result = KernelLoaderResult { 163 | setup_header: Some(boot_header), 164 | kernel_load: mem_offset, 165 | ..Default::default() 166 | }; 167 | 168 | // Seek the compressed `vmlinux.bin` and read it to memory. 169 | kernel_image 170 | .seek(SeekFrom::Start(setup_size as u64)) 171 | .map_err(|_| Error::SeekBzImageCompressedKernel)?; 172 | guest_mem 173 | .read_exact_volatile_from(mem_offset, kernel_image, kernel_size) 174 | .map_err(|_| Error::ReadBzImageCompressedKernel)?; 175 | 176 | loader_result.kernel_end = mem_offset 177 | .raw_value() 178 | .checked_add(kernel_size as GuestUsize) 179 | .ok_or(KernelLoaderError::MemoryOverflow)?; 180 | 181 | Ok(loader_result) 182 | } 183 | } 184 | 185 | #[cfg(test)] 186 | mod tests { 187 | use super::*; 188 | 189 | use std::fs::File; 190 | use std::io::{Cursor, Read}; 191 | use std::process::Command; 192 | use vm_memory::{Address, GuestAddress}; 193 | type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>; 194 | 195 | const MEM_SIZE: u64 = 0x100_0000; 196 | 197 | fn create_guest_mem() -> GuestMemoryMmap { 198 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() 199 | } 200 | 201 | fn download_resources() { 202 | let command = "./.buildkite/download_resources.sh"; 203 | let status = Command::new(command).status().unwrap(); 204 | if !status.success() { 205 | panic!("Cannot run build script"); 206 | } 207 | } 208 | 209 | fn make_bzimage() -> Vec { 210 | download_resources(); 211 | let mut v = Vec::new(); 212 | let path = concat!(env!("CARGO_MANIFEST_DIR"), "/src/loader/bzimage/bzimage"); 213 | let mut f = File::open(path).unwrap(); 214 | f.read_to_end(&mut v).unwrap(); 215 | 216 | v 217 | } 218 | 219 | #[allow(non_snake_case)] 220 | #[test] 221 | fn test_load_bzImage() { 222 | let gm = create_guest_mem(); 223 | let image = make_bzimage(); 224 | let mut kernel_offset = GuestAddress(0x200000); 225 | let mut highmem_start_address = GuestAddress(0x0); 226 | 227 | // load bzImage with good kernel_offset and himem_start setting 228 | let mut loader_result = BzImage::load( 229 | &gm, 230 | Some(kernel_offset), 231 | &mut Cursor::new(&image), 232 | Some(highmem_start_address), 233 | ) 234 | .unwrap(); 235 | let setup_header = loader_result.setup_header.unwrap(); 236 | 237 | assert_eq!(loader_result.kernel_load.raw_value(), 0x200000); 238 | assert_eq!( 239 | // SAFETY: 240 | // Reading the value from an unaligned address is not considered safe. 241 | // but this is not an issue since this is a test. 242 | unsafe { std::ptr::addr_of!(setup_header.header).read_unaligned() }, 243 | 0x53726448 244 | ); 245 | assert_eq!( 246 | // SAFETY: 247 | // Reading the value from an unaligned address is not considered safe. 248 | // but this is not an issue since this is a test. 249 | unsafe { std::ptr::addr_of!(setup_header.version).read_unaligned() }, 250 | 0x20f 251 | ); 252 | assert_eq!(loader_result.setup_header.unwrap().loadflags, 1); 253 | assert_eq!(loader_result.kernel_end, 0x8b5e40); 254 | 255 | // load bzImage without kernel_offset 256 | loader_result = BzImage::load( 257 | &gm, 258 | None, 259 | &mut Cursor::new(&image), 260 | Some(highmem_start_address), 261 | ) 262 | .unwrap(); 263 | let setup_header = loader_result.setup_header.unwrap(); 264 | 265 | assert_eq!(loader_result.kernel_load.raw_value(), 0x100000); 266 | 267 | // load bzImage without himem_start 268 | loader_result = BzImage::load(&gm, None, &mut Cursor::new(&image), None).unwrap(); 269 | // Reading the value from an unaligned address is not considered safe. 270 | assert_eq!( 271 | 0x53726448, 272 | // SAFETY: 273 | // Reading the value from an unaligned address is not considered safe. 274 | // but this is not an issue since this is a test. 275 | unsafe { std::ptr::addr_of!(setup_header.header).read_unaligned() } 276 | ); 277 | assert_eq!(loader_result.kernel_load.raw_value(), 0x100000); 278 | 279 | // load bzImage with a bad himem setting 280 | kernel_offset = GuestAddress(0x1000); 281 | highmem_start_address = GuestAddress(0x200000); 282 | 283 | assert_eq!( 284 | Some(KernelLoaderError::InvalidKernelStartAddress), 285 | BzImage::load( 286 | &gm, 287 | Some(kernel_offset), 288 | &mut Cursor::new(&image), 289 | Some(highmem_start_address), 290 | ) 291 | .err() 292 | ); 293 | } 294 | 295 | #[test] 296 | fn test_invalid_bzimage_underflow() { 297 | use crate::loader::Error as LoaderError; 298 | 299 | let path = concat!( 300 | env!("CARGO_MANIFEST_DIR"), 301 | "/src/loader/bzimage/fuzz_invalid_bzimage.bin" 302 | ); 303 | 304 | let gm = create_guest_mem(); 305 | let mut image = File::open(path).unwrap(); 306 | let kernel_offset = GuestAddress(0x200000); 307 | let highmem_start_address = GuestAddress(0x0); 308 | 309 | // load bzImage with good kernel_offset and himem_start setting 310 | let loader_result = BzImage::load( 311 | &gm, 312 | Some(kernel_offset), 313 | &mut image, 314 | Some(highmem_start_address), 315 | ); 316 | 317 | assert_eq!( 318 | loader_result.unwrap_err(), 319 | LoaderError::Bzimage(Error::Underflow) 320 | ); 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /src/configurator/x86_64/pvh.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2020, Oracle and/or its affiliates. 2 | // 3 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 4 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // 6 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 7 | // Use of this source code is governed by a BSD-style license that can be 8 | // found in the LICENSE-BSD-3-Clause file. 9 | // 10 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 11 | 12 | //! Traits and structs for configuring and loading boot parameters on `x86_64` using the PVH boot 13 | //! protocol. 14 | 15 | #![cfg(any(feature = "elf", feature = "bzimage"))] 16 | 17 | use vm_memory::{ByteValued, Bytes, GuestMemory}; 18 | 19 | use crate::configurator::{BootConfigurator, BootParams, Error as BootConfiguratorError, Result}; 20 | use crate::loader_gen::start_info::{hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info}; 21 | 22 | use std::fmt; 23 | 24 | /// Boot configurator for the PVH boot protocol. 25 | pub struct PvhBootConfigurator {} 26 | 27 | /// Errors specific to the PVH boot protocol configuration. 28 | #[derive(Debug, PartialEq, Eq)] 29 | pub enum Error { 30 | /// The starting address for the memory map wasn't passed to the boot configurator. 31 | MemmapTableAddressMissing, 32 | /// No memory map wasn't passed to the boot configurator. 33 | MemmapTableMissing, 34 | /// The memory map table extends past the end of guest memory. 35 | MemmapTablePastRamEnd, 36 | /// Error writing memory map table to guest memory. 37 | MemmapTableSetup, 38 | /// The hvm_start_info structure extends past the end of guest memory. 39 | StartInfoPastRamEnd, 40 | /// Error writing hvm_start_info to guest memory. 41 | StartInfoSetup, 42 | /// The starting address for the modules descriptions wasn't passed to the boot configurator. 43 | ModulesAddressMissing, 44 | /// Error writing module descriptions to guest memory. 45 | ModulesSetup, 46 | } 47 | 48 | impl fmt::Display for Error { 49 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 50 | use Error::*; 51 | let desc = match self { 52 | MemmapTableAddressMissing => { 53 | "the starting address for the memory map wasn't passed to the boot configurator." 54 | } 55 | MemmapTableMissing => "no memory map was passed to the boot configurator.", 56 | MemmapTablePastRamEnd => "the memory map table extends past the end of guest memory.", 57 | MemmapTableSetup => "error writing memory map table to guest memory.", 58 | StartInfoPastRamEnd => { 59 | "the hvm_start_info structure extends past the end of guest memory." 60 | } 61 | StartInfoSetup => "error writing hvm_start_info to guest memory.", 62 | ModulesAddressMissing => "the starting address for the modules descriptions wasn't passed to the boot configurator.", 63 | ModulesSetup => "error writing module descriptions to guest memory.", 64 | }; 65 | 66 | write!(f, "PVH Boot Configurator: {}", desc) 67 | } 68 | } 69 | 70 | impl std::error::Error for Error {} 71 | 72 | impl From for BootConfiguratorError { 73 | fn from(err: Error) -> Self { 74 | BootConfiguratorError::Pvh(err) 75 | } 76 | } 77 | 78 | // SAFETY: The layout of the structure is fixed and can be initialized by 79 | // reading its content from byte array. 80 | unsafe impl ByteValued for hvm_start_info {} 81 | 82 | // SAFETY: The layout of the structure is fixed and can be initialized by 83 | // reading its content from byte array. 84 | unsafe impl ByteValued for hvm_memmap_table_entry {} 85 | 86 | // SAFETY: The layout of the structure is fixed and can be initialized by 87 | // reading its content from byte array. 88 | unsafe impl ByteValued for hvm_modlist_entry {} 89 | 90 | impl BootConfigurator for PvhBootConfigurator { 91 | /// Writes the boot parameters (configured elsewhere) into guest memory. 92 | /// 93 | /// # Arguments 94 | /// 95 | /// * `params` - boot parameters. The header contains a [`hvm_start_info`] struct. The 96 | /// sections contain the memory map in a vector of [`hvm_memmap_table_entry`] 97 | /// structs. The modules, if specified, contain [`hvm_modlist_entry`] structs. 98 | /// * `guest_memory` - guest's physical memory. 99 | /// 100 | /// [`hvm_start_info`]: ../loader/elf/start_info/struct.hvm_start_info.html 101 | /// [`hvm_memmap_table_entry`]: ../loader/elf/start_info/struct.hvm_memmap_table_entry.html 102 | /// [`hvm_modlist_entry`]: ../loader/elf/start_info/struct.hvm_modlist_entry.html 103 | /// 104 | /// # Examples 105 | /// 106 | /// ```rust 107 | /// # extern crate vm_memory; 108 | /// # use linux_loader::configurator::{BootConfigurator, BootParams}; 109 | /// # use linux_loader::configurator::pvh::PvhBootConfigurator; 110 | /// # use linux_loader::loader::elf::start_info::{hvm_start_info, hvm_memmap_table_entry}; 111 | /// # use vm_memory::{Address, ByteValued, GuestMemory, GuestMemoryMmap, GuestAddress}; 112 | /// # const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578; 113 | /// # const MEM_SIZE: u64 = 0x100_0000; 114 | /// # const E820_RAM: u32 = 1; 115 | /// fn create_guest_memory() -> GuestMemoryMmap { 116 | /// GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() 117 | /// } 118 | /// 119 | /// fn build_boot_params() -> (hvm_start_info, Vec) { 120 | /// let mut start_info = hvm_start_info::default(); 121 | /// let memmap_entry = hvm_memmap_table_entry { 122 | /// addr: 0x7000, 123 | /// size: 0, 124 | /// type_: E820_RAM, 125 | /// reserved: 0, 126 | /// }; 127 | /// start_info.magic = XEN_HVM_START_MAGIC_VALUE; 128 | /// start_info.version = 1; 129 | /// start_info.nr_modules = 0; 130 | /// start_info.memmap_entries = 0; 131 | /// (start_info, vec![memmap_entry]) 132 | /// } 133 | /// 134 | /// fn main() { 135 | /// let guest_mem = create_guest_memory(); 136 | /// let (mut start_info, memmap_entries) = build_boot_params(); 137 | /// let start_info_addr = GuestAddress(0x6000); 138 | /// let memmap_addr = GuestAddress(0x7000); 139 | /// start_info.memmap_paddr = memmap_addr.raw_value(); 140 | /// 141 | /// let mut boot_params = BootParams::new::(&start_info, start_info_addr); 142 | /// boot_params.set_sections::(&memmap_entries, memmap_addr); 143 | /// PvhBootConfigurator::write_bootparams::(&boot_params, &guest_mem).unwrap(); 144 | /// } 145 | /// ``` 146 | fn write_bootparams(params: &BootParams, guest_memory: &M) -> Result<()> 147 | where 148 | M: GuestMemory, 149 | { 150 | // The VMM has filled an `hvm_start_info` struct and a `Vec` 151 | // and has passed them on to this function. 152 | // The `hvm_start_info` will be written at `addr` and the memmap entries at 153 | // `start_info.0.memmap_paddr`. 154 | let memmap = params.sections.as_ref().ok_or(Error::MemmapTableMissing)?; 155 | let memmap_addr = params 156 | .sections_start 157 | .ok_or(Error::MemmapTableAddressMissing)?; 158 | 159 | guest_memory 160 | .checked_offset(memmap_addr, memmap.len()) 161 | .ok_or(Error::MemmapTablePastRamEnd)?; 162 | guest_memory 163 | .write_slice(memmap.as_slice(), memmap_addr) 164 | .map_err(|_| Error::MemmapTableSetup)?; 165 | 166 | guest_memory 167 | .checked_offset(params.header_start, params.header.len()) 168 | .ok_or(Error::StartInfoPastRamEnd)?; 169 | guest_memory 170 | .write_slice(params.header.as_slice(), params.header_start) 171 | .map_err(|_| Error::StartInfoSetup)?; 172 | 173 | if let Some(modules) = params.modules.as_ref() { 174 | let modules_addr = params.modules_start.ok_or(Error::ModulesAddressMissing)?; 175 | guest_memory 176 | .write_slice(modules.as_slice(), modules_addr) 177 | .map_err(|_| Error::ModulesSetup)?; 178 | } 179 | 180 | Ok(()) 181 | } 182 | } 183 | 184 | #[cfg(test)] 185 | mod tests { 186 | use super::*; 187 | use std::mem; 188 | use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; 189 | 190 | const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578; 191 | const MEM_SIZE: u64 = 0x100_0000; 192 | const E820_RAM: u32 = 1; 193 | 194 | fn create_guest_mem() -> GuestMemoryMmap { 195 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() 196 | } 197 | 198 | fn build_bootparams_common() -> ( 199 | hvm_start_info, 200 | Vec, 201 | Vec, 202 | ) { 203 | let mut start_info = hvm_start_info::default(); 204 | let memmap_entry = hvm_memmap_table_entry { 205 | addr: 0x7000, 206 | size: 0, 207 | type_: E820_RAM, 208 | reserved: 0, 209 | }; 210 | 211 | let modlist_entry = hvm_modlist_entry { 212 | paddr: 0x10000, 213 | size: 0x100, 214 | cmdline_paddr: 0, 215 | reserved: 0, 216 | }; 217 | 218 | start_info.magic = XEN_HVM_START_MAGIC_VALUE; 219 | start_info.version = 1; 220 | start_info.nr_modules = 0; 221 | start_info.memmap_entries = 0; 222 | 223 | (start_info, vec![memmap_entry], vec![modlist_entry]) 224 | } 225 | 226 | #[test] 227 | fn test_configure_pvh_boot() { 228 | let (mut start_info, memmap_entries, modlist_entries) = build_bootparams_common(); 229 | let guest_memory = create_guest_mem(); 230 | 231 | let start_info_addr = GuestAddress(0x6000); 232 | let memmap_addr = GuestAddress(0x7000); 233 | let modlist_addr = GuestAddress(0x6040); 234 | start_info.memmap_paddr = memmap_addr.raw_value(); 235 | 236 | let mut boot_params = BootParams::new::(&start_info, start_info_addr); 237 | 238 | // Error case: configure without memory map. 239 | assert_eq!( 240 | PvhBootConfigurator::write_bootparams::(&boot_params, &guest_memory,) 241 | .err(), 242 | Some(Error::MemmapTableMissing.into()) 243 | ); 244 | 245 | // Error case: start_info doesn't fit in guest memory. 246 | let bad_start_info_addr = GuestAddress( 247 | guest_memory.last_addr().raw_value() - mem::size_of::() as u64 + 1, 248 | ); 249 | boot_params.set_sections::(&memmap_entries, memmap_addr); 250 | boot_params.header_start = bad_start_info_addr; 251 | assert_eq!( 252 | PvhBootConfigurator::write_bootparams::(&boot_params, &guest_memory,) 253 | .err(), 254 | Some(Error::StartInfoPastRamEnd.into()) 255 | ); 256 | 257 | // Error case: memory map doesn't fit in guest memory. 258 | let himem_start = GuestAddress(0x100000); 259 | boot_params.header_start = himem_start; 260 | let bad_memmap_addr = GuestAddress( 261 | guest_memory.last_addr().raw_value() - mem::size_of::() as u64 262 | + 1, 263 | ); 264 | boot_params.set_sections::(&memmap_entries, bad_memmap_addr); 265 | 266 | assert_eq!( 267 | PvhBootConfigurator::write_bootparams::(&boot_params, &guest_memory,) 268 | .err(), 269 | Some(Error::MemmapTablePastRamEnd.into()) 270 | ); 271 | 272 | boot_params.set_sections::(&memmap_entries, memmap_addr); 273 | boot_params.set_modules::(&modlist_entries, modlist_addr); 274 | assert!(PvhBootConfigurator::write_bootparams::( 275 | &boot_params, 276 | &guest_memory, 277 | ) 278 | .is_ok()); 279 | } 280 | 281 | #[test] 282 | fn test_error_messages() { 283 | assert_eq!( 284 | format!("{}", Error::MemmapTableMissing), 285 | "PVH Boot Configurator: no memory map was passed to the boot configurator." 286 | ); 287 | assert_eq!( 288 | format!("{}", Error::MemmapTablePastRamEnd), 289 | "PVH Boot Configurator: the memory map table extends past the end of guest memory." 290 | ); 291 | assert_eq!( 292 | format!("{}", Error::MemmapTableSetup), 293 | "PVH Boot Configurator: error writing memory map table to guest memory." 294 | ); 295 | assert_eq!(format!("{}", Error::StartInfoPastRamEnd), "PVH Boot Configurator: the hvm_start_info structure extends past the end of guest memory."); 296 | assert_eq!( 297 | format!("{}", Error::StartInfoSetup), 298 | "PVH Boot Configurator: error writing hvm_start_info to guest memory." 299 | ); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/loader/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2020, Oracle and/or its affiliates. 2 | // 3 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 4 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // 6 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 7 | // Use of this source code is governed by a BSD-style license that can be 8 | // found in the LICENSE-BSD-3-Clause file. 9 | // 10 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 11 | 12 | //! Traits and structs for loading kernels into guest memory. 13 | //! - [KernelLoader](trait.KernelLoader.html): load kernel image into guest memory. 14 | //! - [KernelLoaderResult](struct.KernelLoaderResult.html): structure passed to the VMM to assist 15 | //! zero page construction and boot environment setup. 16 | //! - [Elf](elf/struct.Elf.html): elf image loader. 17 | //! - [BzImage](bzimage/struct.BzImage.html): bzImage loader. 18 | //! - [PE](pe/struct.PE.html): PE image loader. 19 | 20 | extern crate vm_memory; 21 | 22 | use std::fmt; 23 | use std::io::{Read, Seek}; 24 | 25 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 26 | use vm_memory::ByteValued; 27 | use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestUsize, ReadVolatile}; 28 | 29 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 30 | pub use crate::loader_gen::bootparam; 31 | 32 | pub use crate::cmdline::Cmdline; 33 | 34 | #[cfg(all(any(target_arch = "aarch64", target_arch = "riscv64"), feature = "pe"))] 35 | pub mod pe; 36 | #[cfg(all(any(target_arch = "aarch64", target_arch = "riscv64"), feature = "pe"))] 37 | pub use pe::*; 38 | 39 | #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))] 40 | pub mod elf; 41 | #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))] 42 | pub use elf::*; 43 | 44 | #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "bzimage"))] 45 | pub mod bzimage; 46 | #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "bzimage"))] 47 | pub use bzimage::*; 48 | 49 | #[derive(Debug, PartialEq, Eq)] 50 | /// Kernel loader errors. 51 | pub enum Error { 52 | /// Failed to load bzimage. 53 | #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] 54 | Bzimage(bzimage::Error), 55 | 56 | /// Failed to load elf image. 57 | #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] 58 | Elf(elf::Error), 59 | 60 | /// Failed to load PE image. 61 | #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] 62 | Pe(pe::Error), 63 | 64 | /// Invalid command line. 65 | InvalidCommandLine, 66 | /// Failed writing command line to guest memory. 67 | CommandLineCopy, 68 | /// Command line overflowed guest memory. 69 | CommandLineOverflow, 70 | /// Invalid kernel start address. 71 | InvalidKernelStartAddress, 72 | /// Memory to load kernel image is too small. 73 | MemoryOverflow, 74 | } 75 | 76 | /// A specialized [`Result`] type for the kernel loader. 77 | /// 78 | /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html 79 | pub type Result = std::result::Result; 80 | 81 | impl fmt::Display for Error { 82 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 83 | write!(f, "Kernel Loader: ")?; 84 | match self { 85 | #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] 86 | Error::Bzimage(ref e) => write!(f, "failed to load bzImage kernel image: {e}"), 87 | #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] 88 | Error::Elf(ref e) => write!(f, "failed to load ELF kernel image: {e}"), 89 | #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] 90 | Error::Pe(ref e) => write!(f, "failed to load PE kernel image: {e}"), 91 | 92 | Error::InvalidCommandLine => write!(f, "invalid command line provided"), 93 | Error::CommandLineCopy => write!(f, "failed writing command line to guest memory"), 94 | Error::CommandLineOverflow => write!(f, "command line overflowed guest memory"), 95 | Error::InvalidKernelStartAddress => write!(f, "invalid kernel start address"), 96 | Error::MemoryOverflow => write!(f, "memory to load kernel image is not enough"), 97 | } 98 | } 99 | } 100 | 101 | impl std::error::Error for Error { 102 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 103 | match self { 104 | #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] 105 | Error::Bzimage(ref e) => Some(e), 106 | #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] 107 | Error::Elf(ref e) => Some(e), 108 | #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] 109 | Error::Pe(ref e) => Some(e), 110 | 111 | Error::InvalidCommandLine => None, 112 | Error::CommandLineCopy => None, 113 | Error::CommandLineOverflow => None, 114 | Error::InvalidKernelStartAddress => None, 115 | Error::MemoryOverflow => None, 116 | } 117 | } 118 | } 119 | 120 | #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] 121 | impl From for Error { 122 | fn from(err: elf::Error) -> Self { 123 | Error::Elf(err) 124 | } 125 | } 126 | 127 | #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] 128 | impl From for Error { 129 | fn from(err: bzimage::Error) -> Self { 130 | Error::Bzimage(err) 131 | } 132 | } 133 | 134 | #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] 135 | impl From for Error { 136 | fn from(err: pe::Error) -> Self { 137 | Error::Pe(err) 138 | } 139 | } 140 | 141 | /// Result of [`KernelLoader.load()`](trait.KernelLoader.html#tymethod.load). 142 | /// 143 | /// This specifies where the kernel is loading and passes additional 144 | /// information for the rest of the boot process to be completed by 145 | /// the VMM. 146 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 147 | pub struct KernelLoaderResult { 148 | /// Address in the guest memory where the kernel image starts to be loaded. 149 | pub kernel_load: GuestAddress, 150 | /// Offset in guest memory corresponding to the end of kernel image, in case the device tree 151 | /// blob and initrd will be loaded adjacent to kernel image. 152 | pub kernel_end: GuestUsize, 153 | /// Configuration for the VMM to use to fill zero page for bzImage direct boot. 154 | /// See . 155 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 156 | pub setup_header: Option, 157 | /// Availability of a PVH entry point. Only used for ELF boot, indicates whether the kernel 158 | /// supports the PVH boot protocol as described in: 159 | /// 160 | #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] 161 | pub pvh_boot_cap: elf::PvhBootCapability, 162 | } 163 | 164 | /// Trait that specifies kernel image loading support. 165 | pub trait KernelLoader { 166 | /// How to load a specific kernel image format into the guest memory. 167 | /// 168 | /// # Arguments 169 | /// 170 | /// * `guest_mem`: [`GuestMemory`] to load the kernel in. 171 | /// * `kernel_offset`: Usage varies between implementations. 172 | /// * `kernel_image`: Kernel image to be loaded. 173 | /// * `highmem_start_address`: Address where high memory starts. 174 | /// 175 | /// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html 176 | fn load( 177 | guest_mem: &M, 178 | kernel_offset: Option, 179 | kernel_image: &mut F, 180 | highmem_start_address: Option, 181 | ) -> Result 182 | where 183 | F: Read + ReadVolatile + Seek; 184 | } 185 | 186 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 187 | // SAFETY: The layout of the structure is fixed and can be initialized by 188 | // reading its content from byte array. 189 | unsafe impl ByteValued for bootparam::setup_header {} 190 | 191 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 192 | // SAFETY: The layout of the structure is fixed and can be initialized by 193 | // reading its content from byte array. 194 | unsafe impl ByteValued for bootparam::boot_params {} 195 | 196 | /// Writes the command line string to the given guest memory slice. 197 | /// 198 | /// # Arguments 199 | /// 200 | /// * `guest_mem` - [`GuestMemory`] that will be partially overwritten by the command line. 201 | /// * `guest_addr` - The address in `guest_mem` at which to load the command line. 202 | /// * `cmdline` - The kernel command line. 203 | /// 204 | /// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html 205 | /// 206 | /// # Examples 207 | /// 208 | /// ```rust 209 | /// # use std::ffi::CStr; 210 | /// # extern crate vm_memory; 211 | /// # use linux_loader::loader::*; 212 | /// # use vm_memory::{Bytes, GuestAddress}; 213 | /// # type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>; 214 | /// let mem_size: usize = 0x1000000; 215 | /// let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), mem_size)]).unwrap(); 216 | /// let mut cl = Cmdline::new(10).unwrap(); 217 | /// cl.insert("foo", "bar"); 218 | /// let mut buf = vec![0u8;8]; 219 | /// let result = load_cmdline(&gm, GuestAddress(0x1000), &cl).unwrap(); 220 | /// gm.read_slice(buf.as_mut_slice(), GuestAddress(0x1000)).unwrap(); 221 | /// assert_eq!(buf.as_slice(), "foo=bar\0".as_bytes()); 222 | pub fn load_cmdline( 223 | guest_mem: &M, 224 | guest_addr: GuestAddress, 225 | cmdline: &Cmdline, 226 | ) -> Result<()> { 227 | // We need a null terminated string because that's what the Linux 228 | // kernel expects when parsing the command line: 229 | // https://elixir.bootlin.com/linux/v5.10.139/source/kernel/params.c#L179 230 | let cmdline_string = cmdline 231 | .as_cstring() 232 | .map_err(|_| Error::InvalidCommandLine)?; 233 | 234 | let cmdline_bytes = cmdline_string.as_bytes_with_nul(); 235 | 236 | let end = guest_addr 237 | // Underflow not possible because the cmdline contains at least 238 | // a byte (null terminator) 239 | .checked_add((cmdline_bytes.len() - 1) as u64) 240 | .ok_or(Error::CommandLineOverflow)?; 241 | if end > guest_mem.last_addr() { 242 | return Err(Error::CommandLineOverflow); 243 | } 244 | 245 | guest_mem 246 | .write_slice(cmdline_bytes, guest_addr) 247 | .map_err(|_| Error::CommandLineCopy)?; 248 | 249 | Ok(()) 250 | } 251 | 252 | #[cfg(test)] 253 | mod tests { 254 | use super::*; 255 | use vm_memory::{Address, GuestAddress}; 256 | type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>; 257 | 258 | const MEM_SIZE: u64 = 0x100_0000; 259 | 260 | fn create_guest_mem() -> GuestMemoryMmap { 261 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() 262 | } 263 | 264 | #[test] 265 | fn test_cmdline_overflow() { 266 | let gm = create_guest_mem(); 267 | let mut cl = Cmdline::new(10).unwrap(); 268 | cl.insert_str("12345").unwrap(); 269 | 270 | let cmdline_address = GuestAddress(u64::MAX - 5); 271 | assert_eq!( 272 | Err(Error::CommandLineOverflow), 273 | load_cmdline(&gm, cmdline_address, &cl) 274 | ); 275 | 276 | let cmdline_address = GuestAddress(MEM_SIZE - 5); 277 | assert_eq!( 278 | Err(Error::CommandLineOverflow), 279 | load_cmdline(&gm, cmdline_address, &cl) 280 | ); 281 | let cmdline_address = GuestAddress(MEM_SIZE - 6); 282 | assert!(load_cmdline(&gm, cmdline_address, &cl).is_ok()); 283 | } 284 | 285 | #[test] 286 | fn test_cmdline_write_end_regresion() { 287 | let gm = create_guest_mem(); 288 | let mut cmdline_address = GuestAddress(45); 289 | let sample_buf = &[1; 100]; 290 | 291 | // Fill in guest memory with non zero bytes 292 | gm.write(sample_buf, cmdline_address).unwrap(); 293 | 294 | let mut cl = Cmdline::new(10).unwrap(); 295 | 296 | // Test loading an empty cmdline 297 | load_cmdline(&gm, cmdline_address, &cl).unwrap(); 298 | let val: u8 = gm.read_obj(cmdline_address).unwrap(); 299 | assert_eq!(val, b'\0'); 300 | 301 | // Test loading an non-empty cmdline 302 | cl.insert_str("123").unwrap(); 303 | load_cmdline(&gm, cmdline_address, &cl).unwrap(); 304 | 305 | let val: u8 = gm.read_obj(cmdline_address).unwrap(); 306 | assert_eq!(val, b'1'); 307 | cmdline_address = cmdline_address.unchecked_add(1); 308 | let val: u8 = gm.read_obj(cmdline_address).unwrap(); 309 | assert_eq!(val, b'2'); 310 | cmdline_address = cmdline_address.unchecked_add(1); 311 | let val: u8 = gm.read_obj(cmdline_address).unwrap(); 312 | assert_eq!(val, b'3'); 313 | cmdline_address = cmdline_address.unchecked_add(1); 314 | let val: u8 = gm.read_obj(cmdline_address).unwrap(); 315 | assert_eq!(val, b'\0'); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/loader_gen/x86_64/elf.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2020, Oracle and/or its affiliates. 2 | // 3 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | // 5 | // Portions Copyright 2017 The Chromium OS Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style license that can be 7 | // found in the LICENSE-BSD-3-Clause file. 8 | // 9 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 10 | 11 | /* 12 | * automatically generated by rust-bindgen using: 13 | * 14 | * # bindgen --with-derive-default elf.h > elf.rs 15 | * 16 | * From upstream linux include/uapi/linux/elf.h at commit: 17 | * 48b1320a674e1ff5de2fad8606bee38f724594dc 18 | * and then edited to eliminate unnecessary definitions, add comments, 19 | * and relocate definitions and tests for clarity. 20 | */ 21 | 22 | pub const PT_LOAD: u32 = 1; 23 | pub const PT_NOTE: u32 = 4; 24 | 25 | pub const EI_MAG0: u32 = 0; 26 | pub const EI_MAG1: u32 = 1; 27 | pub const EI_MAG2: u32 = 2; 28 | pub const EI_MAG3: u32 = 3; 29 | pub const EI_DATA: u32 = 5; 30 | 31 | pub const ELFMAG0: u32 = 127; 32 | 33 | // The values for the following definitions have been edited 34 | // to use their equivalent byte literal representations. 35 | pub const ELFMAG1: u8 = b'E'; 36 | pub const ELFMAG2: u8 = b'L'; 37 | pub const ELFMAG3: u8 = b'F'; 38 | 39 | pub const ELFDATA2LSB: u32 = 1; 40 | 41 | pub type __s8 = ::std::os::raw::c_schar; 42 | pub type __u8 = ::std::os::raw::c_uchar; 43 | pub type __s16 = ::std::os::raw::c_short; 44 | pub type __u16 = ::std::os::raw::c_ushort; 45 | pub type __s32 = ::std::os::raw::c_int; 46 | pub type __u32 = ::std::os::raw::c_uint; 47 | pub type __s64 = ::std::os::raw::c_longlong; 48 | pub type __u64 = ::std::os::raw::c_ulonglong; 49 | 50 | pub type Elf64_Addr = __u64; 51 | pub type Elf64_Half = __u16; 52 | pub type Elf64_Off = __u64; 53 | pub type Elf64_Sword = __s32; 54 | pub type Elf64_Word = __u32; 55 | pub type Elf64_Xword = __u64; 56 | 57 | #[repr(C)] 58 | #[derive(Debug, Default, Copy, Clone)] 59 | pub struct elf64_hdr { 60 | pub e_ident: [::std::os::raw::c_uchar; 16usize], 61 | pub e_type: Elf64_Half, 62 | pub e_machine: Elf64_Half, 63 | pub e_version: Elf64_Word, 64 | pub e_entry: Elf64_Addr, 65 | pub e_phoff: Elf64_Off, 66 | pub e_shoff: Elf64_Off, 67 | pub e_flags: Elf64_Word, 68 | pub e_ehsize: Elf64_Half, 69 | pub e_phentsize: Elf64_Half, 70 | pub e_phnum: Elf64_Half, 71 | pub e_shentsize: Elf64_Half, 72 | pub e_shnum: Elf64_Half, 73 | pub e_shstrndx: Elf64_Half, 74 | } 75 | pub type Elf64_Ehdr = elf64_hdr; 76 | 77 | #[repr(C)] 78 | #[derive(Debug, Default, Copy, Clone)] 79 | pub struct elf64_phdr { 80 | pub p_type: Elf64_Word, 81 | pub p_flags: Elf64_Word, 82 | pub p_offset: Elf64_Off, 83 | pub p_vaddr: Elf64_Addr, 84 | pub p_paddr: Elf64_Addr, 85 | pub p_filesz: Elf64_Xword, 86 | pub p_memsz: Elf64_Xword, 87 | pub p_align: Elf64_Xword, 88 | } 89 | pub type Elf64_Phdr = elf64_phdr; 90 | 91 | #[repr(C)] 92 | #[derive(Debug, Default, Copy, Clone)] 93 | pub struct elf64_note { 94 | pub n_namesz: Elf64_Word, 95 | pub n_descsz: Elf64_Word, 96 | pub n_type: Elf64_Word, 97 | } 98 | pub type Elf64_Nhdr = elf64_note; 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use super::*; 103 | 104 | #[test] 105 | fn bindgen_test_layout_elf64_hdr() { 106 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 107 | let ptr = UNINIT.as_ptr(); 108 | assert_eq!( 109 | ::std::mem::size_of::(), 110 | 64usize, 111 | concat!("Size of: ", stringify!(elf64_hdr)) 112 | ); 113 | assert_eq!( 114 | ::std::mem::align_of::(), 115 | 8usize, 116 | concat!("Alignment of ", stringify!(elf64_hdr)) 117 | ); 118 | assert_eq!( 119 | unsafe { ::std::ptr::addr_of!((*ptr).e_ident) as usize - ptr as usize }, 120 | 0usize, 121 | concat!( 122 | "Offset of field: ", 123 | stringify!(elf64_hdr), 124 | "::", 125 | stringify!(e_ident) 126 | ) 127 | ); 128 | assert_eq!( 129 | unsafe { ::std::ptr::addr_of!((*ptr).e_type) as usize - ptr as usize }, 130 | 16usize, 131 | concat!( 132 | "Offset of field: ", 133 | stringify!(elf64_hdr), 134 | "::", 135 | stringify!(e_type) 136 | ) 137 | ); 138 | assert_eq!( 139 | unsafe { ::std::ptr::addr_of!((*ptr).e_machine) as usize - ptr as usize }, 140 | 18usize, 141 | concat!( 142 | "Offset of field: ", 143 | stringify!(elf64_hdr), 144 | "::", 145 | stringify!(e_machine) 146 | ) 147 | ); 148 | assert_eq!( 149 | unsafe { ::std::ptr::addr_of!((*ptr).e_version) as usize - ptr as usize }, 150 | 20usize, 151 | concat!( 152 | "Offset of field: ", 153 | stringify!(elf64_hdr), 154 | "::", 155 | stringify!(e_version) 156 | ) 157 | ); 158 | assert_eq!( 159 | unsafe { ::std::ptr::addr_of!((*ptr).e_entry) as usize - ptr as usize }, 160 | 24usize, 161 | concat!( 162 | "Offset of field: ", 163 | stringify!(elf64_hdr), 164 | "::", 165 | stringify!(e_entry) 166 | ) 167 | ); 168 | assert_eq!( 169 | unsafe { ::std::ptr::addr_of!((*ptr).e_phoff) as usize - ptr as usize }, 170 | 32usize, 171 | concat!( 172 | "Offset of field: ", 173 | stringify!(elf64_hdr), 174 | "::", 175 | stringify!(e_phoff) 176 | ) 177 | ); 178 | assert_eq!( 179 | unsafe { ::std::ptr::addr_of!((*ptr).e_shoff) as usize - ptr as usize }, 180 | 40usize, 181 | concat!( 182 | "Offset of field: ", 183 | stringify!(elf64_hdr), 184 | "::", 185 | stringify!(e_shoff) 186 | ) 187 | ); 188 | assert_eq!( 189 | unsafe { ::std::ptr::addr_of!((*ptr).e_flags) as usize - ptr as usize }, 190 | 48usize, 191 | concat!( 192 | "Offset of field: ", 193 | stringify!(elf64_hdr), 194 | "::", 195 | stringify!(e_flags) 196 | ) 197 | ); 198 | assert_eq!( 199 | unsafe { ::std::ptr::addr_of!((*ptr).e_ehsize) as usize - ptr as usize }, 200 | 52usize, 201 | concat!( 202 | "Offset of field: ", 203 | stringify!(elf64_hdr), 204 | "::", 205 | stringify!(e_ehsize) 206 | ) 207 | ); 208 | assert_eq!( 209 | unsafe { ::std::ptr::addr_of!((*ptr).e_phentsize) as usize - ptr as usize }, 210 | 54usize, 211 | concat!( 212 | "Offset of field: ", 213 | stringify!(elf64_hdr), 214 | "::", 215 | stringify!(e_phentsize) 216 | ) 217 | ); 218 | assert_eq!( 219 | unsafe { ::std::ptr::addr_of!((*ptr).e_phnum) as usize - ptr as usize }, 220 | 56usize, 221 | concat!( 222 | "Offset of field: ", 223 | stringify!(elf64_hdr), 224 | "::", 225 | stringify!(e_phnum) 226 | ) 227 | ); 228 | assert_eq!( 229 | unsafe { ::std::ptr::addr_of!((*ptr).e_shentsize) as usize - ptr as usize }, 230 | 58usize, 231 | concat!( 232 | "Offset of field: ", 233 | stringify!(elf64_hdr), 234 | "::", 235 | stringify!(e_shentsize) 236 | ) 237 | ); 238 | assert_eq!( 239 | unsafe { ::std::ptr::addr_of!((*ptr).e_shnum) as usize - ptr as usize }, 240 | 60usize, 241 | concat!( 242 | "Offset of field: ", 243 | stringify!(elf64_hdr), 244 | "::", 245 | stringify!(e_shnum) 246 | ) 247 | ); 248 | assert_eq!( 249 | unsafe { ::std::ptr::addr_of!((*ptr).e_shstrndx) as usize - ptr as usize }, 250 | 62usize, 251 | concat!( 252 | "Offset of field: ", 253 | stringify!(elf64_hdr), 254 | "::", 255 | stringify!(e_shstrndx) 256 | ) 257 | ); 258 | } 259 | 260 | #[test] 261 | fn bindgen_test_layout_elf64_phdr() { 262 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 263 | let ptr = UNINIT.as_ptr(); 264 | assert_eq!( 265 | ::std::mem::size_of::(), 266 | 56usize, 267 | concat!("Size of: ", stringify!(elf64_phdr)) 268 | ); 269 | assert_eq!( 270 | ::std::mem::align_of::(), 271 | 8usize, 272 | concat!("Alignment of ", stringify!(elf64_phdr)) 273 | ); 274 | assert_eq!( 275 | unsafe { ::std::ptr::addr_of!((*ptr).p_type) as usize - ptr as usize }, 276 | 0usize, 277 | concat!( 278 | "Offset of field: ", 279 | stringify!(elf64_phdr), 280 | "::", 281 | stringify!(p_type) 282 | ) 283 | ); 284 | assert_eq!( 285 | unsafe { ::std::ptr::addr_of!((*ptr).p_flags) as usize - ptr as usize }, 286 | 4usize, 287 | concat!( 288 | "Offset of field: ", 289 | stringify!(elf64_phdr), 290 | "::", 291 | stringify!(p_flags) 292 | ) 293 | ); 294 | assert_eq!( 295 | unsafe { ::std::ptr::addr_of!((*ptr).p_offset) as usize - ptr as usize }, 296 | 8usize, 297 | concat!( 298 | "Offset of field: ", 299 | stringify!(elf64_phdr), 300 | "::", 301 | stringify!(p_offset) 302 | ) 303 | ); 304 | assert_eq!( 305 | unsafe { ::std::ptr::addr_of!((*ptr).p_vaddr) as usize - ptr as usize }, 306 | 16usize, 307 | concat!( 308 | "Offset of field: ", 309 | stringify!(elf64_phdr), 310 | "::", 311 | stringify!(p_vaddr) 312 | ) 313 | ); 314 | assert_eq!( 315 | unsafe { ::std::ptr::addr_of!((*ptr).p_paddr) as usize - ptr as usize }, 316 | 24usize, 317 | concat!( 318 | "Offset of field: ", 319 | stringify!(elf64_phdr), 320 | "::", 321 | stringify!(p_paddr) 322 | ) 323 | ); 324 | assert_eq!( 325 | unsafe { ::std::ptr::addr_of!((*ptr).p_filesz) as usize - ptr as usize }, 326 | 32usize, 327 | concat!( 328 | "Offset of field: ", 329 | stringify!(elf64_phdr), 330 | "::", 331 | stringify!(p_filesz) 332 | ) 333 | ); 334 | assert_eq!( 335 | unsafe { ::std::ptr::addr_of!((*ptr).p_memsz) as usize - ptr as usize }, 336 | 40usize, 337 | concat!( 338 | "Offset of field: ", 339 | stringify!(elf64_phdr), 340 | "::", 341 | stringify!(p_memsz) 342 | ) 343 | ); 344 | assert_eq!( 345 | unsafe { ::std::ptr::addr_of!((*ptr).p_align) as usize - ptr as usize }, 346 | 48usize, 347 | concat!( 348 | "Offset of field: ", 349 | stringify!(elf64_phdr), 350 | "::", 351 | stringify!(p_align) 352 | ) 353 | ); 354 | } 355 | 356 | #[test] 357 | fn bindgen_test_layout_elf64_note() { 358 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 359 | let ptr = UNINIT.as_ptr(); 360 | assert_eq!( 361 | ::std::mem::size_of::(), 362 | 12usize, 363 | concat!("Size of: ", stringify!(elf64_note)) 364 | ); 365 | assert_eq!( 366 | ::std::mem::align_of::(), 367 | 4usize, 368 | concat!("Alignment of ", stringify!(elf64_note)) 369 | ); 370 | assert_eq!( 371 | unsafe { ::std::ptr::addr_of!((*ptr).n_namesz) as usize - ptr as usize }, 372 | 0usize, 373 | concat!( 374 | "Offset of field: ", 375 | stringify!(elf64_note), 376 | "::", 377 | stringify!(n_namesz) 378 | ) 379 | ); 380 | assert_eq!( 381 | unsafe { ::std::ptr::addr_of!((*ptr).n_descsz) as usize - ptr as usize }, 382 | 4usize, 383 | concat!( 384 | "Offset of field: ", 385 | stringify!(elf64_note), 386 | "::", 387 | stringify!(n_descsz) 388 | ) 389 | ); 390 | assert_eq!( 391 | unsafe { ::std::ptr::addr_of!((*ptr).n_type) as usize - ptr as usize }, 392 | 8usize, 393 | concat!( 394 | "Offset of field: ", 395 | stringify!(elf64_note), 396 | "::", 397 | stringify!(n_type) 398 | ) 399 | ); 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /src/loader_gen/x86_64/start_info.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Permission is hereby granted, free of charge, to any person obtaining a copy 3 | * of this software and associated documentation files (the "Software"), to 4 | * deal in the Software without restriction, including without limitation the 5 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 6 | * sell copies of the Software, and to permit persons to whom the Software is 7 | * furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in 10 | * all copies or substantial portions of the Software. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 17 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS IN THE SOFTWARE. 19 | * 20 | * Copyright (c) 2016, Citrix Systems, Inc. 21 | */ 22 | 23 | /* 24 | * automatically generated by rust-bindgen using: 25 | * 26 | * # bindgen start_info.h -- -include stdint.h > start_info.rs 27 | * 28 | * From the canonical version in upstream Xen repository 29 | * xen/include/public/arch-x86/hvm/start_info.h 30 | * at commit: 31 | * b4642c32c4d079916d5607ddda0232aae5e1690e 32 | * 33 | * The generated file has been edited to eliminate unnecessary 34 | * definitions, add comments, and relocate definitions and tests for clarity. 35 | * Added Default to the list of traits that are automatically derived. 36 | * 37 | * The definitions in this file are intended to be exported and used by a particular 38 | * VMM implementation in order to boot a Linux guest using the PVH entry point as 39 | * specified in the x86/HVM direct boot ABI. 40 | * These structures contain all the required information (cmdline address, ACPI RSDP, 41 | * memory maps, etc) that must be written to guest memory before starting guest 42 | * execution by jumping to the PVH entry point address. 43 | * A comparable set of definitions to hvm_start_info and hvm_memmap_table_entry in this 44 | * file would be the boot_params and boot_e820_entry definitions used by the Linux 45 | * 64-bit boot protocol. 46 | * 47 | * Start of day structure passed to PVH guests and to HVM guests in %ebx. 48 | * 49 | * NOTE: nothing will be loaded at physical address 0, so a 0 value in any 50 | * of the address fields should be treated as not present. 51 | * 52 | * 0 +----------------+ 53 | * | magic | Contains the magic value XEN_HVM_START_MAGIC_VALUE 54 | * | | ("xEn3" with the 0x80 bit of the "E" set). 55 | * 4 +----------------+ 56 | * | version | Version of this structure. Current version is 1. New 57 | * | | versions are guaranteed to be backwards-compatible. 58 | * 8 +----------------+ 59 | * | flags | SIF_xxx flags. 60 | * 12 +----------------+ 61 | * | nr_modules | Number of modules passed to the kernel. 62 | * 16 +----------------+ 63 | * | modlist_paddr | Physical address of an array of modules 64 | * | | (layout of the structure below). 65 | * 24 +----------------+ 66 | * | cmdline_paddr | Physical address of the command line, 67 | * | | a zero-terminated ASCII string. 68 | * 32 +----------------+ 69 | * | rsdp_paddr | Physical address of the RSDP ACPI data structure. 70 | * 40 +----------------+ 71 | * | memmap_paddr | Physical address of the (optional) memory map. Only 72 | * | | present in version 1 and newer of the structure. 73 | * 48 +----------------+ 74 | * | memmap_entries | Number of entries in the memory map table. Zero 75 | * | | if there is no memory map being provided. Only 76 | * | | present in version 1 and newer of the structure. 77 | * 52 +----------------+ 78 | * | reserved | Version 1 and newer only. 79 | * 56 +----------------+ 80 | * 81 | * The layout of each entry in the module structure is the following: 82 | * 83 | * 0 +----------------+ 84 | * | paddr | Physical address of the module. 85 | * 8 +----------------+ 86 | * | size | Size of the module in bytes. 87 | * 16 +----------------+ 88 | * | cmdline_paddr | Physical address of the command line, 89 | * | | a zero-terminated ASCII string. 90 | * 24 +----------------+ 91 | * | reserved | 92 | * 32 +----------------+ 93 | * 94 | * The layout of each entry in the memory map table is as follows: 95 | * 96 | * 0 +----------------+ 97 | * | addr | Base address 98 | * 8 +----------------+ 99 | * | size | Size of mapping in bytes 100 | * 16 +----------------+ 101 | * | type | Type of mapping as defined between the hypervisor 102 | * | | and guest. See XEN_HVM_MEMMAP_TYPE_* values below. 103 | * 20 +----------------| 104 | * | reserved | 105 | * 24 +----------------+ 106 | * 107 | * The address and sizes are always a 64bit little endian unsigned integer. 108 | * 109 | * NB: Xen on x86 will always try to place all the data below the 4GiB 110 | * boundary. 111 | * 112 | * Version numbers of the hvm_start_info structure have evolved like this: 113 | * 114 | * Version 0: Initial implementation. 115 | * 116 | * Version 1: Added the memmap_paddr/memmap_entries fields (plus 4 bytes of 117 | * padding) to the end of the hvm_start_info struct. These new 118 | * fields can be used to pass a memory map to the guest. The 119 | * memory map is optional and so guests that understand version 1 120 | * of the structure must check that memmap_entries is non-zero 121 | * before trying to read the memory map. 122 | */ 123 | 124 | #[repr(C)] 125 | #[derive(Debug, Copy, Clone, Default)] 126 | pub struct hvm_start_info { 127 | pub magic: u32, 128 | pub version: u32, 129 | pub flags: u32, 130 | pub nr_modules: u32, 131 | pub modlist_paddr: u64, 132 | pub cmdline_paddr: u64, 133 | pub rsdp_paddr: u64, 134 | pub memmap_paddr: u64, 135 | pub memmap_entries: u32, 136 | pub reserved: u32, 137 | } 138 | 139 | #[repr(C)] 140 | #[derive(Debug, Copy, Clone, Default)] 141 | pub struct hvm_modlist_entry { 142 | pub paddr: u64, 143 | pub size: u64, 144 | pub cmdline_paddr: u64, 145 | pub reserved: u64, 146 | } 147 | 148 | #[repr(C)] 149 | #[derive(Debug, Copy, Clone, Default)] 150 | pub struct hvm_memmap_table_entry { 151 | pub addr: u64, 152 | pub size: u64, 153 | pub type_: u32, 154 | pub reserved: u32, 155 | } 156 | 157 | #[cfg(test)] 158 | mod tests { 159 | use super::*; 160 | 161 | #[test] 162 | fn bindgen_test_layout_hvm_start_info() { 163 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 164 | let ptr = UNINIT.as_ptr(); 165 | assert_eq!( 166 | ::std::mem::size_of::(), 167 | 56usize, 168 | concat!("Size of: ", stringify!(hvm_start_info)) 169 | ); 170 | assert_eq!( 171 | ::std::mem::align_of::(), 172 | 8usize, 173 | concat!("Alignment of ", stringify!(hvm_start_info)) 174 | ); 175 | assert_eq!( 176 | unsafe { ::std::ptr::addr_of!((*ptr).magic) as usize - ptr as usize }, 177 | 0usize, 178 | concat!( 179 | "Offset of field: ", 180 | stringify!(hvm_start_info), 181 | "::", 182 | stringify!(magic) 183 | ) 184 | ); 185 | assert_eq!( 186 | unsafe { ::std::ptr::addr_of!((*ptr).version) as usize - ptr as usize }, 187 | 4usize, 188 | concat!( 189 | "Offset of field: ", 190 | stringify!(hvm_start_info), 191 | "::", 192 | stringify!(version) 193 | ) 194 | ); 195 | assert_eq!( 196 | unsafe { ::std::ptr::addr_of!((*ptr).flags) as usize - ptr as usize }, 197 | 8usize, 198 | concat!( 199 | "Offset of field: ", 200 | stringify!(hvm_start_info), 201 | "::", 202 | stringify!(flags) 203 | ) 204 | ); 205 | assert_eq!( 206 | unsafe { ::std::ptr::addr_of!((*ptr).nr_modules) as usize - ptr as usize }, 207 | 12usize, 208 | concat!( 209 | "Offset of field: ", 210 | stringify!(hvm_start_info), 211 | "::", 212 | stringify!(nr_modules) 213 | ) 214 | ); 215 | assert_eq!( 216 | unsafe { ::std::ptr::addr_of!((*ptr).modlist_paddr) as usize - ptr as usize }, 217 | 16usize, 218 | concat!( 219 | "Offset of field: ", 220 | stringify!(hvm_start_info), 221 | "::", 222 | stringify!(modlist_paddr) 223 | ) 224 | ); 225 | assert_eq!( 226 | unsafe { ::std::ptr::addr_of!((*ptr).cmdline_paddr) as usize - ptr as usize }, 227 | 24usize, 228 | concat!( 229 | "Offset of field: ", 230 | stringify!(hvm_start_info), 231 | "::", 232 | stringify!(cmdline_paddr) 233 | ) 234 | ); 235 | assert_eq!( 236 | unsafe { ::std::ptr::addr_of!((*ptr).rsdp_paddr) as usize - ptr as usize }, 237 | 32usize, 238 | concat!( 239 | "Offset of field: ", 240 | stringify!(hvm_start_info), 241 | "::", 242 | stringify!(rsdp_paddr) 243 | ) 244 | ); 245 | assert_eq!( 246 | unsafe { ::std::ptr::addr_of!((*ptr).memmap_paddr) as usize - ptr as usize }, 247 | 40usize, 248 | concat!( 249 | "Offset of field: ", 250 | stringify!(hvm_start_info), 251 | "::", 252 | stringify!(memmap_paddr) 253 | ) 254 | ); 255 | assert_eq!( 256 | unsafe { ::std::ptr::addr_of!((*ptr).memmap_entries) as usize - ptr as usize }, 257 | 48usize, 258 | concat!( 259 | "Offset of field: ", 260 | stringify!(hvm_start_info), 261 | "::", 262 | stringify!(memmap_entries) 263 | ) 264 | ); 265 | assert_eq!( 266 | unsafe { ::std::ptr::addr_of!((*ptr).reserved) as usize - ptr as usize }, 267 | 52usize, 268 | concat!( 269 | "Offset of field: ", 270 | stringify!(hvm_start_info), 271 | "::", 272 | stringify!(reserved) 273 | ) 274 | ); 275 | } 276 | 277 | #[test] 278 | fn bindgen_test_layout_hvm_modlist_entry() { 279 | const UNINIT: ::std::mem::MaybeUninit = 280 | ::std::mem::MaybeUninit::uninit(); 281 | let ptr = UNINIT.as_ptr(); 282 | assert_eq!( 283 | ::std::mem::size_of::(), 284 | 32usize, 285 | concat!("Size of: ", stringify!(hvm_modlist_entry)) 286 | ); 287 | assert_eq!( 288 | ::std::mem::align_of::(), 289 | 8usize, 290 | concat!("Alignment of ", stringify!(hvm_modlist_entry)) 291 | ); 292 | assert_eq!( 293 | unsafe { ::std::ptr::addr_of!((*ptr).paddr) as usize - ptr as usize }, 294 | 0usize, 295 | concat!( 296 | "Offset of field: ", 297 | stringify!(hvm_modlist_entry), 298 | "::", 299 | stringify!(paddr) 300 | ) 301 | ); 302 | assert_eq!( 303 | unsafe { ::std::ptr::addr_of!((*ptr).size) as usize - ptr as usize }, 304 | 8usize, 305 | concat!( 306 | "Offset of field: ", 307 | stringify!(hvm_modlist_entry), 308 | "::", 309 | stringify!(size) 310 | ) 311 | ); 312 | assert_eq!( 313 | unsafe { ::std::ptr::addr_of!((*ptr).cmdline_paddr) as usize - ptr as usize }, 314 | 16usize, 315 | concat!( 316 | "Offset of field: ", 317 | stringify!(hvm_modlist_entry), 318 | "::", 319 | stringify!(cmdline_paddr) 320 | ) 321 | ); 322 | assert_eq!( 323 | unsafe { ::std::ptr::addr_of!((*ptr).reserved) as usize - ptr as usize }, 324 | 24usize, 325 | concat!( 326 | "Offset of field: ", 327 | stringify!(hvm_modlist_entry), 328 | "::", 329 | stringify!(reserved) 330 | ) 331 | ); 332 | } 333 | 334 | #[test] 335 | fn bindgen_test_layout_hvm_memmap_table_entry() { 336 | const UNINIT: ::std::mem::MaybeUninit = 337 | ::std::mem::MaybeUninit::uninit(); 338 | let ptr = UNINIT.as_ptr(); 339 | assert_eq!( 340 | ::std::mem::size_of::(), 341 | 24usize, 342 | concat!("Size of: ", stringify!(hvm_memmap_table_entry)) 343 | ); 344 | assert_eq!( 345 | ::std::mem::align_of::(), 346 | 8usize, 347 | concat!("Alignment of ", stringify!(hvm_memmap_table_entry)) 348 | ); 349 | assert_eq!( 350 | unsafe { ::std::ptr::addr_of!((*ptr).addr) as usize - ptr as usize }, 351 | 0usize, 352 | concat!( 353 | "Offset of field: ", 354 | stringify!(hvm_memmap_table_entry), 355 | "::", 356 | stringify!(addr) 357 | ) 358 | ); 359 | assert_eq!( 360 | unsafe { ::std::ptr::addr_of!((*ptr).size) as usize - ptr as usize }, 361 | 8usize, 362 | concat!( 363 | "Offset of field: ", 364 | stringify!(hvm_memmap_table_entry), 365 | "::", 366 | stringify!(size) 367 | ) 368 | ); 369 | assert_eq!( 370 | unsafe { ::std::ptr::addr_of!((*ptr).type_) as usize - ptr as usize }, 371 | 16usize, 372 | concat!( 373 | "Offset of field: ", 374 | stringify!(hvm_memmap_table_entry), 375 | "::", 376 | stringify!(type_) 377 | ) 378 | ); 379 | assert_eq!( 380 | unsafe { ::std::ptr::addr_of!((*ptr).reserved) as usize - ptr as usize }, 381 | 20usize, 382 | concat!( 383 | "Offset of field: ", 384 | stringify!(hvm_memmap_table_entry), 385 | "::", 386 | stringify!(reserved) 387 | ) 388 | ); 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /src/loader/elf/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2020, Oracle and/or its affiliates. 2 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 3 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | // 5 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style license that can be 7 | // found in the LICENSE-BSD-3-Clause file. 8 | // 9 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 10 | 11 | //! Traits and structs for loading elf image kernels into guest memory. 12 | 13 | #![cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] 14 | 15 | use std::fmt; 16 | use std::io::{Read, Seek, SeekFrom}; 17 | use std::mem; 18 | use std::result; 19 | 20 | use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize, ReadVolatile}; 21 | 22 | use crate::loader::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; 23 | use crate::loader_gen::elf; 24 | pub use crate::loader_gen::start_info; 25 | 26 | // SAFETY: The layout of the structure is fixed and can be initialized by 27 | // reading its content from byte array. 28 | unsafe impl ByteValued for elf::Elf64_Ehdr {} 29 | 30 | // SAFETY: The layout of the structure is fixed and can be initialized by 31 | // reading its content from byte array. 32 | unsafe impl ByteValued for elf::Elf64_Nhdr {} 33 | 34 | // SAFETY: The layout of the structure is fixed and can be initialized by 35 | // reading its content from byte array. 36 | unsafe impl ByteValued for elf::Elf64_Phdr {} 37 | 38 | #[derive(Debug, PartialEq, Eq)] 39 | /// Elf kernel loader errors. 40 | pub enum Error { 41 | /// Invalid alignment. 42 | Align, 43 | /// Loaded big endian binary on a little endian platform. 44 | BigEndianElfOnLittle, 45 | /// Invalid ELF magic number. 46 | InvalidElfMagicNumber, 47 | /// Invalid program header size. 48 | InvalidProgramHeaderSize, 49 | /// Invalid program header offset. 50 | InvalidProgramHeaderOffset, 51 | /// Invalid program header address. 52 | InvalidProgramHeaderAddress, 53 | /// Invalid entry address. 54 | InvalidEntryAddress, 55 | /// Overflow occurred during an arithmetic operation. 56 | Overflow, 57 | /// Unable to read ELF header. 58 | ReadElfHeader, 59 | /// Unable to read kernel image. 60 | ReadKernelImage, 61 | /// Unable to read program header. 62 | ReadProgramHeader, 63 | /// Unable to seek to kernel start. 64 | SeekKernelStart, 65 | /// Unable to seek to ELF start. 66 | SeekElfStart, 67 | /// Unable to seek to program header. 68 | SeekProgramHeader, 69 | /// Unable to seek to note header. 70 | SeekNoteHeader, 71 | /// Unable to read note header. 72 | ReadNoteHeader, 73 | /// Invalid PVH note. 74 | InvalidPvhNote, 75 | } 76 | 77 | impl fmt::Display for Error { 78 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 79 | let desc = match self { 80 | Error::Align => "Invalid alignment", 81 | Error::BigEndianElfOnLittle => { 82 | "Trying to load big-endian binary on little-endian machine" 83 | } 84 | Error::InvalidElfMagicNumber => "Invalid Elf magic number", 85 | Error::InvalidProgramHeaderSize => "Invalid program header size", 86 | Error::InvalidProgramHeaderOffset => "Invalid program header offset", 87 | Error::InvalidProgramHeaderAddress => "Invalid Program Header Address", 88 | Error::InvalidEntryAddress => "Invalid entry address", 89 | Error::Overflow => "Overflow occurred during an arithmetic operation", 90 | Error::ReadElfHeader => "Unable to read elf header", 91 | Error::ReadKernelImage => "Unable to read kernel image", 92 | Error::ReadProgramHeader => "Unable to read program header", 93 | Error::SeekKernelStart => "Unable to seek to kernel start", 94 | Error::SeekElfStart => "Unable to seek to elf start", 95 | Error::SeekProgramHeader => "Unable to seek to program header", 96 | Error::SeekNoteHeader => "Unable to seek to note header", 97 | Error::ReadNoteHeader => "Unable to read note header", 98 | Error::InvalidPvhNote => "Invalid PVH note header", 99 | }; 100 | 101 | write!(f, "Kernel Loader: {}", desc) 102 | } 103 | } 104 | 105 | impl std::error::Error for Error {} 106 | 107 | #[derive(Clone, Default, Copy, Debug, PartialEq, Eq)] 108 | /// Availability of PVH entry point in the kernel, which allows the VMM 109 | /// to use the PVH boot protocol to start guests. 110 | pub enum PvhBootCapability { 111 | /// PVH entry point is present 112 | PvhEntryPresent(GuestAddress), 113 | /// PVH entry point is not present 114 | PvhEntryNotPresent, 115 | /// PVH entry point is ignored, even if available 116 | #[default] 117 | PvhEntryIgnored, 118 | } 119 | 120 | impl fmt::Display for PvhBootCapability { 121 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 122 | use self::PvhBootCapability::*; 123 | match self { 124 | PvhEntryPresent(pvh_entry_addr) => write!( 125 | f, 126 | "PVH entry point present at guest address: {:#x}", 127 | pvh_entry_addr.raw_value() 128 | ), 129 | PvhEntryNotPresent => write!(f, "PVH entry point not present"), 130 | PvhEntryIgnored => write!(f, "PVH entry point ignored"), 131 | } 132 | } 133 | } 134 | 135 | /// Raw ELF (a.k.a. vmlinux) kernel image support. 136 | pub struct Elf; 137 | 138 | impl Elf { 139 | /// Verifies that magic numbers are present in the Elf header. 140 | fn validate_header(ehdr: &elf::Elf64_Ehdr) -> std::result::Result<(), Error> { 141 | // Sanity checks 142 | if ehdr.e_ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8 143 | || ehdr.e_ident[elf::EI_MAG1 as usize] != elf::ELFMAG1 144 | || ehdr.e_ident[elf::EI_MAG2 as usize] != elf::ELFMAG2 145 | || ehdr.e_ident[elf::EI_MAG3 as usize] != elf::ELFMAG3 146 | { 147 | return Err(Error::InvalidElfMagicNumber); 148 | } 149 | if ehdr.e_ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 { 150 | return Err(Error::BigEndianElfOnLittle); 151 | } 152 | if ehdr.e_phentsize as usize != mem::size_of::() { 153 | return Err(Error::InvalidProgramHeaderSize); 154 | } 155 | if (ehdr.e_phoff as usize) < mem::size_of::() { 156 | return Err(Error::InvalidProgramHeaderOffset); 157 | } 158 | Ok(()) 159 | } 160 | } 161 | 162 | impl KernelLoader for Elf { 163 | /// Loads a kernel from a vmlinux elf image into guest memory. 164 | /// 165 | /// By default, the kernel is loaded into guest memory at offset `phdr.p_paddr` specified 166 | /// by the elf image. When used, `kernel_offset` specifies a fixed offset from `phdr.p_paddr` 167 | /// at which to load the kernel. If `kernel_offset` is requested, the `pvh_entry_addr` field 168 | /// of the result will not be populated. 169 | /// 170 | /// # Arguments 171 | /// 172 | /// * `guest_mem`: [`GuestMemory`] to load the kernel in. 173 | /// * `kernel_offset`: Offset to be added to default kernel load address in guest memory. 174 | /// * `kernel_image` - Input vmlinux image. 175 | /// * `highmem_start_address`: Address where high memory starts. 176 | /// 177 | /// # Examples 178 | /// 179 | /// ```rust 180 | /// # extern crate vm_memory; 181 | /// # use std::io::Cursor; 182 | /// # use linux_loader::loader::*; 183 | /// # use vm_memory::{Address, GuestAddress}; 184 | /// # type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>; 185 | /// let mem_size: usize = 0x1000000; 186 | /// let himem_start = GuestAddress(0x0); 187 | /// let kernel_addr = GuestAddress(0x200000); 188 | /// let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), mem_size)]).unwrap(); 189 | /// let mut kernel_image = vec![]; 190 | /// kernel_image.extend_from_slice(include_bytes!("test_elf.bin")); 191 | /// elf::Elf::load( 192 | /// &gm, 193 | /// Some(kernel_addr), 194 | /// &mut Cursor::new(&kernel_image), 195 | /// Some(himem_start), 196 | /// ) 197 | /// .unwrap(); 198 | /// ``` 199 | /// 200 | /// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html 201 | fn load( 202 | guest_mem: &M, 203 | kernel_offset: Option, 204 | kernel_image: &mut F, 205 | highmem_start_address: Option, 206 | ) -> Result 207 | where 208 | F: Read + ReadVolatile + Seek, 209 | { 210 | kernel_image.rewind().map_err(|_| Error::SeekElfStart)?; 211 | 212 | let mut ehdr = elf::Elf64_Ehdr::default(); 213 | kernel_image 214 | .read_exact(ehdr.as_mut_slice()) 215 | .map_err(|_| Error::ReadElfHeader)?; 216 | 217 | // Sanity checks. 218 | Self::validate_header(&ehdr)?; 219 | if let Some(addr) = highmem_start_address { 220 | if (ehdr.e_entry) < addr.raw_value() { 221 | return Err(Error::InvalidEntryAddress.into()); 222 | } 223 | } 224 | 225 | let mut loader_result = KernelLoaderResult { 226 | kernel_load: match kernel_offset { 227 | Some(k_offset) => GuestAddress( 228 | k_offset 229 | .raw_value() 230 | .checked_add(ehdr.e_entry) 231 | .ok_or(Error::Overflow)?, 232 | ), 233 | None => GuestAddress(ehdr.e_entry), 234 | }, 235 | ..Default::default() 236 | }; 237 | 238 | kernel_image 239 | .seek(SeekFrom::Start(ehdr.e_phoff)) 240 | .map_err(|_| Error::SeekProgramHeader)?; 241 | 242 | let mut phdrs: Vec = vec![]; 243 | for _ in 0usize..ehdr.e_phnum as usize { 244 | let mut phdr = elf::Elf64_Phdr::default(); 245 | kernel_image 246 | .read_exact(phdr.as_mut_slice()) 247 | .map_err(|_| Error::ReadProgramHeader)?; 248 | phdrs.push(phdr); 249 | } 250 | 251 | // Read in each section pointed to by the program headers. 252 | for phdr in phdrs { 253 | if phdr.p_type != elf::PT_LOAD || phdr.p_filesz == 0 { 254 | if phdr.p_type == elf::PT_NOTE { 255 | // The PVH boot protocol currently requires that the kernel is loaded at 256 | // the default kernel load address in guest memory (specified at kernel 257 | // build time by the value of CONFIG_PHYSICAL_START). Therefore, only 258 | // attempt to use PVH if an offset from the default load address has not 259 | // been requested using the kernel_offset parameter. 260 | if let Some(_offset) = kernel_offset { 261 | loader_result.pvh_boot_cap = PvhBootCapability::PvhEntryIgnored; 262 | } else { 263 | // If kernel_offset is not requested, check if PVH entry point is present 264 | loader_result.pvh_boot_cap = parse_elf_note(&phdr, kernel_image)?; 265 | } 266 | } 267 | continue; 268 | } 269 | 270 | kernel_image 271 | .seek(SeekFrom::Start(phdr.p_offset)) 272 | .map_err(|_| Error::SeekKernelStart)?; 273 | 274 | // if the vmm does not specify where the kernel should be loaded, just 275 | // load it to the physical address p_paddr for each segment. 276 | let mem_offset = match kernel_offset { 277 | Some(k_offset) => k_offset 278 | .checked_add(phdr.p_paddr) 279 | .ok_or(Error::InvalidProgramHeaderAddress)?, 280 | None => GuestAddress(phdr.p_paddr), 281 | }; 282 | 283 | guest_mem 284 | .read_exact_volatile_from(mem_offset, kernel_image, phdr.p_filesz as usize) 285 | .map_err(|_| Error::ReadKernelImage)?; 286 | 287 | let kernel_end = mem_offset 288 | .raw_value() 289 | .checked_add(phdr.p_memsz as GuestUsize) 290 | .ok_or(KernelLoaderError::MemoryOverflow)?; 291 | loader_result.kernel_end = std::cmp::max(loader_result.kernel_end, kernel_end); 292 | } 293 | 294 | // elf image has no setup_header which is defined for bzImage 295 | loader_result.setup_header = None; 296 | 297 | Ok(loader_result) 298 | } 299 | } 300 | 301 | // Size of string "Xen", including the terminating NULL. 302 | const PVH_NOTE_STR_SZ: usize = 4; 303 | 304 | /// Examines a supplied elf program header of type `PT_NOTE` to determine if it contains an entry 305 | /// of type `XEN_ELFNOTE_PHYS32_ENTRY` (0x12). Notes of this type encode a physical 32-bit entry 306 | /// point address into the kernel, which is used when launching guests in 32-bit (protected) mode 307 | /// with paging disabled, as described by the PVH boot protocol. 308 | /// Returns the encoded entry point address, or `None` if no `XEN_ELFNOTE_PHYS32_ENTRY` entries 309 | /// are found in the note header. 310 | fn parse_elf_note(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result 311 | where 312 | F: Read + ReadVolatile + Seek, 313 | { 314 | // Type of note header that encodes a 32-bit entry point address to boot a guest kernel using 315 | // the PVH boot protocol. 316 | const XEN_ELFNOTE_PHYS32_ENTRY: u32 = 18; 317 | 318 | // Alignment of ELF notes, starting address of name field and descriptor field have a 4-byte 319 | // alignment. 320 | // 321 | // See refer from: 322 | // - 'Note Section' of 'Executable and Linking Format (ELF) Specification' v1.2. 323 | // - Linux implementations, https://elixir.bootlin.com/linux/v6.1/source/include/linux/elfnote.h#L56 324 | const ELFNOTE_ALIGN: u64 = 4; 325 | 326 | // Seek to the beginning of the note segment. 327 | kernel_image 328 | .seek(SeekFrom::Start(phdr.p_offset)) 329 | .map_err(|_| Error::SeekNoteHeader)?; 330 | 331 | // Now that the segment has been found, we must locate an ELF note with the correct type that 332 | // encodes the PVH entry point if there is one. 333 | let mut nhdr: elf::Elf64_Nhdr = Default::default(); 334 | let mut read_size: usize = 0; 335 | let nhdr_sz = mem::size_of::(); 336 | 337 | while read_size < phdr.p_filesz as usize { 338 | kernel_image 339 | .read_exact(nhdr.as_mut_slice()) 340 | .map_err(|_| Error::ReadNoteHeader)?; 341 | 342 | // Check if the note header's name and type match the ones specified by the PVH ABI. 343 | if nhdr.n_type == XEN_ELFNOTE_PHYS32_ENTRY && nhdr.n_namesz as usize == PVH_NOTE_STR_SZ { 344 | let mut buf = [0u8; PVH_NOTE_STR_SZ]; 345 | kernel_image 346 | .read_exact(&mut buf) 347 | .map_err(|_| Error::ReadNoteHeader)?; 348 | if buf == [b'X', b'e', b'n', b'\0'] { 349 | break; 350 | } 351 | } 352 | 353 | // Skip the note header plus the size of its fields (with alignment). 354 | let namesz_aligned = align_up(u64::from(nhdr.n_namesz), ELFNOTE_ALIGN)?; 355 | let descsz_aligned = align_up(u64::from(nhdr.n_descsz), ELFNOTE_ALIGN)?; 356 | 357 | // `namesz` and `descsz` are both `u32`s. We need to also verify for overflow, to be sure 358 | // we do not lose information. 359 | if namesz_aligned > u32::MAX.into() || descsz_aligned > u32::MAX.into() { 360 | return Err(Error::Overflow.into()); 361 | } 362 | 363 | read_size = read_size 364 | .checked_add(nhdr_sz) // Skip the ELF_NOTE known sized fields. 365 | // Safe to truncate or change the type to `usize` (4 or 8 bytes depending on the 366 | // architecture 32/64 bits) since we validated that we do not lose information. 367 | .and_then(|read_size| read_size.checked_add(namesz_aligned as usize)) 368 | .and_then(|read_size| read_size.checked_add(descsz_aligned as usize)) 369 | .ok_or(Error::Overflow)?; 370 | 371 | kernel_image 372 | // The conversion here does not truncate, since `read_size` is of `usize` type, which 373 | // can be at maximum 8 bytes long. 374 | .seek(SeekFrom::Start(phdr.p_offset + read_size as u64)) 375 | .map_err(|_| Error::SeekNoteHeader)?; 376 | } 377 | 378 | if read_size >= phdr.p_filesz as usize { 379 | // PVH ELF note not found, nothing else to do. 380 | return Ok(PvhBootCapability::PvhEntryNotPresent); 381 | } 382 | 383 | // Otherwise the correct note type was found. 384 | // The note header struct has already been read, so we can seek from the current position and 385 | // just skip the name field contents. 386 | kernel_image 387 | .seek(SeekFrom::Current( 388 | // Safe conversion since it is not losing data. 389 | align_up(u64::from(nhdr.n_namesz), ELFNOTE_ALIGN)? as i64 - PVH_NOTE_STR_SZ as i64, 390 | )) 391 | .map_err(|_| Error::SeekNoteHeader)?; 392 | 393 | // The PVH entry point is a 32-bit address, so the descriptor field must be capable of storing 394 | // all such addresses. 395 | if (nhdr.n_descsz as usize) < mem::size_of::() { 396 | return Err(Error::InvalidPvhNote.into()); 397 | } 398 | 399 | let mut pvh_addr_bytes = [0; mem::size_of::()]; 400 | 401 | // Read 32-bit address stored in the PVH note descriptor field. 402 | kernel_image 403 | .read_exact(&mut pvh_addr_bytes) 404 | .map_err(|_| Error::ReadNoteHeader)?; 405 | 406 | Ok(PvhBootCapability::PvhEntryPresent(GuestAddress( 407 | u32::from_le_bytes(pvh_addr_bytes).into(), 408 | ))) 409 | } 410 | 411 | /// Align address upwards. Adapted from x86_64 crate: 412 | /// https://docs.rs/x86_64/latest/x86_64/addr/fn.align_up.html 413 | /// 414 | /// Returns the smallest x with alignment `align` so that x >= addr if the alignment is a power of 415 | /// 2, or an error otherwise. 416 | fn align_up(addr: u64, align: u64) -> result::Result { 417 | if !align.is_power_of_two() { 418 | return Err(Error::Align); 419 | } 420 | let align_mask = align - 1; 421 | if addr & align_mask == 0 { 422 | Ok(addr) // already aligned 423 | } else { 424 | // Safe to unchecked add because this can be at maximum `2^64` - 1, which is not 425 | // overflowing. 426 | Ok((addr | align_mask) + 1) 427 | } 428 | } 429 | 430 | #[cfg(test)] 431 | mod tests { 432 | use super::*; 433 | use std::io::Cursor; 434 | use vm_memory::{Address, GuestAddress}; 435 | type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>; 436 | 437 | const MEM_SIZE: u64 = 0x100_0000; 438 | 439 | fn create_guest_mem() -> GuestMemoryMmap { 440 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() 441 | } 442 | 443 | fn make_elf_bin() -> Vec { 444 | let mut v = Vec::new(); 445 | v.extend_from_slice(include_bytes!("test_elf.bin")); 446 | v 447 | } 448 | 449 | fn make_elfnote() -> Vec { 450 | include_bytes!("test_elfnote.bin").to_vec() 451 | } 452 | 453 | fn make_elfnote_8byte_align() -> Vec { 454 | include_bytes!("test_elfnote_8byte_align.bin").to_vec() 455 | } 456 | 457 | fn make_dummy_elfnote() -> Vec { 458 | include_bytes!("test_dummy_note.bin").to_vec() 459 | } 460 | 461 | fn make_invalid_pvh_note() -> Vec { 462 | include_bytes!("test_invalid_pvh_note.bin").to_vec() 463 | } 464 | 465 | fn make_elfnote_bad_align() -> Vec { 466 | include_bytes!("test_bad_align.bin").to_vec() 467 | } 468 | 469 | #[test] 470 | fn test_load_elf() { 471 | let gm = create_guest_mem(); 472 | let image = make_elf_bin(); 473 | let kernel_addr = GuestAddress(0x200000); 474 | let mut highmem_start_address = GuestAddress(0x0); 475 | let mut loader_result = Elf::load( 476 | &gm, 477 | Some(kernel_addr), 478 | &mut Cursor::new(&image), 479 | Some(highmem_start_address), 480 | ) 481 | .unwrap(); 482 | assert_eq!(loader_result.kernel_load.raw_value(), 0x200400); 483 | 484 | loader_result = Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None).unwrap(); 485 | assert_eq!(loader_result.kernel_load.raw_value(), 0x200400); 486 | 487 | loader_result = Elf::load( 488 | &gm, 489 | None, 490 | &mut Cursor::new(&image), 491 | Some(highmem_start_address), 492 | ) 493 | .unwrap(); 494 | assert_eq!(loader_result.kernel_load.raw_value(), 0x400); 495 | 496 | highmem_start_address = GuestAddress(0xa00000); 497 | assert_eq!( 498 | Some(KernelLoaderError::Elf(Error::InvalidEntryAddress)), 499 | Elf::load( 500 | &gm, 501 | None, 502 | &mut Cursor::new(&image), 503 | Some(highmem_start_address) 504 | ) 505 | .err() 506 | ); 507 | } 508 | 509 | #[test] 510 | fn test_bad_magic_number() { 511 | let gm = create_guest_mem(); 512 | let kernel_addr = GuestAddress(0x0); 513 | let mut bad_image = make_elf_bin(); 514 | bad_image[0x1] = 0x33; 515 | assert_eq!( 516 | Some(KernelLoaderError::Elf(Error::InvalidElfMagicNumber)), 517 | Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err() 518 | ); 519 | } 520 | 521 | #[test] 522 | fn test_bad_endian() { 523 | // Only little endian is supported. 524 | let gm = create_guest_mem(); 525 | let kernel_addr = GuestAddress(0x0); 526 | let mut bad_image = make_elf_bin(); 527 | bad_image[0x5] = 2; 528 | assert_eq!( 529 | Some(KernelLoaderError::Elf(Error::BigEndianElfOnLittle)), 530 | Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err() 531 | ); 532 | } 533 | 534 | #[test] 535 | fn test_bad_phoff() { 536 | // Program header has to be past the end of the elf header. 537 | let gm = create_guest_mem(); 538 | let kernel_addr = GuestAddress(0x0); 539 | let mut bad_image = make_elf_bin(); 540 | bad_image[0x20] = 0x10; 541 | assert_eq!( 542 | Some(KernelLoaderError::Elf(Error::InvalidProgramHeaderOffset)), 543 | Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err() 544 | ); 545 | } 546 | 547 | #[test] 548 | fn test_load_pvh() { 549 | let gm = create_guest_mem(); 550 | let pvhnote_image = make_elfnote(); 551 | let loader_result = Elf::load(&gm, None, &mut Cursor::new(&pvhnote_image), None).unwrap(); 552 | assert_eq!( 553 | loader_result.pvh_boot_cap, 554 | PvhBootCapability::PvhEntryPresent(GuestAddress(0x1e1fe1f)) 555 | ); 556 | 557 | // Verify that PVH is ignored when kernel_start is requested 558 | let loader_result = Elf::load( 559 | &gm, 560 | Some(GuestAddress(0x0020_0000)), 561 | &mut Cursor::new(&pvhnote_image), 562 | None, 563 | ) 564 | .unwrap(); 565 | assert_eq!( 566 | loader_result.pvh_boot_cap, 567 | PvhBootCapability::PvhEntryIgnored 568 | ); 569 | } 570 | 571 | #[test] 572 | fn test_dummy_elfnote() { 573 | let gm = create_guest_mem(); 574 | let dummynote_image = make_dummy_elfnote(); 575 | let loader_result = Elf::load(&gm, None, &mut Cursor::new(&dummynote_image), None).unwrap(); 576 | assert_eq!( 577 | loader_result.pvh_boot_cap, 578 | PvhBootCapability::PvhEntryNotPresent 579 | ); 580 | } 581 | 582 | #[test] 583 | fn test_bad_elfnote() { 584 | let gm = create_guest_mem(); 585 | let badnote_image = make_invalid_pvh_note(); 586 | assert_eq!( 587 | Some(KernelLoaderError::Elf(Error::InvalidPvhNote)), 588 | Elf::load(&gm, None, &mut Cursor::new(&badnote_image), None).err() 589 | ); 590 | } 591 | 592 | #[test] 593 | fn test_load_pvh_with_align() { 594 | // Alignment of ELF notes is always const value (4-bytes), ELF notes parse should not get Align 595 | // error. 596 | { 597 | let gm = 598 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (0x1000_0000_usize))]).unwrap(); 599 | let bad_align_image = make_elfnote_bad_align(); 600 | assert_ne!( 601 | Some(KernelLoaderError::Elf(Error::Align)), 602 | Elf::load(&gm, None, &mut Cursor::new(&bad_align_image), None).err() 603 | ); 604 | } 605 | 606 | // Alignment of ELF notes is always const value (4-byte), ELF notes parse should always 607 | // success even there is incorrect p_align in phdr. 608 | { 609 | let gm = create_guest_mem(); 610 | let pvhnote_image = make_elfnote_8byte_align(); 611 | let loader_result = 612 | Elf::load(&gm, None, &mut Cursor::new(&pvhnote_image), None).unwrap(); 613 | assert_eq!( 614 | loader_result.pvh_boot_cap, 615 | PvhBootCapability::PvhEntryPresent(GuestAddress(0x1e1fe1f)) 616 | ); 617 | } 618 | } 619 | 620 | #[test] 621 | fn test_overflow_loadaddr() { 622 | let gm = create_guest_mem(); 623 | let image = make_elf_bin(); 624 | assert_eq!( 625 | Some(KernelLoaderError::Elf(Error::Overflow)), 626 | Elf::load( 627 | &gm, 628 | Some(GuestAddress(u64::MAX)), 629 | &mut Cursor::new(&image), 630 | None 631 | ) 632 | .err() 633 | ); 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /src/configurator/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2020, Oracle and/or its affiliates. 2 | // 3 | // Copyright (c) 2019 Intel Corporation. All rights reserved. 4 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // 6 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 7 | // Use of this source code is governed by a BSD-style license that can be 8 | // found in the LICENSE-BSD-3-Clause file. 9 | // 10 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause 11 | 12 | //! Traits and structs for configuring and loading boot parameters. 13 | //! - [BootConfigurator](trait.BootConfigurator.html): configure boot parameters. 14 | //! - [LinuxBootConfigurator](linux/struct.LinuxBootConfigurator.html): Linux boot protocol 15 | //! parameters configurator. 16 | //! - [PvhBootConfigurator](pvh/struct.PvhBootConfigurator.html): PVH boot protocol parameters 17 | //! configurator. 18 | 19 | #![cfg(any(feature = "elf", feature = "pe", feature = "bzimage"))] 20 | 21 | use vm_memory::{Address, ByteValued, GuestAddress, GuestMemory}; 22 | 23 | use std::fmt; 24 | 25 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 26 | mod x86_64; 27 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 28 | pub use x86_64::*; 29 | 30 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 31 | pub mod fdt; 32 | 33 | use std::cmp::max; 34 | use std::mem::size_of; 35 | 36 | /// Errors specific to boot protocol configuration. 37 | #[derive(Debug, PartialEq, Eq)] 38 | pub enum Error { 39 | /// Errors specific to the Linux boot protocol configuration. 40 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 41 | Linux(linux::Error), 42 | /// Errors specific to the PVH boot protocol configuration. 43 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 44 | Pvh(pvh::Error), 45 | /// Errors specific to device tree boot configuration. 46 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 47 | Fdt(fdt::Error), 48 | 49 | /// Boot parameter was specified without its starting address in guest memory. 50 | MissingStartAddress, 51 | /// Boot parameter address overflows. 52 | Overflow, 53 | /// Boot parameter address precedes the starting address. 54 | InvalidAddress, 55 | } 56 | 57 | impl fmt::Display for Error { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 59 | use Error::*; 60 | let desc = match self { 61 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 62 | Linux(ref _e) => "failed to configure boot parameter by Linux Boot protocol.", 63 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 64 | Pvh(ref _e) => "failed to configure boot parameter by PVH.", 65 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 66 | Fdt(ref _e) => "failed to configure boot parameter by FDT.", 67 | 68 | MissingStartAddress => { 69 | "boot parameter was specified without its starting address in guest memory." 70 | } 71 | Overflow => "boot parameter address overflows.", 72 | InvalidAddress => "boot parameter address precedes the starting address.", 73 | }; 74 | 75 | write!(f, "Boot Configurator: {}", desc) 76 | } 77 | } 78 | 79 | impl std::error::Error for Error { 80 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 81 | use Error::*; 82 | match self { 83 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 84 | Linux(ref e) => Some(e), 85 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 86 | Pvh(ref e) => Some(e), 87 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 88 | Fdt(ref e) => Some(e), 89 | 90 | MissingStartAddress => None, 91 | Overflow => None, 92 | InvalidAddress => None, 93 | } 94 | } 95 | } 96 | 97 | /// Specialized [`Result`] type for the boot configurator. 98 | /// 99 | /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html 100 | pub type Result = std::result::Result; 101 | 102 | /// Trait that defines interfaces for building (TBD) and configuring boot parameters. 103 | /// 104 | /// Currently, this trait exposes a single function which writes user-provided boot parameters into 105 | /// guest memory at the user-specified addresses. It's meant to be called after the kernel is 106 | /// loaded and after the boot parameters are built externally (in the VMM). 107 | /// 108 | /// This trait will be extended with additional functionality to build boot parameters. 109 | pub trait BootConfigurator { 110 | /// Writes the boot parameters (configured elsewhere) into guest memory. 111 | /// 112 | /// The arguments are split into `header` and `sections` to accommodate different boot 113 | /// protocols like Linux boot and PVH. In Linux boot, the e820 map could be considered as 114 | /// `sections`, but it's already encapsulated in the `boot_params` and thus all the boot 115 | /// parameters are passed through a single struct. In PVH, the memory map table is separated 116 | /// from the `hvm_start_info` struct, therefore it's passed separately. 117 | /// 118 | /// # Arguments 119 | /// 120 | /// * `params` - struct containing the header section of the boot parameters, additional 121 | /// sections and modules, and their associated addresses in guest memory. These 122 | /// vary with the boot protocol used. 123 | /// * `guest_memory` - guest's physical memory. 124 | fn write_bootparams(params: &BootParams, guest_memory: &M) -> Result<()> 125 | where 126 | M: GuestMemory; 127 | } 128 | 129 | /// Boot parameters to be written in guest memory. 130 | #[derive(Clone)] 131 | pub struct BootParams { 132 | /// "Header section", always written in guest memory irrespective of boot protocol. 133 | pub header: Vec, 134 | /// Header section address. 135 | pub header_start: GuestAddress, 136 | /// Optional sections containing boot configurations (e.g. E820 map). 137 | pub sections: Option>, 138 | /// Sections starting address. 139 | pub sections_start: Option, 140 | /// Optional modules specified at boot configuration time. 141 | pub modules: Option>, 142 | /// Modules starting address. 143 | pub modules_start: Option, 144 | } 145 | 146 | impl BootParams { 147 | /// Creates a new [`BootParams`](struct.BootParams.html) struct with the specified header. 148 | /// 149 | /// # Arguments 150 | /// 151 | /// * `header` - [`ByteValued`] representation of mandatory boot parameters. 152 | /// * `header_addr` - address in guest memory where `header` will be written. 153 | /// 154 | /// # Examples 155 | /// 156 | /// ```rust 157 | /// # use linux_loader::configurator::BootParams; 158 | /// # use vm_memory::{GuestAddress, ByteValued}; 159 | /// # #[derive(Clone, Copy, Default)] 160 | /// # struct Header; 161 | /// # unsafe impl ByteValued for Header {} 162 | /// let boot_params = BootParams::new(&Header::default(), GuestAddress(0x1000)); 163 | /// ``` 164 | /// 165 | /// [`ByteValued`]: https://docs.rs/vm-memory/latest/vm_memory/bytes/trait.ByteValued.html 166 | pub fn new(header: &T, header_addr: GuestAddress) -> Self { 167 | BootParams { 168 | header: header.as_slice().to_vec(), 169 | header_start: header_addr, 170 | sections: None, 171 | sections_start: None, 172 | modules: None, 173 | modules_start: None, 174 | } 175 | } 176 | 177 | /// Sets or overwrites the boot sections and associated memory address. 178 | /// 179 | /// Unused on `aarch64` and `riscv64` for the Linux boot protocol. 180 | /// For the PVH boot protocol, the sections specify the memory map table in 181 | /// [`hvm_memmap_table_entry`] structs. 182 | /// 183 | /// # Arguments 184 | /// 185 | /// * `sections` - vector of [`ByteValued`] boot configurations. 186 | /// * `sections_addr` - address where the sections will be written in guest memory. 187 | /// 188 | /// # Examples 189 | /// 190 | /// ```rust 191 | /// # use linux_loader::configurator::BootParams; 192 | /// # use vm_memory::{ByteValued, GuestAddress}; 193 | /// # #[derive(Clone, Copy, Default)] 194 | /// # struct Header; 195 | /// # unsafe impl ByteValued for Header {} 196 | /// # #[derive(Clone, Copy, Default)] 197 | /// # struct Section; 198 | /// # unsafe impl ByteValued for Section {} 199 | /// let mut boot_params = BootParams::new(&Header::default(), GuestAddress(0x1000)); 200 | /// let mut sections: Vec
= vec![Section::default()]; 201 | /// boot_params.set_sections(sections.as_slice(), GuestAddress(0x2000)); 202 | /// // Another call overwrites the sections. 203 | /// sections.clear(); 204 | /// boot_params.set_sections(sections.as_slice(), GuestAddress(0x3000)); 205 | /// assert_eq!(boot_params.sections.unwrap().len(), 0); 206 | /// assert_eq!(boot_params.sections_start.unwrap(), GuestAddress(0x3000)); 207 | /// ``` 208 | /// 209 | /// [`ByteValued`]: https://docs.rs/vm-memory/latest/vm_memory/bytes/trait.ByteValued.html 210 | /// [`hvm_memmap_table_entry`]: ../loader/elf/start_info/struct.hvm_memmap_table_entry.html 211 | pub fn set_sections(&mut self, sections: &[T], sections_addr: GuestAddress) { 212 | self.sections = Some( 213 | sections 214 | .iter() 215 | .flat_map(|section| section.as_slice().to_vec()) 216 | .collect(), 217 | ); 218 | self.sections_start = Some(sections_addr); 219 | } 220 | 221 | /// Adds a boot section at the specified address (if specified and valid), or appends it. 222 | /// 223 | /// It's up to the caller to ensure that the section will not overlap with existing content 224 | /// or leave a gap past the current sections in the list. 225 | /// 226 | /// # Arguments 227 | /// 228 | /// * `section` - [`ByteValued`] boot section element. 229 | /// * `section_addr` - optional address for the section in guest memory. 230 | /// 231 | /// # Returns 232 | /// 233 | /// Starting address of the section in guest memory, or an error. 234 | /// 235 | /// # Examples 236 | /// 237 | /// ```rust 238 | /// # use linux_loader::configurator::BootParams; 239 | /// # use vm_memory::{Address, GuestAddress, ByteValued}; 240 | /// # use std::mem::size_of; 241 | /// # #[derive(Clone, Copy, Default)] 242 | /// # struct Header; 243 | /// # unsafe impl ByteValued for Header {} 244 | /// # #[derive(Clone, Copy, Default)] 245 | /// # struct Section; 246 | /// # unsafe impl ByteValued for Section {} 247 | /// let mut boot_params = BootParams::new(&Header::default(), GuestAddress(0x1000)); 248 | /// let section = Section::default(); 249 | /// // Sections start address needs to be configured first. 250 | /// assert!(boot_params.add_section::
(§ion, None).is_err()); 251 | /// let sections_start = GuestAddress(0x2000); 252 | /// assert!(boot_params 253 | /// .add_section::
(§ion, Some(sections_start)) 254 | /// .is_ok()); 255 | /// // It can be overwritten... 256 | /// assert_eq!( 257 | /// boot_params 258 | /// .add_section::
(§ion, Some(sections_start)) 259 | /// .unwrap(), 260 | /// sections_start 261 | /// ); 262 | /// // But only if the address is valid. 263 | /// assert!(boot_params 264 | /// .add_section::
(§ion, Some(sections_start.unchecked_sub(0x100))) 265 | /// .is_err()); 266 | /// // Or appended... 267 | /// assert_eq!( 268 | /// boot_params.add_section::
(§ion, None).unwrap(), 269 | /// sections_start.unchecked_add(size_of::
() as u64) 270 | /// ); 271 | /// ``` 272 | /// 273 | /// [`ByteValued`]: https://docs.rs/vm-memory/latest/vm_memory/bytes/trait.ByteValued.html 274 | pub fn add_section( 275 | &mut self, 276 | section: &T, 277 | section_addr: Option, 278 | ) -> Result { 279 | Self::add_boot_parameter_to_list( 280 | section, 281 | section_addr, 282 | self.sections.get_or_insert(vec![]), 283 | &mut self.sections_start, 284 | ) 285 | } 286 | 287 | /// Sets or overwrites the boot modules and associated memory address. 288 | /// 289 | /// Unused on `aarch64` and `riscv64` for the Linux boot protocol. 290 | /// For the PVH boot protocol, the modules are specified in [`hvm_modlist_entry`] structs. 291 | /// 292 | /// # Arguments 293 | /// 294 | /// * `modules` - vector of [`ByteValued`] boot configurations. 295 | /// * `modules_addr` - address where the modules will be written in guest memory. 296 | /// 297 | /// # Examples 298 | /// 299 | /// ```rust 300 | /// # use linux_loader::configurator::BootParams; 301 | /// # #[derive(Clone, Copy, Default)] 302 | /// # struct Header; 303 | /// # unsafe impl ByteValued for Header {} 304 | /// # #[derive(Clone, Copy, Default)] 305 | /// # struct Module; 306 | /// # unsafe impl ByteValued for Module {} 307 | /// # use vm_memory::{GuestAddress, ByteValued}; 308 | /// let mut boot_params = BootParams::new(&Header::default(), GuestAddress(0x1000)); 309 | /// let mut modules: Vec = vec![Module::default()]; 310 | /// boot_params.set_modules(modules.as_slice(), GuestAddress(0x2000)); 311 | /// // Another call overwrites the sections. 312 | /// modules.clear(); 313 | /// boot_params.set_modules(modules.as_slice(), GuestAddress(0x3000)); 314 | /// assert_eq!(boot_params.modules.unwrap().len(), 0); 315 | /// assert_eq!(boot_params.modules_start.unwrap(), GuestAddress(0x3000)); 316 | /// ``` 317 | /// 318 | /// [`ByteValued`]: https://docs.rs/vm-memory/latest/vm_memory/bytes/trait.ByteValued.html 319 | /// [`hvm_modlist_entry`]: ../loader/elf/start_info/struct.hvm_modlist_entry.html 320 | pub fn set_modules(&mut self, modules: &[T], modules_addr: GuestAddress) { 321 | self.modules = Some( 322 | modules 323 | .iter() 324 | .flat_map(|module| module.as_slice().to_vec()) 325 | .collect(), 326 | ); 327 | self.modules_start = Some(modules_addr); 328 | } 329 | 330 | /// Adds a boot module at the specified address (if specified and valid), or appends it. 331 | /// 332 | /// It's up to the caller to ensure that the module will not overlap with existing content 333 | /// or leave a gap past the current modules in the list. 334 | /// 335 | /// # Arguments 336 | /// 337 | /// * `module` - [`ByteValued`] boot module element. 338 | /// * `module_addr` - optional address for the module in guest memory. 339 | /// 340 | /// # Returns 341 | /// 342 | /// Starting address of the module in guest memory, or an error. 343 | /// 344 | /// # Examples 345 | /// 346 | /// ```rust 347 | /// # use linux_loader::configurator::BootParams; 348 | /// # use vm_memory::{Address, GuestAddress, ByteValued}; 349 | /// # use std::mem::size_of; 350 | /// # #[derive(Clone, Copy, Default)] 351 | /// # struct Header; 352 | /// # unsafe impl ByteValued for Header {} 353 | /// # #[derive(Clone, Copy, Default)] 354 | /// # struct Module; 355 | /// # unsafe impl ByteValued for Module {} 356 | /// let mut boot_params = BootParams::new(&Header::default(), GuestAddress(0x1000)); 357 | /// let module = Module::default(); 358 | /// // Modules start address needs to be configured first. 359 | /// assert!(boot_params.add_module::(&module, None).is_err()); 360 | /// let modules_start = GuestAddress(0x2000); 361 | /// assert!(boot_params 362 | /// .add_module::(&module, Some(modules_start)) 363 | /// .is_ok()); 364 | /// // It can be overwritten... 365 | /// assert_eq!( 366 | /// boot_params 367 | /// .add_module::(&module, Some(modules_start)) 368 | /// .unwrap(), 369 | /// modules_start 370 | /// ); 371 | /// // But only if the address is valid. 372 | /// assert!(boot_params 373 | /// .add_module::(&module, Some(modules_start.unchecked_sub(0x100))) 374 | /// .is_err()); 375 | /// // Or appended... 376 | /// assert_eq!( 377 | /// boot_params.add_module::(&module, None).unwrap(), 378 | /// modules_start.unchecked_add(size_of::() as u64) 379 | /// ); 380 | /// ``` 381 | /// 382 | /// [`ByteValued`]: https://docs.rs/vm-memory/latest/vm_memory/bytes/trait.ByteValued.html 383 | pub fn add_module( 384 | &mut self, 385 | module: &T, 386 | module_addr: Option, 387 | ) -> Result { 388 | Self::add_boot_parameter_to_list( 389 | module, 390 | module_addr, 391 | self.modules.get_or_insert(vec![]), 392 | &mut self.modules_start, 393 | ) 394 | } 395 | 396 | /// Adds a boot parameter (section or module) to a byte buffer. 397 | /// 398 | /// Initializes the buffer and corresponding starting address, if necessary. 399 | fn add_boot_parameter_to_list( 400 | elem: &T, 401 | elem_start_opt: Option, 402 | bytes_acc: &mut Vec, 403 | list_start_opt: &mut Option, 404 | ) -> Result { 405 | if list_start_opt.is_none() { 406 | *list_start_opt = elem_start_opt; 407 | } 408 | let list_start = list_start_opt.ok_or(Error::MissingStartAddress)?; 409 | let elem_start = elem_start_opt.unwrap_or( 410 | list_start 411 | .checked_add(bytes_acc.len() as u64) 412 | .ok_or(Error::Overflow)?, 413 | ); 414 | let elem_off = elem_start 415 | .checked_offset_from(list_start) 416 | .ok_or(Error::InvalidAddress)? as usize; 417 | let elem_end = elem_off + size_of::(); 418 | bytes_acc.resize(max(elem_end, bytes_acc.len()), 0); 419 | bytes_acc.splice(elem_off..elem_end, elem.as_slice().iter().cloned()); 420 | Ok(elem_start) 421 | } 422 | } 423 | 424 | #[cfg(test)] 425 | mod tests { 426 | #![allow(clippy::undocumented_unsafe_blocks)] 427 | use super::*; 428 | 429 | #[derive(Clone, Copy, Default)] 430 | struct Foobar { 431 | _foo: [u8; 5], 432 | } 433 | 434 | unsafe impl ByteValued for Foobar {} 435 | 436 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 437 | struct DummyHeader { 438 | _dummy: u64, 439 | } 440 | 441 | unsafe impl ByteValued for DummyHeader {} 442 | 443 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 444 | struct DummySection { 445 | _dummy: u64, 446 | } 447 | 448 | unsafe impl ByteValued for DummySection {} 449 | 450 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 451 | struct DummyModule { 452 | _dummy: u64, 453 | } 454 | 455 | unsafe impl ByteValued for DummyModule {} 456 | 457 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 458 | struct OtherDummyModule { 459 | _dummy: u64, 460 | } 461 | 462 | unsafe impl ByteValued for OtherDummyModule {} 463 | 464 | #[test] 465 | fn test_error_messages() { 466 | #[cfg(target_arch = "x86_64")] 467 | { 468 | // Linux 469 | assert_eq!( 470 | format!("{}", Error::Linux(linux::Error::ZeroPagePastRamEnd)), 471 | "Boot Configurator: failed to configure boot parameter by Linux Boot protocol." 472 | ); 473 | assert_eq!( 474 | format!("{}", Error::Linux(linux::Error::ZeroPageSetup)), 475 | "Boot Configurator: failed to configure boot parameter by Linux Boot protocol." 476 | ); 477 | 478 | // PVH 479 | assert_eq!( 480 | format!("{}", Error::Pvh(pvh::Error::MemmapTableMissing)), 481 | "Boot Configurator: failed to configure boot parameter by PVH." 482 | ); 483 | assert_eq!( 484 | format!("{}", Error::Pvh(pvh::Error::MemmapTablePastRamEnd)), 485 | "Boot Configurator: failed to configure boot parameter by PVH." 486 | ); 487 | assert_eq!( 488 | format!("{}", Error::Pvh(pvh::Error::MemmapTableSetup)), 489 | "Boot Configurator: failed to configure boot parameter by PVH." 490 | ); 491 | assert_eq!( 492 | format!("{}", Error::Pvh(pvh::Error::StartInfoPastRamEnd)), 493 | "Boot Configurator: failed to configure boot parameter by PVH." 494 | ); 495 | assert_eq!( 496 | format!("{}", Error::Pvh(pvh::Error::StartInfoSetup)), 497 | "Boot Configurator: failed to configure boot parameter by PVH." 498 | ); 499 | } 500 | 501 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 502 | // FDT 503 | assert_eq!( 504 | format!("{}", Error::Fdt(fdt::Error::WriteFDTToMemory)), 505 | "Boot Configurator: failed to configure boot parameter by FDT." 506 | ); 507 | 508 | assert_eq!( 509 | format!("{}", Error::MissingStartAddress), 510 | "Boot Configurator: \ 511 | boot parameter was specified without its starting address in guest memory." 512 | ); 513 | assert_eq!( 514 | format!("{}", Error::Overflow), 515 | "Boot Configurator: boot parameter address overflows." 516 | ); 517 | assert_eq!( 518 | format!("{}", Error::InvalidAddress), 519 | "Boot Configurator: boot parameter address precedes the starting address." 520 | ); 521 | } 522 | 523 | #[test] 524 | fn test_bootparam_list_addition() { 525 | let mut accumulator: Vec = vec![]; 526 | let start = GuestAddress(0x1000); 527 | let element = Foobar::default(); 528 | 529 | // Error case: start address not specified. 530 | assert_eq!( 531 | format!( 532 | "{:?}", 533 | BootParams::add_boot_parameter_to_list(&element, None, &mut accumulator, &mut None) 534 | .err() 535 | ), 536 | "Some(MissingStartAddress)" 537 | ); 538 | 539 | // Success case: start address is set, element address not specified - will be appended. 540 | assert_eq!( 541 | BootParams::add_boot_parameter_to_list( 542 | &element, 543 | None, 544 | &mut accumulator, 545 | &mut Some(start) 546 | ) 547 | .unwrap(), 548 | start 549 | ); 550 | assert_eq!(accumulator, element.as_slice().to_vec()); 551 | 552 | // Success case: start address is unset, element address is specified. 553 | let mut list_start_opt: Option = None; 554 | assert_eq!( 555 | BootParams::add_boot_parameter_to_list( 556 | &element, 557 | Some(start), 558 | &mut accumulator, 559 | &mut list_start_opt 560 | ) 561 | .unwrap(), 562 | start 563 | ); 564 | assert_eq!(list_start_opt, Some(start)); 565 | assert_eq!(accumulator, element.as_slice().to_vec()); 566 | 567 | // Error case: start address is set, element address is specified, but precedes start. 568 | assert_eq!( 569 | format!( 570 | "{:?}", 571 | BootParams::add_boot_parameter_to_list( 572 | &element, 573 | Some(start.unchecked_sub(0x100)), 574 | &mut accumulator, 575 | &mut list_start_opt 576 | ) 577 | .err() 578 | ), 579 | "Some(InvalidAddress)" 580 | ); 581 | 582 | // Success case: start address is set, element address is specified and valid. 583 | 584 | // Case 1: element falls in the middle of the accumulator. 585 | accumulator.clear(); 586 | // Start by adding 2 elements. 587 | assert!(BootParams::add_boot_parameter_to_list( 588 | &element, 589 | None, 590 | &mut accumulator, 591 | &mut list_start_opt 592 | ) 593 | .is_ok()); 594 | assert!(BootParams::add_boot_parameter_to_list( 595 | &Foobar { 596 | _foo: [2, 2, 2, 3, 3] 597 | }, 598 | None, 599 | &mut accumulator, 600 | &mut list_start_opt 601 | ) 602 | .is_ok()); 603 | // Sanity check. 604 | #[rustfmt::skip] 605 | assert_eq!( 606 | accumulator, 607 | &[ 608 | 0, 0, 0, 0, 0, // elem 0 609 | 2, 2, 2, 3, 3, // elem 1 610 | ] 611 | ); 612 | 613 | // Add a 3rd one that overlaps with the middle of element 1. 614 | assert!(BootParams::add_boot_parameter_to_list( 615 | &Foobar { _foo: [1u8; 5] }, 616 | Some(start.unchecked_add(size_of::() as u64 + 3)), 617 | &mut accumulator, 618 | &mut list_start_opt 619 | ) 620 | .is_ok()); 621 | #[rustfmt::skip] 622 | assert_eq!( 623 | accumulator, 624 | &[ 625 | 0, 0, 0, 0, 0, // elem 0 626 | 2, 2, 2, // elem 1 cut short 627 | 1, 1, 1, 1, 1, // elem 2 628 | ] 629 | ); 630 | assert_eq!(accumulator.len(), 13) 631 | } 632 | 633 | #[test] 634 | fn test_bootparams() { 635 | // Test building bootparams from header. 636 | let hdr = DummyHeader::default(); 637 | let hdr_addr = GuestAddress(0x1000); 638 | let mut bootparams = BootParams::new(&hdr, hdr_addr); 639 | assert_eq!(bootparams.header, hdr.as_slice()); 640 | assert_eq!(bootparams.header_start, hdr_addr); 641 | 642 | // Test setting sections. 643 | let sections = vec![DummySection::default(); 2]; 644 | let sections_addr = GuestAddress(0x2000); 645 | bootparams.set_sections::(sections.as_slice(), sections_addr); 646 | assert_eq!( 647 | bootparams.sections, 648 | Some(vec![0u8; 2 * size_of::()]) 649 | ); 650 | assert_eq!(bootparams.sections_start, Some(sections_addr)); 651 | 652 | // Test overwriting sections. 653 | let sections = vec![DummySection::default(); 3]; 654 | let sections_addr = GuestAddress(0x3000); 655 | bootparams.set_sections::(sections.as_slice(), sections_addr); 656 | assert_eq!( 657 | bootparams.sections, 658 | Some(vec![0u8; 3 * size_of::()]) 659 | ); 660 | assert_eq!(bootparams.sections_start, Some(sections_addr)); 661 | 662 | // Test appending a new section. 663 | assert_eq!( 664 | bootparams.add_section::(&DummySection::default(), None), 665 | Ok(sections_addr.unchecked_add(3 * size_of::() as u64)) 666 | ); 667 | assert_eq!( 668 | bootparams.sections, 669 | Some(vec![0u8; 4 * size_of::()]) 670 | ); 671 | assert_eq!(bootparams.sections_start, Some(sections_addr)); 672 | 673 | // Test setting modules. 674 | let modules = vec![DummyModule::default(); 2]; 675 | let modules_addr = GuestAddress(0x4000); 676 | bootparams.set_modules::(modules.as_slice(), modules_addr); 677 | assert_eq!( 678 | bootparams.modules, 679 | Some(vec![0u8; 2 * size_of::()]) 680 | ); 681 | assert_eq!(bootparams.modules_start, Some(modules_addr)); 682 | 683 | // Test overwriting modules. 684 | let modules = vec![DummyModule::default(); 3]; 685 | let modules_addr = GuestAddress(0x5000); 686 | bootparams.set_modules::(modules.as_slice(), modules_addr); 687 | assert_eq!( 688 | bootparams.modules, 689 | Some(vec![0u8; 3 * size_of::()]) 690 | ); 691 | assert_eq!(bootparams.modules_start, Some(modules_addr)); 692 | 693 | // Test appending a new module. 694 | assert_eq!( 695 | bootparams.add_module::(&DummyModule::default(), None), 696 | Ok(modules_addr.unchecked_add(3 * size_of::() as u64)) 697 | ); 698 | 699 | // Test appending a new module of a different type. 700 | assert_eq!( 701 | bootparams.add_module::(&OtherDummyModule::default(), None), 702 | Ok(modules_addr.unchecked_add( 703 | 3 * size_of::() as u64 + size_of::() as u64 704 | )) 705 | ); 706 | } 707 | } 708 | --------------------------------------------------------------------------------