Live data from Hacker News

Heartbleed in Rust

tedunangst.com

1–10 of 140 posts

Re: Heartbleed in Rust

#3
Slightly OT: while trying to understand the vulnerability I came across a Rust question.

Why can you do this?

    let mut outfd = File::create(&outpath);
    match outfd.write_all(&buffer[0 .. len]) { ... }
According to `old_io::File`'s doc[0] it returns an `IoResult` which is an alias `type IoResult = Result` i.e. `Result`. How come you can do `write_all` directly on a `Result` without unwrapping the `File` first?

The example in the docs does something similar:

    let mut f = File::create(&Path::new("foo.txt"));
    f.write(b"This is a sample file");
So I guess I'm missing something here.

[0] http://doc.rust-lang.org/std/old_io/fs/struct.File.html#meth...

Re: Heartbleed in Rust

#4
This is why I get a little uncomfortable when people suggest Rust fixes tons of security issues. Yes, it will fix some of them. No, just because a Rust program compiles doesn't mean that it won't have problems.

Rust is _memory safe_. Nothing more, nothing less.

Re: Heartbleed in Rust

#5
post #3

Slightly OT: while trying to understand the vulnerability I came across a Rust question. Why can you do this? let mut outfd = File::create(&outpath); match outfd.write_all(&buffer[0 .. len]) { ... } According to `old_io::File`'s doc[0] it returns an `IoResult ` which is an alias `type IoResult = Result ` i.e. `Result `. How come you can do `write_all` directly on a `Result ` without unwrapping the `File` first? The e…

It's because Writer (the trait write_all() is from) is implemented on IoResult. http://doc.rust-lang.org/std/old_io/trait.Writer.html#tymeth...

    impl Writer for IoResult
From the `std::old_io` docs: http://doc.rust-lang.org/std/old_io/index.html

    > Common traits are implemented for IoResult, e.g. impl Reader
    > for IoResult, so that error values do not have to be 'unwrapped' before use.

Re: Heartbleed in Rust

#7
Wasn't the heartbleed issue that you could trick it into reading past the memory it had allocated? That's different to explicitly reusing memory you've allocated without clearing it in between.

The original claim was that rust would prevent the class of errors that caused Heartbleed. No one claimed rust would prevent you from writing a program with a different bug that just happens to exhibit similar behavior.

Buffer overruns are tricker to spot than explicitly reusing a buffer.

[Edit] An example of an actual buffer overrun, with no changes to pingback.

C:

    $:/tmp # cat bleed.c
    #include 
    #include 
    #include 

    void
    pingback(char *path, char *outpath, unsigned char *buffer)
    {
            int fd;
            if ((fd = open(path, O_RDONLY)) == -1)
                    assert(!"open");
            if (read(fd, buffer, 256) 
Rust:

    C:\Users\ajanuary\Desktop>cat hearbleed.rs
    use std::old_io::File;

    fn pingback(path : Path, outpath : Path, buffer : &mut[u8]) {
            let mut fd = File::open(&path);
            match fd.read(buffer) {
                    Err(what) => panic!("say {}", what),
                    Ok(x) => if x  panic!("say {}", what),
                    Ok(_) => ()
            }
    }
    
    fn main() {
            let buffer2 = &mut[0u8; 10];
            let buffer1 = &mut[0u8; 10];
            pingback(Path::new("yourping"), Path::new("yourecho"), buffer1);
            pingback(Path::new("myping"), Path::new("myecho"), buffer2);
    }
    
    C:\Users\ajanuary\Desktop>hearbleed.exe
    thread '' panicked at 'assertion failed: index.end 

Re: Heartbleed in Rust

#8
post #3

Slightly OT: while trying to understand the vulnerability I came across a Rust question. Why can you do this? let mut outfd = File::create(&outpath); match outfd.write_all(&buffer[0 .. len]) { ... } According to `old_io::File`'s doc[0] it returns an `IoResult ` which is an alias `type IoResult = Result ` i.e. `Result `. How come you can do `write_all` directly on a `Result ` without unwrapping the `File` first? The e…

The explanation is at http://doc.rust-lang.org/std/old_io/#error-handling: IoResult implements a bunch of IO traits so you don't need to unwrap it before using it:

> Common traits are implemented for IoResult, e.g. impl Reader for IoResult, so that error values do not have to be 'unwrapped' before use.

Re: Heartbleed in Rust

#9
I don't know that anyone claimed that a bug similar or analogous to heartbleed couldn't be reproduced in Rust. If they did, that was certainly an overstatement. I think more concretely people claimed that unreachable code yields a warning in Rust, which is absolutely true, but certainly not equivalent to saying something like a heartbleed bug would not happen.

In general, Rust is fairly aggressive about linting for "small" details like unused variables, unreachable code, names that don't conform to expected conventions, unnecessary `mut` annotations, and so forth. I've found that these lints are surprisingly effective at catching bugs.

In particular, the lints about unused variables and unreachable code regularly catch bugs for me. These are invariably simple oversights ("just plain forgot to write the code I meant to write which would have used that variable"), but they would have caused devious problems that would have been quite a pain to track down.

I've also found that detailed use of types is similarly a great way to ensure that bugs like heartbleed are less common. Basically making sure that your types match as precisely as possible the shape of your data -- with no extra cases or weird hacks -- will help steer your code in the right direction. This is a technique you can apply in any language, but good, lightweight support for algebraic data types really makes it easier to do.

Re: Heartbleed in Rust

#10
post #3

Slightly OT: while trying to understand the vulnerability I came across a Rust question. Why can you do this? let mut outfd = File::create(&outpath); match outfd.write_all(&buffer[0 .. len]) { ... } According to `old_io::File`'s doc[0] it returns an `IoResult ` which is an alias `type IoResult = Result ` i.e. `Result `. How come you can do `write_all` directly on a `Result ` without unwrapping the `File` first? The e…

It's because Writer (the trait write_all() is from) is implemented on IoResult. http://doc.rust-lang.org/std/old_io/trait.Writer.html#tymeth... impl Writer for IoResult From the `std::old_io` docs: http://doc.rust-lang.org/std/old_io/index.html > Common traits are implemented for IoResult, e.g. impl Reader > for IoResult , so that error values do not have to be 'unwrapped' before use.

Ah, remote implementation of traits bites me once again.

Is there any reason behind not listing the implemented traits in IoResult's docs? Listing the implementors in the trait is not very useful since in the first place you have to know which traits are implemented to consult them. It's backwards and counterintuitive as I see it.

Post reply on HN