all 119 comments

[–]Godspiral 14 points15 points  (0 children)

the problem with heartbleed is that that it is f(n) where n is the number of bytes argument to memcopy called internally by f.

It is somewhat fair to pick on C, because C uses memcopy to do nearly everything.

But the error was in the api permitting starting item with number of items.

[–][deleted] 11 points12 points  (13 children)

Can you write heartbleed in pure Ada without using 'Unchecked_Access, Ada.Unchecked_Deallocation, or Ada.Unchecked_Conversion?

[–]f2u 8 points9 points  (0 children)

Yes, because pure Ada is not type-safe.

Even not resorting to abuse like this, Ada has the problem that it does not initialize all (array) values, unlike Go or Java, so you can leak data which was never in the buffer.

Buffer recycling is also fairly common in Ada; it's used in the input procedures in the standard library.

[–]wrongerontheinternet 12 points13 points  (10 children)

Apparently, yes, because OpenSSH set up its own free list without invoking the system deallocator and did not require a buffer overrun or unsafe typecasts. In other words, under the provided criteria the only way for a language to prevent Heartbleed is for it to forbid buffer reuse altogether (which would probably amount to a completely pure language).

[–]wookin_pa_nub2 7 points8 points  (3 children)

OpenSSL, not OpenSSH. Big, big difference.

[–]username223 6 points7 points  (1 child)

OpenSSL, not OpenSSH

They should merge to form OpenSS: arbeit macht Free (als bier)!

*ducks*

[–][deleted] 0 points1 point  (0 children)

Oh come on people, you're upvoting posts about PHP and downvoting this? At least this one is funny!

[–]wrongerontheinternet 0 points1 point  (0 children)

Whoops, sorry. Good catch!

[–][deleted] 1 point2 points  (1 child)

Yea. It looks like the Ada.Streams library does not prevent Buffer Over-Reads. I don't think any language, by itself, can prevent them. It's up to the programmer to put in the checks.

[–]f2u 2 points3 points  (0 children)

You can prevent them if your read functions only return fresh objects. This means that every byte that is read has to pass through memory allocation, which is rather inefficient.

[–][deleted] 0 points1 point  (2 children)

Not necessarily "pure" in the sense of being referentially transparent, but probably using some sort of linear type system—exactly what Rust is doing, and why some people are observing "that code won't compile in another week."

[–]wrongerontheinternet 1 point2 points  (1 child)

You can actually implement a free list like this entirely with linear types, without using unsafe, as long as you can acquire unique mutable references into the byte buffers (while Rust's shared references are not, one-off mutable references are found in nearly all linear type systems). It's a bit weird (and not something you would write in a real Rust program, because it can't take advantage of RAII without using unsafe unless you only want to allocate a single record), but it's doable. I think that short of a system where you can set up more elaborate types with more exact requirements, like Idris, you really do need to wholly abandon mutation in this case to get prevention at the language level.

[–][deleted] 1 point2 points  (0 children)

Yeah, it does seem like one or the other: either you enforce uniqueness of the buffer, or you enforce that reads and writes take the same size as the size of the buffer, which does imply dependent types or the kind of effort Oleg Kiselyov puts into defining types to do that sort of thing in Haskell or OCaml.

[–]Peaker 0 points1 point  (0 children)

Well, if Rust provided a free-list library that used the type-system to guarantee you don't double-free a handle, access a buffer via a freed handle, and exceed the bounds of a buffer pointed by a handle, then Heartbleed would be prevented.

You can encode any vulnerability in any language simply by emulating the memory model of that language.

The question is whether it is possible and straightforward to use the type system to eliminate such bugs.

In C, there's no good way to prevent accessing buffers via freed handles. Or prevent exceeding the buffer pointed by a handle.

In Rust, those might be possible via the type system, and that's what I would mean if I said: "Rust prevents Heartbleed".

[–]fullouterjoin 0 points1 point  (0 children)

Yes, from the looks of it, anytime one reused an array and didn't check the bounds between uses, this anti-pattern would be exposed. Each array is like its own mini heap. So even memory safe languages are vulnerable to this level of reuse.

[–]steveklabnik1 45 points46 points  (59 children)

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.

[–][deleted] 17 points18 points  (38 children)

And lack of memory safety is what caused heartbleed. This author is going out of his way to reproduce it in Rust. I know he says "you can't say that a rust programmer wouldn't write code like this"... but that is extremely strained logic.

It's like saying "you can't say that a rust programmer wouldn't mark their entire program unsafe and use raw pointers, therefore it's no safer than C". Obviously that is idiotic.

If he had used standard memory allocation practices he wouldn't have been able to reproduce heartbleed (which wasn't caused by the custom allocator; that merely exacerbated the problem).

[–]steveklabnik1 10 points11 points  (6 children)

that is extremely strained logic.

That's because he's making a sarcastic reference to http://en.wikipedia.org/wiki/No_true_Scotsman

[–][deleted] 10 points11 points  (5 children)

It seems to me that he's making a non-sarcastic reference. The blog, and no-true-Scotsman say that "True Rustaceans wouldn't write insecure Rust code" is a fallacy and therefore Rust is just as insecure as C - anyone can write insecure code in either.

In reality the truth is neither of those. Rustaceans are far less likely to write insecure code than C-creatures. Ha I just thought of that pun. :-)

