Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/kernel_loader/src/lib.rs
5394 views
1
// Copyright 2017 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
//! Linux kernel ELF file loader.
6
7
use std::mem;
8
9
use base::FileReadWriteAtVolatile;
10
use base::VolatileSlice;
11
use remain::sorted;
12
use resources::AddressRange;
13
use thiserror::Error;
14
use vm_memory::GuestAddress;
15
use vm_memory::GuestMemory;
16
use zerocopy::FromBytes;
17
use zerocopy::IntoBytes;
18
19
mod multiboot;
20
21
#[allow(dead_code)]
22
#[allow(non_camel_case_types)]
23
#[allow(non_snake_case)]
24
#[allow(non_upper_case_globals)]
25
#[allow(clippy::all)]
26
mod elf;
27
28
mod arm64;
29
30
pub use arm64::load_arm64_kernel;
31
pub use arm64::load_arm64_kernel_lz4;
32
pub use multiboot::load_multiboot;
33
pub use multiboot::multiboot_header_from_file;
34
35
#[sorted]
36
#[derive(Error, Debug, PartialEq, Eq)]
37
pub enum Error {
38
#[error("trying to load big-endian binary on little-endian machine")]
39
BigEndianOnLittle,
40
#[error("invalid elf class")]
41
InvalidElfClass,
42
#[error("invalid elf version")]
43
InvalidElfVersion,
44
#[error("invalid entry point")]
45
InvalidEntryPoint,
46
#[error("invalid flags")]
47
InvalidFlags,
48
#[error("invalid kernel offset")]
49
InvalidKernelOffset,
50
#[error("invalid kernel size")]
51
InvalidKernelSize,
52
#[error("invalid magic number")]
53
InvalidMagicNumber,
54
#[error("invalid Program Header Address")]
55
InvalidProgramHeaderAddress,
56
#[error("invalid Program Header memory size")]
57
InvalidProgramHeaderMemSize,
58
#[error("invalid program header offset")]
59
InvalidProgramHeaderOffset,
60
#[error("invalid program header size")]
61
InvalidProgramHeaderSize,
62
#[error("no loadable program headers found")]
63
NoLoadableProgramHeaders,
64
#[error("program header address out of allowed address range")]
65
ProgramHeaderAddressOutOfRange,
66
#[error("unable to read header")]
67
ReadHeader,
68
#[error("unable to read kernel image")]
69
ReadKernelImage,
70
#[error("unable to read program header")]
71
ReadProgramHeader,
72
#[error("unable to seek to kernel end")]
73
SeekKernelEnd,
74
#[error("unable to seek to kernel start")]
75
SeekKernelStart,
76
#[error("unable to seek to program header")]
77
SeekProgramHeader,
78
}
79
pub type Result<T> = std::result::Result<T, Error>;
80
81
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
82
/// Information about a kernel loaded with the [`load_elf`] function.
83
pub struct LoadedKernel {
84
/// Address range containg the bounds of the loaded program headers.
85
/// `address_range.start` is the start of the lowest loaded program header.
86
/// `address_range.end` is the end of the highest loaded program header.
87
pub address_range: AddressRange,
88
89
/// Size of the kernel image in bytes.
90
pub size: u64,
91
92
/// Entry point address of the kernel.
93
pub entry: GuestAddress,
94
95
/// ELF class or equivalent for other image formats.
96
pub class: ElfClass,
97
}
98
99
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
100
/// ELF image class (32- or 64-bit ELF)
101
pub enum ElfClass {
102
ElfClass32,
103
ElfClass64,
104
}
105
106
/// Loads a kernel from a 32-bit or 64-bit ELF image into memory.
107
///
108
/// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its
109
/// program headers.
110
///
111
/// # Arguments
112
///
113
/// * `guest_mem` - The guest memory region the kernel is written to.
114
/// * `kernel_start` - The minimum guest address to allow when loading program headers.
115
/// * `kernel_image` - Input vmlinux image.
116
/// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`).
117
pub fn load_elf<F>(
118
guest_mem: &GuestMemory,
119
kernel_start: GuestAddress,
120
kernel_image: &mut F,
121
phys_offset: u64,
122
) -> Result<LoadedKernel>
123
where
124
F: FileReadWriteAtVolatile,
125
{
126
let (elf, class) = read_elf(kernel_image)?;
127
let mut start = None;
128
let mut end = 0;
129
130
// Read in each section pointed to by the program headers.
131
for phdr in &elf.program_headers {
132
if phdr.p_type != elf::PT_LOAD {
133
continue;
134
}
135
136
let paddr = phdr
137
.p_paddr
138
.checked_add(phys_offset)
139
.ok_or(Error::ProgramHeaderAddressOutOfRange)?;
140
141
if paddr < kernel_start.offset() {
142
return Err(Error::ProgramHeaderAddressOutOfRange);
143
}
144
145
if start.is_none() {
146
start = Some(paddr);
147
}
148
149
end = paddr
150
.checked_add(phdr.p_memsz)
151
.ok_or(Error::InvalidProgramHeaderMemSize)?;
152
153
if phdr.p_filesz == 0 {
154
continue;
155
}
156
157
let guest_slice = guest_mem
158
.get_slice_at_addr(GuestAddress(paddr), phdr.p_filesz as usize)
159
.map_err(|_| Error::ReadKernelImage)?;
160
kernel_image
161
.read_exact_at_volatile(guest_slice, phdr.p_offset)
162
.map_err(|_| Error::ReadKernelImage)?;
163
}
164
165
// We should have found at least one PT_LOAD program header. If not, `start` will not be set.
166
let start = start.ok_or(Error::NoLoadableProgramHeaders)?;
167
168
let size = end
169
.checked_sub(start)
170
.ok_or(Error::InvalidProgramHeaderSize)?;
171
172
let address_range =
173
AddressRange::from_start_and_size(start, size).ok_or(Error::InvalidProgramHeaderSize)?;
174
175
// The entry point address must fall within one of the loaded sections.
176
// We approximate this by checking whether it within the bounds of the first and last sections.
177
let entry = elf
178
.file_header
179
.e_entry
180
.checked_add(phys_offset)
181
.ok_or(Error::InvalidEntryPoint)?;
182
if !address_range.contains(entry) {
183
return Err(Error::InvalidEntryPoint);
184
}
185
186
Ok(LoadedKernel {
187
address_range,
188
size,
189
entry: GuestAddress(entry),
190
class,
191
})
192
}
193
194
struct Elf64 {
195
file_header: elf::Elf64_Ehdr,
196
program_headers: Vec<elf::Elf64_Phdr>,
197
}
198
199
/// Reads the headers of an ELF32 or ELF64 object file. Returns ELF file and program headers,
200
/// converted to ELF64 format as a single internal representation that can handle either 32- or
201
/// 64-bit images, and the ELF class of the original image.
202
fn read_elf<F>(file: &mut F) -> Result<(Elf64, ElfClass)>
203
where
204
F: FileReadWriteAtVolatile,
205
{
206
// Read the ELF identification (e_ident) block.
207
let mut ident = [0u8; 16];
208
file.read_exact_at_volatile(VolatileSlice::new(&mut ident), 0)
209
.map_err(|_| Error::ReadHeader)?;
210
211
// e_ident checks
212
if ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8
213
|| ident[elf::EI_MAG1 as usize] != elf::ELFMAG1
214
|| ident[elf::EI_MAG2 as usize] != elf::ELFMAG2
215
|| ident[elf::EI_MAG3 as usize] != elf::ELFMAG3
216
{
217
return Err(Error::InvalidMagicNumber);
218
}
219
if ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 {
220
return Err(Error::BigEndianOnLittle);
221
}
222
if ident[elf::EI_VERSION as usize] != elf::EV_CURRENT as u8 {
223
return Err(Error::InvalidElfVersion);
224
}
225
226
let ei_class = ident[elf::EI_CLASS as usize] as u32;
227
match ei_class {
228
elf::ELFCLASS32 => Ok((
229
read_elf_by_type::<_, elf::Elf32_Ehdr, elf::Elf32_Phdr>(file)?,
230
ElfClass::ElfClass32,
231
)),
232
elf::ELFCLASS64 => Ok((
233
read_elf_by_type::<_, elf::Elf64_Ehdr, elf::Elf64_Phdr>(file)?,
234
ElfClass::ElfClass64,
235
)),
236
_ => Err(Error::InvalidElfClass),
237
}
238
}
239
240
/// Reads the headers of an ELF32 or ELF64 object file. Returns ELF file and program headers,
241
/// converted to ELF64 format. `FileHeader` and `ProgramHeader` are the ELF32 or ELF64 ehdr/phdr
242
/// types to read from the file. Caller should check that `file` is a valid ELF file before calling
243
/// this function.
244
fn read_elf_by_type<F, FileHeader, ProgramHeader>(file: &mut F) -> Result<Elf64>
245
where
246
F: FileReadWriteAtVolatile,
247
FileHeader: IntoBytes + FromBytes + Default + Into<elf::Elf64_Ehdr>,
248
ProgramHeader: IntoBytes + FromBytes + Clone + Default + Into<elf::Elf64_Phdr>,
249
{
250
let mut ehdr = FileHeader::new_zeroed();
251
file.read_exact_at_volatile(VolatileSlice::new(ehdr.as_mut_bytes()), 0)
252
.map_err(|_| Error::ReadHeader)?;
253
let ehdr: elf::Elf64_Ehdr = ehdr.into();
254
255
if ehdr.e_phentsize as usize != mem::size_of::<ProgramHeader>() {
256
return Err(Error::InvalidProgramHeaderSize);
257
}
258
if (ehdr.e_phoff as usize) < mem::size_of::<FileHeader>() {
259
// If the program header is backwards, bail.
260
return Err(Error::InvalidProgramHeaderOffset);
261
}
262
263
let num_phdrs = ehdr.e_phnum as usize;
264
let mut phdrs = vec![ProgramHeader::default(); num_phdrs];
265
file.read_exact_at_volatile(VolatileSlice::new(phdrs.as_mut_bytes()), ehdr.e_phoff)
266
.map_err(|_| Error::ReadProgramHeader)?;
267
268
Ok(Elf64 {
269
file_header: ehdr,
270
program_headers: phdrs.into_iter().map(|ph| ph.into()).collect(),
271
})
272
}
273
274
impl From<elf::Elf32_Ehdr> for elf::Elf64_Ehdr {
275
fn from(ehdr32: elf::Elf32_Ehdr) -> Self {
276
elf::Elf64_Ehdr {
277
e_ident: ehdr32.e_ident,
278
e_type: ehdr32.e_type as elf::Elf64_Half,
279
e_machine: ehdr32.e_machine as elf::Elf64_Half,
280
e_version: ehdr32.e_version as elf::Elf64_Word,
281
e_entry: ehdr32.e_entry as elf::Elf64_Addr,
282
e_phoff: ehdr32.e_phoff as elf::Elf64_Off,
283
e_shoff: ehdr32.e_shoff as elf::Elf64_Off,
284
e_flags: ehdr32.e_flags as elf::Elf64_Word,
285
e_ehsize: ehdr32.e_ehsize as elf::Elf64_Half,
286
e_phentsize: ehdr32.e_phentsize as elf::Elf64_Half,
287
e_phnum: ehdr32.e_phnum as elf::Elf64_Half,
288
e_shentsize: ehdr32.e_shentsize as elf::Elf64_Half,
289
e_shnum: ehdr32.e_shnum as elf::Elf64_Half,
290
e_shstrndx: ehdr32.e_shstrndx as elf::Elf64_Half,
291
}
292
}
293
}
294
295
impl From<elf::Elf32_Phdr> for elf::Elf64_Phdr {
296
fn from(phdr32: elf::Elf32_Phdr) -> Self {
297
elf::Elf64_Phdr {
298
p_type: phdr32.p_type as elf::Elf64_Word,
299
p_flags: phdr32.p_flags as elf::Elf64_Word,
300
p_offset: phdr32.p_offset as elf::Elf64_Off,
301
p_vaddr: phdr32.p_vaddr as elf::Elf64_Addr,
302
p_paddr: phdr32.p_paddr as elf::Elf64_Addr,
303
p_filesz: phdr32.p_filesz as elf::Elf64_Xword,
304
p_memsz: phdr32.p_memsz as elf::Elf64_Xword,
305
p_align: phdr32.p_align as elf::Elf64_Xword,
306
}
307
}
308
}
309
310
#[cfg(test)]
311
mod test {
312
use std::fs::File;
313
use std::io::Seek;
314
use std::io::SeekFrom;
315
use std::io::Write;
316
317
use tempfile::tempfile;
318
use vm_memory::GuestAddress;
319
use vm_memory::GuestMemory;
320
321
use super::*;
322
323
const MEM_SIZE: u64 = 0x40_0000;
324
325
fn create_guest_mem() -> GuestMemory {
326
GuestMemory::new(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap()
327
}
328
329
// Elf32 image that prints hello world on x86.
330
fn make_elf32_bin() -> File {
331
// test_elf32.bin built on Linux with gcc -m32 -static-pie
332
let bytes = include_bytes!("test_elf32.bin");
333
make_elf_bin(bytes)
334
}
335
336
// Elf64 image that prints hello world on x86_64.
337
fn make_elf64_bin() -> File {
338
let bytes = include_bytes!("test_elf64.bin");
339
make_elf_bin(bytes)
340
}
341
342
fn make_elf_bin(elf_bytes: &[u8]) -> File {
343
let mut file = tempfile().expect("failed to create tempfile");
344
file.write_all(elf_bytes)
345
.expect("failed to write elf to shared memory");
346
file
347
}
348
349
fn mutate_elf_bin(mut f: &File, offset: u64, val: u8) {
350
f.seek(SeekFrom::Start(offset))
351
.expect("failed to seek file");
352
f.write_all(&[val])
353
.expect("failed to write mutated value to file");
354
}
355
356
#[test]
357
fn load_elf32() {
358
let gm = create_guest_mem();
359
let kernel_addr = GuestAddress(0x0);
360
let mut image = make_elf32_bin();
361
let kernel = load_elf(&gm, kernel_addr, &mut image, 0).unwrap();
362
assert_eq!(kernel.address_range.start, 0x20_0000);
363
assert_eq!(kernel.address_range.end, 0x20_002e);
364
assert_eq!(kernel.size, 0x2f);
365
assert_eq!(kernel.entry, GuestAddress(0x20_000e));
366
assert_eq!(kernel.class, ElfClass::ElfClass32);
367
}
368
369
#[test]
370
fn load_elf64() {
371
let gm = create_guest_mem();
372
let kernel_addr = GuestAddress(0x0);
373
let mut image = make_elf64_bin();
374
let kernel = load_elf(&gm, kernel_addr, &mut image, 0).expect("failed to load ELF");
375
assert_eq!(kernel.address_range.start, 0x20_0000);
376
assert_eq!(kernel.address_range.end, 0x20_002f);
377
assert_eq!(kernel.size, 0x30);
378
assert_eq!(kernel.entry, GuestAddress(0x20_000e));
379
assert_eq!(kernel.class, ElfClass::ElfClass64);
380
}
381
382
#[test]
383
fn bad_magic() {
384
let gm = create_guest_mem();
385
let kernel_addr = GuestAddress(0x0);
386
let mut bad_image = make_elf64_bin();
387
mutate_elf_bin(&bad_image, 0x1, 0x33);
388
assert_eq!(
389
Err(Error::InvalidMagicNumber),
390
load_elf(&gm, kernel_addr, &mut bad_image, 0)
391
);
392
}
393
394
#[test]
395
fn bad_endian() {
396
// Only little endian is supported
397
let gm = create_guest_mem();
398
let kernel_addr = GuestAddress(0x20_0000);
399
let mut bad_image = make_elf64_bin();
400
mutate_elf_bin(&bad_image, 0x5, 2);
401
assert_eq!(
402
Err(Error::BigEndianOnLittle),
403
load_elf(&gm, kernel_addr, &mut bad_image, 0)
404
);
405
}
406
407
#[test]
408
fn bad_phoff() {
409
// program header has to be past the end of the elf header
410
let gm = create_guest_mem();
411
let kernel_addr = GuestAddress(0x0);
412
let mut bad_image = make_elf64_bin();
413
mutate_elf_bin(&bad_image, 0x20, 0x10);
414
assert_eq!(
415
Err(Error::InvalidProgramHeaderOffset),
416
load_elf(&gm, kernel_addr, &mut bad_image, 0)
417
);
418
}
419
420
#[test]
421
fn paddr_below_start() {
422
let gm = create_guest_mem();
423
// test_elf.bin loads a phdr at 0x20_0000, so this will fail due to an out-of-bounds address
424
let kernel_addr = GuestAddress(0x30_0000);
425
let mut image = make_elf64_bin();
426
let res = load_elf(&gm, kernel_addr, &mut image, 0);
427
assert_eq!(res, Err(Error::ProgramHeaderAddressOutOfRange));
428
}
429
}
430
431