Live data from Hacker News

Heartbleed in Rust

tedunangst.com

31–40 of 140 posts

Re: Heartbleed in Rust

#31
post #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…

> Wasn't the heartbleed issue that you could trick it into reading past the memory it had allocated? No. Heartbleed is this: 1. malloc an input and an output buffer of the size specified by the caller (16 bits, up to 65536 bytes) 2. copy input data into input buffer (as little as 1 byte) 3. copy input buffer to output buffer 4. send output buffer to caller Neither malloc nor free zero-out their stuff, so when you mal…

The equivalent would be `Vec::with_capacity` which allocates the right size but does not provide safe access to uninitialized memory - in other words, you can only read what you wrote.

Re: Heartbleed in Rust

#32

Earlier quoted context omitted.

In Rust, you cannot ever read uninitialized memory (including allocated memory) without using unsafe code (as can be seen in the original code sample, the Rust buffer, unlike the C buffer, is initially zeroed out). So in safe Rust, what you are describing indeed could not happen. The unsafety would have to be explicit at the caller end: the unsafe within the allocator implementation isn't enough.

> So in safe Rust, what you are describing indeed could not happen. Read the last paragraph. OpenSSL has a buffer reuse system via a freelist (and the non-freelist code had bitrotted), it didn't release buffers to the system's allocator after use, buffers were initialised across calls. Otherwise while heartbleed would still have existed to a large extent, it would also have been mitigable by e.g. malloc.conf or shimm…

An allocator like that would require unsafe code to write and could not expose a safe interface unless it couldn't be used to read uninitialized memory. `Vec::with_capacity` would still not let you read the data even if you had a custom allocator.

Re: Heartbleed in Rust

#33

No true blogger would wilfully misunderstand a buffer overrun vulnerability in order to score some cheap pageviews. To put it simply, his examples are the equivalent of doing this: unsigned char data[4096]; #define X (*(int *)(&data[0])) #define Y (*(int *)(&data[4])) ... Basically, he's explicitly re-using a buffer, no buffer was overrun. In Rust you will not read something out of a buffer you didn't put there first…

> No true blogger would wilfully misunderstand a buffer overrun vulnerability in order to score some cheap pageviews. You may want to read up on Ted, and realise that when he writes > if we don’t actually understand what vulnerabilities like Heartbleed are he's probably talking about you. > Basically, he's explicitly re-using a buffer, no buffer was overrun. Which is essentially what happened in heartbleed. Heartblee…

What he's done is actually very different to heartbleed. The heartbleed flaw was made much worse by the custom allocator used, but that wasn't the source of the flaw. The source was the fact that dynamically allocated memory in C is not bounds checked.

That isn't true in Rust, and he had to basically implement a deliberately unsafe memory allocator to show this flaw. His argument that you can't say "no rust programmer would write this code" is flawed. Of course any programmer can write insecure code in any language. The point is that Rust makes it far less likely.

If he had ignored the custom allocator and used the defaults in both languages (e.g. malloc in C, whatever it is in Rust), then you would have seen the difference.

Re: Heartbleed in Rust

#34
post #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…

Openssl uses their own memory allocator (since malloc is slow on big-endian x86 xenix or something) so they _do_ reuse memory without clearing it in between. Had they used the system malloc, it wouldn't have been vulnerable (on OpenBSD and probably elsewhere). Can rust prevent you from implementing your own (buggy) memory allocator?

You would have to use unsafe code and isolate that as much as you can, behind a safe interface. Memory reuse without clearing would be considered safe, though, as none of the other abstractions actually let you read that memory (doing so would easily be UB anyways in LLVM).

Re: Heartbleed in Rust

#35
post #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…

Openssl uses their own memory allocator (since malloc is slow on big-endian x86 xenix or something) so they _do_ reuse memory without clearing it in between. Had they used the system malloc, it wouldn't have been vulnerable (on OpenBSD and probably elsewhere). Can rust prevent you from implementing your own (buggy) memory allocator?

Standard `malloc` doesn't zero memory either. The problem was not caused by their custom allocator. It was exacerbated by it because it allocated everything really close together.

Re: Heartbleed in Rust

