
Would Rust have prevented Heartbleed? Another look - wasd
http://tonyarcieri.com/would-rust-have-prevented-heartbleed-another-look
======
MichaelGG
Ted's post was a reaction to my overconfident assertion that even with a
custom allocator, Rust would prohibit code from compiling that could lead to
Heartbleed-like conditions. Furthermore, he was pointing out that random dorks
like me talking great about Rust don't seem to actually know what was going
on. I was under the impression that Heartbleed was _purely_ out-of-bounds
access, and it's not quite just that. (Still, I find it highly unlikely any
memory safe language would end up with a real world program reusing the same
buffers for SSL keys _and_ user buffers, but it's possible.)

Had I been a bit more careful in my claim then there'd have been no question.
Ted was technically accurate (the best kind), but a lot of the resulting
discussion was of the form "bad coders can always write security holes,
therefore a language isn't the fix, so C's OK". Even when major products, like
everything MS ships, have critical CVEs almost exclusively due to memory
safety, not arbitrary bad logic. Further tptacek likes to point out that
memory safety is just one class of sec bugs, and that there's frillions of
other bugs in other classes. My suspicion is that this is more of a bias
towards app-level stuff (ie command injection in a billion web apps), not
systems. I suppose if you get pwnd it doesn't matter how, but a world free of
all those MS memsafety RCEs seems like a qualitatively different one.

But hey, I'm just some arbitrary weakling, and I find it hard to challenge
people that are both smarter and drastically more experienced than I.

~~~
krenoten
Writing widely-deployed infrastructure (web browsers, networking stacks,
device drivers, openssl libraries) in C will be less and less justifiable over
time. Rust is far from optimized right now. Rust is probably not going to be a
language that you will script in. But it is a language that will possibly give
Mozilla the world's most secure web browser by a significant margin if they
succeed in writing the most bug prone components in Rust.

It takes more thought to write Rust programs. If you want to write safer
widely-deployed infrastructure, you should learn about it. It may not be your
best option right now, but its ideals are those that we absolutely need to
strive for. Acting macho about C and the ability to write it safely does not
lead to fewer RCE's in the world. It leads to more, because newcomers may see
such people who can do so as examples to be emulated, far before they are
capable.

Note that there are no familiar critics who say that Rust will not prevent
some bugs. Note that there are no familiar proponents who claim that Rust will
prevent all bugs. It is an implementation that strives to make progress in an
area that we can all agree with - the safety of our programs with minimal
performance degradation.

~~~
greggman
> But it is a language that will possibly give Mozilla the world's most secure
> web browser by a significant margin if they succeed in writing the most bug
> prone components in Rust.

A memory safe language alone isn't enough. Look at Java. Isn't that supposed
to be a memory safe language? And yet it's constantly got security issues.

~~~
pserwylo
I would suggest that the security issues in Java are due to bugs in the
various JVM implementations, which are likely written in C, right?

Not to say that Java is going to eliminate all bugs. However, it should
eliminate all bugs to do with, e.g., reading/writing out of the bounds of an
array and resulting in RCE due to bugs introduced in the application code.
Reading/writing out of the bounds of an array will still be a bug, but not one
that allows arbitrary code execution (at least not in the same way as it may
for a language like C.

~~~
MichaelGG
And many of them, from a quick glance, are actually due to complicated
sandboxing policies. That is, RCE was an explicit feature, but they found it
hard to get right.

In fact, one of the first real security holes I've ever found was in 1.0
version of the CLR. You could load a function pointer to an un-JIT'd function,
then later on use that to safely jump to other code. This is technically a
RCE, but the user has to be executing your code first anyways. That is, if the
CLR and Java didn't set out to run "partially trusted" code with no help from
the OS, these wouldn't have been problems at all.

Other languages like C, Ruby, etc. simply don't have this "partially trusted"
concept so there's nothing to attack. (Well I guess things like Native Client
or VMware qualify, but they're at a better abstraction later than JVM or CLR
permissions.)

