Skip to main content

kernel_api/memory/
asan.rs

1//! ABI for interfacing with KASAN shadow memory
2//!
3//! KASAN functions by keeping an in-memory shadow map of all kernelspace memory.
4//! Each byte in the shadow map represents 8 bytes of kernel memory, which can either be
5//! accessible, partially accessible, or poisoned.
6//!
7//! There are a range of poison values used in Popcorn2, some of which are defined by compiler ABI.
8//! In practice not all of these values are used.
9//! The current list is:
10//! - `0xfa`: Heap left redzone - memory just before a heap allocation
11//! - `0xfb`: Heap right redzone - memory just after a heap allocation
12//! - `0xfc`: Heap headers - memory used by heap internals
13//! - `0xfd`: Freed heap memory - heap memory that has recently been deallocated, and is currently
14//!                               in a quarantine period
15//! - `0xf1`: Stack left redzone - memory just before a stack allocation
16//! - `0xf2`: Stack mid redzone - memory between two stack allocations
17//! - `0xf3`: Stack right redzone - memory just after a stack allocation
18//! - `0xf4`: Stack guard page - the page of unmapped memory below the stack to catch stack overflows
19//! - `0xf5`: Stack after return - the stack frame of a function that has already returned, used to
20//!                                catch dangling references returned by a function
21//! - `0xf8`: Stack use after scope - a stack slot in the current function but is now out of scope
22//! - `0xf9`: Global redzone - memory around global variables
23//! - `0xc0`: Freed virtual memory - memory that has just been deallocated by a [`Vmm`](crate::allocator::Vmm)
24//! - `0xc1`: Uninitialized virtual memory - memory that has never been allocated
25//! - `0xcc`: Shadow gap - the shadow map itself
26
27use crate::memory::VirtualAddress;
28
29/// Offset to add to `addr / 8` to calculate shadow map address.
30pub const SHADOW_MAP_SHIFT: usize = cfg_select! {
31	target_arch = "x86_64" => 0xdfff_d000_0000_0000,
32};
33
34/// Address of the start of the shadow map region.
35pub const SHADOW_MAP_START: VirtualAddress = cfg_select! {
36	target_arch = "x86_64" => VirtualAddress::new(0xffff_c000_0000_0000),
37};
38
39/// Address of the end of the shadow map region.
40pub const SHADOW_MAP_END: VirtualAddress = cfg_select! {
41	target_arch = "x86_64" => SHADOW_MAP_START + SHADOW_MAP_SIZE,
42};
43
44const SHADOW_MAP_SIZE: usize = cfg_select! {
45	target_arch = "x86_64" => 16*1024*1024*1024*1024,
46};
47
48/// Converts the passed `address` into the corresponding address in the shadow map.
49#[must_use]
50pub fn mem_to_shadow(address: VirtualAddress) -> VirtualAddress {
51	let ret = (address.addr >> 3) + SHADOW_MAP_SHIFT;
52	let ret = VirtualAddress::new(ret);
53	debug_assert!(ret >= SHADOW_MAP_START && ret < SHADOW_MAP_END, "address = {address:#x}, ret = {ret:#x}, start = {SHADOW_MAP_START:#x}, end = {SHADOW_MAP_END:#x}");
54	ret
55}
56
57/// Converts the number of bytes into the lower bound number of bytes in the shadow map.
58#[must_use]
59pub const fn count_to_shadow(count: usize) -> usize {
60	count / 8
61}
62
63#[cfg(feature = "full")] pub use full::*;
64#[cfg(feature = "full")] mod full {
65	pub use super::*;
66	use core::cmp::max;
67	use crate::memory::VirtualAddress;
68	use core::fmt::Write as _;
69	use log::{debug, warn};
70
71	const BYTES_AROUND: usize = 32*8*4;
72
73	const LEGEND: &str = "\
74Shadow byte legend (one shadow byte represents 8 kernel bytes):
75  Addressable:           00
76  Partially addressable: 01 - 07
77  Heap left redzone:     fa
78  Heap right redzone:    fb
79  Heap headers:          fc
80  Freed heap memory:     fd
81  Stack left redzone:    f1
82  Stack mid redzone:     f2
83  Stack right redzone:   f3
84  Stack guard page:      f4
85  Stack after return:    f5
86  Use after scope:       f8
87  Global redzone:        f9
88  Freed vmem:            c0
89  Uninitialised vmem:    c1
90  Shadow gap:            cc
91";
92
93	pub macro no_asan_shim {
94		($([$($tt:tt)*])?|$($i:ident:$ty:ty),*$(,)?| $(-> $ret:ty)? $e:block) => {{
95			#[cfg_attr(kasan, sanitize(address = "off"))]
96			#[cfg_attr(kasan, inline(never))]
97			#[cfg_attr(not(kasan), inline(always))]
98			fn noasan_shim<$($tt)*>($($i:$ty),*) $(-> $ret)? {$e}
99
100			noasan_shim($($i),*)
101		}},
102		($([$($tt:tt)*])?|$($i:ident:$ty:ty),*$(,)?| $e:expr) => {
103			$crate::memory::asan::no_asan_shim!($([$($tt)*])?|$($i:$ty),*| { $e:expr })
104		},
105	}
106
107	pub fn read_shadow_map_raw(idx: usize) -> i8 {
108		assert!(idx < SHADOW_MAP_SIZE, "attempt to read outside of shadow map");
109		no_asan_shim!(|idx: usize| -> i8 {
110			// SAFETY: just checked the index is within the shadow map and all values within shadow map
111			//  are aligned and accessible due to lazy mapping
112			unsafe { *SHADOW_MAP_START.as_ptr().byte_add(idx).cast() }
113		})
114	}
115
116	pub fn read_shadow_map_for(addr: VirtualAddress) -> i8 {
117		read_shadow_map_raw((addr.addr >> 3) + SHADOW_MAP_SHIFT)
118	}
119
120	/// If `idx` is greater than `SHADOW_MAP_END - SHADOW_MAP_START`.
121	pub fn write_shadow_map_raw(idx: usize, val: i8) {
122		assert!(idx < SHADOW_MAP_SIZE, "attempt to read outside of shadow map");
123		no_asan_shim!(|idx: usize, val: i8| {
124			// SAFETY: just checked the index is within the shadow map and all values within shadow map
125			//  are aligned and accessible due to lazy mapping
126			unsafe { *SHADOW_MAP_START.as_ptr().byte_add(idx).cast() = val };
127		});
128	}
129
130	pub fn write_shadow_map_for(addr: VirtualAddress, val: i8) {
131		write_shadow_map_raw((addr.addr >> 3) + SHADOW_MAP_SHIFT, val);
132	}
133
134	struct Serial;
135
136	impl core::fmt::Write for Serial {
137		fn write_str(&mut self, s: &str) -> core::fmt::Result {
138			unsafe extern "Rust" {
139				#[link_name = "__popcorn_force_unsafe_serial"]
140				fn force_serial(s: &str);
141			}
142			unsafe { force_serial(s); }
143			Ok(())
144		}
145	}
146
147	#[cold]
148	fn do_report(address: VirtualAddress, ty: &str, width: usize) {
149		let mut writer = Serial;
150
151		let dump_start = mem_to_shadow(
152			max(
153				address.saturating_sub(BYTES_AROUND),
154				VirtualAddress::new(0xffff_8000_0000_0000)
155			)
156		);
157		let dump_end = mem_to_shadow(address.saturating_add(BYTES_AROUND - 1)); // make this an inclusive range so we can print up to usize::MAX
158
159		let _ = writeln!(&mut writer, "===========================================================");
160		let _ = writeln!(&mut writer, "KASAN violation detected:");
161		let _ = writeln!(&mut writer, "  {ty} of {width} bytes at {address:#x}");
162		let _ = writeln!(&mut writer, "Shadow bytes around the buggy address:");
163
164		for i in dump_start..=dump_end {
165			if (i - dump_start) % 16 == 0 {
166				let _ = write!(&mut writer, "  0x{:016x}:", i);
167			}
168			let byte = read_shadow_map_raw(i - SHADOW_MAP_START);
169
170			if i >= mem_to_shadow(address) && i <= mem_to_shadow(address + width - 1usize) {
171				let _ = write!(&mut writer, " \u{001b}[1m{:02x}\u{001b}[0m", byte);
172			} else {
173				let _ = write!(&mut writer, " {:02x}", byte);
174			}
175
176			if (i - dump_start + 1) % 16 == 0 {
177				let _ = writeln!(&mut writer);
178			}
179		}
180
181		let _ = writeln!(&mut writer, "\n{}", LEGEND);
182
183		let _ = writeln!(&mut writer, "===========================================================");
184
185		panic!("fatal KASAN error");
186	}
187
188	#[doc(hidden)]
189	#[unsafe(no_mangle)]
190	pub extern "C-unwind" fn __asan_register_globals(_globals: *const u8, _num: usize) {}
191
192	#[doc(hidden)]
193	#[unsafe(no_mangle)]
194	pub extern "C-unwind" fn __asan_unregister_globals(_globals: *const u8, _num: usize) {}
195
196	#[doc(hidden)]
197	#[unsafe(no_mangle)]
198	pub extern "C-unwind" fn __asan_report_load1(address: VirtualAddress) {
199		if !cfg!(kasan) { return; }
200
201		do_report(address, "load", 1);
202	}
203
204	#[doc(hidden)]
205	#[unsafe(no_mangle)]
206	pub extern "C-unwind" fn __asan_report_load2(address: VirtualAddress) {
207		if !cfg!(kasan) { return; }
208
209		do_report(address, "load", 2);
210	}
211
212	#[doc(hidden)]
213	#[unsafe(no_mangle)]
214	pub extern "C-unwind" fn __asan_report_load4(address: VirtualAddress) {
215		if !cfg!(kasan) { return; }
216
217		do_report(address, "load", 4);
218	}
219
220	#[doc(hidden)]
221	#[unsafe(no_mangle)]
222	pub extern "C-unwind" fn __asan_report_load8(address: VirtualAddress) {
223		if !cfg!(kasan) { return; }
224
225		do_report(address, "load", 8);
226	}
227
228	#[doc(hidden)]
229	#[unsafe(no_mangle)]
230	pub extern "C-unwind" fn __asan_report_load16(address: VirtualAddress) {
231		if !cfg!(kasan) { return; }
232
233		do_report(address, "load", 16);
234	}
235
236	#[doc(hidden)]
237	#[unsafe(no_mangle)]
238	pub extern "C-unwind" fn __asan_report_load_n(address: VirtualAddress, count: usize) {
239		if !cfg!(kasan) { return; }
240
241		do_report(address, "load", count);
242	}
243
244	fn asan_mem_n(address: VirtualAddress, count: usize, ty: &str) {
245		if !cfg!(kasan) { return; }
246
247		let end = address + count;
248		for byte in (address..end).step_by(8) {
249			let shadow = read_shadow_map_for(byte);
250			if shadow < 0 {
251				do_report(address, ty, count);
252			} else if shadow != 0 {
253				let bytes_left = core::cmp::min(end - byte, 8);
254				if bytes_left > shadow.cast_unsigned() as usize {
255					do_report(address, ty, count);
256				}
257			}
258		}
259	}
260
261	#[doc(hidden)]
262	#[unsafe(no_mangle)]
263	pub extern "C-unwind" fn __asan_load_n(address: VirtualAddress, count: usize) {
264		asan_mem_n(address, count, "load");
265	}
266
267	#[doc(hidden)]
268	#[unsafe(no_mangle)]
269	pub extern "C-unwind" fn __asan_store_n(address: VirtualAddress, count: usize) {
270		asan_mem_n(address, count, "store");
271	}
272
273	#[doc(hidden)]
274	#[unsafe(no_mangle)]
275	pub extern "C-unwind" fn __asan_report_store1(address: VirtualAddress) {
276		if !cfg!(kasan) { return; }
277
278		do_report(address, "store", 1);
279	}
280
281	#[doc(hidden)]
282	#[unsafe(no_mangle)]
283	pub extern "C-unwind" fn __asan_report_store2(address: VirtualAddress) {
284		if !cfg!(kasan) { return; }
285
286		do_report(address, "store", 2);
287	}
288
289	#[doc(hidden)]
290	#[unsafe(no_mangle)]
291	pub extern "C-unwind" fn __asan_report_store4(address: VirtualAddress) {
292		if !cfg!(kasan) { return; }
293
294		do_report(address, "store", 4);
295	}
296
297	#[doc(hidden)]
298	#[unsafe(no_mangle)]
299	pub extern "C-unwind" fn __asan_report_store8(address: VirtualAddress) {
300		if !cfg!(kasan) { return; }
301
302		do_report(address, "store", 8);
303	}
304
305	#[doc(hidden)]
306	#[unsafe(no_mangle)]
307	pub extern "C-unwind" fn __asan_report_store16(address: VirtualAddress) {
308		if !cfg!(kasan) { return; }
309
310		do_report(address, "store", 16);
311	}
312
313	#[doc(hidden)]
314	#[unsafe(no_mangle)]
315	pub extern "C-unwind" fn __asan_report_store_n(address: VirtualAddress, count: usize) {
316		if !cfg!(kasan) { return; }
317
318		do_report(address, "store", count);
319	}
320
321	#[doc(hidden)]
322	#[unsafe(no_mangle)]
323	//#[no_sanitize(address)]
324	pub extern "C-unwind" fn __asan_handle_no_return() {
325		if !cfg!(kasan) { return; }
326
327		/* idk what to do here */
328		/*let rsp: usize;
329		unsafe {
330			core::arch::asm!("mov {}, rsp", out(reg) rsp);
331		}
332		let rsp_shadow = SHADOW_MAP_SHIFT + (rsp >> 3);
333		let _ = writeln!(&mut Serial, "__asan_handle_no_return - clearing from {rsp_shadow:#x} to {SHADOW_MAP_END:#x}");
334		for i in 0..(SHADOW_MAP_END as usize - rsp_shadow) {
335			unsafe { *(rsp_shadow as *mut u8).offset(1) = 0; }
336		}*/
337		warn!("ignoring `__asan_handle_no_return`");
338	}
339
340	/// Marks `count` entries in the shadow map starting at `address` as "stack left redzone"
341	///
342	/// # Safety
343	///
344	/// `address` must be an address in the shadow map
345	#[unsafe(export_name = "__asan_set_shadow_f1")]
346	#[sanitize(address = "off")]
347	#[inline(never)]
348	pub unsafe extern "C-unwind" fn set_shadow_stack_left(address: VirtualAddress, count: usize) {
349		if !cfg!(kasan) { return; }
350
351		#[cfg(debug_assertions)] assert!(address >= SHADOW_MAP_START && address < SHADOW_MAP_END, "Safety violation: {address:#x} is not in the shadow map");
352		unsafe {
353			core::ptr::write_bytes(address.as_ptr(), 0xf1, count);
354		}
355	}
356
357	/// Marks `count` entries in the shadow map starting at `address` as "stack use after scope"
358	///
359	/// # Safety
360	///
361	/// `address` must be an address in the shadow map
362	#[unsafe(export_name = "__asan_set_shadow_f8")]
363	#[sanitize(address = "off")]
364	#[inline(never)]
365	pub unsafe extern "C-unwind" fn set_shadow_use_after_scope(address: VirtualAddress, count: usize) {
366		if !cfg!(kasan) { return; }
367
368		#[cfg(debug_assertions)] assert!(address >= SHADOW_MAP_START && address < SHADOW_MAP_END, "Safety violation: {address:#x} is not in the shadow map");
369		unsafe {
370			core::ptr::write_bytes(address.as_ptr(), 0xf8, count);
371		}
372	}
373
374	/// Marks `count` entries in the shadow map starting at `address` as "accessible"
375	///
376	/// # Safety
377	///
378	/// `address` must be an address in the shadow map
379	#[unsafe(export_name = "__asan_set_shadow_00")]
380	#[sanitize(address = "off")]
381	#[inline(never)]
382	pub unsafe extern "C-unwind" fn set_shadow_free(address: VirtualAddress, count: usize) {
383		if !cfg!(kasan) { return; }
384
385		#[cfg(debug_assertions)] assert!(address >= SHADOW_MAP_START && address < SHADOW_MAP_END, "Safety violation: {address:#x} is not in the shadow map");
386		unsafe {
387			core::ptr::write_bytes(address.as_ptr(), 0, count);
388		}
389	}
390
391	/// Marks `count` entries in the shadow map starting at `address` as "freed virtual memory"
392	///
393	/// # Safety
394	///
395	/// `address` must be an address in the shadow map
396	#[sanitize(address = "off")]
397	#[inline(never)]
398	pub unsafe extern "C-unwind" fn set_shadow_free_vmem(address: VirtualAddress, count: usize) {
399		if !cfg!(kasan) { return; }
400
401		#[cfg(debug_assertions)] assert!(address >= SHADOW_MAP_START && address < SHADOW_MAP_END, "Safety violation: {address:#x} is not in the shadow map");
402		unsafe {
403			core::ptr::write_bytes(address.as_ptr(), 0xc0, count);
404		}
405	}
406
407	/// Marks `count` entries in the shadow map starting at `address` as "uninitialized virtual memory"
408	///
409	/// This is the default value for lazily allocated shadow memory
410	///
411	/// # Safety
412	///
413	/// `address` must be an address in the shadow map
414	#[sanitize(address = "off")]
415	#[inline(never)]
416	pub unsafe extern "C-unwind" fn set_shadow_uninit_vmem(address: VirtualAddress, count: usize) {
417		if !cfg!(kasan) { return; }
418
419		#[cfg(debug_assertions)] assert!(address >= SHADOW_MAP_START && address < SHADOW_MAP_END, "Safety violation: {address:#x} is not in the shadow map");
420		unsafe {
421			core::ptr::write_bytes(address.as_ptr(), 0xc1, count);
422		}
423	}
424
425	/// Marks `count` entries in the shadow map starting at `address` as "heap left redzone"
426	///
427	/// # Safety
428	///
429	/// `address` must be an address in the shadow map
430	#[unsafe(export_name = "__asan_set_shadow_fa")]
431	#[sanitize(address = "off")]
432	#[inline(never)]
433	pub unsafe extern "C-unwind" fn set_shadow_heap_left(address: VirtualAddress, count: usize) {
434		if !cfg!(kasan) { return; }
435
436		#[cfg(debug_assertions)] assert!(address >= SHADOW_MAP_START && address < SHADOW_MAP_END, "Safety violation: {address:#x} is not in the shadow map");
437		unsafe {
438			core::ptr::write_bytes(address.as_ptr(), 0xfa, count);
439		}
440	}
441
442	/// Marks `count` entries in the shadow map starting at `address` as "heap right redzone".
443	///
444	/// # Safety
445	///
446	/// `address` through `address + count` must be addresses in the shadow map.
447	#[sanitize(address = "off")]
448	#[inline(never)]
449	pub unsafe extern "C-unwind" fn set_shadow_heap_right(address: VirtualAddress, count: usize) {
450		if !cfg!(kasan) { return; }
451
452		#[cfg(debug_assertions)] assert!(address >= SHADOW_MAP_START && address < SHADOW_MAP_END, "Safety violation: {address:#x} is not in the shadow map");
453		// SAFETY: `address` through `address + count` are addresses within the shadow map
454		unsafe {
455			core::ptr::write_bytes(address.as_ptr(), 0xfb, count);
456		}
457	}
458
459	/// Marks `count` entries in the shadow map starting at `address` as "heap headers".
460	///
461	/// # Safety
462	///
463	/// `address` through `address + count` must be addresses in the shadow map.
464	#[sanitize(address = "off")]
465	#[inline(never)]
466	pub unsafe extern "C-unwind" fn set_shadow_heap_header(address: VirtualAddress, count: usize) {
467		if !cfg!(kasan) { return; }
468
469		#[cfg(debug_assertions)] assert!(address >= SHADOW_MAP_START && address < SHADOW_MAP_END, "Safety violation: {address:#x} is not in the shadow map");
470		// SAFETY: `address` through `address + count` are addresses within the shadow map
471		unsafe {
472			core::ptr::write_bytes(address.as_ptr(), 0xfc, count);
473		}
474	}
475
476	/// Marks `count` entries in the shadow map starting at `address` as "freed heap memory"
477	///
478	/// # Safety
479	///
480	/// `address` must be an address in the shadow map
481	#[unsafe(export_name = "__asan_set_shadow_fd")]
482	#[sanitize(address = "off")]
483	#[inline(never)]
484	pub unsafe extern "C-unwind" fn set_shadow_heap_free(address: VirtualAddress, count: usize) {
485		if !cfg!(kasan) { return; }
486
487		#[cfg(debug_assertions)] assert!(address >= SHADOW_MAP_START && address < SHADOW_MAP_END, "Safety violation: {address:#x} is not in the shadow map");
488		unsafe {
489			core::ptr::write_bytes(address.as_ptr(), 0xfd, count);
490		}
491	}
492
493	/// Marks the region from `start` to `start + count` as accessible
494	///
495	/// This internally calculates the correct shadow map entries
496	pub fn asan_free_range(start: VirtualAddress, count: usize) {
497		assert!(start.aligned_to(8), "asan free range must be 8 byte aligned");
498		if !cfg!(kasan) { return; }
499
500		assert!(start.is_higher_half(), "asan only covers higher half");
501
502		debug!("zero shadow memory ({:#x} -> {:#x})", mem_to_shadow(start), mem_to_shadow(start) + count_to_shadow(count));
503		unsafe {
504			set_shadow_free(
505				mem_to_shadow(start),
506				count_to_shadow(count),
507			);
508		}
509
510		let last = start + count - 1usize;
511
512		match count % 8 {
513			0 => {},
514			1 => write_shadow_map_for(last, 1),
515			2 => write_shadow_map_for(last, 2),
516			3 => write_shadow_map_for(last, 3),
517			4 => write_shadow_map_for(last, 4),
518			5 => write_shadow_map_for(last, 5),
519			6 => write_shadow_map_for(last, 6),
520			7 => write_shadow_map_for(last, 7),
521			_ => unreachable!("x % 8 < 8"),
522		}
523	}
524}