Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CommandExecutor with InputLocation::StdIn fails on Linux #1306

Closed
DanBlackwell opened this issue Jun 7, 2023 · 3 comments
Closed

CommandExecutor with InputLocation::StdIn fails on Linux #1306

DanBlackwell opened this issue Jun 7, 2023 · 3 comments
Labels
bug Something isn't working

Comments

@DanBlackwell
Copy link
Contributor

IMPORTANT

  1. You have verified that the issue to be present in the current main branch
    Have done.

Thank you for making LibAFL better!

Describe the bug
CommandExecutor crashes after some time on Linux when using InputLocation::StdIn to provide input. I have tested this on two machines with different hardware and Linux setups and found that they both fail eventually with this error message:

thread 'main' panicked at 'Error in the fuzzing loop: File(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }, ErrorBacktrace)', src/main.rs:137:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

To Reproduce
Steps to reproduce the behavior:

  1. Replace the contents of /fuzzers/baby_fuzzer/src/main.rs with the following and cargo run (it crashes after around 10 seconds on my local machine but may take longer):
#[cfg(windows)]
use std::ptr::write_volatile;
use std::{path::PathBuf, ptr::write};

#[cfg(feature = "tui")]
use libafl::monitors::tui::TuiMonitor;
#[cfg(not(feature = "tui"))]
use libafl::monitors::SimpleMonitor;
use libafl::{
    bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice},
    corpus::{InMemoryCorpus, OnDiskCorpus},
    events::SimpleEventManager,
    executors::{inprocess::InProcessExecutor, ExitKind},
    feedbacks::{CrashFeedback, MaxMapFeedback},
    fuzzer::{Fuzzer, StdFuzzer},
    generators::RandPrintablesGenerator,
    inputs::{BytesInput, HasTargetBytes},
    mutators::scheduled::{havoc_mutations, StdScheduledMutator},
    observers::StdMapObserver,
    schedulers::QueueScheduler,
    stages::mutational::StdMutationalStage,
    state::StdState,
    prelude::{CommandExecutor, TimeFeedback, TimeObserver}
};

/// Coverage map with explicit assignments due to the lack of instrumentation
static mut SIGNALS: [u8; 16] = [0; 16];
static mut SIGNALS_PTR: *mut u8 = unsafe { SIGNALS.as_mut_ptr() };

/// Assign a signal to the signals map
fn signals_set(idx: usize) {
    unsafe { write(SIGNALS_PTR.add(idx), 1) };
}

#[allow(clippy::similar_names, clippy::manual_assert)]
pub fn main() {
    // The closure that we want to fuzz
    let mut harness = |input: &BytesInput| {
        let target = input.target_bytes();
        let buf = target.as_slice();
        signals_set(0);
        if !buf.is_empty() && buf[0] == b'a' {
            signals_set(1);
            if buf.len() > 1 && buf[1] == b'b' {
                signals_set(2);
                if buf.len() > 2 && buf[2] == b'c' {
                    #[cfg(unix)]
                    panic!("Artificial bug triggered =)");

                    // panic!() raises a STATUS_STACK_BUFFER_OVERRUN exception which cannot be caught by the exception handler.
                    // Here we make it raise STATUS_ACCESS_VIOLATION instead.
                    // Extending the windows exception handler is a TODO. Maybe we can refer to what winafl code does.
                    // https://github.com/googleprojectzero/winafl/blob/ea5f6b85572980bb2cf636910f622f36906940aa/winafl.c#L728
                    #[cfg(windows)]
                    unsafe {
                        write_volatile(0 as *mut u32, 0);
                    }
                }
            }
        }
        ExitKind::Ok
    };

    // Create an observation channel using the signals map
    // let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS.len()) };
    let observer = TimeObserver::new("time");

    // Feedback to rate the interestingness of an input
    // let mut feedback = MaxMapFeedback::new(&observer);
    let mut feedback = TimeFeedback::with_observer(&observer);

    // A feedback to choose if an input is a solution or not
    let mut objective = CrashFeedback::new();

    // create a State from scratch
    let mut state = StdState::new(
        // RNG
        StdRand::with_seed(current_nanos()),
        // Corpus that will be evolved, we keep it in memory for performance
        InMemoryCorpus::new(),
        // Corpus in which we store solutions (crashes in this example),
        // on disk so the user can get them after stopping the fuzzer
        OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
        // States of the feedbacks.
        // The feedbacks can report the data that should persist in the State.
        &mut feedback,
        // Same for objective feedbacks
        &mut objective,
    )
    .unwrap();

    // The Monitor trait define how the fuzzer stats are displayed to the user
    #[cfg(not(feature = "tui"))]
    let mon = SimpleMonitor::new(|s| println!("{s}"));
    #[cfg(feature = "tui")]
    let mon = TuiMonitor::new(String::from("Baby Fuzzer"), false);

    // The event manager handle the various events generated during the fuzzing loop
    // such as the notification of the addition of a new item to the corpus
    let mut mgr = SimpleEventManager::new(mon);

    // A queue policy to get testcasess from the corpus
    let scheduler = QueueScheduler::new();

    // A fuzzer with feedbacks and a corpus scheduler
    let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);

    // Create the executor for an in-process function with just one observer
    let mut builder = CommandExecutor::builder();
    builder.program("echo");

    let mut executor = builder.build(tuple_list!(observer)).unwrap();

    // Generator of printable bytearrays of max size 32
    let mut generator = RandPrintablesGenerator::new(32);

    // Generate 8 initial inputs
    state
        .generate_initial_inputs_forced(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
        .expect("Failed to generate the initial corpus");

    // Setup a mutational stage with a basic bytes mutator
    let mutator = StdScheduledMutator::new(havoc_mutations());
    let mut stages = tuple_list!(StdMutationalStage::new(mutator));

    fuzzer
        .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
        .expect("Error in the fuzzing loop");
}