~~~
seanmcdirmid
When I was an undergrad at UW, I engineered a vacuum bug that took advantage
of a JDK1.1 bug in constant pool verification to suck out the user's in memory
environment configuration (so like PATH variables and such).

[http://www.cs.cornell.edu/People/egs/kimera/flaws/vacuum/](http://www.cs.cornell.edu/People/egs/kimera/flaws/vacuum/)

But most security holes these days are not flaws in the JVM/CLR, but logical
errors made in the application; e.g. allocate a buffer and reuse it, this says
nothing about memory safety at the VM level at all!

And at that, no one really trusts JVM security anymore, preferring to sandbox
the entire operating environment in a heavyweight or lightweight VM.

~~~
pcwalton
> But most security holes these days are not flaws in the JVM/CLR, but logical
> errors made in the application; e.g. allocate a buffer and reuse it, this
> says nothing about memory safety at the VM level at all!

But most applications are written in memory-safe languages, so it's not
surprising that most vulnerabilities are found in non-memory-safety related
areas. The more interesting statistic is the number of critical security bugs
that are memory-safety related in non-memory-safe languages.

~~~
seanmcdirmid
That doesn't address what I said in the sentence you quoted at all. We were
discussing Java and vulnerabilities that arise given flaws in the JVM. You are
talking about something else that is completely different.

------
steveklabnik
Hi. Rust core here.

I like to say that of course Rust prevented Heartbleed, because we don't
currently support wrapping malloc ;). That's of course a bit glib.

While it's true that Rust gives you a lot of assistance with writing safe
code, given that unsafe exists, and that we, as a systems language, need to
give you underlying access to the machine, Rust will allow you to do all kinds
of stupid things if you vouch to the compiler that you're going to handle the
safety aspect. This means that ultimately, you can do any kind of stupid thing
in Rust that you can do in any other language.

An example: a common problem in C code is buffer overflows, which happens when
some code thinks that an array has more elements in it than it does. Because
C's arrays don't have a length as part of the type, it can be error prone to
keep track of the length of the array. Rust's arrays, vectors, and slices all
have a length as a part of the type, and so it's more difficult to have this
data be out of sync. But Rust will let you be stupid if you want to:

    
    
        fn bad_stuff(v: Vec<i32>) -> i32 {
            unsafe {
                *(v.get_unchecked(v.len() + 1))
            }
        }
    
        fn main() {
            let vec = vec![1, 2, 3];
    
            let val = bad_stuff(vec);
    
            println!("{}", val);
        }
    

The `bad_stuff` function here uses `get_unchecked()`, which skips the array
bounds check, to grab some memory past the length of the end of the vector.
Rust will happily compile this code, your `unsafe` block tells the compiler to
trust that you're using `get_unchecked()` in a safe way, even though it can't
verify it.

Now, there's a decent discussion that can be had on this topic: 'usual' Rust
code will of course be significantly safer than 'usual' C code, since you have
the compiler's assistance. Unfortunately, security is often about the edge
cases. While it's true that not wrapping malloc would have prevented
Heartbleed from happening, and most C projects don't wrap malloc... it did
happen. Once Rust starts getting used in the wild, we'll see Rust with
security problems, I'm sure of it. That doesn't mean that Rust has no value,
just that we need to be realistic about the tradeoffs that any of our
technologies make. Rust is not a panacea, even if it does cure some of your
aches and pains.

~~~
latiera
Oh but it _DOES_ mean that Rust has no value, at least regarding security.
Once you give them the rope, they _WILL_ use it. Let's see if time will prove
me right.

~~~
kibwen
Every language has a C FFI that users can use as rope to hang themselves with.
`unsafe` code in Rust is just a reified FFI that actually manages to make Rust
code _safer_ because it means fewer things need to be written in C, and even
unsafe Rust code is safer than C. Furthermore, the strict demarcation of
`unsafe` blocks greatly alleviates the burden upon code auditors, allowing
them to focus their efforts. Even if an extraordinarily high percentage of
your code is contained within unsafe blocks, say 10%, that's ten times less
code to audit than an equivalently-sized C codebase.

------
wglb
I disagree about Ted being wrong.

A key phrase in Ted's article is _Hopefully the simulacrum retains the essence
of the problem._

What Tedbleed, as your article calls it, demonstrates is a bug similar to
Heartbleed: reuse of a buffer in a memory-safe language is susceptible to
leakage of secrets.

While I might answer the question _are Rust’s memory safety features being
oversold by uninformed zealots?_ , no I don't think so, but I would suggest
that thinking that memory safety ends all security issues is a not good plan.

The key is _Unless you venture into the (explicitly demarcated) unsafe portion
of Rust, you will not see memory exposure vulnerabilities like Heartbleed_ :
the language does not prevent this. Perhaps your post will stimulate Ted to go
further with his example and write an erroneous complete TLS stack in Rust,
showing how it too can do Heartbleed. Perhaps you agree that this can be done.

Among the scariest mass vulnerabilities relating to total information
disclosure, the Padding Oracle Attack, the BEAST attack, the compression flaw
CRIME, none have to do with memory safety of languages. Rust there would not
have helped one whit.

Memory safety is just one issue in writing secure code.

Edit: typo

~~~
saidajigumi
To my knowledge, _no one_ in the Rust or greater memory-safe programming
language community would claim:

 _Using <$LANG> prevents leakage of secrets!_

When put like that, it sounds like a straw-man argument. In my own words, the
narrower and stronger claim being made is that _the specific and important
avenue by which Heartbleed leaked secrets_ is _not possible_ without unsafe
code in Rust.

So if you're going to claim that Ted "isn't wrong", but on terms that no one
was originally arguing... I don't even know what to say.

~~~
lmm
I would claim that using Scala (and presumably Haskell) gives you the tools to
make it impossible to leak secrets, if you want to. You could use a path-
dependent container type to ensure that content was associated with a
particular connection and never passed off to a different connection; if you
tried to do something like reusing a buffer, it would be a compile failure.

There would be costs to this - both in runtime performance (presumably you
wanted to reuse buffers for a reason) and in program complexity. And of course
it's possible to make an error in your type definitions (though you have to
make two errors to be exploitable - one in the types and one in the values -
and the types are shorter and much easier to audit than the full program
code). But it absolutely is possible, and I think that pretty soon we'll reach
the point where it becomes worthwhile for security-critical code.

~~~
steveklabnik
Yeah but even Haskell (and I'm assuming Scala has something similar) has
unsafePerformIO...

~~~
olavk
As far as I understand, unsafePerformIO is not unsafe in relation to memory
safety issues. It is just unsafe in that it circumvents the IO monad, which
means order of execution is not guaranteed.

~~~
steveklabnik
It's a different kind of unsafe, but my point is that it violates the normal
guarantees of the language.

------
copsarebastards
I don't see any value in arguing over whether a hypothetical program written
in Rust would have had one specific bug.

What is more important is whether using a language like Rust will prevent
_some_ severe security holes, and the answer is quite obviously that it will.
Sure, it's possible to create bugs in any language, but it's much harder to
prevent bugs in C than in most languages.

------
munin
Ted's original point about Heartbleed not being a violation of memory safety
is bogus, because there is a configuration of OpenSSL (achievable with flags
passed to ./config) where extended-sized Payload values cause the OpenSSL
server to segfault with an out of bounds read. It takes a special kind of
bravery to look at that kind of crash and say that it is not the result of a
violation of memory safety.

~~~
tedunangst
I'll be the last person to claim there aren't memory safety violations in
OpenSSL.

~~~
munin
Yes, but you specifically said that Heartbleed wasn't a memory safety
violation, when in fact OpenSSL-vulnerable-to-Heartbleed can segfault on over-
read. So what is it?

~~~
MichaelGG
But Heartbleed still existed even without out-of-bound reads, right? That's
the critical distinction I got wrong and why Ted made his rather fine point.
In Ted post, IIRC, he's clear that Rust will unlikely have such needed up code
written. But that if people like me boast how Rust would have flat-out refused
to compile such code, then we don't know the details and probably aren't in a
position to be spouting off on security. A one or two word edit of my original
comment would have avoided this whole discussion while retaining the essence
that Rust would, in practice, almost certainly eliminate these bugs.

------
SomeCallMeTim
As soon as we have a full port of OpenSSL in Rust, this will be interesting.
Talk is cheap; who's going to step up and do the hard work?

I do believe that the right developers, using the right practices, could make
a more-secure OpenSSL. But it would be expensive in terms of developer time.
Maybe a group with the right experience could crowdfund it?

~~~
agentultra
Another interesting tack is happening in Mirage with a TLS stack written in
OCaml.

[https://tls.openmirage.org/](https://tls.openmirage.org/)

------
Someone1234
I have a question: How do Rust and Go compare in terms of security? Or are
they both very similar in terms of mitigating certain classic C vulnerability
classes.

~~~
slimsag
Background: I have been developing in Go for ~5 years, and Rust for probably
less than 2 months.

\- Both Go and Rust use bounds checking on arrays/slices (which means out-of-
bounds accesses are not allowed).

\- Go is garbage collected, so memory leaks cannot occur.

\- Rust encourages an ownership/lifetime model, so memory leaks cannot occur.

\- Go can still have data races between multiple concurrent threads. To catch
them, you must use the race detector _at run time_. Note however that Go
encourages communicating information over channels, and if idiomatic code is
being written the chance of data races is next to none.

\- Rust cannot have data races in most cases, as it's ownership/lifetime model
allows for catching them _at compile-time_.

\- Both of these assumptions are based on no external C or unsafe code being
in the picture.

TL;DR: Very similar.

EDIT: I used the wrong terminology -- sorry. Instead of _memory leak_ I should
have said _dangling pointers_ (memory leaks can occur in _any_ language,
Go/Rust/Java/JS/etc).

~~~
rdtsc
> Go is garbage collected, so memory leaks cannot occur.

Well it depends on how you define memory leaks. Memory leaks can of course
occur logically. Like say you create objects in a cache and never remove them.
It is still a memory leak.

But at the level that you define the memory leak, it seems Rust can be just as
good as Go because memory ownership is tracked by the compiler. One can say it
is even better because of compile time checks and no need to have a GC @
runtime present.

~~~
comex
If you're worried about memory leaks, an obvious downside of Rust is that it
encourages reference counting over GC (more accurately: has no GC, but that
might change in the future), and reference counting can leak via cycles.

However, in practice most values can be neither Rc nor Gc but single-owner,
like unique_ptr in C++, which is easier to reason about than either.

~~~
steveklabnik
Yes, it's important to note that Rust isn't 'refcounting but not GC,' but
single ownership + borrowing over all else, and then using refcounting if you
find yourself in a situation where you need multiple ownership.

------
jguegant
A side question to HN:

For the security topic, most of the power of Rust come from a strong typing,
safety checks (boundaries), ownership checks... Well, a lot of checks at
compile time.

As far as I know of Rust by glancing at the doc frequently, C++11/14 also
contains these "safe tools": ownership can be dealt with smart pointers
(unique, shared), value semantics, move semantics (rvalues) and the rest.
Boundaries checks with the correct containers (std::vector, std::array...).
Mutability with "const". Atomicity with std::atomic... Type safety by using
explicit conversions, templates instead of macros, variadic args...

Heck, all the drama from C++ seems to come from a misusage or some legacy
parts from another age (up to C). In order to avoid people to shot themselves
in their feets, why couldn't we just introduce a strict compilation mode a la
JavaScript to let say Clang: # pragma strict

# pragma unsafe # pragma safe

Nowadays, good C++11 compilers can even deal with such a things like
constexpr. This seems a reachable goal.

What are the true drawbacks of a fully modern C++ code compared to a Rust? Is
there any true feature that can't be done in C++ due to a deep problem of
conception? Don't le me wrong, I do not seek to diminished the amazing work of
Mozilla. I am not talking neither about the other cool features from
functional languages, switch cases... that Rust has and C++ don't.

~~~
gilgoomesh
You're mistaken if you think that anything in C++11/14 makes it memory-safe
like Rust guarantees.

C++11/14 lets you assign a std::unique_ptr to another variable then re-use the
original variable, causing a null dereference. You can also have a reference
to the same std::unique_ptr from two different threads causing a race-
condition on the underlying pointer.

There is no bounds checking on std::vector by default. You _can_ bounds check
a std::vector (using "at" or various configuration/compiler settings which
change the semantics of the standard library) but by default, it is not
checked.

Fundamentally, the issue is that C++ lets you have null pointers and
references (yes, references can be null). It lets you use before initializing.
It lets you run out of bounds. It lets you access the same variable from
multiple threads without any safety. Yes, idiomatically, none of these should
be an issue but it's not possible to be a perfect programmer.

Rust forces idiomatic memory management and eliminates all these issues. If
you make a memory safety mistake, the compiler will force you to fix it.

~~~
jguegant
Your points are clearly valids. But wouldn't some of them corrected by a
strict mode?

\- For instance, the reuse of the original variable could be deduced by the
compiler. \- The bound checking for std::vector can effectively be enable. One
could imagine an std::strict_vector that do so.

What I am wondering is: does the same idiomatic memory management applied to
C++ would require some huge tweaks to the language, some bad tricks, new
keywords, or can it be done without changing its design but enforcing some
rules?

I, for instance, have no clues how to deal with your "one unique_ptr two
threads" problem. Could it be done in an elegant way in C++?

~~~
lomnakkus
Linear types (as in Rust) can prevent (at compile time) some of the more
trivial use-after-free issues e.g. for unique_ptr, but I think the main
reasons you won't see undefined behavior eliminated from C++ wholesale is that
it a) often requires extensive support at runtime (see ASAN, UBSAN, etc.) and
b) presents a huge barrier to optimization in certain cases and c) (thus) is
going to be waaay too slow for production use. (I.e. if C++ were to go in this
direction someone would basically either "fork" C++ or a new (similar)
language would supplant it.)