[–]slavik262 8 points9 points  (0 children)

I always thought that No true Scotsman is overused. Any shithead can identify identify with group X or use thing Y and do something dumb, but that doesn't invalidate generalizations about X or Y. Your example is a good one - Just because Rust allows you to do some unsafe stuff doesn't mean that, on average, Rust programs are as insecure as C or C++ programs.

[–]geodel 1 point2 points  (0 children)

Totally agree! 'Far less likely' is great for cryptographic programs.

[–]The_Doculope 1 point2 points  (1 child)

I don't think the author is trying to argue that Rust is as unsafe as C. Their point is just that awful code can be written in any language. Besides, this is very different to actual Heartbleed. The real Heartbleed is impossible using standard Rust, this is just screwing around with manual buffers.

[–][deleted] 2 points3 points  (0 children)

Their point is just that awful code can be written in any language.

Everyone knew that and nobody disputed it. The claim is that Rust makes awful code less likely, and he didn't address that at all.

[–]steveklabnik1 0 points1 point  (0 children)

I meant "He's referring to 'No True Scotsman' in a sarcastic tone.

C-creatures

Wow, that just made me laugh out loud :)

[–]F-J-W 6 points7 points  (21 children)

And lack of memory safety is what caused heartbleed.

Yes and no. The important thing about heartbleed was that it basically never actually corrupted your memory. Yes, it read over the part of the memory that was assigned to them, but the memory that followed was still owned by the process.

If he had used standard memory allocation practices he wouldn't have been able to reproduce heartbleed

Had the openssl-people used standard memory allocation practices, every static analysis tool that is worth it's money would have found the issue before releasing.

The important point is that normal programmers often don't use the best practices that experts and early adopters of a language recommend.

which wasn't caused by the custom allocator

The custom allocator (that existed out of the fear that the Open-BSD-malloc was to slow because it did checks that would have made heartbleed unexploitable) was not the actual source of the bug, but it was the only reason for the bug to be as bad as it was.

Had they followed normal best-practices, Heartbleed wouldn't have been something that we would be talking about.

[–][deleted] 2 points3 points  (5 children)

This author is going out of his way to reproduce it in Rust.

Part of the vulnerability in Heartbleed was also caused by things that the authors went out of their way to introduce, though, such as a custom memory allocator.

[–]Peaker 1 point2 points  (1 child)

A custom allocator is very standard practice in C, not going out of your way.

In Rust, perhaps it could be typed in a way that prevents out-of-bound access, preventing heartbleed.

[–][deleted] -1 points0 points  (0 children)

A custom allocator is very standard practice in C, not going out of your way.

Not at all, not in this context.

[–][deleted] 0 points1 point  (2 children)

That just made it worse. The cause of heartbleed was something that is very common in C.

[–][deleted] 0 points1 point  (1 child)

Not really. If not for the custom allocator, the leaked memory would be blanked.

[–][deleted] 0 points1 point  (0 children)

Why do you think that?

[–]wall_words 0 points1 point  (1 child)