#36
post #30
post #14

I mostly agree with the premise: logic errors are always going to be there, at least until the compiler is an IA strong enough to catch them for us (and by then we probably won't need coders anyway...). There's no silver bullet, bad coders are always going to produce. And I also don't like it when people claim that bug X or vulnerability Y wouldn't have happened if they had been technology Z, they're just begging for…

Is this logic error or just misuse of memory? (The buffer array)

The latter. It's trivial to reuse buffers in Rust while avoiding this issue, for example `Vec` has a `clear` method that sets the length to 0 while keeping the allocation.

But AFAICT, in C it didn't even have the same buffer by design, it was reading uninitialized memory from whatever `malloc` gave it back - which is equivalent to allocating a new buffer in Rust.

Re: Heartbleed in Rust

#37

No true blogger would wilfully misunderstand a buffer overrun vulnerability in order to score some cheap pageviews. To put it simply, his examples are the equivalent of doing this: unsigned char data[4096]; #define X (*(int *)(&data[0])) #define Y (*(int *)(&data[4])) ... Basically, he's explicitly re-using a buffer, no buffer was overrun. In Rust you will not read something out of a buffer you didn't put there first…

> No true blogger would wilfully misunderstand a buffer overrun vulnerability in order to score some cheap pageviews. You may want to read up on Ted, and realise that when he writes > if we don’t actually understand what vulnerabilities like Heartbleed are he's probably talking about you. > Basically, he's explicitly re-using a buffer, no buffer was overrun. Which is essentially what happened in heartbleed. Heartblee…

From the link that you supply in [0]:

> What if the requester didn't actually supply payload bytes, like she said she did? What if pl really is only one byte? Then the read from memcpy is going to read whatever memory was near the SSLv3 record and within the same process.

Re: Heartbleed in Rust

#38
post #32

Earlier quoted context omitted.

> So in safe Rust, what you are describing indeed could not happen. Read the last paragraph. OpenSSL has a buffer reuse system via a freelist (and the non-freelist code had bitrotted), it didn't release buffers to the system's allocator after use, buffers were initialised across calls. Otherwise while heartbleed would still have existed to a large extent, it would also have been mitigable by e.g. malloc.conf or shimm…

An allocator like that would require unsafe code to write and could not expose a safe interface unless it couldn't be used to read uninitialized memory. `Vec::with_capacity` would still not let you read the data even if you had a custom allocator.

You could write a custom "allocator" that created a pool of zeroed buffers initially, gave out buffers from that pool and accepted buffers back into that pool, and just didn't zero them in between. That would be perfectly "safe" as far as the language was concerned, and would allow you to reproduce the problem.

Re: Heartbleed in Rust

#39
"Code no true C programmer would write", eh? And yet one did, in a high-profile, security-critical library. When you find Rust code like this in the wild, I'll start to believe in some kind of equivalence.

Re: Heartbleed in Rust

#40
post #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…

> Wasn't the heartbleed issue that you could trick it into reading past the memory it had allocated? No. Heartbleed is this: 1. malloc an input and an output buffer of the size specified by the caller (16 bits, up to 65536 bytes) 2. copy input data into input buffer (as little as 1 byte) 3. copy input buffer to output buffer 4. send output buffer to caller Neither malloc nor free zero-out their stuff, so when you mal…

This isn't how I remember it nor what the code looks like [1]. Briefly:

1. tls1_process_heartbeat() retrieves type and 16-bit length from the SSL record.

2. It stores a pointer to the payload (which is after the two length bytes).

3. For the response, it allocates an output buffer and memcpy()s from the payload pointer to the output buffer.

4. If the specified payload length (the two bytes preceding the payload) is less than the actual length of the payload, memcpy() will read past the actual payload in the SSL record and will copy arbitrary memory contents to the response.

This looks like a pretty traditional buffer overrun to me. As far as I can tell, nowhere in the code was the input ever extended to match the specified rather than the actual payload length and the fix was to insert a sanity check to abort if there's a mismatch.

[1] http://git.openssl.org/gitweb/?p=openssl.git;a=blob;f=ssl/t1...

Post reply on HN