1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// Copyright (C) 2019-2021 O.S. Systems Sofware LTDA
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::VersionFinder;
use regex::bytes::Regex;
use std::{io::SeekFrom, str};
use tokio::io::{self, AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt};
#[allow(clippy::enum_variant_names, clippy::upper_case_acronyms)]
enum LinuxKernelKind {
ARMzImage,
UImage,
X86bzImage,
X86zImage,
}
// U-Boot Image Magic Number
const UIMAGE_MAGIC_NUMBER: u32 = 0x2705_1956;
// zImage Magic Number used in ARM
const ARM_ZIMAGE_MAGIC_NUMBER: u32 = 0x016F_2818;
async fn discover_linux_kernel_kind<R: AsyncRead + AsyncSeek + Unpin>(
buf: &mut R,
) -> Option<LinuxKernelKind> {
// U-Boot Image Magic header is stored at begin of file
buf.seek(SeekFrom::Start(0x0000)).await.ok()?;
if buf.read_u32().await.ok()? == UIMAGE_MAGIC_NUMBER {
return Some(LinuxKernelKind::UImage);
}
// ARM zImage Magic header is stored at offset 0x0024 of file
buf.seek(SeekFrom::Start(0x0024)).await.ok()?;
if buf.read_u32_le().await.ok()? == ARM_ZIMAGE_MAGIC_NUMBER {
return Some(LinuxKernelKind::ARMzImage);
}
// Taken from: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/x86/boot.txt#n144
//
// Offset Proto Name Meaning
// /Size
// ...
// 01FE/2 ALL boot_flag 0xAA55 magic number
// ...
// 0211/1 2.00+ loadflags Boot protocol option flags
// Verify the boot_flag magic number
buf.seek(SeekFrom::Start(0x01FE)).await.ok()?;
if buf.read_u16_le().await.ok()? != 0xAA55 {
return None;
}
// Field name: loadflags
// Type: modify (obligatory)
// Offset/size: 0x211/1
// Protocol: 2.00+
//
// This field is a bitmask.
//
// Bit 0 (read): LOADED_HIGH
// - If 0, the protected-mode code is loaded at 0x10000.
// - If 1, the protected-mode code is loaded at 0x100000.
// ...
buf.seek(SeekFrom::Start(0x0211)).await.ok()?;
match buf.read_u8().await.ok()? & 0x1 {
0 => Some(LinuxKernelKind::X86zImage),
1 => Some(LinuxKernelKind::X86bzImage),
_ => None,
}
}
pub(crate) struct LinuxKernel<'a, R: AsyncRead + AsyncSeek + Unpin> {
buf: &'a mut R,
}
impl<'a, R: AsyncRead + AsyncSeek + Unpin> LinuxKernel<'a, R> {
pub(crate) fn from_reader(buf: &'a mut R) -> Self {
LinuxKernel { buf }
}
}
#[async_trait::async_trait(?Send)]
impl<'a, R: AsyncRead + AsyncSeek + Unpin> VersionFinder for LinuxKernel<'a, R> {
async fn get_version(&mut self) -> Option<String> {
match discover_linux_kernel_kind(self.buf).await? {
LinuxKernelKind::ARMzImage => {
async fn get_version_from_arm<R: AsyncRead + Unpin>(mut rd: R) -> Option<String> {
let mut buffer = Vec::default();
compress_tools::tokio_support::uncompress_data(&mut rd, &mut buffer)
.await
.ok()?;
let re = Regex::new(r"Linux version (?P<version>\S+).*").unwrap();
re.captures(&buffer)
.and_then(|m| m.name("version"))
.and_then(|v| str::from_utf8(v.as_bytes()).ok())
.map(|v| v.to_string())
}
let mut buffer = [0; 0x200];
loop {
let n = self.buf.read(&mut buffer).await.ok()?;
// No more data to read
if n == 0 {
return None;
}
// Look for compression format header
for (offset, window) in buffer[0..n].windows(6).enumerate() {
// Headers taken from:
// https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux
match window {
[0x1f, 0x8b, 0x08, ..] => {} // gzip
[0xfd, b'7', b'z', b'X', b'Z', 0x00] => {} // xz
[b'B', b'Z', b'h', ..] => {} // bzip2
[0x5d, 0x00, 0x00, ..] => {} // lzma
[0x89, 0x4c, 0x5a, ..] => {} // lzo
[0x02, b'!', b'L', 0x18, ..] => {} // lz4
[b'(', 0xb5, b'/', 0xfd, ..] => {} // zstd
_ => continue,
}
let mut slice = &buffer[offset..];
let current = self.buf.seek(SeekFrom::Current(0)).await.ok()?;
let rd = io::AsyncReadExt::chain(&mut slice, &mut self.buf);
// Try to get version from uncompressed data
if let Some(version) = get_version_from_arm(rd).await {
return Some(version);
}
// Seek back to current position so we can keep looking
// for the next compression header
self.buf.seek(SeekFrom::Start(current)).await.ok()?;
}
}
}
LinuxKernelKind::X86bzImage | LinuxKernelKind::X86zImage => {
// Taken from: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/x86/boot.txt#n144
//
// Offset Proto Name Meaning
// /Size
// ...
// 01F1/1 ALL(1 setup_sects The size of the setup in sectors
// ...
// 020E/2 2.00+ kernel_version Pointer to kernel version string
// Get the setup_sects information
self.buf.seek(SeekFrom::Start(0x01F1)).await.ok()?;
let setup_sects = u64::from(self.buf.read_u8().await.ok()?);
// Get kernel_version pointer
self.buf.seek(SeekFrom::Start(0x020E)).await.ok()?;
let kernel_version_ptr = u64::from(self.buf.read_u16_le().await.ok()?);
// Field name: kernel_version
// Type: read
// Offset/size: 0x20e/2
// Protocol: 2.00+
//
// If set to a nonzero value, contains a pointer to a NUL-terminated
// human-readable kernel version number string, less 0x200. This can
// be used to display the kernel version to the user. This value
// should be less than (0x200*setup_sects).
if kernel_version_ptr >= setup_sects * 0x200 {
return None;
}
// Move to the kernel version location
self.buf
.seek(SeekFrom::Start(kernel_version_ptr + 0x200))
.await
.ok()?;
// Read the Linux kernel version from the reader
let mut buffer = [0; 0x200];
let _ = self.buf.read(&mut buffer).await.ok()?;
let re = Regex::new(r"(?P<version>\d+.?\.[^\s\u{0}]+)").unwrap();
re.captures(&buffer)
.and_then(|m| m.name("version"))
.and_then(|v| str::from_utf8(v.as_bytes()).ok())
.map(|v| v.to_string())
}
LinuxKernelKind::UImage => {
// Move to the begin of the file, so we can next read the
// buffer to match the version.
self.buf.seek(SeekFrom::Start(0)).await.ok()?;
// Read the Linux kernel version from the reader
let mut buffer = [0; 0x200];
let _ = self.buf.read(&mut buffer).await.ok()?;
let re = Regex::new(r"(?P<version>\d+.?\.[^\s\u{0}]+)").unwrap();
re.captures(&buffer)
.and_then(|m| m.name("version"))
.and_then(|v| str::from_utf8(v.as_bytes()).ok())
.map(|v| v.to_string())
}
}
}
}
#[cfg(test)]
mod test {
use crate::{version, BinaryKind};
use tokio::io::{AsyncRead, AsyncSeek};
async fn fixture(name: &str) -> impl AsyncRead + AsyncSeek {
use tokio::{fs::File, io::BufReader};
BufReader::new(
File::open(&format!("tests/fixtures/linuxkernel/{}", name))
.await
.unwrap_or_else(|_| panic!("Couldn't open the fixture {}", name)),
)
}
#[tokio::test]
async fn linux_version() {
for (f, v) in &[
("arm-uImage", "4.1.15-1.2.0+g274a055"),
("arm-zImage", "4.4.1"),
("x86-bzImage", "4.1.30-1-MANJARO"),
("x86-zImage", "4.1.30-1-MANJARO"),
] {
assert_eq!(
version(&mut fixture(f).await, BinaryKind::LinuxKernel).await,
Some(v.to_string())
);
}
}
}