top | item 19271487

Implications of Rewriting a Browser Component in Rust

464 points| zwliew | 7 years ago |hacks.mozilla.org | reply

268 comments

order
[+] atoav|7 years ago|reply
This is in tune with my own experience using Rust in production: it can stop you from doing certain classes of mistakes, but it won't stop you from doing stupid things.

But the idea that I don't have to think about certain classes of problems allows me to give these stupid things more focus, which is surprisingly refreshing.

The predictable nature of Rust was so refreshing for me that I ended up using it even for smaller reusable scripts where I would happily have used Python before but soon got annoyed with obvious errors that would only show up once you run a program.

If you e.g. have a `print foo` in some obscure branch that rarely happens, that print will ruin your day if you use Python 3. If python would be a little like Rust you would get on save (or at least on compile) a hint or error, that the print should look like this: `print(foo)` for Python 3. You can be incredibly careful and rust will still catch things now and then, that would have gone unnoticed into production unless you have immense test coverage.

I like Rust for the experience I had with it. It definitly changed how I approach certain problems in a very good and productive way, even when I don't use it.

[+] asdkhadsj|7 years ago|reply
Agree completely. For a bit of my own story, a year+ ago I had the option to write a project in Rust and evaluated it vs Go. Long story short, I tried rust, and it was a massive headache and I failed. We used Go (as I had been for ~4 years).

Fast forward to ~2 months ago, a work project dictated tight control over memory which, while possible in Go, had me looking at alternatives. I decided to give Rust another try. This time it wasn't just an evaluation, it was needed to work so I bought and Rust book and spent some after hours time learning/etc.

This time, Rust has been an absolute joy. I have no understanding why last time was so painful, and this time it's been so amazing. Maybe it was the book[1]? Maybe it was just a 2nd round of learning based on my previous experience? Regardless, it's been great.

There's just so many mental overheads like what is concurrent safe, what is non-null, etc that are just great to not think about anymore. On top of that, the formatter and LSP are just great. It highlights in my text editor (Kakoune) what variable caused an error, where it gets moved incorrectly, etc. So much just works, it's great.

My only complaint these days is:

1. I find it odd that some things like slice reads can still panic by default. Yes, I can use `foo.get(1)` to avoid panics, but still - it's a bit odd to me. 2. I'm anxiously awaiting async/await. It's quite difficult to be patient.

[1]: Programming Rust: Fast, Safe Systems Development

[+] johnisgood|7 years ago|reply
> before but soon got annoyed with obvious errors that would only show up once you run a program.

From what I gather you are comparing static vs dynamic typing, and I agree. I do not use Python because I prefer a subset of errors caught at compile-time. However, it is silly to make it sound like that this is somehow limited to Rust. You could just as well have used Go or OCaml and feel "refreshed" because obvious errors would have been caught at compile-time.

> The predictable nature of Rust

Again, given the context, it is static vs dynamic typing. This is not something limited to Rust. In case you were not talking about static typing and its pros, could you please tell me what kind of official formal proof tools exist for Rust that makes it predictable?

[+] FreeFull|7 years ago|reply
Python will actually give you an error for `print foo` as soon as it parses the file. But there definitely are other scenarios where you'll only get the error in the middle of execution (such as passing the wrong type of thing as an argument to a function)
[+] Twirrim|7 years ago|reply
Use a linter. Most IDEs will do it for you natively, and even if you're dealing with a terminal based editor like vim, it's really easy to get the output of pylint, flake8 etc. running in it. It's extremely rare I ever get a syntax error make it through to code review, let alone build/deploy.
[+] londt8|7 years ago|reply
I recommend using mypy for python, type safe python has came a long way. The type annotation syntax has been in the language spec for years
[+] dtech|7 years ago|reply
> If you e.g. have a `print foo` in some obscure branch that rarely happens, that print will ruin your day if you use Python 3

You can use any compiled language for that though, and even some uncompiled ones (PHP will refuse to run the file)

[+] js2|7 years ago|reply
A linter would catch that.
[+] GrumpyNl|7 years ago|reply
Every software allows you to do stupid things. Its your job to avoid them.
[+] rkangel|7 years ago|reply
It's nice to see a balanced, real world, case study including 'these things are fixed by Rust', 'these are problems that don't occur in idiomatic Rust', and 'these are problems that Rust can't help you with'.

I'm a big fan of Rust, but the one sided 'Rust makes all the problems go away' articles don't provide any value.

[+] tspiteri|7 years ago|reply
It also highlights an example of a security bug introduced during rewriting; highlighting that rewriting any significantly large piece of software is bound to introduce bugs.
[+] millstone|7 years ago|reply
These are all ways that Rust is neutral or better. Were there any ways that Rust was worse? For example, did the previous code use value-type templates? If so how was their absence worked around in the new code?
[+] hedora|7 years ago|reply
Overall, it did a decent job of being balanced, but I don’t buy the memory overflow example at all.

For one thing, idiomatic C++ bounds checks by default. You need to use at(). If you don’t like typing at(), you can implement an array type that always bounds checks fairly easily. On that note, the vulnerable c++ code should be using accessors, not indexing to access the oddly packed and laid out array. Even the fixed version wouldn’t pass a code review from me. You could write equivalently bad code in any language that supports array types, and get similarly broken results.

For another thing, there’s no evidence that you couldn’t achieve the same improved data structures in C++ using its type system (which is turing complete...)

The “thread safe by default” property sounds interesting; I’d be interested in reading more about that.

[+] duxup|7 years ago|reply
Yeah I keep hearing / reading about rust, even seen some demos but the demos all end with "oh no I'm not using this for anything". Still cool but ... want to see someone doing something in production / get their thoughts on that.

Edit: To be clear I'm not saying anyone isn't using it, this is just more of a comment about the impression I can get when I hear about X tech is so cool, but that's most of what I hear and at some point I want to see those same articles about real world use / experiences. I'm aware Mozilla and others are using it.

[+] mrath|7 years ago|reply
I primarily use Java for my job. Security and memory related features of Rust are not an advantage compared to Java. But I like rust because it feels modern and produces efficient standalone binaries. Most of my hobby projects are in Rust now. But I would not rewrite any of my work projects in Rust even though they require ultimate performance. That would be a maintenance burden.

It is great to see people rewriting in Rust where it makes sense.

[+] ilovecaching|7 years ago|reply
Rust is often sold feature by feature; the borrow check offers proof like safety over fuzzing, cargo provides real versioned package management over makefiles or git commits...

I choose Rust because taken as a whole, Rust changed the way I approached laying out my memory and how I composed my code. I think this more than anything leads to less issues than the equivalent C++. The article points out that a Rust vs C++ solution to any given problem are going to be completely different.

My only desire for Rust is to see compile times speed up and the C++ interop to improve.

[+] mrath|7 years ago|reply
Yes compilation times are a big pain point. I heard that there is work being done in this area.
[+] jupp0r|7 years ago|reply
One of the major hurdles in rewriting parts of C++ projects in Rust is that the interop surface between both languages is C. The necessary interface layer has created more bugs and work than the conversion saved. I'd really like to see more high-level interoperability between the two languages in the future, although C++ is a pretty fast-moving target at this point, with all the changes in C++20.
[+] jcranmer|7 years ago|reply
Honestly, I wish a few different major languages would get together and start developing system ABIs that move beyond C as the interchange language.
[+] steveklabnik|7 years ago|reply
> The necessary interface layer has created more bugs and work than the conversion saved.

Is this from a project you did? This is weakness, for sure, but I'd be interested in hearing more about why it failed for you! We have some people working on this.

[+] Paul-ish|7 years ago|reply
This was my experience when I was at Mozilla converting a part of Firefox to Rust. It also feels like a lot of Rust's safety guarantees go out the window when you are using the C FFI to talk between C++ and Rust. I'm not sure how bad it actually is in practice (vs just writing the whole thing in C++).
[+] WhitneyLand|7 years ago|reply
That's a proportional problem at least right? As months pass the more of your libraries converted to Rust the lesser the problem.

The potential party spoiler being third party code that's not practical to replace. In some cases even decades won't change it's nature, as a few examples have shown.

[+] rini17|7 years ago|reply
Does Rust allow for taint analysis too like Perl has for long time? If not I'd say it's missed opportunity.

(It marks all untrusted input as tainted and programmer must explicitly parse the data or mark them untainted to pass them further.)

[+] palotasb|7 years ago|reply
In Rust, or any statically typed language such as C++ or Java, the idiomatic way to handle untrusted input is to treat it as a "bag of bytes" before you access it. Then either parse it into a strongly typed object or bail out of parsing. The strongly typed object is safe to use. Bailing out (throwing an exception or returning an error type) does not allow the program to continue assuming that the (malformed) input was correct.
[+] steveklabnik|7 years ago|reply
There's not a language feature to do so, but you can do it through the type system if you wish.
[+] SamReidHughes|7 years ago|reply
That's a feature that might work well with some plausible ways of guaranteeing memory safety in a type system because object reachability is a form of taintedness.
[+] guscost|7 years ago|reply
Recently some colleagues started using the type annotations in the latest python3. Really excited for this feature! It’s going to make a lot of our production systems safer to work with.

And of course Rust is a great technology, etc.

[+] herogreen|7 years ago|reply
Can you build in some kind of "unsafe release" mode, so that every array bound check that were asked in the code are skipped ? If not, would it be an interesting feature ?
[+] steveklabnik|7 years ago|reply
No. Such a thing could only remove some kinds of checks; for example, if you see that code sample later in the thread with a manual check, it wouldn't know that's what you're doing.

In general, we don't want to make it easy to turn checks off. They get removed if the compiler can prove they're not needed; if they're there, they're almost always for good reason.

[+] dagmx|7 years ago|reply
You could do something with rusts feature system and macros. In essence you'd have a macro that would run a different line of code if your feature is enabled versus disabled, so you could use the unbounded lookup on the array.

That said, this would be a user implementation and wouldn't be likely to be provided by the standard Library

[+] sanxiyn|7 years ago|reply
This is trivial to implement, but it will never be accepted by Rust upstream. There will be a fork if someone really wants this.
[+] tonetheman|7 years ago|reply
Meh. The whole thing seems weird to me.

We totally tried to write this twice then we switched to language x and everything is great. Feels like something a language zealot would say. I would scoff if someone at my company rewrote a core section in a different language. It is their language so maybe they just told them to do it that way ha.

[+] gubbrora|7 years ago|reply
> could have been caught by a run time bounds check

And here I thought rust was all about zero cost abstractions.

[+] tspiteri|7 years ago|reply
This comment is confusing two completely separate concepts: zero cost abstractions and run time bounds checks. Rust does provide zero cost abstractions: if you do something explicitly/manually instead of using the abstraction you get the same generated code.

If you want to combine the two concepts, I guess you could go for an example like this:

    if index >= vec.len() {
        panic!("out of bounds");
    }
    let value = unsafe { vec.get_unchecked(index) };
which can be rewritten using higher level abstractions as:

    let value = vec[index];
The second is more abstract, and that abstraction is zero cost, you do not pay for using the abstraction more than the explicit code above it.
[+] mitchty|7 years ago|reply
You can't know everything at compile time. Runtime checks are witnesses that run at program execution instead of compile time.
[+] rocqua|7 years ago|reply
Speculative execution and branch prediction make bound-checks much less of a burden. I believe this is actually a whole category of Spectre/Meltdown.
[+] gameswithgo|7 years ago|reply
normally you can use iterators to access arrays and when you do, array bounds checks are elided.

if you can't use iterators, and you need to omit bounds checks for perf, you can do so with unsafe blocks.

[+] blub|7 years ago|reply
It's not, that's C++.

Rust requires a lot of runtime checks, but that's the price one has to pay for memory safety.

[+] rujuladanh|7 years ago|reply
The article is arguing that Rust somehow has better capabilities than C++ to fight memory-related bugs, but the example vulnerability given is not something Rust can solve nor is more powerful than C++ in its “bug catching” capabilities regarding this kind of bug.

Concretely, the article claims that in Rust the vulnerability doesn’t become a bigger problem because it simply crashes at run-time due to built-in bounds checking. True, but that is alao the case as well with C++ if you were using the equivalent Vec type with mandatory bounds checking - which many projects do (and, critically, enforce).

Personally, I like what Rust brought to the compiler/language world. However, some people is definitely overstating the case. Most non-trivial memory-safety errors and vulnerabilities are related to runtime problems like the example shown. In these, no language can help in the general case - we are not solving the Halting Problem. Therefore, saying Rust is immune to memory-related problems is not true. It is true, however, that those bugs will not trigger anything worse than a crash if there is no unsafe blocks. The same way that many other common languages out there do (Java, C# and many others).

The same way, I have seen people (and even the linked blog) to claim Rust is free of race conditions or thread-safety issues (even if it introduced great ideas to write correct code).

Giving a false sense of security is the worst thing we can do.

[+] rujuladanh|7 years ago|reply
(Continued...)

It is not realistic either to ask everyone and every company to rewrite all their C/C++ code in Rust. Even if it were financially doable and a rewrite were to happen, in many cases it would simply be best to move to a language like C# anyway, not Rust; for productivity reasons. Where performance allows, of course.

In my opinion, the realistic and pragmatic solution is, instead, to strive to make all languages (in particular C and C++) embrace security-first approaches/types/mechanisms like Rust does. The compiler tecnology is already written - now retrofit as much as possible into C++ (even to the point of introducing a “safe” scope if needed) and allow companies to embrace it at minimal cost and progressively.

[+] scoutt|7 years ago|reply
A system crash is a bug. Period. In many cases it could lead to Denial of Service. An insulin pump can stop working.

I remember when C# came out almost 20 years ago. People said "I can forget about managing memory so I can focus on the logic". Programs kept crashing, memory problems were still there.

The article goes with "...remove the burden of memory safety from our shoulders, allowing us to focus on logical correctness and soundness instead...". More or less the same, and admitting that said problems won't go away.

But here we are, it's 2019 and we're still using C/C++ as if nothing happened.