xref: /aosp_15_r20/external/crosvm/x86_64/src/bzimage.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2019 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Loader for bzImage-format Linux kernels as described in
6 //! <https://www.kernel.org/doc/Documentation/x86/boot.txt>
7 
8 use std::cmp::Ordering;
9 use std::io;
10 use std::mem::offset_of;
11 
12 use base::debug;
13 use base::FileGetLen;
14 use base::FileReadWriteAtVolatile;
15 use base::VolatileSlice;
16 use remain::sorted;
17 use thiserror::Error;
18 use vm_memory::GuestAddress;
19 use vm_memory::GuestMemory;
20 use vm_memory::GuestMemoryError;
21 use zerocopy::AsBytes;
22 
23 use crate::bootparam::boot_params;
24 use crate::bootparam::XLF_KERNEL_64;
25 use crate::CpuMode;
26 use crate::KERNEL_32BIT_ENTRY_OFFSET;
27 use crate::KERNEL_64BIT_ENTRY_OFFSET;
28 
29 #[sorted]
30 #[derive(Error, Debug)]
31 pub enum Error {
32     #[error("bad kernel header signature")]
33     BadSignature,
34     #[error("entry point out of range")]
35     EntryPointOutOfRange,
36     #[error("unable to get kernel file size: {0}")]
37     GetFileLen(io::Error),
38     #[error("guest memory error {0}")]
39     GuestMemoryError(GuestMemoryError),
40     #[error("invalid setup_header_end value {0}")]
41     InvalidSetupHeaderEnd(usize),
42     #[error("invalid setup_sects value {0}")]
43     InvalidSetupSects(u8),
44     #[error("invalid syssize value {0}")]
45     InvalidSysSize(u32),
46     #[error("unable to read boot_params: {0}")]
47     ReadBootParams(io::Error),
48     #[error("unable to read header size: {0}")]
49     ReadHeaderSize(io::Error),
50     #[error("unable to read kernel image: {0}")]
51     ReadKernelImage(io::Error),
52 }
53 
54 pub type Result<T> = std::result::Result<T, Error>;
55 
56 /// Loads a kernel from a bzImage to a slice
57 ///
58 /// # Arguments
59 ///
60 /// * `guest_mem` - The guest memory region the kernel is written to.
61 /// * `kernel_start` - The offset into `guest_mem` at which to load the kernel.
62 /// * `kernel_image` - Input bzImage.
load_bzimage<F>( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, ) -> Result<(boot_params, u64, GuestAddress, CpuMode)> where F: FileReadWriteAtVolatile + FileGetLen,63 pub fn load_bzimage<F>(
64     guest_mem: &GuestMemory,
65     kernel_start: GuestAddress,
66     kernel_image: &mut F,
67 ) -> Result<(boot_params, u64, GuestAddress, CpuMode)>
68 where
69     F: FileReadWriteAtVolatile + FileGetLen,
70 {
71     let mut params = boot_params::default();
72 
73     // The start of setup header is defined by its offset within boot_params (0x01f1).
74     let setup_header_start = offset_of!(boot_params, hdr);
75 
76     // Per x86 Linux 64-bit boot protocol:
77     // "The end of setup header can be calculated as follows: 0x0202 + byte value at offset 0x0201"
78     let mut setup_size_byte = 0u8;
79     kernel_image
80         .read_exact_at_volatile(
81             VolatileSlice::new(std::slice::from_mut(&mut setup_size_byte)),
82             0x0201,
83         )
84         .map_err(Error::ReadHeaderSize)?;
85     let setup_header_end = 0x0202 + usize::from(setup_size_byte);
86 
87     debug!(
88         "setup_header file offset range: 0x{:04x}..0x{:04x}",
89         setup_header_start, setup_header_end,
90     );
91 
92     // Read `setup_header` into `boot_params`. The bzImage may have a different size of
93     // `setup_header`, so read directly into a byte slice of the outer `boot_params` structure
94     // rather than reading into `params.hdr`. The bounds check in `.get_mut()` will ensure we do not
95     // read beyond the end of `boot_params`.
96     let setup_header_slice = params
97         .as_bytes_mut()
98         .get_mut(setup_header_start..setup_header_end)
99         .ok_or(Error::InvalidSetupHeaderEnd(setup_header_end))?;
100 
101     kernel_image
102         .read_exact_at_volatile(
103             VolatileSlice::new(setup_header_slice),
104             setup_header_start as u64,
105         )
106         .map_err(Error::ReadBootParams)?;
107 
108     // bzImage header signature "HdrS"
109     if params.hdr.header != 0x53726448 {
110         return Err(Error::BadSignature);
111     }
112 
113     let setup_sects = if params.hdr.setup_sects == 0 {
114         4u64
115     } else {
116         params.hdr.setup_sects as u64
117     };
118 
119     let kernel_offset = setup_sects
120         .checked_add(1)
121         .and_then(|sectors| sectors.checked_mul(512))
122         .ok_or(Error::InvalidSetupSects(params.hdr.setup_sects))?;
123     let kernel_size = (params.hdr.syssize as usize)
124         .checked_mul(16)
125         .ok_or(Error::InvalidSysSize(params.hdr.syssize))?;
126 
127     let file_size = kernel_image.get_len().map_err(Error::GetFileLen)?;
128     let load_size = file_size
129         .checked_sub(kernel_offset)
130         .and_then(|n| usize::try_from(n).ok())
131         .ok_or(Error::InvalidSetupSects(params.hdr.setup_sects))?;
132 
133     match kernel_size.cmp(&load_size) {
134         Ordering::Greater => {
135             // `syssize` from header was larger than the actual file.
136             return Err(Error::InvalidSysSize(params.hdr.syssize));
137         }
138         Ordering::Less => {
139             debug!(
140                 "loading {} extra bytes appended to bzImage",
141                 load_size - kernel_size
142             );
143         }
144         Ordering::Equal => {}
145     }
146 
147     // Load the whole kernel image to kernel_start
148     let guest_slice = guest_mem
149         .get_slice_at_addr(kernel_start, load_size)
150         .map_err(Error::GuestMemoryError)?;
151     kernel_image
152         .read_exact_at_volatile(guest_slice, kernel_offset)
153         .map_err(Error::ReadKernelImage)?;
154 
155     let (entry_offset, cpu_mode) = if params.hdr.xloadflags & XLF_KERNEL_64 != 0 {
156         (KERNEL_64BIT_ENTRY_OFFSET, CpuMode::LongMode)
157     } else {
158         (KERNEL_32BIT_ENTRY_OFFSET, CpuMode::FlatProtectedMode)
159     };
160 
161     let bzimage_entry = guest_mem
162         .checked_offset(kernel_start, entry_offset)
163         .ok_or(Error::EntryPointOutOfRange)?;
164 
165     Ok((
166         params,
167         kernel_start.offset() + load_size as u64,
168         bzimage_entry,
169         cpu_mode,
170     ))
171 }
172