nbp / holyjit

Generic purpose Just-In-time compiler for Rust.
https://holyjit.org/
Mozilla Public License 2.0
1.51k stars 26 forks source link

Segfault inside Vec::index_mut call #20

Open mateon1 opened 7 years ago

mateon1 commented 7 years ago

I came across this while trying to implement input/output for brainfuck. I looked at the crash for a bit in rr, it seems that something is calling index_mut without setting registers properly. rsi = 0x0, which causes a null deref

In short:

Program received signal SIGSEGV, Segmentation fault.
0x00005586bde7eacc in alloc::vec::{{impl}}::index_mut<u8> (self=0x7fff4ff77878, index=...) at /shared/dev/rust/rust-patch/src/liballoc/vec.rs:1627
1627        fn index_mut(&mut self, index: ops::Range<usize>) -> &mut [T] {
(rr) print *self
$1 = alloc::vec::Vec<u8> {buf: alloc::raw_vec::RawVec<u8, alloc::heap::Heap> {ptr: core::ptr::Unique<u8> {pointer: core::nonzero::NonZero<*const u8> (0x7f0f8621e000 "\000"), _marker: core::marker::PhantomData<u8>}, cap: 256, a: alloc::heap::Heap}, len: 256}
(rr) bt
#0  0x00005586bde7eacc in alloc::vec::{{impl}}::index_mut<u8> (self=0x7fff4ff77878, index=...) at /shared/dev/rust/rust-patch/src/liballoc/vec.rs:1627
#1  0x00007f0f87570de8 in ?? ()
#2  0x00005586be19f250 in panic_loc.C ()
#3  0x00005586bdf4bcc1 in str.j ()
#4  0x000000000001459c in ?? ()
#5  0x00005586be19f250 in panic_loc.C ()
#6  0x00000000000005b0 in ?? ()
#7  0x00007f0f86229040 in ?? ()
#8  0x0000000000000004 in ?? ()
#9  0x0000000000000004 in ?? ()
#10 0x00007f0f8622a200 in ?? ()
#11 0x0000000000000096 in ?? ()
#12 0x0000000000000096 in ?? ()
#13 0x0000000000000000 in ?? ()

So this is definitely crashing on indexing the mem variable.

Here's code that triggers this crash: [Note: I use boxed traits, because the jit!() macro doesn't accept generic functions]

#![feature(plugin, custom_attribute)]
#![plugin(holyjit_plugin)]
#![feature(unboxed_closures)]
#[macro_use] extern crate holyjit_lib as hj;

use std::io;
use std::io::{Read, Write, Cursor};

jit!{ fn eval(jc: hj::JitContext, program: String, input: Box<Read>, output: Box<Write>) -> Result<(), ()> = eval_impl in jc; }
fn eval_impl(_jc: hj::JitContext, program: String, mut input: Box<Read>, mut output: Box<Write>) -> Result<(), ()> {
    let prog = program.as_bytes();
    let mut pc : usize = 0;
    let mut ptr : usize = 0;
    let mut mem : Vec<u8> = Vec::with_capacity(256);
    mem.resize(256, 0);
    loop {
        if pc >= prog.len() {
            return Ok(());
        }
        match *prog.get(pc).unwrap() {
            b'>' => {
                ptr += 1;
                if ptr >= mem.len() {
                    mem.push(0);
                }
            }
            b'<' => { ptr = ptr.saturating_sub(1); }
            b'-' => { mem[ptr] = mem[ptr].wrapping_sub(1); }
            b'+' => { mem[ptr] = mem[ptr].wrapping_add(1); }
            b'.' => { output.write(&mem[ptr .. ptr + 1]).unwrap(); }
            b',' => { input.read_exact(&mut mem[ptr .. ptr + 1]).unwrap(); }
            b'[' => {
                if mem[ptr] == 0 {
                    let mut iter = (pc + 1, 0);
                    loop {
                        iter = match (iter, prog[iter.0]) {
                            ((p, 0), b']') => {
                                pc = p + 1;
                                break;
                            },
                            ((p, d), b'[') => (p + 1, d + 1),
                            ((p, d), b']') => (p + 1, d - 1),
                            ((p, d), _) => (p + 1, d)
                        }
                    }
                    continue; // skip pc increment
                }
            }
            b']' => {
                let mut iter = (pc - 1, 0);
                loop {
                    iter = match (iter, prog[iter.0]) {
                        ((p, 0), b'[') => {
                            pc = p;
                            break;
                        },
                        ((p, d), b'[') => (p - 1, d + 1),
                        ((p, d), b']') => (p - 1, d - 1),
                        ((p, d), _) => (p - 1, d)
                    }
                }
                continue; // skip pc increment
            }
            _ => { panic!("Unknown Symbol"); }
        }
        pc += 1;
    }
}

fn main() {
    let jc : hj::JitContext = Default::default();
    let res = eval(jc, ",[.,]".into(), Box::new(Cursor::new(b"Hello, world!")), Box::new(io::stderr()));
    // let res = eval(jc, "-[>-[>-[>-<-]<-]<-]<-]".into());
    res.unwrap();
}
nbp commented 7 years ago

Thanks a lot for this great report :)

Here's code that triggers this crash: [Note: I use boxed traits, because the jit!() macro doesn't accept generic functions]

Yes, this is a problem which is due to the fact that we would have to re-implement the elision for the jit! macro, as it stores the signature of the function in a structure.

I looked at the crash for a bit in rr, it seems that something is calling index_mut without setting registers properly.

At the moment the assembly output is quite similar to the -O0 of LLVM, except for the fact that HolyJit does not yet use the ModRm addressing modes. You should be (almost) able to compare the assembly produced by HolyJit with the result of:

(rr) disas brainfuck::eval_impl
nbp commented 7 years ago

Looking at the generated code, it seems that the problem is that instead of giving the Range argument by reference, we give it by value to the index_mut function:

0x7f2fef121dc6  mov    (%rsi),%rdi
0x7f2fef121dc9  movabs $0xfffffffffffffd68,%rsi
0x7f2fef121dd3  add    %rbp,%rsi
0x7f2fef121dd6  mov    (%rsi),%rax
0x7f2fef121dd9  mov    0x8(%rsi),%rcx
0x7f2fef121ddd  mov    %rax,%rsi
0x7f2fef121de0  mov    %rdx,%rax
0x7f2fef121de3  mov    %rcx,%rdx
0x7f2fef121de6  callq  *%rax

The problem is likely located here: https://github.com/nbp/holyjit/blob/e4ed3be729ae91c5aa8ce93ca0c648afb642feb9/plugin/src/trans.rs#L1083-L1093