#[cfg(feature = "std")]
use alloc::boxed::Box;
use alloc::{
borrow::Cow,
rc::Rc,
string::{String, ToString},
vec::Vec,
};
use core::cell::Cell;
#[cfg(feature = "std")]
use std::{fs, path::Path};
#[cfg(all(feature = "lua_mutator", feature = "std"))]
use libafl_bolts::rands::StdRand;
use libafl_bolts::{Error, Named, rands::Rand};
use mlua::{Function, HookTriggers, Lua, VmState, prelude::LuaError};
use super::MutationResult;
use crate::{
HasMetadata,
corpus::CorpusId,
inputs::{HasMutatorBytes, ResizableMutator},
mutators::Mutator,
state::{HasMaxSize, HasRand},
};
const DEFAULT_TIMEOUT_STEPS: u32 = 1_000_000;
#[allow(clippy::needless_pass_by_value)] fn convert_error(err: LuaError) -> Error {
Error::illegal_argument(format!("Lua execution returned error: {err:?}"))
}
#[cfg(all(feature = "lua_mutator", feature = "std"))]
struct RandState(StdRand);
#[cfg(all(feature = "lua_mutator", feature = "std"))]
impl HasRand for RandState {
type Rand = StdRand;
fn rand(&self) -> &Self::Rand {
&self.0
}
fn rand_mut(&mut self) -> &mut Self::Rand {
&mut self.0
}
}
#[cfg(all(feature = "lua_mutator", feature = "std"))]
pub fn load_lua_mutations<
I: HasMutatorBytes + ResizableMutator<u8>,
S: HasMetadata + HasRand + HasMaxSize,
>(
lua_path: &Path,
) -> Result<Vec<Box<dyn Mutator<I, S>>>, Error> {
let mut mutations: Vec<Box<dyn Mutator<I, S>>> = vec![];
let mut rand_state = RandState(StdRand::with_seed(1337));
let lua_dir = fs::read_dir(lua_path).unwrap();
for mutation in lua_dir {
let mutation = mutation?;
log::info!("Loading lua_mutator from {mutation:?}");
let mutator =
LuaMutator::eat_errors(&mut rand_state, &fs::read_to_string(mutation.path())?);
if let Ok(mutator) = mutator {
mutations.push(Box::new(mutator));
} else {
log::warn!("Mutator {mutation:?} did not run: {mutator:?}");
}
}
Ok(mutations)
}
fn create_lua_fn<S: HasRand>(
lua: &Lua,
state: &mut S,
mutator_lua_fn: &str,
timeout_steps_min: Option<u32>,
test: bool,
) -> Result<(Function, Rc<Cell<bool>>), Error> {
#[allow(clippy::cast_possible_truncation)] let lua_seed = state.rand_mut().next() as u32;
let timeouted_once = Rc::new(Cell::new(true));
let timeouted_once_cb = timeouted_once.clone();
lua.load(format!("math.randomseed({lua_seed})"))
.exec()
.map_err(convert_error)?;
if let Some(timeout_steps_min) = timeout_steps_min {
let hook_triggers = HookTriggers::new().every_nth_instruction(timeout_steps_min);
lua.set_hook(hook_triggers, move |_lua, _debug| {
log::trace!("I'm here. Timeouted_once: {timeouted_once_cb:?}");
if timeouted_once_cb.get() {
Err(mlua::Error::RuntimeError(
"Instruction limit reached!".to_string(),
))
} else {
timeouted_once_cb.set(false);
Ok(VmState::Continue)
}
})
.unwrap();
}
let func = mutator_lua_fn.to_string();
let chunk = lua.load(&func);
let mutator: Function = chunk.eval().map_err(convert_error)?;
if test {
let bytes = vec![1_u8, 2, 3, 4, 5, 6, 7, 8, 9];
drop(mutator.call::<Vec<u8>>((bytes,)).map_err(convert_error)?);
}
Ok((mutator, timeouted_once))
}
pub struct LuaMutator {
#[allow(dead_code)] lua: Lua,
func: String,
mutator: Function,
eat_errors: bool,
errored: bool,
timeout_handler_called_once: Rc<Cell<bool>>,
}
impl core::fmt::Debug for LuaMutator {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LuaMutator")
.field("func", &self.func)
.field("mutator", &self.mutator)
.field(
"timeout_handler_called_once",
&self.timeout_handler_called_once,
)
.field("eat_errors", &self.eat_errors)
.field("errored", &self.errored)
.finish_non_exhaustive()
}
}
impl LuaMutator {
#[allow(unused)]
pub fn new<S: HasRand>(state: &mut S, mutator_lua_fn: &str) -> Result<Self, Error> {
let lua = Lua::new();
let func = mutator_lua_fn.to_string();
let (mutator, timeouted_once) = create_lua_fn(
&lua,
state,
mutator_lua_fn,
Some(DEFAULT_TIMEOUT_STEPS),
true,
)?;
Ok(Self {
lua,
func,
mutator,
timeout_handler_called_once: timeouted_once,
eat_errors: false,
errored: false,
})
}
pub fn eat_errors<S: HasRand>(state: &mut S, mutator_lua_fn: &str) -> Result<Self, Error> {
let lua = Lua::new();
let func = mutator_lua_fn.to_string();
let (mutator, timeouted_once) = create_lua_fn(
&lua,
state,
mutator_lua_fn,
Some(DEFAULT_TIMEOUT_STEPS),
true,
)?;
Ok(Self {
lua,
func,
mutator,
timeout_handler_called_once: timeouted_once,
eat_errors: true,
errored: false,
})
}
}
impl<I, S> Mutator<I, S> for LuaMutator
where
S: HasMetadata + HasRand + HasMaxSize,
I: HasMutatorBytes + ResizableMutator<u8>,
{
fn mutate(&mut self, _state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
self.timeout_handler_called_once.set(false);
let bytes = input.mutator_bytes().to_vec();
let result = match self.mutator.call::<Vec<u8>>(bytes) {
Err(err) => Err(Error::illegal_state(format!("Lua mutation failed: {err}"))),
Ok(mutated) => {
if mutated.eq(input.mutator_bytes()) {
Ok(MutationResult::Skipped)
} else {
input.resize(mutated.len(), 0);
input.mutator_bytes_mut().clone_from_slice(&mutated);
Ok(MutationResult::Mutated)
}
}
};
if self.eat_errors {
log::debug!("Mutation Errored: {}", &self.func);
self.errored = true;
if result.is_err() {
Ok(MutationResult::Skipped)
} else {
result
}
} else {
result
}
}
#[inline]
fn post_exec(&mut self, _state: &mut S, _new_corpus_id: Option<CorpusId>) -> Result<(), Error> {
Ok(())
}
}
impl Named for LuaMutator {
fn name(&self) -> &Cow<'static, str> {
&Cow::Borrowed("LuaMutator")
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "std")]
use std::println;
use libafl_bolts::Error;
use crate::{
inputs::BytesInput,
mutators::{MutationResult, Mutator, lua::LuaMutator},
state::NopState,
};
#[test]
fn simple_test() {
let mut state: NopState<BytesInput> = NopState::new();
let mut lua_mutator = LuaMutator::new(
&mut state,
r"function (bytes)
for i, byte in ipairs(bytes) do
if math.random() < 0.5 then
bytes[i] = math.random(0, 255)
end
end
return bytes
end
",
)
.unwrap();
let bytes = vec![0, 1, 2, 3, 4];
let mut bytesinput = BytesInput::new(bytes);
for _i in 0..10 {
let mutation_result = lua_mutator.mutate(&mut state, &mut bytesinput).unwrap();
if matches!(mutation_result, MutationResult::Mutated) {
#[cfg(feature = "std")]
println!("MutationResult: {mutation_result:?} after {_i} iterations");
return;
}
}
panic!("LuaMutator did not mutate once in 10 tries!");
}
#[test]
fn test_timeout() {
let mut state: NopState<BytesInput> = NopState::new();
assert!(
matches!(
LuaMutator::new(
&mut state,
r"function (bytes)
while true do
i = i + 1
end
end
"
),
Err(Error::IllegalArgument(_, _))
),
"Expected endless loop to raise an 'IllegalArgument' error!"
);
}
}