It's kind of depressing to see that a considerable portion of this depends on design decisions that probably seemed safe at the time but are wildly dangerous in hindsight.
Getting access to the privileged 'crosh' extension in this exploit depends on getting code into its process, which depends on the odd choice to put unrelated extensions into a shared extension process when there's too many processes. Given the insecure nature of the Chrome App Store, this was questionable to begin with - should a potentially backdoored bookmarking extension really share a process with my password safe? - but once privileged Chrome OS code started running in extension processes this immediately became a huge security hole waiting to be exploited.
In this case they also don't even need a compromised extension, because they can (if I understand this right) impersonate an extension's frame and then send messages of their choice to the extension. The access control here is almost entirely done inside the content process, where potentially compromised content is running. It's hard to avoid this since most extension code runs inside content processes by design, but it's a weakness that is rarely (if ever) called out in the chrome extension documentation.
The webassembly exploit part of the chain bums me out (I was always afraid of stuff like this when I was working on the design for it) but it's pretty uninteresting, really. The simple sort of bug you get when you insist on writing stuff in C++.
The parts of the chain after getting access to crosh also seem like tough-to-avoid oversights. This set of attacks definitely make symlinks look like a problem child, given how useful they are for all sorts of naughty behavior the attack gets up to :)
>The webassembly exploit part of the chain bums me out (I was always afraid of stuff like this when I was working on the design for it) but it's pretty uninteresting, really. The simple sort of bug you get when you insist on writing stuff in C++.
I really hope people don't think webassembly is the fault for this, this vulnerability is no different from any other memory corruption vulnerability you would find in the js interpreter or the css parser or whatever.
Well, WebAssembly's primary near term contribution will be introducing the world of C++ exploits to web apps, which are already groaning under the load of XSS, XSRF, path traversal, SSRF etc attacks. Adding double frees, use after frees and buffer overflows on top doesn't seem ideal.
As for the rest, well, it'd be nice if there was any sort of plan to make Blink safer. I know about oilpan but what Mozilla is doing with Rust is impressive. The JVM guys are working on rewriting the JVM in Java. What's Blink's plan to make its own code safer? Sandboxing alone?
I don't know enough about wasm to know if it has some special mitigations for this but when I looked at it, wasm seemed closer to a CPU emulation than a high level language VM. Flat memory space, no GC, no pointer maps.
WASM memory is a set of memory specific to a module (and they only allow one memory instance right now). It can be imported/exported to other modules, but there is no sandbox escape (in theory). For the web backend, it's just backed by a UInt8Array IIRC. It's all userland. If anything escapes the WASM interpreter/compiler, it is the fault of the interpreter/compiler (as is the case here) and not the fault of the WASM bytecode itself which has no escape mechanism. Think of a WASM VM just like a JS VM. Even though it may appear low level just because it can JIT better/cleaner, it operates in the same arena as JIT'd JS (at least for the web target).
You don't need to escape a sandbox when the application has access to all the user's data.
The attack surface of a gmail implemented in C++-compiled-to-wasm is almost certainly going to be larger than a gmail implemented in JS, because the runtime environment is vulnerable to double frees and heap corruption and other attacks, even if it won't escape the browser sandbox. My gmail tab basically has access to my entire life.
I don't understand. In the gmail example, the attack surface to who, a malicious email sender? As in something being handled by wasm in the browser has a better chance at XSS than if it was handled with JS? Why would untrusted content like that be handled by a client-side language anyways? Whether it is wasm, JS, wasm-interpreted-by-a-JS-interpreter, JS-interpreted-by-a-c++-intrpreter, wasm-interpreted-by-a-c++-interpreter or whatever the risks are similar. If you are talking about untrusted wasm or JS scripts accessing things inside the same sandbox, that's a different vector and it's less about the size of surface area and more about the introduction of the vector in the first place.
Simple example (though not something I think the Gmail team would actually ship): I want to load a .png file that's attached to an email. If I decide to use a build of libpng I control (for example, to work around broken color profile support in browsers), a bug in that libpng build could allow privilege escalation within the tab to get access to my gmail contacts or send emails. Bugs in loaders for image file formats are not unheard of, and people treat image files as innocuous.
Ah that example makes it clear. I'll ignore the obvious issue of libpng being written in JS having a similar problem. So the libpng WASM would have its memory instance (probably imported from the caller) and functions would reference the memory. It's not like with regular RAM where if you overflowed a mem write that it would write executable instructions. Code is different from memory. There is no eval. There is a "call_indirect" which can call a function by a dynamic function index, but what would a dangerous function that libpng imported? You can't execute memory or anything.
I can see some site DOSing though where you use the equivalent of a png-bomb to blow up CPU from the parser, but that is any not-meticulously written client-side parser of untrusted input.
So while you can toy with memory and maybe even affect the function pointer before a successive indirect call, it's not near as dangerous to the outside-of-wasm world as raw CPU instructions. I can see an issue where the caller that imports libpng and exports his memory to it might have something secret in that memory...hopefully multi-memory and GC-like-structs and what not can make passing and manipulating large blocks more like params than shared mem (and all of shared mem's faults).
It's a bit extreme but the reality is that a lot of production libraries tend to pull in imports that are as dangerous as eval, because the scope of the library is enormous, or it's actively supposed to interact with the DOM or JS. At that point, if someone can more trivially exploit it with a double free or buffer overflow, you've increased your security risk relative to JS (because overflowing a Uint8Array is basically never going to result in arbitrary function invocation)
The way function addresses are sequential in wasm tables (and deterministic) also means it is probably easier to get to the function you want once you get code execution.
WASM resulted in adding a lot of new API to JS, like thread-shared buffers and coming atomics. This requires quite a few new lines of native code in the implementation significantly increasing attack surface. Another thing is that WASM makes code faster so exploiting timing bugs or cache leaks gets easier.
My sibling comment is correct; the only way this can happen is an interpreter bug. Bugs happen, but they can happen in JS too. I think you’re assuming things the spec doesn’t allow.
"Controls your account" is possible without ever exploiting an interpreter bug or escaping the sandbox. Your account credentials are usually available inside the current tab.
The example I gave in another comment holds here: Let's say I want to load PNGs and I'm fed up with color profile bugs in browsers' image decoders (sigh...) so I decide to compile a known-good build of libpng or stb_image with wasm. Now someone finds a png decoder exploit that works against my build. If I'm not cautious about my imports, they can escalate privilege out of my wasm library and then take control over my gmail.
Ideally wasm libraries will always be narrowly scoped and good about what they import, but there will definitely be broadly scoped libraries that import a ton of dangerous stuff, and there will be some that import a function that is effectively eval because they don't want to declare a thousand imports by hand.
It's certainly possible for JS libraries to have these kinds of vulnerabilities, but it's hard for me to imagine how a JS PNG decoder would end up with the same sort of attack possible on it since it's parsing binary data into pixel buffers. At worst, you'd crash it.
Yeah, sorry about the duplication here, I'm extremely interested in this specific topic.
> Now someone finds a png decoder exploit that works against my build.
I think this is the part I don't get. Specifically, how would an exploit work within wasm? That is, in the wasm environment is different than in native; the memory is bounds checked, for example. Basically, I 100% agree that some security bugs are logic bugs, but take the above stack smash, for example: that can't happen, in my understanding. Again, modulo interpreter bugs, like any sandboxing technique.
> it's hard for me to imagine how a JS PNG decoder would end up with the same sort of attack possible on it since it's parsing binary data into pixel buffers. At worst, you'd crash it.
It's hard for me to imagine how wasm is any different than JS here.
How does wasm stop stack smashes? I can see that if it's not a von Neumann machine i.e. code is in a different memory space to data, it'd be harder, but that doesn't seem really compatible with C/C++?
Just in general if I have an arbitrary memory write primitive inside the wasm memory space, how much control over the program can I obtain?
wasm does some stack isolation so that the function's local stack variables are not next to things like the return value in memory and loads to/from stack slots are special instructions that reference slot identifiers. Most stack operations aren't arbitrary memory reads/writes from addresses so it's not possible to overflow them and corrupt other values.
The caveat is that not everything native apps put on the stack can currently be stored in wasm's safe stack, so applications often put a secondary stack inside their heap. This will also happen if you're - for example - passing large structs around as arguments. You can smash the heap stack if you manage to find an exploit, and if function pointers or other important data are stored there, you can turn that into an attack.
It's absolutely the case that a large subset of stack smashing attacks don't work on wasm, because of the safety properties. Some of them will still work though. The way function pointers work in wasm raises the risk profile a bit if you manage to get control over the value of a function pointer, since function pointer values are extremely easy to predict.
I am curious how JIT compilation policies will affect this. Wasm is a new bytecode form that has no mature JIT compilers. I wonder how many safety properties the compilers will assume. For instance if the wasm VM only really tries to stop wasm code escaping its own sandbox, then I guess compiling all stack ops down to a unified stack, C style, is a perfectly legitimate approach as long as the sandbox properties can be maintained. I don't think wasm is claiming it will make all C/C++ hackproof code.
Yeah, sorry for being unclear - that is what I meant. I don't see wasm as at fault here, it's just a bummer that this new attack surface was introduced by writing the wasm implementation in C++ instead of memory-safe languages. It's not something so complex that it really needs to be C++.
Most (all?) browser wasm backends function by just generating the internal IR used by the existing JS runtime, so it's not especially necessary to write the loader/generator in C++. The generated native module(s) are often cached, also, which diminishes the importance of making the generator fast at the cost of safety.
I wrote all the original encoder and decoder prototypes in JS for this reason - you can make it fast enough, and the browser already has a high-performance environment in which you can run that decoder. When the result is already being cached I think writing this in C++ is a dangerous premature optimization.
Similarly it's common to write decoders as a bunch of switch statements and bitpacking, which creates a lot of duplication and space for bugs to hide. You can build these things generally out of a smaller set of robust primitives to limit attack surface, but that wasn't done here either, despite my best efforts.
This is a text book example on why ChromeOS makes sense. Here this bug was found, fixed and because of the evergreen model deployed to actual people's machines and the users never even knew anything about it.
Not really, everyone is safe in a cage but in the real world people make tradeoffs.
Chrome OS is web centric, the bulk of users are desktop centric so it cannot be compared to desktop usage patterns.
Some developers tend to be extremist about updates, but given bug less software or a seamless disruption free upgrade cannot be guaranteed it's remains a better option to educate users rather than try to dis-empower and take control.
One of the security models built into ChromeOS is a well defined attack surface. It is not for general apps, it only runs Chrome (and things within an Android environment on newer machines). There is a comprehensive auditing and monitoring solution combination (Auditd & SELinux) that is hardened above what is normally possible on Linux desktops and servers.
When an audit violation is detected ChromeOS forces an update an reboot to the new version (which takes seconds and feels almost like you've been unexpectedly logged out). All without losing any data except for unsubmitted form contents. Even if an update isn't available, the writable part of the system (not user data) is completely wiped limiting the ability of malicious actors to persist anything.
It's not a panacea but it is way more aggressive and seamless than any of the major operating systems could possibly do.
That's quite impressive. It's the first I even consider how secure Chrome can be aside from "it runs on Linux, it should be safe!"
That aside, I wonder how much effort it takes to produce a hardened Ubuntu that behaves somewhat like ChromeOS, but you get to use any browser you want instead of just Chrome. Then you just run it under a tiling window manager (or minimal WM) to force focus on the browser, or on app at a time, while still having access to development tools (I guess the security aspect falls short here?) and maybe some office software.
My memory is that ChromeOS defaults to aggressively auto-updating itself, where macOS and Windows both remind / pester / offer to defer. So greener than evergreen?
Windows today installs updates automatically and reboots when it's the least convenient. For a normal user, it's near-impossible to make it stop, and most people just grudgingly accept the lost work and ruined movie nights.
You can google for a solution, but that solution only works on last year's version, the professional edition, or similar.
> Windows today installs updates automatically and reboots when it's the least convenient
I thought that was how it worked too having just switched back to having to use windows.
But you can pick a time of your choice and specify a day up to about 5 days in the future or something, so it's not as bad as I thought it was going to be.
Coming from Linux it still an annoying difference between the operating systems but not as bad as people tend to make out.
> But you can pick a time of your choice and specify a day up to about 5 days in the future or something, so it's not as bad as I thought it was going to be.
Only if you catch it while it's prompting.
I've seen people set up for a meeting/presentation/etc, test everything's working and then walk away from their computer to grab a drink. In that time, Windows has popped up a "Hey, we're going to perform updates" dialog which if you don't catch it before it disappears, becomes uncancellable/undeferrable. Depending on the update (eg Creators Edition) it then spends the next 20-45 minutes with the entire machine being unusable.
I've seen many panicky tweets from people who're about to give a talk in an hour or two that they've just opened their laptop and it's resumed into the dreaded "Oh, we're just updating now!" screen.
> But you can pick a time of your choice and specify a day up to about 5 days in the future or something, so it's not as bad as I thought it was going to be.
OTOH, when you do that there is no confirmation button on the UI, and the button placed and styled the way users expect a confirmation button to be is a “do the upgrade right now instead of at the time I just selected” button, so all is not roses and sunshine.
I can confirm that in my case windows installed an update without asking me at the worst possible moment.
It took a while to install and the only visible change (I am sure there were bugfixes as well) was the automatic installation of Edge & Cortana.
I don't know whether it was a bug or planned but it was very agressive.
With how often serious bugs ship to Chrome release channel, I don't think evergreen is implicitly a good thing in the OS that drives my computer, especially if it's my work PC.
If you look through the chromium issue tracker you can also find multiple bug reports where people were struggling to get their chromebooks to update to the latest release, because auto-update had broken. Some of those people were Google employees.
Been involved with many Chromebooks and never had a single issue with any updating. It just happens completely transparently.
Chromebooks have two boot partitions. So one updates while you are using the other. You then just pick up the update all ready to go next time you boot. You have zero idea that your laptop even updated.
Wow! That's really impressive. Seems like a _lot_ of work and ingenuity went into this.
It's great that large, corporate projects like Chrome OS are attracting the sustained attention necessary to find bugs such as this one. But I worry that projects without such deep pockets are crowded out, leaving bugs unreported. Are many people doing security audits of open source projects without bug bounties?
Google has been doing something close to bug bounties for many "critical" open source projects. Instead of focusing on bugs however, the Patch Rewards focuses on countermeasures: integrating a project into OSSFuzz, adding sandboxing, etc.
one avenue for many smaller projects (especially open source libraries) is to become a dependency of a huge project like chrome. then the larger project redirects some of their auditing efforts toward your project.
I wonder if s/he save a bunch of bugs to chain together a single big exploit. From the latest report, there was 6 bugs chained together.
Imagine reporting 6 small bugs individually that nets you 6 x 1000$ = 6k$. But if you save each one, it may chain together for a potential 100k$ bounty. Of course, any insight that reveals these underlying relations is most certainly worth 100k$.
AFAIUI (not a security researcher) that's actually how most of the most devastating security exploits work. There are obvious exceptions like HEARTBLEED, but in general escalation through multiple levels seems to be the name of the game.
If they care about security, they shouldn't pay more for chaining, because that gives security researchers incentive to hoard vulnerabilities, rather than report them ASAP.
From the outside, it feels that most exploit chains on modern systems rely on 4 mostly-independent steps:
1. code execution in a worker process - typically a memory corruption
2. ACE in worker process to ACE in unsandboxed process
3. code execution in unsandboxed process to local root
4. local root to persistence
Finding (1) (3) and (4) is old-school exploit development - a combination of looking at fuzzers, looking at code, looking at bug reports, and memory exploit development (which is a black art I'm not familiar with). So persistence and luck. Be lucky 3 times and you have 3 steps. If you were an organization I suppose you could have 3 separate groups or buy from 3 separate blackhats.
I'm less familiar with the "worker process to user process" part, which tends to rely on combining a few vulnerabilities (in this exploit, 2 + 1 broken hardening), but it's probably similar.
Getting access to the privileged 'crosh' extension in this exploit depends on getting code into its process, which depends on the odd choice to put unrelated extensions into a shared extension process when there's too many processes. Given the insecure nature of the Chrome App Store, this was questionable to begin with - should a potentially backdoored bookmarking extension really share a process with my password safe? - but once privileged Chrome OS code started running in extension processes this immediately became a huge security hole waiting to be exploited.
In this case they also don't even need a compromised extension, because they can (if I understand this right) impersonate an extension's frame and then send messages of their choice to the extension. The access control here is almost entirely done inside the content process, where potentially compromised content is running. It's hard to avoid this since most extension code runs inside content processes by design, but it's a weakness that is rarely (if ever) called out in the chrome extension documentation.
The webassembly exploit part of the chain bums me out (I was always afraid of stuff like this when I was working on the design for it) but it's pretty uninteresting, really. The simple sort of bug you get when you insist on writing stuff in C++.
The parts of the chain after getting access to crosh also seem like tough-to-avoid oversights. This set of attacks definitely make symlinks look like a problem child, given how useful they are for all sorts of naughty behavior the attack gets up to :)