1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::ffi::CStr;
6 use std::fmt::{self, Display};
7 use std::io::{Read, Seek, SeekFrom};
8 use std::mem;
9
10 use base::AsRawDescriptor;
11 use vm_memory::{GuestAddress, GuestMemory};
12
13 #[allow(dead_code)]
14 #[allow(non_camel_case_types)]
15 #[allow(non_snake_case)]
16 #[allow(non_upper_case_globals)]
17 #[allow(clippy::all)]
18 mod elf;
19
20 #[derive(Debug, PartialEq)]
21 pub enum Error {
22 BigEndianElfOnLittle,
23 CommandLineCopy,
24 CommandLineOverflow,
25 InvalidElfMagicNumber,
26 InvalidProgramHeaderSize,
27 InvalidProgramHeaderOffset,
28 InvalidProgramHeaderAddress,
29 InvalidProgramHeaderMemSize,
30 ReadElfHeader,
31 ReadKernelImage,
32 ReadProgramHeader,
33 SeekKernelStart,
34 SeekElfStart,
35 SeekProgramHeader,
36 }
37 pub type Result<T> = std::result::Result<T, Error>;
38
39 impl std::error::Error for Error {}
40
41 impl Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result42 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 use self::Error::*;
44
45 let description = match self {
46 BigEndianElfOnLittle => "trying to load big-endian binary on little-endian machine",
47 CommandLineCopy => "failed writing command line to guest memory",
48 CommandLineOverflow => "command line overflowed guest memory",
49 InvalidElfMagicNumber => "invalid Elf magic number",
50 InvalidProgramHeaderSize => "invalid program header size",
51 InvalidProgramHeaderOffset => "invalid program header offset",
52 InvalidProgramHeaderAddress => "invalid Program Header Address",
53 InvalidProgramHeaderMemSize => "invalid Program Header memory size",
54 ReadElfHeader => "unable to read elf header",
55 ReadKernelImage => "unable to read kernel image",
56 ReadProgramHeader => "unable to read program header",
57 SeekKernelStart => "unable to seek to kernel start",
58 SeekElfStart => "unable to seek to elf start",
59 SeekProgramHeader => "unable to seek to program header",
60 };
61
62 write!(f, "kernel loader: {}", description)
63 }
64 }
65
66 /// Loads a kernel from a vmlinux elf image to a slice
67 ///
68 /// # Arguments
69 ///
70 /// * `guest_mem` - The guest memory region the kernel is written to.
71 /// * `kernel_start` - The offset into `guest_mem` at which to load the kernel.
72 /// * `kernel_image` - Input vmlinux image.
load_kernel<F>( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, ) -> Result<u64> where F: Read + Seek + AsRawDescriptor,73 pub fn load_kernel<F>(
74 guest_mem: &GuestMemory,
75 kernel_start: GuestAddress,
76 kernel_image: &mut F,
77 ) -> Result<u64>
78 where
79 F: Read + Seek + AsRawDescriptor,
80 {
81 let mut ehdr: elf::Elf64_Ehdr = Default::default();
82 kernel_image
83 .seek(SeekFrom::Start(0))
84 .map_err(|_| Error::SeekElfStart)?;
85 unsafe {
86 // read_struct is safe when reading a POD struct. It can be used and dropped without issue.
87 base::read_struct(kernel_image, &mut ehdr).map_err(|_| Error::ReadElfHeader)?;
88 }
89
90 // Sanity checks
91 if ehdr.e_ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8
92 || ehdr.e_ident[elf::EI_MAG1 as usize] != elf::ELFMAG1
93 || ehdr.e_ident[elf::EI_MAG2 as usize] != elf::ELFMAG2
94 || ehdr.e_ident[elf::EI_MAG3 as usize] != elf::ELFMAG3
95 {
96 return Err(Error::InvalidElfMagicNumber);
97 }
98 if ehdr.e_ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 {
99 return Err(Error::BigEndianElfOnLittle);
100 }
101 if ehdr.e_phentsize as usize != mem::size_of::<elf::Elf64_Phdr>() {
102 return Err(Error::InvalidProgramHeaderSize);
103 }
104 if (ehdr.e_phoff as usize) < mem::size_of::<elf::Elf64_Ehdr>() {
105 // If the program header is backwards, bail.
106 return Err(Error::InvalidProgramHeaderOffset);
107 }
108
109 kernel_image
110 .seek(SeekFrom::Start(ehdr.e_phoff))
111 .map_err(|_| Error::SeekProgramHeader)?;
112 let phdrs: Vec<elf::Elf64_Phdr> = unsafe {
113 // Reading the structs is safe for a slice of POD structs.
114 base::read_struct_slice(kernel_image, ehdr.e_phnum as usize)
115 .map_err(|_| Error::ReadProgramHeader)?
116 };
117
118 let mut kernel_end = 0;
119
120 // Read in each section pointed to by the program headers.
121 for phdr in &phdrs {
122 if phdr.p_type != elf::PT_LOAD || phdr.p_filesz == 0 {
123 continue;
124 }
125
126 kernel_image
127 .seek(SeekFrom::Start(phdr.p_offset))
128 .map_err(|_| Error::SeekKernelStart)?;
129
130 let mem_offset = kernel_start
131 .checked_add(phdr.p_paddr)
132 .ok_or(Error::InvalidProgramHeaderAddress)?;
133 guest_mem
134 .read_to_memory(mem_offset, kernel_image, phdr.p_filesz as usize)
135 .map_err(|_| Error::ReadKernelImage)?;
136
137 kernel_end = mem_offset
138 .offset()
139 .checked_add(phdr.p_memsz)
140 .ok_or(Error::InvalidProgramHeaderMemSize)?;
141 }
142
143 Ok(kernel_end)
144 }
145
146 /// Writes the command line string to the given memory slice.
147 ///
148 /// # Arguments
149 ///
150 /// * `guest_mem` - A u8 slice that will be partially overwritten by the command line.
151 /// * `guest_addr` - The address in `guest_mem` at which to load the command line.
152 /// * `cmdline` - The kernel command line.
load_cmdline( guest_mem: &GuestMemory, guest_addr: GuestAddress, cmdline: &CStr, ) -> Result<()>153 pub fn load_cmdline(
154 guest_mem: &GuestMemory,
155 guest_addr: GuestAddress,
156 cmdline: &CStr,
157 ) -> Result<()> {
158 let len = cmdline.to_bytes().len();
159 if len == 0 {
160 return Ok(());
161 }
162
163 let end = guest_addr
164 .checked_add(len as u64 + 1)
165 .ok_or(Error::CommandLineOverflow)?; // Extra for null termination.
166 if end > guest_mem.end_addr() {
167 return Err(Error::CommandLineOverflow);
168 }
169
170 guest_mem
171 .write_at_addr(cmdline.to_bytes_with_nul(), guest_addr)
172 .map_err(|_| Error::CommandLineCopy)?;
173
174 Ok(())
175 }
176
177 #[cfg(test)]
178 mod test {
179 use super::*;
180 use std::fs::File;
181 use std::io::Write;
182 use tempfile::tempfile;
183 use vm_memory::{GuestAddress, GuestMemory};
184
185 const MEM_SIZE: u64 = 0x8000;
186
create_guest_mem() -> GuestMemory187 fn create_guest_mem() -> GuestMemory {
188 GuestMemory::new(&vec![(GuestAddress(0x0), MEM_SIZE)]).unwrap()
189 }
190
191 #[test]
cmdline_overflow()192 fn cmdline_overflow() {
193 let gm = create_guest_mem();
194 let cmdline_address = GuestAddress(MEM_SIZE - 5);
195 assert_eq!(
196 Err(Error::CommandLineOverflow),
197 load_cmdline(
198 &gm,
199 cmdline_address,
200 CStr::from_bytes_with_nul(b"12345\0").unwrap()
201 )
202 );
203 }
204
205 #[test]
cmdline_write_end()206 fn cmdline_write_end() {
207 let gm = create_guest_mem();
208 let mut cmdline_address = GuestAddress(45);
209 assert_eq!(
210 Ok(()),
211 load_cmdline(
212 &gm,
213 cmdline_address,
214 CStr::from_bytes_with_nul(b"1234\0").unwrap()
215 )
216 );
217 let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
218 assert_eq!(val, '1' as u8);
219 cmdline_address = cmdline_address.unchecked_add(1);
220 let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
221 assert_eq!(val, '2' as u8);
222 cmdline_address = cmdline_address.unchecked_add(1);
223 let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
224 assert_eq!(val, '3' as u8);
225 cmdline_address = cmdline_address.unchecked_add(1);
226 let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
227 assert_eq!(val, '4' as u8);
228 cmdline_address = cmdline_address.unchecked_add(1);
229 let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
230 assert_eq!(val, '\0' as u8);
231 }
232
233 // Elf64 image that prints hello world on x86_64.
make_elf_bin() -> File234 fn make_elf_bin() -> File {
235 let elf_bytes = include_bytes!("test_elf.bin");
236 let mut file = tempfile().expect("failed to create tempfile");
237 file.write_all(elf_bytes)
238 .expect("failed to write elf to shared memoy");
239 file
240 }
241
mutate_elf_bin(mut f: &File, offset: u64, val: u8)242 fn mutate_elf_bin(mut f: &File, offset: u64, val: u8) {
243 f.seek(SeekFrom::Start(offset))
244 .expect("failed to seek file");
245 f.write(&[val])
246 .expect("failed to write mutated value to file");
247 }
248
249 #[test]
load_elf()250 fn load_elf() {
251 let gm = create_guest_mem();
252 let kernel_addr = GuestAddress(0x0);
253 let mut image = make_elf_bin();
254 assert_eq!(Ok(16613), load_kernel(&gm, kernel_addr, &mut image));
255 }
256
257 #[test]
bad_magic()258 fn bad_magic() {
259 let gm = create_guest_mem();
260 let kernel_addr = GuestAddress(0x0);
261 let mut bad_image = make_elf_bin();
262 mutate_elf_bin(&bad_image, 0x1, 0x33);
263 assert_eq!(
264 Err(Error::InvalidElfMagicNumber),
265 load_kernel(&gm, kernel_addr, &mut bad_image)
266 );
267 }
268
269 #[test]
bad_endian()270 fn bad_endian() {
271 // Only little endian is supported
272 let gm = create_guest_mem();
273 let kernel_addr = GuestAddress(0x0);
274 let mut bad_image = make_elf_bin();
275 mutate_elf_bin(&bad_image, 0x5, 2);
276 assert_eq!(
277 Err(Error::BigEndianElfOnLittle),
278 load_kernel(&gm, kernel_addr, &mut bad_image)
279 );
280 }
281
282 #[test]
bad_phoff()283 fn bad_phoff() {
284 // program header has to be past the end of the elf header
285 let gm = create_guest_mem();
286 let kernel_addr = GuestAddress(0x0);
287 let mut bad_image = make_elf_bin();
288 mutate_elf_bin(&bad_image, 0x20, 0x10);
289 assert_eq!(
290 Err(Error::InvalidProgramHeaderOffset),
291 load_kernel(&gm, kernel_addr, &mut bad_image)
292 );
293 }
294 }
295