Use ShMemProvider for forkserver (#3249)

* Add android CI

* update

* Move together

* Use shmem for forkserver several pointers

* Fix clippy

* Fix

* Make shmem's into_raw private

* Log error message in forkserver

* Fix clippy

* Fix clippy

---------

Co-authored-by: mio <mio@lazym.io>
Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
EvianZhang 2025-05-19 18:17:53 +08:00 committed by GitHub
parent 74a5362c15
commit 0015254071
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 140 additions and 77 deletions

View File

@ -641,7 +641,7 @@ jobs:
- name: Clippy
run: cargo clippy --tests --all --exclude libafl_nyx --exclude symcc_runtime --exclude runtime_test
android:
ubuntu-cross-android-arm64:
runs-on: ubuntu-24.04
steps:
- uses: dtolnay/rust-toolchain@stable
@ -657,6 +657,27 @@ jobs:
- name: Build Android
run: cd libafl && PYO3_CROSS_PYTHON_VERSION=$(python3 -c "print('{}.{}'.format(__import__('sys').version_info.major, __import__('sys').version_info.minor))") cargo ndk -t arm64-v8a build --release
ubuntu-cross-android-x86_64:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/workflows/ubuntu-prepare
- uses: Swatinem/rust-cache@v2
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r27c
add-to-path: false
- name: cargo-ndk
run: cargo install cargo-ndk
- name: cargo android targets
run: |
rustup target add x86_64-linux-android
- name: Build Android
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
run: cargo ndk -t x86_64 build
#run: cargo build --target aarch64-linux-android
# TODO: Figure out how to properly build stuff with clang
#- name: Add clang path to $PATH env

View File

@ -1,19 +1,31 @@
use libafl_bolts::shmem::{ShMemProvider, StdShMemProvider};
use libafl_targets::{
map_input_shared_memory, map_shared_memory, start_forkserver, MaybePersistentForkserverParent,
};
#[no_mangle]
pub extern "C" fn libafl_start_forkserver() {
let mut shm_provider = match StdShMemProvider::new() {
Ok(shm_provider) => shm_provider,
Err(err) => {
eprintln!("Forkserver failed to create shared memory provider: {err}");
std::process::exit(1);
}
};
// Map shared memory region for the edge coverage map
if map_shared_memory().is_err() {
if let Err(err) = map_shared_memory(&mut shm_provider) {
eprintln!("Forkserver failed to create edge map: {err}");
std::process::exit(1);
}
// Map shared memory region for input and its len
if map_input_shared_memory().is_err() {
if let Err(err) = map_input_shared_memory(&mut shm_provider) {
eprintln!("Forkserver failed to create input map: {err}");
std::process::exit(1);
};
}
// Start the forkserver
if start_forkserver(&mut MaybePersistentForkserverParent::new()).is_err() {
if let Err(err) = start_forkserver(&mut MaybePersistentForkserverParent::new()) {
eprintln!("Forkserver unexpected error: {err}");
std::process::exit(1);
};
}
}

View File

@ -1,19 +1,31 @@
use libafl_bolts::shmem::{ShMemProvider, StdShMemProvider};
use libafl_targets::{
map_input_shared_memory, map_shared_memory, start_forkserver, MaybePersistentForkserverParent,
};
#[no_mangle]
pub extern "C" fn libafl_start_forkserver() {
let mut shm_provider = match StdShMemProvider::new() {
Ok(shm_provider) => shm_provider,
Err(err) => {
eprintln!("Forkserver failed to create shared memory provider: {err}");
std::process::exit(1);
}
};
// Map shared memory region for the edge coverage map
if map_shared_memory().is_err() {
if let Err(err) = map_shared_memory(&mut shm_provider) {
eprintln!("Forkserver failed to create edge map: {err}");
std::process::exit(1);
}
// Map shared memory region for input and its len
if map_input_shared_memory().is_err() {
if let Err(err) = map_input_shared_memory(&mut shm_provider) {
eprintln!("Forkserver failed to create input map: {err}");
std::process::exit(1);
};
}
// Start the forkserver
if start_forkserver(&mut MaybePersistentForkserverParent::new()).is_err() {
if let Err(err) = start_forkserver(&mut MaybePersistentForkserverParent::new()) {
eprintln!("Forkserver unexpected error: {err}");
std::process::exit(1);
};
}
}

View File

@ -143,9 +143,11 @@ fn report_error_and_exit(status: i32) -> Result<(), Error> {
}
/// The length of header bytes which tells shmem size
const SHMEM_FUZZ_HDR_SIZE: usize = 4;
const MAX_INPUT_SIZE_DEFAULT: usize = 1024 * 1024;
const MIN_INPUT_SIZE_DEFAULT: usize = 1;
pub const SHMEM_FUZZ_HDR_SIZE: usize = 4;
/// Maximum default length for input
pub const MAX_INPUT_SIZE_DEFAULT: usize = 1024 * 1024;
/// Minimum default length for input
pub const MIN_INPUT_SIZE_DEFAULT: usize = 1;
/// Environment variable key for shared memory id for input and its len
pub const SHM_FUZZ_ENV_VAR: &str = "__AFL_SHM_FUZZ_ID";
/// Environment variable key for the page size (at least/usually `testcase_size_max + sizeof::<u32>()`)

View File

@ -6,24 +6,31 @@ use std::{
sync::OnceLock,
};
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
use libafl::executors::forkserver::FS_NEW_OPT_AUTODTCT;
#[cfg(feature = "cmplog")]
use libafl::executors::forkserver::SHM_CMPLOG_ENV_VAR;
use libafl::{
Error,
executors::forkserver::{
FORKSRV_FD, FS_ERROR_SHM_OPEN, FS_NEW_OPT_AUTODTCT, FS_NEW_OPT_MAPSIZE,
FS_NEW_OPT_SHDMEM_FUZZ, FS_NEW_VERSION_MAX, FS_OPT_ERROR, SHM_CMPLOG_ENV_VAR, SHM_ENV_VAR,
SHM_FUZZ_ENV_VAR,
AFL_MAP_SIZE_ENV_VAR, FORKSRV_FD, FS_ERROR_SHM_OPEN, FS_NEW_OPT_MAPSIZE,
FS_NEW_OPT_SHDMEM_FUZZ, FS_NEW_VERSION_MAX, FS_OPT_ERROR, MAX_INPUT_SIZE_DEFAULT,
SHM_ENV_VAR, SHM_FUZZ_ENV_VAR, SHM_FUZZ_MAP_SIZE_ENV_VAR, SHMEM_FUZZ_HDR_SIZE,
},
};
use libafl_bolts::os::{ChildHandle, ForkResult};
use libafl_bolts::{
os::{ChildHandle, ForkResult},
shmem::{ShMem, ShMemId, ShMemProvider},
};
use nix::{
sys::signal::{SigHandler, Signal},
unistd::Pid,
};
#[cfg(feature = "cmplog")]
use crate::cmps::CMPLOG_MAP_PTR;
#[cfg(feature = "cmplog_extended_instrumentation")]
use crate::cmps::EXTENDED_CMPLOG_MAP_PTR;
#[cfg(feature = "cmplog")]
use crate::cmps::{AflppCmpLogMap, CMPLOG_MAP_PTR};
use crate::coverage::{__afl_map_size, EDGES_MAP_PTR, INPUT_LENGTH_PTR, INPUT_PTR, SHM_FUZZING};
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
@ -54,6 +61,7 @@ fn write_to_forkserver(message: &[u8]) -> Result<(), Error> {
}
Ok(())
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
fn write_all_to_forkserver(message: &[u8]) -> Result<(), Error> {
let mut remain_len = message.len();
while remain_len > 0 {
@ -89,6 +97,39 @@ fn read_u32_from_forkserver() -> Result<u32, Error> {
Ok(u32::from_ne_bytes(buf))
}
/// Consume current shared memory structure, and get the raw pointer to
/// this shared memory.
///
/// Note that calling this method will result in a memory leak.
fn shmem_into_raw<T: Sized>(shmem: impl ShMem) -> *mut T {
let mut manually_dropped = core::mem::ManuallyDrop::new(shmem);
manually_dropped.as_mut_ptr().cast()
}
fn map_shared_memory_common<SHM: ShMemProvider>(
shmem_provider: &mut SHM,
map_env_var: &str,
map_size_env_var: &str,
map_size_default_fallback: usize,
) -> Result<*mut u8, Error> {
let Ok(id_str) = std::env::var(map_env_var) else {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_argument(format!(
"Error: shared memory variable {map_env_var} is not set"
)));
};
let map_size = if let Ok(map_size_str) = std::env::var(map_size_env_var) {
map_size_str
.parse()
.map_err(|_| Error::illegal_argument(format!("Invalid {map_size_env_var} value")))?
} else {
map_size_default_fallback
};
let shmem = shmem_provider.shmem_from_id_and_size(ShMemId::from_string(&id_str), map_size)?;
Ok(shmem_into_raw(shmem))
}
/// Guard [`map_shared_memory`] is invoked only once
static SHM_MAP_GUARD: OnceLock<()> = OnceLock::new();
@ -97,31 +138,18 @@ static SHM_MAP_GUARD: OnceLock<()> = OnceLock::new();
///
/// If anything failed, the forkserver will be notified with
/// [`FS_ERROR_SHM_OPEN`].
pub fn map_shared_memory() -> Result<(), Error> {
pub fn map_shared_memory<SHM: ShMemProvider>(shmem_provider: &mut SHM) -> Result<(), Error> {
if SHM_MAP_GUARD.set(()).is_err() {
return Err(Error::illegal_state("shared memory has been mapped before"));
}
map_shared_memory_internal()
map_shared_memory_internal(shmem_provider)
}
fn map_shared_memory_internal() -> Result<(), Error> {
let Ok(id_str) = std::env::var(SHM_ENV_VAR) else {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_argument(
"Error: variable for edge coverage shared memory is not set",
));
};
let Ok(shm_id) = id_str.parse() else {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_argument("Invalid __AFL_SHM_ID value"));
};
let map = unsafe { libc::shmat(shm_id, core::ptr::null(), 0) };
if map.is_null() || core::ptr::eq(map, libc::MAP_FAILED) {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_state("shmat for map"));
}
fn map_shared_memory_internal<SHM: ShMemProvider>(shmem_provider: &mut SHM) -> Result<(), Error> {
let target_ptr =
map_shared_memory_common(shmem_provider, SHM_ENV_VAR, AFL_MAP_SIZE_ENV_VAR, 65536)?;
unsafe {
EDGES_MAP_PTR = map.cast();
EDGES_MAP_PTR = target_ptr;
}
Ok(())
}
@ -134,32 +162,23 @@ static INPUT_SHM_MAP_GUARD: OnceLock<()> = OnceLock::new();
///
/// If anything failed, the forkserver will be notified with
/// [`FS_ERROR_SHM_OPEN`].
pub fn map_input_shared_memory() -> Result<(), Error> {
pub fn map_input_shared_memory<SHM: ShMemProvider>(shmem_provider: &mut SHM) -> Result<(), Error> {
if INPUT_SHM_MAP_GUARD.set(()).is_err() {
return Err(Error::illegal_state("shared memory has been mapped before"));
}
map_input_shared_memory_internal()
map_input_shared_memory_internal(shmem_provider)
}
fn map_input_shared_memory_internal() -> Result<(), Error> {
let Ok(id_str) = std::env::var(SHM_FUZZ_ENV_VAR) else {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_argument(
"Error: variable for fuzzing shared memory is not set",
));
};
let Ok(shm_id) = id_str.parse() else {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_argument("Invalid __AFL_SHM_FUZZ_ID value"));
};
let map = unsafe { libc::shmat(shm_id, core::ptr::null(), 0) };
if map.is_null() || core::ptr::eq(map, libc::MAP_FAILED) {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_state(
"Could not access fuzzing shared memory",
));
}
let map: *mut u32 = map.cast();
fn map_input_shared_memory_internal<SHM: ShMemProvider>(
shmem_provider: &mut SHM,
) -> Result<(), Error> {
let target_ptr = map_shared_memory_common(
shmem_provider,
SHM_FUZZ_ENV_VAR,
SHM_FUZZ_MAP_SIZE_ENV_VAR,
MAX_INPUT_SIZE_DEFAULT + SHMEM_FUZZ_HDR_SIZE,
)?;
let map: *mut u32 = target_ptr.cast();
unsafe {
INPUT_LENGTH_PTR = map;
INPUT_PTR = map.add(1).cast();
@ -177,36 +196,33 @@ static CMPLOG_SHM_MAP_GUARD: OnceLock<()> = OnceLock::new();
/// If anything failed, the forkserver will be notified with
/// [`FS_ERROR_SHM_OPEN`].
#[cfg(feature = "cmplog")]
pub fn map_cmplog_shared_memory() -> Result<(), Error> {
pub fn map_cmplog_shared_memory<SHM: ShMemProvider>(shmem_provider: &mut SHM) -> Result<(), Error> {
if CMPLOG_SHM_MAP_GUARD.set(()).is_err() {
return Err(Error::illegal_state("shared memory has been mapped before"));
}
map_cmplog_shared_memory_internal()
map_cmplog_shared_memory_internal(shmem_provider)
}
#[cfg(feature = "cmplog")]
fn map_cmplog_shared_memory_internal() -> Result<(), Error> {
fn map_cmplog_shared_memory_internal<SHM: ShMemProvider>(
shmem_provider: &mut SHM,
) -> Result<(), Error> {
let Ok(id_str) = std::env::var(SHM_CMPLOG_ENV_VAR) else {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_argument(
"Error: variable for cmplog shared memory is not set",
));
return Err(Error::illegal_argument(format!(
"Error: shared memory variable {SHM_CMPLOG_ENV_VAR} is not set"
)));
};
let Ok(shm_id) = id_str.parse() else {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_argument("Invalid __AFL_CMPLOG_SHM_ID value"));
};
let map = unsafe { libc::shmat(shm_id, core::ptr::null(), 0) };
if map.is_null() || core::ptr::eq(map, libc::MAP_FAILED) {
write_error_to_forkserver(FS_ERROR_SHM_OPEN)?;
return Err(Error::illegal_state("shmat for map"));
}
let map_size = size_of::<AflppCmpLogMap>();
let shmem = shmem_provider.shmem_from_id_and_size(ShMemId::from_string(&id_str), map_size)?;
let target_ptr = shmem_into_raw(shmem);
unsafe {
CMPLOG_MAP_PTR = map.cast();
CMPLOG_MAP_PTR = target_ptr;
}
#[cfg(feature = "cmplog_extended_instrumentation")]
unsafe {
EXTENDED_CMPLOG_MAP_PTR = map.cast();
EXTENDED_CMPLOG_MAP_PTR = target_ptr;
}
Ok(())
}