
Securing Firefox with WebAssembly - edmorley
https://hacks.mozilla.org/2020/02/securing-firefox-with-webassembly/
======
pcwalton
It's really great to see this work. Cutting down the amount of memory-unsafe
code in the browser is an important ongoing goal.

I'd love to do the same for our contenteditable implementation. The hard part
here is to implement a DOM abstraction, because that code currently pokes at
DOM objects directly and you can't safely do that across a wasm barrier. Once
we've done that, though, Firefox's contenteditable implementation would become
a cross-browser library, which opens up some interesting opportunities. We
could use it in Servo, and authors could ship it on their Web pages if they
want to ensure a consistent contenteditable experience across browsers.

~~~
nicoburns
How would this work? Isn't the content editable attribute magic so far as web
code is concerned. You can't currently implement contenteditable in userspace,
can you?

I guess maybe you could make an attribute which just enables a flashing
cursor, and then everything else could be done with DOM apis...

~~~
PetahNZ
Well Google Docs editing is fully custom, including the selection and caret.
So it must be possible.

~~~
nicoburns
I believe Google Docs implements it's own text layout entirely from scratch.
Sure, you can do that, but that's a lot of work, and it doesn't perform
particularly well.

~~~
jhpriestley
I've implemented text editing on top of contenteditable, and directly via key
events etc. The latter is far less work. contenteditable gets you 60% of the
way there but is so buggy and inconsistent that you have to override all its
behavior anyway.

~~~
chrismorgan
As a user I bet I’d prefer the contenteditable approach. I find that
_absolutely consistently_ the cleverer a web rich text editing widget tries to
be, the worse the experience is, and the more it breaks my model of how such
things work on the native platform. Dropbox Paper’s is atrocious (I’ve
reported several _super_ -annoying bugs, and never had even a response).
Slack’s new widget is abysmal (I haven’t even bothered filing bugs with them,
because everyone else has already complained about the same things—most of
all, its caret handling is quite insane). Google Docs I haven’t edited with
for many years, so I can’t fairly remark on it. I can think vaguely of a
couple of tech demos of not using contenteditable at all from a few years
back, and at that time they were awful on desktop and entirely unusable on
mobile. I don’t know if the situation has improved _at all_ from that, but I’m
sceptical.

Do you happen to have either or both implementations on the public internet?
If so, I could see just how many seconds it takes me to be infuriated by them,
especially by the non-contenteditable text editor. If I fail, I will gladly
eat my metaphorical hat, but presently I would assign odds of well under 0.1%
of that happening.

Sure, contenteditable has many bugs and inconsistencies, and Chrome’s
implementation especially is a toy as regards functionality (e.g. poor table
and image handling, difficulty with distinguishing a caret inside the end of
one node from one after the end of that node), but much of the 60% that it
gets you is stuff you _can’t get at all_ any other way, especially insofar as
it varies by platform, mostly deliberately.

pcwalton’s proposal interests me because it would actually _concretely_
examine what can and can’t be done, and definitely identify the shortcomings.
It would have a chance of actually being good.

~~~
nicoburns
> I find that absolutely consistently the cleverer a web rich text editing
> widget tries to be, the worse the experience is

