Hello World! I recently decided to dive deeper into logic bugs, specifically within browser sandboxes. And here I am: in today's blog post, I'll try to explain what the browser sandbox is, what Site Isolation is, and how bugs arise in this context.

Erstwhile

I think the best way to start understanding is by beginning at the beginning, right?

While I was trying to understand how sandboxes actually worked, I stumbled upon a Google document. That was the beginning of it all. (https://research.google/pubs/analysis-of-uxss-exploits-and-mitigations-in-chromium/). Essentially, this Google article addresses and attempts to understand the causes that allow a compromised renderer to achieve UXSS, back when there were fewer mitigations in place.

Google research paper on UXSS exploits and mitigations in Chromium

Many of these bugs were caused by trivial issues, such as missing validation and things of that nature.

Breakdown of UXSS bug causes in Chromium

In the Chromium codebase, the browser would decide whether a javascript: URI could execute by checking the current execution context on the stack. If there was no active JS context, it assumed, "The HTML parser initiated this, so it's safe." Researchers realized this and managed to exploit it using tricks and DOM corruption techniques (e.g., forcing the parser to insert an <iframe> containing a cross-origin page from a subtree that wasn't attached to the document).

Diagram of the javascript URI parser bug

In other words, Google itself admitted that fixing bugs one by one wouldn't solve the problem; this led to the mitigations proposed in this paper, aimed at making this type of bug as costly to exploit as a full RCE.

But wait, what even IS a sandbox?

Okay, the paper kept saying things like "renderer process", "browser process", "sandbox". But what does that even mean? Before we talk about Site Isolation, we need to understand this first. Don't worry, it's easier than it sounds.

Modern browsers are not one single program. They are actually many programs running at the same time, talking to each other. For us, only two of them matter:

Now think about this: the renderer's whole job is to run code from random websites. Any website. Including bad ones. So the browser engineers accepted a sad truth: one day, someone will find a bug and take over the renderer. It's not "if", it's "when".

So what do you do if you know the worker will get hacked one day? You lock him in a room. That room is the sandbox. The renderer runs inside it, and it can't touch your files, can't install anything, can't talk to the OS directly. If the renderer wants to do something real, like save a file, it has to ask the browser process first. And the browser process checks if the request makes sense.

So even if an attacker fully owns the renderer, they should be stuck in the room. That's the idea.

Renderer locked in a sandbox room

This image you just saw? That's actually the fix.

Cool. The renderer is locked in a room. Problem solved, right?

Not really. Here is the part that made all those 63 bugs possible:
Back then, many different websites lived in the SAME renderer process.

Your Gmail and evil.com could be in the same process, at the same time. The only thing keeping them apart was the Same-Origin Policy. And remember what the SOP is: just code. A bunch of checks written by humans. And humans forget checks.

So when one of those logic bugs showed up, the attacker didn't need to break out of any room. Your Gmail's data was already in the same room as him. There was no wall to climb.

That's the big secret: the sandbox protected your computer from the renderer. But nothing protected one website from another website inside the same renderer. That was the real hole.

Multiple websites sharing one renderer process

And this is the problem Google decided to fix. Not by writing more checks. But by changing the whole architecture. Let's see how.

Site Isolation: one site, one process

So how do you fix a problem like this? Remember, the hole was: many websites living in the same renderer, and only code keeping them apart. And code has bugs. It always will.

Google's answer was beautiful because it's so simple: if the bug happens because two sites share a process, then never let two sites share a process.

That's Site Isolation. Every website gets its own renderer process. Only that website. Nothing else. Gmail gets a process. evil.com gets another process. They never mix.

Now look what happens to our 63 bugs. Imagine the attacker finds a shiny new logic bug and bypasses the Same-Origin Policy. He runs his code, tries to read your Gmail and... there's nothing there. The Gmail data doesn't exist in his process. It's in another process, far away, locked in a different room. The bug still works. The SOP is still broken. But the prize is gone.

That's the whole trick: you can't steal what isn't in the room.

The bug goes from "read the victim's emails" to "congratulations, you broke a check and won nothing". Remember the quote from the paper? That's exactly what they meant: the cost of a UXSS became almost the cost of a full RCE plus a sandbox escape. The most expensive thing in the exploit world.

Site Isolation: one site per renderer process

Quick timeline, because it didn't happen overnight:

And Firefox? Same idea, different name. They call it Fission, and it landed in 2021. Same rule: one site, one process.

So... problem solved forever? You already know the answer. It's never solved forever.

There are two cracks in this wall. First, "site" is not the same as "origin". Second, the renderer can still talk to the browser process. And if it can talk, it can lie.

Where the sandcastle crumbles

Before anything, credit where credit is due. Most of what I'm about to explain comes from two amazing articles by Masato Kinugawa: Deep Dive into Site Isolation (Part 1) and Part 2. When Site Isolation launched in 2018, he went hunting for bugs in it and found more than 10, earning $32k in bounties. If this section makes you curious, go read both. Seriously.

Now, remember how the last section ended: Site Isolation killed the classic UXSS. But it didn't kill the bugs. It just made them move.

Before Site Isolation, the bug was "the SOP check inside the renderer is wrong". After Site Isolation, the browser process has to decide everything: which process gets which site, which origin can ask for which cookie, which navigation is allowed. And how does the browser process know who is asking? The renderer tells him. Through IPC messages.

And here's the problem: Chrome's threat model now assumes the renderer can be fully compromised. A hacked renderer can read and write its own memory, which means it can fake any IPC message. It can lie about its origin, its URL, everything.

So the new game is simple: the renderer talks, and the browser process must decide what to believe. Every place where the browser process believes too much... that's a bug. The logic bugs didn't die. They moved from the DOM to the IPC.

After reading a lot of these bugs, I noticed they fall into a few classes. Let's go through them.

Class 1: The browser believes the origin the renderer claims

The most common one. A renderer sends an IPC saying "hi, I'm target.example, please do X". And somewhere in the browser process, nobody double checks that.

Class 2: The check exists, but in the wrong process

Sometimes the check is there. It's just running inside the renderer. Which is the process we already assume is evil. Oops.

The best example is registerProtocolHandler (Chromium). The API had three protections: the protocol must be in an allow-list, the destination URL must be same-origin, and the user must accept a prompt. Problem: the first two checks lived in the renderer, and the prompt calculated the origin from the (spoofable) destination URL. Masato chained all three, registered a data:text/html URL as a protocol handler, clickjacked a victim into clicking a tel: link, and got his script executing inside the victim's process. Fixed by moving the checks to the browser process.

The rule of thumb: a security check that runs in the renderer protects nothing. The renderer is the attacker.

Class 3: Process confusion, aka "the page landed in the wrong room"

Site Isolation's whole job is deciding which process hosts which page. So any bug in that decision is fatal. And the edge cases are where it breaks:

The pattern

Look at all three classes. They're the same bug wearing different clothes:

A piece of code received an identity claim from across a process boundary, and used it without checking.

Before Site Isolation, the claim was "this document has this origin" inside one process. After Site Isolation, the claim is "this IPC message comes from this site" across processes. Same mistake, new address. That's what I mean when I say logic bugs don't die. They migrate.

And if the bugs migrated to IPC...

Conclusion: castles made of sand

Let's recap the whole journey.

We started with 63 bugs that all had the same DNA: logic mistakes. Missing checks, wrong assumptions, code that trusted what it shouldn't trust. Google's own researchers read every single one of them and reached an uncomfortable conclusion: fixing them one by one would never be enough.

So they changed the game. Site Isolation took the Same-Origin Policy, which was just software checks inside a shared process, and turned it into real walls. One site, one process. You can't steal what isn't in the room.

But walls don't fix human nature. The checks still have to exist somewhere. They just moved to a new address: the IPC layer. And as we saw, every Site Isolation bypass is the same old bug wearing new clothes. A message crosses a process boundary carrying an identity claim, and somebody believes it without checking. The browser believes the renderer. The extension believes the content script. The GPU process believes the namespace. Same mistake, new address.

That's the real lesson of this post. Mitigations like Site Isolation don't kill bug classes. They raise the price. A UXSS used to cost one missing if. Now it costs a compromised renderer plus a clever lie told to the right process. The bugs are still there. They're just more expensive.

Jimi Hendrix said that castles made of sand slip into the sea, eventually. He was right. But here's what he didn't say: every time the sea takes the castle, we learn a little more about how to build the next one. And honestly? Watching how they fall is the fun part.

Thanks for reading. Now go build some sandcastles. Or better: go find where they crumble.

References