Skip to content

Commit 0f744a3

Browse files
authored
Add Python Grammar Loader for Nautilus (#2635)
* add python grammar loader for Nautilus * fmt * fmt toml * add python to macos CI deps * install python * fmt * ci * clippy * fix workflow * fmt * fix baby nautilus * fix nautilus sync * fmt * fmt * clippy * typo * fix miri * remove pyo3 from workspace to packages which need it and make it optional * go back to AsRef<Path> for nautilus grammar loading * replace hardcoded python flags for macos build * typo * taplo fmt * revert formatting of libafl_qemu_arch * ci * typo * remove expects in NautilusContext::from_file and make them Results * remove not(miri) clause in test * try and fix python build fir ios and android * again * android * tmate * fix android build * document load_python_grammar * log if python or json when loading nautilus grammar * make nautilus optional * add nautilus as feature to forkserver_simple_nautilus
1 parent 58fad2b commit 0f744a3

File tree

17 files changed

+114
-35
lines changed

17 files changed

+114
-35
lines changed

.github/workflows/build_and_test.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,13 @@ jobs:
5353
run: ./scripts/check_for_blobs.sh
5454
- name: Build libafl debug
5555
run: cargo build -p libafl
56-
- name: Test the book
56+
- name: Test the book (Linux)
5757
# TODO: fix books test fail with updated windows-rs
58-
if: runner.os != 'Windows'
58+
if: runner.os == 'Linux'
5959
run: cd docs && mdbook test -L ../target/debug/deps
60+
- name: Test the book (MacOS)
61+
if: runner.os == 'MacOS'
62+
run: cd docs && mdbook test -L ../target/debug/deps $(python3-config --ldflags | cut -d ' ' -f1)
6063
- name: Run tests
6164
run: cargo test
6265
- name: Test libafl no_std
@@ -468,7 +471,7 @@ jobs:
468471
- name: Add nightly clippy
469472
run: rustup toolchain install nightly --component clippy --allow-downgrade && rustup default nightly
470473
- name: Install deps
471-
run: brew install z3 gtk+3
474+
run: brew install z3 gtk+3 python
472475
- name: Install cxxbridge
473476
run: cargo install cxxbridge-cmd
474477
- uses: actions/checkout@v4
@@ -491,7 +494,7 @@ jobs:
491494
- uses: actions/checkout@v4
492495
- uses: Swatinem/rust-cache@v2
493496
- name: Build iOS
494-
run: cargo build --target aarch64-apple-ios && cd libafl_frida && cargo build --target aarch64-apple-ios && cd ..
497+
run: PYO3_CROSS_PYTHON_VERSION=$(python3 -c "print('{}.{}'.format(__import__('sys').version_info.major, __import__('sys').version_info.minor))") cargo build --target aarch64-apple-ios && cd libafl_frida && cargo build --target aarch64-apple-ios && cd ..
495498

496499
android:
497500
runs-on: ubuntu-24.04
@@ -509,7 +512,7 @@ jobs:
509512
- uses: actions/checkout@v4
510513
- uses: Swatinem/rust-cache@v2
511514
- name: Build Android
512-
run: cd libafl && cargo ndk -t arm64-v8a build --release
515+
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
513516

514517
#run: cargo build --target aarch64-linux-android
515518
# TODO: Figure out how to properly build stuff with clang

Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,6 @@ paste = "1.0.15"
6969
postcard = { version = "1.0.10", features = [
7070
"alloc",
7171
], default-features = false } # no_std compatible serde serialization format
72-
pyo3 = "0.22.3"
73-
pyo3-build-config = "0.22.3"
74-
pyo3-log = "0.11.0"
7572
rangemap = "1.5.1"
7673
regex = "1.10.6"
7774
rustversion = "1.0.17"

fuzzers/binary_only/frida_libpng/harness_win.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#include <string.h>
55

66
extern "C" __declspec(dllexport) size_t
7-
LLVMFuzzerTestOneInput(const char *data, unsigned int len) {
7+
LLVMFuzzerTestOneInput(const char *data, unsigned int len) {
88
if (data[0] == 'b') {
99
if (data[1] == 'a') {
1010
if (data[2] == 'd') {

fuzzers/structure_aware/baby_fuzzer_nautilus/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ fn signals_set(idx: usize) {
3535

3636
#[allow(clippy::similar_names)]
3737
pub fn main() {
38-
let context = NautilusContext::from_file(15, "grammar.json");
38+
let context = NautilusContext::from_file(15, "grammar.json").unwrap();
3939
let mut bytes = vec![];
4040

4141
// The closure that we want to fuzz

fuzzers/structure_aware/forkserver_simple_nautilus/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ opt-level = 3
1818
[dependencies]
1919
clap = { version = "4.5.18", features = ["derive"] }
2020
env_logger = "0.11.5"
21-
libafl = { path = "../../../libafl", features = ["std", "derive"] }
21+
libafl = { path = "../../../libafl", features = ["std", "derive", "nautilus"] }
2222
libafl_bolts = { path = "../../../libafl_bolts" }
2323
log = { version = "0.4.22", features = ["release_max_level_info"] }
2424
nix = { version = "0.29.0", features = ["signal"] }

fuzzers/structure_aware/forkserver_simple_nautilus/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ pub fn main() {
108108
// Create an observation channel to keep track of the execution time
109109
let time_observer = TimeObserver::new("time");
110110

111-
let context = NautilusContext::from_file(15, opt.grammar);
111+
let context = NautilusContext::from_file(15, opt.grammar).unwrap();
112112

113113
// Feedback to rate the interestingness of an input
114114
// This one is composed by two Feedbacks in OR

fuzzers/structure_aware/nautilus_sync/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ pub extern "C" fn libafl_main() {
118118
// The Monitor trait define how the fuzzer stats are reported to the user
119119
let monitor = SimpleMonitor::new(|s| println!("{s}"));
120120

121-
let context = NautilusContext::from_file(15, "grammar.json");
121+
let context = NautilusContext::from_file(15, "grammar.json").unwrap();
122122

123123
let mut event_converter = opt.bytes_broker_port.map(|port| {
124124
LlmpEventConverter::builder()

libafl/Cargo.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ rustc-args = ["--cfg", "docsrs"]
2727

2828
[features]
2929
default = [
30-
"nautilus",
3130
"std",
3231
"derive",
3332
"llmp_compression",
@@ -180,7 +179,7 @@ llmp_small_maps = [
180179
nautilus = [
181180
"std",
182181
"serde_json/std",
183-
"pyo3",
182+
"dep:pyo3",
184183
"rand_trait",
185184
"regex-syntax",
186185
"regex",
@@ -261,8 +260,8 @@ arrayvec = { version = "0.7.6", optional = true, default-features = false } # us
261260
const_format = "0.2.33" # used for providing helpful compiler output
262261
const_panic = "0.2.9" # similarly, for formatting const panic output
263262

264-
pyo3 = { workspace = true, optional = true } # For nautilus
265-
regex-syntax = { version = "0.8.4", optional = true } # For nautilus
263+
pyo3 = { version = "0.22.3", features = ["gil-refs"], optional = true }
264+
regex-syntax = { version = "0.8.4", optional = true } # For nautilus
266265

267266
# optional-dev deps (change when target.'cfg(accessible(::std))'.test-dependencies will be stable)
268267
serial_test = { workspace = true, optional = true, default-features = false, features = [

libafl/src/common/nautilus/grammartec/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ pub mod chunkstore;
22
pub mod context;
33
pub mod mutator;
44
pub mod newtypes;
5+
#[cfg(feature = "nautilus")]
6+
pub mod python_grammar_loader;
57
pub mod recursion_info;
68
pub mod rule;
79
pub mod tree;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use std::{string::String, vec::Vec};
2+
3+
use pyo3::{prelude::*, pyclass, types::IntoPyDict};
4+
5+
use crate::{nautilus::grammartec::context::Context, Error};
6+
7+
#[pyclass]
8+
struct PyContext {
9+
ctx: Context,
10+
}
11+
impl PyContext {
12+
fn get_context(&self) -> Context {
13+
self.ctx.clone()
14+
}
15+
}
16+
17+
#[pymethods]
18+
impl PyContext {
19+
#[new]
20+
fn new() -> Self {
21+
PyContext {
22+
ctx: Context::new(),
23+
}
24+
}
25+
26+
fn rule(&mut self, py: Python, nt: &str, format: &Bound<PyAny>) -> PyResult<()> {
27+
if let Ok(s) = format.extract::<&str>() {
28+
self.ctx.add_rule(nt, s.as_bytes());
29+
} else if let Ok(s) = format.extract::<&[u8]>() {
30+
self.ctx.add_rule(nt, s);
31+
} else {
32+
return Err(pyo3::exceptions::PyValueError::new_err(
33+
"format argument should be string or bytes",
34+
));
35+
}
36+
Ok(())
37+
}
38+
39+
#[allow(clippy::needless_pass_by_value)]
40+
fn script(&mut self, nt: &str, nts: Vec<String>, script: PyObject) {
41+
self.ctx.add_script(nt, &nts, script);
42+
}
43+
44+
fn regex(&mut self, nt: &str, regex: &str) {
45+
self.ctx.add_regex(nt, regex);
46+
}
47+
}
48+
49+
fn loader(py: Python, grammar: &str) -> PyResult<Context> {
50+
let py_ctx = Bound::new(py, PyContext::new())?;
51+
let locals = [("ctx", &py_ctx)].into_py_dict_bound(py);
52+
py.run_bound(grammar, None, Some(&locals))?;
53+
Ok(py_ctx.borrow().get_context())
54+
}
55+
56+
/// Create a `NautilusContext` from a python grammar file
57+
#[must_use]
58+
pub fn load_python_grammar(grammar: &str) -> Context {
59+
Python::with_gil(|py| {
60+
loader(py, grammar)
61+
.map_err(|e| e.print_and_set_sys_last_vars(py))
62+
.expect("failed to parse python grammar")
63+
})
64+
}

0 commit comments

Comments
 (0)