I don't know about this. Google Docs isn't great (I haven't used Paper), but
CodeMirror ([https://codemirror.net/](https://codemirror.net/)) is
_excellent_. Performance is flawless. All the platform shortcuts + extra
niceties like multiple cursors.

~~~
chrismorgan
I should have clarified that I’m specifically speaking of _rich text_ editors.
CodeMirror is a plain text editor, which removes most of the difficult-to-
implement-without-contenteditable stuff. It uses a textarea, not any
contenteditable or contenteditable-like thing.

CodeMirror is rather good as such things go. Last time I tried it it had
_serious_ issues with some keyboards on Android (regardless of browser, I
believe), but that looks to be working well now. Well, either that or the new
Firefox for Android has worked around the input problems; could be either, I
don’t have a URL that I know was broken and hasn’t been updated recently.

But even so, it still has problems, some niggling and some major. Navigation
keys don’t behave natively (e.g. on Windows, select a range and then press Up
or Down and it should go up or down one line from where the caret is, but
instead Up takes you to the start of the selection and Down to the end). I
can’t get a caret thumb on Firefox on Windows, and it seems very hit-and-miss
on Android, and the Samsung keyboard on Android is going _crazy_ with its
suggestions as you move the caret around the document—and that’ll be basically
because of my next point.

Perhaps most seriously, it’s _completely_ unusable from the perspective of
accessibility tech. And most damningly, guess what the solution to that is?
They’re working on CodeMirror 6, and concerning accessibility
[https://codemirror.net/6/](https://codemirror.net/6/) says:

> _This version leaves more to the browser, instead of “faking” the editing
> process in JavaScript. This makes it more transparent to screen readers and
> other accessibility tools._

You know what “leaving more to the browser” means?

contenteditable.

Yep.

It still interferes with various native user agent functionality (e.g.
navigation keys are still wrong and it’s still interfering with caret thumb
and long press on at least Windows), but it’s distinctly better because it
reduces the amount of the important type of cleverness.

------
est31
> but we’re performing the wasm to native code translation ahead of time, when
> Firefox itself is built.

This might be an advantage for startup performance, but it's also a
disadvantage performance wise as then you can't use all available features of
the CPU. That's one of the advantages that wasm gives you.

> we were often asked, “why do you even need this step? You could distribute
> the wasm code and compile it on-the-fly on the user’s machine when Firefox
> starts.” We could have done that, but that method requires the wasm code to
> be freshly compiled for every sandbox instance. Per-sandbox compiled code is
> unnecessary duplication in a world where every origin resides in a separate
> process.

Android keeps an on-disk cache, performing native compilation at installation
time, or at first startup after an OS update. One could have a similar cache
for Firefox as well.

Anyways, this stuff can be improved in the future as well. This doesn't change
the fact a bit that it's an amazing and really great idea to improve safety of
the browser. I love it!

~~~
chrismorgan
> Android keeps an on-disk cache, performing native compilation at
> installation time, or at first startup after an OS update.

If you’re describing what I think you’re describing, that was a Dalvik thing
(Android 4.4 and earlier), which ART (Android 5 onwards) doesn’t do. I also
found it a pain, because from time to time even when there hadn’t been an OS
update or any other such change my phone would take three or four minutes
longer to boot up as it decided it had to optimise all its packages. No idea
whether that was a typical experience.

~~~
est31
The compile target of ART's dex2oat tool is actually native ELF code while
Dalvik's dex2opt only created odex files, so the on-disk cache still exists.
I've found sources that this is still done on app installation. On whether
it's still done on OS updates, I couldn't find sources, but I guess your
experiences mean that now OS updates don't trigger recompilations any more.
Thanks for the pointer!

~~~
yzmtf2008
It still happens, except now the recompilation is done _before_ the reboot for
the upgrade, so that it doesn't create a massive downtime for upgrades. But if
you manually upgrade, you can still see one (very long) step called
"Optimizing Apps".

------
bigato
if parts of the web browser start being shipped as wasm code, we will
eventually reach the point where the web browser shipped to the user is only a
wasm vm, and all the rest will be shipped as optional libraries or even
downloaded on the fly. Even stuff like the html engine, the css, and the
javascript. In that world, using the messy web standards evolved over time
would be optional. The web browser would then become the universal virtual
machine that the world seems to want it to be, instead of a browser. The web
would be the app distribution system. One could for example, decide to write
their site using tcl/tk.

Implementing the wasm vm and its basic apis would be simpler in a new
operating system. Because the way it is now, the web browser itself is more
complex that writing a simple operating system. That hinders innovation in the
Operating System space.

~~~
allendoerfer
All I can see is people wanting to use web-technologies to develop
applications. So I would place my bet on the DOM/CSS evolving further and
swallowing everything. JavaScript might get some contenders.

~~~
pjmlp
50 years later the mainframes have won.

Language environments and containers.

~~~
marcosdumay
They have been "winning" since they started losing. Our PCs are much more
similar to 80's mainframes than to 80's PCs.

------
badsectoracula
Looks like eventually Wasm will become JVM and Firefox will become HotJava[0]
:-P

[0]
[https://en.wikipedia.org/wiki/HotJava](https://en.wikipedia.org/wiki/HotJava)

~~~
imtringued
Every single day I am thinking how the JVM could actually be a platform that
is worth using in many situations. That day may come but certainly not within
the next decade. No matter how well you optimize your code the JVM will ruin
it and make it slower and consume more memory than necessary. Of course none
of this matters in a world where your internal dashboard with single digit
user counts is set to use 4GB RAM just "to be sure".

~~~
eitland
> No matter how well you optimize your code the JVM will ruin it and make it
> slower and consume more memory than necessary.

Maybe you are really good at this.

But for a good chunk of software engineers AFAIK compiling Java to bytecode
and running it on the JVM easily outperforms their optimized code performance
wise in most cases.

JVM and JDK writers (and the same people on the Dotnet side) aren't dimwits
and their efforts over the last two decades are being applied every time we
compile and run software on their platforms.

------
skybrian
This is kind of neat but it's not clear how to think about what WebAssembly is
doing, since the output is native code. I guess it's essentially a safe
compiler for C++ code?

For a language whose compiler already outputs safe binaries, this step would
be redundant.

It's not that easy to write a safe compiler though. Would a compile toolchain
that uses WebAssembly for all compilation be useful?

~~~
nikbackm
It's not a safe compiler for C/C++, the compiled wasm code can still be
compromised, it just cannot touch the rest of the process except indirectly
via returned values.

~~~
cogman10
And, to be clear, people could still do bad stuff with compromised WASM.

The big difference is that the WASM sandbox significantly reduces the surface
area of what bad stuff can be done.

Today, a compromise in the browser means the attacker can do whatever the
browser can do (which is usually a LOT). With the sandbox, a compromise can
only really affect what the sandbox has available to it. That means, if your
sandbox only exposes a single method which takes in a string and returns a
string, the worst thing an attacker can do is return a malformed string.

Of course, if you mishandle that returned string then bad stuff will happen
but it's a far cry from the input string being able to potentially cause
arbitrary code execution which installs a virus on your machine.

To really do something evil you have to not only compromise the code running
in WASM, you have to find a way to break out of WASM. That's a lot harder to
do.

------
daxfohl
Is there any reason to continue using process-level sandboxing if this
approach is available? It sounds like WASI adds an extra layer of security
even beyond process-level sandboxing by being able to limit access to system
resources. Or is access to system resources really not the concern here / can
you do that just as easily with process-level sandboxing?

And you can pass callback functions to sandboxes, which IDK if you can do with
separate processes. Does process-level sandboxing provide any advantages WASI
doesn't?

------
daxfohl
The docs list WASI as currently experimental[1] and has missing features[2]. I
understand how the sandboxing approach can add security, but if using
experimental technology to enable this, doesn't it potentially open up more
new holes than it closes? I'd love to hear more detail about what exact parts
of WASI are used here, and what happens if you compile C code that targets a
"rough edge" or "missing feature" of WASI by accident?

[1]
[https://github.com/bytecodealliance/wasmtime/blob/master/doc...](https://github.com/bytecodealliance/wasmtime/blob/master/docs/WASI-
overview.md)

[2]
[https://github.com/bytecodealliance/wasmtime/blob/master/doc...](https://github.com/bytecodealliance/wasmtime/blob/master/docs/WASI-
intro.md)

~~~
afiori
External experimental tools are different than internal experimental tools. It
would not be a good idea to do the same using a tool they have little control
over, but in this case they have full control over cranelift (up to even
feeling secure in using a different version)

------
TuringTest
The sandbox architecture model makes me think of cell membranes.

[https://plsyssec.github.io/rlbox_sandboxing_api/sphinx/](https://plsyssec.github.io/rlbox_sandboxing_api/sphinx/)

------
beagle3
It's very impressive that they can do it with existing libraries properly.

Personally, if I had to design a solution for this problem, I would use the
io_uring model - which would allow every part to reside in its own process and
memory space.

------
nwah1
This kind of technique could also be applied to the parts of Firefox built
with Rust, correct?

~~~
stjohnswarts
It could but Rust already solves the issues this is aiming to solve but with
fewer steps. I don't know what Mozilla's policy is on adding Rust vs C++ for
new features.

~~~
cogman10
I somewhat disagree.

Safe rust solves the memory safety problem. However, there is still the
possibility of a logic bug causing rust to touch something it shouldn't.

The sandbox approach is about adding a second level for malicious code to
bypass. You now not only need to find a way to get past the code, you also
need to find a way out of the sandbox.

It's a little like running your apps on a server with highly restricted
permissions. You do that so the app compromise limits what is exposed.

------
gok
I wonder how this would compare to compiling the sandboxed code with ASan and
UBSan on

~~~
earenndil
Afaik those make for a 2x performance hit. And they don't necessarily protect
against buffer overflows; if you overflow out of data addressable from one
pointer, but into data that's legally addressable from another pointer, that's
still an issue, but it won't be caught.

~~~
gok
But WASM has the same buffer overflow problem, and at least that bad of a perf
hit.

~~~
earenndil
WASM has less of a perf hit.

------
jokoon
I'm still waiting for better toolchains.

WASM is really the greatest thing, but it's lacking proper support from
compilers. Binaryen seems like a beast to use. I don't really understand why
WASM is not just supported natively directly by clang or gcc.

~~~
froydnj
The WASI tutorial for compiling C/Rust to wasm and running it has some useful
information on this front:
[https://github.com/bytecodealliance/wasmtime/blob/master/doc...](https://github.com/bytecodealliance/wasmtime/blob/master/docs/WASI-
tutorial.md)

We wouldn't have been able to put wasm in Firefox like this if there wasn't
decent compiler/runtime support via the clang ecosystem.

~~~
wahern
That C code has a bug where the inner write loop writes the same portion of
the buffer after a short write.

The Rust code, by contrast, slurps up the entire file contents into a buffer
before writing it out. If someone is going to write code that way why even
bother with a low-level language? (This "memory is infinite" mentality is
something I've noticed in other Rust projects, even major ones like mio.)

~~~
sunfish
Good catch on that bug! I'll fix that.

Beyond that, that file is just a simple example for showing how to work with
the toolchain and the sandbox.

