Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e1b4d7f
fix asan according to aarch64 48-bit VMA
rmalmain Jan 5, 2026
b733546
oops
rmalmain Jan 5, 2026
ff95740
testing stuff
rmalmain Jan 6, 2026
e5fd6d9
verbose missing arch
rmalmain Jan 6, 2026
45ff7d5
fix
rmalmain Jan 6, 2026
caf0799
fix
rmalmain Jan 6, 2026
3ec3e2f
default layout
rmalmain Jan 6, 2026
fb09291
fix
rmalmain Jan 6, 2026
d0a95a4
more details
rmalmain Jan 6, 2026
bb15c7c
more details
rmalmain Jan 6, 2026
26ac3da
more details
rmalmain Jan 6, 2026
fd08e30
fix
rmalmain Jan 6, 2026
5de7868
default vma for aarch64
rmalmain Jan 6, 2026
0dbc4f8
fmt
rmalmain Jan 6, 2026
27e0b9b
fix
rmalmain Jan 6, 2026
2ea50f6
use weak symbols to define shadow base in qemu-libafl-bridge
rmalmain Jan 6, 2026
f586856
Merge branch 'main' into fix_asan
rmalmain Jan 19, 2026
ade7380
use host arch for test
rmalmain Jan 20, 2026
091106d
changed default layout to old one
rmalmain Jan 20, 2026
db186af
docs, fmt
rmalmain Jan 20, 2026
0bd2c2a
nostd
rmalmain Jan 20, 2026
bf9064d
fmt
rmalmain Jan 20, 2026
90f8343
update wrong qemu paths in github action files
rmalmain Jan 20, 2026
5bc20bf
update libafl_qemu_asan_*
rmalmain Jan 20, 2026
55f5737
remove mimalloc from qemu_launcher. it causes asan host issues due to…
rmalmain Jan 23, 2026
356611f
fmt
rmalmain Jan 23, 2026
d70be3d
Merge branch 'main' into fix_asan
rmalmain Jan 23, 2026
b8ba558
clippy
rmalmain Jan 23, 2026
f1368e0
fix clippy
rmalmain Jan 23, 2026
6640017
temporarily ignore qemu_coverage tests
rmalmain Jan 23, 2026
b8482cb
useless now
rmalmain Jan 23, 2026
b6dcd20
ignore qemu_tmin tests for now
rmalmain Jan 23, 2026
38c4c4b
fmt, clippy
rmalmain Jan 23, 2026
3663563
Merge branch 'main' into fix_asan
rmalmain Apr 16, 2026
e903aab
Merge branch 'main' into fix_asan
rmalmain Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion crates/libafl_asan/.cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ linker = "i686-linux-gnu-gcc"
linker = "arm-linux-gnueabi-gcc"
runner = "qemu-arm -L /usr/arm-linux-gnueabi/"