Expected behavior
The fuzzer should continue as normal.

Screen output/Screenshots

[Testcase #0] run time: 0h-0m-0s, clients: 1, corpus: 1, objectives: 0, executions: 1, exec/sec: 0.000
[Testcase #0] run time: 0h-0m-0s, clients: 1, corpus: 2, objectives: 0, executions: 2, exec/sec: 0.000
[Testcase #0] run time: 0h-0m-0s, clients: 1, corpus: 3, objectives: 0, executions: 3, exec/sec: 0.000
[Testcase #0] run time: 0h-0m-0s, clients: 1, corpus: 4, objectives: 0, executions: 4, exec/sec: 0.000
[Testcase #0] run time: 0h-0m-0s, clients: 1, corpus: 5, objectives: 0, executions: 5, exec/sec: 0.000
[Testcase #0] run time: 0h-0m-0s, clients: 1, corpus: 6, objectives: 0, executions: 6, exec/sec: 0.000
[Testcase #0] run time: 0h-0m-0s, clients: 1, corpus: 7, objectives: 0, executions: 7, exec/sec: 0.000
[Testcase #0] run time: 0h-0m-0s, clients: 1, corpus: 8, objectives: 0, executions: 8, exec/sec: 0.000
thread 'main' panicked at 'Error in the fuzzing loop: File(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }, ErrorBacktrace)', src/main.rs:137:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[1]    15497 abort (core dumped)  cargo run

Additional context
The following patch fixes the issue for me:

diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs
index a92022f6..43e161dd 100644
--- a/libafl/src/executors/command.rs
+++ b/libafl/src/executors/command.rs
@@ -132,8 +132,7 @@ impl CommandConfigurator for StdCommandConfigurator {
                 Ok(cmd.spawn()?)
             }
             InputLocation::StdIn => {
-                self.command.stdin(Stdio::piped()).spawn()?;
-                let mut handle = self.command.spawn()?;
+                let mut handle = self.command.stdin(Stdio::piped()).spawn()?;
                 let mut stdin = handle.stdin.take().unwrap();
@DanBlackwell DanBlackwell added the bug Something isn't working label Jun 7, 2023
@domenukk
Copy link
Member

domenukk commented Jun 7, 2023

Feel free to open a PR for this, thanks for tracking it down :)
I cannot say I understand why this happens, but if the fix works, awesome!

DanBlackwell pushed a commit to DanBlackwell/LibAFL that referenced this issue Jun 8, 2023
@DanBlackwell
Copy link
Contributor Author

Assuming that this reproduces for other others here. I have opened a PR: #1308.

domenukk pushed a commit that referenced this issue Jun 8, 2023
…ssue #1306) (#1308)

Co-authored-by: dan <dan@localhost.localdomain>
@domenukk
Copy link
Member

domenukk commented Jun 8, 2023

Fixed with #1308

@domenukk domenukk closed this as completed Jun 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants