Hacker News new | past | comments | ask | show | jobs | submit login
WebAssembly doesn’t make unsafe languages safe (00f.net)
161 points by paraboul on Nov 25, 2018 | hide | past | favorite | 83 comments



The general point that this article makes is true--C and C++ are still unsafe, even if you execute them in a VM--but there are some very important caveats to note:

* ROP should be impossible in Web Assembly, because the call stack is a separate stack, inaccessible from the heap and effectively invisible to the program.

* For the same reason, classic buffer overflow attacks involving attacker-supplied machine code are impossible. In addition, Web Assembly bytecode is never mapped into the heap, so attackers cannot inject shellcode via the usual methods.

* Mmap functionality is proposed for the future [1], which would allow for the implementation of guard pages. (Guard pages are fairly weak defenses anyway...)

I'm sure that it's possible to attack a Web Assembly program's control flow by overwriting vtables and function pointers and taking advantage of the resulting type confusion. But it's significantly more difficult than it is in native code.

[1]: https://github.com/WebAssembly/design/blob/master/FutureFeat...


If WebAssembly had tagged pointers that could be avoided, as proven by Solaris for SPARC with ADI in production.

Because when something is possible in security attacks, it will eventually be exploited.


ADI doesn't seem to me to have enough bits to effectively protect against vtable type confusion.

Besides, nothing stops a wasm compiler from emitting such checks itself, if it wants to.


Not sure about that, it does seem quite effective, though.

Having the compiler emiting it wouldn't be an option, as hardware enabled solutions seem to be the only ones accepted by C devs, as shown by SPARC and now ARM.


I think there is the possibility of integrating the source maps and the WASM with special purpose execution envs to apply these checks.


You can overwrite vtable pointers, but you're further restricted to calling another function of the same type signature.


Making unsafe languages safe is not a design goal of WebAssembly. The author seems to believe that WebAssembly should provide many things that are the responsibility of the operating system and standard c library.

I do not believe that is a good way of thinking about webassembly. It shouldn't be thought of as an operating system target (like Windows, Linux, or Mac); it should be thought of as an architecture target (like Arm vs x86). What the author wants is an operating system and runtime, which you can write for (and standardize on) web assembly and then load your program into it.


> it should be thought of as an architecture target (like Arm vs x86)

Then I pick SPARC with ADI tagged pointers, as used by Solaris.

Or ARM64 tagged pointers being adopted by Android.

WebAssembly could also provide such feature.


Many people seem to think it is. I work on the area of secure software stacks and the #1 “helpful” suggestion I get is “why don’t you use WebAssembly?” As if that made a significant difference to the attack surface, at all.


WebAssembly is designed to provide safety, in the sense that WebAssembly programs are sandboxed and unable to do anything you don't give them permission to do. WebAssembly won't make the runtime behavior of your programs correct, which is what the article seems to be getting at, but that doesn't make the WebAssembly VM model unsafe or insecure from a host perspective.

If you isolate untrusted code in a WebAssembly VM, that should reduce the attack surface for the system as a whole to whatever functions you expose into the WebAssembly VM.

Personally, my bias is to suggest that using Rust would significantly cut down on (but not eliminate) the attack surface inside the WebAssembly program... but some people don't like to hear that.


The simple version:

WebAssembly does protect the host from a compromised process. WebAssembly doesn't do jack to prevent a process (and whatever data it controls) from being compromised in the first place.


It does in the sense that it totally protects the stack and doesn't allow creating new executable memory. Control flow can be manipulated, but then only via changing function pointers to other functions of the same type


Control flow can also be manipulated via memory corruption, making the existing code take other decisions, due to non valid semantic states.


Sure, but that's even weaker, since you can't even create new control flows, just cause surprising ones.


It is enough to work around authentication checks, for example.


It doesn’t even do a particularly good job of that, given the size of the WebAssembly code base and reasonable bugs-per-kLOC assumptions. If you really want to protect the host then run a small, probably correct interpreter or JIT that is simplified enough to prove properties about and have an implementation small enough to be potentially bug free.


Well, it certainly could be helpful, if part of your stack wants to run inherently untrusted code. I think this is what gets people excited by the idea of putting it into the kernel.

It is a bit surprising, though, that many people do not realize WebAssembly doesn't make C code safe against itself.


Making unsafe languages safe (at least to some degree) has to be a design goal of WebAssembly; else, it has zero business being used to run arbitrary code that's automatically downloaded from the Internet.


