1use memf_format::PhysicalMemoryProvider;
8use memf_symbols::SymbolResolver;
9
10use crate::{Error, Result};
11
12const BANNER_PREFIX: &[u8] = b"Linux version ";
14
15const KERNEL_MAP_BASE: u64 = 0xFFFF_FFFF_8000_0000;
17
18pub fn detect_kaslr_offset(
22 physical: &dyn PhysicalMemoryProvider,
23 symbols: &dyn SymbolResolver,
24) -> Result<u64> {
25 let banner_symbol_vaddr =
26 symbols
27 .symbol_address("linux_banner")
28 .ok_or_else(|| Error::MissingKernelSymbol {
29 name: "linux_banner".into(),
30 })?;
31
32 let banner_phys = scan_for_banner(physical)?;
33
34 let actual_virt = banner_phys.wrapping_add(KERNEL_MAP_BASE);
35 let offset = actual_virt.wrapping_sub(banner_symbol_vaddr);
36
37 Ok(offset)
38}
39
40fn scan_for_banner(physical: &dyn PhysicalMemoryProvider) -> Result<u64> {
42 let mut buf = vec![0u8; 4096];
43
44 for range in physical.ranges() {
45 let mut addr = range.start;
46 while addr < range.end {
47 let to_read = ((range.end - addr) as usize).min(buf.len());
48 let n =
49 physical
50 .read_phys(addr, &mut buf[..to_read])
51 .map_err(|e| Error::WalkFailed {
52 walker: "scan_for_banner",
53 reason: format!("physical read error: {e}"),
54 })?;
55 if n == 0 {
56 break;
57 }
58
59 if let Some(pos) = find_subsequence(&buf[..n], BANNER_PREFIX) {
60 return Ok(addr + pos as u64);
61 }
62
63 if n > BANNER_PREFIX.len() {
65 addr += (n - BANNER_PREFIX.len()) as u64;
66 } else {
67 addr += n as u64;
68 }
69 }
70 }
71
72 Err(Error::WalkFailed {
73 walker: "scan_for_banner",
74 reason: "Linux banner string not found in physical memory".into(),
75 })
76}
77
78fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
79 haystack.windows(needle.len()).position(|w| w == needle)
80}
81
82#[must_use]
84pub fn adjust_address(original: u64, kaslr_offset: u64) -> u64 {
85 original.wrapping_add(kaslr_offset)
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use memf_format::PhysicalRange;
92 use memf_symbols::isf::IsfResolver;
93 use memf_symbols::test_builders::IsfBuilder;
94
95 struct BannerPhysMem {
96 data: Vec<u8>,
97 ranges: Vec<PhysicalRange>,
98 }
99
100 impl PhysicalMemoryProvider for BannerPhysMem {
101 fn read_phys(&self, addr: u64, buf: &mut [u8]) -> memf_format::Result<usize> {
102 let start = addr as usize;
103 if start >= self.data.len() {
104 return Ok(0);
105 }
106 let available = self.data.len() - start;
107 let to_read = buf.len().min(available);
108 buf[..to_read].copy_from_slice(&self.data[start..start + to_read]);
109 Ok(to_read)
110 }
111
112 fn ranges(&self) -> &[PhysicalRange] {
113 &self.ranges
114 }
115
116 fn format_name(&self) -> &str {
117 "Test"
118 }
119 }
120
121 #[test]
122 fn detect_no_kaslr() {
123 let banner_phys: u64 = 0x0200_0000;
124 let banner_vaddr: u64 = 0xFFFF_FFFF_8200_0000;
125
126 let mut data = vec![0u8; (banner_phys as usize) + 4096];
127 let banner = b"Linux version 5.15.0-generic";
128 data[banner_phys as usize..banner_phys as usize + banner.len()].copy_from_slice(banner);
129
130 let mem = BannerPhysMem {
131 ranges: vec![PhysicalRange {
132 start: 0,
133 end: data.len() as u64,
134 }],
135 data,
136 };
137
138 let isf = IsfBuilder::new()
139 .add_symbol("linux_banner", banner_vaddr)
140 .build_json();
141 let resolver = IsfResolver::from_value(&isf).unwrap();
142
143 let offset = detect_kaslr_offset(&mem, &resolver).unwrap();
144 assert_eq!(offset, 0);
145 }
146
147 #[test]
148 fn detect_with_kaslr() {
149 let kaslr_slide: u64 = 0x0060_0000;
150 let default_banner_vaddr: u64 = 0xFFFF_FFFF_8200_0000;
151 let banner_phys: u64 = 0x0260_0000;
152
153 let mut data = vec![0u8; (banner_phys as usize) + 4096];
154 let banner = b"Linux version 6.1.0-kaslr";
155 data[banner_phys as usize..banner_phys as usize + banner.len()].copy_from_slice(banner);
156
157 let mem = BannerPhysMem {
158 ranges: vec![PhysicalRange {
159 start: 0,
160 end: data.len() as u64,
161 }],
162 data,
163 };
164
165 let isf = IsfBuilder::new()
166 .add_symbol("linux_banner", default_banner_vaddr)
167 .build_json();
168 let resolver = IsfResolver::from_value(&isf).unwrap();
169
170 let offset = detect_kaslr_offset(&mem, &resolver).unwrap();
171 assert_eq!(offset, kaslr_slide);
172 }
173
174 #[test]
175 fn missing_linux_banner_symbol_returns_missing_kernel_symbol() {
176 let data = vec![0u8; 4096];
177 let mem = BannerPhysMem {
178 ranges: vec![PhysicalRange {
179 start: 0,
180 end: 4096,
181 }],
182 data,
183 };
184 let isf = IsfBuilder::new().build_json();
186 let resolver = IsfResolver::from_value(&isf).unwrap();
187 let result = detect_kaslr_offset(&mem, &resolver);
188 assert!(
189 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "linux_banner"),
190 "expected MissingKernelSymbol {{name: \"linux_banner\"}}, got {result:?}"
191 );
192 }
193
194 #[test]
195 fn no_banner_found() {
196 let data = vec![0u8; 4096];
197 let mem = BannerPhysMem {
198 ranges: vec![PhysicalRange {
199 start: 0,
200 end: 4096,
201 }],
202 data,
203 };
204
205 let isf = IsfBuilder::new()
206 .add_symbol("linux_banner", 0xFFFF_FFFF_8200_0000)
207 .build_json();
208 let resolver = IsfResolver::from_value(&isf).unwrap();
209
210 let result = detect_kaslr_offset(&mem, &resolver);
211 assert!(result.is_err());
212 }
213
214 #[test]
215 fn adjust_address_with_offset() {
216 let original = 0xFFFF_FFFF_8260_0000u64;
217 let kaslr = 0x0060_0000u64;
218 let adjusted = adjust_address(original, kaslr);
219 assert_eq!(adjusted, 0xFFFF_FFFF_82C0_0000);
220 }
221
222 #[test]
223 fn find_subsequence_basic() {
224 let haystack = b"hello world Linux version 5.15";
225 let needle = b"Linux version ";
226 assert_eq!(find_subsequence(haystack, needle), Some(12));
227 }
228
229 #[test]
230 fn find_subsequence_not_found() {
231 let haystack = b"no banner here";
232 let needle = b"Linux version ";
233 assert_eq!(find_subsequence(haystack, needle), None);
234 }
235}