1use loader_defs::linux as defs;
15use std::io::Read;
16use std::io::Seek;
17use std::io::SeekFrom;
18use std::mem::size_of;
19use thiserror::Error;
20use zerocopy::FromBytes;
21
22const HDRS_MAGIC: u32 = 0x53726448;
25
26const BOOT_FLAG: u16 = 0xAA55;
28
29const MIN_PROTOCOL_VERSION: u16 = 0x020C;
31
32const SETUP_HEADER_OFFSET: usize = 0x1F1;
34
35const MIN_HEADER_SIZE: usize = SETUP_HEADER_OFFSET + size_of::<defs::setup_header>();
38
39const LOADED_HIGH: u8 = 0x01;
41
42const XLF_KERNEL_64: u16 = 0x01;
44
45#[derive(Debug, Error)]
47pub enum Error {
48 #[error("I/O error reading bzImage")]
50 Io(#[source] std::io::Error),
51 #[error("not a valid bzImage (missing boot flag or HdrS magic)")]
53 NotBzImage,
54 #[error(
56 "bzImage boot protocol version {version:#06x} is too old (need >= 2.12 for 64-bit boot)"
57 )]
58 ProtocolTooOld {
59 version: u16,
61 },
62 #[error("bzImage does not have LOADED_HIGH flag set")]
64 NotLoadedHigh,
65 #[error("bzImage does not support 64-bit boot (XLF_KERNEL_64 not set in xloadflags)")]
67 No64BitEntry,
68 #[error(
71 "bzImage is truncated: protected-mode size ({size}) is too small for entry offset ({entry_offset})"
72 )]
73 Truncated {
74 size: u64,
76 entry_offset: u64,
78 },
79}
80
81#[derive(Debug, Clone)]
83pub struct BzImageInfo {
84 pub setup_header: defs::setup_header,
86 pub setup_sects: u8,
89 pub protected_mode_size: u64,
91 pub entry_offset: u64,
95 pub init_size: u32,
98}
99
100pub fn is_bzimage(kernel_image: &mut (impl Read + Seek)) -> Result<bool, Error> {
106 kernel_image.seek(SeekFrom::Start(0)).map_err(Error::Io)?;
107
108 let mut buf = [0u8; MIN_HEADER_SIZE];
109 let result = kernel_image.read_exact(&mut buf);
110
111 kernel_image.seek(SeekFrom::Start(0)).map_err(Error::Io)?;
113
114 match result {
115 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(false),
116 Err(e) => return Err(Error::Io(e)),
117 Ok(()) => {}
118 }
119
120 let boot_flag = u16::from_le_bytes([buf[0x1fe], buf[0x1ff]]);
121 let header_magic = u32::from_le_bytes([buf[0x202], buf[0x203], buf[0x204], buf[0x205]]);
122
123 Ok(boot_flag == BOOT_FLAG && header_magic == HDRS_MAGIC)
124}
125
126pub fn parse_bzimage(kernel_image: &mut (impl Read + Seek)) -> Result<BzImageInfo, Error> {
132 kernel_image.seek(SeekFrom::Start(0)).map_err(Error::Io)?;
133 let result = parse_bzimage_inner(kernel_image);
134 let _ = kernel_image.seek(SeekFrom::Start(0));
135 result
136}
137
138fn parse_bzimage_inner(kernel_image: &mut (impl Read + Seek)) -> Result<BzImageInfo, Error> {
139 let mut buf = [0u8; MIN_HEADER_SIZE];
140 kernel_image.read_exact(&mut buf).map_err(Error::Io)?;
141
142 let boot_flag = u16::from_le_bytes([buf[0x1fe], buf[0x1ff]]);
144 let header_magic = u32::from_le_bytes([buf[0x202], buf[0x203], buf[0x204], buf[0x205]]);
145 if boot_flag != BOOT_FLAG || header_magic != HDRS_MAGIC {
146 return Err(Error::NotBzImage);
147 }
148
149 let hdr = defs::setup_header::read_from_bytes(
152 &buf[SETUP_HEADER_OFFSET..SETUP_HEADER_OFFSET + size_of::<defs::setup_header>()],
153 )
154 .expect("buf is large enough: MIN_HEADER_SIZE is derived from setup_header size");
155
156 let version: u16 = hdr.version.into();
157 if version < MIN_PROTOCOL_VERSION {
158 return Err(Error::ProtocolTooOld { version });
159 }
160
161 let loadflags: u8 = hdr.loadflags;
162 if loadflags & LOADED_HIGH == 0 {
163 return Err(Error::NotLoadedHigh);
164 }
165
166 let xloadflags: u16 = hdr.xloadflags.into();
167 if xloadflags & XLF_KERNEL_64 == 0 {
168 return Err(Error::No64BitEntry);
169 }
170
171 let setup_sects = if hdr.setup_sects == 0 {
172 4
173 } else {
174 hdr.setup_sects
175 };
176 let protected_mode_offset = (setup_sects as u64 + 1) * 512;
177
178 let syssize: u32 = hdr.syssize.into();
181 let protected_mode_size = if syssize != 0 {
182 syssize as u64 * 16
183 } else {
184 let file_size = kernel_image.seek(SeekFrom::End(0)).map_err(Error::Io)?;
185 file_size.saturating_sub(protected_mode_offset)
186 };
187
188 let entry_offset: u64 = 0x200;
191 if protected_mode_size <= entry_offset {
192 return Err(Error::Truncated {
193 size: protected_mode_size,
194 entry_offset,
195 });
196 }
197
198 let init_size: u32 = hdr.init_size.into();
199
200 tracing::debug!(
201 version = format_args!("{:#06x}", version),
202 setup_sects,
203 protected_mode_offset,
204 protected_mode_size,
205 init_size,
206 "parsed bzImage setup header"
207 );
208
209 Ok(BzImageInfo {
210 setup_header: hdr,
211 setup_sects,
212 protected_mode_size,
213 entry_offset,
214 init_size,
215 })
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use std::io::Cursor;
222
223 fn make_test_bzimage() -> Vec<u8> {
225 let setup_sects: u8 = 1;
226 let protected_mode_offset = (setup_sects as u32 + 1) * 512;
227 let pm_code = vec![0xCC; 1024];
229
230 let total_size = protected_mode_offset as usize + pm_code.len();
231 let mut image = vec![0u8; total_size];
232
233 image[0x1f1] = setup_sects;
235 image[0x1fe..0x200].copy_from_slice(&BOOT_FLAG.to_le_bytes());
237 image[0x202..0x206].copy_from_slice(&HDRS_MAGIC.to_le_bytes());
239 image[0x206..0x208].copy_from_slice(&0x020fu16.to_le_bytes());
241 image[0x211] = LOADED_HIGH;
243 image[0x236..0x238].copy_from_slice(&XLF_KERNEL_64.to_le_bytes());
245 image[0x258..0x260].copy_from_slice(&0x1000000u64.to_le_bytes());
247 let syssize = (pm_code.len() as u32) / 16;
249 image[0x1f4..0x1f8].copy_from_slice(&syssize.to_le_bytes());
250 image[0x260..0x264].copy_from_slice(&0x1000000u32.to_le_bytes());
252
253 image[protected_mode_offset as usize..].copy_from_slice(&pm_code);
255
256 image
257 }
258
259 #[test]
260 fn test_is_bzimage_positive() {
261 let bzimage = make_test_bzimage();
262 let mut cursor = Cursor::new(bzimage);
263 assert!(is_bzimage(&mut cursor).unwrap());
264 }
265
266 #[test]
267 fn test_is_bzimage_negative_elf() {
268 let mut elf = vec![0u8; 0x1000];
269 elf[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
270 let mut cursor = Cursor::new(elf);
271 assert!(!is_bzimage(&mut cursor).unwrap());
272 }
273
274 #[test]
275 fn test_parse_bzimage() {
276 let bzimage = make_test_bzimage();
277 let mut cursor = Cursor::new(bzimage);
278
279 let info = parse_bzimage(&mut cursor).unwrap();
280 assert_eq!(info.setup_sects, 1);
281 assert_eq!(info.protected_mode_size, 1024);
282 assert_eq!(info.entry_offset, 0x200);
283 assert_eq!(info.init_size, 0x1000000);
284 }
285
286 #[test]
287 fn test_not_bzimage_returns_false() {
288 let mut elf = vec![0u8; 0x1000];
289 elf[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
290 let mut cursor = Cursor::new(elf);
291 assert!(!is_bzimage(&mut cursor).unwrap());
292 }
293}