[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
runner = "qemu-aarch64 -L /usr/aarch64-linux-gnu/"
Expand Down
7 changes: 7 additions & 0 deletions crates/libafl_asan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ default = [
"mimalloc",
"test",
"tracking",
"dynamic_layout",
]
## Enable support for the `dlmalloc` allocator backend
dlmalloc = ["dep:dlmalloc"]
Expand All @@ -58,6 +59,8 @@ mimalloc = ["dep:baby-mimalloc"]
test = ["dlmalloc", "guest", "libc"]
## Enable support for memory tracking
tracking = []
## Generate a dynamic shadow layout automatically
dynamic_layout = []

[dependencies]
baby-mimalloc = { version = "0.2.1", default-features = false, features = [
Expand Down Expand Up @@ -95,6 +98,10 @@ hashbrown = { workspace = true, default-features = false }

[build-dependencies]
cc = { version = "1.2.51" }
build-target = "0.8.0"
libc = "0.2.179"
rand = "0.9.2"
page_size = "0.6.0"

[dev-dependencies]
env_logger = { version = "0.11.6" }
Expand Down
294 changes: 293 additions & 1 deletion crates/libafl_asan/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,277 @@
use std::{collections::HashMap, env, fs, ops::RangeInclusive, path::Path, sync::LazyLock};

use build_target::{Arch, Os, PointerWidth, target_arch, target_os, target_pointer_width};
use rand::Rng;

// Default Linux/i386 mapping on i386 machine
// (addresses starting with 0xc0000000 are reserved
// for kernel and thus not sanitized):
// || `[0x38000000, 0xbfffffff]` || HighMem ||
// || `[0x27000000, 0x37ffffff]` || HighShadow ||
// || `[0x24000000, 0x26ffffff]` || ShadowGap ||
// || `[0x20000000, 0x23ffffff]` || LowShadow ||
// || `[0x00000000, 0x1fffffff]` || LowMem ||
const DEFAULT_32B_LAYOUT: TargetShadowLayout = TargetShadowLayout {
high_mem: 0x38000000..=0xbfffffff,
high_shadow: 0x27000000..=0x37ffffff,
shadow_gap: 0x24000000..=0x26ffffff,
low_shadow: 0x20000000..=0x23ffffff,
low_mem: 0x00000000..=0x1fffffff,
};

// Typical shadow mapping on Linux/x86_64 with SHADOW_OFFSET == 0x00007fff8000:
// || `[0x10007fff8000, 0x7fffffffffff]` || HighMem ||
// || `[0x02008fff7000, 0x10007fff7fff]` || HighShadow ||
// || `[0x00008fff7000, 0x02008fff6fff]` || ShadowGap ||
// || `[0x00007fff8000, 0x00008fff6fff]` || LowShadow ||
// || `[0x000000000000, 0x00007fff7fff]` || LowMem ||
const DEFAULT_64B_LAYOUT: TargetShadowLayout = TargetShadowLayout {
high_mem: 0x10007fff8000..=0x7fffffffffff,
high_shadow: 0x02008fff7000..=0x10007fff7fff,
shadow_gap: 0x00008fff7000..=0x02008fff6fff,
low_shadow: 0x00007fff8000..=0x00008fff6fff,
low_mem: 0x000000000000..=0x00007fff7fff,
};

#[expect(clippy::type_complexity)]
static SPECIFIC_LAYOUTS: LazyLock<HashMap<(Arch, Option<Vma>, Os), TargetShadowLayout>> =
LazyLock::new(|| {
let mut layouts = HashMap::new();

// Typical shadow mapping on Linux/x86_64 with SHADOW_OFFSET == 0x00007fff8000:
// || `[0x10007fff8000, 0x7fffffffffff]` || HighMem ||
// || `[0x02008fff7000, 0x10007fff7fff]` || HighShadow ||
// || `[0x00008fff7000, 0x02008fff6fff]` || ShadowGap ||
// || `[0x00007fff8000, 0x00008fff6fff]` || LowShadow ||
// || `[0x000000000000, 0x00007fff7fff]` || LowMem ||
layouts.insert((Arch::X86_64, None, Os::Linux), DEFAULT_64B_LAYOUT.clone());

// Default Linux/i386 mapping on i386 machine
// (addresses starting with 0xc0000000 are reserved
// for kernel and thus not sanitized):
// || `[0x38000000, 0xbfffffff]` || HighMem ||
// || `[0x27000000, 0x37ffffff]` || HighShadow ||
// || `[0x24000000, 0x26ffffff]` || ShadowGap ||
// || `[0x20000000, 0x23ffffff]` || LowShadow ||
// || `[0x00000000, 0x1fffffff]` || LowMem ||
layouts.insert((Arch::X86, None, Os::Linux), DEFAULT_32B_LAYOUT.clone());

// Default Linux/AArch64 (48-bit VMA) mapping:
// || `[0x201000000000, 0xffffffffffff]` || HighMem || 229312GB
// || `[0x041200000000, 0x200fffffffff]` || HighShadow || 28664GB
// || `[0x001200000000, 0x0411ffffffff]` || ShadowGap || 4096GB
// || `[0x001000000000, 0x0011ffffffff]` || LowShadow || 8GB
// || `[0x000000000000, 0x000fffffffff]` || LowMem || 64GB
layouts.insert(
(Arch::AArch64, Some(Vma::Vma48), Os::Linux),
TargetShadowLayout {
high_mem: 0x201000000000..=0xffffffffffff,
high_shadow: 0x041200000000..=0x200fffffffff,
shadow_gap: 0x001200000000..=0x0411ffffffff,
low_shadow: 0x001000000000..=0x0011ffffffff,
low_mem: 0x000000000000..=0x000fffffffff,
},
);

layouts
});

const LAYOUT_TEMPLATE: &str = r#"
use crate::GuestAddr;
use super::ShadowLayout;

#[derive(Debug)]
pub struct DefaultShadowLayout;

impl ShadowLayout for DefaultShadowLayout {
const SHADOW_OFFSET: usize = {shadow_offset};
const LOW_MEM_OFFSET: GuestAddr = {low_mem_offset};
const LOW_MEM_SIZE: usize = {low_mem_size};
const LOW_SHADOW_OFFSET: GuestAddr = {low_shadow_offset};
const LOW_SHADOW_SIZE: usize = {low_shadow_size};
const HIGH_SHADOW_OFFSET: GuestAddr = {high_shadow_offset};
const HIGH_SHADOW_SIZE: usize = {high_shadow_size};
const HIGH_MEM_OFFSET: GuestAddr = {high_mem_offset};
const HIGH_MEM_SIZE: usize = {high_mem_size};

const ALLOC_ALIGN_POW: usize = 3;
const ALLOC_ALIGN_SIZE: usize = 1 << Self::ALLOC_ALIGN_POW;
}
"#;

#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
pub enum Vma {
Vma39,
Vma42,
Vma47,
Vma48,
Vma52,
}

#[derive(Clone, Debug)]
pub struct TargetShadowLayout {
high_mem: RangeInclusive<u64>,
high_shadow: RangeInclusive<u64>,
shadow_gap: RangeInclusive<u64>,
low_shadow: RangeInclusive<u64>,
low_mem: RangeInclusive<u64>,
}

impl TargetShadowLayout {
pub fn low_mem_offset(&self) -> String {
format!("{:#x}", self.low_mem.start())
}

pub fn low_mem_size(&self) -> String {
format!("{:#x}", self.low_mem.clone().count())
}

pub fn low_shadow_offset(&self) -> String {
format!("{:#x}", self.low_shadow.start())
}

pub fn low_shadow_size(&self) -> String {
format!("{:#x}", self.low_shadow.clone().count())
}

pub fn shadow_gap_offset(&self) -> String {
format!("{:#x}", self.shadow_gap.start())
}

pub fn shadow_gap_size(&self) -> String {
format!("{:#x}", self.shadow_gap.clone().count())
}

pub fn high_mem_offset(&self) -> String {
format!("{:#x}", self.high_mem.start())
}

pub fn high_mem_size(&self) -> String {
format!("{:#x}", self.high_mem.clone().count())
}

pub fn high_shadow_offset(&self) -> String {
format!("{:#x}", self.high_shadow.start())
}

pub fn high_shadow_size(&self) -> String {
format!("{:#x}", self.high_shadow.clone().count())
}
}

fn find_max_vaddr_bits<const NB_TRIES: usize>() -> usize {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be an easier way to detect the supported VA space. See "52-bit userspace VAs" at the bottom of this article. https://opensource.com/article/20/12/52-bit-arm64-kernel

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i found the linux doc explaining the same things there: https://www.kernel.org/doc/html/latest/arch/arm64/memory.html

afaik this information is only accessible in kernel land, and not in userland.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the article is found said you can call mmap with a hint value to discover it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from what i understand it doesn't seem to be the exact purpose of calling mmap with ~0UL. it just hints to the kernel you are ready to receive addresses up to 52 bits, but it doesn't mean the kernel will even give you a 52bit address. also, it would not detect smaller vmas (39, 42, etc.), even if i guess this is not very common nowadays.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes. Good point. Might be worth scanning the current address map to see the highest address currently in use as a fallback? Or using the current address space to work out where to request new mappings when probing?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought about that, but i think the problem is a bit more tricky.
according to the documentation, linux will give by default 48 bit addresses, and give 52bit addresses if the application supports it. so i guess you could have an address map that looks like it's 48bits until later on the app requests a (possibly) 52bit address with the ~0UL trick. so i'm not sure it's easy to know in advance if the app will use 48bits or 52bits just by looking at the current address space.
with find_max_vaddr_bits, the idea is to know the 'real' max limit of the vma in the current environment, whatever the app does afterwards. we could end up using the 52-bits layout even though the app only gets 48-bits addresses, but i don't think that's a problem?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking if you know the current max va, then you know any requests to map higher addresses should only fail if the requested address is invalid rather than a collision? Maybe able to determine based on the value of errno too? Eexist vs einval?

let mut rng = rand::rng();
let page_size = page_size::get();

assert_eq!(page_size.count_ones(), 1);

let mut bits_min: usize = page_size.trailing_zeros() as usize; // log2(page_size)
let mut bits_max: usize = usize::BITS as usize; // size in bits of max addressable memory

while bits_min != bits_max {
let bits_current = (bits_min + bits_max) / 2;

let mut is_mappable = false;
for _ in 0..NB_TRIES {
let current_addr_min = 1usize << (bits_current - 1);
let current_addr_max = 1usize << bits_current;
let current_addr_sz = current_addr_max - current_addr_min;

assert_eq!(current_addr_sz % page_size, 0);

let max_page = current_addr_sz / page_size;

let rdm_page = rng.random_range(0..max_page);

let map_addr = current_addr_min + (page_size * rdm_page);

let map_addr_ptr = unsafe {
libc::mmap(
map_addr as *mut libc::c_void,
page_size,
libc::PROT_READ,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
-1,
0,
)
};

if map_addr_ptr != (-1isize as *mut libc::c_void) {
unsafe {
libc::munmap(map_addr_ptr, page_size);
}
is_mappable = true;
break;
}
}

if is_mappable {
bits_min = bits_current + 1;
} else {
bits_max = bits_current;
}
}

bits_min
}

fn get_host_vma() -> Vma {
match find_max_vaddr_bits::<8>() {
39 => Vma::Vma39,
42 => Vma::Vma42,
47 => Vma::Vma47,
48 => Vma::Vma48,
52 => Vma::Vma52,
val => {
panic!("Dynamic layout does not support VMA with {val} bits")
}
}
}

fn guess_vma(arch: &Arch) -> Option<Vma> {
match arch {
Arch::AArch64 => {
let host = env::var_os("HOST").unwrap();
let target = env::var_os("TARGET").unwrap();

if host == target {
Some(get_host_vma())
} else {
let default_vma = Vma::Vma48;
println!(
"cargo:warning=Host and target triplets do not match. Using default VMA: {default_vma:?}"
);
Some(default_vma)
}
}
_ => None,
}
}

fn get_layout() -> TargetShadowLayout {
let arch = target_arch();
let vma = guess_vma(&arch);
let os = target_os();

println!("cargo:warning=Generating layout for environment: {arch} - VMA {vma:?} - {os}.");

if let Some(specific_layout) = SPECIFIC_LAYOUTS.get(&(arch.clone(), vma, os.clone())) {
specific_layout.clone()
} else {
let (default_layout, nb_bits) = match target_pointer_width() {
PointerWidth::U32 => (DEFAULT_32B_LAYOUT.clone(), 32),
PointerWidth::U64 => (DEFAULT_64B_LAYOUT.clone(), 64),
_ => {
panic!("Could not find the right layout for {arch:?} (VMA {vma:?}) {os:?}")
}
};

println!("cargo:warning=Using default layout for {nb_bits} bits architectures.");

default_layout
}
}

fn main() {
//#[cfg(all(feature = "syscalls", not(target_os = "linux")))]
println!("cargo:warning=The feature `linux` can only be used on Linux!");
Expand All @@ -8,7 +282,7 @@ fn main() {
println!("cargo:rerun-if-changed=cc/src/log.c");
println!("cargo:rerun-if-changed=cc/src/vasprintf.c");

if std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() != "windows" {
if env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() != "windows" {
cc::Build::new()
.define("_GNU_SOURCE", None)
.opt_level(3)
Expand Down Expand Up @@ -45,4 +319,22 @@ fn main() {
.include("cc/include/")
.file("cc/src/log.c")
.compile("log");

let layout = get_layout();

let gen_layout = LAYOUT_TEMPLATE
.to_string()
.replace("{shadow_offset}", &layout.low_shadow_offset())
.replace("{low_mem_offset}", &layout.low_mem_offset())
.replace("{low_mem_size}", &layout.low_mem_size())
.replace("{low_shadow_offset}", &layout.low_shadow_offset())
.replace("{low_shadow_size}", &layout.low_shadow_size())
.replace("{high_shadow_offset}", &layout.high_shadow_offset())
.replace("{high_shadow_size}", &layout.high_shadow_size())
.replace("{high_mem_offset}", &layout.high_mem_offset())
.replace("{high_mem_size}", &layout.high_mem_size());

let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("gen_layout.rs");
fs::write(&dest_path, gen_layout).unwrap();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ use libafl_asan::{
frontend::{AllocatorFrontend, default::DefaultFrontend},
},
mmap::unix::MmapRegion,
shadow::{
Shadow,
guest::{DefaultShadowLayout, GuestShadow},
},
shadow::{Shadow, guest::GuestShadow, layout::DefaultShadowLayout},
tracking::guest::GuestTracking,
};
use libfuzzer_sys::fuzz_target;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ use libafl_asan::{
GuestAddr,
allocator::frontend::{AllocatorFrontend, default::DefaultFrontend},
mmap::{Mmap, unix::MmapRegion},
shadow::{
Shadow,
guest::{DefaultShadowLayout, GuestShadow},
},
shadow::{Shadow, guest::GuestShadow, layout::DefaultShadowLayout},
tracking::guest::GuestTracking,
};
use libfuzzer_sys::fuzz_target;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use libafl_asan::{
mmap::libc::LibcMmap,
shadow::{
PoisonType, Shadow,
guest::{DefaultShadowLayout, GuestShadow, GuestShadowError},
guest::{GuestShadow, GuestShadowError},
layout::DefaultShadowLayout,
},
symbols::dlsym::{DlSymSymbols, LookupTypeNext},
};
Expand Down
Loading
Loading