Hacker News new | past | comments | ask | show | jobs | submit login
Notes on WebAssembly (neugierig.org)
158 points by chmaynard on June 20, 2022 | hide | past | favorite | 56 comments



> It is a stack machine, no registers; instructions generally push and pop 32/64-bit ints and floats onto that stack.

One thing to note here is that WebAssembly is not just a stack machine but a stack machine encoding of an instruction tree. You can still execute it as like a stack machine but it doesn't look like a typical stack machine. In particular there are no generic stack manipulation operations at all (`drop` is the sole exception), so it is trivial to convert this encoding back to the instruction tree. This greatly simplifies the validation step.

(Edited to clarify that WebAssembly is still a stack machine, just that it lacks usual stack operations.)


Thanks for this comment. In a previous draft of the post I had more about this, but I decided to omit it because I didn't have much insight about it beyond some handwaving around how it's just encoding the instructions and the optimizer will do something different. Your comments (this and the other one in this thread) are better than what I would have written, so I learned something today! (Edit: I updated the post to link to your comment.)


I dislike referring to WASM as a "stack machine" for this reason. It is useful to remember that the text format started out as a low-level lisp-y expression tree with local variables and nested control flow. It still basically is that, but with the AST syntactically flattened in a way that looks a bit like a stack machine. (Admittedly, multi-value and block returns have since made it trickier to recover the AST.)

Things like Forth or the JVM byte code are true stack machines: all locals can be on the stack, and the stack shape can vary at runtime (which in turn makes validation harder). I seem to be in the minority, but I think wasm is quite different and the distinction is useful to maintain.


I am concerned that web assembly will mean an end to ad blocking and to a proliferation of malware, as the end user is basically going to be handed an opaque binary blob they can't do much with but execute.


Wasm in the browser can only communicate by going through browser APIs. Most ad / analytics / tracker blocking works by intercepting HTTP requests, so they will work just as well for Wasm code.

WASM that uses the regular DOM for rendering also doesn't really change blocking capabilities.

The only real issue is visual/selector based blocking when frameworks do their own rendering via canvas, webgl or webgpu. Flutter does this. But doing your own rendering comes with a lot of overhead, both in download sizes, startup time and performance.

Applications might do that (with lot's of caveats), but most websites never will. So I'm not that concerned.

Wasm is certainly much lower level than JS, but also much higher level than Assembly. It's far from an opaque binary blob. All the minified and partially even obfuscated JS is not that much better.


> but most websites never will

only because it's harder to do that right now than just describing a document via html.

But there will come a time when it's trivially easy to write a full app in the browser via wasm, and the outcome will be that the user can either accept or reject the entire app, rather than selectively choose the part of the app to view or use.


You’re right, but it’s worth noting that nothing about that is particular to wasm. A JS app could equally avoid the DOM and render its own UI components via WebGL/WebGPU.


wasm is just fast at doing that that's all.

However, do make us to think we need another API for general JS without WebGL and GPU access.


"there will come a time when it's trivially easy to write a full app in the browser via wasm"

This will be facilitated by libraries and frameworks that do all the low-level heavy lifting for the developer, who'll be able to concentrate on higher level concerns.


WebAssembly just had its 7th anniversary. If it hasn't happened yet, it probably won't happen at any time in the future.

And I think this hasn't happened because it wouldn't work with the "ad mafia" business model. Embedding hardwired ads into a WASM blob of an application doesn't make any business sense just as it doesn't make business sense to host ads locally from your own server. Instead ads are typically injected from an external ad network, and those requests can be filtered even when they are coming out of a WASM blob.

As for the malware comment: WASM isn't any different from minified Javascript in that regard.


> If it hasn't happened yet, it probably won't happen at any time in the future.

Remember that ads is an unethical arms race and there was a time where ad companies were using VBScript to create system-level popups which defeated any popup blocker.

The question is not if it will happen, but when and what will be the consequences.


More to the point, it didn't happen with Java, it didn't happen with Flash and it didn't happen with Silverlight. This is just a perennial conspiracy theory.


You don't need wasm for that, anything that just throws up a huge <canvas> in the browser will do.

What I expect wasm to do is to introduce new frameworks that take whatever the devs consider to be native behaviour and plonk it right into the browser. There are already plenty of demos out there that break the most basic browser features because compiling all of Qt into a canvas view can't possibly render a native experience.

Then again, we've lost the concept of "native" years ago when we started packaging browser engines with popular applications.

Soon we'll reach a stage where native code gets compiled to wasm, bundled with a slightly outdated copy of Chrome and sold off as a "native application".


>the end user is basically going to be handed an opaque binary blob they can't do much with but execute.

If you step back far enough, you realize that pretty much everything we run on our machines fits this definition. The key feature of WebAssembly is that the code has NO access to the rest of the system, by default. It's this freedom to just run anything, without the worry of unwanted side effects, that gives me hope for the future.


If they're comparing to JS, that was already true of JS.

And JS is so easy to minify / obfuscate that de-obfuscating JS isn't much better than dis-assembling WebASM.

Anything that gets popular gets dis-assembled, even MIPS and x86 binaries.


Minified obfuscated Javascript isn't really all that different. You can create call graphs out of WASM binary as well, decompile and analyze it in detail.

In fact, even before WASM, you could compile C++ to very hard-to-read asm.js subset of Javascript as well, see Emscripten.

You could create tools to block WASM based on signatures, heuristics, behavioral analysis, etc. Difficulty level is very similar to doing same with Javascript.

[0]: https://en.wikipedia.org/wiki/Emscripten


"You could create tools to block WASM based on signatures, heuristics, behavioral analysis, etc."

Yes, the concern is that as the browser becomes the operating system, we'll need anti-malware and anti-virus tools on it just as we've needed them on Windows.


The only way to fight that is to, as a community, refuse to run _all_ wasm that is found to serve ads, or infringe on privacy, or exhibit other malware traits.

The only way to do that would be to use browser extensions that disable and report, to disable for other users automatically. But how would we identify the executables? hash? That can be changed trivially.

I really fear that wasm brings us 180 degrees from "don't execute anything from the internet" to "all WWW usage is running other peoples' executables".


That's already the case! Also, having other people tricked into running other-other people's code with the others' credentials against _your_ code.

I think WASM will actually be more secure than the exsting www model. See: POST for an example :)