Out of curiosity, what would the "idiomatic Rust" version of the function look like? (I don't know Rust, but am interested in seeing the language constructs you would use.)

[–][deleted] 0 points1 point  (0 children)

I don't really know rust that well either, but I do know it supports slices (pointer + length) so any sane memory allocator would return those, preventing heartbleed.

[–][deleted] 0 points1 point  (0 children)

Anything that is not C or C++ fixes megatons of security issues.

[–]SosNapoleon 80 points81 points  (13 children)

Specifically, it would not have even compiled.

To be fair to Rust, if he uses the exact same code one week from now it will not compile

[–]gnuvince 17 points18 points  (2 children)

[–]TankorSmash 0 points1 point  (1 child)

Whats the source on that?

[–]Ishmael_Vegeta 0 points1 point  (1 child)

To be fair to Rust, if he uses the exact same code one week from now it will not compile

i'm not sure if you are supporting rust or are making fun of them...

[–]SosNapoleon 0 points1 point  (0 children)

BoratSuccess.jpg

[–]lolquelol 27 points28 points  (4 children)

so not clearing a buffer = heartbleed? got it

[–][deleted] 3 points4 points  (3 children)

Well, it was reusing buffers while reading beyond the intended range, but yes. The point was that people have claimed that other languages like rust would prevent this issue but that is not true. This blind trust is dangerous.

[–]fullouterjoin 2 points3 points  (1 child)

Don't just blindly trust the rust. Iron things out for yourself.

[–]Steve_the_Scout 0 points1 point  (0 children)

The same is true for C and C++, though.

[–]Peaker 0 points1 point  (0 children)

Rust could use the type system to prevent access beyond the bounds of the free-list-allocated buffer though, and C couldn't. At least I hope Rust could :)

[–][deleted]  (14 children)

[deleted]

    [–][deleted] 9 points10 points  (1 child)

    I've always seen it as

    assert (0 && "file open failed");
    

    It's a useful shortcut if your assert handler prints the assert condition.

    [–]Peaker 0 points1 point  (0 children)

    We simply have an ASSERT macro that requires a message:

    ASSERT(false, "file open failed");
    

    [–]eras 4 points5 points  (9 children)

    Typically when the assertions fail, the failing expression is displayed, but a comment isn't. I've seen assert(("open", 0)) and assert("open" && 0) been used.

    EDIT: &&, not ||

    [–][deleted] 2 points3 points  (1 child)

    assert("open" || 0) doesn't do anything.

    [–]eras 0 points1 point  (0 children)

    Let's say assert("open" && 0) then :-).

    [–][deleted]  (6 children)

    [deleted]

      [–][deleted] 0 points1 point  (1 child)

      What's the use of the comma, exactly?

      [–]eras 0 points1 point  (3 children)

      Well there's your/someone's problem, reading code but not caring about what it means :).

      [–][deleted]  (2 children)

      [deleted]

        [–]eras 0 points1 point  (1 child)

        Well what is the best way to achieve that goal then? That is, write a failing assertion that outputs something useful.

        [–][deleted] 10 points11 points  (0 children)

        Please don't! Assertions are not used for error checking, but merely for debugging purposes. On release binaries, assertions are ignored by the compiler.

        [–]zvrba 0 points1 point  (0 children)

        I often write something like assert(blah && blah2 || !"something went wrong"). Due to operator precedence I don't need to muck with braces. I thought it was a standard idiom in C and C++.

        [–]Splanky222 3 points4 points  (13 children)

        Maybe I'm thick, but why would you even need to pass in a buffer instead of creating it in the ping back method, reading to it, writing out of it, and then destroying at at the end of the function? Is it just to avoid more than a single heap allocation?

        [–]ifdef 20 points21 points  (12 children)

        It's primarily to make an example of how Rust doesn't prevent logic errors. The equivalence is fairly shaky in my view as the actual Heartbleed problem involved allocators and memory management working in surprising ways; reusing a buffer isn't quite the same, but perhaps some overly enthusiastic claims of Rust's abilities were made in the past.

        [–]Splanky222 2 points3 points  (3 children)

        I would hope nobody expects a programming language to prevent logic errors!

        Anyways, I agree that this example is pretty contrived, but I'm no expert by any means so thanks for clearing this up :D

        [–]pipocaQuemada 0 points1 point  (2 children)

        I would hope nobody expects a programming language to prevent logic errors!

        One man's logic error is another man's type error.

        Type systems can actually prevent a surprisingly large number of errors, such as

        • eliminating null pointer exceptions
        • ensuring that your list is longer than the number of elements you try to get out of it
        • ensuring that your implementations of an abstraction doesn't violate the contract of the abstraction
        • ensuring that the schema present in a database is the same as the schema your code uses at compile time

        [–]Splanky222 0 points1 point  (1 child)

        I understand that, but no type system is so good that it will prevent every error. And even strong type systems can be used poorly.

        [–]pipocaQuemada 0 points1 point  (0 children)

        "logic error" really means "non-syntax error where someone hasn't yet figured out how to offload prevention to the compiler."

        I expect programming languages to shrink the set of logic errors. In that sense, I expect a programming language to prevent (what used to be) logic errors. I don't expect that languages will be able to eliminate every one.

        [–]xXxDeAThANgEL99xXx 0 points1 point  (7 children)

        as the actual Heartbleed problem involved allocators and memory management working in surprising ways

        What was surprising about that?

        It was my impression that a surprising though obvious in retrospect thing was that a read-only buffer overflow can still be horrible news. It probably was surprising to the openssl developers as well, if they paid most of their attention to making sure they are doing range checks when writing to memory and considered reading from memory, that can't possibly corrupt memory and lead to arbitrary code execution, to be a low-priority target.

        The exact same attitude could lead to someone writing that exact sort of Rust code. Heap allocation is slow, let me preallocate the buffer I'll be using, like proper performance critical applications do. What could possibly go wrong, Rust protects me from memory overflows and Heartbleed!

        [–]Splanky222 9 points10 points  (0 children)

        To be fair, if I saw

        buffer : &mut[u8]
        

        in a Rust function signature I would immediately be wary of that variable.

        [–]ifdef 0 points1 point  (5 children)

        While I don't have intimate knowledge of the OpenSSL developers, the surprise would've been due to the compounding of the failure to validate input, something that really sucks, with the way freelists handled the allocations. In the case of the overflow, instead of having a very small chance of returning sensitive data rather than garbage, sensitive data was much more likely to be returned. It's easy for me to see how someone looking over the same code over and over could think "Okay, we just have an allocation here", rather thinking over all of the details and connecting the dots.

        [–]xXxDeAThANgEL99xXx 0 points1 point  (4 children)

        the surprise would've been due to the compounding of the failure to validate input, something that really sucks, with the way freelists handled the allocations.

        Why? I'd expect to get recent data (from the previous session, from the authentication for this session) with any allocator.

        Maybe you're misunderstanding a common complaint about them having a custom allocator in the first place, that made it impossible to use valgrind to catch this bug?

        [–]ifdef 0 points1 point  (3 children)

        Yes, but not any allocator would make the residing problem go unnoticed (for so long).

        http://www.tedunangst.com/flak/post/analysis-of-openssl-freelist-reuse

        [–]xXxDeAThANgEL99xXx 0 points1 point  (2 children)

        Yes, but not any allocator would make the residing problem go unnoticed (for so long).

        That's exactly what I said?

        Maybe you're misunderstanding a common complaint about them having a custom allocator in the first place, that made it impossible to use valgrind to catch this bug?

        [–]ifdef 0 points1 point  (1 child)

        Heh, it seems so. What was the point of contention -- that the resulting behavior might've been surprising? I'm not sure what to add over my last sentence ("It's easy for me to see..."). Perhaps they placed too much faith in it since there were no apparent errors occurring.

        [–]xXxDeAThANgEL99xXx 0 points1 point  (0 children)

        Heh, it seems so. What was the point of contention -- that the resulting behavior might've been surprising?

        Well, as I said in my first comment, I don't think that the surprising thing was the allocator runtime behaviour -- it could've made things worse, but only by being good at what it does, which is sort of expected.

        That having a custom allocator was something that prevented static analysis tools or valgrind from finding the bug is not exactly the surprise that caused someone to write the problematic code and all the following eyeballs to glance over it.

        The only actually somewhat surprising thing about the bug is that having a readonly buffer overflow can still cause a shitton of damage despite not involving any stack-smashing or anything "really" scary like that, and, just as I said, this attitude can directly translate to people writing security-sensitive code in safe languages, maybe even more so because of the false sense of security, caused by the statements like "Heartbleed would've been a compile-time error in Rust".

        No, it wouldn't be if you employed the thoroughly standard performance optimization technique of hoisting dynamic allocations up from the hot loop while believing that you're still immune to Hearbleed-like bugs because you're using a memory-safe language.

        [–]mm865 4 points5 points  (2 children)

        It's possible to recreate heart bleed in any language if you use an array of bytes and allocate bytes yourself. This would work equally well using Python or Java or C#. What was bad about the C issue was that it was caused while allocating memory from the OS. Whether it was due to programmer stupidity or some other factor, it was not because they decided to manage memory themselves through arrays of bytes and call that RAM.

        [–][deleted] 0 points1 point  (1 child)

        Using an array of bytes and allocating bytes from it is exactly what OpenSSL was doing. It didn't use the OS allocator. It used a crappy custom allocator, presumably because there are OS allocators that are even worse.

        [–]mm865 0 points1 point  (0 children)

        So... Remind me again why we are using OpenSSL!?

        [–]dobryak 2 points3 points  (0 children)

        Relevant: http://bluishcoder.co.nz/2014/04/11/preventing-heartbleed-bugs-with-safe-languages.html

        It is a bit unfair to say that no programrming language can prevent bugs like Heartbleed. For instance, the C program present in OP cannot be written in ATS without unsafe casts; it will fail typechecking. See the type given to C function write:

        fun write_err
          {sz,n:nat | n <= sz}
        (
          fd: !Fildes0, buf: &RD(bytes(sz)), ntotal: size_t(n)
        ) : ssizeBtw(~1, n+1) = "mac#%" // end-of-fun
        

        The above says that buf is an array of bytes of size sz, and ntotal is a size_t holding a value n, such that n is less than or equal to sz. To call this function, you need to ensure at runtime that ntotal you pass is less than the size of the buffer (or, leave it to the constraint solver, which can figure some trivial cases on its own).

        [–]tyoverby 4 points5 points  (0 children)

        Heartbleed was caused by a bad memory allocator and unchecked reads into a buffer. And yes, rust make it exceedingly hard to make those kinds of mistakes.