Unfortunately, currently no "sufficiently smart compiler" exists, so that
high-level code can be optimized sufficiently to beat what a good micro-
optimizing C++ compiler (which can assume that no undefined behavior can occur
at runtime) can achieve.

~~~
kibwen
Undefined behavior does permit optimizations, yes, but I think you're
overselling its benefit. Rust doesn't have undefined behavior and there are
many instances where its strict semantics mean that it is far more optimizable
and runtime-efficient than C++ (though some of those optimizations are yet to
be implemented).

~~~
lomnakkus
Perhaps, these days you're right -- assuming you want to only support
mainstream architectures. These days you can mostly rely on all mainstream
architectures to do something sensible with e.g. signed integer overflow[1] or
excessive shifting, but that wasn't necessarily the case when most of C++ was
standardized. As an example of a similar nature -- as I'm sure you know --
Rust has chosen to _not_ abort/panic on signed overflow although almost all
instances of such are most probably plain logic errors and could lead to
security problems[2]. As far as I could understand, this was for performance
reasons. Granted, this is not quite as disastrous for general program
correctness as UB, but it can lead to security bugs.

Point being: Underspecification can give you a _lot_ of leeway in how you do
something -- and that can be hugely valuable in practice.

Just as an aside: Personally I tend to prefer safety over performance, but I
was persuaded that UB is valuable by comments that Chandler Carruth of Clang
(and now ISO C++ committee) fame made about UB actually being essential to
optimization in C++. Sorry, can't remember where, exactly, those comments were
made.