I agree though... there will be bad actors and an ever evolving cat and mouse game


The end point of ad blocking is neural networks analyzing the final rendered image before being shown to the user.

This blocking could be strict, or lax, where you block even non-intentional ad stuff like someone writing in a HN comment "I love X".


"The end point of ad blocking is neural networks analyzing the final rendered image before being shown to the user."

This is an endless cat-and-mouse game, where advertisers could start using DRM that prevents the viewer from interposing anything between the browser and the digitally-connected monitor.

Maybe eventually users will start wearing AR glasses that filter out ads for them...

Until advertisers figure out a way around that too.


Wasm executing in the browser can't do anything on it's own. Can't make requests, cant update the dom, can't ask for input, nothing. All that still needs to happen in javascript.

So I don't see much has changed.


55 votes and no comments. Looks like a lot of people are interested in WASM but no one has anything to say about it.


There are some comments now... I guess a lot of people (like me) are interested in WASM, but most don't have enough knowledge to say anything "authoritative" about it. When I read "It is a stack machine, no registers", I remembered this other article http://troubles.md/wasm-is-not-a-stack-machine/ and thought "Ok now, these can't both be right at the same time, right?". But apparently they can - as another comment here states, "WebAssembly is not exactly a stack machine but a stack machine encoding of an instruction tree". To that I say, if I can use the stack as I'm used to from other systems, I don't really care if it's a "real" stack machine. But I'm not really sure of that either...


I've updated my comment to add more clarification, but let me elaborate a bit more here. If you have the following code:

  int powi(int n, int m) {
      int s = 1;
      while (--m >= 0) s *= n;
      return s;
  }
The compiled WebAssembly code would look like this (the actual textual format):

  (func $powi (param $0 i32) (param $1 i32) (result i32)
      (local $2 i32)
      (set_local $2 (i32.const 1))
      (block $break
          (loop $continue
              (br_if $break (i32.ge_s (tee_local $1 (i32.sub (get_local $1) (i32.const 1)))
                                      (i32.const 0)))
              (set_local $2 (i32.mul (get_local $2) (get_local $0)))
              (br $continue)
          )
      )
      (get_local $2)
  )
Note the clear resemblance between the original code and the compiled binary. The main difference is two branch labels, because WebAssembly distinguishes forward and backward jumps by block types (`block` vs. `loop`). The binary retains all parameters and local variables.

When we say WebAssembly is a stack machine, it means that this gets serialized like this: (The textual format allows both forms, while the binary format is a direct translation of the latter.)

  (func $powi (param $0 i32) (param $1 i32) (result i32)
      (local $2 i32)
      i32.const 1
      set_local $2
      block $break
          loop $continue
              get_local $1
              i32.const 1
              i32.sub
              tee_local $1
              i32.const 0
              i32.ge_s
              br_if $break
              get_local $2
              get_local $0
              i32.mul
              set_local $2
              br $continue
          end
      end
      get_local $2
  )
In principle you can execute this just like a stack machine with variables, but optimizing implementations will translate this into machine codes, which wouldn't look much like either forms. So the stack machine representation doesn't imply the stack machine execution.


Iirc WASM was initially explicitly designed to be translate-able to Asm.JS (that in turn translated to JS), basically they continued the way Asm.JS had retained backwards compatiblity with JS to either force other browsers to play along in implementing the standard or just look bad in benchmarks. (I think they've relaxed on back-compat now but it was a tool for getting everyone on board and once that had happened it was easier to break it).

This is why WASM kind-of-is a stack machine. Basically the opcode sequences should be possible to easily re-translate back into a syntax tree for a back-compat JS generator.

This is also why you have these block/loop branches.. it basically is translateable to labelled while loops and if blocks.


> Here's a thing that is obvious to me now but I think wasn't obvious when I started. C++ has its own notion of the stack and heap, in that in C++ you can e.g. take the address of something on the stack. This translates to Wasm by C++ stack variables existing in the Wasm-addressable memory, in the same way the C++ stack exists in memory. That memory is totally under the control of the C++ code. Meanwhile, Wasm runtime structures, including the call stack, are separate and are effectively invisible to C++. In summary, C++ memory errors like buffer overflows can still stomp on C++ memory, but that memory is distinct from any Wasm structures such as the call stack, so all a broken Wasm program can do is mess up its own memory, not the host's.

And this kids, is why security in WASM still needs to be taken with a grain of salt if any of those modules touch critical data or return values that influence the caller's decisions.


Yes. WASM is secure in that it won't let programs escape. But it runs C and C++ programs which means that it lets them do anything those programs would do - just inside a secure box. If you want your program to be more correct and secure that's then down to you to make sure it is. WASM just makes sure it can't take over the user's PC (or host's, if it's running outside of a browser).


Not only that, but WASM leaves out security features that native platforms have, making it actually easier in some ways to exploit programs than it would be if they were native. https://www.usenix.org/conference/usenixsecurity20/presentat...


Which is a message that unfortunely many WASM advocates leave out, asserting it is secure all over the board, hence why I tend to point out there are actually some footnotes related to it.


Maybe I’m missing some nuance here but it’s not clear to me what the omission by wasm advocates is?

> And this kids, is why security in WASM still needs to be taken with a grain of salt if any of those modules touch critical data or return values that influence the caller's decisions

Isn’t this true of any sandboxed environment? You can trust it not to touch things outside of the sandbox, but any holes you poke to get data in/out need their own verification.


WASM is sold as being 100% secure.

No it isn't true for all sandboxed environments, there are sandboxed environments where memory accesses are bound checked for the underlying data types, instead of only the complete linear memory segment.


Thanks, that makes sense. This would only matter if you’re sharing the linear memory between different bytecode modules, though, right? If only one wasm module can touch a span of linear memory I can’t think of anything that it could do that wouldn’t be possible with type-aware bound checks (in the extreme, it could just treat the memory as an array of u8s and implement data structures on top of that, bypassing any type-awareness that the VM has)


In the extreme, one can write bytecode or Assembly directly and thus exploit everything, including binaries generated from safe languages.

The point is preventing that a 100 byte array gets a 200 index access, even though the overall linear memory segment for the WASM module is 500 big.


The sandbox design makes it very impractical to hack wasm, though. Okay you've got code execution in my browser tab, but now you can't send network requests, read the file system, access contacts, what are you going to do? Run a cryptominer?

I suppose you could access and maybe even exfiltrate data from the application itself but unless you get some kind of reflected XSS down to autoload your malware, your infection clears the second I refresh. When the application authors fix the hole and the user reloads your persistence vanishes.

Wasm won't solve C(++) memory management flaws but it can severely limit the impact they have in practice. In my opinion that's the strong suit of wasm as an application format.


WASM authentication module reusing code from something like Heartbleed.

Great it doesn't affect the host code, yet it affects the authentication, thus now a plain user might eventually be upgraded into admin.

And who knows it doesn't send network request?

It is all a matter of what the JavaScript functions imported into the module are capable of, or the code that happens to call into the module and what decisions it does in regards to the returned values.


I don't really understand the scenario you pose. WASM isn't exactly something you run on the backend. If you're writing code that you need to compile to run, what's the point of compiling to WASM when you can generate native binaries that are much faster and much less complex? To me, this stuff only makes sense for optimizing some kind of core logic in a browser game or heavy web application, which hopefully doesn't need to do any authentication at all.

I suppose developers could be foolish enough to break the WASM security model by exposing function to connect to arbitrary hosts but code running in the browser is still subject to CORS/CSP. You need to actively work around those to break the security layer and if someone is foolish enough to sabotage their own security that way then I don't think it matters much whether you use WASM or not.


This is on the browser scenario.

As for the backend, WebAssembly startups want to bring back Application Servers.

As how foolish one might be, as we often discover during pentesting of Web applications, quite often.


> As for the backend, WebAssembly startups want to bring back Application Servers.

We're really moving back to the early 2000s. I wonder when websites start working without Javascript again!

WASM on the backend makes sense in a few cases (i.e. let customers upload modules in a sandboxed, heavily restricted environment, or whatever) but building your entire application around a speedup format for a Javascript engine while writing low-level code is a new level of ridiculous I didn't even see coming.


There is a good chance that it will happen eventually. Atwood's law is: "Any application that can be written in JavaScript, will eventually be written in JavaScript." But the root cause of this is really that when you get enough volume of activity in some area then the lowest common denominator tends to take over. "Worse is better" should have been written as "Worse is better for adoption" because it is definitely worse for every other attribute. In general, network effects are lowering the quality of everything.


Here, for your amusement, WASM modules as Kubernetes containers.

https://krustlet.dev/

As for your JavaScript remark, well SPAs are now rediscovering SSR, naturally with newer buzzwords like hydration.


Where are the great C++ WASM frameworks ? Rust is Ruling the Roost here. Has Figma open-sourced anything yet ?


Shameless plug: It's not C++ (but C instead, but of course also usable from C++), and not "great" as in "created by a big Silicon Valley company", but check out the sokol headers:

https://github.com/floooh/sokol

...live samples:

https://floooh.github.io/sokol-html5/index.html


I just compile my C++ Qt code with emscripten and it mostly works.

See for instance:

- https://qmlonline.kde.org/

- https://qt-webassembly.io/designviewer/

- https://www.canonic.com/


This may be a problem with the underlying C++ code but many of these demos are absolutely unusable through touch screens. The UI doesn't seem to know what to do with simple things like a drag motion or a tap. Canonic won't even open the soft keyboard on my phone.

It's good that these things render flawlessly in the browser but it seems like the platform still has some work to do.


Last time I checked, Rust still depends on C++ tooling like Emscripten, LLVM and Binaryen to "rule the Roost".


the entire C/C++ ecosystem are WASM frameworks, thanks to emscripten, you can basically use everything

rust is stuck with "rewrite everything to be usable", it can't consume any of that

zig on the other hand.. it can, i think it'll have a bright future


Rust wasm support seems quite excellent, though? I haven't had too many issues with getting wasm output out of Rust. Emscriptem, though, has been a whole different story. I suppose it depends on what tool chains you're used to using.

I don't expect Zig to do any better or worse than Rust in this regard.


Zig can (eventually) do better than Rust because it includes the ability to compile C/C++ code to WASM, Rust needs an external C/C++ compiler for this (so you're back to having to deal with Emscripten if you need to embed C/C++ libraries in your Rust project).


The same is true in reverse, though, you need an external Rust compiler if you include Rust code in your C++ project. Mixing compilers and languages will always cause conflicts, I don't think either Emscripten or Zig will be any different other than that Zig has some backwards compatibility with C/C++.


They are good for different things, atm. Rust is focused on people writing new code for the Web, and does well there. C++ is focused on porting existing applications, which works well for things like Unity, Photoshop, Google Earth, etc.

The reverse cases are sadly not that great atm. If you have a Rust application that uses SDL2 and OpenGL, for example, porting it to the Web is not that easy (you either need to port those APIs to something else, or mix Rust and C to get Emscripten's SDL2 and OpenGL support). And if you want to write new C++ code just for the Web, current bindings solutions are not polished enough.

But hopefully all those things will keep improving!


Huh? You don't need to mix Rust and C, Rust has direct Emscripten support. I've used it + rust-sdl2 for a game project.


True, but there is an unfortunate amount of bugs in that area. We (I'm an Emscripten dev) would like to look more into improving Rust's Emscripten support, but there have just been higher priority items for us (and I guess others) so far.


Yeah that's certainly true, I've had to use weird compiler flags to get it to work properly.


> Writing to a null pointer is just writing to the memory array at offset zero, as legal as any other write.

This seems like a huge design mistake. Is it possible to make dereferencing a null pointer trap in WASM? That's an essential part of debugging C/C++ applications.


Thank you for these notes




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

Search: