 So for wasm time security begins with a safe implementation language wasm time is implemented in rust Google Microsoft and Mozilla have each independently found that about 70% of the security bugs in their web browsers Historically, we're memory safety bugs. These are things like use after freeze and out of bounds heap accesses And it turns out that this is exactly the class of bugs that rust helps us avoid And there's lots of languages that don't kind of suffer from those problems But what's unique about rust is that it also gives us the low-level control that we need to efficiently implement a language runtime So large portions of wasm time even have zero unsafe blocks Such as our web assembly binary parser, which is the first component to process potentially malicious input And the parts of wasm time that do use unsafe out of necessity to implement primitives Are carefully vetted both the implementation and the interface to make sure that you can't use them wrong But of course rust doesn't prevent every single bug It doesn't save us from a miscompilation due to a logic error in the compiler That could ultimately lead to a wasm sandbox escape So we're going to discuss the techniques that we use to address those sorts of bugs a little bit later on But first I wanted to point out that the benefits of a safe implementation language Actually extend to applications that are embedding wasm time as well. It's not just kind of only for the wasm time developers Because even a correct web assembly run times value is kind of weakened if the interface to that runtime is unsafe Or it's so clunky that it pushes and betters towards unsafe code out of convenience Or to meet performance goals because the safe APIs are kind of too slow and involve too many checks So that's why we designed wasm times user-facing API such that misusing it is nearly impossible It doesn't require any unsafe rust But at the same time the safety doesn't sacrifice performance So this example is our typed function API and how it kind of leverages rust type system to do one type check up front You kind of start by grabbing an untyped You know function from the instance and then you do a single type check to kind of convert that into a typed function Which you can then call any number of times and enter, you know wasm through that function with zero more Type checks are overhead, right? So our strongly typed APIs let us the wasm time developers kind of maintain the critical safety and variance for you the people embedding wasm time And this both avoids any kind of potential for misuse and also the overhead of things like repeated dynamic checks So malicious dependencies are becoming more common This is when an attacker gains control over a library that your application depends on say for unit testing And they add code to kind of search the file system to steal SSH keys And then the next time you upgrade your dependencies and run tests your world falls apart We cannot let wasm time and by extension any application that embeds wasm time be compromised by malicious third-party dependencies And so it's worth pointing out that wasm itself can help prevent against these kinds of attacks The component model and wazzy which have been a ton of talks at the conference about Give us capabilities based security and lightweight isolation between software components And this means that you can put Untrusted dependencies into their own sandbox separate from the rest of your application And you can kind of limit the blast radius of how much damage they can do But unfortunately, this can't really be a solution for wasm time itself Since wasm time needs to implement that sandbox and is kind of sitting below that abstraction level So to secure wasm time against malicious dependencies, we use this tool called cargo vet This was a tool created by Mozilla to mechanically ensure that every third-party Library used in Firefox has been vetted and manually reviewed by a trusted auditor And when performing an audit reviewers double-check the use of unsafe that potentially malicious User-supplied data is kind of handled with care and you're not you know blindly recurring recursioning recurring over User input that could let an attack or craft an input to trigger you to blow the stack Things like that a markdown parsing library doesn't try to access the file system or network when really it should have No business with either of those things And that using the crate otherwise just won't open the door to security vulnerabilities in production So in the wasm time project We use cargo vet to require that a trusted wasm time maintainer manually reviews all new dependencies and all updates to existing dependencies and Then at the same time we're slowly burning down the list of get-to-be-reviewed Libraries that wasm time already depended upon before we kind of introduced cargo vet And what's cool about cargo vet is that it actually benefits from network effects So it allows one project to import audits from another project And so the more trustworthy organizations that start using cargo vet and auditing their dependencies Then the fewer audits we the wasm time project will have to perform ourselves And kind of conversely the more organizations that trust our audits Then the more value each one of the audits that we do provides to the larger community So right now wasm time imports and trusts firefox's audits firefox imports and trusts our audits Google chrome and fuchsia are also publishing audits. And so the bigger the cargo vet community gets the better we all benefit But the security of applications that are using wasm time isn't just determined by wasm times development process It's also determined by how wasm time can help unlock more secure application designs That might not have been considered before say because the performance overhead made it impractical And so one example of this is what I call kind of the disposable instance paradigm I talked a little bit about this in my 2021 web assembly summit talk And all the work that we've put into making instantiation really fast at that time But I'm happy to say that we're even faster now So you can see that it takes just really a handful of microseconds to instantiate a new web assembly module Even when it has a full JavaScript engine inside of that web assembly module And so this is really fast enough that you can create a fresh instance per task Right and then when that task is completed you can throw away that task and then create a new instance for the next task and so this gives you The ability to do things like instantiate a fresh web assembly module for every HTTP requests coming in for your service list application For example, and it provides what I kind of call temporal isolation between tasks and So this means that a bug that's triggered maybe in one request to the HTTP server Can't persist and keep messing up with all subsequent requests that come after that And so this is something that wouldn't be possible or at least wouldn't be practical without all the work We've put into making instantiation super fast so Fuzzing is a software testing technique where we find security and correctness issues by feeding pseudo random data as input into the system that we're testing We the wasm time project we love fuzzing we do continuous fuzzing in the background 24-7 We do targeted fuzzing when we're developing new features We kind of fuzz in the large in terms of you know fuzzing all of wasm time But also in the small like fuzzing a particular, you know, web assembly text format parser And at the same time we contribute to and maintain some of the core fuzzing infrastructure That's used by the whole rust ecosystem And I would really say that our pervasive use of fuzzing is probably the biggest single contributing factor to wasm times code quality We fuzz because writing tests by hand while necessary is not enough We are fallible human beings and so we're inevitably going to miss edge cases Our minds are not twisted enough to come up with the kinds of inputs that a fuzzer is going to find when you give it enough time And so fuzzing can be relatively simple You know like this where we take random bytes and we throw it out a web assembly binary parser And we see if it crashes right and we do this kind of simple fuzzing But we also do more complex fuzzing and it can get as complex as you want, right? So we generate arbitrary pseudo random guaranteed valid wasm modules, which is a whole task in itself And then we compile those web assembly modules both with optimizations and without optimizations And then we assert that running both of them yields the same result either way Because any optimizations we perform need to be semantically transparent if an optimization is changing the result of your computation That's like a big problem, right? and so this is called differential fuzzing and In addition to differentially fuzzing against ourselves. We also differential Differentially fuzz against other wasm engines So we differential fuzz against a formally verified version of the spec interpreter We do it against a v8 the wasm engine inside chrome and we do it against the wasm I interpreter as well These are the tools which our fuzzing infrastructure is primarily built upon We use lib fuzzer, which is a coverage guided fuzzing engine that's developed as part of the LLVM project our fuzzers run 24-7 as part of the OSS fuzz project and then there are a variety of Rust tools that we kind of help maintain such as the cargo fuzz tool that kind of orchestrates building and running fuzzers the arbitrary crate which lets you build structured inputs for your fuzz targets and the lib fuzzer syscrate which provides the rust bindings to lib fuzzer So when you're creating a fuzz target, you're kind of pairing together Generators and oracles and so a generator is the thing that creates new pseudo random test cases And this could be you know a no op where you just take raw bytes from the fuzzer and then you pass them along To the system under test But typically for us we have an implementation of the arbitrary trait And so what this is doing is it's actually kind of interpreting the raw bytes from the fuzzer And it's turning those into structured inputs that you know actually mean something to us like a guaranteed valid web assembly module And then on the flip side oracles are the things that take those generated test cases and they check security and correctness properties And evaluate them in wasm time So this is kind of like the bit of code that runs a wasm test case in both wasm time and v8 and it compares the results So we have a variety of generators But the one we use the most is wasm smith and this is the thing that produces the random web assembly modules that are guaranteed valid And this helps us test deeper into wasm time and crane lift because we're not going to bounce off the parser due to a malformed memory definition And we're not going to fail the validator because of a type error inside a function body where we expected an i32 But got an f64 or something like that. So this means we really Get through we go into the compiler and all the optimization passes and we can ultimately run the results of it So we have configuration options to avoid generating code that's going to trap at runtime To only generate certain kinds of instructions such as only numeric instructions or only control flow instructions things like that Or to turn various web assembly proposals on or off and so you might wonder like how do we decide how to configure the test case generator and We throw fuzzing at that as well We do this thing called swarm testing where we tell the fuzzer to generate The configuration itself and then we use that configuration to generate the web assembly module And this ultimately improves the diversity of our generated test cases because we end up generating even weirder stuff And I'm happy to say that Firefox has also started using wasm smith to exercise its web assembly engine So it's great when you know, we can kind of all get better together So we use we use a lot of generators for our fuzzing, but we also use a lot of different oracles They can be really simple like did the program crash or did it fail in assertion? We run our fuzzers with various sanitizers. We can ask Address sanitizer if we saw use after free bug You know did we capture? If we capture the web assembly stack do we see all of the expected stack frames You know do we see the expected number of garbage collector managed allocations and D allocations? Is there an unexpected leak? Can we take a web assembly binary? Translate it to the text format and then translate that text format back to a web assembly binary And is it the same web assembly binary that was the original one because it really should be? And there's many more We even have a symbolic checker for register allocation that we use as an oracle for fuzzing and so Register allocation kind of goes from when you have you know a bunch of instructions that use an unbounded number of virtual registers To kind of an equivalent program that uses a fixed number of physical registers that the machine actually has And so what the checker does is it takes both of those versions of the program and it proves that the Version with the bounded number of physical registers Actually implements the same program as the one that had an unlimited number of virtual registers And so when we're fuzzing what we do is we'll actually generate arbitrary control flow graphs of basic blocks that contain Instructions that operate on virtual registers Then we run the register allocator and so we get an equivalent program that uses the fixed number of physical registers And then we use this checker as an oracle to assert that the register allocator did its job correctly And so we just do that in a loop and Yeah so When we implement new features in wasm time we write generators and oracles that are specifically designed to exercise those new features and That register allocation checker was one example as it was developed alongside a new register allocator for crane lift But when we're implementing new web assembly proposals in wasm time Kind of the baseline for considering that web assembly proposal implemented and done is to add support for that proposal to wasm Smith So that we can fuzz with that But we'll also create generators for testing more specific things like when we are implementing the web assembly reference type proposal We started emitting inline garbage collector write barriers in our code And so we generate it we created new generators specifically for exercising those things and really hammering on those operations And that found a bunch of bugs, you know before they went to production, right? We also developed a fuzzer for the component models interface functions before Or as in concert with their implementation in wasm time So you could really say we've fully embraced fuzz driven development But at the end of the day fuzzing just gives us a statistical claim that the program is correct with Respects to what the fuzzer has exercised so far, right? And the the longer we run the fuzzer and the more inputs we feed to it the closer that claim will get to 100% But it's never quite going to reach 100% because the input space is way too large or even infinite And so we'll never exhaustively enumerate every input and be able to say that it's correct for everything And so that's where our form our efforts to formally verify parts of wasm time and crane lift come in The vero asm project was a collaboration between UCSD Stanford and Fastly and it's a translation validator for WebAssembly programs that are compiled with crane lift It proves that for a particular program that was compiled with crane lift That programs control flow and its memory accesses cannot escape its isolated sandbox And so this is not a claim about like the handful of inputs we happen to run on this program This is actually a proof that every single input that could be given to this program stays within the sandbox So it's a super strong claim and It's great to have So vero asm was developed for an older version of crane lift And we're currently in the process of kind of rewriting it and extending it to work with all the kinds of wasm memory Configurations that wasm time currently supports now We're also verifying our instruction selection So instruction selection is the phase within the compiler where we go from kind of this high-level target independent form which we call cliff and crane lift case and we lower that to a architecture-specific form which we call vcode And this is kind of in the pipeline after we've run all the common target independent optimizations like global value numbering and Lupin variant code motion But it's before we do things like register allocation And so this is really when we're choosing exactly which machine instructions we're going to admit When we're doing instruction selection and crane lift we use this DSL called aisle and we designed aisle with an eye towards verification from the start But first here's an example so this is taking an I add cliff instruction and It's mapping it to an AR 64 add instruction. And so in this case, it's really simple. They're one to one But there are a ton of different special cases when we're doing instruction selection and each one doesn't really matter a ton But ultimately they come together and they really add up and it makes a big difference So for example, if one of our operands is a constant We might be able to fit that constant into the add instruction itself And this saves us code size it lowers register pressure and ultimately it leads to better optimized programs But we can only do this with certain constants They have to fit into this special 12-bit encoding that's specified by the air 64 instruction set And so though all of those constraints are what this top rule is describing It's saying if we match an I add where one of those operands is a constant And if we can fit that constant into the special 12-bit encoding Then we can use the special AR 64 add instruction that contains a constant 12-bit operand The bottom rule is kind of the same thing But for when we can turn an addition with a constant operand into a subtraction with the negated version of that operand But it can fit into the 12-bit encoding And so there are tons and tons of rules like this, but they get much more complicated than this even And the problem is Regardless of how complicated they are getting any of them subtly wrong could lead to a security vulnerability So it's really important that we get these correct And so as I mentioned, luckily we designed our DSL with an eye towards verification from the start So we have an ongoing collaboration to formally verify the correctness of our instruction selection This is proving that the machine instructions that we emit Do in fact actually implement the original target independent cliff that we were given And this is true for all values that they could be given It's it's a proof again And so if we write a buggy rule then the verifier will Tell us it's incorrect by giving us a counter example. So this is an input where For the original cliff it evaluates to one value And then for the lowered machine instructions that input evaluates to a different value And so the counter example and its divergent results make it really easy for us to kind of diagnose and fix the buggy rule So i'm happy to say that this work has been conditionally accepted for as plus 2024 At the same time, we've also refactored crane lifts middle end people optimizations to use The same isle dsl and to be expressed as rewrite rules So these are all the classic kind of target independent optimizations Like x plus zero can be rewritten to x x times one can also be rewritten to x Or what we have here kind of x times two can be rewritten to x left shift one Although this is the generic form that works for any power of two So rewriting these optimizations into isle lets us formally verify the correctness of these rewrite rules And so we can further shrink the amount of unverified code we have in our code base And we intend to basically keep applying this sort of process to the whole compiler pipeline and really pushing it as far as we can So specter is a class of attacks exploiting speculative execution and modern processors Speculative execution is when the processor kind of guesses where control is going to flow even though it hasn't actually computed branch conditions or indirect jump targets yet And it starts tentatively executing its guess And when the processor guesses correctly the speculations Executions results are used and this gives you a massive speed up to the program But when it guesses incorrectly it kind of has to discard that whole speculation Unfortunately, even discarded speculations can still affect the contents of caches and other internal process state And so an attacker can indirectly observe these effects by measuring the time it takes to perform operations that access that same internal state And so under the right conditions when the stars align this allows the attacker to kind of deduce what happened in the discarded speculative execution And effectively kind of see past Bounds checks and other software implemented security measures So it can be tempting to try and do things like add os process boundaries between everything This is a common mitigation But one of web assembly's most enticing features is that we can make it sandbox isolation Super lightweight lighter weight than an os process And as we talked about earlier that actually unlocks further security benefits like the disposable instances and isolation between tasks tasks Um Or you might be tempted to think like hey attackers need to have access to a timer to pull off a specter attack Can we simply remove timers? But it's surprisingly easy to find widgets that can be turned into timers actually there's this really cool paper called fantastic timers and where to find them That shows that really kind of seemingly completely benign apis can be turned into timers actually So you know kind of we're going too much further I want to highlight that the nature and severity of specter vulnerabilities is going to depend greatly on context And so these mitigations that we're going to talk about can form part of your overall protection But it's something you should really be thinking about kind of holistically so Wasm time implements a number of specter mitigations to prevent speculative execution from leaking information to malicious programs So we bound the bounds checks for tables and the call and distract in indirect instruction Have mitigations to basically prevent a call indirect from calling an arbitrary location in the process Under speculation The bird table instruction is protected from speculative attack which ensures that it can't transfer control to arbitrary places in the process under speculation And by default wasm times configuration for memories is going to use virtual memory guard pages to align Bounds checks for memories But you can disable this if you don't want to use that and then in that case we have to emit explicit bounds checks for every memory access And so when we do that then we also emit code that mitigates against specter And prevents wasm memory accesses from escaping the wasm linear memory And finally, we're also implementing support for hardware control flow integrity features, which can also help mitigate specter attacks Such as the bti extension for ar 64 So security researchers keep discovering new specter attacks and inventing better mitigations for them So this is sort of an evolving space and we're going to keep expanding and refining wasm time specter mitigations in the future as well so Even with all the things that we've discussed all of our safeguards to catch bugs before they're written and before they ship to production The reality is that bugs can still slip through No matter what you do So we have backup contingencies It begins with our published guidelines for reporting security bugs And our published disclosure policy And when we do get a report of a security bug We know that handling it is a very delicate matter and we don't want to make mistakes So we have a vulnerability response runbook Authored by pat over here. Thank you pat That we use to actually walk ourselves through responding to security bugs in the moment so we can make sure to like check boxes one by one And then once a patch is written We backport the security fix to the two most recent wasm time releases as per our documented release process And you know, I I could just say all of these things, but um, I want to tell you that these aren't just words and hollow promises We've exercised these safety nets before and flex these muscles When faced with security bugs like crane lift, miscompilations and incomplete stack maps for garbage collection in the past So I've talked about how we bolster wasm time security posture and the security postures of applications embedding wasm time or running in wasm time with things like language safety fine grained isolation dependency auditing ubiquitous fuzzing fuzzing fuzzing fuzzing fuzzing informal verification And we believe that these are really the minimum practices that you must demand from any web assembly run time when you're running untrusted or security sensitive code We're constantly trying to raise this bar and further strengthen wasm times security and correctness assurances And you don't have to use wasm time But you do have to make sure that whatever runtime you are using is doing these same things if you're running untrusted code And if you find that a runtime isn't obsessively fuzzed, for example, do not use it for these purposes Only use wasm run times that have these security practices Thank you