symbolic execution engine for Rust

Related tags

Security tools seer
Overview

Seer: Symbolic Execution Engine for Rust

Build Status crates.io

Seer is a fork of miri that adds support for symbolic execution, using z3 as a solver backend.

Given a program written in Rust, Seer attempts to exhaustively enumerate the possible execution paths through it. To achieve this, Seer represents the program's input in a symbolic form, maintaining a set of constraints on it. When Seer reaches a branch point in the program, it invokes its solver backend to compute which continuations are possible given the current constraints. The possible continuations are then enqueued for exploration, augmented with the respective new constraints learned from the branch condition.

Seer considers any bytes read in through ::std::io::stdin() as symbolic input. This means that once Seer finds an interesting input for your program, you can easily compile your program with plain rustc and run it on that input.

example: decode base64 given only an encoder

[source code]

Suppose we are given a base64 encoder function:

fn base64_encode(input: &[u8]) -> String { ... }

and suppose that we would like to decode a base64-encoded string, but we don't want to bother to actually write the corresponding base64_decode() function. We can write the following program and ask Seer to execute it:

fn main() {
    let value_to_decode = "aGVsbG8gd29ybGQh";
    let mut data: Vec<u8> = vec![0; (value_to_decode.len() + 3) / 4 * 3];
    std::io::stdin().read_exact(&mut data[..]).unwrap();

    let result = base64_encode(&data[..]);

    if result.starts_with(value_to_decode) {
        panic!("we found it!");
    }
}

Seer will then attempt to find input values that can trigger the panic. It succeeds after a few seconds:

$ cargo run --bin seer -- example/standalone/base64.rs
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/seer example/standalone/base64.rs`
ExecutionComplete { input: [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33], result: Err(Panic) }
as string: Ok("hello world!")
hit an error. halting

There is our answer! Our string decodes as "hello world!"

using the helper crate

The helper crate provides some extra conveniences. The input that triggers a panic can be split by variable and types that implement Debug are printed in their debug representation instead of using the underlying bytes:

#[macro_use]
extern crate seer_helper;
seer_helper_init!();

#[derive(Debug)]
struct MyStruct {
    a: u32,
    b: u64,
}

fn main() {
    let mut t = MyStruct { a: 0, b: 0 };
    seer_helper::mksym(&mut t);
    if t.a == 123 && t.b == 321 {
        panic!();
    }
}

For the formatting to work, it is necessary to build MIR for the standard library, so we will use Xargo:

$ RUSTFLAGS="-Z always-encode-mir" xargo seer
...
ExecutionComplete { input: [stdin: [], t: "MyStruct { a: 123, b: 321 }"], result: Err(NoMirFor("std::sys::unix::fast_thread_local::register_dtor::::__cxa_thread_atexit_impl")) }
hit an error. halting

The full example crate can be found here.

limitations

Seer is currently in the proof-of-concept stage and therefore has lots of unimplemented!() holes in it. In particular, it does not yet handle:

  • allocations with size depending on symbolic input
  • pointer-to-pointer with symbolic offset
  • overflow checking on symbolic arithmetic
  • ... lots of other things that you will quickly discover if you try to use it!

long-term vision

The goal is that Seer will help in two primary use cases:

  • in exploratory tests, as a complementary approach to fuzzing
  • in program verification, to exhaustively check that error states cannot be reached
Comments
  • Easier symbolic variables and output readiability

    Easier symbolic variables and output readiability

    I wanted to broaden the ways that users can make symbolic variables and provide more readable output.

    The result is a helper crate with a function mksym, that takes any Sized variable and fills it with symbolic bytes. Usage looks like

    let mut t = MyStruct { a: 0, b: 0 };
    seer_helper::mksym(&mut t);
    if t.a == 123 && t.b == 321 {
        panic!();
    }
    

    I did two things to make the output easier to read:

    • separate the variables so it clear what data corresponds to which variable in the program
    • replace the [u8] representation with the Debug representation for the type

    The result looks like this:

    ExecutionComplete { input: [stdin: [], t: "MyStruct { a: 123, b: 321 }"], result: Err(...) }
    hit an error. halting
    

    In implementation, seer_helper::mksym is an empty function, but Seer intercepts calls to it and overwrites the memory of the argument with (so far unconstrained) abstract bytes. In order to improve the output, the name and type of the argument is stored in the ConstraintContext. For formatting, the idea is to evaluate a call to a generic formatting function that returns a String in a fresh EvalContext (see format_executor.rs). I had some trouble finding DefIds for functions in the helper crate, so I resorted to making the client crate use an init macro that defines the function inside the client crate.

    opened by nilfit 10
  • Support for `numeric_intrinsic`s

    Support for `numeric_intrinsic`s

    The code I am working on uses the cttz intrinsic on PrimVal::Abstract, resulting in a panic on line 624 in intrinsic.rs.

    What would be the best approach to implement it? I believe an open-coded implementation (based on comparisons) would work/be required for these intrinsics, as Z3 does not seem to provide them natively.

    I would likt to work on these intrinsics, but I just started looking at the code and I might use some directions :)

    opened by ranma42 2
  • Overflow checking for symbolic add/sub

    Overflow checking for symbolic add/sub

    This adds overflow checks for abstract addition and subtraction. intrinsic_with_overflow can now store an abstract PrimVal for the overflow bool. This allows the compiler-inserted Asserts to be evaluated meaningfully for Add and Sub with at least one abstract operand.

    Some of the function signatures in operator.rs have been changed: instead of returning a bool to indicate overflow, a PrimVal is used. This allows us to return abstract values when needed.

    I ran into some trouble with tests failing because intrinsic_overflowing had to return a bool. If the overflow PrimVal is abstract, conversion to bool is unimplemented!(). The workaround returns to the old behavior of just returning false (no overflow) for all abstract ops. From a quick search of the codebase, it seems like none of the users of the function actually use the returned overflow bool. It might be a better solution to just remove the overflow bool from the return value.

    A few other tests failed because Seer stops at the first panic, which ended up being an overflow. I replaced the adds with wrapping_add, which prevents the insertion of overflow checks.

    opened by nilfit 2
  • fix cargo seer no-trans error

    fix cargo seer no-trans error

    When I try to use cargo seer on examples/base64, it fails with message

    error: unknown debugging option: `no-trans`
    

    MIRI has recently replaced its use of the -Zno-trans arg with --emit=dep-info,metadata in https://github.com/solson/miri/commit/675587280f5cee0ea99ebb4c4f70043e89aa1aed. This PR does the same for Seer, making the example work again.

    opened by nilfit 1
  • Allow consumers to continue on error

    Allow consumers to continue on error

    If Seer is configured with a consumer that wants to continue execution when encountering errors (a consumer that always returns true), it repeatedly reports the same error. This is because the unmodified EvalContext is pushed back on the stack in the main loop in the error case. This fixes the problem.

    opened by nilfit 1
  • Implement numeric intrinsics

    Implement numeric intrinsics

    An implementation of cttz :)

    I added a test to check that it works (aka it gets translated to valid Z3 constraints and it gets the correct anwser in at least one case XD )

    WARNING: Running it on my target code I get multiple ExecutionComplete { ... result: Ok(()) } outputs before the final ExecutionComplete { ..., result: Err(Panic) } one, which is different from what happens in other (standalone) examples (but maybe this is expected?).

    opened by ranma42 1
  • can't handle type X with layout General { ... }

    can't handle type X with layout General { ... }

    Consider the following program.

    enum Token {
        _LeftAngle,
        _RightAngle
    }
    
    struct Failures<P> {
        _point: P,
    }
    
    struct ParseMaster<P> {
        _failures: Failures<P>,
    }
    
    struct Alternate<'pm, P: 'pm, T> {
        _master: &'pm mut ParseMaster<P>,
        _current: Option<Progress<P, T>>,
        _point: P,
    }
    
    struct TokenPoint<'a, T: 'a> {
        _offset: usize,
        _sub_offset: Option<u8>,
        _s: &'a [T],
    }
    
    impl <P> ParseMaster<P> {
        fn alternate<'pm, T>(&'pm mut self, pt: P) -> Alternate<'pm, P, T> {
            Alternate {
                _master: self,
                _current: None,
                _point: pt,
            }
        }
    }
    
    struct Progress<P, T> {
        _point: P,
        _status: Status<T>,
    }
    
    enum Status<T> {
        _Success(T),
        _Failure,
    }
    
    fn main() {
        let mut pm : ParseMaster<TokenPoint<Token>> = ParseMaster {
            _failures: Failures {
                _point: TokenPoint {
                    _offset: 0,
                    _sub_offset: None,
                    _s:&[],
                },
            },
        };
    
        pm.alternate::<()>(TokenPoint {
            _offset: 0,
            _sub_offset: None,
            _s:&[],
        });
    
    }
    

    I expect seer to be able to run this program successfully. Indeed, miri has no problem running it. However, seer returns an error:

    Unimplemented("can\'t handle type: std::option::Option<u8>, with layout: General { discr: I8, variants: [Struct { align: Align { abi: 0, pref: 3 }, primitive_align: Align { abi: 0, pref: 3 }, packed: false, sized: true, offsets: [Size { raw: 0 }], memory_index: [0], min_size: Size { raw: 1 } }, Struct { align: Align { abi: 0, pref: 3 }, primitive_align: Align { abi: 0, pref: 3 }, packed: false, sized: true, offsets: [Size { raw: 0 }, Size { raw: 1 }], memory_index: [0, 1], min_size: Size { raw: 2 } }], size: Size { raw: 2 }, align: Align { abi: 0, pref: 3 }, primitive_align: Align { abi: 0, pref: 3 } }")
    
    opened by dwrensha 1
  • base64 example in README no longer works without xargo-built libstd

    base64 example in README no longer works without xargo-built libstd

    It now hits the error NoMirFor("alloc::allocator::Layout::from_size_align"). It looks like this is due to https://github.com/rust-lang/rust/pull/42313.

    opened by dwrensha 1
  • breakage from latest nightly

    breakage from latest nightly

    Due to https://github.com/rust-lang/rust/pull/52597, we get a bunch of errors:

    error[E0432]: unresolved import `rustc::mir::Literal`
       --> src/eval_context.rs:815:21
        |
    815 |                 use rustc::mir::Literal;
        |                     ^^^^^^^^^^^^^^^^^^^ no `Literal` in `mir`
    
    error[E0433]: failed to resolve. Could not find `Literal` in `mir`
       --> src/step.rs:203:18
        |
    203 |             mir::Literal::Value { value: &ty::Const { val: ConstValue::Unevaluated(def_id, substs), .. } } => {
        |                  ^^^^^^^ Could not find `Literal` in `mir`
    
    error[E0433]: failed to resolve. Could not find `Literal` in `mir`
       --> src/step.rs:206:18
        |
    206 |             mir::Literal::Value { .. } => {}
        |                  ^^^^^^^ Could not find `Literal` in `mir`
    
    error[E0433]: failed to resolve. Could not find `Literal` in `mir`
       --> src/step.rs:207:18
        |
    207 |             mir::Literal::Promoted { index } => {
        |                  ^^^^^^^ Could not find `Literal` in `mir`
    
    error: aborting due to 4 previous errors
    
    opened by dwrensha 0
  • Unexpected error instead of `Err(panic)` when MIR for std is present

    Unexpected error instead of `Err(panic)` when MIR for std is present

    If I add examples/base64/Xargo.toml:

    [dependencies]
    std = {features = ["panic_unwind", "jemalloc"]}
    

    and try to run the example with

    RUSTFLAGS="-Z always-encode-mir" xargo seer
    

    the Err(Panic) in the output is gone and I get a different error:

    ExecutionComplete { input: [116, 104, 105, 115, 32, 105, 115, 35, 64, 0, 0, 0], result: Err(NoMirFor("std::sys::unix::fast_thread_local::register_dtor::::__cxa_thread_atexit_impl")) }
    as string: Ok("this is#@\u{0}\u{0}\u{0}")
    hit an error. halting
    

    I think this happens because we have MIR for the expansion of panic!(). Seer detects panics in src/terminator/mod.rs in fn call_missing_fn. Since MIR is not missing, the panic is not detected and we run into this other error.

    opened by nilfit 0
  • Anyway to use Seer for concolic testing?

    Anyway to use Seer for concolic testing?

    Hi, I'd like to use seer for concolic testing. However, the examples are all code with main functions, and do not show how to hook into the engine for extension. I'd like to run concolic testing on library functions rather than whole programs. Could you please give me some pointers on how to achieve this?

    opened by minhnhdo 1
  • Verification potential

    Verification potential

    ~~~Right now, the crate doesn't seem to compile on the latest nightly, but I would imagine that to be a somewhat easy fix.~~~ (it compiles on master, just not on crates.io)

    My question is, how close is seer to being able to verify some pretty simplistic but long stateless code? If the effort isn't so high (more than a week or so of work), I would be interested to help get the project there.

    opened by dragostis 1
Owner
David Renshaw
David Renshaw
Secure sandboxing system for untrusted code execution

Godbox Secure sandboxing system for untrusted code execution. It uses isolate which uses specific functionnalities of the Linux kernel, thus godbox no

Nathanael Demacon 19 Dec 14, 2022
Brave's Rust-based adblock engine

Ad Block engine in Rust Native Rust module for Adblock Plus syntax (e.g. EasyList, EasyPrivacy) filter parsing and matching. It uses a tokenisation ap

Brave Software 961 Jan 5, 2023
Modular, structure-aware, and feedback-driven fuzzing engine for Rust functions

Fuzzcheck Fuzzcheck is a modular, structure-aware, and feedback-driven fuzzing engine for Rust functions. Given a function test: (T) -> bool, you can

Loïc Lecrenier 397 Jan 6, 2023
Detects usage of unsafe Rust in a Rust crate and its dependencies.

cargo-geiger ☢️ Looking for maintainer: https://github.com/rust-secure-code/cargo-geiger/issues/210 A program that lists statistics related to the usa

Rust Secure Code Working Group 1.1k Jan 4, 2023
An esoteric language/compiler written with Rust and Rust LLVM bindings

MeidoLang (メイドラング) A not so useful and esoteric language. The goal of this project was to contain some quirky or novel syntax in a stack-style program

null 0 Dec 24, 2021
Rust-verification-tools - RVT is a collection of tools/libraries to support both static and dynamic verification of Rust programs.

Rust verification tools This is a collection of tools/libraries to support both static and dynamic verification of Rust programs. We see static verifi

null 253 Dec 31, 2022
Rust bindings for libinjection

libinjection-rs Rust bindings for libinjection. How to use Add libinjection to dependencies of Cargo.toml: libinjection = "0.2" Import crate: extern c

ArvanCloud 35 Sep 24, 2022
A simple password manager written in Rust

ripasso A simple password manager written in Rust. The root crate ripasso is a library for accessing and decrypting passwords stored in pass format (G

Joakim Lundborg 548 Dec 26, 2022
tcp connection hijacker, rust rewrite of shijack

rshijack tcp connection hijacker, rust rewrite of shijack from 2001. This was written for TAMUctf 2018, brick house 100. The target was a telnet serve

null 377 Jan 1, 2023
A fast, simple, recursive content discovery tool written in Rust.

A simple, fast, recursive content discovery tool written in Rust ?? Releases ✨ Example Usage ✨ Contributing ✨ Documentation ?? ?? What the heck is a f

epi 3.6k Dec 30, 2022
link is a command and control framework written in rust

link link is a command and control framework written in rust. Currently in alpha. Table of Contents Introduction Features Feedback Build Process Ackno

null 427 Dec 24, 2022
CVEs for the Rust standard library

Rust CVE Preface This is a list of CVEs for unsound APIs in the Rust standard library. These bugs break Rust's memory safety guarantee and lead to sec

Yechan Bae 26 Dec 4, 2022
Rust bindings for VirusTotal/Yara

yara-rust Bindings for the Yara library from VirusTotal. More documentation can be found on the Yara's documentation. Example The implementation is in

null 43 Dec 17, 2022
Rust library for building and running BPF/eBPF modules

RedBPF A Rust eBPF toolchain. Overview The redbpf project is a collection of tools and libraries to build eBPF programs using Rust. It includes: redbp

foniod 1.5k Jan 1, 2023
Rust library for developing safe canisters.

IC Kit This library provides an alternative to ic-cdk that can help developers write canisters and unit test them in their Rust code. Install Add this

Psychedelic 26 Nov 28, 2022
MimiRust - Hacking the Windows operating system to hand us the keys to the kingdom with Rust.

MimiRust - Hacking the Windows operating system to hand us the keys to the kingdom with Rust. MimiRust is a program based on the wdigest attack vector

Thotty 0 Nov 29, 2022
simple multi-threaded port scanner written in rust

knockson simple multi-threaded port scanner written in rust Install Using AUR https://aur.archlinux.org/packages/knockson-bin/ yay -Syu knockson-bin M

Josh Münte 4 Oct 5, 2022
Rust TLS/SSL certificate expiration date from command-line checker

Rust TLS/SSL certificate expiration date from command-line checker

Jose Bovet Derpich 9 Nov 9, 2022
Lightweight slowloris (HTTP DoS) implementation in Rust.

slowlorust Lightweight slowloris (HTTP DoS) implementation in Rust. Slowloris is a denial-of-service attack program which allows an attacker to overwh

Michael Van Leeuwen 6 Sep 29, 2022