[1] Everybody's probably using two's-complement (for good reasons).

[2] Not nearly as easily as plain buffer overflows, but there have been a fair
few of these that have been exploitable.

~~~
FreeFull
Rust will actually now panic when not in release mode when an integer overflow
happens, as opposed to how things used to be before, where it would just
accept the overflow silently with wrapping semantics. Here is the discussion
from when this change was announced: [http://internals.rust-lang.org/t/a-tale-
of-twos-complement/1...](http://internals.rust-lang.org/t/a-tale-of-twos-
complement/1062)

~~~
lomnakkus
Oh, that's good news!

------
moomin
Remember: Heartbleed was a consequence of the OpenSSL implementors avoiding
using glibc for allocations. A "fair" comparison is with unsafe rust code,
which of course can have the problem.

~~~
MichaelGG
Was it? I thought that only exacerbated the issue. malloc is free to return
previously used memory, is it not? Various systems might provide some last-
chance efforts against this kinda thing, but the bug would still exist and be
exploitable in some configurations, right?

~~~
moomin
Free by the standard, yes. In the average stdlib, not any more. In particular,
Linux web servers would be safe, which would have rendered Heartbleed more of
a "edge case platforms" kind of bug.

------
choffee
Can you use a Rust library easily from all the current C code?

~~~
steveklabnik
As long as you declare the functions to use a C ABI, yes.

------
ElectricFeel
Heartbleed = 100,000 brokenhearted nerds

------
EugeneOZ
Can real projects like Servo brag "0 unsafe code"? If not, then this safety is
not so practical as it sounds in theory.

~~~
steveklabnik
Cargo only has unsafe code in its FFI binding. (Funny enough, its binding to
OpenSSL.)

~~~
EugeneOZ
Some shiny badges "unsafe lines: 0" will encourage people to create products
without unsafe code. Like badge "build: passing" do.

------
exabrial
Ok, maybe, and definitely cool. But Java was supposed to prevent memory leaks
but it's still possible :)

~~~
alephnil
There is a whole class of memory leaks that is possible in C but not in Java.
You can leak memory by forgetting to deallocate it. This class is not possible
in typical Java code, because the garbage collector collects it.

Then you have memory leaks that happens because the application has data
structures that grow unbounded. The latter one is not really preventable by
automatic means, because the memory is still referenced from elsewhere in the
application, and a garbage collector can't know that this memory won't be
accessed again.

It does help that the most common source of memory leaks is eliminated
however. That many Java applications are memory hogs is more a result of
typical Java programming style than anything else.

~~~
mercurial
> It does help that the most common source of memory leaks is eliminated
> however. That many Java applications are memory hogs is more a result of
> typical Java programming style than anything else.

This may be part of it, but typically, the JVM will happily reclaim large
chunks of memory from the OS, without necessarily allocating anything
internally.

------
gaius
The code that led to the Heartbleed vulnerability was checked in in December
2011. The first _pre-Alpha_ release of Rust was in January 2012 (source:
Wikipaedia for both). Would _technology from the future_ have prevented it, is
the actual question, and the answer should be obvious...

~~~
geofft
Rust isn't technology from the future, for the most part. It's _engineering_
from the future -- everything is in one language with a native ABI -- but with
very few exceptions, each _technology_ in Rust has been around for a while.

Memory-safe arrays, which is what's at issue here, is precedented in tons of
languages. Even GW-BASIC got this right.

It should be an embarrassment to the entire systems programming community that
it's 2015 and we're only starting to think of replacing C.

~~~
steveklabnik
It's been an explicit goal of Rust to not use cutting edge PLT. For an
industry language, Rust's stuff is pretty new, but that's because industry
languages lag so far behind PLT as a field.

