* Bring back python bindings for sugar,qemu (partially revert #2005) * sugarman, won't you hurry * Test?
This commit is contained in:
parent
e8fe5bb614
commit
f19302c9b1
25
.github/workflows/build_and_test.yml
vendored
25
.github/workflows/build_and_test.yml
vendored
@ -280,6 +280,31 @@ jobs:
|
||||
- name: Run smoke test
|
||||
run: ./libafl_concolic/test/smoke_test.sh
|
||||
|
||||
python-bindings:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
- name: Remove existing clang and LLVM
|
||||
run: sudo apt purge llvm* clang*
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@v1
|
||||
with:
|
||||
directory: ${{ runner.temp }}/llvm
|
||||
version: 17
|
||||
- name: Install deps
|
||||
run: sudo apt-get install -y ninja-build python3-dev python3-pip python3-venv libz3-dev
|
||||
- name: Install maturin
|
||||
run: python3 -m pip install maturin
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run a maturin build
|
||||
run: export LLVM_CONFIG=llvm-config-16 && cd ./bindings/pylibafl && python3 -m venv .env && . .env/bin/activate && pip install --upgrade --force-reinstall . && ./test.sh
|
||||
- name: Run python test
|
||||
run: . ./bindings/pylibafl/.env/bin/activate && cd ./fuzzers/baby_fuzzer && python3 baby_fuzzer.py 2>&1 | grep "Bye"
|
||||
|
||||
fuzzers:
|
||||
strategy:
|
||||
matrix:
|
||||
|
1
bindings/pylibafl/.gitignore
vendored
Normal file
1
bindings/pylibafl/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist/
|
23
bindings/pylibafl/Cargo.toml
Normal file
23
bindings/pylibafl/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "pylibafl"
|
||||
version = "0.11.2"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.18.3", features = ["extension-module"] }
|
||||
pyo3-log = "0.8.1"
|
||||
libafl_sugar = { path = "../../libafl_sugar", version = "0.11.2", features = ["python"] }
|
||||
libafl_bolts = { path = "../../libafl_bolts", version = "0.11.2", features = ["python"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libafl_qemu = { path = "../../libafl_qemu", version = "0.11.2", features = ["python"] }
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { version = "0.17" }
|
||||
|
||||
[lib]
|
||||
name = "pylibafl"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
31
bindings/pylibafl/README.md
Normal file
31
bindings/pylibafl/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# How to use python bindings
|
||||
## First time setup
|
||||
```bash
|
||||
# Install maturin
|
||||
pip install maturin
|
||||
# Create virtual environment
|
||||
python3 -m venv .env
|
||||
```
|
||||
## Build bindings
|
||||
```
|
||||
# Activate virtual environment
|
||||
source .env/bin/activate
|
||||
# Build python module
|
||||
maturin develop
|
||||
```
|
||||
This is going to install `pylibafl` python module into this venv.
|
||||
|
||||
## Use bindings
|
||||
### Example: Running baby_fuzzer in fuzzers/baby_fuzzer/baby_fuzzer.py
|
||||
First, make sure the python virtual environment is activated. If not, run `source .env/bin/activate
|
||||
`. Running `pip freeze` at this point should display the following (versions may differ):
|
||||
```
|
||||
maturin==0.12.6
|
||||
pylibafl==0.7.0
|
||||
toml==0.10.2
|
||||
```
|
||||
Then simply run
|
||||
```
|
||||
python PATH_TO_BABY_FUZZER/baby_fuzzer.py
|
||||
```
|
||||
The crashes directory will be created in the directory from which you ran the command.
|
26
bindings/pylibafl/pyproject.toml
Normal file
26
bindings/pylibafl/pyproject.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[build-system]
|
||||
requires = ["maturin[patchelf]>=0.14.10,<0.15"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "PyLibAFL"
|
||||
version = "0.10.1"
|
||||
description = "Advanced Fuzzing Library for Python"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = {text = "Apache-2.0"}
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Rust",
|
||||
"Topic :: Security",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
repository = "https://github.com/AFLplusplus/LibAFL.git"
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "pylibafl"
|
||||
manifest-path = "Cargo.toml"
|
||||
python-source = "python"
|
||||
all-features = true
|
33
bindings/pylibafl/src/lib.rs
Normal file
33
bindings/pylibafl/src/lib.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use pyo3::prelude::*;
|
||||
|
||||
/// Setup python modules for `libafl_qemu` and `libafl_sugar`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns error if python libafl setup failed.
|
||||
#[pymodule]
|
||||
#[pyo3(name = "pylibafl")]
|
||||
pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
pyo3_log::init();
|
||||
|
||||
let modules = py.import("sys")?.getattr("modules")?;
|
||||
|
||||
let sugar_module = PyModule::new(py, "sugar")?;
|
||||
libafl_sugar::python_module(py, sugar_module)?;
|
||||
m.add_submodule(sugar_module)?;
|
||||
modules.set_item("pylibafl.sugar", sugar_module)?;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let qemu_module = PyModule::new(py, "qemu")?;
|
||||
libafl_qemu::python_module(py, qemu_module)?;
|
||||
m.add_submodule(qemu_module)?;
|
||||
modules.set_item("pylibafl.qemu", qemu_module)?;
|
||||
}
|
||||
|
||||
let bolts_module = PyModule::new(py, "libafl_bolts")?;
|
||||
libafl_bolts::pybind::python_module(py, bolts_module)?;
|
||||
m.add_submodule(bolts_module)?;
|
||||
modules.set_item("pylibafl.libafl_bolts", bolts_module)?;
|
||||
|
||||
Ok(())
|
||||
}
|
7
bindings/pylibafl/test.py
Normal file
7
bindings/pylibafl/test.py
Normal file
@ -0,0 +1,7 @@
|
||||
import pylibafl.sugar as sugar
|
||||
import ctypes
|
||||
import platform
|
||||
|
||||
print("Starting to fuzz from python!")
|
||||
fuzzer = sugar.InMemoryBytesCoverageSugar(input_dirs=["./in"], output_dir="out", broker_port=1337, cores=[0,1])
|
||||
fuzzer.run(lambda b: print("foo"))
|
14
bindings/pylibafl/test.sh
Executable file
14
bindings/pylibafl/test.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
mkdir in || true
|
||||
echo "a" > ./in/a
|
||||
|
||||
timeout 10 python3 ./test.py
|
||||
export exit_code=$?
|
||||
if [ $exit_code -eq 124 ]; then
|
||||
# 124 = timeout happened. All good.
|
||||
exit 0
|
||||
else
|
||||
exit $exit_code
|
||||
fi
|
||||
|
14
fuzzers/python_qemu/fuzz.c
Normal file
14
fuzzers/python_qemu/fuzz.c
Normal file
@ -0,0 +1,14 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
// printf("Got %ld bytes.\n", Size);
|
||||
if (Size >= 4 && *(uint32_t *)Data == 0xaabbccdd) { abort(); }
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
char buf[10] = {0};
|
||||
LLVMFuzzerTestOneInput(buf, 10);
|
||||
}
|
43
fuzzers/python_qemu/fuzzer.py
Normal file
43
fuzzers/python_qemu/fuzzer.py
Normal file
@ -0,0 +1,43 @@
|
||||
# from the maturin venv, after running 'maturin develop' in the pylibafl directory
|
||||
|
||||
from pylibafl import sugar, qemu
|
||||
import lief
|
||||
|
||||
MAX_SIZE = 0x100
|
||||
BINARY_PATH = './a.out'
|
||||
|
||||
emu = qemu.Emulator(['qemu-x86_64', BINARY_PATH], [])
|
||||
|
||||
elf = lief.parse(BINARY_PATH)
|
||||
test_one_input = elf.get_function_address("LLVMFuzzerTestOneInput")
|
||||
if elf.is_pie:
|
||||
test_one_input += emu.load_addr()
|
||||
print('LLVMFuzzerTestOneInput @ 0x%x' % test_one_input)
|
||||
|
||||
emu.set_breakpoint(test_one_input)
|
||||
emu.run()
|
||||
|
||||
sp = emu.read_reg(qemu.regs.Rsp)
|
||||
print('SP = 0x%x' % sp)
|
||||
|
||||
retaddr = int.from_bytes(emu.read_mem(sp, 8), 'little')
|
||||
print('RET = 0x%x' % retaddr)
|
||||
|
||||
inp = emu.map_private(0, MAX_SIZE, qemu.mmap.ReadWrite)
|
||||
assert(inp > 0)
|
||||
|
||||
emu.remove_breakpoint(test_one_input)
|
||||
emu.set_breakpoint(retaddr)
|
||||
|
||||
def harness(b):
|
||||
if len(b) > MAX_SIZE:
|
||||
b = b[:MAX_SIZE]
|
||||
emu.write_mem(inp, b)
|
||||
emu.write_reg(qemu.regs.Rsi, len(b))
|
||||
emu.write_reg(qemu.regs.Rdi, inp)
|
||||
emu.write_reg(qemu.regs.Rsp, sp)
|
||||
emu.write_reg(qemu.regs.Rip, test_one_input)
|
||||
emu.run()
|
||||
|
||||
fuzz = sugar.QemuBytesCoverageSugar(['./in'], './out', 3456, [0,1,2,3])
|
||||
fuzz.run(emu, harness)
|
@ -35,6 +35,9 @@ derive = ["libafl_derive"]
|
||||
## If set, libafl_bolt's `rand` implementations will implement `rand::Rng`
|
||||
rand_trait = ["rand_core"]
|
||||
|
||||
## Will build the `pyo3` bindings
|
||||
python = ["pyo3", "std"]
|
||||
|
||||
## Expose `libafl::prelude` for direct access to all types without additional `use` directives
|
||||
prelude = []
|
||||
|
||||
@ -113,6 +116,8 @@ uuid = { version = "1.4", optional = true, features = ["serde", "v4"] }
|
||||
clap = {version = "4.5", features = ["derive", "wrap_help"], optional = true} # CLI parsing, for libafl_bolts::cli / the `cli` feature
|
||||
log = "0.4.20"
|
||||
|
||||
pyo3 = { version = "0.18", optional = true, features = ["serde", "macros"] }
|
||||
|
||||
# optional-dev deps (change when target.'cfg(accessible(::std))'.test-dependencies will be stable)
|
||||
serial_test = { version = "2", optional = true, default-features = false, features = ["logging"] }
|
||||
|
||||
|
@ -611,6 +611,22 @@ impl From<windows::core::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
impl From<pyo3::PyErr> for Error {
|
||||
fn from(err: pyo3::PyErr) -> Self {
|
||||
pyo3::Python::with_gil(|py| {
|
||||
if err.matches(
|
||||
py,
|
||||
pyo3::types::PyType::new::<pyo3::exceptions::PyKeyboardInterrupt>(py),
|
||||
) {
|
||||
Self::shutting_down()
|
||||
} else {
|
||||
Self::illegal_state(format!("Python exception: {err:?}"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(nightly), feature = "std"))]
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
@ -1022,6 +1038,148 @@ pub unsafe fn set_error_print_panic_hook(new_stderr: RawFd) {
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[allow(missing_docs)]
|
||||
pub mod pybind {
|
||||
|
||||
use pyo3::{pymodule, types::PyModule, PyResult, Python};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_me_body {
|
||||
($wrapper:expr, $name:ident, $body:block, $wrapper_type:ident, { $($wrapper_option:tt),* }) => {
|
||||
match &$wrapper {
|
||||
$(
|
||||
$wrapper_type::$wrapper_option(py_wrapper) => {
|
||||
Python::with_gil(|py| -> PyResult<_> {
|
||||
let borrowed = py_wrapper.borrow(py);
|
||||
let $name = &borrowed.inner;
|
||||
Ok($body)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
($wrapper:expr, $name:ident, $body:block, $wrapper_type:ident, { $($wrapper_option:tt),* }, { $($wrapper_optional:tt($pw:ident) => $code_block:block)* }) => {
|
||||
match &$wrapper {
|
||||
$(
|
||||
$wrapper_type::$wrapper_option(py_wrapper) => {
|
||||
Python::with_gil(|py| -> PyResult<_> {
|
||||
let borrowed = py_wrapper.borrow(py);
|
||||
let $name = &borrowed.inner;
|
||||
Ok($body)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
)*
|
||||
$($wrapper_type::$wrapper_optional($pw) => { $code_block })*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_me_mut_body {
|
||||
($wrapper:expr, $name:ident, $body:block, $wrapper_type:ident, { $($wrapper_option:tt),*}) => {
|
||||
match &mut $wrapper {
|
||||
$(
|
||||
$wrapper_type::$wrapper_option(py_wrapper) => {
|
||||
Python::with_gil(|py| -> PyResult<_> {
|
||||
let mut borrowed = py_wrapper.borrow_mut(py);
|
||||
let $name = &mut borrowed.inner;
|
||||
Ok($body)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
($wrapper:expr, $name:ident, $body:block, $wrapper_type:ident, { $($wrapper_option:tt),*}, { $($wrapper_optional:tt($pw:ident) => $code_block:block)* }) => {
|
||||
match &mut $wrapper {
|
||||
$(
|
||||
$wrapper_type::$wrapper_option(py_wrapper) => {
|
||||
Python::with_gil(|py| -> PyResult<_> {
|
||||
let mut borrowed = py_wrapper.borrow_mut(py);
|
||||
let $name = &mut borrowed.inner;
|
||||
Ok($body)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
)*
|
||||
$($wrapper_type::$wrapper_optional($pw) => { $code_block })*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_serde_pyobjectwrapper {
|
||||
($struct_name:ident, $inner:tt) => {
|
||||
const _: () = {
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
impl Serialize for $struct_name {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let buf = Python::with_gil(|py| -> PyResult<Vec<u8>> {
|
||||
let pickle = PyModule::import(py, "pickle")?;
|
||||
let buf: Vec<u8> =
|
||||
pickle.getattr("dumps")?.call1((&self.$inner,))?.extract()?;
|
||||
Ok(buf)
|
||||
})
|
||||
.unwrap();
|
||||
serializer.serialize_bytes(&buf)
|
||||
}
|
||||
}
|
||||
|
||||
struct PyObjectVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for PyObjectVisitor {
|
||||
type Value = $struct_name;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter
|
||||
.write_str("Expecting some bytes to deserialize from the Python side")
|
||||
}
|
||||
|
||||
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let obj = Python::with_gil(|py| -> PyResult<PyObject> {
|
||||
let pickle = PyModule::import(py, "pickle")?;
|
||||
let obj = pickle.getattr("loads")?.call1((v,))?.to_object(py);
|
||||
Ok(obj)
|
||||
})
|
||||
.unwrap();
|
||||
Ok($struct_name::new(obj))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for $struct_name {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_byte_buf(PyObjectVisitor)
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
#[pyo3(name = "libafl_bolts")]
|
||||
/// Register the classes to the python module
|
||||
pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
crate::rands::pybind::register(py, m)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
@ -433,3 +433,92 @@ mod tests {
|
||||
log::info!("random value: {}", mutator.rng.next_u32());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[allow(clippy::unnecessary_fallible_conversions, unused_qualifications)]
|
||||
#[allow(missing_docs)]
|
||||
/// `Rand` Python bindings
|
||||
pub mod pybind {
|
||||
use pyo3::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Rand;
|
||||
use crate::{current_nanos, rands::StdRand};
|
||||
|
||||
#[pyclass(unsendable, name = "StdRand")]
|
||||
#[allow(clippy::unsafe_derive_deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// Python class for StdRand
|
||||
pub struct PythonStdRand {
|
||||
/// Rust wrapped StdRand object
|
||||
pub inner: StdRand,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PythonStdRand {
|
||||
#[staticmethod]
|
||||
fn with_current_nanos() -> Self {
|
||||
Self {
|
||||
inner: StdRand::with_seed(current_nanos()),
|
||||
}
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn with_seed(seed: u64) -> Self {
|
||||
Self {
|
||||
inner: StdRand::with_seed(seed),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_rand(slf: Py<Self>) -> PythonRand {
|
||||
PythonRand::new_std(slf)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
enum PythonRandWrapper {
|
||||
Std(Py<PythonStdRand>),
|
||||
}
|
||||
|
||||
/// Rand Trait binding
|
||||
#[pyclass(unsendable, name = "Rand")]
|
||||
#[allow(clippy::unsafe_derive_deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PythonRand {
|
||||
wrapper: PythonRandWrapper,
|
||||
}
|
||||
|
||||
macro_rules! unwrap_me_mut {
|
||||
($wrapper:expr, $name:ident, $body:block) => {
|
||||
crate::unwrap_me_mut_body!($wrapper, $name, $body, PythonRandWrapper, { Std })
|
||||
};
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PythonRand {
|
||||
#[staticmethod]
|
||||
fn new_std(py_std_rand: Py<PythonStdRand>) -> Self {
|
||||
Self {
|
||||
wrapper: PythonRandWrapper::Std(py_std_rand),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rand for PythonRand {
|
||||
fn set_seed(&mut self, seed: u64) {
|
||||
unwrap_me_mut!(self.wrapper, r, { r.set_seed(seed) });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> u64 {
|
||||
unwrap_me_mut!(self.wrapper, r, { r.next() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the classes to the python module
|
||||
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<PythonStdRand>()?;
|
||||
m.add_class::<PythonRand>()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ edition = "2021"
|
||||
categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["document-features", "default", "x86_64", "usermode"]
|
||||
features = ["document-features", "default", "python", "x86_64", "usermode"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
@ -24,6 +24,8 @@ document-features = ["dep:document-features"]
|
||||
#! ### General Features
|
||||
## Find injections during fuzzing
|
||||
injections = ["serde_yaml", "toml"]
|
||||
## Python bindings support
|
||||
python = ["pyo3", "pyo3-build-config", "libafl_qemu_sys/python"]
|
||||
## Fork support
|
||||
fork = ["libafl/fork"]
|
||||
## Build libqasan for address sanitization
|
||||
@ -85,10 +87,12 @@ paste = "1"
|
||||
enum-map = "2.7"
|
||||
serde_yaml = { version = "0.8", optional = true } # For parsing the injections yaml file
|
||||
toml = { version = "0.4.2", optional = true } # For parsing the injections toml file
|
||||
pyo3 = { version = "0.18", optional = true }
|
||||
# Document all features of this crate (for `cargo doc`)
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { version = "0.18", optional = true }
|
||||
rustversion = "1.0"
|
||||
bindgen = "0.69"
|
||||
|
||||
|
@ -30,6 +30,8 @@ be = []
|
||||
usermode = []
|
||||
systemmode = []
|
||||
|
||||
python = ["pyo3", "pyo3-build-config"]
|
||||
|
||||
slirp = [ "systemmode", "libafl_qemu_build/slirp" ] # build qemu with host libslirp (for user networking)
|
||||
shared = [ "libafl_qemu_build/shared" ]
|
||||
|
||||
@ -41,6 +43,8 @@ num_enum = "0.7"
|
||||
libc = "0.2"
|
||||
strum = "0.25"
|
||||
strum_macros = "0.25"
|
||||
pyo3 = { version = "0.18", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
libafl_qemu_build = { path = "../libafl_qemu_build", version = "0.11.2" }
|
||||
pyo3-build-config = { version = "0.18", optional = true }
|
||||
|
@ -121,6 +121,7 @@ pub type GuestVirtAddr = crate::vaddr;
|
||||
pub type GuestHwAddrInfo = crate::qemu_plugin_hwaddr;
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "python", pyclass(unsendable))]
|
||||
pub struct MapInfo {
|
||||
start: GuestAddr,
|
||||
end: GuestAddr,
|
||||
@ -207,6 +208,7 @@ extern_c_checked! {
|
||||
pub fn libafl_qemu_gdb_reply(buf: *const u8, len: usize);
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "python", pymethods)]
|
||||
impl MapInfo {
|
||||
#[must_use]
|
||||
pub fn start(&self) -> GuestAddr {
|
||||
@ -282,3 +284,11 @@ impl MmapPerms {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
impl IntoPy<PyObject> for MmapPerms {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let n: i32 = self.into();
|
||||
n.into_py(py)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ use std::sync::OnceLock;
|
||||
use capstone::arch::BuildsCapstone;
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::aarch64::*;
|
||||
|
||||
@ -71,6 +73,14 @@ impl Regs {
|
||||
pub const Lr: Regs = Regs::X30;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
impl IntoPy<PyObject> for Regs {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let n: i32 = self.into();
|
||||
n.into_py(py)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an ARM64 ArchCapstoneBuilder
|
||||
pub fn capstone() -> capstone::arch::arm64::ArchCapstoneBuilder {
|
||||
capstone::Capstone::new()
|
||||
|
@ -3,6 +3,8 @@ use std::sync::OnceLock;
|
||||
use capstone::arch::BuildsCapstone;
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::arm::*;
|
||||
|
||||
@ -61,6 +63,14 @@ impl Regs {
|
||||
pub const Cpsr: Regs = Regs::R25;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
impl IntoPy<PyObject> for Regs {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let n: i32 = self.into();
|
||||
n.into_py(py)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an ARM ArchCapstoneBuilder
|
||||
pub fn capstone() -> capstone::arch::arm::ArchCapstoneBuilder {
|
||||
capstone::Capstone::new()
|
||||
|
@ -352,6 +352,9 @@ impl From<libafl_qemu_sys::MemOpIdx> for MemAccessInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
pub const SKIP_EXEC_HOOK: u64 = u64::MAX;
|
||||
|
||||
pub use libafl_qemu_sys::{CPUArchState, CPUState};
|
||||
@ -360,11 +363,33 @@ use crate::sync_backdoor::{SyncBackdoor, SyncBackdoorError};
|
||||
|
||||
// syshook_ret
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "python", pyclass)]
|
||||
#[cfg_attr(feature = "python", derive(FromPyObject))]
|
||||
pub struct SyscallHookResult {
|
||||
pub retval: GuestAddr,
|
||||
pub skip_syscall: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[pymethods]
|
||||
impl SyscallHookResult {
|
||||
#[new]
|
||||
#[must_use]
|
||||
pub fn new(value: Option<GuestAddr>) -> Self {
|
||||
value.map_or(
|
||||
Self {
|
||||
retval: 0,
|
||||
skip_syscall: false,
|
||||
},
|
||||
|v| Self {
|
||||
retval: v,
|
||||
skip_syscall: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "python"))]
|
||||
impl SyscallHookResult {
|
||||
#[must_use]
|
||||
pub fn new(value: Option<GuestAddr>) -> Self {
|
||||
@ -625,25 +650,25 @@ pub struct HookData(u64);
|
||||
|
||||
impl<T> From<Pin<&mut T>> for HookData {
|
||||
fn from(value: Pin<&mut T>) -> Self {
|
||||
unsafe { HookData(transmute::<Pin<&mut T>, u64>(value)) }
|
||||
unsafe { HookData(core::mem::transmute(value)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Pin<&T>> for HookData {
|
||||
fn from(value: Pin<&T>) -> Self {
|
||||
unsafe { HookData(transmute::<Pin<&T>, u64>(value)) }
|
||||
unsafe { HookData(core::mem::transmute(value)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<&'static mut T> for HookData {
|
||||
fn from(value: &'static mut T) -> Self {
|
||||
unsafe { HookData(transmute::<&mut T, u64>(value)) }
|
||||
unsafe { HookData(core::mem::transmute(value)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<&'static T> for HookData {
|
||||
fn from(value: &'static T) -> Self {
|
||||
unsafe { HookData(transmute::<&T, u64>(value)) }
|
||||
unsafe { HookData(core::mem::transmute(value)) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -903,7 +928,7 @@ impl Qemu {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(clippy::missing_transmute_annotations)]
|
||||
|
||||
fn post_run(&self) -> Result<QemuExitReason, QemuExitReasonError> {
|
||||
let exit_reason = unsafe { libafl_get_exit_reason() };
|
||||
if exit_reason.is_null() {
|
||||
@ -1718,3 +1743,190 @@ where
|
||||
// self.qemu.write_function_argument(conv, idx, val)
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
pub mod pybind {
|
||||
use pyo3::{exceptions::PyValueError, prelude::*, types::PyInt};
|
||||
|
||||
use super::{GuestAddr, GuestUsize, MmapPerms, SyscallHookResult};
|
||||
|
||||
static mut PY_SYSCALL_HOOK: Option<PyObject> = None;
|
||||
static mut PY_GENERIC_HOOKS: Vec<(GuestAddr, PyObject)> = vec![];
|
||||
|
||||
extern "C" fn py_syscall_hook_wrapper(
|
||||
_data: u64,
|
||||
sys_num: i32,
|
||||
a0: u64,
|
||||
a1: u64,
|
||||
a2: u64,
|
||||
a3: u64,
|
||||
a4: u64,
|
||||
a5: u64,
|
||||
a6: u64,
|
||||
a7: u64,
|
||||
) -> SyscallHookResult {
|
||||
unsafe { PY_SYSCALL_HOOK.as_ref() }.map_or_else(
|
||||
|| SyscallHookResult::new(None),
|
||||
|obj| {
|
||||
let args = (sys_num, a0, a1, a2, a3, a4, a5, a6, a7);
|
||||
Python::with_gil(|py| {
|
||||
let ret = obj.call1(py, args).expect("Error in the syscall hook");
|
||||
let any = ret.as_ref(py);
|
||||
if any.is_none() {
|
||||
SyscallHookResult::new(None)
|
||||
} else {
|
||||
let a: Result<&PyInt, _> = any.downcast();
|
||||
if let Ok(i) = a {
|
||||
SyscallHookResult::new(Some(
|
||||
i.extract().expect("Invalid syscall hook return value"),
|
||||
))
|
||||
} else {
|
||||
SyscallHookResult::extract(any)
|
||||
.expect("The syscall hook must return a SyscallHookResult")
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
extern "C" fn py_generic_hook_wrapper(idx: u64, _pc: GuestAddr) {
|
||||
let obj = unsafe { &PY_GENERIC_HOOKS[idx as usize].1 };
|
||||
Python::with_gil(|py| {
|
||||
obj.call0(py).expect("Error in the hook");
|
||||
});
|
||||
}
|
||||
|
||||
#[pyclass(unsendable)]
|
||||
pub struct Qemu {
|
||||
pub qemu: super::Qemu,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Qemu {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[new]
|
||||
fn new(args: Vec<String>, env: Vec<(String, String)>) -> PyResult<Qemu> {
|
||||
let qemu = super::Qemu::init(&args, &env)
|
||||
.map_err(|e| PyValueError::new_err(format!("{e}")))?;
|
||||
|
||||
Ok(Qemu { qemu })
|
||||
}
|
||||
|
||||
fn write_mem(&self, addr: GuestAddr, buf: &[u8]) {
|
||||
unsafe {
|
||||
self.qemu.write_mem(addr, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_mem(&self, addr: GuestAddr, size: usize) -> Vec<u8> {
|
||||
let mut buf = vec![0; size];
|
||||
unsafe {
|
||||
self.qemu.read_mem(addr, &mut buf);
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
fn num_regs(&self) -> i32 {
|
||||
self.qemu.num_regs()
|
||||
}
|
||||
|
||||
fn write_reg(&self, reg: i32, val: GuestUsize) -> PyResult<()> {
|
||||
self.qemu.write_reg(reg, val).map_err(PyValueError::new_err)
|
||||
}
|
||||
|
||||
fn read_reg(&self, reg: i32) -> PyResult<GuestUsize> {
|
||||
self.qemu.read_reg(reg).map_err(PyValueError::new_err)
|
||||
}
|
||||
|
||||
fn set_breakpoint(&self, addr: GuestAddr) {
|
||||
self.qemu.set_breakpoint(addr);
|
||||
}
|
||||
|
||||
fn entry_break(&self, addr: GuestAddr) {
|
||||
self.qemu.entry_break(addr);
|
||||
}
|
||||
|
||||
fn remove_breakpoint(&self, addr: GuestAddr) {
|
||||
self.qemu.remove_breakpoint(addr);
|
||||
}
|
||||
|
||||
fn g2h(&self, addr: GuestAddr) -> u64 {
|
||||
self.qemu.g2h::<*const u8>(addr) as u64
|
||||
}
|
||||
|
||||
fn h2g(&self, addr: u64) -> GuestAddr {
|
||||
self.qemu.h2g(addr as *const u8)
|
||||
}
|
||||
|
||||
fn binary_path(&self) -> String {
|
||||
self.qemu.binary_path().to_owned()
|
||||
}
|
||||
|
||||
fn load_addr(&self) -> GuestAddr {
|
||||
self.qemu.load_addr()
|
||||
}
|
||||
|
||||
fn flush_jit(&self) {
|
||||
self.qemu.flush_jit();
|
||||
}
|
||||
|
||||
fn map_private(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<GuestAddr> {
|
||||
if let Ok(p) = MmapPerms::try_from(perms) {
|
||||
self.qemu
|
||||
.map_private(addr, size, p)
|
||||
.map_err(PyValueError::new_err)
|
||||
} else {
|
||||
Err(PyValueError::new_err("Invalid perms"))
|
||||
}
|
||||
}
|
||||
|
||||
fn map_fixed(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<GuestAddr> {
|
||||
if let Ok(p) = MmapPerms::try_from(perms) {
|
||||
self.qemu
|
||||
.map_fixed(addr, size, p)
|
||||
.map_err(PyValueError::new_err)
|
||||
} else {
|
||||
Err(PyValueError::new_err("Invalid perms"))
|
||||
}
|
||||
}
|
||||
|
||||
fn mprotect(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<()> {
|
||||
if let Ok(p) = MmapPerms::try_from(perms) {
|
||||
self.qemu
|
||||
.mprotect(addr, size, p)
|
||||
.map_err(PyValueError::new_err)
|
||||
} else {
|
||||
Err(PyValueError::new_err("Invalid perms"))
|
||||
}
|
||||
}
|
||||
|
||||
fn unmap(&self, addr: GuestAddr, size: usize) -> PyResult<()> {
|
||||
self.qemu.unmap(addr, size).map_err(PyValueError::new_err)
|
||||
}
|
||||
|
||||
fn set_syscall_hook(&self, hook: PyObject) {
|
||||
unsafe {
|
||||
PY_SYSCALL_HOOK = Some(hook);
|
||||
}
|
||||
self.qemu
|
||||
.add_pre_syscall_hook(0u64, py_syscall_hook_wrapper);
|
||||
}
|
||||
|
||||
fn set_hook(&self, addr: GuestAddr, hook: PyObject) {
|
||||
unsafe {
|
||||
let idx = PY_GENERIC_HOOKS.len();
|
||||
PY_GENERIC_HOOKS.push((addr, hook));
|
||||
self.qemu
|
||||
.set_hook(idx as u64, addr, py_generic_hook_wrapper, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_hooks_at(&self, addr: GuestAddr) -> usize {
|
||||
unsafe {
|
||||
PY_GENERIC_HOOKS.retain(|(a, _)| *a != addr);
|
||||
}
|
||||
self.qemu.remove_hooks_at(addr, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,12 @@ use std::{cell::OnceCell, slice::from_raw_parts, str::from_utf8_unchecked};
|
||||
|
||||
use libafl_qemu_sys::{
|
||||
exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk,
|
||||
libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, libafl_set_brk,
|
||||
mmap_next_start, read_self_maps, strlen, GuestAddr, GuestUsize, MapInfo, MmapPerms,
|
||||
VerifyAccess,
|
||||
libafl_load_addr, libafl_maps_next, libafl_qemu_run, libafl_set_brk, mmap_next_start,
|
||||
read_self_maps, strlen, GuestAddr, GuestUsize, MapInfo, MmapPerms, VerifyAccess,
|
||||
};
|
||||
use libc::c_int;
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use crate::{
|
||||
emu::{HasExecutions, State},
|
||||
@ -23,6 +24,7 @@ pub enum HandlerError {
|
||||
MultipleInputDefinition,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "python", pyclass(unsendable))]
|
||||
pub struct GuestMaps {
|
||||
maps_root: *const c_void,
|
||||
maps_node: *const c_void,
|
||||
@ -59,6 +61,17 @@ impl Iterator for GuestMaps {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[pymethods]
|
||||
impl GuestMaps {
|
||||
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
|
||||
slf
|
||||
}
|
||||
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyObject> {
|
||||
Python::with_gil(|py| slf.next().map(|x| x.into_py(py)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GuestMaps {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
|
@ -2,6 +2,8 @@ use std::sync::OnceLock;
|
||||
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
|
||||
use crate::{sync_backdoor::BackdoorArgs, CallingConvention};
|
||||
|
@ -3,6 +3,8 @@ use std::{mem::size_of, sync::OnceLock};
|
||||
use capstone::arch::BuildsCapstone;
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::x86::*;
|
||||
|
||||
@ -47,6 +49,14 @@ impl Regs {
|
||||
pub const Pc: Regs = Regs::Eip;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
impl IntoPy<PyObject> for Regs {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let n: i32 = self.into();
|
||||
n.into_py(py)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an X86 ArchCapstoneBuilder
|
||||
pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
|
||||
capstone::Capstone::new()
|
||||
|
@ -131,3 +131,33 @@ pub fn filter_qemu_args() -> Vec<String> {
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[pymodule]
|
||||
#[pyo3(name = "libafl_qemu")]
|
||||
#[allow(clippy::items_after_statements, clippy::too_many_lines)]
|
||||
pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
let regsm = PyModule::new(py, "regs")?;
|
||||
for r in Regs::iter() {
|
||||
let v: i32 = r.into();
|
||||
regsm.add(&format!("{r:?}"), v)?;
|
||||
}
|
||||
m.add_submodule(regsm)?;
|
||||
|
||||
let mmapm = PyModule::new(py, "mmap")?;
|
||||
for r in emu::MmapPerms::iter() {
|
||||
let v: i32 = r.into();
|
||||
mmapm.add(&format!("{r:?}"), v)?;
|
||||
}
|
||||
m.add_submodule(mmapm)?;
|
||||
|
||||
m.add_class::<emu::MapInfo>()?;
|
||||
m.add_class::<emu::GuestMaps>()?;
|
||||
m.add_class::<emu::SyscallHookResult>()?;
|
||||
m.add_class::<emu::pybind::Qemu>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ use std::sync::OnceLock;
|
||||
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::mips::*;
|
||||
|
||||
@ -70,6 +72,14 @@ impl Regs {
|
||||
pub const Zero: Regs = Regs::R0;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
impl IntoPy<PyObject> for Regs {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let n: i32 = self.into();
|
||||
n.into_py(py)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an MIPS ArchCapstoneBuilder
|
||||
pub fn capstone() -> capstone::arch::mips::ArchCapstoneBuilder {
|
||||
capstone::Capstone::new().mips()
|
||||
|
@ -2,6 +2,8 @@ use std::sync::OnceLock;
|
||||
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::powerpc::*;
|
||||
|
||||
@ -110,6 +112,14 @@ impl Regs {
|
||||
pub const Sp: Regs = Regs::R1;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
impl IntoPy<PyObject> for Regs {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let n: i32 = self.into();
|
||||
n.into_py(py)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an MIPS ArchCapstoneBuilder
|
||||
pub fn capstone() -> capstone::arch::ppc::ArchCapstoneBuilder {
|
||||
capstone::Capstone::new().ppc()
|
||||
|
@ -3,6 +3,8 @@ use std::{mem::size_of, sync::OnceLock};
|
||||
use capstone::arch::BuildsCapstone;
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::x86_64::*;
|
||||
|
||||
@ -55,6 +57,14 @@ impl Regs {
|
||||
pub const Pc: Regs = Regs::Rip;
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
impl IntoPy<PyObject> for Regs {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
let n: i32 = self.into();
|
||||
n.into_py(py)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an X86 `ArchCapstoneBuilder`
|
||||
#[must_use]
|
||||
pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
|
||||
|
@ -9,12 +9,14 @@ readme = "../README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["fuzzing"]
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[features]
|
||||
python = ["pyo3", "libafl_qemu/python", "pyo3-build-config"]
|
||||
default = []
|
||||
|
||||
# for libafl_qemu
|
||||
@ -27,12 +29,16 @@ mips = ["libafl_qemu/mips"] # build qemu for mips (el, use with the 'be' feature
|
||||
ppc = ["libafl_qemu/ppc"] # build qemu for powerpc
|
||||
hexagon = ["libafl_qemu/hexagon"] # build qemu for hexagon
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { version = "0.18", optional = true }
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../libafl", version = "0.11.2" }
|
||||
libafl_bolts = { path = "../libafl_bolts", version = "0.11.2" }
|
||||
libafl_targets = { path = "../libafl_targets", version = "0.11.2" }
|
||||
|
||||
typed-builder = "0.16" # Implement the builder pattern at compiletime
|
||||
pyo3 = { version = "0.18", optional = true }
|
||||
log = "0.4.20"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
|
4
libafl_sugar/build.rs
Normal file
4
libafl_sugar/build.rs
Normal file
@ -0,0 +1,4 @@
|
||||
fn main() {
|
||||
#[cfg(feature = "python")]
|
||||
pyo3_build_config::add_extension_module_link_args();
|
||||
}
|
@ -299,3 +299,80 @@ impl<'a> ForkserverBytesCoverageSugar<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The python bindings for this sugar
|
||||
#[cfg(feature = "python")]
|
||||
pub mod pybind {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use libafl_bolts::core_affinity::Cores;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use crate::forkserver;
|
||||
|
||||
/// Python bindings for the `LibAFL` forkserver sugar
|
||||
#[pyclass(unsendable)]
|
||||
#[derive(Debug)]
|
||||
struct ForkserverBytesCoverageSugar {
|
||||
input_dirs: Vec<PathBuf>,
|
||||
output_dir: PathBuf,
|
||||
broker_port: u16,
|
||||
cores: Cores,
|
||||
use_cmplog: Option<bool>,
|
||||
iterations: Option<u64>,
|
||||
tokens_file: Option<PathBuf>,
|
||||
timeout: Option<u64>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl ForkserverBytesCoverageSugar {
|
||||
/// Create a new [`ForkserverBytesCoverageSugar`]
|
||||
#[new]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
input_dirs: Vec<PathBuf>,
|
||||
output_dir: PathBuf,
|
||||
broker_port: u16,
|
||||
cores: Vec<usize>,
|
||||
use_cmplog: Option<bool>,
|
||||
iterations: Option<u64>,
|
||||
tokens_file: Option<PathBuf>,
|
||||
timeout: Option<u64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
input_dirs,
|
||||
output_dir,
|
||||
broker_port,
|
||||
cores: cores.into(),
|
||||
use_cmplog,
|
||||
iterations,
|
||||
tokens_file,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the fuzzer
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn run(&self, program: String, arguments: Vec<String>) {
|
||||
forkserver::ForkserverBytesCoverageSugar::builder()
|
||||
.input_dirs(&self.input_dirs)
|
||||
.output_dir(self.output_dir.clone())
|
||||
.broker_port(self.broker_port)
|
||||
.cores(&self.cores)
|
||||
.program(program)
|
||||
.arguments(&arguments)
|
||||
.use_cmplog(self.use_cmplog)
|
||||
.timeout(self.timeout)
|
||||
.tokens_file(self.tokens_file.clone())
|
||||
.iterations(self.iterations)
|
||||
.build()
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the module
|
||||
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<ForkserverBytesCoverageSugar>()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -348,3 +348,87 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Python bindings for this sugar
|
||||
#[cfg(feature = "python")]
|
||||
pub mod pybind {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use libafl_bolts::core_affinity::Cores;
|
||||
use pyo3::{prelude::*, types::PyBytes};
|
||||
|
||||
use crate::inmemory;
|
||||
|
||||
/// In-Memory fuzzing made easy.
|
||||
/// Use this sugar for scaling `libfuzzer`-style fuzzers.
|
||||
#[pyclass(unsendable)]
|
||||
#[derive(Debug)]
|
||||
struct InMemoryBytesCoverageSugar {
|
||||
input_dirs: Vec<PathBuf>,
|
||||
output_dir: PathBuf,
|
||||
broker_port: u16,
|
||||
cores: Cores,
|
||||
use_cmplog: Option<bool>,
|
||||
iterations: Option<u64>,
|
||||
tokens_file: Option<PathBuf>,
|
||||
timeout: Option<u64>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl InMemoryBytesCoverageSugar {
|
||||
/// Create a new [`InMemoryBytesCoverageSugar`]
|
||||
#[new]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
input_dirs: Vec<PathBuf>,
|
||||
output_dir: PathBuf,
|
||||
broker_port: u16,
|
||||
cores: Vec<usize>,
|
||||
use_cmplog: Option<bool>,
|
||||
iterations: Option<u64>,
|
||||
tokens_file: Option<PathBuf>,
|
||||
timeout: Option<u64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
input_dirs,
|
||||
output_dir,
|
||||
broker_port,
|
||||
cores: cores.into(),
|
||||
use_cmplog,
|
||||
iterations,
|
||||
tokens_file,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the fuzzer
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn run(&self, harness: PyObject) {
|
||||
inmemory::InMemoryBytesCoverageSugar::builder()
|
||||
.input_dirs(&self.input_dirs)
|
||||
.output_dir(self.output_dir.clone())
|
||||
.broker_port(self.broker_port)
|
||||
.cores(&self.cores)
|
||||
.harness(|buf| {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
let args = (PyBytes::new(py, buf),); // TODO avoid copy
|
||||
harness.call1(py, args)?;
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
})
|
||||
.use_cmplog(self.use_cmplog)
|
||||
.timeout(self.timeout)
|
||||
.tokens_file(self.tokens_file.clone())
|
||||
.iterations(self.iterations)
|
||||
.build()
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the module
|
||||
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<InMemoryBytesCoverageSugar>()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -78,3 +78,23 @@ pub const DEFAULT_TIMEOUT_SECS: u64 = 1200;
|
||||
/// Default cache size for the corpus in memory.
|
||||
/// Anything else will be on disk.
|
||||
pub const CORPUS_CACHE_SIZE: usize = 4096;
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
/// The sugar python module
|
||||
#[cfg(feature = "python")]
|
||||
#[pymodule]
|
||||
#[pyo3(name = "libafl_sugar")]
|
||||
pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
inmemory::pybind::register(py, m)?;
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
qemu::pybind::register(py, m)?;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
forkserver::pybind::register(py, m)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -437,3 +437,86 @@ where
|
||||
launcher.build().launch().expect("Launcher failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// python bindings for this sugar
|
||||
#[cfg(feature = "python")]
|
||||
pub mod pybind {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use libafl_bolts::core_affinity::Cores;
|
||||
use libafl_qemu::emu::pybind::Qemu;
|
||||
use pyo3::{prelude::*, types::PyBytes};
|
||||
|
||||
use crate::qemu;
|
||||
|
||||
#[pyclass(unsendable)]
|
||||
#[derive(Debug)]
|
||||
struct QemuBytesCoverageSugar {
|
||||
input_dirs: Vec<PathBuf>,
|
||||
output_dir: PathBuf,
|
||||
broker_port: u16,
|
||||
cores: Cores,
|
||||
use_cmplog: Option<bool>,
|
||||
iterations: Option<u64>,
|
||||
tokens_file: Option<PathBuf>,
|
||||
timeout: Option<u64>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl QemuBytesCoverageSugar {
|
||||
/// Create a new [`QemuBytesCoverageSugar`]
|
||||
#[new]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
input_dirs: Vec<PathBuf>,
|
||||
output_dir: PathBuf,
|
||||
broker_port: u16,
|
||||
cores: Vec<usize>,
|
||||
use_cmplog: Option<bool>,
|
||||
iterations: Option<u64>,
|
||||
tokens_file: Option<PathBuf>,
|
||||
timeout: Option<u64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
input_dirs,
|
||||
output_dir,
|
||||
broker_port,
|
||||
cores: cores.into(),
|
||||
use_cmplog,
|
||||
iterations,
|
||||
tokens_file,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the fuzzer
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn run(&self, qemu: &Qemu, harness: PyObject) {
|
||||
qemu::QemuBytesCoverageSugar::builder()
|
||||
.input_dirs(&self.input_dirs)
|
||||
.output_dir(self.output_dir.clone())
|
||||
.broker_port(self.broker_port)
|
||||
.cores(&self.cores)
|
||||
.harness(|buf| {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
let args = (PyBytes::new(py, buf),); // TODO avoid copy
|
||||
harness.call1(py, args)?;
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
})
|
||||
.use_cmplog(self.use_cmplog)
|
||||
.timeout(self.timeout)
|
||||
.tokens_file(self.tokens_file.clone())
|
||||
.iterations(self.iterations)
|
||||
.build()
|
||||
.run(&qemu.qemu);
|
||||
}
|
||||
}
|
||||
|
||||
/// Register this class
|
||||
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<QemuBytesCoverageSugar>()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user