There are different kinds of safety, though. There's sandboxing, which means that applications running in WebAssembly should not have access to resources they are not privileged to access. And there's memory safety, which means the WebAssembly application itself shouldn't try to break guarantees about memory. These are two separate things, and only the first can do things outside of breaking the website you're on.


WebAssembly protects you from untrusted code getting out of its sandbox and accessing data outside its own context, and that's the whole point.

But it will not (and hasn't been designed to) magically remove crashing bugs from your code, just as (e.g.) Javascript doesn't protect you from calling an undefined function.

But those bugs can't do any harm to your machine except crashing the WebAssembly application itself.

*this doesn't mean the sandbox itself doesn't contain bugs of course, but it's the job of the sandbox to prevent stuff running inside it from accessing stuff outside


Your system is safe from the arbitrary code. The article points out that WASM makes it really hard to catch memory bugs inside the app itself even though these bugs can't escape the sandbox.


> it has zero business being used to run arbitrary code that's automatically downloaded from the Internet

That has always been the very nature of the web.

Web Assembly, like other web applications, will be executed in a sandbox separate from other things. Isolation alone does not make it safe, but it does prevent corruption of other things.

This is why many websites execute advertisements in an iframe, because the code that comes across is not trusted and often not safe. The unsafe code will still attempt to stalk and/or attack the end user, but the iframe prevents that malicious code from corrupting the serving page. There is no reason expect anything different from WASM.


> Technically, this can be implemented already. However, for successful adoption, a standard interface has to be defined.

This is literally the opposite of how the web works. The web is based on the Extensible Web Manifesto[0], where first we come to a consensus and see widespread adoption, and then we make a standard that reflects that consensus.

> We have a fantastic and highly secure execution environment from a host perspective. But from the guest perspective, that very same environment looks like MS-DOS, where memory is a giant playground with no rules.

Well, yeah. Add me to the list of people who are just kind of confused about why fixing C's problems is the responsibility of the browser, and why people thought that it was ever a priority for WASM. WASM is a VM-like isolation chamber for low-level code. It's not designed to make your code safe, it's designed to protect the host environment from unsafe code.

I've seen multiple people make the argument that manually laying out memory in WASM is a design flaw rather than one of the biggest points of the entire implementation. Manually laying out memory with close to zero restrictions or paradigms or safeguards is the thing that makes WASM good.

I feel like I missed something, I don't know where people got this idea that WASM was trying to be a higher level tool. It's an extremely low-level language-agnostic compile target for both safe and unsafe languages, and languages that care about safety should add their own safeguards as they see fit.

[0]: https://github.com/extensibleweb/manifesto


It is worse than the title says: a key tool for trapping corrupted process state, null pointer segfault, is turned off in WebAssembly targets.

The second most common source of process corruption, integer overflow, usually ignored in native targets because direct memory corruption is so much worse, is also adopted into WebAssembly wholesale.

This is all overwhelmingly worse than in the native case, because we make some effort to run only trustworthy native code, but browsers actively solicit unknown code from known-hostile sources--most particularly, web ads.

The existence of safe-ish languages doesn't help, because there is no incentive to deploy them in user-hostile code fragments.


>This is all overwhelmingly worse than in the native case, because we make some effort to run only trustworthy native code, but browsers actively solicit unknown code from known-hostile sources--most particularly, web ads. The existence of safe-ish languages doesn't help, because there is no incentive to deploy them in user-hostile code fragments.

WebAssembly strongly sandboxes WASM modules that are executed, so the situation where an attacker creates and serves a malicious WASM module to the user's browser is one that is handled well.

The article is saying that WASM's protections stop a module from escaping its sandbox, but it doesn't prevent a buggy module from having its own memory get corrupted by its own memory-related bugs common in unsafe languages.


WASM was designed to be fast enough to compete with native code and can't do integer overflow checking without serious performance regressions - Rust had to make the same tradeoff.

Furthermore, induction and compiler optimizations can remove the need for such checks. Aren't you solving this at the wrong layer of abstraction?


I'm not sure the NULL pointer access being allowed point is valid.

The C standard allows for the NULL pointer to be numerically any value, not just 0, and on some architectures in isn't 0. So I would hope C compilers targeting wasm would just use a different value for the NULL pointer, eg 0x8000000000000000 not 0.

Whether they actually do or not I don't know, but it isn't an architectural failing of wasm.


It's true that in theory a NULL pointer could be 0xffffffff or such, and that would prevent some of the problems the article mentions. However, that would only help when you assign NULL to a pointer explicitly. The problem is that in practice NULL pointers are often caused by other things, like thinking that zero-initialized memory contains a pointer - in fact, this is even more common in wasm than natively, since wasm guarantees memory and stack are initialized to zero.

This is serious enough an issue that for debugging purposes emscripten has a SAFE_HEAP mode which will check for NULL pointers on every access. We'd still need that to look for 0 even if the compiler considered NULL to be something other than 0.


> The problem is that in practice NULL pointers are often caused by other things, like thinking that zero-initialized memory contains a pointer

Which, to be clear, is not a valid assumption wrt portable C or C++.


While C may not mandate zero-initialized pointer is NULL, POSIX does.


You can have a valid pointer to 0x00, but you only see it on microcontrollers and it doesn't really exist in any context WASM is being discussed.

The author's argument that this is a mistake is true. WASM should almost certainly have had some arbitrary offset like 0x1000 just due to the extreme commonality of null pointer derefs, and changing NULL to something else won't come close to solving that problem. You still have pointer derefs of fields in structs that were calloc'd or memset(0)'d. Compiler can't magically swap out the writes-to-zero there with whatever NULL should be instead.


Instead of providing some one-off special case for addresses close to 0, they could have what actual computers actually have: a way to mark regions of memory as unusable (which the WebAssembly code could then do), which would also provide help with other issues like stack overflows.


What computers actually have is literally just an arbitrary offset to catch null pointers.

They then also have ways to mark pages as read-only and such, but they don't burn a bunch of pages on this for NULL pointer catching. They simply just don't map anything before the program offset.


I don't understand what you mean by "arbitrary offset", but the way people usually implement both "crash on null dereference" and stack guards is the same: they not only don't map anything at those addresses but they map reservations at those addresses that are neither readable nor writable (and so usually never commit to backing memory). On 32-bit macOS Mach-O executables you can even see the reservation as it is so explicit: there is literally just a "segment" defined in the executable, with no contents, mapped to the pages at address 0 that can't be accessed. If you leave it out of the executable, the page might get used (such as by mmap, maybe due to malloc): it is entirely optional, but happens to be a default from the C compiler/linker. As we almost always want this, often operating systems now do this for you by default without requiring the programmer to instruct it, but that is just a different programmer instructing the computer, still using the same virtual memory paging system: there is no magic property of the computer that is making low addresses inaccessible, they are just using the mechanism for mapping virtual address space with different permissions (and again: this same mechanism is used to prevent null dereferences and protect against stack overflows, both of which are semi-arbitrary flaws in C that the computer has no business hardcoding).


Setting NULL or nullptr to any value other than 0 is highly likely to break most complex C/C++ codebases.


Even simple codebases that do

    if (!p)
Anywhere would immediately cause problems.


This is legal in C, even if the representation for a null pointer is not actually "all zero bits" internally. The null pointer is defined to compare equal to 0, which is the contrapositive of what your test is doing.


What part of the C standard allows for the NULL pointer to be numerically any value? I am looking at n1256.pdf, but I can't seem to find the relevant text.


It's not spelled out explicitly. Rather, what's missing from the standard is the requirement that a null pointer is all-bits-zero, or that reinterpret_cast'ing it to an integer type produces a zero (note that even these two are actually separate). This is deliberate - there have been platforms in the past where null pointer had a non-zero bit pattern for largely the same reasons (null address was valid and useful).


n1256.pdf, Section 6.3.2.3 Pointers, says "constant expression with the value 0".

  An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.
I don't think this definition excludes platforms that use non-zero bit patterns. A constant 0 in C code should get converted to the appropriate (possibly non-zero) bit-pattern for the platform.

Also, my understanding is that whether the platform has useful things at address 0 falls in the category of "implementation defined".


> Also, my understanding is that whether the platform has useful things at address 0 falls in the category of "implementation defined".

If address zero is a null pointer, then IIRC no allocated object, whether static, automatic or heap, can have that address - i.e pointers to them must always compare unequal to nullptr.

If null pointer is something other than address zero, then it's just a regular address, and can have anything - it's not even "implementation defined", not anymore so than saying that e.g. 0x12345678 is a valid object address on x86.


> I don't think this definition excludes platforms that use non-zero bit patterns.

It doesn't; the comment above yours is saying the same thing as you are.


You are right, but changing the starting address of the WebAssembly memory map to something else (like 0x8000) would've been easier.


> there would be clear benefits in also delegating basic dynamic memory management to the host.

It would be quite hard to spec malloc/free into the Web platform, because of the necessary detail: the behavior of that malloc/free pair would need to be identical (same pointers returned from malloc) in all browsers, for every possible sequence of allocations and deallocations. GC is actually easier to spec since so much of the underlying details are unobservable.

It's not impossible in theory, but it seems like it would mandate all browsers use the exact same allocator implementation (say, dlmalloc version x.y.z). In addition, this would not allow optimization over time, again, unlike GC.

In the non-browser case, server-side implementations may not need to worry about specs, so more options may be open there.


>the behavior of that malloc/free pair would need to be identical (same pointers returned from malloc) in all browsers, for every possible sequence of allocations and deallocations

Why would you ever spec it like that? That doesn't seem needed at all. Plenty of programs run across platforms with wildly different mallocs and everything runs file. Replacing the malloc with LD_PRELOAD tricks is even done sometimes.


For almost all platforms that's true, but the culture of standardization on the Web is unique. It comes from wanting a Web page to have the maximal chance of running across all standard-compliant browsers, forever - that's just not the case for practically every other platform.


If only that was true. Javascript has had all plenty of inconsistent behaviors across browsers and the feature matrix for what is and isn't supported in each browser is hugely complex. And even if that was true standardizing the results of malloc makes no sense, it gives you no extra compatibility guarantees for any sane programs and no one cares about insane ones.


I've yet to see a standard-conforming C app that relied solely on the standard library - which, of course, includes malloc/free - to not run across all platforms (that have a filesystem and a console, or some equivalent input/output facilities, anyway).


C is unsafe because it comes with no memory protections. In an environment like a typical operating system (depending on the OS of course), there's a chance it will escape its sandbox. This risk does not exist in wasm. You can write the most atrociously misguided C ever and run it in wasm and know for sure that it will never do anything worse than corrupt its own program state.

You get similar benefits by using a decent OS and/or by putting the untrustworthy code in e.g. a docker container. It's still code that can't be trusted but at least you are setting some hard boundaries that have no easy/known ways of being bypassed.

However, it would still be bad if the program gets compromised. If, say, you are handling some credit card details and are using some C code to do that and it gets compromised, all of the interesting stuff (i.e. the credit card details) would be inside those boundaries. This is why using C to do that is not a great idea.

This is a concept that is often misunderstood by people: most of the interesting stuff (from a hacker point of view) happens inside the sandbox. This is where you access and handle sensitive data and access protected resources (e.g. a third party website). Many attacks use relatively low tech mechanisms such as script injection, man in the middle attacks, social engineering, etc.

Wasm indeed does nothing to protect against that. If you are handling user input in any way, that is potentially a way for hackers to inject code in your sandbox. If that sandbox has access to or control over anything interesting, that just got compromised. If you are using a language that is notorious for its decades long history of input validation mechanisms getting compromised (cough C cough), that means you can't trust input validation to function properly even inside a wasm container.

This is exactly how many browser attacks work. They don't install viruses on your machine or whatever but they simply trick you into revealing your credentials, visiting some evil website, or entering your credit card. A bit of injected javascript or a redirect is all that this takes. Any injected code never leaves the sandbox; it doesn't have to.


Which is exactly why C++ modules in the CLR are marked as unsafe.

There used to be the possibility to restrict the C++ language to a safe subset via /clr:pure and clr:safe switches, but now Microsoft official position is that C# or other .NET languages, are the only way to effectively write safe and verifiable code.

https://docs.microsoft.com/en-us/cpp/build/reference/clr-res...

https://docs.microsoft.com/en-us/cpp/dotnet/pure-and-verifia...


There is also another blog post (link to white paper inside) that talks about different memory safety issues with WebAssembly if using a memory-unsafe language: https://www.forcepoint.com/blog/security-labs/new-whitepaper...


> First, hosts have no visibility on how memory is being managed within a guest. Want to diagnose memory leaks? Good luck with that.

Is that really true? I think we can implement a memory allocator done by hosts. By using "import" feature of WASM, a guest could implement a memory allocator that imports functions from the host that ask the host to look through the gest's memory directly (it is possible in JS code) and then allocate one. Maybe it will be slower but it is certainly possible that hosts can know what is happening to the memory in this way.


I love the idea of WebAssembly in the browser, but what's the point of using it as an universal module language everywhere else?

We can already run most popular languages pretty much anywhere, and I imagine at better performance than WebAssembly, no?


The advantage is that code run through WASM is fast, sandboxed and platform-independant. Nothing else is capable of giving you all 3 of those features.

Embedded scripting languages (JS / Lua) are platform-independant and sandboxed, but much slower than native code.

C is fast and small. It can be very carefully sandboxed by the OS (eg iOS apps) but the sandbox has historically been very leaky. Also binaries must target a specific hardware architecture.

The JVM & .NET environments are platform independent, but their heavy reliance on garbage collection slow them down. They also both have large runtime environments which makes them more awkward to embed. I wouldn't want to embed the JRE inside the linux kernel or in a web browser because its so big and it has such a terrible security track record. But WASM would work great.

What other technology would be a good fit for making mods for a multiplayer game? Java? Too bloated. Lua? Workable but slow. C? You can't ship 3rd party mods safely and even if you could they would need to be recompiled for every platform. But WASM will work great. What other tech could you use to run a user-supplied filesystem driver in-kernel? Everything else is too slow (lua) or too unsafe (C).


Ok, plugins/mods for real time applications are a good use case. Not only games, but also databases, web servers, etc.

I'm starting to see the value in using WebAssembly outside the browser.


> Memory is represented as a single linear block

iirc you can have multiple instances or modules, each with its own private linear memory, and they can be set up to call each other's exported functions. It's just that current compilers don't use that encapsulation when compiling a single program. Am I wrong about this?

(I haven't used WebAssembly, only read about it some time ago. But here's a link in support: https://groups.google.com/forum/#!topic/e-lang/3A6zYWF6u5E "Multiple module instances only share an address space if they explicitly export/import a linear memory, which is just another kind of module-level definition. A module can define its own linear memory and not export it, in which case nobody else can access it.")


It might be possible when exchanging only numbers. But I think it's going to be quite tricky when exchanging pointers of buffers between modules since values of a pointer in a module is meaningless to the other module if the momory block is not shared.


this is complete nonsense. A normal c program running on a modern operating system has the mmu control what memory it can access. So, Web assembly's "just a block of memory" is no better - it is the same.


Correct.

A normal C program has full reign over the entire process memory space.

A wasm program only has access to the call interface provided to it and its linear memory region allocated to it.

The guarantees are much different and greatly in favor of wasm. In wasm the stack and the code segments are off limits.


You missed the author's point by miles.

C only has access to its process space as does WASM. WASM's only improvement is it makes stack smashing bugs harder (which ironically makes real security worse as it means you can't use retpoline, but let's ignore that for now)

HOWEVER it gives up on using guard pages and address randomization (systemic preventions against common bugs like buffer overflows), making WASM in practice less safe than C.

WASM is only safer for the host embedding untrusted code. For trusted code, though, it's across the board worse in every way - including security. Which shouldn't be all that contentious of a statement since WASM had no goals or intentions to ever replace trusted native code execution. That's not a thing it tries to do, and it shouldn't be a surprise that it didn't do it.


> which ironically makes real security worse as it means you can't use retpoline

Why wouldn't the WASM JIT retpoline all code (probably only if the underlying processor needs it) rather than relying on the guest programs to do it themselves.

> HOWEVER it gives up on using guard pages and address randomization (systemic preventions against common bugs like buffer overflows), making WASM in practice less safe than C.

Once again, wouldn't the JIT be able to enforce this at runtime?

It's not like WASM binaries are referencing raw address offsets.


> Why wouldn't the WASM JIT retpoline all code (probably only if the underlying processor needs it) rather than relying on the guest programs to do it themselves.

Possible, but is that then a runtime option in the WASM header? How does it know if the program needs the cost of retpoline or not? And how can a program ensure that it's getting such mitigations when it needs them?

> Once again, wouldn't the JIT be able to enforce this at runtime?

The JIT has no insight into the app's malloc/free usage, so no, it can't. There is no system allocator in WASM, just a big chunk of linear memory that the app does whatever it wants with.

For an actual case study in why this is risky all you have to do is look at OpenSSL's heartbleed. If it had been using the system's malloc/free (which WASM doesn't have) instead of using its internal free lists then heartbleed largely wouldn't have existed on platforms like OpenBSD.


> Possible, but is that then a runtime option in the WASM header? How does it know if the program needs the cost of retpoline or not? And how can a program ensure that it's getting such mitigations when it needs them?

You're looking at it the wrong way. Untrusted code doesn't have a "I pinky promise that I'm safe" bit they can twiddle in their header. If the code running is untrusted, it doesn't get to make that decision, all code under that context needs to be retpolined. (V8 already does this https://chromium.googlesource.com/v8/v8/+/7d356ac4927e9da3e0... )

> The JIT has no insight into the app's malloc/free usage, so no, it can't. There is no system allocator in WASM, just a big chunk of linear memory that the app does whatever it wants with.

It can retarget the code so that the virtual base address of the linear region isn't fixed. That's about all ASLR does anyway.

> For an actual case study in why this is risky all you have to do is look at OpenSSL's heartbleed. If it had been using the system's malloc/free (which WASM doesn't have) instead of using its internal free lists then heartbleed largely wouldn't have existed on platforms like OpenBSD.

You can implement all of the malloc protection strategies that OpenBSD uses in user space. It's completely legitimate to use your own malloc in those circumstances, libc's malloc isn't magically special, and it makes sense that they'd want to rely on memory allocation with additional guarantees than libc gives you. OpenSSL's sin wasn't re-implementing malloc; it was re-implementing it poorly (and not bounds checking input from attacker controlled packets).


> You're looking at it the wrong way. Untrusted code doesn't have a "I pinky promise that I'm safe" bit they can twiddle in their header. If the code running is untrusted, it doesn't get to make that decision, all code under that context needs to be retpolined. (V8 already does this https://chromium.googlesource.com/v8/v8/+/7d356ac4927e9da3e0.... )

No, you're not understanding.

retpoline doesn't keep you from attacking others, it keeps you from being attacked. But not everything handles sensitive data and thus doesn't care about being attacked, and doesn't want to pay the cost of the retpoline that they didn't need at all.

How can a WASM app signal "I need retpoline because I have secrets to protect" from "I don't give a damn about retpoline because there's no secret data in my process space at all"?

That's not a decision WASM can unanimously make on behalf of others.

> You can implement all of the malloc protection strategies that OpenBSD uses in user space.

No, you literally can't. WASM doesn't have the APIs necessary to do that. It doesn't have mmap & mprotect. Those are in the "proposals we might consider" future section of WASM. But WASM today cannot implement a malloc/free on-par with those in actual libc implementations on major platforms. To say nothing of debug tools like valgrind.

> OpenSSL's sin wasn't re-implementing malloc; it was re-implementing it poorly (and not bounds checking input from attacker controlled packets).

So the solution to people re-implementing malloc badly is to force everyone to re-implement malloc? How does that follow at all?

I get WASM doesn't want to include malloc/free, but the problem is it didn't include paging-based allocation APIs. It used a nonsense linear growth heap design that doesn't match anything about how memory works on any platform and has been obsolete for 20+ years.

This isn't a big deal in the hyper specific context that WASM is actually targeting, but it is a big deal if you're talking about shoving WASM in places it never had any intention of being.


> That's not a decision WASM can unanimously make on behalf of others.

I literally linked to the commit where V8 is indeed unanimously making that decision. If you clear the indirect branch table when transitioning to and from all untrusted programs, what's your threat model again?

> I get WASM doesn't want to include malloc/free, but the problem is it didn't include paging-based allocation APIs. It used a nonsense linear growth heap design that doesn't match anything about how memory works on any platform and has been obsolete for 20+ years.

Go read the WASM spec. It's explicitly is designed for multiple linear regions, just how mmap works. It just didn't make it into the MVP.


> I literally linked to the commit where V8 is indeed unanimously making that decision.

And for things where retpoline isn't needed it's unambiguously wrong (wrong in that runtime is slower than it should be for no gain). What are you not getting about that? They made a decision that when it's wrong it's still safe, but it's still going to be wrong for a non-trivial amount of users.

> Go read the WASM spec. [...] It just didn't make it into the MVP.

Yes, I know, that's why I said what I said. Which was correct and supported by both the spec and your statement just now...?

We're not talking about hypothetical future WASM, we're talking about WASM as it exists today and what problems it can/can't effectively handle.


This is ignoring an important benefit of the libc implementations of malloc/free on OpenBSD, free pages are being opportunistically munmap'd at random. This catches actual bugs.

Otto Moerbeek has a great recent Twitter thread on the topic: https://twitter.com/ottom6k/status/1065594523559096321

https://twitter.com/ottom6k/status/1063118597160091648

> The JIT has no insight into the app's malloc/free usage, so no, it can't. There is no system allocator in WASM, just a big chunk of linear memory that the app does whatever it wants with.

This point by kllrnohj illustrates a pretty significant weakness.


I figured kllrnohj was referencing the fact that OpenBSD clears data when it's allocated. That you can do from WASM pretty easily.

Additionally WASM is moving towards multiple linear regions, which should allow mmap/munmap like functionality.

And in the context of OpenSSL, this kind of fanciness in some libc's that'll make all sorts of syscalls randomly is exactly why they wrote their own allocator.


The Heartbleed bug would have been found then fixed by the munmap, as the process would have crashed. This has nothing to do with clearing data, it would not have found this.

> And in the context of OpenSSL, this kind of fanciness in some libc's that'll make all sorts of syscalls randomly is exactly why they wrote their own allocator.

"web asm -- if it was good enough for openssl!"


Heartbleed fully allocated the larger buffer. It wouldn't have crashed. It was just uninitialized memory being read out. Yes, munmap and remap would have cleared that ram, but so would the malloc clearing.

> "web asm -- if it was good enough for openssl!"

More like, "OpenBSD, so slow that people feel the need to write their own malloc".


Please write an essay on how to attack WASM based code and how to structure your codebase to mitigate the outlined flaws.

[edit this was not snark, I believe the parent has important insights]

I believe it is possible, but the severity seems orders of magnitude less than native code.

The host controls the VM interface boundary. Native code can only get this partially via process isolation.


"Full rein".

And the C heap is typically a linear memory region (that grows monotonically) isn't it?


brk does grow linearly (a holdover from the days of segmentation). But malloc may well allocate chunks of virtual memory for large allocations. Windows doesn't have a brk equivalent and the underlying heap is fully implemented via VirtualAlloc.


Pretty much only glibc malloc still uses brk. GNU being GNU I guess. Modern mallocs like jemalloc use mmap exclusively.

FreeBSD straight up did not implement sbrk at all for aarch64 and riscv – very very few things broke because of that (one of the things that did break was, IIRC, GNU Emacs).


Fair, but I think this all amounts to "A linear patch of memory is fine primitive to build an allocator or standard library on."

The "not faulting on address zero" thing is interesting but not crazy -- only broken programs will break.


While you can build it that way it is not at all a good way to do it. You lose all the benefits of paging when handling fragmentation - you basically will never be able to shrink the data segment on most practical programs - and therefore never be able to return memory to the operating system.

brk is an ancient holdover from when memory protection was done via segmentation. Paging eliminates the need for this historical misfeature. I'm a bit sad WASM propagated it further into modern systems.


Isn't paging orthogonal to using brk? Or is "paging" something more specific than "virtual memory is handled by the OS in big chunks"?

I guess your allocator can try its best to prevent fragmentation, but you're right that returning memory to the OS isn't going to happen without explicit compaction (which nobody in native code is going to do.) Dunno if anyone ever expects memory use to go down though.

(Is less true than it used to be? -- I guess we used to make big swap files and now that's less fashionable.)

I guess if it's not too much more of a burden on the OS to make it keep track of a Swiss cheese virtual memory allocation then it is useful.


The OS implements brk under the hood with "swiss cheese virtual memory" so you don't get any real optimizations here. On AMD64 paging is mandatory. It does have a cost but the MMU hardware accelerates this to make it mostly not noticeable.

The brk system call places an unnecessary restriction on how the memory is used that is not imposed by the hardware. Back in the 70s when segmentation was a thing and virtual memory hadn't taken hold this would be different - but virtual memory has won.


The process’ address space includes more than just malloc zones: it includes the code of the running process, the stacks for all of the running threads, linked libraries and their imported functions, etc.


NULL pointing to valid memory seems like a serious flaw that can and should be easily fixed.


There have been plenty of real environments where NULL pointed to valid memory (AIX, for example)

It also has a reasonable use case ( or did, pre Spectre).

It allows you to hoist and speculate loads that may be null. Since it is valid memory, instead of a fault, you would just get a result (usually zero on most of these platforms) you would throw away if it was not needed.


> While escaping the sandbox itself may be difficult to achieve, application security doesn’t benefit from the mitigations commonly found in traditional environments.

I feel this the most important point regarding use server-side.


There is a yet in the original title.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: